Skip to content

Commit 5d82a0c

Browse files
committed
test(compositor): check the P010 and full-range views without a media file
Address review on #1408: build each VideoToolbox biplanar format in process and assert the plane formats and full-range flag nv12_srvs picks, so CI covers the path. Also pass the opt-in source path through unchanged.
1 parent 9e7faac commit 5d82a0c

3 files changed

Lines changed: 61 additions & 5 deletions

File tree

‎crates/compositor/src/compositor_macos.rs‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3860,6 +3860,10 @@ mod tests {
38603860
fn from_planes(w: u32, h: u32, y: &[u8], uv: &[u8]) -> FakeFrame {
38613861
let pb = crate::mac_frames::nv12_pixel_buffer_from_planes(w, h, y, uv)
38623862
.expect("CVPixelBuffer NV12");
3863+
FakeFrame::from_pixel_buffer(w, h, pb)
3864+
}
3865+
3866+
fn from_pixel_buffer(w: u32, h: u32, pb: crate::mac_frames::CVPixelBufferRef) -> FakeFrame {
38633867
let mut frame: Box<AVFrame> = Box::new(unsafe { std::mem::zeroed() });
38643868
frame.format = crate::ffi::AVPixelFormat::AV_PIX_FMT_VIDEOTOOLBOX as i32;
38653869
frame.data[3] = pb.as_ptr() as *mut u8;
@@ -3873,6 +3877,44 @@ mod tests {
38733877
}
38743878
}
38753879

3880+
/// Les vues que `nv12_srvs` tire de chaque format biplanaire de VideoToolbox, sans
3881+
/// décoder ni fichier média. Un flux 10 bits (`x420`, `xf20`) se lit en plans 16 bits,
3882+
/// sinon l'image part en bandes (#1390). Une frame pleine plage (`420f`, `xf20`) porte un
3883+
/// 1 dans le bleu de sa vue CbCr : c'est le drapeau que `sample_yuv` lit.
3884+
#[test]
3885+
fn nv12_srvs_reads_bit_depth_and_range_from_the_pixel_format() {
3886+
use metal::MTLPixelFormat::{R16Unorm, R8Unorm, RG16Unorm, RG8Unorm};
3887+
use objc::{msg_send, sel, sel_impl};
3888+
#[repr(C)]
3889+
struct Swizzle([u8; 4]);
3890+
const SWIZZLE_ONE: u8 = 1;
3891+
3892+
let Ok(gpu) = crate::d3d::Gpu::create(false) else {
3893+
eprintln!("pas de device Metal — test sauté");
3894+
return;
3895+
};
3896+
let comp = super::Compositor::new_sized(&gpu, 64, 64).expect("Compositor::new_sized");
3897+
for (fourcc, y_fmt, uv_fmt, full_range) in [
3898+
(b"420v", R8Unorm, RG8Unorm, false),
3899+
(b"420f", R8Unorm, RG8Unorm, true),
3900+
(b"x420", R16Unorm, RG16Unorm, false),
3901+
(b"xf20", R16Unorm, RG16Unorm, true),
3902+
] {
3903+
let name = std::str::from_utf8(fourcc).unwrap();
3904+
let pb = unsafe {
3905+
crate::mac_frames::create_nv12_pixel_buffer(64, 64, u32::from_be_bytes(*fourcc))
3906+
}
3907+
.unwrap_or_else(|e| panic!("{name} : {e}"));
3908+
let frame = FakeFrame::from_pixel_buffer(64, 64, pb);
3909+
let (y, uv) = unsafe { comp.nv12_srvs(frame.as_ptr()) }
3910+
.unwrap_or_else(|e| panic!("{name} : {e}"));
3911+
assert_eq!(y.pixel_format(), y_fmt, "{name} : plan Y");
3912+
assert_eq!(uv.pixel_format(), uv_fmt, "{name} : plan CbCr");
3913+
let swizzle: Swizzle = unsafe { msg_send![uv, swizzle] };
3914+
assert_eq!(swizzle.0[2] == SWIZZLE_ONE, full_range, "{name} : drapeau pleine plage");
3915+
}
3916+
}
3917+
38763918
/// Scène PiP minimale. `effect` est le JSON de `webcamEffect` (`"null"` pour aucun).
38773919
///
38783920
/// `effects.shadow` vaut 0 À DESSEIN : ce curseur ne pilote plus que l'ombre de l'écran,

‎crates/compositor/src/mac_frames.rs‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,17 @@ extern "C" {
105105
}
106106

107107
/// Crée un `CVPixelBufferRef` NV12 IOSurface-backed, dimensions paires `(w, h)`.
108+
/// `format` est le fourcc CoreVideo : `420v` partout, sauf les tests qui fabriquent les
109+
/// autres formats biplanaires que VideoToolbox sort (`420f`, `x420`, `xf20`).
108110
///
109111
/// NV12 impose des dimensions paires : on arrondit AU-DESSUS pour le buffer et on
110112
/// laisse `present.width/height` aux dimensions visibles — c'est le même écart
111113
/// texture/visible que produit l'alignement macrobloc de D3D11VA (1080 → 1088).
112-
unsafe fn create_nv12_pixel_buffer(w: usize, h: usize) -> Result<CVPixelBufferRef> {
114+
pub(crate) unsafe fn create_nv12_pixel_buffer(
115+
w: usize,
116+
h: usize,
117+
format: u32,
118+
) -> Result<CVPixelBufferRef> {
113119
// `{ IOSurfaceProperties: {}, MetalCompatibility: true }` — un dictionnaire
114120
// IOSurface vide suffit à demander le backing, `MetalCompatibility` fait valider
115121
// par CoreVideo que le résultat est utilisable depuis Metal.
@@ -130,7 +136,7 @@ unsafe fn create_nv12_pixel_buffer(w: usize, h: usize) -> Result<CVPixelBufferRe
130136
ptr::null(), // default allocator
131137
w,
132138
h,
133-
K_CV_PIXEL_FORMAT_TYPE_420_Y_P_C_B_CR_8_BI_PLANAR_VIDEO_RANGE,
139+
format,
134140
attributes.as_concrete_TypeRef() as *const std::ffi::c_void,
135141
&mut pixel_buffer,
136142
);
@@ -256,7 +262,11 @@ impl CpuFrames {
256262
if self.pixel_buffer.is_some() && self.pixel_buffer_dims == dims {
257263
return Ok(());
258264
}
259-
let pb = create_nv12_pixel_buffer(dims.0 as usize, dims.1 as usize)?;
265+
let pb = create_nv12_pixel_buffer(
266+
dims.0 as usize,
267+
dims.1 as usize,
268+
K_CV_PIXEL_FORMAT_TYPE_420_Y_P_C_B_CR_8_BI_PLANAR_VIDEO_RANGE,
269+
)?;
260270
self.pixel_buffer = Some(pb);
261271
self.pixel_buffer_dims = dims;
262272
Ok(())
@@ -401,7 +411,11 @@ pub(crate) fn nv12_pixel_buffer_from_planes(
401411
);
402412
}
403413
unsafe {
404-
let pb = create_nv12_pixel_buffer(w, h)?;
414+
let pb = create_nv12_pixel_buffer(
415+
w,
416+
h,
417+
K_CV_PIXEL_FORMAT_TYPE_420_Y_P_C_B_CR_8_BI_PLANAR_VIDEO_RANGE,
418+
)?;
405419
if CVPixelBufferLockBaseAddress(pb.as_ptr(), 0) != 0 {
406420
bail!("CVPixelBufferLockBaseAddress (fixture NV12)");
407421
}

‎crates/compositor/tests/hevc10_decode.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ fn scene_json(source: &str) -> String {
3939

4040
/// La frame 10 de `source`, rendue seule sur un fond uni, sans effet.
4141
fn render_frame_10(source: &str) -> RenderedFrame {
42-
let source = source.replace('\\', "/");
42+
let source = source.to_owned();
4343
let gpu = Gpu::create_auto(false).expect("device");
4444
let clips = [ClipSource {
4545
screen: source.clone(),

0 commit comments

Comments
 (0)