The Case of the Hoarded Gigabytes
Found and fixed the leak that made every headless render permanently keep about 100MB: the bindless texture registry was immortalizing its texture views, and each one pinned an entire GPU device. Render farms, exports, and CI machines can breathe again.


Render the same scene ten times in a row, in one process, each time creating a fresh GPU context and dropping it when done. Memory use should look like a heartbeat: up, down, up, down. Ours looked like a staircase with no down. On the CI machines, which rasterize on the CPU and therefore keep every texture in ordinary RAM, one test binary marched from 1.8GB to 7.8GB and then started eating swap. Locally nobody noticed, because a real GPU hides the bodies in VRAM.
The suspect had a note explaining the crime
The hunt was a process of elimination. A minimal create-render-drop loop in raw wgpu released everything, which acquitted the GPU library and the driver. A pure 2D patch released everything, which acquitted the compositor. Any 3D patch leaked about 100MB per render, regardless of what was in it; a lone gray box leaked just as enthusiastically as a chrome sphere with an environment bake. A fixed cost, per render, independent of content: that is the signature of something holding an entire subsystem hostage.
The hostage was the device itself. The bindless texture registry, which lets materials sample from thousands of textures in one draw, needed its texture references to live as long as the renderer. The original implementation achieved this with the bluntest tool in Rust: leak them, on purpose, forever. The note in the code explained the reference would “drop at app exit.” True for an artist’s session with one renderer. False for every export, render-farm job, and test run that creates renderers in a loop. And a leaked texture view does not just keep one texture alive: it keeps the whole GPU device alive, along with every memory pool that device ever filled. One leak, one device, one hundred megabytes, every render.
The fix gives the registry actual ownership of its views, with the same zero-allocation lookup the hot path requires, so everything is released the moment the renderer goes away. Two tripwires now stand guard: one renders a scene eight times and fails if memory does not plateau, and one runs the minimal wgpu loop so that if a future driver update starts hoarding, the blame lands on the right doorstep.
Memory now goes up, does its job, and comes back down. Revolutionary stuff.