fix(clients): shared-VkQueue race + swapchain recreate destroy-in-use — the intermittent device-lost stream killer
apple / swift (push) Successful in 1m7s
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m18s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m41s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m43s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m11s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m34s
ci / bench (push) Successful in 6m12s
apple / screenshots (push) Successful in 5m18s
docker / deploy-docs (push) Successful in 27s
flatpak / build-publish (push) Failing after 5m25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m35s
arch / build-publish (push) Successful in 19m55s
deb / build-publish (push) Successful in 18m59s
ci / rust (push) Successful in 20m9s
android / android (push) Successful in 20m11s
apple / swift (push) Successful in 1m7s
ci / web (push) Successful in 1m9s
ci / docs-site (push) Successful in 1m18s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m41s
decky / build-publish (push) Successful in 21s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m43s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m11s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m34s
ci / bench (push) Successful in 6m12s
apple / screenshots (push) Successful in 5m18s
docker / deploy-docs (push) Successful in 27s
flatpak / build-publish (push) Failing after 5m25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 17m35s
arch / build-publish (push) Successful in 19m55s
deb / build-publish (push) Successful in 18m59s
ci / rust (push) Successful in 20m9s
android / android (push) Successful in 20m11s
Live-diagnosed on the RTX box during the adaptive-bitrate A/B: 2 of 3 streams died with VK_ERROR_DEVICE_LOST at stream start or at a mid-stream encoder rebuild, then zombied — the demote-to-software path rebuilt the decoder against the same dead device, FFmpeg wedged inside the rebuild, and the pump flushed a never-draining backlog every 2 s forever. No OS TDR: the client's own Vulkan misuse. Two causes: 1. The presenter creates ONE graphics-family queue and hands FFmpeg's AVVulkanDeviceContext the same family (nb_graphics_queues=1 ⇒ queue 0) for its transfer/compute prep — so the pump thread and the presenter thread submitted to the SAME VkQueue with no shared lock, violating vkQueueSubmit's external-sync rule exactly when FFmpeg puts work on the graphics queue (decoder open / frames-context rebuild = stream start and every ABR encoder re-target). New guard-less QueueLock (FFmpeg's lock_queue/unlock_queue callbacks are a raw pair) shared by all four queue users: FFmpeg (callbacks installed via user_opaque), the presenter's submit/present/wait-idle, and the Skia overlay's flushes. 2. Swapchain recreation destroyed the old swapchain + render semaphores after ONE fence cycle — the fence proves our submit, not the presentation engine's semaphore consumption (VUID-vkDestroySemaphore-05149 + VUID-vkDestroySwapchainKHR-01282 on every recreate). Recreate now drains the queue (vkQueueWaitIdle under the shared lock — safe now that FFmpeg honours it) and destroys immediately; the deferred DisplayGarbage machinery is gone. Resilience: VK_ERROR_DEVICE_LOST anywhere in a present error chain is now fatal — the run loop fails the session loudly instead of demoting to software on a dead device (the zombie path). Verified on the RTX box (RTX 4090 → host .21) under VK_LAYER_KHRONOS_validation: 3/3 stream start/stop cycles clean, then 8 mid-stream encoder rebuilds in one session (4 ABR down-steps under 10% induced loss + 4 clean-link recovery up-steps — the exact scenarios that previously killed the device): 0 device losses, 0 wedges, both recreate VUIDs gone (previously fired on every path). Remaining validation messages are FFmpeg's own video-session VUIDs, untouched by this change. Linux: clippy -D warnings + tests green (home-worker-2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -91,6 +91,10 @@ struct Gpu {
|
||||
device: ash::Device,
|
||||
queue_family_index: u32,
|
||||
context: DirectContext,
|
||||
/// The device's shared queue lock (see `SharedDevice::queue_lock`): Skia submits
|
||||
/// on the presenter's graphics queue, which FFmpeg's decode prep also uses from
|
||||
/// the pump thread — every `flush*`/`submit` below holds this.
|
||||
queue_lock: std::sync::Arc<pf_client_core::video::QueueLock>,
|
||||
// Keep the loader library + instance dispatch alive as long as the DirectContext
|
||||
// (its baked fn pointers live inside libvulkan).
|
||||
_entry: ash::Entry,
|
||||
@@ -157,6 +161,7 @@ impl Drop for SkiaOverlay {
|
||||
unsafe { gpu.device.destroy_image_view(slot.view, None) };
|
||||
drop(slot.surface);
|
||||
}
|
||||
let _q = gpu.queue_lock.guard(); // queue external sync vs FFmpeg's pump
|
||||
gpu.context.flush_and_submit();
|
||||
}
|
||||
}
|
||||
@@ -215,6 +220,7 @@ impl Overlay for SkiaOverlay {
|
||||
device: shared.device.clone(),
|
||||
queue_family_index: shared.queue_family_index,
|
||||
context,
|
||||
queue_lock: shared.queue_lock.clone(),
|
||||
_entry: shared.entry.clone(),
|
||||
_instance: shared.instance.clone(),
|
||||
});
|
||||
@@ -310,15 +316,19 @@ impl Overlay for SkiaOverlay {
|
||||
ctx.pad_pref,
|
||||
ctx.pads,
|
||||
);
|
||||
gpu.context.flush_surface_with_texture_state(
|
||||
&mut slot.surface,
|
||||
&gpu::FlushInfo::default(),
|
||||
Some(&skvk::mutable_texture_states::new_vulkan(
|
||||
skvk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
gpu.queue_family_index,
|
||||
)),
|
||||
);
|
||||
gpu.context.submit(None);
|
||||
{
|
||||
// Queue external sync vs FFmpeg's pump-thread submits (same queue).
|
||||
let _q = gpu.queue_lock.guard();
|
||||
gpu.context.flush_surface_with_texture_state(
|
||||
&mut slot.surface,
|
||||
&gpu::FlushInfo::default(),
|
||||
Some(&skvk::mutable_texture_states::new_vulkan(
|
||||
skvk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
gpu.queue_family_index,
|
||||
)),
|
||||
);
|
||||
gpu.context.submit(None);
|
||||
}
|
||||
self.current = next;
|
||||
self.drawn = Drawn::default(); // stream chrome re-renders when it returns
|
||||
let slot = self.slots[next].as_ref().expect("just rendered");
|
||||
@@ -386,15 +396,19 @@ impl Overlay for SkiaOverlay {
|
||||
|
||||
// Flush on the shared queue, ending in SHADER_READ_ONLY on our family — the
|
||||
// layout the presenter's composite samples (its own barrier covers visibility).
|
||||
gpu.context.flush_surface_with_texture_state(
|
||||
&mut slot.surface,
|
||||
&gpu::FlushInfo::default(),
|
||||
Some(&skvk::mutable_texture_states::new_vulkan(
|
||||
skvk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
gpu.queue_family_index,
|
||||
)),
|
||||
);
|
||||
gpu.context.submit(None);
|
||||
// Lock: queue external sync vs FFmpeg's pump-thread submits (same queue).
|
||||
{
|
||||
let _q = gpu.queue_lock.guard();
|
||||
gpu.context.flush_surface_with_texture_state(
|
||||
&mut slot.surface,
|
||||
&gpu::FlushInfo::default(),
|
||||
Some(&skvk::mutable_texture_states::new_vulkan(
|
||||
skvk::ImageLayout::SHADER_READ_ONLY_OPTIMAL,
|
||||
gpu.queue_family_index,
|
||||
)),
|
||||
);
|
||||
gpu.context.submit(None);
|
||||
}
|
||||
|
||||
self.current = next;
|
||||
self.drawn = want;
|
||||
|
||||
Reference in New Issue
Block a user