test(client): the D3D11VA rung's ten-bit path, measured too

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.
This commit is contained in:
2026-08-06 13:53:23 +02:00
parent c91a482b4e
commit 509843f0d4
@@ -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