use crate::error::Error;
pub fn decode_bc6h_into(
data: &[u8],
width: u32,
height: u32,
signed: bool,
out: &mut [f32],
) -> Result<(), Error> {
let (blocks_x, blocks_y, w, h) = validate(data, width, height)?;
if out.len() != w.checked_mul(h).and_then(|n| n.checked_mul(4)).ok_or(Error::OutOfBounds)? {
return Err(Error::OutOfBounds);
}
let mut scratch = [0u16; 4 * 4 * 3];
let mut fscratch = [0f32; 4 * 4 * 3];
for (by, brow) in data.chunks_exact(16 * blocks_x).take(blocks_y).enumerate() {
for (bx, blk) in brow.chunks_exact(16).enumerate() {
if !bc6h_mode11_half(blk, &mut scratch, signed) {
let mut ilv = [0u16; 4 * 4 * 3];
bcdec_rs::bc6h_half(blk, &mut ilv, 4 * 3, signed);
for p in 0..16usize {
for ch in 0..3usize {
scratch[ch * 16 + p] = ilv[p * 3 + ch];
}
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
let px0 = bx * 4;
let py0 = by * 4;
if py0 + 4 <= h && px0 + 4 <= w {
let d = (py0 * w + px0) * 4;
let pitch = w * 4;
if unsafe {
crate::decode::simd::bc6h_planar_to_rgba(
&scratch,
out.as_mut_ptr().add(d),
pitch,
)
} {
continue;
}
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
let converted = crate::decode::simd::half48_to_f32(&scratch, &mut fscratch);
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
let converted = false;
if !converted {
for k in 0..4 * 4 * 3 {
fscratch[k] = half_to_f32(scratch[k]);
}
}
let px0 = bx * 4;
let py0 = by * 4;
for row in 0..4 {
let y = py0 + row;
if y >= h {
break;
}
let n = (w - px0).min(4);
let s = row * 4;
let d = (y * w + px0) * 4;
if n == 4 {
let px = [
fscratch[s],
fscratch[16 + s],
fscratch[32 + s],
1.0,
fscratch[s + 1],
fscratch[17 + s],
fscratch[33 + s],
1.0,
fscratch[s + 2],
fscratch[18 + s],
fscratch[34 + s],
1.0,
fscratch[s + 3],
fscratch[19 + s],
fscratch[35 + s],
1.0,
];
out[d..d + 16].copy_from_slice(&px);
} else {
for i in 0..n {
out[d + i * 4] = fscratch[s + i];
out[d + i * 4 + 1] = fscratch[16 + s + i];
out[d + i * 4 + 2] = fscratch[32 + s + i];
out[d + i * 4 + 3] = 1.0;
}
}
}
}
}
Ok(())
}
const BC6H_W4: [i32; 16] = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
#[inline]
fn bc6h_mode11_half(blk: &[u8], out: &mut [u16; 4 * 4 * 3], signed: bool) -> bool {
if signed || blk[0] & 0x1f != 0x03 {
return false;
}
let Ok(bytes) = <[u8; 16]>::try_from(&blk[..16]) else {
return false;
};
let b = u128::from_le_bytes(bytes);
let f = |sh: u32| ((b >> sh) & 0x3ff) as i32;
let uq = |v: i32| {
if v == 0 {
0
} else if v == 1023 {
0xFFFF
} else {
((v << 16) + 0x8000) >> 10
}
};
let a = [uq(f(5)), uq(f(15)), uq(f(25))];
let c = [uq(f(35)), uq(f(45)), uq(f(55))];
let base = [a[0] * 64 + 32, a[1] * 64 + 32, a[2] * 64 + 32];
let delta = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let idx = (b >> 65) as u64;
let mut w = [0i32; 16];
w[0] = BC6H_W4[(idx & 0x7) as usize];
for (p, wp) in w.iter_mut().enumerate().skip(1) {
*wp = BC6H_W4[((idx >> (3 + (p - 1) * 4)) & 0xf) as usize];
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
if crate::decode::simd::bc6h_interp_avx2(&base, &delta, &w, out) {
return true;
}
for (p, &wp) in w.iter().enumerate() {
for ch in 0..3 {
let v = (base[ch] + wp * delta[ch]) >> 6;
out[ch * 16 + p] = ((v * 31) >> 6) as u16;
}
}
true
}
#[cfg(test)]
mod mode11_tests {
use super::bc6h_mode11_half;
#[test]
fn mode11_matches_the_general_decoder() {
let mut state = 0x6bc6_1111_2222_3333u64;
let mut next = move || {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
state
};
for case in 0..40_000 {
let mut blk = [0u8; 16];
match case {
0 => {}
1 => blk.iter_mut().for_each(|x| *x = 0xff),
_ => {
blk[..8].copy_from_slice(&next().to_le_bytes());
blk[8..].copy_from_slice(&next().to_le_bytes());
}
}
blk[0] = (blk[0] & !0x1f) | 0x03;
let mut ours = [0u16; 4 * 4 * 3];
assert!(bc6h_mode11_half(&blk, &mut ours, false), "case {case}");
let mut ilv = [0u16; 4 * 4 * 3];
bcdec_rs::bc6h_half(&blk, &mut ilv, 4 * 3, false);
let mut theirs = [0u16; 4 * 4 * 3];
for p in 0..16usize {
for ch in 0..3usize {
theirs[ch * 16 + p] = ilv[p * 3 + ch];
}
}
assert_eq!(ours, theirs, "case {case}: block {blk:02x?}");
}
}
#[test]
fn other_modes_and_signed_are_declined() {
let mut out = [0u16; 4 * 4 * 3];
let mut blk = [0u8; 16];
blk[0] = 0x03;
assert!(bc6h_mode11_half(&blk, &mut out, false));
assert!(!bc6h_mode11_half(&blk, &mut out, true));
for m in 0..32u8 {
if m == 0x03 {
continue;
}
blk[0] = m;
assert!(!bc6h_mode11_half(&blk, &mut out, false), "mode field {m:#07b}");
}
}
}
fn validate(data: &[u8], width: u32, height: u32) -> Result<(usize, usize, usize, usize), Error> {
if width == 0 || height == 0 {
return Err(Error::InvalidField("zero image dimension".into()));
}
let blocks_x = (width as usize + 3) / 4;
let blocks_y = (height as usize + 3) / 4;
let expected = blocks_x
.checked_mul(blocks_y)
.and_then(|n| n.checked_mul(16))
.ok_or(Error::OutOfBounds)?;
if data.len() < expected {
return Err(Error::TruncatedData);
}
Ok((blocks_x, blocks_y, width as usize, height as usize))
}
pub fn decode_bc6h(
data: &[u8],
width: u32,
height: u32,
signed: bool,
) -> Result<Vec<f32>, Error> {
let (_, _, w, h) = validate(data, width, height)?;
let mut out = vec![
0f32;
w.checked_mul(h)
.and_then(|n| n.checked_mul(4))
.ok_or(Error::OutOfBounds)?
];
decode_bc6h_into(data, width, height, signed, &mut out)?;
Ok(out)
}
#[inline(always)]
pub(super) fn half_to_f32(h: u16) -> f32 {
const SHIFTED_EXP: u32 = 0x7c00 << 13;
let h = h as u32;
let sign = (h & 0x8000) << 16;
let mut o = (h & 0x7fff) << 13;
let exp = o & SHIFTED_EXP;
o += (127 - 15) << 23;
o += ((exp == SHIFTED_EXP) as u32) * ((128 - 16) << 23);
let magic = f32::from_bits(113 << 23);
let denorm = (f32::from_bits(o + (1 << 23)) - magic).to_bits();
let is_denorm = 0u32.wrapping_sub((exp == 0) as u32);
let o = (denorm & is_denorm) | (o & !is_denorm);
f32::from_bits(o | sign)
}
#[cfg(test)]
mod tests {
use super::half_to_f32;
#[test]
fn half_to_f32_matches_reference_for_every_bit_pattern() {
for bits in 0..=u16::MAX {
let ours = half_to_f32(bits);
let theirs = reference(bits);
if ours.is_nan() && theirs.is_nan() {
continue;
}
assert_eq!(
ours.to_bits(),
theirs.to_bits(),
"half {bits:#06x}: {ours} != {theirs}"
);
}
}
fn reference(half: u16) -> f32 {
let magic = f32::from_bits(113 << 23);
let shifted_exp = 0x7c00 << 13;
let mut o = (half as u32 & 0x7fff) << 13;
let exp = shifted_exp & o;
o += (127 - 15) << 23;
if exp == shifted_exp {
o += (128 - 16) << 23;
} else if exp == 0 {
o += 1 << 23;
o = (f32::from_bits(o) - magic).to_bits();
}
o |= (half as u32 & 0x8000) << 16;
f32::from_bits(o)
}
}