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 <noreply@anthropic.com>
This commit is contained in:
@@ -84,17 +84,35 @@ fn rgb_request() -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `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
|
/// Live RGB-direct session config: the chroma-siting bits the session was created with
|
||||||
/// (chosen from what the driver advertises — see [`probe_rgb_direct`]).
|
/// (chosen from what the driver advertises — see [`probe_rgb_direct`]).
|
||||||
struct RgbDirect {
|
struct RgbDirect {
|
||||||
x_offset: u32, // vk_valve_rgb::CHROMA_OFFSET_*
|
x_offset: u32, // vk_valve_rgb::CHROMA_OFFSET_*
|
||||||
y_offset: u32,
|
y_offset: u32,
|
||||||
/// The mode is not 64x16-aligned, so the captured buffer cannot be the encode source
|
/// 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
|
/// under the session's ALIGNED source extent (the EFC read past it — the 2026-07-20 field
|
||||||
/// frame is copied into a per-slot ALIGNED BGRA staging image with the edge rows/columns
|
/// GPU hang, when the source `codedExtent` was the aligned size and RADV therefore derived
|
||||||
/// duplicated into the padding (transfer-only, no shader) and encoded from there. Aligned
|
/// ZERO firmware padding). Each frame is copied into a per-slot ALIGNED BGRA staging image
|
||||||
/// modes keep the true zero-copy import.
|
/// 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,
|
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
|
/// 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)
|
probe_rgb_direct(&instance, &vq_inst, pd, codec_op, av1)
|
||||||
};
|
};
|
||||||
let rgb_cfg: Option<RgbDirect> = match (&rgb_probe, want_rgb) {
|
let rgb_cfg: Option<RgbDirect> = match (&rgb_probe, want_rgb) {
|
||||||
(Ok((x, y)), true) => Some(RgbDirect {
|
(Ok((x, y)), true) => {
|
||||||
x_offset: *x,
|
let true_extent = !aligned && rgb_true_extent_request();
|
||||||
y_offset: *y,
|
Some(RgbDirect {
|
||||||
padded: !aligned,
|
x_offset: *x,
|
||||||
}),
|
y_offset: *y,
|
||||||
|
padded: !aligned && !true_extent,
|
||||||
|
true_extent,
|
||||||
|
})
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
if native_nv12 {
|
if native_nv12 {
|
||||||
@@ -665,6 +687,16 @@ impl VulkanVideoEncoder {
|
|||||||
} else {
|
} else {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
rgb_direct = match (&rgb_probe, want_rgb, &rgb_cfg) {
|
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: false, .. })) => "active",
|
||||||
(_, _, Some(RgbDirect { padded: true, .. })) =>
|
(_, _, Some(RgbDirect { padded: true, .. })) =>
|
||||||
"active(padded-copy: mode is not 64x16-aligned — staging blit + edge \
|
"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 {
|
let (src_img, src_view, acquire, compute_active) = match &frame.payload {
|
||||||
FramePayload::Dmabuf(d) if !padded => {
|
FramePayload::Dmabuf(d) if !padded => {
|
||||||
// Defense in depth for the OOB class the alignment gate closes at open: the
|
// 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
|
// imported buffer must cover the source extent the encode declares — the FULL
|
||||||
// (VM faults → VCN ring hang → GPU reset, the 2026-07-20 field report). A
|
// aligned coded extent normally (or the EFC reads past it: VM faults → VCN
|
||||||
// mismatched frame (mid-flight mode change, odd capture) errors out here and
|
// ring hang → GPU reset, the 2026-07-20 field report), the render size in
|
||||||
// takes the encoder-rebuild path instead of faulting the GPU.
|
// true-extent mode (where the declared source codedExtent shrinks with it and
|
||||||
if frame.width != self.width || frame.height != self.height {
|
// 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!(
|
bail!(
|
||||||
"vulkan-encode (rgb-direct): frame {}x{} does not cover the coded \
|
"vulkan-encode (rgb-direct): frame {}x{} does not cover the declared \
|
||||||
extent {}x{} — refusing an out-of-bounds encode source",
|
source extent {}x{} — refusing an out-of-bounds encode source",
|
||||||
frame.width,
|
frame.width,
|
||||||
frame.height,
|
frame.height,
|
||||||
self.width,
|
need_w,
|
||||||
self.height
|
need_h
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?;
|
let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?;
|
||||||
@@ -2547,10 +2587,8 @@ impl VulkanVideoEncoder {
|
|||||||
poc: i32,
|
poc: i32,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use ash::vk::native as h;
|
use ash::vk::native as h;
|
||||||
// Setup/reference AND source share ONE coded extent. App-aligned sessions (CSC, RGB)
|
// Setup/reference extent: the aligned size for app-aligned sessions (CSC, RGB — it
|
||||||
// use the aligned size — their sources cover it by construction. Native NV12 runs
|
// pairs with their aligned SPS), the render size for native NV12's true-size headers.
|
||||||
// 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`).
|
|
||||||
let ext2d = if self.native_nv12 {
|
let ext2d = if self.native_nv12 {
|
||||||
vk::Extent2D {
|
vk::Extent2D {
|
||||||
width: self.render_w,
|
width: self.render_w,
|
||||||
@@ -2562,6 +2600,17 @@ impl VulkanVideoEncoder {
|
|||||||
height: self.height,
|
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 ref_poc = if is_idr { 0 } else { self.slot_poc[ref_slot] };
|
||||||
|
|
||||||
let mut pic_flags: h::StdVideoEncodeH265PictureInfoFlags = std::mem::zeroed();
|
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());
|
dev.cmd_begin_query(cmd, query_pool, 0, vk::QueryControlFlags::empty());
|
||||||
let src_res = vk::VideoPictureResourceInfoKHR::default()
|
let src_res = vk::VideoPictureResourceInfoKHR::default()
|
||||||
.coded_extent(ext2d)
|
.coded_extent(src_extent)
|
||||||
.image_view_binding(src_view);
|
.image_view_binding(src_view);
|
||||||
let mut enc = vk::VideoEncodeInfoKHR::default()
|
let mut enc = vk::VideoEncodeInfoKHR::default()
|
||||||
.dst_buffer(bs_buf)
|
.dst_buffer(bs_buf)
|
||||||
@@ -2776,10 +2825,8 @@ impl VulkanVideoEncoder {
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
use super::vk_av1_encode as av1;
|
use super::vk_av1_encode as av1;
|
||||||
use ash::vk::native as h;
|
use ash::vk::native as h;
|
||||||
// Setup/reference AND source share ONE coded extent. App-aligned sessions (CSC, RGB)
|
// Setup/reference extent: the aligned size for app-aligned sessions (CSC, RGB — it
|
||||||
// use the aligned size — their sources cover it by construction. Native NV12 runs
|
// pairs with their aligned SPS), the render size for native NV12's true-size headers.
|
||||||
// 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`).
|
|
||||||
let ext2d = if self.native_nv12 {
|
let ext2d = if self.native_nv12 {
|
||||||
vk::Extent2D {
|
vk::Extent2D {
|
||||||
width: self.render_w,
|
width: self.render_w,
|
||||||
@@ -2791,6 +2838,17 @@ impl VulkanVideoEncoder {
|
|||||||
height: self.height,
|
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) ----
|
// ---- required AV1 frame sub-structs (single tile; no CDEF/LR/segmentation/global-motion) ----
|
||||||
let mut tile_flags: h::StdVideoAV1TileInfoFlags = std::mem::zeroed();
|
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());
|
dev.cmd_begin_query(cmd, query_pool, 0, vk::QueryControlFlags::empty());
|
||||||
let src_res = vk::VideoPictureResourceInfoKHR::default()
|
let src_res = vk::VideoPictureResourceInfoKHR::default()
|
||||||
.coded_extent(ext2d)
|
.coded_extent(src_extent)
|
||||||
.image_view_binding(src_view);
|
.image_view_binding(src_view);
|
||||||
let mut enc = vk::VideoEncodeInfoKHR::default()
|
let mut enc = vk::VideoEncodeInfoKHR::default()
|
||||||
.dst_buffer(bs_buf)
|
.dst_buffer(bs_buf)
|
||||||
|
|||||||
Reference in New Issue
Block a user