summaryrefslogtreecommitdiffstats
path: root/src/hap.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/hap.rs')
-rw-r--r--src/hap.rs162
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)
+}