#![cfg_attr(not(test), allow(dead_code))]
#[inline(always)]
fn load_f16<const BE: bool>(plane: &[half::f16], i: usize) -> half::f16 {
let raw = plane[i];
if BE {
half::f16::from_bits(u16::from_be(raw.to_bits()))
} else {
half::f16::from_bits(u16::from_le(raw.to_bits()))
}
}
#[cfg_attr(not(feature = "std"), allow(dead_code))]
#[inline(always)]
pub(crate) fn widen_f16_be_to_host_f32<const BE: bool>(
src: &[half::f16],
offset: usize,
dst: &mut [f32],
n: usize,
) {
for i in 0..n {
let raw = src[offset + i].to_bits();
let host_bits = if BE {
u16::from_be(raw)
} else {
u16::from_le(raw)
};
dst[i] = half::f16::from_bits(host_bits).to_f32();
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn gbrpf16_to_rgb_f16_row<const BE: bool>(
g: &[half::f16],
b: &[half::f16],
r: &[half::f16],
rgb_out: &mut [half::f16],
width: usize,
) {
debug_assert!(g.len() >= width, "g row too short");
debug_assert!(b.len() >= width, "b row too short");
debug_assert!(r.len() >= width, "r row too short");
debug_assert!(rgb_out.len() >= width * 3, "rgb_out row too short");
for x in 0..width {
let dst = x * 3;
rgb_out[dst] = load_f16::<BE>(r, x);
rgb_out[dst + 1] = load_f16::<BE>(g, x);
rgb_out[dst + 2] = load_f16::<BE>(b, x);
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn gbrpf16_to_rgba_f16_row<const BE: bool>(
g: &[half::f16],
b: &[half::f16],
r: &[half::f16],
rgba_out: &mut [half::f16],
width: usize,
) {
debug_assert!(g.len() >= width, "g row too short");
debug_assert!(b.len() >= width, "b row too short");
debug_assert!(r.len() >= width, "r row too short");
debug_assert!(rgba_out.len() >= width * 4, "rgba_out row too short");
let one_f16 = half::f16::from_f32(1.0);
for x in 0..width {
let dst = x * 4;
rgba_out[dst] = load_f16::<BE>(r, x);
rgba_out[dst + 1] = load_f16::<BE>(g, x);
rgba_out[dst + 2] = load_f16::<BE>(b, x);
rgba_out[dst + 3] = one_f16;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn gbrapf16_to_rgba_f16_row<const BE: bool>(
g: &[half::f16],
b: &[half::f16],
r: &[half::f16],
a: &[half::f16],
rgba_out: &mut [half::f16],
width: usize,
) {
debug_assert!(g.len() >= width, "g row too short");
debug_assert!(b.len() >= width, "b row too short");
debug_assert!(r.len() >= width, "r row too short");
debug_assert!(a.len() >= width, "a row too short");
debug_assert!(rgba_out.len() >= width * 4, "rgba_out row too short");
for x in 0..width {
let dst = x * 4;
rgba_out[dst] = load_f16::<BE>(r, x);
rgba_out[dst + 1] = load_f16::<BE>(g, x);
rgba_out[dst + 2] = load_f16::<BE>(b, x);
rgba_out[dst + 3] = load_f16::<BE>(a, x);
}
}
#[cfg_attr(not(feature = "std"), expect(dead_code))]
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn copy_alpha_plane_f16<const BE: bool>(
alpha: &[half::f16],
rgba_out: &mut [half::f16],
width: usize,
) {
debug_assert!(alpha.len() >= width, "alpha plane too short");
debug_assert!(rgba_out.len() >= width * 4, "rgba_out too short");
for n in 0..width {
let raw = alpha[n].to_bits();
let host_bits = if BE {
u16::from_be(raw)
} else {
u16::from_le(raw)
};
rgba_out[n * 4 + 3] = half::f16::from_bits(host_bits);
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
fn as_le_f16(host: &[half::f16]) -> std::vec::Vec<half::f16> {
host
.iter()
.map(|v| half::f16::from_bits(u16::from_ne_bytes(v.to_bits().to_le_bytes())))
.collect()
}
fn as_be_f16(host: &[half::f16]) -> std::vec::Vec<half::f16> {
host
.iter()
.map(|v| half::f16::from_bits(u16::from_ne_bytes(v.to_bits().to_be_bytes())))
.collect()
}
fn ref_gbrpf16_to_rgb_f16(
g: &[half::f16],
b: &[half::f16],
r: &[half::f16],
width: usize,
) -> std::vec::Vec<half::f16> {
let mut out = std::vec![half::f16::ZERO; width * 3];
for x in 0..width {
let dst = x * 3;
out[dst] = r[x];
out[dst + 1] = g[x];
out[dst + 2] = b[x];
}
out
}
fn ref_gbrpf16_to_rgba_f16(
g: &[half::f16],
b: &[half::f16],
r: &[half::f16],
width: usize,
) -> std::vec::Vec<half::f16> {
let mut out = std::vec![half::f16::ZERO; width * 4];
let one = half::f16::from_f32(1.0);
for x in 0..width {
let dst = x * 4;
out[dst] = r[x];
out[dst + 1] = g[x];
out[dst + 2] = b[x];
out[dst + 3] = one;
}
out
}
fn ref_gbrapf16_to_rgba_f16(
g: &[half::f16],
b: &[half::f16],
r: &[half::f16],
a: &[half::f16],
width: usize,
) -> std::vec::Vec<half::f16> {
let mut out = std::vec![half::f16::ZERO; width * 4];
for x in 0..width {
let dst = x * 4;
out[dst] = r[x];
out[dst + 1] = g[x];
out[dst + 2] = b[x];
out[dst + 3] = a[x];
}
out
}
fn ref_copy_alpha_plane_f16(
intended_alpha: &[half::f16],
fill: half::f16,
width: usize,
) -> std::vec::Vec<half::f16> {
let mut out = std::vec![fill; width * 4];
for n in 0..width {
out[n * 4 + 3] = intended_alpha[n];
}
out
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrpf16_to_rgb_f16_channel_reorder() {
let g = [half::f16::from_f32(0.25)];
let b = [half::f16::from_f32(0.5)];
let r = [half::f16::from_f32(1.0)];
let mut out = vec![half::f16::ZERO; 3];
gbrpf16_to_rgb_f16_row::<false>(&g, &b, &r, &mut out, 1);
assert_eq!(out[0], half::f16::from_f32(1.0), "R");
assert_eq!(out[1], half::f16::from_f32(0.25), "G");
assert_eq!(out[2], half::f16::from_f32(0.5), "B");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrpf16_to_rgb_f16_hdr_preserved() {
let hdr = half::f16::from_f32(2.5);
let g = [hdr];
let b = [half::f16::from_f32(0.0)];
let r = [half::f16::from_f32(0.0)];
let mut out = vec![half::f16::ZERO; 3];
gbrpf16_to_rgb_f16_row::<false>(&g, &b, &r, &mut out, 1);
assert_eq!(out[1], hdr, "HDR G preserved bit-exact");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrpf16_to_rgb_f16_be_parity() {
let g_intended = [
half::f16::from_f32(0.0),
half::f16::from_f32(0.25),
half::f16::from_f32(0.5),
half::f16::from_f32(1.0),
];
let b_intended = [
half::f16::from_f32(0.1),
half::f16::from_f32(0.3),
half::f16::from_f32(0.7),
half::f16::from_f32(0.9),
];
let r_intended = [
half::f16::from_f32(0.5),
half::f16::from_f32(0.8),
half::f16::from_f32(0.2),
half::f16::from_f32(0.6),
];
let g_le = as_le_f16(&g_intended);
let b_le = as_le_f16(&b_intended);
let r_le = as_le_f16(&r_intended);
let g_be = as_be_f16(&g_intended);
let b_be = as_be_f16(&b_intended);
let r_be = as_be_f16(&r_intended);
let mut le_out = vec![half::f16::ZERO; 4 * 3];
let mut be_out = vec![half::f16::ZERO; 4 * 3];
gbrpf16_to_rgb_f16_row::<false>(&g_le, &b_le, &r_le, &mut le_out, 4);
gbrpf16_to_rgb_f16_row::<true>(&g_be, &b_be, &r_be, &mut be_out, 4);
let expected = ref_gbrpf16_to_rgb_f16(&g_intended, &b_intended, &r_intended, 4);
assert_eq!(le_out, expected, "LE path must match scalar reference");
assert_eq!(be_out, expected, "BE path must match scalar reference");
assert_eq!(be_out, le_out, "BE gbrpf16_to_rgb_f16_row must match LE");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrpf16_to_rgba_f16_alpha_is_one() {
let g = [half::f16::from_f32(0.5)];
let b = [half::f16::from_f32(0.5)];
let r = [half::f16::from_f32(0.5)];
let mut out = vec![half::f16::ZERO; 4];
gbrpf16_to_rgba_f16_row::<false>(&g, &b, &r, &mut out, 1);
assert_eq!(out[3], half::f16::from_f32(1.0), "alpha must be f16(1.0)");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrpf16_to_rgba_f16_be_parity() {
let g_intended = [
half::f16::from_f32(0.0),
half::f16::from_f32(0.25),
half::f16::from_f32(0.5),
half::f16::from_f32(1.0),
];
let b_intended = [
half::f16::from_f32(0.1),
half::f16::from_f32(0.3),
half::f16::from_f32(0.7),
half::f16::from_f32(0.9),
];
let r_intended = [
half::f16::from_f32(0.5),
half::f16::from_f32(0.8),
half::f16::from_f32(0.2),
half::f16::from_f32(0.6),
];
let g_le = as_le_f16(&g_intended);
let b_le = as_le_f16(&b_intended);
let r_le = as_le_f16(&r_intended);
let g_be = as_be_f16(&g_intended);
let b_be = as_be_f16(&b_intended);
let r_be = as_be_f16(&r_intended);
let mut le_out = vec![half::f16::ZERO; 4 * 4];
let mut be_out = vec![half::f16::ZERO; 4 * 4];
gbrpf16_to_rgba_f16_row::<false>(&g_le, &b_le, &r_le, &mut le_out, 4);
gbrpf16_to_rgba_f16_row::<true>(&g_be, &b_be, &r_be, &mut be_out, 4);
let expected = ref_gbrpf16_to_rgba_f16(&g_intended, &b_intended, &r_intended, 4);
assert_eq!(le_out, expected, "LE path must match scalar reference");
assert_eq!(be_out, expected, "BE path must match scalar reference");
assert_eq!(be_out, le_out, "BE gbrpf16_to_rgba_f16_row must match LE");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrapf16_to_rgba_f16_source_alpha_passthrough() {
let g = [half::f16::from_f32(0.25)];
let b = [half::f16::from_f32(0.5)];
let r = [half::f16::from_f32(0.75)];
let a = [half::f16::from_f32(0.9)];
let mut out = vec![half::f16::ZERO; 4];
gbrapf16_to_rgba_f16_row::<false>(&g, &b, &r, &a, &mut out, 1);
assert_eq!(out[0], half::f16::from_f32(0.75), "R");
assert_eq!(out[1], half::f16::from_f32(0.25), "G");
assert_eq!(out[2], half::f16::from_f32(0.5), "B");
assert_eq!(out[3], half::f16::from_f32(0.9), "A from source");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn gbrapf16_to_rgba_f16_be_parity() {
let g_intended = [
half::f16::from_f32(0.0),
half::f16::from_f32(0.25),
half::f16::from_f32(0.5),
half::f16::from_f32(1.0),
];
let b_intended = [
half::f16::from_f32(0.1),
half::f16::from_f32(0.3),
half::f16::from_f32(0.7),
half::f16::from_f32(0.9),
];
let r_intended = [
half::f16::from_f32(0.5),
half::f16::from_f32(0.8),
half::f16::from_f32(0.2),
half::f16::from_f32(0.6),
];
let a_intended = [
half::f16::from_f32(0.2),
half::f16::from_f32(0.4),
half::f16::from_f32(0.6),
half::f16::from_f32(0.8),
];
let g_le = as_le_f16(&g_intended);
let b_le = as_le_f16(&b_intended);
let r_le = as_le_f16(&r_intended);
let a_le = as_le_f16(&a_intended);
let g_be = as_be_f16(&g_intended);
let b_be = as_be_f16(&b_intended);
let r_be = as_be_f16(&r_intended);
let a_be = as_be_f16(&a_intended);
let mut le_out = vec![half::f16::ZERO; 4 * 4];
let mut be_out = vec![half::f16::ZERO; 4 * 4];
gbrapf16_to_rgba_f16_row::<false>(&g_le, &b_le, &r_le, &a_le, &mut le_out, 4);
gbrapf16_to_rgba_f16_row::<true>(&g_be, &b_be, &r_be, &a_be, &mut be_out, 4);
let expected = ref_gbrapf16_to_rgba_f16(&g_intended, &b_intended, &r_intended, &a_intended, 4);
assert_eq!(le_out, expected, "LE path must match scalar reference");
assert_eq!(be_out, expected, "BE path must match scalar reference");
assert_eq!(be_out, le_out, "BE gbrapf16_to_rgba_f16_row must match LE");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn copy_alpha_plane_f16_only_writes_alpha_slot() {
let host_alpha = [half::f16::from_f32(0.7), half::f16::from_f32(0.3)];
let alpha: std::vec::Vec<half::f16> = host_alpha
.iter()
.map(|v| half::f16::from_bits(u16::from_ne_bytes(v.to_bits().to_le_bytes())))
.collect();
let sentinel = half::f16::from_f32(0.1);
let mut rgba = vec![sentinel; 8];
copy_alpha_plane_f16::<false>(&alpha, &mut rgba, 2);
assert_eq!(rgba[0], sentinel, "R slot 0 untouched");
assert_eq!(rgba[1], sentinel, "G slot 0 untouched");
assert_eq!(rgba[2], sentinel, "B slot 0 untouched");
assert_eq!(rgba[3], half::f16::from_f32(0.7), "A slot 0");
assert_eq!(rgba[4], sentinel, "R slot 1 untouched");
assert_eq!(rgba[5], sentinel, "G slot 1 untouched");
assert_eq!(rgba[6], sentinel, "B slot 1 untouched");
assert_eq!(rgba[7], half::f16::from_f32(0.3), "A slot 1");
}
#[test]
#[cfg_attr(
miri,
ignore = "half::f16 uses inline assembly on aarch64 unsupported by Miri"
)]
fn copy_alpha_plane_f16_be_parity_with_swapped_buffer() {
let intended = vec![
half::f16::from_f32(0.0),
half::f16::from_f32(0.25),
half::f16::from_f32(0.5),
half::f16::from_f32(1.0),
half::f16::from_f32(2.5),
half::f16::from_f32(-1.0),
];
let alpha_le = as_le_f16(&intended);
let alpha_be = as_be_f16(&intended);
let mut rgba_le = vec![half::f16::ZERO; 24];
let mut rgba_be = vec![half::f16::ZERO; 24];
copy_alpha_plane_f16::<false>(&alpha_le, &mut rgba_le, 6);
copy_alpha_plane_f16::<true>(&alpha_be, &mut rgba_be, 6);
let expected = ref_copy_alpha_plane_f16(&intended, half::f16::ZERO, 6);
let bits_le: std::vec::Vec<u16> = rgba_le.iter().map(|v| v.to_bits()).collect();
let bits_be: std::vec::Vec<u16> = rgba_be.iter().map(|v| v.to_bits()).collect();
let bits_expected: std::vec::Vec<u16> = expected.iter().map(|v| v.to_bits()).collect();
assert_eq!(
bits_le, bits_expected,
"LE path must match scalar reference"
);
assert_eq!(
bits_be, bits_expected,
"BE path must match scalar reference"
);
assert_eq!(
bits_le, bits_be,
"BE flag + bit-swapped buffer must match LE path bit-for-bit"
);
}
}