From 263f34711a2426825864c00bee7d528b2b1dfc6a Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 26 Aug 2026 10:04:35 +0200 Subject: [PATCH 1/3] SSRNode: Clean up. (#34377) --- examples/jsm/tsl/display/SSRNode.js | 2 ++ examples/webgpu_postprocessing_ssr_denoise.html | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/jsm/tsl/display/SSRNode.js b/examples/jsm/tsl/display/SSRNode.js index c270998844e85d..86d3a4901559e1 100644 --- a/examples/jsm/tsl/display/SSRNode.js +++ b/examples/jsm/tsl/display/SSRNode.js @@ -954,6 +954,8 @@ class SSRNode extends TempNode { sampleEnvReflection = () => { + if ( this._importanceEnvironment === null ) return vec3( 0 ); + const envColor = vec3( 0 ).toVar(); if ( this.envImportanceSampling ) { diff --git a/examples/webgpu_postprocessing_ssr_denoise.html b/examples/webgpu_postprocessing_ssr_denoise.html index 13b7a6a9de5658..3211398176bc9e 100644 --- a/examples/webgpu_postprocessing_ssr_denoise.html +++ b/examples/webgpu_postprocessing_ssr_denoise.html @@ -288,9 +288,7 @@ environmentNode: hdrTexture, envImportanceSampling: params.ssr.envImportanceSampling, binaryRefine: params.ssr.binaryRefine - } ); - ssrNode.setEnvMap( hdrTexture ); - ssrNode.toInspector( 'SSR' ); + } ).toInspector( 'SSR' ); temporalReprojectNode = temporalReproject( ssrNode, scenePassDepth, scenePassNormal, scenePassVelocity, camera, { mode: 'specular', From 9eac32005a3b3d0a8ad8f45d49ca1f5c75bf568c Mon Sep 17 00:00:00 2001 From: 204a Date: Wed, 26 Aug 2026 16:21:36 +0800 Subject: [PATCH 2/3] ClusteredLightsNode: Add `dispose()`, optimize `create()`. (#34375) Co-authored-by: Michael Herzog --- .../jsm/tsl/lighting/ClusteredLightsNode.js | 58 +++++++++++++------ 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/examples/jsm/tsl/lighting/ClusteredLightsNode.js b/examples/jsm/tsl/lighting/ClusteredLightsNode.js index d30c9163aa74cc..43ae8a342adbd3 100644 --- a/examples/jsm/tsl/lighting/ClusteredLightsNode.js +++ b/examples/jsm/tsl/lighting/ClusteredLightsNode.js @@ -56,9 +56,15 @@ class ClusteredLightsNode extends LightsNode { this._lightIndexes = null; this._screenClusterIndex = null; this._compute = null; - this._lightsTexture = null; - this._zSliceRangesTexture = null; - this._zSliceRangesData = null; + + const lightsData = new Float32Array( maxLights * 4 * 2 ); + this._lightsTexture = new DataTexture( lightsData, lightsData.length / 8, 2, RGBAFormat, FloatType ); + + // Per Z-slice light range for Z-culling (CPU-sorted, uploaded each frame) + + this._zSliceRangesData = new Float32Array( zSlices * 4 ); + this._zSliceRangesTexture = new DataTexture( this._zSliceRangesData, zSlices, 1, RGBAFormat, FloatType ); + this._lightViewZ = new Float32Array( maxLights ); this._lightSortOrder = []; @@ -138,8 +144,6 @@ class ClusteredLightsNode extends LightsNode { const zRanges = this._zSliceRangesData; - if ( zRanges === null ) return; - const near = camera.near; const far = camera.far; const NZ = this.zSlices; @@ -418,7 +422,7 @@ class ClusteredLightsNode extends LightsNode { create( width, height ) { - const { tileSize, maxLights, zSlices, maxLightsPerCluster, _chunksPerCluster: chunksPerCluster } = this; + const { tileSize, zSlices, maxLightsPerCluster, _chunksPerCluster: chunksPerCluster } = this; const bufferSize = new Vector2( width, height ); @@ -429,20 +433,25 @@ class ClusteredLightsNode extends LightsNode { this._gridDimensions.value.set( NX, NY ); - // Lights data texture (same layout as TiledLightsNode) + // release the compute pipeline and bindings built for the previous grid - const lightsData = new Float32Array( maxLights * 4 * 2 ); - const lightsTexture = new DataTexture( lightsData, lightsData.length / 8, 2, RGBAFormat, FloatType ); + if ( this._compute !== null ) this._compute.dispose(); - // Per Z-slice light range for Z-culling (CPU-sorted, uploaded each frame) + // Per-cluster light-index storage (ivec4 chunks). + + const lightIndexesLength = clusterCount * chunksPerCluster * 4; + + let lightIndexes = this._lightIndexes; - const zSliceRangesData = new Float32Array( NZ * 4 ); - const zSliceRangesTexture = new DataTexture( zSliceRangesData, NZ, 1, RGBAFormat, FloatType ); + if ( lightIndexes === null || lightIndexes.value.array.length < lightIndexesLength ) { - // Per-cluster light-index storage (ivec4 chunks) + // TODO: Free the outgoing buffer once the renderer destroys the GPU resources of + // standalone storage attributes (`Bindings._destroyBindings()` skips storage buffers). - const lightIndexesArray = new Int32Array( clusterCount * chunksPerCluster * 4 ); - const lightIndexes = attributeArray( lightIndexesArray, 'ivec4' ).setName( 'lightIndexes' ); + const lightIndexesArray = new Int32Array( lightIndexesLength ); + lightIndexes = attributeArray( lightIndexesArray, 'ivec4' ).setName( 'lightIndexes' ); + + } // compute-side accessors (use instanceIndex) @@ -528,7 +537,7 @@ class ClusteredLightsNode extends LightsNode { const index = int( 0 ).toVar(); // Z-culling: only test lights that can reach this cluster's Z-slice - const zRange = textureLoad( zSliceRangesTexture, ivec2( cz, 0 ) ); + const zRange = textureLoad( this._zSliceRangesTexture, ivec2( cz, 0 ) ); const rangeStart = int( zRange.x ); const rangeEnd = int( zRange.y ); @@ -589,9 +598,20 @@ class ClusteredLightsNode extends LightsNode { this._lightIndexes = lightIndexes; this._screenClusterIndex = screenClusterIndex; this._compute = compute; - this._lightsTexture = lightsTexture; - this._zSliceRangesTexture = zSliceRangesTexture; - this._zSliceRangesData = zSliceRangesData; + + } + + /** + * Frees the GPU resources allocated by this node. + */ + dispose() { + + if ( this._compute !== null ) this._compute.dispose(); + + this._lightsTexture.dispose(); + this._zSliceRangesTexture.dispose(); + + super.dispose(); } From 768a10b473e90404085c6dd3594ae69972b723ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Kwas?= Date: Wed, 26 Aug 2026 10:37:08 +0200 Subject: [PATCH 3/3] WebGLRenderer: fix compileAsync() crashing when a tracked material is disposed mid-flight (#34378) Co-authored-by: Michael Herzog --- src/renderers/WebGLRenderer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index cf2b0cc6b10162..ba22a50f721579 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1521,9 +1521,9 @@ class WebGLRenderer { const materialProperties = properties.get( material ); const program = materialProperties.currentProgram; - if ( program.isReady() ) { + if ( program === undefined || program.isReady() ) { - // remove any programs that report they're ready to use from the list + // stop waiting for materials that are ready to use or have been disposed materials.delete( material ); }