From cc848479c49404e56c0e4eeb548a8c95c019b5a3 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Sun, 26 Jul 2026 10:20:37 +0200 Subject: [PATCH] test(pf-encode): re-point the cpu_img size-change smoke at the CSC guard's contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit vulkan_cpu_img_survives_a_source_size_change drove MISMATCHED source sizes through the then-lenient CSC arm as its vehicle for the staging cache hazard (format-only-keyed cpu_img → OOB copy while submit said Ok). e3354b6d's guard — correctly the equality check every sibling arm always had, against the MODE, not the coded extent, so the padded render-vs-coded tolerance in the direct arms is untouched — makes that scenario unrepresentable through submit and broke the test on main (.25 layers baseline read 13/13 instead of 14/12). Replaced by vulkan_csc_refuses_a_mismatched_source: refusal pinned in BOTH directions, plus the property that actually needs proving — a refused submit does not WEDGE the session (the bail lands after step 1's frame-type bookkeeping; the next well-sized frame must still encode, and an AU must come out). Verified on the 780M under validation layers: 8/8 vulkan tests, full-suite baseline restored to 14/12. The WP4.2 size-keyed staging stays as belt-and-braces; the hazard it fixed is now structurally unreachable through submit. Co-Authored-By: Claude Opus 5 (1M context) --- .../pf-encode/src/enc/linux/vulkan_video.rs | 66 ++++++++++--------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/crates/pf-encode/src/enc/linux/vulkan_video.rs b/crates/pf-encode/src/enc/linux/vulkan_video.rs index 85b944c0..27246779 100644 --- a/crates/pf-encode/src/enc/linux/vulkan_video.rs +++ b/crates/pf-encode/src/enc/linux/vulkan_video.rs @@ -4486,44 +4486,50 @@ mod tests { } } - /// A CPU-capture source that CHANGES SIZE mid-session must not copy past the cached staging - /// image. `ensure_cpu_rgb` keys that image on (format, width, height); when it was keyed on - /// format alone the image kept the FIRST frame's size while `cmd_copy_buffer_to_image` used the - /// current frame's extent, so a same-format size increase wrote out of bounds — and `submit` - /// still returned `Ok`, so nothing upstream noticed. - /// - /// The out-of-bounds copy is only *observable* through the Vulkan validation layers, so run this - /// as: `VK_LOADER_LAYERS_ENABLE='*validation*' cargo test ... -- --ignored`. Confirmed on RADV - /// PHOENIX 2026-07-25: 8 x VUID-vkCmdCopyBufferToImage-imageSubresource-07971 before the fix - /// ("extent.width (512) exceeds imageSubresource width extent (128)"), zero after. + /// The CSC arm REFUSES a source that doesn't match the session mode (the e3354b6d guard — + /// the check every sibling arm always had; a clamped-texelFetch mismatch used to stream a + /// silently cropped/edge-padded picture). This test previously DROVE mismatched sizes through + /// the lenient arm to exercise the per-slot `cpu_img` staging across a size change (the + /// format-only-keyed cache wrote out of bounds while `submit` returned `Ok`); the guard makes + /// that scenario unrepresentable through `submit`, structurally retiring the hazard — the + /// size-keyed staging from that fix stays as belt-and-braces. What's left to pin: + /// refusal in BOTH directions, and that a refused submit does not WEDGE the session — the + /// bail happens after step 1's frame-type bookkeeping, so "the next well-sized frame still + /// encodes" is a real property, not a formality (the host routes the error to its + /// encoder-rebuild path and the session must be able to continue if that path retries). #[test] #[ignore = "needs a real VK_KHR_video_encode_h265 device; meaningful only under validation layers"] - fn vulkan_cpu_img_survives_a_source_size_change() { - // CSC mode (rgb=false) — that is the arm where the image is sized to the SOURCE frame. + fn vulkan_csc_refuses_a_mismatched_source() { + // CSC mode (rgb=false) — the arm the guard covers. let mut enc = VulkanVideoEncoder::open_opts(Codec::H265, 512, 512, 60, 10_000_000, false) .expect("open"); - // `cpu_img` is cached PER RING SLOT, so the small frames must first populate every slot; - // only when the ring wraps does a slot holding a 128x128 image get a 512x512 copy. - eprintln!("phase 1: 8x 128x128 — populates every ring slot with a 128x128 cpu_img"); - for i in 0..8u64 { + enc.submit_indexed(&cpu_frame(512, 512, 0, [40, 40, 200, 255]), 0) + .expect("well-sized baseline"); + while enc.poll().expect("poll").is_some() {} + // Smaller AND larger both refuse — the guard is equality on the MODE (render size), not + // a ceiling; the render-vs-CODED padding tolerance lives in the direct arms, untouched. + let e = enc + .submit_indexed(&cpu_frame(128, 128, 16_666_667, [200, 40, 40, 255]), 1) + .expect_err("smaller source must refuse"); + assert!(e.to_string().contains("mismatched"), "{e:#}"); + let e = enc + .submit_indexed(&cpu_frame(640, 640, 33_333_334, [200, 40, 40, 255]), 2) + .expect_err("larger source must refuse"); + assert!(e.to_string().contains("mismatched"), "{e:#}"); + // The refusals must not wedge the session: a well-sized frame still encodes and an AU + // still comes out the other end. + let mut got_au = false; + for i in 3..11u64 { enc.submit_indexed( - &cpu_frame(128, 128, i * 16_666_667, [40, 40, 200, 255]), + &cpu_frame(512, 512, i * 16_666_667, [40, 200, 40, 255]), i as u32, ) - .expect("submit small"); - while enc.poll().expect("poll").is_some() {} + .expect("well-sized after refusal"); + while let Ok(Some(_)) = enc.poll() { + got_au = true; + } } - eprintln!("phase 2: 8x 512x512 — SAME format, so each slot REUSES its 128x128 image"); - for i in 8..16u64 { - let r = enc.submit_indexed( - &cpu_frame(512, 512, i * 16_666_667, [200, 40, 40, 255]), - i as u32, - ); - r.expect("submit after the source grew"); - while matches!(enc.poll(), Ok(Some(_))) {} - } - let _ = enc.flush(); - while matches!(enc.poll(), Ok(Some(_))) {} + assert!(got_au, "no AU after the refused submits — session wedged"); eprintln!("done — under validation layers this run must report ZERO VUID errors"); }