Synopsis: EGLImage import of a cross-process dma_buf leaks VRAM when the imported texture is sampled — GPU memory is not reclaimed on eglDestroyImageKHR / glDeleteTextures.
Brief Description:
When a process imports a dma_buf that was allocated by a different process as an EGLImage (via EGL_EXT_image_dma_buf_import), binds it to a GLES texture, and samples it in a draw, the driver allocates GPU memory that is not released when the EGLImage and the texture are destroyed and the source buffer is freed. The retained memory accumulates with the number of distinct imported buffers and is reclaimed only when the importing process exits.
The leak requires both conditions; either one alone is clean:
- the dma_buf is foreign (allocated by a different process), and
- the imported texture is sampled in a draw.
Steps to Reproduce and Frequency:
Frequency: 100%, deterministic. Please find the code to reproduce this issue attached in nvidia-dmabuf-leak-repro.tar.gz (repro_cross_process.c & repro_same_process.c). Every run of the cross-process + sample case leaks; the two control cases never do.
-
Build (Mesa headers for GBM/EGL/GLES2; links against the NVIDIA EGL/GLES runtime):
gcc -O2 -Wall -o repro_cross_process repro_cross_process.c $(pkg-config --cflags --libs gbm egl glesv2)
gcc -O2 -Wall -o repro_same_process repro_same_process.c $(pkg-config --cflags --libs gbm egl glesv2) -
repro_cross_processforks a producer that allocates GBM buffers (GBM_BO_USE_RENDERING, ARGB8888) and passes each buffer’s dma_buf fd to the importer over a unix socket (SCM_RIGHTS). The importer imports each as anEGLImage(EGL_LINUX_DMA_BUF_EXT), optionally binds + samples it, then callsglDeleteTextures+eglDestroyImageKHR; the producer frees each buffer only after the importer acks. At most one buffer is alive at any instant. Each program prints its own per-process GPU memory (parsed fromnvidia-smi) every 2000 iterations. -
Run the leaking case and the three controls:
# argv: <iters> <draw 0|1> <w> <h> <vary 0|1>
./repro_cross_process 20000 1 1920 1080 0 # cross-process, sampled -> LEAKS
./repro_cross_process 20000 0 1920 1080 0 # cross-process, NOT sampled -> no leak
./repro_cross_process 20000 1 1920 1080 1 # cross-process, sampled, varying sizes -> LEAKS (bounded)
# argv: <iters> <mode 2=sample> <w> <h>
./repro_same_process 20000 2 1920 1080 # SAME-process, sampled -> no leak -
Watch the importer’s per-process GPU memory. In the leaking case it climbs from a few MiB into the gigabytes within the first ~2000 iterations and stays there; every control case stays flat at ~6 MiB. Since every
EGLImage, texture, and source buffer is freed each iteration, a correct driver stays flat.
System Configuration:
- System model: Gigabyte B650 AORUS ELITE AX (custom desktop build)
- Operating System: Fedora Linux 44 (Workstation Edition), kernel 7.0.12-201.fc44.x86_64
- Desktop Environment: COSMIC (cosmic-comp) — but the reproducer is standalone and needs no compositor or desktop environment; it renders to an off-screen GBM/EGL pbuffer via the render node
/dev/dri/renderD128. - GPU: NVIDIA GeForce RTX 4090 (24564 MiB), driver 595.71.05
- Monitors — Model and Refresh Rates: 2 outputs — DP-2 @ 3840×2160, HDMI-A-1 @ 2560×1600. Not relevant to the bug; the reproducer is headless.
Linux Bug Report: See attached nvidia-bug-report.log.gz
Additional details:
Observed — importer per-process GPU memory as the loop runs (a correct driver stays flat):
| scenario | importer per-process GPU memory |
|---|---|
| cross-process import + sample, fixed size | 8 MiB → 2278 MiB, then plateaus ~2180 MiB |
| cross-process import, no sample | flat 6 MiB |
| cross-process import + sample, varying sizes | climbs into 1.5–2.0 GiB (bounded) |
| same-process import + sample | flat 6 MiB |
Representative console output of the leaking run (./repro_cross_process 8000 1 1920 1080 0):
xproc importer pid=1801566 iters=8000 draw=1 size=1920x1080
EGL vendor=NVIDIA
[xproc] iter=0 importer_proc=8MiB gpu_total_used_MiB=3289
[xproc] iter=2000 importer_proc=2278MiB gpu_total_used_MiB=5555
[xproc] iter=4000 importer_proc=2178MiB gpu_total_used_MiB=5432
[xproc] iter=6000 importer_proc=2178MiB gpu_total_used_MiB=5432
[xproc] iter=8000 importer_proc=2178MiB gpu_total_used_MiB=5534
Expected: GPU memory allocated to service a sampled cross-process dma_buf import should be reclaimed when its EGLImage and texture are destroyed and the source buffer is released, returning the importer to baseline — as it already does in every other case.
Analysis:
- The 2×2 control matrix (foreign vs. same-process × sampled vs. not) isolates the trigger precisely: VRAM is retained only in the foreign-and-sampled cell. This is consistent with the driver making a private VRAM copy of the foreign buffer on first sample (it cannot sample a foreign-layout buffer in place) and pooling that copy instead of freeing it when the importing
EGLImage/texture is destroyed. - The pool is bounded (it plateaus near ~2.2 GiB even under ~20k imports of distinct buffers, and stays bounded when sizes vary), so this is a non-reclaimed cache rather than an unbounded per-import leak. But it is large, idle-sticky, and reclaimed only on exit, so for a long-lived process such as a compositor it is indistinguishable from a leak.
Impact: Every Wayland compositor on this driver accumulates VRAM across a session and never returns to its idle floor without a restart, because compositing inherently imports and samples client dma_bufs. This was confirmed on cosmic-comp after ruling out compositor-side object leaks, so its residual VRAM is the driver pool, not a compositor bug — and that residual’s magnitude (~2.2–2.4 GiB) matches this reproducer’s ~2.2 GiB plateau.
Open questions:
-
Is the private VRAM copy made for a sampled cross-process dma_buf import pooled and not released when the triggering
EGLImage+ texture are destroyed? -
Is there any way to reclaim this pool short of restarting the process — on
eglDestroyImageKHR+glDeleteTextures, or via any driver / application-profile setting? In our testing it is sticky: with all but one client closed, the compositor still held ~2 GB. -
I see this on 595.71.05; driver 610 (610.43.02 / 610.62) release notes add dma_buf mmap support but list no VRAM-reclaim fix — is it addressed in a newer version?
nvidia-dmabuf-leak-repro.tar.gz (5.0 KB)
nvidia-bug-report.log.gz (508.8 KB)