use super::*;
pub(super) fn solid_v210_frame(width: u32, height: u32, y: u16, u: u16, v: u16) -> Vec<u8> {
let words_per_row = width.div_ceil(6) as usize;
let mut buf = std::vec![0u8; words_per_row * 16 * height as usize];
let samples: [u16; 12] = [u, y, v, y, u, y, v, y, u, y, v, y];
let word = pack_v210_word_for_test(samples);
for row in 0..height as usize {
for w in 0..words_per_row {
let off = (row * words_per_row + w) * 16;
buf[off..off + 16].copy_from_slice(&word);
}
}
buf
}
fn pack_v210_word_for_test(samples: [u16; 12]) -> [u8; 16] {
let mut out = [0u8; 16];
let pack = |a: u16, b: u16, c: u16| -> u32 {
(a as u32 & 0x3FF) | ((b as u32 & 0x3FF) << 10) | ((c as u32 & 0x3FF) << 20)
};
out[0..4].copy_from_slice(&pack(samples[0], samples[1], samples[2]).to_le_bytes());
out[4..8].copy_from_slice(&pack(samples[3], samples[4], samples[5]).to_le_bytes());
out[8..12].copy_from_slice(&pack(samples[6], samples[7], samples[8]).to_le_bytes());
out[12..16].copy_from_slice(&pack(samples[9], samples[10], samples[11]).to_le_bytes());
out
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_luma_only_extracts_y_bytes() {
let buf = solid_v210_frame(6, 8, 200, 512, 512); let src = V210Frame::new(&buf, 6, 8, 16);
let mut luma = std::vec![0u8; 6 * 8];
let mut sink = MixedSinker::<V210>::new(6, 8).with_luma(&mut luma).unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
assert!(luma.iter().all(|&y| y == 50), "luma {luma:?}");
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_luma_u16_only_extracts_y_native_depth() {
let buf = solid_v210_frame(6, 8, 200, 512, 512);
let src = V210Frame::new(&buf, 6, 8, 16);
let mut luma = std::vec![0u16; 6 * 8];
let mut sink = MixedSinker::<V210>::new(6, 8)
.with_luma_u16(&mut luma)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
assert!(luma.iter().all(|&y| y == 200), "luma_u16 {:?}", &luma[..16]);
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_rgb_only_converts_gray_to_gray() {
let buf = solid_v210_frame(12, 4, 512, 512, 512);
let src = V210Frame::new(&buf, 12, 4, 32);
let mut rgb = std::vec![0u8; 12 * 4 * 3];
let mut sink = MixedSinker::<V210>::new(12, 4).with_rgb(&mut rgb).unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
for px in rgb.chunks(3) {
assert!(px[0].abs_diff(128) <= 1);
assert_eq!(px[0], px[1]);
assert_eq!(px[1], px[2]);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_rgba_only_converts_gray_to_gray_with_opaque_alpha() {
let buf = solid_v210_frame(12, 4, 512, 512, 512);
let src = V210Frame::new(&buf, 12, 4, 32);
let mut rgba = std::vec![0u8; 12 * 4 * 4];
let mut sink = MixedSinker::<V210>::new(12, 4)
.with_rgba(&mut rgba)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
for px in rgba.chunks(4) {
assert_eq!(px[3], 0xFF);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_rgb_u16_only_converts_gray_to_gray_native_depth() {
let buf = solid_v210_frame(12, 4, 512, 512, 512);
let src = V210Frame::new(&buf, 12, 4, 32);
let mut rgb = std::vec![0u16; 12 * 4 * 3];
let mut sink = MixedSinker::<V210>::new(12, 4)
.with_rgb_u16(&mut rgb)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
for px in rgb.chunks(3) {
assert!(px[0].abs_diff(512) <= 2, "expected ~512, got {}", px[0]);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_rgba_u16_alpha_is_max() {
let buf = solid_v210_frame(12, 4, 512, 512, 512);
let src = V210Frame::new(&buf, 12, 4, 32);
let mut rgba = std::vec![0u16; 12 * 4 * 4];
let mut sink = MixedSinker::<V210>::new(12, 4)
.with_rgba_u16(&mut rgba)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
for px in rgba.chunks(4) {
assert_eq!(px[3], 1023);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_with_rgb_and_with_rgba_byte_identical_u8() {
let w = 12u32;
let h = 4u32;
let buf = solid_v210_frame(w, h, 700, 400, 600);
let src = V210Frame::new(&buf, w, h, (w / 6) * 16);
let mut rgb = std::vec![0u8; (w * h) as usize * 3];
let mut rgba = std::vec![0u8; (w * h) as usize * 4];
let mut sink = MixedSinker::<V210>::new(w as usize, h as usize)
.with_rgb(&mut rgb)
.unwrap()
.with_rgba(&mut rgba)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
for i in 0..(w * h) as usize {
assert_eq!(rgba[i * 4], rgb[i * 3]);
assert_eq!(rgba[i * 4 + 1], rgb[i * 3 + 1]);
assert_eq!(rgba[i * 4 + 2], rgb[i * 3 + 2]);
assert_eq!(rgba[i * 4 + 3], 0xFF);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_with_rgb_u16_and_with_rgba_u16_byte_identical() {
let w = 12u32;
let h = 4u32;
let buf = solid_v210_frame(w, h, 700, 400, 600);
let src = V210Frame::new(&buf, w, h, (w / 6) * 16);
let mut rgb = std::vec![0u16; (w * h) as usize * 3];
let mut rgba = std::vec![0u16; (w * h) as usize * 4];
let mut sink = MixedSinker::<V210>::new(w as usize, h as usize)
.with_rgb_u16(&mut rgb)
.unwrap()
.with_rgba_u16(&mut rgba)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt601, &mut sink).unwrap();
for i in 0..(w * h) as usize {
assert_eq!(rgba[i * 4], rgb[i * 3]);
assert_eq!(rgba[i * 4 + 1], rgb[i * 3 + 1]);
assert_eq!(rgba[i * 4 + 2], rgb[i * 3 + 2]);
assert_eq!(rgba[i * 4 + 3], 1023);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_with_simd_false_matches_with_simd_true() {
for w in [
2usize, 4, 6, 8, 10, 12, 14, 18, 24, 30, 1280, 1920, 1922, 1926,
] {
let h = 2usize;
let mut buf = std::vec![0u8; w.div_ceil(6) * 16 * h];
pseudo_random_u8(&mut buf, 0xC0FFEE);
for i in (0..buf.len()).step_by(4) {
buf[i + 3] &= 0x3F;
}
let src = V210Frame::new(&buf, w as u32, h as u32, (w.div_ceil(6) * 16) as u32);
let mut rgb_simd = std::vec![0u8; w * h * 3];
let mut rgb_scalar = std::vec![0u8; w * h * 3];
let mut sink_simd = MixedSinker::<V210>::new(w, h)
.with_rgb(&mut rgb_simd)
.unwrap();
let mut sink_scalar = MixedSinker::<V210>::new(w, h)
.with_rgb(&mut rgb_scalar)
.unwrap()
.with_simd(false);
v210_to(&src, false, ColorMatrix::Bt709, &mut sink_simd).unwrap();
v210_to(&src, false, ColorMatrix::Bt709, &mut sink_scalar).unwrap();
assert_eq!(rgb_simd, rgb_scalar, "V210 SIMD≠scalar at width {w}");
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_partial_word_width_works_end_to_end() {
let buf = solid_v210_frame(1280, 1, 512, 512, 512);
let stride = 1280u32.div_ceil(6) * 16;
let src = V210Frame::new(&buf, 1280, 1, stride);
let mut rgb = std::vec![0u8; 1280 * 3];
let mut sink = MixedSinker::<V210>::new(1280, 1)
.with_rgb(&mut rgb)
.unwrap();
v210_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for px in rgb.chunks(3) {
assert!(
px[0].abs_diff(128) <= 1,
"partial-word RGB diverged: {px:?}"
);
assert_eq!(px[0], px[1]);
assert_eq!(px[1], px[2]);
}
}
#[test]
fn v210_luma_u16_buffer_too_short_returns_err() {
let mut luma = std::vec![0u16; 6 * 7];
let result = MixedSinker::<V210>::new(6, 8).with_luma_u16(&mut luma);
let Err(err) = result else {
panic!("expected InsufficientLumaU16Buffer");
};
assert_eq!(
err,
MixedSinkerError::InsufficientLumaU16Buffer(InsufficientBuffer::new(48, 42))
);
}
pub(super) fn pack_yuv422p10_to_v210(
y: &[u16],
u: &[u16],
v: &[u16],
width: usize,
height: usize,
) -> Vec<u8> {
let cw = width / 2;
let words_per_row = width / 6;
let mut out = std::vec![0u8; words_per_row * 16 * height];
for row in 0..height {
for w in 0..words_per_row {
let px = w * 6;
let cu = px / 2;
let samples: [u16; 12] = [
u[row * cw + cu],
y[row * width + px],
v[row * cw + cu],
y[row * width + px + 1],
u[row * cw + cu + 1],
y[row * width + px + 2],
v[row * cw + cu + 1],
y[row * width + px + 3],
u[row * cw + cu + 2],
y[row * width + px + 4],
v[row * cw + cu + 2],
y[row * width + px + 5],
];
let bytes = pack_v210_word_for_test(samples);
let off = (row * words_per_row + w) * 16;
out[off..off + 16].copy_from_slice(&bytes);
}
}
out
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_reconstructed_from_yuv422p10_matches_yuv422p10_to_rgb() {
let w = 12usize;
let h = 4usize;
let mut y_plane = std::vec![0u16; w * h];
let mut u_plane = std::vec![0u16; (w / 2) * h];
let mut v_plane = std::vec![0u16; (w / 2) * h];
pseudo_random_u16_low_n_bits(&mut y_plane, 0xC0FFEE, 10);
pseudo_random_u16_low_n_bits(&mut u_plane, 0xBADF00D, 10);
pseudo_random_u16_low_n_bits(&mut v_plane, 0xFEEDFACE, 10);
let planar = Yuv422p10Frame::new(
&y_plane,
&u_plane,
&v_plane,
w as u32,
h as u32,
w as u32,
(w / 2) as u32,
(w / 2) as u32,
);
let packed = pack_yuv422p10_to_v210(&y_plane, &u_plane, &v_plane, w, h);
let v210 = V210Frame::new(&packed, w as u32, h as u32, ((w / 6) * 16) as u32);
let mut rgb_planar = std::vec![0u8; w * h * 3];
let mut rgb_packed = std::vec![0u8; w * h * 3];
let mut s_planar = MixedSinker::<Yuv422p10>::new(w, h)
.with_rgb(&mut rgb_planar)
.unwrap();
let mut s_packed = MixedSinker::<V210>::new(w, h)
.with_rgb(&mut rgb_packed)
.unwrap();
yuv422p10_to(&planar, false, ColorMatrix::Bt709, &mut s_planar).unwrap();
v210_to(&v210, false, ColorMatrix::Bt709, &mut s_packed).unwrap();
assert_eq!(rgb_planar, rgb_packed);
}
fn v210_as_be(plane_le: &[u8]) -> Vec<u8> {
assert_eq!(
plane_le.len() % 4,
0,
"v210 plane length must be word-aligned"
);
let mut out = Vec::with_capacity(plane_le.len());
for chunk in plane_le.chunks_exact(4) {
let w = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
out.extend_from_slice(&w.to_be_bytes());
}
out
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn v210_le_be_roundtrip_byte_identical() {
let intended = solid_v210_frame(12, 4, 600, 400, 700);
let pix_le = intended.clone();
let pix_be = v210_as_be(&intended);
let stride: u32 = 32;
let frame_le = V210LeFrame::try_new(&pix_le, 12, 4, stride).unwrap();
let mut out_le_rgba = std::vec![0u8; 12 * 4 * 4];
let mut out_le_luma_u16 = std::vec![0u16; 12 * 4];
let mut sink_le = MixedSinker::<V210>::new(12, 4)
.with_simd(false)
.with_rgba(&mut out_le_rgba)
.unwrap()
.with_luma_u16(&mut out_le_luma_u16)
.unwrap();
v210_to(&frame_le, true, ColorMatrix::Bt709, &mut sink_le).unwrap();
let frame_be = V210BeFrame::try_new(&pix_be, 12, 4, stride).unwrap();
let mut out_be_rgba = std::vec![0u8; 12 * 4 * 4];
let mut out_be_luma_u16 = std::vec![0u16; 12 * 4];
let mut sink_be = MixedSinker::<V210<true>>::new(12, 4)
.with_simd(false)
.with_rgba(&mut out_be_rgba)
.unwrap()
.with_luma_u16(&mut out_be_luma_u16)
.unwrap();
v210_to_endian(&frame_be, true, ColorMatrix::Bt709, &mut sink_be).unwrap();
assert_eq!(
out_le_rgba, out_be_rgba,
"V210 RGBA u8 LE/BE outputs diverge — `<const BE>` propagation broken"
);
assert_eq!(
out_le_luma_u16, out_be_luma_u16,
"V210 luma u16 LE/BE outputs diverge — `<const BE>` propagation broken"
);
}