diff options
Diffstat (limited to 'src/hap.rs')
| -rw-r--r-- | src/hap.rs | 162 |
1 files changed, 162 insertions, 0 deletions
diff --git a/src/hap.rs b/src/hap.rs new file mode 100644 index 0000000..2189d7b --- /dev/null +++ b/src/hap.rs @@ -0,0 +1,162 @@ +//! 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<HapFormat, HapError> { + 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<u8>, +) -> 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<HapFormat, HapError> { + 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) +} |
