feat(host): isolate the zero-copy GPU import in a worker process
The tiled EGL/GL→CUDA import crashed the whole host (SIGSEGV inside libnvidia-eglcore via cuGraphicsMapResources) when the compositor invalidated an imported dmabuf mid-map — reproduced on the Bazzite F44 Game→Desktop switch (design/zerocopy-hardening-handoff.md). A driver SIGSEGV is uncatchable in-process, so the whole EglImporter (tiled EGL/GL→CUDA and LINEAR Vulkan→CUDA) now runs in a per-capture `zerocopy-worker` subprocess: dmabuf fds go over a SEQPACKET socketpair (SCM_RIGHTS, sent once per buffer keyed by dmabuf st_ino; NeedFd resend self-heals cache desync), frames come back as CUDA-IPC pooled device buffers (still zero-copy, +one socket RTT/frame). Worker death poisons the capturer so the existing capture-loss rebuild runs — the host survives; 3 consecutive deaths latch the GPU import off (CPU/SHM path). PUNKTFUNK_ZEROCOPY_INPROC=1 keeps the old in-process import for debugging/A-B. Also fixed along the way: a failed *tiled* import no longer falls through to the CPU mmap de-pad (which scrambled tiled bytes; LINEAR keeps the fallback); Nv12Blit dropped its GL textures while still CUDA-registered (unregister now runs first); GlBlit had no Drop at all (GL objects leaked per size change); VkBridge's per-fd src cache is now invalidated on renegotiation/eviction instead of never. Design: design/zerocopy-worker-isolation.md. Unit tests: 14 new (protocol fd-passing, worker dispatch, client handshake/death/NeedFd, death latch). On-glass validated on the RTX 5070 Ti/GNOME box (.21): the worker path streams at p50 1.30 ms (NV12, 1800 frames 0-mismatched, parity with the in-process path), and a kill -9 of the worker mid-stream is survived by the host and recovered — poison -> capture lost, rebuilding pipeline in place -> a fresh worker in ~185 ms -> streaming resumes (2385 frames, 0 mismatched). A real KWin compositor-crash repro is still pending (a worker kill -9 is strictly harsher, so it corroborates). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -270,6 +270,27 @@ impl GlBlit {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GlBlit {
|
||||
fn drop(&mut self) {
|
||||
// Unregister the CUDA graphics resource BEFORE deleting the GL texture it wraps (see
|
||||
// `Nv12Blit::drop` — same ordering hazard). Previously `GlBlit` had no `Drop` at all, so
|
||||
// its GL objects leaked on every size change and on importer teardown.
|
||||
self.registered.release();
|
||||
// SAFETY: these GL names were all created by THIS `GlBlit` in `GlBlit::new` on the current
|
||||
// GL context, still current here (the owning `EglImporter` drops on its single capture
|
||||
// thread and never releases the context). Each `glDelete*` gets a count of 1 and a `&u32`
|
||||
// to one live field; the symbols dispatch through libGL to the driver for the current
|
||||
// context. Each name is deleted exactly once, after its CUDA registration was released.
|
||||
unsafe {
|
||||
glDeleteTextures(1, &self.dst_tex);
|
||||
glDeleteTextures(1, &self.src_tex);
|
||||
glDeleteFramebuffers(1, &self.fbo);
|
||||
glDeleteVertexArrays(1, &self.vao);
|
||||
glDeleteProgram(self.program);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-size GL machinery to convert a dmabuf EGLImage into an NV12 (BT.709 limited-range) pair —
|
||||
/// the [`GlBlit`] analogue for the `PUNKTFUNK_NV12` path. Two passes share `src_tex`: a full-res Y
|
||||
/// pass into a CUDA-registrable `GL_R8` texture and a half-res UV pass into a `GL_RG8` texture.
|
||||
@@ -417,6 +438,12 @@ impl Nv12Blit {
|
||||
|
||||
impl Drop for Nv12Blit {
|
||||
fn drop(&mut self) {
|
||||
// Unregister the CUDA graphics resources BEFORE deleting the GL textures they wrap.
|
||||
// `Drop::drop` runs before the fields' own drops, so without this the `glDeleteTextures`
|
||||
// below would destroy `y_tex`/`uv_tex` while still CUDA-registered — leaving the driver a
|
||||
// registration onto freed GL state (the stale-driver-state class that crashed this path).
|
||||
self.y_registered.release();
|
||||
self.uv_registered.release();
|
||||
// SAFETY: these GL names (textures/FBOs/VAO/programs) were all created by THIS `Nv12Blit`
|
||||
// in `Nv12Blit::new` on the current GL context, which is still current because the owning
|
||||
// `EglImporter` is dropped on its single capture thread (fields drop before
|
||||
@@ -424,7 +451,8 @@ impl Drop for Nv12Blit {
|
||||
// pointer to that many names: `&self.y_tex`/`&self.vao` are `&u32` to one live field (n=1);
|
||||
// `[self.y_fbo, self.uv_fbo].as_ptr()` points at a 2-element temporary that lives for the
|
||||
// whole `glDeleteFramebuffers` call (n=2 matches). The symbols dispatch through libGL
|
||||
// (libglvnd) to the driver for the current context. Each name is deleted exactly once.
|
||||
// (libglvnd) to the driver for the current context. Each name is deleted exactly once,
|
||||
// after its CUDA registration was released above.
|
||||
unsafe {
|
||||
glDeleteTextures(1, &self.y_tex);
|
||||
glDeleteTextures(1, &self.uv_tex);
|
||||
@@ -637,6 +665,22 @@ impl EglImporter {
|
||||
)
|
||||
}
|
||||
|
||||
/// Drop the Vulkan bridge's cached per-fd import (see [`super::vulkan::VkBridge::forget_fd`]).
|
||||
/// No-op when the bridge hasn't been built (tiled-only captures).
|
||||
pub fn forget_linear_fd(&mut self, fd: i32) {
|
||||
if let Some(vk) = self.vk.as_mut() {
|
||||
vk.forget_fd(fd);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tear down the whole LINEAR-path import cache (the Vulkan bridge and every per-fd source
|
||||
/// buffer in it). Called when the PipeWire stream renegotiates — the buffer pool the cache
|
||||
/// keyed on is gone, and a recycled fd number must never resolve to a stale import. The
|
||||
/// bridge lazily rebuilds on the next LINEAR frame (renegotiations are rare).
|
||||
pub fn clear_linear_cache(&mut self) {
|
||||
self.vk = None;
|
||||
}
|
||||
|
||||
/// The DRM format modifiers the NVIDIA EGL stack can import for `fourcc`, via
|
||||
/// `eglQueryDmaBufModifiersEXT`. We advertise these to PipeWire so the compositor allocates
|
||||
/// a dmabuf in a layout we can import. Empty on failure (caller falls back).
|
||||
|
||||
Reference in New Issue
Block a user