fix(zerocopy/egl): construction can fail late, so teardown must be structural

Three unwind holes in the EGL side, all of the same shape — fallible
construction over raw handles with no Drop to unwind through — and all
retried per frame, so a sustained failure (VRAM pressure is the realistic
one) leaked unboundedly:

- The GlBlit/Nv12Blit/Yuv444Blit constructors interleave GL-object creation
  with fallible steps (FBO completeness, CUDA registration, pool
  allocation). A GlNameGuard now owns every bare GL name until the final
  struct exists; on unwind it deletes them AFTER the RegisteredTexture
  locals unregister (declaration order), preserving the
  unregister-before-delete invariant. gl.rs gets the same treatment for its
  compile paths: the vertex shader on a fragment-compile failure, the
  program on a link failure. (R1)
- EglImporter::new leaked the DRM render-node fd and the whole gbm_device on
  every '?' after their creation (most realistically cuda::context() failing
  on a host where EGL comes up but CUDA does not). Both now live in a
  GbmDevice with its own Drop: destroy the device, then close the fd. (R2)
- EglImporter's manual Drop destroyed the gbm device and closed the fd
  BEFORE field drops ran the blit destructors, which then called
  cuGraphicsUnregisterResource/glDeleteTextures against a dead native
  display — the stale-driver-state class this path once crashed on. The
  Drop impl is gone; teardown is now purely field order (blits and bridge
  first, GbmDevice last), and the blit SAFETY comments cite that order
  instead of the previously-false claim. (R4)

(R1/R2/R4 from design/pf-zerocopy-sweep-handoff.md.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 12:47:54 +02:00
co-authored by Claude Fable 5
parent 8de5ba4092
commit 8d02255703
2 changed files with 166 additions and 52 deletions
+18 -2
View File
@@ -166,9 +166,22 @@ pub(crate) unsafe fn compile_shader(kind: u32, src: &[u8]) -> Result<u32> {
/// Compile+link the fullscreen-triangle program with fragment source `frag` and bind its `image`
/// sampler to texture unit 0.
pub(crate) unsafe fn compile_program_with(frag: &[u8]) -> Result<u32> {
// Callers retry per frame, so every error path below must delete what it created — a leak
// here is unbounded, not one-shot.
let vs = compile_shader(GL_VERTEX_SHADER, VERT_SRC)?;
let fs = compile_shader(GL_FRAGMENT_SHADER, frag)?;
let fs = match compile_shader(GL_FRAGMENT_SHADER, frag) {
Ok(fs) => fs,
Err(e) => {
glDeleteShader(vs);
return Err(e);
}
};
let prog = glCreateProgram();
if prog == 0 {
glDeleteShader(vs);
glDeleteShader(fs);
bail!("glCreateProgram failed");
}
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
@@ -176,7 +189,10 @@ pub(crate) unsafe fn compile_program_with(frag: &[u8]) -> Result<u32> {
glDeleteShader(fs);
let mut ok: c_int = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &mut ok);
ensure!(ok != 0, "GL program link failed");
if ok == 0 {
glDeleteProgram(prog);
bail!("GL program link failed");
}
glUseProgram(prog);
let loc = glGetUniformLocation(prog, c"image".as_ptr());
if loc >= 0 {