fix(encode): Vulkan Video poll() must block per the depth-1 pump contract
apple / swift (push) Successful in 1m20s
windows-host / package (push) Successful in 9m30s
apple / screenshots (push) Successful in 6m39s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 1m2s
ci / bench (push) Successful in 8m45s
decky / build-publish (push) Successful in 20s
android / android (push) Successful in 20m36s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 44s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5m4s
ci / rust (push) Successful in 18m50s
deb / build-publish (push) Successful in 12m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
arch / build-publish (push) Successful in 23m15s
deb / build-publish-host (push) Successful in 11m37s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11m4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 15m29s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 13m4s
docker / deploy-docs (push) Successful in 12s
apple / swift (push) Successful in 1m20s
windows-host / package (push) Successful in 9m30s
apple / screenshots (push) Successful in 6m39s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 1m2s
ci / bench (push) Successful in 8m45s
decky / build-publish (push) Successful in 20s
android / android (push) Successful in 20m36s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 44s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5m4s
ci / rust (push) Successful in 18m50s
deb / build-publish (push) Successful in 12m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
arch / build-publish (push) Successful in 23m15s
deb / build-publish-host (push) Successful in 11m37s
docker / build-push (ci, ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 11m4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 15m29s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 13m4s
docker / deploy-docs (push) Successful in 12s
The session pump's depth-1 loop (capture → submit → poll) treats a poll()
None as "the backend holds the frame internally, re-poll next tick" —
true only of the libav AMF/QSV wrappers, whose codecs genuinely hold ~2
frames. The Vulkan Video backend probed its slot fence with
get_fence_status instead, so the AU of every frame — finished on the
ASIC in ~5 ms — sat unharvested until the tick AFTER the next frame was
submitted: encode_us read as one full frame period (~17 ms at 60 Hz vs
VAAPI's ~5.3 ms on the same VCN, the AMD field report) and every frame
shipped a frame period late — one frame of avoidable glass-to-glass
latency on the path that exists to beat libav VAAPI.
Wait the oldest in-flight slot's fence in poll() instead, matching the
sync NVENC backend's blocking lock_bitstream and the documented
Capturer::pipeline_depth contract ("capture → submit → poll-blocks").
Bounded by ENCODE_FENCE_TIMEOUT_NS like enqueue()'s backpressure wait,
for the same reason: poll runs on the thread the stall recovery runs on,
so a wedged GPU must surface as an error, not park it. None now only
means "nothing submitted". Covers H265 and AV1 (shared poll).
Verified: cargo check + DPB/RPS unit tests green (home-worker-5); the
GPU smoke tests fail at session open on that box's NVIDIA driver
identically on unmodified origin/main — pre-existing, needs the RADV
box for on-glass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2065,18 +2065,34 @@ impl Encoder for VulkanVideoEncoder {
|
||||
|
||||
fn poll(&mut self) -> Result<Option<EncodedFrame>> {
|
||||
// Backpressure-drained frames (already read, oldest) come out first, then the oldest slot
|
||||
// still in flight once its fence signals — both in submission order. Non-blocking: an
|
||||
// unfinished frame returns None so the caller keeps the pipeline moving.
|
||||
// still in flight — both in submission order. BLOCKING, per the depth-1 pump contract
|
||||
// (`Capturer::pipeline_depth`: "capture → submit → poll-blocks", the convention the sync
|
||||
// NVENC backend's `lock_bitstream` follows): the pump polls right after submit and treats
|
||||
// `None` as "the backend holds the frame internally, re-poll next tick" (true only of the
|
||||
// libav AMF/QSV wrappers). A `get_fence_status` probe here therefore deferred every AU to
|
||||
// the NEXT tick — shipped a full frame period after the ASIC finished, so a ~5 ms VCN
|
||||
// encode read as `encode_us`≈interval (~17 ms at 60 Hz, the AMD field report) and cost one
|
||||
// frame of avoidable glass-to-glass latency. `None` now only means "nothing submitted".
|
||||
if let Some(f) = self.pending.pop_front() {
|
||||
return Ok(Some(f));
|
||||
}
|
||||
let Some(&slot) = self.in_flight.front() else {
|
||||
return Ok(None);
|
||||
};
|
||||
// SAFETY: probing a fence + reading back this slot's own owned objects under `&mut self`.
|
||||
let ready = unsafe { self.device.get_fence_status(self.frames[slot].fence)? };
|
||||
if !ready {
|
||||
return Ok(None);
|
||||
// Bounded like `enqueue`'s backpressure wait, and for the same reason: this is the thread
|
||||
// the stall recovery runs on, so a wedged GPU must surface as an error, not park it.
|
||||
// SAFETY: waiting a fence owned by this encoder's slot under `&mut self`.
|
||||
match unsafe {
|
||||
self.device
|
||||
.wait_for_fences(&[self.frames[slot].fence], true, ENCODE_FENCE_TIMEOUT_NS)
|
||||
} {
|
||||
Ok(()) => {}
|
||||
Err(vk::Result::TIMEOUT) => anyhow::bail!(
|
||||
"vulkan-encode: fence for slot {slot} did not signal within {} ms — GPU or \
|
||||
driver wedged; failing the poll so the session can reset",
|
||||
ENCODE_FENCE_TIMEOUT_NS / 1_000_000
|
||||
),
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
self.in_flight.pop_front();
|
||||
// SAFETY: fence signaled ⟹ this slot's CSC+encode is complete; read its bitstream.
|
||||
|
||||
Reference in New Issue
Block a user