From 509843f0d4a9ececff4293abf7f0668f46605fc7 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 6 Aug 2026 13:53:23 +0200 Subject: [PATCH] test(client): the D3D11VA rung's ten-bit path, measured too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The companion to the Vulkan ten-bit leg, over the same vector and the same P010 goldens — one golden file serves both rungs because a D3D11 P010 surface and Vulkan's 3PACK16 family hold the ten bits in the same place. This is the rung where the gap mattered most. D3D11VA exposes no per-picture status query at all, so its HDR evidence was a session that built a Main10 decoder and streamed without complaint — which is precisely what a Main10 stream decoding to garbage would also produce. Now there is a number. It exercises geometry the eight-bit legs cannot reach: P010 samples are two bytes, so a row is width * 2 rather than width, and HEVC's 128-line granule pads a 240-line picture to a 256-line surface — so the chroma plane starts a long way from where the display height alone would put it. Getting either wrong is the smeared-rows failure this project has already paid for once, and it would have looked like a decoder fault. The run body now takes the stream format and the expected access-unit count rather than assuming the eight-bit envelope and 250 frames. A CPU guard pins the vector at ten bits — 4:2:0, both depths minus8 == 2, 320x240, 50 access units. A regenerated eight-bit vector would otherwise turn this into a second run of the eight-bit path under a ten-bit name, passing, because its goldens would have been regenerated with it. Hardware: HEVC Main 10 50/50 bit-identical on the RTX 4090 and on the AMD Radeon iGPU, alongside the unchanged eight-bit legs at 250/250 on both. With the Vulkan leg's two drivers that is four independent drivers across two rungs for the ten-bit path, where yesterday there were none. --- .../pf-client-core/src/video_d3d11_native.rs | 98 ++++++++++++++++++- 1 file changed, 94 insertions(+), 4 deletions(-) diff --git a/crates/pf-client-core/src/video_d3d11_native.rs b/crates/pf-client-core/src/video_d3d11_native.rs index 7e04c9de..bdbb15d0 100644 --- a/crates/pf-client-core/src/video_d3d11_native.rs +++ b/crates/pf-client-core/src/video_d3d11_native.rs @@ -1039,6 +1039,15 @@ mod parity { /// Both vendored vectors are 250 display frames. const FRAME_COUNT: usize = 250; + /// The Main 10 vector: 50 frames of 320x240 HEVC Main 10 4:2:0, generated by + /// libx265 and hashed from libavcodec's software decode as tightly packed P010. + /// Its provenance, the generation commands and the reason P010 rather than + /// `yuv420p10le` is the golden layout are all in the golden file's header. + const TEST_MAIN10_H265: &[u8] = include_bytes!("../../pf-vkdecode/tests/data/test-main10.h265"); + const GOLDENS_MAIN10: &str = + include_str!("../../pf-vkdecode/tests/data/test-main10.p010.sha256"); + const MAIN10_FRAME_COUNT: usize = 50; + /// The golden file's hash lines (comments and blanks skipped). fn golden_hashes(file: &'static str) -> Vec<&'static str> { file.lines() @@ -1344,11 +1353,19 @@ mod parity { /// Decode `aus` through a real `NativeD3d11Decoder`, hash every picture, and /// compare the planner's display order against libavcodec's goldens. - fn parity_run(codec: Codec, aus: &[&[u8]], order: &Order, goldens: &[&str], label: &str) { + fn parity_run( + codec: Codec, + stream: StreamFormat, + aus: &[&[u8]], + order: &Order, + goldens: &[&str], + expected_aus: usize, + label: &str, + ) { assert_eq!( aus.len(), - FRAME_COUNT, - "{label}: the vector must split into {FRAME_COUNT} access units — a \ + expected_aus, + "{label}: the vector must split into {expected_aus} access units — a \ different count means this file's splitter disagrees with pf-bitstream's, \ and nothing below it is meaningful" ); @@ -1361,7 +1378,7 @@ mod parity { ); let luid = pinned_adapter(); - let mut decoder = NativeD3d11Decoder::new(codec, StreamFormat::SDR_420_8, luid, false) + let mut decoder = NativeD3d11Decoder::new(codec, stream, luid, false) .unwrap_or_else(|e| panic!("{label}: the box must host this profile — {e:#}")); let mut readback = Readback { ctx: decoder.context.clone(), @@ -1422,9 +1439,11 @@ mod parity { let order = order_h264(&aus); parity_run( Codec::H264, + StreamFormat::SDR_420_8, &aus, &order, &golden_hashes(GOLDENS_H264), + FRAME_COUNT, "H.264", ); } @@ -1436,13 +1455,45 @@ mod parity { let order = order_h265(&aus); parity_run( Codec::H265, + StreamFormat::SDR_420_8, &aus, &order, &golden_hashes(GOLDENS_H265), + FRAME_COUNT, "H.265", ); } + /// The ten-bit path, which no golden set in this program covered until now. + /// + /// The HDR legs proved a Main10 session BUILDS and streams clean, which is a + /// weaker claim than it looks: D3D11VA exposes no per-picture status query, so a + /// Main10 stream decoding to garbage logs exactly as cleanly as one decoding + /// correctly. This is the leg that can tell them apart. + /// + /// It also exercises geometry the 8-bit legs cannot: P010 samples are two bytes, + /// so a row is `width * 2`, and HEVC's 128-line granule pads a 240-line picture + /// to a 256-line surface — the chroma plane therefore starts a long way from + /// where the display height would put it. + #[test] + #[ignore = "needs a Windows D3D11 video device (see module docs)"] + fn main10_every_frame_hashes_bit_identical_to_libavcodec() { + let aus = split_h265_aus(TEST_MAIN10_H265); + let order = order_h265(&aus); + parity_run( + Codec::H265, + StreamFormat { + chroma_format_idc: 1, + bit_depth: 10, + }, + &aus, + &order, + &golden_hashes(GOLDENS_MAIN10), + MAIN10_FRAME_COUNT, + "HEVC Main 10", + ); + } + // --------------------------------------------------------------------- // CPU guards — NOT `#[ignore]`d, so ordinary CI notices when this file's // splitter or the goldens drift away from pf-bitstream. @@ -1471,6 +1522,45 @@ mod parity { ); } + #[test] + fn the_main10_vector_really_is_ten_bit() { + let aus = split_h265_aus(TEST_MAIN10_H265); + assert_eq!( + aus.len(), + MAIN10_FRAME_COUNT, + "the Main 10 vector is {MAIN10_FRAME_COUNT} access units" + ); + let order = order_h265(&aus); + assert_eq!( + order.display.len(), + golden_hashes(GOLDENS_MAIN10).len(), + "the planner's output count must match the Main 10 golden count" + ); + + // The point of the leg. A regenerated vector that came out 8-bit would make + // `main10_every_frame_hashes_bit_identical_to_libavcodec` a second run of the + // 8-bit path wearing a ten-bit name — and it would pass, because the goldens + // would have been regenerated alongside it. + let mut planner = H265Planner::new(); + let plan = planner + .plan_au(aus[0]) + .expect("the Main 10 vector's first access unit must plan"); + assert_eq!( + ( + plan.picture.chroma_format_idc, + plan.picture.bit_depth_luma_minus8, + plan.picture.bit_depth_chroma_minus8 + ), + (1, 2, 2), + "the Main 10 vector must be 4:2:0 at ten bits" + ); + assert_eq!( + (plan.picture.coded_width, plan.picture.coded_height), + (320, 240), + "the golden frame size is 320x240" + ); + } + #[test] fn both_vendored_vectors_really_do_reorder() { // The module docs claim the harness must reorder because these vectors do. If