fix(encode): RGB-direct must not engage on unaligned modes — the EFC reads past the capture buffer (GPU hang)
apple / swift (push) Successful in 1m23s
apple / screenshots (push) Successful in 6m27s
windows-host / package (push) Successful in 9m29s
ci / web (push) Successful in 1m1s
ci / docs-site (push) Successful in 1m15s
ci / bench (push) Successful in 6m8s
android / android (push) Successful in 15m47s
arch / build-publish (push) Successful in 14m49s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 13s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 13s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
decky / build-publish (push) Successful in 25s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 12s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 13s
deb / build-publish (push) Successful in 9m32s
ci / rust (push) Successful in 19m0s
deb / build-publish-host (push) Successful in 13m3s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m8s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 22m27s
docker / deploy-docs (push) Successful in 13s

Field report (commit 25624443, RDNA2/VCN3 host, 1080p): repeated VCN0 VM
protection faults from punktfunk-host, vcn_enc_0.0 ring timeouts, two ring
resets, then a failed reset escalating to a full MODE1 GPU reset with VRAM
loss. Root cause is B1's RGB-direct source: the session's coded extent is
64x16-aligned (1920x1088) but the captured dmabuf is only the real mode
size (1920x1080) — the VCN EFC reads the 8 padding rows past the end of
the buffer (8 x 7680 B = 61 KB, matching the faulting page span exactly).
The CSC shader absorbs alignment by clamping reads and duplicating edges;
RGB-direct has no such stage. Every prior validation surface happened to
be aligned (256x256 smoke, 1440p live box) — 1080p is the common mode
that is not. The stall watchdog's rebuild-and-refault loop then turned a
per-session fault into the GPU death spiral.

Two layers:
- Open-time gate: RGB-direct engages only when the real mode equals the
  aligned coded extent (720p/1440p/4K yes, 1080p no); otherwise the CSC
  path runs and the verdict line says why (unaligned-mode). The padded-
  copy variant that re-enables 1080p is design-doc B2 work.
- Submit-time check: an RGB-direct frame that does not cover the coded
  extent errors out (encoder-rebuild path) instead of importing an
  undersized source.

On-glass (780M, host Mesa 26.0.4): all four smokes pass aligned; at
PF_SMOKE_W/H=250 the gate refuses RGB-direct and the rgb smokes soft-skip.
clippy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
enricobuehler
2026-07-20 20:22:18 +02:00
parent 84e1e5aeb4
commit 3aacec53d8
+36 -6
View File
@@ -526,18 +526,33 @@ impl VulkanVideoEncoder {
// the session's picture format. The probe runs unconditionally: its verdict is the // the session's picture format. The probe runs unconditionally: its verdict is the
// field telemetry that decides where B2 can default this on. // field telemetry that decides where B2 can default this on.
let rgb_probe = probe_rgb_direct(&instance, &vq_inst, pd, codec_op, av1); let rgb_probe = probe_rgb_direct(&instance, &vq_inst, pd, codec_op, av1);
let rgb_cfg: Option<RgbDirect> = match (&rgb_probe, want_rgb) { // ALIGNMENT GATE (field GPU-hang, 2026-07-20): the coded extent is 64x16-aligned but the
(Ok((x, y)), true) => Some(RgbDirect { // captured dmabuf is only the REAL mode size — handing it to the encoder as the direct
// source makes the VCN's EFC read the alignment-padding rows PAST the end of the buffer.
// At 1920x1080 (coded 1088) that is 8 rows = 61 KB of out-of-bounds reads per frame:
// deterministic VM protection faults, vcn_enc ring timeouts, and — through the stall
// watchdog's rebuild-and-refault loop — a full MODE1 GPU reset with VRAM loss. The CSC
// shader absorbs the padding by clamping reads and duplicating the edge; RGB-direct has
// no such stage, so until the padded-copy variant lands (design doc B2) it only engages
// when the mode is already aligned (720p/1440p/4K are; 1080p is NOT).
let aligned = rw == w && rh == h;
let rgb_cfg: Option<RgbDirect> = match (&rgb_probe, want_rgb, aligned) {
(Ok((x, y)), true, true) => Some(RgbDirect {
x_offset: *x, x_offset: *x,
y_offset: *y, y_offset: *y,
}), }),
_ => None, _ => None,
}; };
tracing::info!( tracing::info!(
rgb_direct = match (&rgb_probe, &rgb_cfg) { rgb_direct = match (&rgb_probe, want_rgb, aligned, &rgb_cfg) {
(_, Some(_)) => "active", (_, _, _, Some(_)) => "active",
(Ok(_), None) => "available(off; set PUNKTFUNK_VULKAN_RGB_DIRECT=1)", (Ok(_), true, false, None) =>
(Err(e), None) => e, "unaligned-mode(coded extent is 64x16-aligned; direct source would read \
past the capture buffer — CSC path used; padded-copy variant is B2)",
(Ok(_), false, _, None) => "available(off; set PUNKTFUNK_VULKAN_RGB_DIRECT=1)",
(Err(e), _, _, None) => e,
// (Ok, wanted, aligned) always builds Some above.
(Ok(_), true, true, None) => unreachable!("rgb gate and cfg disagree"),
}, },
"vulkan-encode: EFC RGB-direct encode source (design/vulkan-rgb-direct-encode.md)" "vulkan-encode: EFC RGB-direct encode source (design/vulkan-rgb-direct-encode.md)"
); );
@@ -1753,6 +1768,21 @@ 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) => { FramePayload::Dmabuf(d) => {
// 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 {
bail!(
"vulkan-encode (rgb-direct): frame {}x{} does not cover the coded \
extent {}x{} — refusing an out-of-bounds encode source",
frame.width,
frame.height,
self.width,
self.height
);
}
let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?; let (img, view, fresh) = self.import_cached(d, frame.width, frame.height)?;
let acq = if fresh { let acq = if fresh {
SrcAcquire::RgbFresh SrcAcquire::RgbFresh