diff --git a/examples/files.json b/examples/files.json
index 065ccac7461dea..99d4bc835b326d 100644
--- a/examples/files.json
+++ b/examples/files.json
@@ -455,6 +455,7 @@
"webgpu_sprites",
"webgpu_storage_buffer",
"webgpu_struct_drawindirect",
+ "webgpu_test_memory",
"webgpu_texturegrad",
"webgpu_textures_2d-array",
"webgpu_textures_2d-array_compressed",
diff --git a/examples/screenshots/webgpu_test_memory.jpg b/examples/screenshots/webgpu_test_memory.jpg
new file mode 100644
index 00000000000000..fbaa2cfd538661
Binary files /dev/null and b/examples/screenshots/webgpu_test_memory.jpg differ
diff --git a/examples/webgpu_test_memory.html b/examples/webgpu_test_memory.html
new file mode 100644
index 00000000000000..e604b93edae645
--- /dev/null
+++ b/examples/webgpu_test_memory.html
@@ -0,0 +1,301 @@
+
+
+
+ three.js webgpu - memory test I
+
+
+
+
+
+
+
+
+
+
+
+
+ This example tests memory management with WebGPU renderer.
+
Spheres are created, rendered, and disposed in each frame.
+
+
+
+
+
+
+
+
+
diff --git a/src/lights/DirectionalLight.js b/src/lights/DirectionalLight.js
index 6eb0da6b26c77c..ce9682fffec79b 100644
--- a/src/lights/DirectionalLight.js
+++ b/src/lights/DirectionalLight.js
@@ -80,6 +80,8 @@ class DirectionalLight extends Light {
dispose() {
+ super.dispose();
+
this.shadow.dispose();
}
diff --git a/src/lights/Light.js b/src/lights/Light.js
index 47b0b5004678f8..4d81b98fe10c5a 100644
--- a/src/lights/Light.js
+++ b/src/lights/Light.js
@@ -54,7 +54,7 @@ class Light extends Object3D {
*/
dispose() {
- // Empty here in base class; some subclasses override.
+ this.dispatchEvent( { type: 'dispose' } );
}
diff --git a/src/lights/PointLight.js b/src/lights/PointLight.js
index 7e8255985cc769..d760abfa45f6ad 100644
--- a/src/lights/PointLight.js
+++ b/src/lights/PointLight.js
@@ -94,6 +94,8 @@ class PointLight extends Light {
dispose() {
+ super.dispose();
+
this.shadow.dispose();
}
diff --git a/src/lights/SpotLight.js b/src/lights/SpotLight.js
index eb93251abc651a..287437f654378a 100644
--- a/src/lights/SpotLight.js
+++ b/src/lights/SpotLight.js
@@ -147,6 +147,8 @@ class SpotLight extends Light {
dispose() {
+ super.dispose();
+
this.shadow.dispose();
}
diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js
index 7fac36424e280a..46dc98d02560ef 100644
--- a/src/nodes/lighting/AnalyticLightNode.js
+++ b/src/nodes/lighting/AnalyticLightNode.js
@@ -96,6 +96,53 @@ class AnalyticLightNode extends LightingNode {
*/
this.updateType = NodeUpdateType.FRAME;
+ if ( light && light.shadow ) {
+
+ this._shadowDisposeListener = () => {
+
+ this.disposeShadow();
+
+ };
+
+ light.addEventListener( 'dispose', this._shadowDisposeListener );
+
+ }
+
+ }
+
+ dispose() {
+
+ if ( this._shadowDisposeListener ) {
+
+ this.light.removeEventListener( 'dispose', this._shadowDisposeListener );
+
+ }
+
+ super.dispose();
+
+ }
+
+ /**
+ * Frees internal resources related to shadows.
+ */
+ disposeShadow() {
+
+ if ( this.shadowNode !== null ) {
+
+ this.shadowNode.dispose();
+ this.shadowNode = null;
+
+ }
+
+ this.shadowColorNode = null;
+
+ if ( this.baseColorNode !== null ) {
+
+ this.colorNode = this.baseColorNode;
+ this.baseColorNode = null;
+
+ }
+
}
getHash() {
diff --git a/src/nodes/lighting/ShadowFilterNode.js b/src/nodes/lighting/ShadowFilterNode.js
index 37b477e0b47719..b41f42dc754dff 100644
--- a/src/nodes/lighting/ShadowFilterNode.js
+++ b/src/nodes/lighting/ShadowFilterNode.js
@@ -272,3 +272,21 @@ export const getShadowMaterial = ( light ) => {
return material;
};
+
+/**
+ * Disposes the shadow material for the given light source.
+ *
+ * @param {Light} light - The light source.
+ */
+export const disposeShadowMaterial = ( light ) => {
+
+ const material = shadowMaterialLib.get( light );
+
+ if ( material !== undefined ) {
+
+ material.dispose();
+ shadowMaterialLib.delete( light );
+
+ }
+
+};
diff --git a/src/nodes/lighting/ShadowNode.js b/src/nodes/lighting/ShadowNode.js
index 03d4da5f4e7aaa..3ff442311ef53f 100644
--- a/src/nodes/lighting/ShadowNode.js
+++ b/src/nodes/lighting/ShadowNode.js
@@ -17,7 +17,7 @@ import { viewZToLogarithmicDepth } from '../display/ViewportDepthNode.js';
import { lightShadowMatrix } from '../accessors/Lights.js';
import { resetRendererAndSceneState, restoreRendererAndSceneState } from '../../renderers/common/RendererUtils.js';
import { getDataFromObject } from '../core/NodeUtils.js';
-import { getShadowMaterial, BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter } from './ShadowFilterNode.js';
+import { getShadowMaterial, disposeShadowMaterial, BasicShadowFilter, PCFShadowFilter, PCFSoftShadowFilter, VSMShadowFilter } from './ShadowFilterNode.js';
import ChainMap from '../../renderers/common/ChainMap.js';
import { warn } from '../../utils.js';
import { textureSize } from '../accessors/TextureSizeNode.js';
@@ -748,6 +748,8 @@ class ShadowNode extends ShadowBaseNode {
this._currentShadowType = null;
+ disposeShadowMaterial( this.light );
+
if ( this.shadowMap ) {
this.shadowMap.dispose();
diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js
index a909d55420b36b..356317afe559ca 100644
--- a/test/e2e/puppeteer.js
+++ b/test/e2e/puppeteer.js
@@ -35,6 +35,7 @@ const exceptionList = [
'webgpu_postprocessing_sss',
'webgpu_postprocessing_traa',
'webgpu_reflection',
+ 'webgpu_test_memory',
'webgpu_texturegrad',
'webgpu_tsl_vfx_flames',