Skip to content

Dispose GPU resources when the SDF mesh is rebuilt#57

Open
kmatzen wants to merge 1 commit into
mainfrom
fix-gpu-resource-leak
Open

Dispose GPU resources when the SDF mesh is rebuilt#57
kmatzen wants to merge 1 commit into
mainfrom
fix-gpu-resource-leak

Conversation

@kmatzen

@kmatzen kmatzen commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Fixes #53.

SdfMesh dropped its mesh in three places and each only called scene.remove():

  • rebuild() (SdfMesh.ts:188)
  • the cleared-display branch of onStoreChange() (:168)
  • dispose() (:296)

Three.js does not free the compiled program, geometry buffers, or textures when an object leaves the scene graph — scene.remove() only unlinks it. So every structural tree edit leaked a shader program, a geometry, and all of that build's textures. Viewport.tsx:20 calls engine.dispose() on unmount, which compounded it across mount/unmount cycles, including React StrictMode's double-invoke in development.

The codebase already had the right pattern next door: OutlinePass.dispose() disposes correctly, and OutlinePass.update() frees the old geometry before swapping. This is the same rule, missed in the hottest path.

Change

All three sites route through one releaseGpu() helper. Textures need explicit handling because they are reachable only through material.uniforms, not from the mesh — the helper walks the uniforms and disposes any THREE.Texture it finds.

Verification

check result
tsc --noEmit exit 0
new tests, this branch 4/4 pass
new tests, pre-fix code 3 fail, 1 pass

The test that passes pre-fix asserts the mesh leaves the scene, which the old code already did correctly — it just never freed the GPU side. The three that fail are the disposal assertions for material, geometry, and textures across all three drop sites.

Tests construct real three objects against a stub engine ({ scene }); dispose() on these is CPU-side bookkeeping and needs no WebGL context, so they run headless.

Related

#55 (rebuild key ignores hasWarn/textures) should land after this — fixing that key makes rebuilds strictly more frequent, which would have amplified this leak. #32 (cache compiled shaders by tree hash) touches the same function and changes disposal from "always free the old build" to "free on eviction", so it should build on this rather than land alongside it.

🤖 Generated with Claude Code

Fixes #53.

SdfMesh dropped its mesh in three places -- rebuild(), the cleared-display
branch of onStoreChange(), and dispose() -- and each only called
scene.remove(). Three.js does not free the compiled program, geometry
buffers, or textures when an object leaves the scene graph, so every
structural tree edit leaked a shader program, a geometry, and all of that
build's textures. Viewport.tsx calls engine.dispose() on unmount, so a
mount/unmount cycle compounded it, including React StrictMode's double
invoke in development.

All three sites now route through a releaseGpu() helper. Textures are
reachable only through material.uniforms rather than from the mesh, so it
walks the uniforms and disposes any THREE.Texture it finds.

The tests fail 3/4 against the pre-fix code; the one that passes checks
scene removal, which the old code already did.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GPU resources leaked on every shader rebuild

1 participant