summaryrefslogtreecommitdiffstats
path: root/src/hap.rs
blob: 218942099b177cefcbe97872397031eaa7a5100b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! 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)
}