fix(client): the native VAAPI rung stopped dropping decoded frames on the floor
`finish` showed `outputs.last()` and retired every other picture an access unit bumped out of the DPB without ever displaying it, and nothing flushed the DPB at end of stream. Measured on .25 against the vendored vectors: 225 of 250 frames for H.264, 204 of 250 for H.265, 45 of 50 for HEVC Main 10. D3D11VA and Vulkan deliver every frame, so this was the rung's alone. All four legs now deliver 250 / 250 / 50 / 250. The same function carried a second defect. `DmabufFrame::keyframe` was stamped with the CURRENT access unit's `is_idr`, not the flag of the picture it was about to display, and on a reordering stream those are different pictures: the IDR is bumped out several units after it decodes and arrived flagged `false` on all three legs' first frame, while a later AU draining the DPB flagged some old trailing picture as a keyframe. That field is `DecodedImage::is_keyframe`, the pump's post-loss re-anchor signal, so a mislabel re-anchors on the wrong frame. Three changes, all inside this rung: * **A deliverable queue**, the same shape as `video_vk_native`'s — extend, ship the front, trim the oldest past the bound, count and rate-limit the drops into `DecodeHealth::dropped`. Its DEPTH is derived differently and the divergence is documented: the Vulkan rung's bound is `HOLD_HEADROOM - PIPELINE_HOLD` = 1 because a queued frame there counts against the pool ON TOP of the DPB's own residency. Here the three claims are disjoint and a bumped picture MOVES from `pending`/slot to `held`, so the queue inherits the claim rather than adding one. The bound is the DPB's depth — the deepest carry-over a bump can leave — and the measured cost is at most one surface (zero on H.264, whose three seven-picture IDR drains are the deepest bursts these vectors have). A bound of 1 would have left 235 of 250 on H.264, most of the defect still in place. * **An end-of-stream flush.** This rung has no EOS signal and cannot have one: the pump feeds access units until the session ends and then drops the decoder. So `flush` has the two honest callers — `Drop`, where nothing can be presented and the job is to release the queue's surfaces and the DPB's before the pool goes, and a caller that KNOWS the stream ended, which today is the conformance harness. One walk, not a production path and an untested teardown path. AV1 needs none: it shows at most one frame per temporal unit and buffers nothing, which its 250/250 says out loud. * **`PictureFacts` recorded when a picture decodes**, and read back when it is displayed. `keyframe` was the defect; `color` and `display` are the same mistake one field along — an in-band HDR switch changes the VUI mid-stream and AV1's render region is per-frame, so a queued frame shown two units later would have been drawn with the newest picture's signalling. Concealment answers `Ok(None)` and deliberately does NOT drain the queue, which is the Vulkan rung's order and is load-bearing: `clears_demotion_streak` is `delivered || !concealed`, so shipping a queued frame on a concealed AU would zero the streak and take away the escape hatch that stops a rung concealing forever from holding a frozen picture. The three delivered-count assertions moved with the fix, and so did the CPU derivation that reproduces them without a GPU — it now simulates the whole delivery model (ledger, queue, one-per-AU hand-off, flush) in the order `decode` does it, and carries the old behaviour beside the new one as a counterfactual: a queue bound of 0 with no flush still reproduces 225/204/45 exactly, and the test fails if it ever stops being SHORT. `settle` was split out as the pure half of `finish` so the claim walk, the display ordering and the picture facts are all assertable with no device; `the_queue_never_needs_a_surface_the_pool_does_not_have` runs the surface-lifetime arithmetic over the real vectors and pins the peak claims (9 of a 16-surface pool on H.264, 8 of 14 on both HEVC vectors), with an unbounded queue as the counterfactual that shows the bound doing its job. Gates run: `cargo fmt --all -- --check`, `cargo clippy -p pf-client-core -p pf-vaadec --all-targets --features sdl3/build-from-source -- -D warnings`, `cargo test -p pf-client-core --lib --features sdl3/build-from-source` (176 pass), the same filtered to `video_vaapi_native -- --include-ignored` (23 pass, 0 ignored) and `cargo test -p pf-vaadec` (48 pass) — all on .25 (Radeon 780M, RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23); plus `cargo fmt --all -- --check` and `cargo clippy --workspace --all-targets -- -D warnings` in pf-lxcheck2.
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
//! | 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 | **yes** — 250/250 delivered frames bit-identical to libavcodec on an RTX 3500 Ada AND an Intel Arc (2026-08-07). It got there from 186/250 and 245/250 DIVERGING frames on those same two GPUs: `plan_to_dxva_av1` released the picture this frame's own refresh displaces before assigning the decode target its slot, and `SlotMap::assign` hands back the slot just vacated — so 268 of the vector's 274 frames named one surface as both `CurrPicTextureIndex` and a `RefFrameMapTextureIndex` entry. Intel followed the aliased surface (structurally wrong from display frame 4); NVIDIA tolerated it until the `order_hint` wrap at 64 made one 16x24 luma block depend on it. ONE defect, two driver tolerances — the two unlike signatures were not two bugs. TWO vendors, still NO soak on the goldens: the 5-minute 4K60 soak this row used to cite measured throughput, and "streams cleanly" was true throughout the failure |
|
||||
//! | 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 (Main / Main 10) | **not proven** — but all three have now DECODED, on `.25` (Radeon 780M, RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23) on 2026-08-07: every access unit of the vendored H.264 (250), H.265 (250) and HEVC Main 10 (50) vectors was accepted with no decode error, NV12 for the 8-bit legs and **P010** for Main 10, all on the same tiled AMD modifier, and `probe_this_machines_libva` reports VLD decode for H.264 High, HEVC Main and HEVC Main 10. Delivered frame counts are 225/204/45 rather than 250/250/50, and that is the RUNG, not the driver: `finish` shows `outputs.last()` and never more, so an access unit that bumps several pictures out of the DPB shows the last and drops the rest, and nothing flushes the DPB at end of stream — a conformance vector reorders, punktfunk's zero-reorder host output never does. Never frame-hash parity-checked, for the same reason as AV1: the rung exports a tiled dmabuf with no CPU-readable image |
|
||||
//! | native VAAPI | | H.264, H.265 (Main / Main 10) | **not proven** — but all three have now DECODED, on `.25` (Radeon 780M, RDNA3, radeonsi, Mesa 26.0.3, VA-API 1.23) on 2026-08-07: every access unit of the vendored H.264 (250), H.265 (250) and HEVC Main 10 (50) vectors was accepted with no decode error, **250/250/50 frames delivered**, NV12 for the 8-bit legs and **P010** for Main 10, all on the same tiled AMD modifier, and `probe_this_machines_libva` reports VLD decode for H.264 High, HEVC Main and HEVC Main 10. The counts were 225/204/45 when these legs were first written, and that was the RUNG rather than the driver: `finish` showed `outputs.last()` and retired the rest unshown, and nothing flushed the DPB at end of stream. Both are fixed — `video_vaapi_native` grew a bounded deliverable queue and a flush, and the keyframe flag now travels with the PICTURE rather than with the access unit that displays it (the pump's post-loss re-anchor signal, mislabelled on every one of these legs' first frames). Never frame-hash parity-checked, for the same reason as AV1: the rung exports a tiled dmabuf with no CPU-readable image |
|
||||
//! | 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
|
||||
@@ -242,8 +242,9 @@ pub struct DecodeHealth {
|
||||
pub worst_run: u32,
|
||||
/// Frames that decoded CORRECTLY and were then discarded without ever being
|
||||
/// shown, because the backend's deliverable queue overflowed
|
||||
/// (`video_vk_native::MAX_DELIVERABLE` — a decoder making more pictures
|
||||
/// display-ready per access unit than the pump can take one at a time).
|
||||
/// (`video_vk_native::MAX_DELIVERABLE` and `video_vaapi_native::max_deliverable`
|
||||
/// — a decoder making more pictures display-ready per access unit than the pump
|
||||
/// can take one at a time).
|
||||
///
|
||||
/// Deliberately its own number and not folded into any of the three above:
|
||||
/// nothing was damaged, nothing was refused and no driver failed, so counting
|
||||
@@ -253,8 +254,8 @@ pub struct DecodeHealth {
|
||||
/// discarding a frame per AU is one running at half the frame rate it thinks
|
||||
/// it is, and before this counter existed it read as perfectly clean.
|
||||
///
|
||||
/// Structurally 0 on every rung but native Vulkan — it is the only one with a
|
||||
/// deliverable queue — and not on the session stats line today; the
|
||||
/// Structurally 0 on every rung but native Vulkan and native VAAPI — the two with
|
||||
/// a deliverable queue — and not on the session stats line today; the
|
||||
/// rate-limited `warn` at the drop site is the field signal, and this is the
|
||||
/// number a stats field would read.
|
||||
pub dropped: u64,
|
||||
@@ -773,8 +774,10 @@ 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. ⚠ 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`
|
||||
/// order. ⚠ All four legs have now decoded on RDNA3 — 250/250/50/250 for
|
||||
/// H.264/H.265/HEVC Main 10/AV1 — but every one of them is unverified for want of
|
||||
/// frame-hash parity, which the tiled dmabuf makes impossible without a readback path
|
||||
/// this rung does not have ([`native_evidence`]) — so `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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user