From 4433fbb868f763f9830914435362021234b5f130 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 18 Aug 2026 13:29:03 +0200 Subject: [PATCH 1/3] WebGPURenderer: Make cache key for bind groups more robust. (#34288) --- src/renderers/common/Backend.js | 8 ++++---- src/renderers/common/Bindings.js | 8 ++++---- src/renderers/webgl-fallback/WebGLBackend.js | 8 ++++---- src/renderers/webgpu/WebGPUBackend.js | 12 +++++------ .../webgpu/utils/WebGPUBindingUtils.js | 20 +++++++++---------- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index 7e2ec3b7540d12..afd44468947e7e 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -202,10 +202,10 @@ class Backend { * @abstract * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + createBindings( /*bindGroup, bindings, cacheKey, version*/ ) { } /** * Updates the given bind group definition. @@ -213,10 +213,10 @@ class Backend { * @abstract * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + updateBindings( /*bindGroup, bindings, cacheKey, version*/ ) { } /** * Updates a buffer binding. diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js index cffc7412ac9206..996d4be4903a5a 100644 --- a/src/renderers/common/Bindings.js +++ b/src/renderers/common/Bindings.js @@ -262,7 +262,7 @@ class Bindings extends DataMap { // each object defines an array of bindings (ubos, textures, samplers etc.) - this.backend.createBindings( bindGroup, bindings, 0 ); + this.backend.createBindings( bindGroup, bindings, '' ); groupData.bindGroup = bindGroup; groupData.usedTimes = 1; @@ -359,7 +359,7 @@ class Bindings extends DataMap { let needsBindingsUpdate = false; let cacheBindings = true; - let cacheIndex = 0; + let cacheKey = ''; let version = 0; // iterate over all bindings and check if buffer updates or a new binding group is required @@ -443,7 +443,7 @@ class Bindings extends DataMap { } else { - cacheIndex = cacheIndex * 10 + texture.id; + cacheKey += texture.id + ','; version += texture.version; } @@ -496,7 +496,7 @@ class Bindings extends DataMap { if ( needsBindingsUpdate === true ) { - this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheIndex : 0, version ); + this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheKey : '', version ); } diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index fe80a3258522fc..ff4bb212b9407d 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -1875,10 +1875,10 @@ class WebGLBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings /*, cacheIndex, version*/ ) { + createBindings( bindGroup, bindings /*, cacheKey, version*/ ) { if ( this._knownBindings.has( bindings ) === false ) { @@ -1914,10 +1914,10 @@ class WebGLBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( bindGroup /*, bindings, cacheIndex, version*/ ) { + updateBindings( bindGroup /*, bindings, cacheKey, version*/ ) { const { gl } = this; diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index c73621664eddbf..92c647333ab92e 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -2794,12 +2794,12 @@ class WebGPUBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings, cacheIndex, version ) { + createBindings( bindGroup, bindings, cacheKey, version ) { - this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version ); } @@ -2808,12 +2808,12 @@ class WebGPUBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( bindGroup, bindings, cacheIndex, version ) { + updateBindings( bindGroup, bindings, cacheKey, version ) { - this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version ); } diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index b3f020af46e94c..1d0692888703ac 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -137,10 +137,10 @@ class WebGPUBindingUtils { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings, cacheIndex, version = 0 ) { + createBindings( bindGroup, bindings, cacheKey, version = 0 ) { const { backend } = this; const bindingsData = backend.get( bindGroup ); @@ -151,18 +151,18 @@ class WebGPUBindingUtils { let bindGroupGPU; - if ( cacheIndex > 0 ) { + if ( cacheKey !== '' ) { if ( bindingsData.groups === undefined ) { - bindingsData.groups = []; - bindingsData.versions = []; + bindingsData.groups = {}; + bindingsData.versions = {}; } - if ( bindingsData.versions[ cacheIndex ] === version ) { + if ( bindingsData.versions[ cacheKey ] === version ) { - bindGroupGPU = bindingsData.groups[ cacheIndex ]; + bindGroupGPU = bindingsData.groups[ cacheKey ]; } @@ -172,10 +172,10 @@ class WebGPUBindingUtils { bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU ); - if ( cacheIndex > 0 ) { + if ( cacheKey !== '' ) { - bindingsData.groups[ cacheIndex ] = bindGroupGPU; - bindingsData.versions[ cacheIndex ] = version; + bindingsData.groups[ cacheKey ] = bindGroupGPU; + bindingsData.versions[ cacheKey ] = version; } From 24b04d7646b17e603b2c6ea19aab036ba130b8fa Mon Sep 17 00:00:00 2001 From: Jim <38460550+jjmhalew@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:37:58 +0200 Subject: [PATCH 2/3] WebGPUBackend: compare GPU bind groups by handle in _draw() (#34276) Co-authored-by: Claude Fable 5 Co-authored-by: Michael Herzog --- src/renderers/webgpu/WebGPUBackend.js | 34 ++++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 92c647333ab92e..446157f6661ac9 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -2014,8 +2014,9 @@ class WebGPUBackend extends Backend { * @param {{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} drawParams - The draw parameters. * @param {GPURenderPassEncoder|GPURenderBundleEncoder} passEncoderGPU - The GPU pass encoder used for recording draw commands. * @param {Object} currentSets - Tracking object for currently set pipeline, attributes, bind groups, and index state. + * @param {number} [cameraIndexSlot=-1] - Index of the binding slot holding the camera index bind group. */ - _draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets ) { + _draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets, cameraIndexSlot = - 1 ) { const { object, material, context } = renderObject; @@ -2037,14 +2038,19 @@ class WebGPUBackend extends Backend { for ( let i = 0, l = bindings.length; i < l; i ++ ) { - const bindGroup = bindings[ i ]; + // the camera index slot is bound per sub-camera in draw() + + if ( i === cameraIndexSlot ) continue; - if ( currentBindingGroups[ i ] !== bindGroup.id ) { + // compare native bind groups + + const bindGroup = bindings[ i ]; + const bindGroupGPU = this.get( bindGroup ).group; - const bindingsData = this.get( bindGroup ); + if ( currentBindingGroups[ i ] !== bindGroupGPU ) { - passEncoderGPU.setBindGroup( i, bindingsData.group ); - currentBindingGroups[ i ] = bindGroup.id; + passEncoderGPU.setBindGroup( i, bindGroupGPU ); + currentBindingGroups[ i ] = bindGroupGPU; } @@ -2252,6 +2258,7 @@ class WebGPUBackend extends Backend { } const pixelRatio = this.renderer.getPixelRatio(); + const indexPos = cameraIndex ? bindings.indexOf( cameraIndex ) : - 1; for ( let i = 0, len = cameras.length; i < len; i ++ ) { @@ -2289,15 +2296,20 @@ class WebGPUBackend extends Backend { } // Set camera index binding for this layer - if ( cameraIndex && cameraData.indexesGPU ) { + if ( indexPos !== - 1 && cameraData.indexesGPU ) { - const indexPos = bindings.indexOf( cameraIndex ); - pass.setBindGroup( indexPos, cameraData.indexesGPU[ i ] ); - sets.bindingGroups[ indexPos ] = cameraIndex.id; + const cameraIndexGPU = cameraData.indexesGPU[ i ]; + + if ( sets.bindingGroups[ indexPos ] !== cameraIndexGPU ) { + + pass.setBindGroup( indexPos, cameraIndexGPU ); + sets.bindingGroups[ indexPos ] = cameraIndexGPU; + + } } - this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets ); + this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets, indexPos ); } From 83d8667898fd32a6a0f1af92f6d91065db272ce2 Mon Sep 17 00:00:00 2001 From: Mugen87 Date: Tue, 18 Aug 2026 14:58:27 +0200 Subject: [PATCH 3/3] Updated builds. --- build/three.core.js | 11 +- build/three.webgpu.js | 216 ++++++++++++++++++++++++++++-------- build/three.webgpu.nodes.js | 216 ++++++++++++++++++++++++++++-------- 3 files changed, 337 insertions(+), 106 deletions(-) diff --git a/build/three.core.js b/build/three.core.js index f6a5c55729ffd2..55ea0f5a29713f 100644 --- a/build/three.core.js +++ b/build/three.core.js @@ -10629,8 +10629,6 @@ class Matrix4 { /** * Computes and returns the determinant of this matrix. * - * Based on the method outlined [here](http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.html). - * * @return {number} The determinant. */ determinant() { @@ -10946,17 +10944,12 @@ class Matrix4 { * Sets this matrix as a rotational transformation around the given axis by * the given angle. * - * This is a somewhat controversial but mathematically sound alternative to - * rotating via Quaternions. See the discussion [here](https://www.gamedev.net/articles/programming/math-and-physics/do-we-really-need-quaternions-r1199). - * * @param {Vector3} axis - The normalized rotation axis. * @param {number} angle - The rotation in radians. * @return {Matrix4} A reference to this matrix. */ makeRotationAxis( axis, angle ) { - // Based on http://www.gamedev.net/reference/articles/article1199.asp - const c = Math.cos( angle ); const s = Math.sin( angle ); const t = 1 - c; @@ -15469,8 +15462,6 @@ class Triangle { */ static getBarycoord( point, a, b, c, target ) { - // based on: http://www.blackpawn.com/texts/pointinpoly/default.html - _v0$2.subVectors( c, a ); _v1$5.subVectors( b, a ); _v2$4.subVectors( point, a ); @@ -20686,7 +20677,7 @@ const _normalMatrix = /*@__PURE__*/ new Matrix3(); /** * A two dimensional surface that extends infinitely in 3D space, represented - * in [Hessian normal form](http://mathworld.wolfram.com/HessianNormalForm.html) + * in [Hessian normal form](https://mathworld.wolfram.com/HessianNormalForm.html) * by a unit length normal vector and a constant. */ class Plane { diff --git a/build/three.webgpu.js b/build/three.webgpu.js index d4b8c19670949e..eaf1ddad13cd7a 100644 --- a/build/three.webgpu.js +++ b/build/three.webgpu.js @@ -105,6 +105,14 @@ const _materialCache = new WeakMap(); */ const _geometryCache = new WeakMap(); +/** + * Holds the texture data for comparison. + * + * @private + * @type {WeakMap} + */ +const _textureCache = new WeakMap(); + /** * This class is used by {@link WebGPURenderer} as management component. * It's primary purpose is to determine whether render objects require a @@ -361,6 +369,14 @@ class NodeMaterialObserver { drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count } }; + // force refresh on dispose + + geometry.addEventListener( 'dispose', () => { + + data._version ++; + + } ); + _geometryCache.set( geometry, data ); } @@ -369,6 +385,47 @@ class NodeMaterialObserver { } + /** + * Returns a texture data structure holding the texture state for + * monitoring. + * + * @param {Texture} texture - The texture. + * @return {Object} An object for monitoring the texture. + */ + getTextureData( texture ) { + + let data = _textureCache.get( texture ); + + if ( data === undefined ) { + + data = { _version: 0 }; + + // force refresh on dispose + + const onDispose = () => { + + data._version ++; + + }; + + if ( texture.renderTarget !== null ) { + + texture.renderTarget.addEventListener( 'dispose', onDispose ); + + } else { + + texture.addEventListener( 'dispose', onDispose ); + + } + + _textureCache.set( texture, data ); + + } + + return data; + + } + /** * Returns a material data structure holding the material property values for * monitoring. @@ -388,13 +445,17 @@ class NodeMaterialObserver { const value = material[ property ]; - if ( value === null || value === undefined ) continue; + if ( value === undefined ) continue; + + if ( value === null ) { - if ( typeof value === 'object' && value.clone !== undefined ) { + data[ property ] = null; // track unset properties + + } else if ( typeof value === 'object' && value.clone !== undefined ) { if ( value.isTexture === true ) { - data[ property ] = { id: value.id, version: 0 }; + data[ property ] = { id: value.id, version: 0, cacheVersion: this.getTextureData( value )._version }; } else { @@ -462,7 +523,35 @@ class NodeMaterialObserver { if ( property === '_renderId' ) continue; if ( property === '_version' ) continue; - if ( value.equals !== undefined ) { + if ( value === null || mtlValue === null || mtlValue === undefined ) { + + // a property was assigned or removed since the last observation so a new snapshot is required + + if ( value !== ( mtlValue === undefined ? null : mtlValue ) ) { + + if ( mtlValue === null || mtlValue === undefined ) { + + materialData[ property ] = null; + + } else if ( mtlValue.isTexture === true ) { + + materialData[ property ] = { id: mtlValue.id, version: mtlValue.version, cacheVersion: this.getTextureData( mtlValue )._version }; + + } else if ( typeof mtlValue === 'object' && mtlValue.clone !== undefined ) { + + materialData[ property ] = mtlValue.clone(); + + } else { + + materialData[ property ] = mtlValue; + + } + + changed = true; + + } + + } else if ( value.equals !== undefined ) { if ( value.equals( mtlValue ) === false ) { @@ -474,10 +563,13 @@ class NodeMaterialObserver { } else if ( mtlValue.isTexture === true ) { - if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) { + const textureData = this.getTextureData( mtlValue ); + + if ( value.id !== mtlValue.id || value.version !== mtlValue.version || value.cacheVersion !== textureData._version ) { value.id = mtlValue.id; value.version = mtlValue.version; + value.cacheVersion = textureData._version; changed = true; @@ -527,6 +619,8 @@ class NodeMaterialObserver { if ( renderObjectData.geometryId !== geometry.id ) { renderObjectData.geometryId = geometry.id; + renderObjectData.geometryVersion = this.getGeometryData( geometry )._version; + return false; } @@ -698,7 +792,12 @@ class NodeMaterialObserver { for ( let i = 0; i < lightsData.length; i ++ ) { - if ( renderObjectData.lights[ i ].map !== lightsData[ i ].map ) { + const lightData = renderObjectData.lights[ i ]; + + if ( lightData.map !== lightsData[ i ].map || lightData.cacheVersion !== lightsData[ i ].cacheVersion ) { + + lightData.map = lightsData[ i ].map; + lightData.cacheVersion = lightsData[ i ].cacheVersion; return false; @@ -768,7 +867,7 @@ class NodeMaterialObserver { // only add lights that have a map - lights.push( { map: light.map.version } ); + lights.push( { map: light.map.version, cacheVersion: this.getTextureData( light.map )._version } ); } @@ -33212,7 +33311,7 @@ class Bindings extends DataMap { for ( const binding of bindGroup.bindings ) { - if ( binding.isNodeUniformsGroup === true && binding.groupNode.shared === true ) { + if ( ( binding.isNodeUniformsGroup === true || binding.isNodeUniformBuffer === true ) && binding.groupNode.shared === true ) { const updatedGroup = this.nodes.updateGroup( binding ); @@ -33312,7 +33411,7 @@ class Bindings extends DataMap { // each object defines an array of bindings (ubos, textures, samplers etc.) - this.backend.createBindings( bindGroup, bindings, 0 ); + this.backend.createBindings( bindGroup, bindings, '' ); groupData.bindGroup = bindGroup; groupData.usedTimes = 1; @@ -33409,7 +33508,7 @@ class Bindings extends DataMap { let needsBindingsUpdate = false; let cacheBindings = true; - let cacheIndex = 0; + let cacheKey = ''; let version = 0; // iterate over all bindings and check if buffer updates or a new binding group is required @@ -33493,7 +33592,7 @@ class Bindings extends DataMap { } else { - cacheIndex = cacheIndex * 10 + texture.id; + cacheKey += texture.id + ','; version += texture.version; } @@ -33546,7 +33645,7 @@ class Bindings extends DataMap { if ( needsBindingsUpdate === true ) { - this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheIndex : 0, version ); + this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheKey : '', version ); } @@ -67621,10 +67720,10 @@ class Backend { * @abstract * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + createBindings( /*bindGroup, bindings, cacheKey, version*/ ) { } /** * Updates the given bind group definition. @@ -67632,10 +67731,10 @@ class Backend { * @abstract * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + updateBindings( /*bindGroup, bindings, cacheKey, version*/ ) { } /** * Updates a buffer binding. @@ -74475,10 +74574,10 @@ class WebGLBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings /*, cacheIndex, version*/ ) { + createBindings( bindGroup, bindings /*, cacheKey, version*/ ) { if ( this._knownBindings.has( bindings ) === false ) { @@ -74514,10 +74613,10 @@ class WebGLBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( bindGroup /*, bindings, cacheIndex, version*/ ) { + updateBindings( bindGroup /*, bindings, cacheKey, version*/ ) { const { gl } = this; @@ -83394,10 +83493,10 @@ class WebGPUBindingUtils { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings, cacheIndex, version = 0 ) { + createBindings( bindGroup, bindings, cacheKey, version = 0 ) { const { backend } = this; const bindingsData = backend.get( bindGroup ); @@ -83408,18 +83507,18 @@ class WebGPUBindingUtils { let bindGroupGPU; - if ( cacheIndex > 0 ) { + if ( cacheKey !== '' ) { if ( bindingsData.groups === undefined ) { - bindingsData.groups = []; - bindingsData.versions = []; + bindingsData.groups = {}; + bindingsData.versions = {}; } - if ( bindingsData.versions[ cacheIndex ] === version ) { + if ( bindingsData.versions[ cacheKey ] === version ) { - bindGroupGPU = bindingsData.groups[ cacheIndex ]; + bindGroupGPU = bindingsData.groups[ cacheKey ]; } @@ -83429,10 +83528,10 @@ class WebGPUBindingUtils { bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU ); - if ( cacheIndex > 0 ) { + if ( cacheKey !== '' ) { - bindingsData.groups[ cacheIndex ] = bindGroupGPU; - bindingsData.versions[ cacheIndex ] = version; + bindingsData.groups[ cacheKey ] = bindGroupGPU; + bindingsData.versions[ cacheKey ] = version; } @@ -87563,8 +87662,9 @@ class WebGPUBackend extends Backend { * @param {{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} drawParams - The draw parameters. * @param {GPURenderPassEncoder|GPURenderBundleEncoder} passEncoderGPU - The GPU pass encoder used for recording draw commands. * @param {Object} currentSets - Tracking object for currently set pipeline, attributes, bind groups, and index state. + * @param {number} [cameraIndexSlot=-1] - Index of the binding slot holding the camera index bind group. */ - _draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets ) { + _draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets, cameraIndexSlot = -1 ) { const { object, material, context } = renderObject; @@ -87586,14 +87686,19 @@ class WebGPUBackend extends Backend { for ( let i = 0, l = bindings.length; i < l; i ++ ) { - const bindGroup = bindings[ i ]; + // the camera index slot is bound per sub-camera in draw() - if ( currentBindingGroups[ i ] !== bindGroup.id ) { + if ( i === cameraIndexSlot ) continue; - const bindingsData = this.get( bindGroup ); + // compare native bind groups - passEncoderGPU.setBindGroup( i, bindingsData.group ); - currentBindingGroups[ i ] = bindGroup.id; + const bindGroup = bindings[ i ]; + const bindGroupGPU = this.get( bindGroup ).group; + + if ( currentBindingGroups[ i ] !== bindGroupGPU ) { + + passEncoderGPU.setBindGroup( i, bindGroupGPU ); + currentBindingGroups[ i ] = bindGroupGPU; } @@ -87801,6 +87906,7 @@ class WebGPUBackend extends Backend { } const pixelRatio = this.renderer.getPixelRatio(); + const indexPos = cameraIndex ? bindings.indexOf( cameraIndex ) : -1; for ( let i = 0, len = cameras.length; i < len; i ++ ) { @@ -87838,15 +87944,20 @@ class WebGPUBackend extends Backend { } // Set camera index binding for this layer - if ( cameraIndex && cameraData.indexesGPU ) { + if ( indexPos !== -1 && cameraData.indexesGPU ) { + + const cameraIndexGPU = cameraData.indexesGPU[ i ]; + + if ( sets.bindingGroups[ indexPos ] !== cameraIndexGPU ) { - const indexPos = bindings.indexOf( cameraIndex ); - pass.setBindGroup( indexPos, cameraData.indexesGPU[ i ] ); - sets.bindingGroups[ indexPos ] = cameraIndex.id; + pass.setBindGroup( indexPos, cameraIndexGPU ); + sets.bindingGroups[ indexPos ] = cameraIndexGPU; + + } } - this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets ); + this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets, indexPos ); } @@ -87858,7 +87969,16 @@ class WebGPUBackend extends Backend { if ( renderContextData.currentPass ) { // Handle occlusion queries - if ( renderContextData.occlusionQuerySet !== undefined ) { + + if ( renderContextData.currentPass instanceof GPURenderBundleEncoder ) { + + if ( object.occlusionTest === true ) { + + warnOnce( 'WebGPUBackend: Occlusion queries can not be recorded into render bundles.' ); + + } + + } else if ( renderContextData.occlusionQuerySet !== undefined ) { const lastObject = renderContextData.lastOcclusionObject; if ( lastObject !== object ) { @@ -88334,12 +88454,12 @@ class WebGPUBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings, cacheIndex, version ) { + createBindings( bindGroup, bindings, cacheKey, version ) { - this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version ); } @@ -88348,12 +88468,12 @@ class WebGPUBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( bindGroup, bindings, cacheIndex, version ) { + updateBindings( bindGroup, bindings, cacheKey, version ) { - this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version ); } diff --git a/build/three.webgpu.nodes.js b/build/three.webgpu.nodes.js index f90f5e47e749f5..516e1d1e227207 100644 --- a/build/three.webgpu.nodes.js +++ b/build/three.webgpu.nodes.js @@ -105,6 +105,14 @@ const _materialCache = new WeakMap(); */ const _geometryCache = new WeakMap(); +/** + * Holds the texture data for comparison. + * + * @private + * @type {WeakMap} + */ +const _textureCache = new WeakMap(); + /** * This class is used by {@link WebGPURenderer} as management component. * It's primary purpose is to determine whether render objects require a @@ -361,6 +369,14 @@ class NodeMaterialObserver { drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count } }; + // force refresh on dispose + + geometry.addEventListener( 'dispose', () => { + + data._version ++; + + } ); + _geometryCache.set( geometry, data ); } @@ -369,6 +385,47 @@ class NodeMaterialObserver { } + /** + * Returns a texture data structure holding the texture state for + * monitoring. + * + * @param {Texture} texture - The texture. + * @return {Object} An object for monitoring the texture. + */ + getTextureData( texture ) { + + let data = _textureCache.get( texture ); + + if ( data === undefined ) { + + data = { _version: 0 }; + + // force refresh on dispose + + const onDispose = () => { + + data._version ++; + + }; + + if ( texture.renderTarget !== null ) { + + texture.renderTarget.addEventListener( 'dispose', onDispose ); + + } else { + + texture.addEventListener( 'dispose', onDispose ); + + } + + _textureCache.set( texture, data ); + + } + + return data; + + } + /** * Returns a material data structure holding the material property values for * monitoring. @@ -388,13 +445,17 @@ class NodeMaterialObserver { const value = material[ property ]; - if ( value === null || value === undefined ) continue; + if ( value === undefined ) continue; + + if ( value === null ) { - if ( typeof value === 'object' && value.clone !== undefined ) { + data[ property ] = null; // track unset properties + + } else if ( typeof value === 'object' && value.clone !== undefined ) { if ( value.isTexture === true ) { - data[ property ] = { id: value.id, version: 0 }; + data[ property ] = { id: value.id, version: 0, cacheVersion: this.getTextureData( value )._version }; } else { @@ -462,7 +523,35 @@ class NodeMaterialObserver { if ( property === '_renderId' ) continue; if ( property === '_version' ) continue; - if ( value.equals !== undefined ) { + if ( value === null || mtlValue === null || mtlValue === undefined ) { + + // a property was assigned or removed since the last observation so a new snapshot is required + + if ( value !== ( mtlValue === undefined ? null : mtlValue ) ) { + + if ( mtlValue === null || mtlValue === undefined ) { + + materialData[ property ] = null; + + } else if ( mtlValue.isTexture === true ) { + + materialData[ property ] = { id: mtlValue.id, version: mtlValue.version, cacheVersion: this.getTextureData( mtlValue )._version }; + + } else if ( typeof mtlValue === 'object' && mtlValue.clone !== undefined ) { + + materialData[ property ] = mtlValue.clone(); + + } else { + + materialData[ property ] = mtlValue; + + } + + changed = true; + + } + + } else if ( value.equals !== undefined ) { if ( value.equals( mtlValue ) === false ) { @@ -474,10 +563,13 @@ class NodeMaterialObserver { } else if ( mtlValue.isTexture === true ) { - if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) { + const textureData = this.getTextureData( mtlValue ); + + if ( value.id !== mtlValue.id || value.version !== mtlValue.version || value.cacheVersion !== textureData._version ) { value.id = mtlValue.id; value.version = mtlValue.version; + value.cacheVersion = textureData._version; changed = true; @@ -527,6 +619,8 @@ class NodeMaterialObserver { if ( renderObjectData.geometryId !== geometry.id ) { renderObjectData.geometryId = geometry.id; + renderObjectData.geometryVersion = this.getGeometryData( geometry )._version; + return false; } @@ -698,7 +792,12 @@ class NodeMaterialObserver { for ( let i = 0; i < lightsData.length; i ++ ) { - if ( renderObjectData.lights[ i ].map !== lightsData[ i ].map ) { + const lightData = renderObjectData.lights[ i ]; + + if ( lightData.map !== lightsData[ i ].map || lightData.cacheVersion !== lightsData[ i ].cacheVersion ) { + + lightData.map = lightsData[ i ].map; + lightData.cacheVersion = lightsData[ i ].cacheVersion; return false; @@ -768,7 +867,7 @@ class NodeMaterialObserver { // only add lights that have a map - lights.push( { map: light.map.version } ); + lights.push( { map: light.map.version, cacheVersion: this.getTextureData( light.map )._version } ); } @@ -33212,7 +33311,7 @@ class Bindings extends DataMap { for ( const binding of bindGroup.bindings ) { - if ( binding.isNodeUniformsGroup === true && binding.groupNode.shared === true ) { + if ( ( binding.isNodeUniformsGroup === true || binding.isNodeUniformBuffer === true ) && binding.groupNode.shared === true ) { const updatedGroup = this.nodes.updateGroup( binding ); @@ -33312,7 +33411,7 @@ class Bindings extends DataMap { // each object defines an array of bindings (ubos, textures, samplers etc.) - this.backend.createBindings( bindGroup, bindings, 0 ); + this.backend.createBindings( bindGroup, bindings, '' ); groupData.bindGroup = bindGroup; groupData.usedTimes = 1; @@ -33409,7 +33508,7 @@ class Bindings extends DataMap { let needsBindingsUpdate = false; let cacheBindings = true; - let cacheIndex = 0; + let cacheKey = ''; let version = 0; // iterate over all bindings and check if buffer updates or a new binding group is required @@ -33493,7 +33592,7 @@ class Bindings extends DataMap { } else { - cacheIndex = cacheIndex * 10 + texture.id; + cacheKey += texture.id + ','; version += texture.version; } @@ -33546,7 +33645,7 @@ class Bindings extends DataMap { if ( needsBindingsUpdate === true ) { - this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheIndex : 0, version ); + this.backend.updateBindings( bindGroup, bindings, cacheBindings ? cacheKey : '', version ); } @@ -67621,10 +67720,10 @@ class Backend { * @abstract * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + createBindings( /*bindGroup, bindings, cacheKey, version*/ ) { } /** * Updates the given bind group definition. @@ -67632,10 +67731,10 @@ class Backend { * @abstract * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( /*bindGroup, bindings, cacheIndex, version*/ ) { } + updateBindings( /*bindGroup, bindings, cacheKey, version*/ ) { } /** * Updates a buffer binding. @@ -74475,10 +74574,10 @@ class WebGLBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings /*, cacheIndex, version*/ ) { + createBindings( bindGroup, bindings /*, cacheKey, version*/ ) { if ( this._knownBindings.has( bindings ) === false ) { @@ -74514,10 +74613,10 @@ class WebGLBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( bindGroup /*, bindings, cacheIndex, version*/ ) { + updateBindings( bindGroup /*, bindings, cacheKey, version*/ ) { const { gl } = this; @@ -83394,10 +83493,10 @@ class WebGPUBindingUtils { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings, cacheIndex, version = 0 ) { + createBindings( bindGroup, bindings, cacheKey, version = 0 ) { const { backend } = this; const bindingsData = backend.get( bindGroup ); @@ -83408,18 +83507,18 @@ class WebGPUBindingUtils { let bindGroupGPU; - if ( cacheIndex > 0 ) { + if ( cacheKey !== '' ) { if ( bindingsData.groups === undefined ) { - bindingsData.groups = []; - bindingsData.versions = []; + bindingsData.groups = {}; + bindingsData.versions = {}; } - if ( bindingsData.versions[ cacheIndex ] === version ) { + if ( bindingsData.versions[ cacheKey ] === version ) { - bindGroupGPU = bindingsData.groups[ cacheIndex ]; + bindGroupGPU = bindingsData.groups[ cacheKey ]; } @@ -83429,10 +83528,10 @@ class WebGPUBindingUtils { bindGroupGPU = this.createBindGroup( bindGroup, bindLayoutGPU ); - if ( cacheIndex > 0 ) { + if ( cacheKey !== '' ) { - bindingsData.groups[ cacheIndex ] = bindGroupGPU; - bindingsData.versions[ cacheIndex ] = version; + bindingsData.groups[ cacheKey ] = bindGroupGPU; + bindingsData.versions[ cacheKey ] = version; } @@ -87563,8 +87662,9 @@ class WebGPUBackend extends Backend { * @param {{vertexCount: number, firstVertex: number, instanceCount: number, firstInstance: number}} drawParams - The draw parameters. * @param {GPURenderPassEncoder|GPURenderBundleEncoder} passEncoderGPU - The GPU pass encoder used for recording draw commands. * @param {Object} currentSets - Tracking object for currently set pipeline, attributes, bind groups, and index state. + * @param {number} [cameraIndexSlot=-1] - Index of the binding slot holding the camera index bind group. */ - _draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets ) { + _draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, passEncoderGPU, currentSets, cameraIndexSlot = -1 ) { const { object, material, context } = renderObject; @@ -87586,14 +87686,19 @@ class WebGPUBackend extends Backend { for ( let i = 0, l = bindings.length; i < l; i ++ ) { - const bindGroup = bindings[ i ]; + // the camera index slot is bound per sub-camera in draw() - if ( currentBindingGroups[ i ] !== bindGroup.id ) { + if ( i === cameraIndexSlot ) continue; - const bindingsData = this.get( bindGroup ); + // compare native bind groups - passEncoderGPU.setBindGroup( i, bindingsData.group ); - currentBindingGroups[ i ] = bindGroup.id; + const bindGroup = bindings[ i ]; + const bindGroupGPU = this.get( bindGroup ).group; + + if ( currentBindingGroups[ i ] !== bindGroupGPU ) { + + passEncoderGPU.setBindGroup( i, bindGroupGPU ); + currentBindingGroups[ i ] = bindGroupGPU; } @@ -87801,6 +87906,7 @@ class WebGPUBackend extends Backend { } const pixelRatio = this.renderer.getPixelRatio(); + const indexPos = cameraIndex ? bindings.indexOf( cameraIndex ) : -1; for ( let i = 0, len = cameras.length; i < len; i ++ ) { @@ -87838,15 +87944,20 @@ class WebGPUBackend extends Backend { } // Set camera index binding for this layer - if ( cameraIndex && cameraData.indexesGPU ) { + if ( indexPos !== -1 && cameraData.indexesGPU ) { + + const cameraIndexGPU = cameraData.indexesGPU[ i ]; + + if ( sets.bindingGroups[ indexPos ] !== cameraIndexGPU ) { - const indexPos = bindings.indexOf( cameraIndex ); - pass.setBindGroup( indexPos, cameraData.indexesGPU[ i ] ); - sets.bindingGroups[ indexPos ] = cameraIndex.id; + pass.setBindGroup( indexPos, cameraIndexGPU ); + sets.bindingGroups[ indexPos ] = cameraIndexGPU; + + } } - this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets ); + this._draw( renderObject, info, renderContextData, pipelineGPU, bindings, vertexBuffers, drawParams, pass, sets, indexPos ); } @@ -87858,7 +87969,16 @@ class WebGPUBackend extends Backend { if ( renderContextData.currentPass ) { // Handle occlusion queries - if ( renderContextData.occlusionQuerySet !== undefined ) { + + if ( renderContextData.currentPass instanceof GPURenderBundleEncoder ) { + + if ( object.occlusionTest === true ) { + + warnOnce( 'WebGPUBackend: Occlusion queries can not be recorded into render bundles.' ); + + } + + } else if ( renderContextData.occlusionQuerySet !== undefined ) { const lastObject = renderContextData.lastOcclusionObject; if ( lastObject !== object ) { @@ -88334,12 +88454,12 @@ class WebGPUBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - createBindings( bindGroup, bindings, cacheIndex, version ) { + createBindings( bindGroup, bindings, cacheKey, version ) { - this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version ); } @@ -88348,12 +88468,12 @@ class WebGPUBackend extends Backend { * * @param {BindGroup} bindGroup - The bind group. * @param {Array} bindings - Array of bind groups. - * @param {number} cacheIndex - The cache index. + * @param {string} cacheKey - The cache key. * @param {number} version - The version. */ - updateBindings( bindGroup, bindings, cacheIndex, version ) { + updateBindings( bindGroup, bindings, cacheKey, version ) { - this.bindingUtils.createBindings( bindGroup, bindings, cacheIndex, version ); + this.bindingUtils.createBindings( bindGroup, bindings, cacheKey, version ); }