Skip to content

Conversation

@querielo
Copy link
Contributor

@querielo querielo commented Nov 27, 2025

Summary

Prevent memory leaks caused by ShadowNode instances and their associated resources by:

  • Ensuring LightsNode disposes obsolete ShadowNode instances when lights change and when the node itself is disposed.
  • Resetting internal caches so RenderObjects and their references to meshes can be garbage collected.

The PR partially resolves the issue. At least for now, the GC successfully deallocates meshes when I recreate the light sources.


Background / Problem

A ShadowNode can have associated runtime resources (shadow materials, textures, RenderObject's created for different light nodes)

If we do not dispose ShadowNode instances:

  • Their associated shadow materials are never disposed.
  • This implies their render objects are also never disposed.
  • These render objects keep references to all meshes they were created for.
  • Even if the renderer no longer uses these nodes in renderer._nodes, those meshes remain indirectly reachable via nodes.nodeBuilderCache.

So, as long as a ShadowNode sticks around, the JS GC cannot reclaim:

  • Shadow materials
  • Render objects
  • Meshes referenced by those render objects (geometry, materials, textures)

This makes long-running apps slowly accumulate “dead” meshes, materials, geometries in memory.


There is a bigger problem

WebGPURenderer: Right now it’s too hard to clean memory of meshes, even if the user explicitly disposes geometry, materials and textures and remove references.

The reason is:

  • A mesh “does not know” with which materials it was rendered (scene.overrideMaterial)
  • Each PassNode / override material can create one or more RenderObjects behind the scenes.
  • Those RenderObjects can only be destroyed when the corresponding material is disposed.
  • As a result, it’s not enough to dispose just the resources (geometry, materials, textures).
    You must also dispose all nodes that use overrideMaterial (including ShadowNode and PP nodes), or RenderObjects will stay alive and keep meshes strongly referenced.

There is still a problem that a user have to recreate PP and light sources to clean references on removed meshes so GC can deallocate removed meshes.

@sunag @Mugen87 Should I create an issue?

@querielo querielo changed the title LST: prevent memory leaks caused by LightNodes LST: prevent memory leaks caused by LightsNode Nov 27, 2025
@github-actions
Copy link

github-actions bot commented Nov 27, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 350.26
83.06
350.26
83.06
+0 B
+0 B
WebGPU 613.66
170.42
614.26
170.53
+605 B
+113 B
WebGPU Nodes 612.26
170.14
612.87
170.26
+605 B
+119 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 482.25
117.84
482.25
117.84
+0 B
+0 B
WebGPU 684.79
186.19
685.43
186.31
+632 B
+118 B
WebGPU Nodes 634.63
173.37
635.26
173.48
+632 B
+106 B

@querielo querielo changed the title LST: prevent memory leaks caused by LightsNode TSL: prevent memory leaks caused by LightsNode Nov 27, 2025
@sunag
Copy link
Collaborator

sunag commented Nov 28, 2025

@sunag @Mugen87 Should I create an issue?

If there is a simple way to reproduce this problem, it will certainly help us identify the best way to fix it.

@querielo querielo marked this pull request as draft November 28, 2025 09:09
@querielo querielo marked this pull request as ready for review November 28, 2025 12:20
Copilot AI review requested due to automatic review settings November 28, 2025 12:20
Copilot finished reviewing on behalf of querielo November 28, 2025 12:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses memory leaks in the WebGPU renderer related to LightNode instances and their associated shadow resources. The changes ensure proper disposal of shadow materials, textures, and render objects when lights are disposed or modified, preventing memory accumulation in long-running applications.

Key Changes:

  • Added disposal mechanism for shadow materials through disposeShadowMaterial function
  • Implemented event-driven cleanup where AnalyticLightNode responds to shadow disposal events
  • Extended LightShadow to dispatch disposal events via EventDispatcher

Reviewed changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/nodes/lighting/ShadowFilterNode.js Added disposeShadowMaterial function to properly dispose shadow materials and remove them from the material library
src/nodes/lighting/ShadowNode.js Integrated disposeShadowMaterial call in the _reset() method to clean up shadow materials during node disposal
src/nodes/lighting/AnalyticLightNode.js Added event listener for shadow disposal and disposeShadow() method to clean up shadow-related nodes and restore color nodes
src/lights/LightShadow.js Extended EventDispatcher to enable disposal event dispatching, allowing dependent nodes to respond to shadow cleanup
examples/webgpu_test_memory.html Added memory test example demonstrating proper disposal patterns for meshes, lights, and post-processing effects
examples/screenshots/webgpu_test_memory.jpg Screenshot for the new memory test example
examples/files.json Registered the new memory test example in the examples list
test/e2e/puppeteer.js Added the new example to the test exception list for investigation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

@querielo
Copy link
Contributor Author

@sunag I’ve just updated the PR.

The next video was recorded inside the dev branch. As you can see, there’s no way for the GC to collect the unused memory after enabling and disabling shadows, then recreating the shadow light. NOTE: RenderObject are not cleared until I disable PP and shadows

Video (dev branch):

output2.mp4

Here is the behavior in the PR branch:

output1.mp4

I'll create an issue for this. It would be great to have a way to notify Renderer that a mesh is no longer needed, so it can remove all RenderObject instances associated with that mesh.

@querielo querielo changed the title TSL: prevent memory leaks caused by LightsNode TSL: ShadowNode: prevent memory. Nov 28, 2025
@querielo querielo changed the title TSL: ShadowNode: prevent memory. TSL: ShadowNode: prevent memory leaks Nov 28, 2025
@querielo querielo changed the title TSL: ShadowNode: prevent memory leaks TSL: ShadowNode: prevent memory leaks. Add webgpu_test_memory Nov 28, 2025
@cmhhelgeson
Copy link
Contributor

@querielo
Copy link
Contributor Author

@cmhhelgeson The PR does not include the engine build.

So, we can treat your link as the dev branch, and this link as the PR version (it is a branch with the built engine):
https://raw.githack.com/querielo/three.js/kirill/lighting-memory-leak-build-engine/examples/webgpu_test_memory.html

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.

3 participants