Material disposal behavior across Three.js renderer program cache boundaries
0 reputation · 01 Oct 2025, 12:13 UTC
0 reputation · 01 Oct 2025, 12:13 UTC
Three.js documents explicit .dispose() calls as the required mechanism for releasing GPU memory held by geometries, materials, textures, and render targets, because JavaScript garbage collection does not automatically free WebGL buffers. In long-running applications, developers depend on this contract to prevent leaks when objects leave the scene graph.
The uncertainty arises at the boundary between material disposal and the WebGLRenderer's internal program cache. While material.dispose() removes the material's own GPU resources, the renderer may retain the compiled WebGLProgram if another material shares compatible shader parameters or renderer state. This makes it difficult to determine when a shader program becomes eligible for driver-side deallocation, especially since renderer.info.memory counters track geometries and textures but not programs directly, and may lag behind actual reclamation.
Version-sensitive cache eviction semantics and browser WebGL implementation differences further complicate the lifecycle. Applications that dynamically generate materials need a predictable boundary between JavaScript-side disposal and GPU-side program release.
Under what conditions does the Three.js renderer evict a cached WebGLProgram after all materials using it have been disposed? Does renderer.info expose a reliable counter for live shader programs across recent releases, or must developers inspect internal state? What is the documented compatibility boundary between calling material.dispose() and expecting the underlying shader program to be freed without a context loss?
35275 reputation · 01 Oct 2025, 21:32 UTC
Three.js evicts a cached WebGLProgram only after the last material referencing it has been disposed. Eviction is strictly reference-count driven; the renderer does not age out programs or evict them under memory pressure. renderer.info does not expose a reliable public counter for live shader programs in current releases, so developers must inspect internal renderer state to observe program lifetime. The documented compatibility boundary guarantees that material.dispose() releases material-specific GPU resources, but it does not promise immediate deletion of the underlying shader program, particularly when programs are shared across materials.
When material.dispose() is called, the renderer decrements the reference count associated with the material's compiled program. If another material with compatible shader parameters and renderer state still references that program, the program remains cached. Only when the final referencing material is disposed does the count reach zero and the renderer become eligible to release the program from GPU memory.
There is no LRU or automatic eviction based on time or memory pressure. A program that was never rendered may not have a compiled program to release, so disposing an unused material is a no-op for the program cache.
renderer.info.memory tracks geometries and textures, but it does not provide a stable, public counter for live shader programs across recent Three.js releases. Older or experimental properties such as renderer.info.programs are not reliable and may be absent. To count active programs, developers must inspect internal state—for example, the program cache inside WebGLPrograms or a successor manager—or use browser-level WebGL debugging tools. These internals are not part of the public API and can change without notice between minor versions.
The documented contract for material.dispose() covers resources owned by the material itself. The release of the underlying WebGLProgram is treated as an implementation detail of the renderer's cache. Three.js does not guarantee synchronous program deletion at the moment of disposal, and browser WebGL implementations may defer actual driver-side deallocation even after gl.deleteProgram is called. Program release occurs without a context loss, but the timing is asynchronous relative to the JavaScript call.
Because cache internals have been refactored across Three.js versions, verify behavior against your target release rather than assuming stable APIs. A conservative test is to create two materials with identical parameters, render both, dispose one, and confirm the program persists until the second material is disposed. Use a browser GPU profiler or WebGL debug extension to observe actual deletion events after the final dispose(), accounting for driver-side latency.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.