//! Safe Rust wrapper around the HAP codec C library. //! //! HAP is a video codec that stores GPU-compressed DXT/BC texture data //! (optionally Snappy-compressed). This module decodes HAP frames into //! raw BC block data suitable for direct GPU upload. #[allow(non_upper_case_globals)] #[allow(non_camel_case_types)] #[allow(non_snake_case)] #[allow(dead_code)] mod ffi { include!(concat!(env!("OUT_DIR"), "/hap_bindings.rs")); } use std::fmt; /// Compressed texture format produced by HAP decoding. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum HapFormat { /// DXT1 / BC1 — 4 bits per pixel, RGB with 1-bit alpha Bc1, /// DXT5 / BC3 — 8 bits per pixel, RGB with full alpha Bc3, } impl HapFormat { /// Returns the corresponding wgpu texture format. pub fn wgpu_format(self) -> wgpu::TextureFormat { match self { HapFormat::Bc1 => wgpu::TextureFormat::Bc1RgbaUnorm, HapFormat::Bc3 => wgpu::TextureFormat::Bc3RgbaUnorm, } } /// Bytes per 4x4 block for this format. pub fn block_bytes(self) -> u32 { match self { HapFormat::Bc1 => 8, HapFormat::Bc3 => 16, } } /// Compute the byte size of a compressed image with the given pixel dimensions. pub fn compressed_size(self, width: u32, height: u32) -> usize { let blocks_x = width.div_ceil(4); let blocks_y = height.div_ceil(4); (blocks_x * blocks_y * self.block_bytes()) as usize } } impl fmt::Display for HapFormat { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { HapFormat::Bc1 => write!(f, "BC1/DXT1"), HapFormat::Bc3 => write!(f, "BC3/DXT5"), } } } /// Error type for HAP decoding operations. #[derive(Debug)] pub enum HapError { BadArguments, BufferTooSmall, BadFrame, InternalError, UnsupportedFormat(u32), } impl fmt::Display for HapError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { HapError::BadArguments => write!(f, "HAP: bad arguments"), HapError::BufferTooSmall => write!(f, "HAP: buffer too small"), HapError::BadFrame => write!(f, "HAP: bad frame data"), HapError::InternalError => write!(f, "HAP: internal error"), HapError::UnsupportedFormat(fmt) => { write!(f, "HAP: unsupported texture format 0x{fmt:04x}") } } } } impl std::error::Error for HapError {} fn result_from_code(code: u32) -> Result<(), HapError> { match code { 0 => Ok(()), 1 => Err(HapError::BadArguments), 2 => Err(HapError::BufferTooSmall), 3 => Err(HapError::BadFrame), _ => Err(HapError::InternalError), } } fn format_from_raw(raw: u32) -> Result { match raw { ffi::HapTextureFormat_HapTextureFormat_RGB_DXT1 => Ok(HapFormat::Bc1), ffi::HapTextureFormat_HapTextureFormat_RGBA_DXT5 => Ok(HapFormat::Bc3), other => Err(HapError::UnsupportedFormat(other)), } } /// Callback passed to HapDecode for multi-threaded chunk decoding. /// We decode sequentially (sufficient for our use case). unsafe extern "C" fn decode_callback( function: ffi::HapDecodeWorkFunction, p: *mut std::os::raw::c_void, count: std::os::raw::c_uint, _info: *mut std::os::raw::c_void, ) { if let Some(func) = function { for i in 0..count { func(p, i); } } } /// Decode a HAP video packet into compressed BC texture data. /// /// `packet_data` is the raw packet data from ffmpeg. /// `output_buf` is a reusable buffer that will be resized as needed. /// /// Returns the format and the number of valid bytes written to `output_buf`. pub fn decode( packet_data: &[u8], output_buf: &mut Vec, ) -> Result<(HapFormat, usize), HapError> { let mut output_length: u64 = 0; let mut raw_format: u32 = 0; let result = unsafe { ffi::HapDecode( packet_data.as_ptr() as *const std::os::raw::c_void, packet_data.len() as u64, 0, // texture index (always 0 for single-texture HAP) Some(decode_callback), std::ptr::null_mut(), output_buf.as_mut_ptr() as *mut std::os::raw::c_void, output_buf.len() as u64, &mut output_length, &mut raw_format, ) }; result_from_code(result)?; let format = format_from_raw(raw_format)?; Ok((format, output_length as usize)) } /// Detect the texture format of a HAP frame without fully decoding it. pub fn get_frame_format(packet_data: &[u8]) -> Result { let mut raw_format: u32 = 0; let result = unsafe { ffi::HapGetFrameTextureFormat( packet_data.as_ptr() as *const std::os::raw::c_void, packet_data.len() as u64, 0, &mut raw_format, ) }; result_from_code(result)?; format_from_raw(raw_format) }