fix(client): the D3D11VA AV1 rung decodes wrong pixels — the parity harness existed all along
The follow-up was framed as "build the frame-hash parity harness the D3D11VA AV1 rung is missing, then flip hardware_verified to true". Both halves were wrong. The harness was never missing. `video_d3d11_native`'s `parity` module has carried `av1_every_delivered_frame_hashes_bit_identical_to_libavcodec` since M7 wired the rung — wired to the SAME libavcodec goldens the Vulkan AV1 leg passes against, with the display-order model that handles the vector's 24 hidden frames, sitting `#[ignore]`d beside the H.264/H.265/Main10 legs. It had simply never been run on a device; .173 was powered off the day it was written. What the old evidence note called a missing harness is real about pf-dxvadec the CRATE, which cannot host one — it links no D3D11 — but the device half lives here and was already done. Run on .221, it FAILS, on both GPUs, deterministically (three runs each, identical first-divergent frame and identical hashes): 186/250 diverging display frames on an RTX 3500 Ada, 245/250 on an Intel Arc. It is the decode that is wrong, not the measurement, and three independent checks say so. H.264 and H.265 pass 250/250 and HEVC Main 10 50/50 through the same harness, the same readback geometry, the same crop and the same slot map on those same two GPUs. pf-vkdecode's Vulkan AV1 leg reproduces the same golden file 250/250 on the same box. And the goldens regenerate byte-for-byte from the ffmpeg build their own header names. Two signatures, and they are not one defect wearing two faces. NVIDIA is bit-exact for display frames 0..=63 and then loses ONE 16x24 luma block — 174 pixels, max |delta| 8, chroma untouched — on the frame whose order_hint first reaches 64, after which every remaining frame is downstream of it through prediction. The stream parks the key frame (order_hint 0) in BWDREF and ALTREF2 for its whole length, so 64 is where the distance to it reaches the edge of what get_relative_dist can represent at OrderHintBits = 7. Intel is structurally wrong from display frame 4 — 47% of luma, max |delta| 242, chroma wrong too, a frame predicted from the wrong picture — and the only later frame it gets right is the one whose primary_ref_frame is PRIMARY_REF_NONE. None of this is visible on glass, which is the whole argument for goldens: the rung streams 4K60 on both parts with a clean five-minute soak at roughly ten times the Vulkan leg's decode time. The 2026-08-07 field sessions that looked clean were looking at wrong pixels. So hardware_verified stays false, and the note now says why in the strongest available terms — it prints at warn on every session that lands here, and "decodes AV1 to wrong pixels" is what a support engineer needs to read. The pair stays in `every_rung_runs_and_the_unproven_ones_are_named`'s unproven array; its note still contains NEVER, because the pair has never PASSED parity, which is now a measured statement rather than an absence. Left deliberately unchanged: `auto` on Windows can still reach this rung for AV1, and on Intel it is the arm that fires, because that vendor advertises no SAMPLED usage on any decode profile so zero-copy Vulkan Video cannot run there. Barring it trades visibly-wrong AV1 for the software rung, which cannot keep up at 4K and is itself unproven. Which way that trade goes is a product call, so it is recorded at the admission site rather than made silently here. `av1_divergence_map` is kept, cleaned up and documented: it is what turned "186 frames differ" into a lead — one line per display frame, its verdict beside the plan facts that could explain it, and an opt-in raw-NV12 dump. At a frame where one vendor hashes correctly, that vendor's bytes ARE libavcodec's bytes and so a valid reference for the other's, which is how "how badly" was answered without new goldens. The tool that would localise the rest does not exist: pf-dxvadec's libav_picparams_parity covers H.264 and HEVC only, so the AV1 conversion has never been compared against libavcodec at the picture-parameter level either. That is the next step, not another session. Also in this file, since it is the same table and the same day: the VAAPI rung's AV1 leg has now decoded 250/250 of the vendored vector on RDNA3 and its arm is split from the H.264/H.265 ones, which genuinely have still never decoded anything. It is unverified for the same reason as ever — no parity — and the D3D11VA row above is exactly why that distinction is worth keeping: a rung can decode 250 frames and still be wrong.
This commit is contained in:
@@ -24,7 +24,7 @@
|
||||
//! **One column of that table is different, and it is the one the filter still guards.**
|
||||
//! On Linux, Intel and every unknown vendor id run `native-vaapi → native-vk → sw`
|
||||
//! ([`VulkanDecodeDevice::prefer_vulkan_first`] is true for NVIDIA and AMD only), so the
|
||||
//! rung directly below the never-run pf-vaadec is native Vulkan Video — H.264 and H.265 on
|
||||
//! rung directly below the unproven pf-vaadec is native Vulkan Video — H.264 and H.265 on
|
||||
//! three drivers plus a 92-minute soak, AV1 250/250. There, barring the unproven rung moves
|
||||
//! the session exactly one rung down, onto proven code, so it is barred: an unproven rung
|
||||
//! yields to a rung that is BOTH verified for this codec and usable on THIS device, and to
|
||||
@@ -48,9 +48,10 @@
|
||||
//! | native Vulkan Video | [`crate::video_vk_native`] | H.264 | **yes** — bit-exact vs libavcodec, 250/250 AUs on three drivers + a 92-minute soak (M2 WP-D) |
|
||||
//! | native Vulkan Video | | H.265 (Main / Main10 / 4:4:4) | **yes** — same parity run + HDR chain and Deck/VanGogh legs (M3) |
|
||||
//! | native Vulkan Video | | AV1 | **yes** — 250/250 bit-identical to libavcodec on an RTX 5070 Ti (M7); ONE vendor, no soak |
|
||||
//! | native D3D11VA | [`crate::video_d3d11_native`] | H.264, H.265 | **yes** — frame-hash parity on an RTX 4090 and an AMD iGPU + a 30-minute soak (M5) |
|
||||
//! | native D3D11VA | | AV1 | **not proven, but no longer unexercised** — 4K60 on an RTX 3500 Ada AND an Intel Arc, plus a clean 5-minute Arc soak (2026-08-07), at roughly 10x the speed of the Vulkan leg on the same GPU. It stays out of the admission filter for one specific missing thing: frame-hash parity against libavcodec, which every verified pair here has and pf-dxvadec has no harness for. Its M7 wiring was right all along: what looked like a DXVA reference-mapping bug (`reference picture N holds no DPB slot`, 72 consecutive failures) was the HOST shipping half of every AV1 frame — see `pf_encode`'s `resolve_split_subframe` |
|
||||
//! | native VAAPI | [`crate::video_vaapi_native`] | H.264, H.265, AV1 | **NO** — has never decoded a frame anywhere (M6/M7). Still true as of 2026-08-07: the reachable VAAPI box (`.25`, RDNA3) reports `VAProfileAV1Profile0: VAEntrypointVLD` and no Vulkan AV1 decode at all, so it is the right box to prove this rung on — it has no punktfunk checkout and 4 GB of usable RAM, which is why it has not happened |
|
||||
//! | native D3D11VA | [`crate::video_d3d11_native`] | H.264, H.265 | **yes** — frame-hash parity on an RTX 4090 and an AMD iGPU + a 30-minute soak (M5), re-confirmed 250/250 (+ 50/50 Main 10) on an RTX 3500 Ada and an Intel Arc on 2026-08-07 |
|
||||
//! | native D3D11VA | | AV1 | **NO — it decodes WRONG PIXELS.** The parity harness that was missing turned out to exist (`video_d3d11_native`'s `parity` module, written by M7 and never run); running it on 2026-08-07 failed on BOTH GPUs of `.221`, deterministically: 186/250 diverging frames on an RTX 3500 Ada and 245/250 on an Intel Arc. Not the environment — H.264, H.265 and HEVC Main 10 pass 250/250/50 through the SAME harness on the same two GPUs, and pf-vkdecode's Vulkan AV1 leg reproduces the SAME goldens 250/250 on the same box. Two unlike signatures: NVIDIA is bit-exact for 63 frames and then loses ONE 16x24 luma block (174 px, max |delta| 8) on the frame whose `order_hint` first reaches 64, which then propagates; Intel is structurally wrong from display frame 4 (47% of luma, max |delta| 242). It still streams — 4K60 on both, a clean 5-minute Arc soak, ~10x the Vulkan leg's speed — which is exactly why the picture looked fine and only the goldens caught it. See `av1_divergence_map` |
|
||||
//! | native VAAPI | [`crate::video_vaapi_native`] | AV1 | **not proven** — but it has now DECODED: 250/250 frames of the vendored AV1 vector on `.25` (Radeon 780M, RDNA3, Mesa 26.0.3) on 2026-08-07, NV12 on a tiled AMD modifier, and `probe_this_machines_libva` reports `AV1 Profile 0: VLD decode`. Never frame-hash parity-checked: the rung exports a tiled dmabuf with no CPU-readable image, so parity needs a readback path that does not exist yet |
|
||||
//! | native VAAPI | | H.264, H.265 | **NO** — these two legs have still never decoded a frame anywhere (M6/M7) |
|
||||
//! | software | `video_software` | H.264, AV1 | **not proven** — openh264 has never run on glass; rav1d HAS now decoded 1080p and 4K60 AV1 there (2026-08-07, .21) and recovers in-session from a mid-stream reference loss, but with no parity check and no soak. Its 4K "abort" was never about 4K: rav1d 1.1.0 kills the process on ANY decode error while it holds a single frame context, so `video_software` opens it with two — see [`crate::video_software`] |
|
||||
//!
|
||||
//! The software rung's evidence is recorded for the same reason but does not gate
|
||||
@@ -771,7 +772,8 @@ enum Backend {
|
||||
/// libavcodec's VAAPI hwaccel, and since M10 the only VAAPI rung: libva driven
|
||||
/// straight from pf-bitstream plans, dlopen'd, exporting the same DRM-PRIME dmabufs.
|
||||
/// Reachable by pin (`PUNKTFUNK_DECODER=native-vaapi`) and by `auto` in the vendor
|
||||
/// order. ⚠ This rung has decoded NOTHING on hardware ([`native_evidence`]) — `auto`
|
||||
/// order. ⚠ Its AV1 leg has now decoded 250/250 on RDNA3 but is unverified for want of
|
||||
/// parity, and its H.264/H.265 legs have decoded NOTHING ([`native_evidence`]) — `auto`
|
||||
/// runs it where the alternative below it is the CPU, and yields to native Vulkan
|
||||
/// Video where that rung is proven for the codec and usable on the device
|
||||
/// ([`native_rung_admitted`], which is the Intel/unknown arm). Every session that
|
||||
@@ -1109,38 +1111,67 @@ pub fn native_evidence(rung: NativeRung, wire: u8) -> RungEvidence {
|
||||
true,
|
||||
"frame-hash parity on an RTX 4090 and an AMD iGPU + 30-min soak (M5)",
|
||||
),
|
||||
// Decoded on hardware for the first time on 2026-08-07 (4K60, RTX 3500 Ada) once the
|
||||
// host stopped truncating AV1 — so the old "NEVER decoded a frame anywhere" is no
|
||||
// longer true and must not be printed.
|
||||
// 2026-08-07: this pair was PARITY-CHECKED for the first time, and it FAILED.
|
||||
//
|
||||
// Re-measured the same day against a host carrying #95, and it now has more than one
|
||||
// session behind it: 4K60 on the RTX 3500 Ada AND on the box's Intel Arc (which
|
||||
// advertises no SAMPLED usage on any decode profile, so zero-copy Vulkan Video cannot
|
||||
// work there and `auto` correctly lands here), plus a 5-minute Arc soak — 297 stats
|
||||
// lines, 60 fps, decode 1.3 ms, e2e 10.9/14.8 ms p50/p95, and exactly ONE warning in
|
||||
// the whole run: this very notice. No refusals, no demotions, no concealed runs, and
|
||||
// the session was still alive at the end. Two vendors and a soak. It is also
|
||||
// roughly 10x faster than Vulkan
|
||||
// Video's AV1 leg on the SAME NVIDIA GPU (1.0-1.4 ms against 11.6-16.7 ms), which is
|
||||
// the strongest argument yet for eventually admitting it.
|
||||
// The harness the previous note said did not exist did exist — `video_d3d11_native`'s
|
||||
// `parity` module, written by M7 against the same libavcodec goldens the Vulkan rung
|
||||
// uses, `#[ignore]`d and never once run on a device. Running it on `.221` failed on
|
||||
// BOTH GPUs and did so deterministically (three runs each, identical first-divergent
|
||||
// frame and identical hashes): 186/250 diverging display frames on an RTX 3500 Ada,
|
||||
// 245/250 on an Intel Arc.
|
||||
//
|
||||
// Still NOT `verified`, and the missing piece is named rather than hand-waved:
|
||||
// `verified` gates `native_rung_admitted` — whether `auto` may pick this rung AHEAD
|
||||
// of Vulkan Video — and there is still NO frame-hash parity against libavcodec.
|
||||
// Every other verified pair in this table earned it with one. pf-dxvadec has no
|
||||
// `gpu_parity`-style harness at all (only `libav_picparams_parity`, which compares
|
||||
// picture parameters on the CPU and never decodes), so building that harness is the
|
||||
// work that would promote this, not another session.
|
||||
// It is the DECODE that is wrong, not the measurement. Three things rule the harness
|
||||
// and the box out: H.264 and H.265 pass 250/250 and HEVC Main 10 50/50 through the
|
||||
// SAME harness, the same readback geometry and the same slot map on those same two
|
||||
// GPUs; pf-vkdecode's Vulkan AV1 leg reproduces the SAME golden file 250/250 on the
|
||||
// same box; and the goldens themselves reproduce byte-for-byte from ffmpeg 8.1.1.
|
||||
//
|
||||
// Two signatures, and they are not the same defect wearing two faces:
|
||||
// * NVIDIA is bit-exact for display frames 0..=63 and then loses ONE 16x24 luma
|
||||
// block — 174 pixels, max |delta| 8, chroma untouched — on the frame whose
|
||||
// `order_hint` first reaches 64, after which every remaining frame is downstream
|
||||
// of it. The stream parks the key frame (`order_hint` 0) in BWDREF and ALTREF2 for
|
||||
// its whole length, so 64 is where the distance to it reaches the edge of
|
||||
// `get_relative_dist`'s range at `OrderHintBits = 7`.
|
||||
// * Intel is structurally wrong from display frame 4 — 47% of luma, max |delta| 242,
|
||||
// chroma wrong too, i.e. predicted from the wrong picture — and the only later
|
||||
// frame it gets right is the one whose `primary_ref_frame` is PRIMARY_REF_NONE.
|
||||
//
|
||||
// None of this shows on glass: the rung streams 4K60 on both parts with a clean
|
||||
// 5-minute soak at ~10x the Vulkan leg's speed. That is the point of a golden.
|
||||
//
|
||||
// What would localise it does not exist: pf-dxvadec's `libav_picparams_parity`
|
||||
// covers H.264 and HEVC only, so the AV1 conversion has never been compared against
|
||||
// libavcodec at the picture-parameter level either. `av1_divergence_map` in
|
||||
// `video_d3d11_native` carries the per-frame evidence.
|
||||
(NativeRung::D3d11va, CODEC_AV1) => (
|
||||
false,
|
||||
"4K60 on an RTX 3500 Ada AND an Intel Arc + a clean 5-min soak (2026-08-07), \
|
||||
and ~10x faster than the Vulkan leg on the same GPU - but has NEVER been \
|
||||
frame-hash parity-checked against libavcodec, which every verified pair \
|
||||
here has (M7)",
|
||||
"streams 4K60 on an RTX 3500 Ada AND an Intel Arc with a clean 5-min soak, but \
|
||||
has NEVER passed frame-hash parity and now measurably FAILS it: 186/250 and \
|
||||
245/250 display frames diverge from libavcodec on those two GPUs (2026-08-07), \
|
||||
while H.264/H.265/Main10 pass through the same harness - this rung decodes AV1 \
|
||||
to wrong pixels (M7)",
|
||||
),
|
||||
// 2026-08-07: the VAAPI rung decoded its first frames ever — 250/250 of the vendored
|
||||
// AV1 vector on `.25` (Radeon 780M, RDNA3, Mesa 26.0.3), NV12 on a tiled AMD
|
||||
// modifier. So "never decoded a frame anywhere" is no longer true of AV1 and must not
|
||||
// be printed for it; it is still exactly true of the other two legs, which is why
|
||||
// this arm is now split.
|
||||
//
|
||||
// Not `verified`, and for a reason the AV1 row above makes vivid: a rung can decode
|
||||
// 250 frames and still produce wrong pixels. Parity is what tells them apart, and
|
||||
// this rung exports a TILED dmabuf with no CPU-readable image, so it needs a readback
|
||||
// path nothing has written yet.
|
||||
(NativeRung::Vaapi, CODEC_AV1) => (
|
||||
false,
|
||||
"decoded 250/250 frames of the vendored AV1 vector on RDNA3 (Mesa 26.0.3, \
|
||||
2026-08-07) - its first frames on any hardware - but has never been frame-hash \
|
||||
parity-checked: the rung exports a tiled dmabuf with no CPU-readable image (M7)",
|
||||
),
|
||||
(NativeRung::Vaapi, _) => (
|
||||
false,
|
||||
"NEVER decoded a frame on any hardware - no VAAPI device was reachable (M6/M7)",
|
||||
"NEVER decoded a frame on any hardware - the AV1 leg has now run on RDNA3, these \
|
||||
two have not (M6/M7)",
|
||||
),
|
||||
// The 4K AV1 abort recorded here on 2026-08-07 is FIXED, and it was never about 4K.
|
||||
// rav1d 1.1.0 aborts the process on ANY decode error while it holds a single frame
|
||||
@@ -1192,7 +1223,7 @@ pub fn native_vulkan_usable(wire: u8, video_decode: bool, decode_video_caps: u32
|
||||
/// Where the rule bites, and where it deliberately does not:
|
||||
///
|
||||
/// * **Linux, Intel and every unknown vendor id.** The order is `native-vaapi →
|
||||
/// native-vk → sw`, so the rung under the never-run pf-vaadec is native Vulkan Video,
|
||||
/// native-vk → sw`, so the rung under the unproven pf-vaadec is native Vulkan Video,
|
||||
/// proven for all three codecs. Barring VAAPI there moves the session ONE rung down onto
|
||||
/// proven code, so it is barred — and it stays reachable below Vulkan (the same ladder
|
||||
/// reaches it again if Vulkan can't be built) and by pin.
|
||||
@@ -1462,8 +1493,11 @@ pub fn decodable_codecs() -> u8 {
|
||||
/// * the presenter's Vulkan device advertises `DECODE_AV1` in its decode queue
|
||||
/// family's codec operations, or
|
||||
/// * (Windows) the presenter can import D3D11 textures — the native DXVA rung then decodes
|
||||
/// AV1 Profile 0 through the adapter's profile GUID, and `auto` reaches it. ⚠ That leg
|
||||
/// has decoded nothing on hardware ([`native_evidence`]); the session says so at `warn`.
|
||||
/// AV1 Profile 0 through the adapter's profile GUID, and `auto` reaches it. ⚠⚠ That leg
|
||||
/// decodes WRONG PIXELS — measured against libavcodec on two GPUs on 2026-08-07
|
||||
/// ([`native_evidence`]); the session says so at `warn`. Advertising AV1 is still answered
|
||||
/// from device facts here, deliberately: withdrawing the codec would be a product call
|
||||
/// about what a Windows Intel box streams instead, not a fact about the device.
|
||||
/// Before M10 this arm was conditional, because the leg was kept out of `auto` while
|
||||
/// libavcodec's DXVA rung was still below it — with that rung deleted there is no
|
||||
/// condition left to write.
|
||||
@@ -1568,7 +1602,8 @@ fn report_au_fault_env(native_rung: bool) {
|
||||
/// a frame through it for this codec.
|
||||
///
|
||||
/// This is the program's honesty surface, and M10 is where it earns its keep: every rung
|
||||
/// is now native, two of them have never decoded anything anywhere, and there is no
|
||||
/// is now native, one of them still has legs that have never decoded anything anywhere,
|
||||
/// another has a leg that decodes wrong pixels, and there is no
|
||||
/// libavcodec twin left underneath to catch a session that lands wrong. A field report of
|
||||
/// the form "M10 broke my stream" is only actionable if the log distinguishes *the rung
|
||||
/// with three drivers and a 92-minute soak behind it* from *the rung nothing has ever
|
||||
@@ -1646,10 +1681,10 @@ impl Decoder {
|
||||
/// Intel/unknown (Intel's driver advertises Vulkan Video, but Vulkan decode on it
|
||||
/// strobed/overran the budget — B580 field report).
|
||||
///
|
||||
/// On top of that order sits the evidence filter ([`native_rung_admitted`]): a rung
|
||||
/// that has never decoded a frame does not go FIRST when the rung directly below it is
|
||||
/// proven for this codec and usable on this device. That is the Linux Intel/unknown
|
||||
/// arm and only that arm — everywhere else what is below is the CPU.
|
||||
/// On top of that order sits the evidence filter ([`native_rung_admitted`]): an
|
||||
/// UNPROVEN rung does not go FIRST when the rung directly below it is proven for this
|
||||
/// codec and usable on this device. That is the Linux Intel/unknown arm and only that
|
||||
/// arm — everywhere else what is below is the CPU.
|
||||
///
|
||||
/// Whatever it lands on, the session logs `decode rung active` with the rung's name
|
||||
/// and its evidence state, and that line is a WARNING when no hardware has ever
|
||||
@@ -1994,6 +2029,20 @@ impl Decoder {
|
||||
// so what is really below the DXVA AV1 leg is the CPU — and its H.264/H.265
|
||||
// legs are verified anyway, which is what the first clause of
|
||||
// [`native_rung_admitted`] answers.
|
||||
//
|
||||
// ⚠⚠ OPEN, 2026-08-07: for AV1 this now admits a rung MEASURED to decode wrong
|
||||
// pixels (186/250 and 245/250 diverging frames — [`native_evidence`]), and on
|
||||
// Intel it is the arm that actually fires, because that vendor advertises no
|
||||
// SAMPLED usage on any decode profile so zero-copy Vulkan Video cannot run
|
||||
// there. The filter is behaving as written — "unproven yields only to PROVEN
|
||||
// code below", and below here is the CPU — but the premise has changed: this is
|
||||
// no longer a rung with no evidence, it is a rung with BAD evidence, and the
|
||||
// rule was never asked that question. Deliberately NOT changed here: barring it
|
||||
// trades visibly-wrong AV1 for the software rung, which cannot keep up at 4K
|
||||
// (see [`crate::video_software`]) and is itself unproven, so which way that
|
||||
// trade should go is a product call and not this function's to make silently.
|
||||
// Every such session says so at `warn` via [`log_rung`], and the note now names
|
||||
// the parity failure rather than merely the absence of a check.
|
||||
&& native_rung_admitted(NativeRung::D3d11va, wire, None)
|
||||
{
|
||||
d3d11_tried = true;
|
||||
@@ -3282,22 +3331,22 @@ mod tests {
|
||||
(
|
||||
NativeRung::D3d11va,
|
||||
CODEC_AV1,
|
||||
"the DXVA AV1 leg never ran (M7)",
|
||||
"the DXVA AV1 leg FAILS parity on two GPUs (M7)",
|
||||
),
|
||||
(
|
||||
NativeRung::Vaapi,
|
||||
CODEC_H264,
|
||||
"no VAAPI device was reachable",
|
||||
"no VAAPI device has run this leg",
|
||||
),
|
||||
(
|
||||
NativeRung::Vaapi,
|
||||
CODEC_HEVC,
|
||||
"no VAAPI device was reachable",
|
||||
"no VAAPI device has run this leg",
|
||||
),
|
||||
(
|
||||
NativeRung::Vaapi,
|
||||
CODEC_AV1,
|
||||
"no VAAPI device was reachable",
|
||||
"VAAPI decoded AV1 on RDNA3 but has no parity check",
|
||||
),
|
||||
(
|
||||
NativeRung::Software,
|
||||
@@ -3375,8 +3424,9 @@ mod tests {
|
||||
e.note.contains("NEVER") || e.note.contains("never"),
|
||||
"{} / {codec:#x}: the note is what the session log prints at warn — it \
|
||||
must name plainly what this pair has NEVER had, whether that is a \
|
||||
hardware run at all (the VAAPI rows) or the parity check that would \
|
||||
promote it (D3D11VA/AV1), got {:?}",
|
||||
hardware run at all (VAAPI H.264/H.265) or the parity check that would \
|
||||
promote it (VAAPI AV1, which HAS decoded, and D3D11VA AV1, which has \
|
||||
decoded and then FAILED that check on two GPUs), got {:?}",
|
||||
rung.name(),
|
||||
e.note
|
||||
);
|
||||
|
||||
@@ -18,10 +18,19 @@
|
||||
//! (`video::native_evidence`, and the table in `video`'s module docs):
|
||||
//!
|
||||
//! * **H.264 and H.265** — frame-hash parity against libavcodec on an RTX 4090 and an AMD
|
||||
//! iGPU plus a 30-minute soak (M5).
|
||||
//! * **AV1** — wired in M7, has decoded nothing on any hardware. Until M10 `auto` skipped it
|
||||
//! in favour of the libavcodec rung below; with that gone the alternative is the CPU, so it
|
||||
//! runs and the session log says so at `warn`.
|
||||
//! iGPU plus a 30-minute soak (M5), re-confirmed on an RTX 3500 Ada and an Intel Arc on
|
||||
//! 2026-08-07 (250/250 both codecs, plus 50/50 HEVC Main 10 on both).
|
||||
//! * **AV1** — wired in M7. It streams: 4K60 on an RTX 3500 Ada and on an Intel Arc, with a
|
||||
//! clean 5-minute soak. But it **fails frame-hash parity on both of those GPUs**, measured
|
||||
//! 2026-08-07 — 186/250 diverging frames on the NVIDIA part and 245/250 on the Intel one,
|
||||
//! deterministically, against the same libavcodec goldens the Vulkan rung reproduces
|
||||
//! 250/250 on the SAME box. So this rung's AV1 leg produces wrong pixels and the session
|
||||
//! log says so at `warn`. `av1_divergence_map` (below) carries the two signatures; the
|
||||
//! tool that would localise it — an AV1 leg for pf-dxvadec's `libav_picparams_parity`,
|
||||
//! which covers only H.264 and HEVC — does not exist yet.
|
||||
//!
|
||||
//! Until M10 `auto` skipped AV1 here in favour of the libavcodec rung below; with that
|
||||
//! gone the alternative is the CPU, so it still runs.
|
||||
//!
|
||||
//! A refusal or an init failure logs and falls through to the standard ladder, so neither the
|
||||
//! pin nor the `auto` admission can cost a session its decoder.
|
||||
@@ -2227,6 +2236,163 @@ mod parity {
|
||||
);
|
||||
}
|
||||
|
||||
/// The AV1 leg's post-mortem: one line per DISPLAY frame, its verdict against the
|
||||
/// goldens beside the plan facts that could explain it.
|
||||
///
|
||||
/// Not a gate — it asserts nothing and always "passes". It exists because
|
||||
/// [`av1_every_delivered_frame_hashes_bit_identical_to_libavcodec`] FAILS on
|
||||
/// every device tried so far, and a count of diverging frames is not a lead. This
|
||||
/// is what turned that count into one, on 2026-08-07:
|
||||
///
|
||||
/// * **NVIDIA RTX 3500 Ada** — display frames 0..=63 bit-identical, then every one
|
||||
/// of the remaining 186 diverges. The first bad frame is the one whose
|
||||
/// `order_hint` first reaches **64**, and its error is 174 luma pixels in a
|
||||
/// single 16x24 block (max |delta| 8, chroma untouched) which then propagates
|
||||
/// through prediction. The stream keeps the key frame (`order_hint` 0) in the
|
||||
/// BWDREF and ALTREF2 slots for its whole length, so 64 is where the distance to
|
||||
/// it reaches the edge of what `get_relative_dist` can represent at
|
||||
/// `OrderHintBits = 7`.
|
||||
/// * **Intel Arc** — only display frames 0, 1, 2, 3 and 10 are bit-identical, and
|
||||
/// the divergence is STRUCTURAL rather than marginal (47% of luma at the first
|
||||
/// bad frame, max |delta| 242, chroma wrong too): a frame predicted from the
|
||||
/// wrong picture, not a filter rounding.
|
||||
///
|
||||
/// Both are deterministic — three runs each, identical first-divergent frame and
|
||||
/// identical hashes — so neither is a race against the decode queue.
|
||||
///
|
||||
/// Set `PF_AV1_DUMP=<tag>` to also write a few frames' raw NV12 to the temp
|
||||
/// directory. That is how "how badly" was answered: at a frame where ONE vendor
|
||||
/// hashes correctly, that vendor's bytes are libavcodec's bytes and so a valid
|
||||
/// reference for the other's, and `ffmpeg -f rawvideo -pix_fmt nv12` regenerates
|
||||
/// the rest (the golden file's header carries the exact command).
|
||||
#[test]
|
||||
#[ignore = "diagnostic, needs a Windows D3D11 video device (see module docs)"]
|
||||
fn av1_divergence_map() {
|
||||
let units = split_ivf(TEST_25FPS_AV1);
|
||||
let order = order_av1(&units);
|
||||
let goldens = golden_hashes(GOLDENS_AV1);
|
||||
|
||||
// Plan facts per PicId, from a planner run alongside the decoder's own.
|
||||
let mut facts: HashMap<u64, String> = HashMap::new();
|
||||
let mut hidden: std::collections::HashSet<u64> = std::collections::HashSet::new();
|
||||
{
|
||||
let mut planner = pf_dxvadec::Av1Planner::new();
|
||||
for unit in &units {
|
||||
for plan in planner.plan_au(unit).expect("the clean vector plans") {
|
||||
let Some(id) = plan.dpb.stored else { continue };
|
||||
let h = &*plan.header;
|
||||
if !h.show_frame {
|
||||
hidden.insert(id);
|
||||
}
|
||||
let mut refs = String::new();
|
||||
for r in plan.refs.iter() {
|
||||
match r {
|
||||
Some(r) => {
|
||||
refs.push_str(&format!("{}/{} ", r.slot, r.id));
|
||||
}
|
||||
None => refs.push_str("-/- "),
|
||||
}
|
||||
}
|
||||
facts.insert(
|
||||
id,
|
||||
format!(
|
||||
"ft={} show={} oh={:3} pri={} refresh={:#06x} grain={} seg={} \
|
||||
sr={} warp={} refmvs={} skip={} refsel={} tiles={}x{} \
|
||||
lf={:?} lfsharp={} lfdelta={}{} refd={:?} moded={:?} \
|
||||
cdefbits={} lr={:?} refs=[{}]",
|
||||
h.frame_type as u8,
|
||||
u8::from(h.show_frame),
|
||||
h.order_hint,
|
||||
h.primary_ref_frame,
|
||||
h.refresh_frame_flags,
|
||||
u8::from(h.film_grain_params.apply_grain),
|
||||
u8::from(h.segmentation_params.segmentation_enabled),
|
||||
u8::from(h.use_superres),
|
||||
u8::from(h.allow_warped_motion),
|
||||
u8::from(h.use_ref_frame_mvs),
|
||||
u8::from(h.skip_mode_present),
|
||||
u8::from(h.reference_select),
|
||||
h.tile_info.tile_cols,
|
||||
h.tile_info.tile_rows,
|
||||
h.loop_filter_params.loop_filter_level,
|
||||
h.loop_filter_params.loop_filter_sharpness,
|
||||
u8::from(h.loop_filter_params.loop_filter_delta_enabled),
|
||||
u8::from(h.loop_filter_params.loop_filter_delta_update),
|
||||
h.loop_filter_params.loop_filter_ref_deltas,
|
||||
h.loop_filter_params.loop_filter_mode_deltas,
|
||||
h.cdef_params.cdef_bits,
|
||||
h.loop_restoration_params.frame_restoration_type,
|
||||
refs.trim_end(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let luid = pinned_adapter();
|
||||
let mut decoder = NativeD3d11Decoder::new(Codec::Av1, StreamFormat::SDR_420_8, luid, false)
|
||||
.expect("the box must host AV1 Profile 0");
|
||||
let mut readback = Readback {
|
||||
ctx: decoder.context.clone(),
|
||||
staging: None,
|
||||
};
|
||||
// Raw NV12 for a few display frames is kept as well as its hash, so a
|
||||
// divergence can be classified by plane and magnitude against a vendor whose
|
||||
// hash at that same frame MATCHES the golden. It has to be captured inside
|
||||
// the loop: surfaces are recycled, so by the end of the run the slot that
|
||||
// held an early picture holds someone else's pixels.
|
||||
let dump_tag = std::env::var("PF_AV1_DUMP").ok();
|
||||
let wanted: Vec<u64> = if dump_tag.is_some() {
|
||||
[3usize, 4, 10, 63, 64]
|
||||
.iter()
|
||||
.filter_map(|&n| order.display.get(n).copied())
|
||||
.collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let mut by_id: HashMap<u64, String> = HashMap::new();
|
||||
for (index, unit) in units.iter().enumerate() {
|
||||
decoder.decode_av1(unit).expect("decode");
|
||||
for &id in &order.per_unit[index] {
|
||||
let (slot, f, pool) = {
|
||||
let session = decoder.session.as_ref().expect("session");
|
||||
let slot = session.slots.slot_of(id).expect("slot");
|
||||
let f = session.held[usize::from(slot)].expect("facts");
|
||||
(slot, f, session.pool.clone())
|
||||
};
|
||||
let bytes =
|
||||
readback.read(&decoder.device, &pool, u32::from(slot), (f.width, f.height));
|
||||
if wanted.contains(&id) {
|
||||
let tag = dump_tag.as_deref().unwrap_or("x");
|
||||
let path = std::env::temp_dir().join(format!("pf-nv12-{tag}-pic{id}.bin"));
|
||||
std::fs::write(&path, &bytes).expect("write the dump");
|
||||
eprintln!("dumped pic {id} -> {}", path.display());
|
||||
}
|
||||
by_id.insert(id, sha256_hex(&bytes));
|
||||
}
|
||||
}
|
||||
|
||||
eprintln!("=== MAP BEGIN ===");
|
||||
for (n, (id, golden)) in order.display.iter().zip(goldens.iter()).enumerate() {
|
||||
let got = by_id.get(id).expect("decoded");
|
||||
eprintln!(
|
||||
"disp {n:3} pic {id:3} {} | {}",
|
||||
if got == golden { "OK " } else { "BAD" },
|
||||
facts.get(id).map(String::as_str).unwrap_or("?")
|
||||
);
|
||||
}
|
||||
eprintln!("=== HIDDEN ===");
|
||||
let mut h: Vec<u64> = hidden.into_iter().collect();
|
||||
h.sort_unstable();
|
||||
for id in h {
|
||||
eprintln!(
|
||||
"hidden pic {id:3} | {}",
|
||||
facts.get(&id).map(String::as_str).unwrap_or("?")
|
||||
);
|
||||
}
|
||||
eprintln!("=== MAP END ===");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs a Windows D3D11 video device (see module docs)"]
|
||||
fn av1_every_delivered_frame_hashes_bit_identical_to_libavcodec() {
|
||||
|
||||
Reference in New Issue
Block a user