From b4fbc94e363686ce57249d8afb2cb979d4de8dd4 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 21 Jul 2026 13:06:50 +0200 Subject: [PATCH] feat(linux/vulkan-encode): PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT bring-up gate Opt-in alternative to RGB-direct's padded-copy staging at unaligned modes: direct-import the visible-size capture and declare the TRUE-SIZE source codedExtent. RADV has derived the VCN session_init firmware padding from srcPictureResource.codedExtent since Mesa 24.2, so the EFC is told the source lacks the alignment rows and the hardware edge-extends them internally -- which also reframes the 2026-07-20 field GPU reset: that crash passed the ALIGNED extent as the source codedExtent (firmware padding zero) over a visible-size buffer. Off by default until a guarded live test proves the EFC front-end honors the padding like the YUV fetch path does; the padded-copy staging remains the shipped behavior. Co-Authored-By: Claude Fable 5 --- .../pf-encode/src/enc/linux/vulkan_video.rs | 114 +++++++++++++----- 1 file changed, 86 insertions(+), 28 deletions(-) diff --git a/crates/pf-encode/src/enc/linux/vulkan_video.rs b/crates/pf-encode/src/enc/linux/vulkan_video.rs index 779572c9..9095a01f 100644 --- a/crates/pf-encode/src/enc/linux/vulkan_video.rs +++ b/crates/pf-encode/src/enc/linux/vulkan_video.rs @@ -84,17 +84,35 @@ fn rgb_request() -> Option { } } +/// `PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=1` (bring-up): at unaligned modes, replace RGB-direct's +/// padded-copy staging with a direct import carrying the TRUE-SIZE source `codedExtent` — RADV +/// derives nonzero VCN firmware padding from it, so the EFC is told the source lacks the +/// alignment rows (see [`RgbDirect::true_extent`]). Off by default until the EFC front-end is +/// proven to honor the padding like the YUV fetch path does. +fn rgb_true_extent_request() -> bool { + std::env::var("PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT").as_deref() == Ok("1") +} + /// Live RGB-direct session config: the chroma-siting bits the session was created with /// (chosen from what the driver advertises — see [`probe_rgb_direct`]). struct RgbDirect { x_offset: u32, // vk_valve_rgb::CHROMA_OFFSET_* y_offset: u32, /// The mode is not 64x16-aligned, so the captured buffer cannot be the encode source - /// directly (the EFC would read past it — the 2026-07-20 field GPU hang). Instead each - /// frame is copied into a per-slot ALIGNED BGRA staging image with the edge rows/columns - /// duplicated into the padding (transfer-only, no shader) and encoded from there. Aligned - /// modes keep the true zero-copy import. + /// under the session's ALIGNED source extent (the EFC read past it — the 2026-07-20 field + /// GPU hang, when the source `codedExtent` was the aligned size and RADV therefore derived + /// ZERO firmware padding). Each frame is copied into a per-slot ALIGNED BGRA staging image + /// with the edge rows/columns duplicated into the padding (transfer-only, no shader) and + /// encoded from there. Aligned modes keep the true zero-copy import. padded: bool, + /// `PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT=1` bring-up alternative to `padded` at unaligned + /// modes: direct-import the visible-size buffer and pass the TRUE-SIZE source + /// `codedExtent` — RADV then programs nonzero firmware padding from it (Mesa ≥ 24.2 + /// derives `session_init` padding from `srcPictureResource.codedExtent`; see + /// [`VulkanVideoEncoder::native_nv12`]), telling the VCN the source lacks the alignment + /// rows. The session/SPS/DPB stay app-aligned. Field-proven for YUV fetches; the EFC RGB + /// front-end honoring the padding is what this gate exists to prove. + true_extent: bool, } /// Stack storage for a complete rgb-chained video profile. Profiled image creation AFTER open @@ -644,11 +662,15 @@ impl VulkanVideoEncoder { probe_rgb_direct(&instance, &vq_inst, pd, codec_op, av1) }; let rgb_cfg: Option = match (&rgb_probe, want_rgb) { - (Ok((x, y)), true) => Some(RgbDirect { - x_offset: *x, - y_offset: *y, - padded: !aligned, - }), + (Ok((x, y)), true) => { + let true_extent = !aligned && rgb_true_extent_request(); + Some(RgbDirect { + x_offset: *x, + y_offset: *y, + padded: !aligned && !true_extent, + true_extent, + }) + } _ => None, }; if native_nv12 { @@ -665,6 +687,16 @@ impl VulkanVideoEncoder { } else { tracing::info!( rgb_direct = match (&rgb_probe, want_rgb, &rgb_cfg) { + ( + _, + _, + Some(RgbDirect { + true_extent: true, .. + }), + ) => + "active(true-extent: unaligned mode, direct import with the true-size \ + source codedExtent — RADV firmware padding covers the alignment rows; \ + PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT bring-up)", (_, _, Some(RgbDirect { padded: false, .. })) => "active", (_, _, Some(RgbDirect { padded: true, .. })) => "active(padded-copy: mode is not 64x16-aligned — staging blit + edge \ @@ -2250,18 +2282,26 @@ impl VulkanVideoEncoder { let (src_img, src_view, acquire, compute_active) = match &frame.payload { FramePayload::Dmabuf(d) if !padded => { // Defense in depth for the OOB class the alignment gate closes at open: the - // imported buffer must cover the FULL coded extent, or the EFC reads past it - // (VM faults → VCN ring hang → GPU reset, the 2026-07-20 field report). A - // mismatched frame (mid-flight mode change, odd capture) errors out here and - // takes the encoder-rebuild path instead of faulting the GPU. - if frame.width != self.width || frame.height != self.height { + // imported buffer must cover the source extent the encode declares — the FULL + // aligned coded extent normally (or the EFC reads past it: VM faults → VCN + // ring hang → GPU reset, the 2026-07-20 field report), the render size in + // true-extent mode (where the declared source codedExtent shrinks with it and + // RADV's firmware padding covers the alignment rows). A mismatched frame + // (mid-flight mode change, odd capture) errors out here and takes the + // encoder-rebuild path instead of faulting the GPU. + let (need_w, need_h) = if self.rgb.as_ref().is_some_and(|r| r.true_extent) { + (self.render_w, self.render_h) + } else { + (self.width, self.height) + }; + if frame.width != need_w || frame.height != need_h { bail!( - "vulkan-encode (rgb-direct): frame {}x{} does not cover the coded \ - extent {}x{} — refusing an out-of-bounds encode source", + "vulkan-encode (rgb-direct): frame {}x{} does not cover the declared \ + source extent {}x{} — refusing an out-of-bounds encode source", frame.width, frame.height, - self.width, - self.height + need_w, + need_h ); } let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?; @@ -2547,10 +2587,8 @@ impl VulkanVideoEncoder { poc: i32, ) -> Result<()> { use ash::vk::native as h; - // Setup/reference AND source share ONE coded extent. App-aligned sessions (CSC, RGB) - // use the aligned size — their sources cover it by construction. Native NV12 runs - // true-size headers: the render-size extent matches the SPS/sequence header, and RADV - // keys the firmware's internal alignment padding off it (see `native_nv12`). + // Setup/reference extent: the aligned size for app-aligned sessions (CSC, RGB — it + // pairs with their aligned SPS), the render size for native NV12's true-size headers. let ext2d = if self.native_nv12 { vk::Extent2D { width: self.render_w, @@ -2562,6 +2600,17 @@ impl VulkanVideoEncoder { height: self.height, } }; + // Source extent additionally drops to the render size in RGB true-extent mode: RADV + // derives the VCN firmware padding from srcPictureResource.codedExtent (Mesa ≥ 24.2), + // so the visible-size import is never read past its extent (see RgbDirect::true_extent). + let src_extent = if self.rgb.as_ref().is_some_and(|r| r.true_extent) { + vk::Extent2D { + width: self.render_w, + height: self.render_h, + } + } else { + ext2d + }; let ref_poc = if is_idr { 0 } else { self.slot_poc[ref_slot] }; let mut pic_flags: h::StdVideoEncodeH265PictureInfoFlags = std::mem::zeroed(); @@ -2732,7 +2781,7 @@ impl VulkanVideoEncoder { } dev.cmd_begin_query(cmd, query_pool, 0, vk::QueryControlFlags::empty()); let src_res = vk::VideoPictureResourceInfoKHR::default() - .coded_extent(ext2d) + .coded_extent(src_extent) .image_view_binding(src_view); let mut enc = vk::VideoEncodeInfoKHR::default() .dst_buffer(bs_buf) @@ -2776,10 +2825,8 @@ impl VulkanVideoEncoder { ) -> Result<()> { use super::vk_av1_encode as av1; use ash::vk::native as h; - // Setup/reference AND source share ONE coded extent. App-aligned sessions (CSC, RGB) - // use the aligned size — their sources cover it by construction. Native NV12 runs - // true-size headers: the render-size extent matches the SPS/sequence header, and RADV - // keys the firmware's internal alignment padding off it (see `native_nv12`). + // Setup/reference extent: the aligned size for app-aligned sessions (CSC, RGB — it + // pairs with their aligned SPS), the render size for native NV12's true-size headers. let ext2d = if self.native_nv12 { vk::Extent2D { width: self.render_w, @@ -2791,6 +2838,17 @@ impl VulkanVideoEncoder { height: self.height, } }; + // Source extent additionally drops to the render size in RGB true-extent mode: RADV + // derives the VCN firmware padding from srcPictureResource.codedExtent (Mesa ≥ 24.2), + // so the visible-size import is never read past its extent (see RgbDirect::true_extent). + let src_extent = if self.rgb.as_ref().is_some_and(|r| r.true_extent) { + vk::Extent2D { + width: self.render_w, + height: self.render_h, + } + } else { + ext2d + }; // ---- required AV1 frame sub-structs (single tile; no CDEF/LR/segmentation/global-motion) ---- let mut tile_flags: h::StdVideoAV1TileInfoFlags = std::mem::zeroed(); @@ -3011,7 +3069,7 @@ impl VulkanVideoEncoder { } dev.cmd_begin_query(cmd, query_pool, 0, vk::QueryControlFlags::empty()); let src_res = vk::VideoPictureResourceInfoKHR::default() - .coded_extent(ext2d) + .coded_extent(src_extent) .image_view_binding(src_view); let mut enc = vk::VideoEncodeInfoKHR::default() .dst_buffer(bs_buf)