From 832c59594a299efcac256abdf1a991a38c93d3dd Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 28 Sep 2026 13:48:19 +0200 Subject: [PATCH 1/4] WebGLBackend: Improve `dispose()`. (#34688) --- src/renderers/webgl-fallback/WebGLBackend.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index 64f9f36c04d03c..4d01e2748891be 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -2873,8 +2873,12 @@ class WebGLBackend extends Backend { if ( this.vertexArrayUtils !== null ) this.vertexArrayUtils.dispose(); if ( this.textureUtils !== null ) this.textureUtils.dispose(); - const extension = this.extensions.get( 'WEBGL_lose_context' ); - if ( extension ) extension.loseContext(); + if ( this.parameters.canvas === undefined ) { + + const extension = this.extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); + + } this.renderer.domElement.removeEventListener( 'webglcontextlost', this._onContextLost ); From 8418093bd8b12291af76dad7f321e02df4fd9fb7 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 28 Sep 2026 15:22:59 +0200 Subject: [PATCH 2/4] WebGPURenderer: Unify handling of interleaved attributes. (#34690) --- src/renderers/common/Attributes.js | 27 +++---------- src/renderers/common/Backend.js | 15 +++++++ src/renderers/common/Geometries.js | 32 +++------------ .../utils/WebGLAttributeUtils.js | 40 ++++++------------- .../utils/WebGLVertexArrayUtils.js | 12 ++++-- .../webgpu/utils/WebGPUAttributeUtils.js | 33 +++++---------- 6 files changed, 57 insertions(+), 102 deletions(-) diff --git a/src/renderers/common/Attributes.js b/src/renderers/common/Attributes.js index 96a275041470d9..516bd9eaf33c3c 100644 --- a/src/renderers/common/Attributes.js +++ b/src/renderers/common/Attributes.js @@ -58,7 +58,7 @@ class Attributes extends DataMap { /** * Deletes the data for the given attribute. * - * @param {BufferAttribute} attribute - The attribute. + * @param {BufferAttribute|InterleavedBuffer} attribute - The attribute. * @return {?Object} The deleted attribute data. */ delete( attribute ) { @@ -90,7 +90,7 @@ class Attributes extends DataMap { * Updates the given attribute. This method creates attribute buffers * for new attributes and updates data for existing ones. * - * @param {BufferAttribute} attribute - The attribute to update. + * @param {BufferAttribute|InterleavedBuffer} attribute - The attribute to update. * @param {number} type - The attribute type. */ update( attribute, type ) { @@ -121,7 +121,7 @@ class Attributes extends DataMap { } - data.version = this._getBufferAttribute( attribute ).version; + data.version = attribute.version; // only storage buffer attributes support disposal @@ -145,13 +145,11 @@ class Attributes extends DataMap { } else { - const bufferAttribute = this._getBufferAttribute( attribute ); - - if ( data.version < bufferAttribute.version || bufferAttribute.usage === DynamicDrawUsage ) { + if ( data.version < attribute.version || attribute.usage === DynamicDrawUsage ) { this.backend.updateAttribute( attribute ); - data.version = bufferAttribute.version; + data.version = attribute.version; } @@ -159,21 +157,6 @@ class Attributes extends DataMap { } - /** - * Utility method for handling interleaved buffer attributes correctly. - * To process them, their `InterleavedBuffer` is returned. - * - * @param {BufferAttribute} attribute - The attribute. - * @return {BufferAttribute|InterleavedBuffer} - */ - _getBufferAttribute( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return attribute; - - } - dispose() { for ( const ref of this._tracked ) { diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index f36a31cba05fe3..6b31fbdf8b3ab3 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -454,6 +454,21 @@ class Backend { */ destroyAttribute( /*attribute*/ ) { } + /** + * Utility method for handling interleaved buffer attributes correctly. + * To process them, their `InterleavedBuffer` is returned. + * + * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute. + * @return {BufferAttribute|InterleavedBuffer} + */ + getBufferAttribute( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return attribute; + + } + // canvas /** diff --git a/src/renderers/common/Geometries.js b/src/renderers/common/Geometries.js index a9d3acd0d3fbb0..4453a135d667aa 100644 --- a/src/renderers/common/Geometries.js +++ b/src/renderers/common/Geometries.js @@ -221,7 +221,7 @@ class Geometries extends DataMap { for ( const attribute of Object.values( geometry.attributes ) ) { - this.attributes.delete( attribute ); + this.attributes.delete( this.backend.getBufferAttribute( attribute ) ); } @@ -313,33 +313,13 @@ class Geometries extends DataMap { const callId = this.info.render.calls; - if ( ! attribute.isInterleavedBufferAttribute ) { + const bufferAttribute = this.backend.getBufferAttribute( attribute ); - if ( this.attributeCall.get( attribute ) !== callId ) { + if ( this.attributeCall.get( bufferAttribute ) !== callId ) { - this.attributes.update( attribute, type ); + this.attributes.update( bufferAttribute, type ); - this.attributeCall.set( attribute, callId ); - - } - - } else { - - if ( this.attributeCall.get( attribute ) === undefined ) { - - this.attributes.update( attribute, type ); - - this.attributeCall.set( attribute, callId ); - - } else if ( this.attributeCall.get( attribute.data ) !== callId ) { - - this.attributes.update( attribute, type ); - - this.attributeCall.set( attribute.data, callId ); - - this.attributeCall.set( attribute, callId ); - - } + this.attributeCall.set( bufferAttribute, callId ); } @@ -425,7 +405,7 @@ class Geometries extends DataMap { if ( currentAttributes.has( attribute ) === false ) { - this.attributes.delete( attribute ); + this.attributes.delete( this.backend.getBufferAttribute( attribute ) ); } diff --git a/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js b/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js index d475d61ed680a9..0fea06ebb0e7c4 100644 --- a/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js +++ b/src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js @@ -81,23 +81,14 @@ class WebGLAttributeUtils { const backend = this.backend; const { gl } = backend; - const array = attribute.array; - const usage = attribute.usage || gl.STATIC_DRAW; - - const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; - const bufferData = backend.get( bufferAttribute ); + const bufferAttribute = backend.getBufferAttribute( attribute ); - let bufferGPU = bufferData.bufferGPU; + if ( backend.get( bufferAttribute ).bufferGPU !== undefined ) return; - if ( bufferGPU === undefined ) { - - bufferGPU = this._createBuffer( gl, bufferType, array, usage ); - - bufferData.bufferGPU = bufferGPU; - bufferData.bufferType = bufferType; - bufferData.version = bufferAttribute.version; + const array = attribute.array; + const usage = attribute.usage || gl.STATIC_DRAW; - } + const bufferGPU = this._createBuffer( gl, bufferType, array, usage ); //attribute.onUploadCallback(); @@ -159,7 +150,7 @@ class WebGLAttributeUtils { type, byteLength: array.byteLength, bytesPerElement: array.BYTES_PER_ELEMENT, - version: attribute.version, + version: bufferAttribute.version, pbo: attribute.pbo, isInteger: type === gl.INT || type === gl.UNSIGNED_INT || attribute.gpuType === IntType, id: _id ++ @@ -173,7 +164,7 @@ class WebGLAttributeUtils { } - backend.set( attribute, attributeData ); + backend.set( bufferAttribute, attributeData ); } @@ -188,10 +179,10 @@ class WebGLAttributeUtils { const { gl } = backend; const array = attribute.array; - const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const bufferAttribute = backend.getBufferAttribute( attribute ); const bufferData = backend.get( bufferAttribute ); const bufferType = bufferData.bufferType; - const updateRanges = attribute.isInterleavedBufferAttribute ? attribute.data.updateRanges : attribute.updateRanges; + const updateRanges = bufferAttribute.updateRanges; gl.bindBuffer( bufferType, bufferData.bufferGPU ); @@ -231,13 +222,8 @@ class WebGLAttributeUtils { const backend = this.backend; const { gl } = backend; - if ( attribute.isInterleavedBufferAttribute ) { - - backend.delete( attribute.data ); - - } - - const attributeData = backend.get( attribute ); + const bufferAttribute = backend.getBufferAttribute( attribute ); + const attributeData = backend.get( bufferAttribute ); if ( attributeData.buffers !== undefined ) { @@ -254,7 +240,7 @@ class WebGLAttributeUtils { } - backend.delete( attribute ); + backend.delete( bufferAttribute ); } @@ -276,7 +262,7 @@ class WebGLAttributeUtils { const backend = this.backend; const { gl } = backend; - const bufferAttribute = attribute.isInterleavedBufferAttribute ? attribute.data : attribute; + const bufferAttribute = backend.getBufferAttribute( attribute ); const attributeInfo = backend.get( bufferAttribute ); const { bufferGPU } = attributeInfo; diff --git a/src/renderers/webgl-fallback/utils/WebGLVertexArrayUtils.js b/src/renderers/webgl-fallback/utils/WebGLVertexArrayUtils.js index 3af96b5b42f212..46bf16396719a2 100644 --- a/src/renderers/webgl-fallback/utils/WebGLVertexArrayUtils.js +++ b/src/renderers/webgl-fallback/utils/WebGLVertexArrayUtils.js @@ -56,9 +56,13 @@ class WebGLVertexArrayUtils { for ( let i = 0; i < attributes.length; i ++ ) { - const attributeData = backend.get( attributes[ i ] ); + const attribute = attributes[ i ]; + const attributeData = backend.get( backend.getBufferAttribute( attribute ) ); key += ':' + attributeData.id; + + if ( attribute.isInterleavedBufferAttribute ) key += '.' + attribute.offset; + variant += ':' + ( attributeData.activeBufferIndex || 0 ); buffers.push( attributeData.bufferGPU ); @@ -163,7 +167,9 @@ class WebGLVertexArrayUtils { for ( let i = 0; i < attributes.length; i ++ ) { - if ( this.backend.get( attributes[ i ] ).bufferGPU !== buffers[ i ] ) return true; + const bufferAttribute = this.backend.getBufferAttribute( attributes[ i ] ); + + if ( this.backend.get( bufferAttribute ).bufferGPU !== buffers[ i ] ) return true; } @@ -217,7 +223,7 @@ class WebGLVertexArrayUtils { for ( let i = 0; i < attributes.length; i ++ ) { const attribute = attributes[ i ]; - const attributeData = this.backend.get( attribute ); + const attributeData = this.backend.get( this.backend.getBufferAttribute( attribute ) ); gl.bindBuffer( gl.ARRAY_BUFFER, attributeData.bufferGPU ); gl.enableVertexAttribArray( i ); diff --git a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js index 0d4ab9c7f58db2..4a7458ac9f0030 100644 --- a/src/renderers/webgpu/utils/WebGPUAttributeUtils.js +++ b/src/renderers/webgpu/utils/WebGPUAttributeUtils.js @@ -66,9 +66,9 @@ class WebGPUAttributeUtils { */ createAttribute( attribute, usage ) { - const bufferAttribute = this._getBufferAttribute( attribute ); - const backend = this.backend; + + const bufferAttribute = backend.getBufferAttribute( attribute ); const bufferData = backend.get( bufferAttribute ); let buffer = bufferData.buffer; @@ -184,11 +184,10 @@ class WebGPUAttributeUtils { */ updateAttribute( attribute ) { - const bufferAttribute = this._getBufferAttribute( attribute ); - const backend = this.backend; const device = backend.device; + const bufferAttribute = backend.getBufferAttribute( attribute ); const bufferData = backend.get( bufferAttribute ); const buffer = backend.get( bufferAttribute ).buffer; @@ -291,7 +290,7 @@ class WebGPUAttributeUtils { const geometryAttribute = attributes[ slot ]; const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT; - const bufferAttribute = this._getBufferAttribute( geometryAttribute ); + const bufferAttribute = this.backend.getBufferAttribute( geometryAttribute ); let vertexBufferLayout = vertexBuffers.get( bufferAttribute ); @@ -352,11 +351,13 @@ class WebGPUAttributeUtils { destroyAttribute( attribute ) { const backend = this.backend; - const data = backend.get( this._getBufferAttribute( attribute ) ); + + const bufferAttribute = backend.getBufferAttribute( attribute ); + const data = backend.get( bufferAttribute ); data.buffer.destroy(); - backend.delete( attribute ); + backend.delete( bufferAttribute ); } @@ -378,7 +379,7 @@ class WebGPUAttributeUtils { const backend = this.backend; const device = backend.device; - const data = backend.get( this._getBufferAttribute( attribute ) ); + const data = backend.get( backend.getBufferAttribute( attribute ) ); const bufferGPU = data.buffer; const byteLength = count === - 1 ? bufferGPU.size - offset : count; @@ -552,22 +553,6 @@ class WebGPUAttributeUtils { } - /** - * Utility method for handling interleaved buffer attributes correctly. - * To process them, their `InterleavedBuffer` is returned. - * - * @private - * @param {BufferAttribute} attribute - The attribute. - * @return {BufferAttribute|InterleavedBuffer} - */ - _getBufferAttribute( attribute ) { - - if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; - - return attribute; - - } - } export default WebGPUAttributeUtils; From e38bd859455109658df731e2bd64d1828b7905b0 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 28 Sep 2026 15:51:32 +0200 Subject: [PATCH 3/4] Core: Make `copy()` methods more robust. (#34691) --- src/core/RenderTarget.js | 4 ++++ src/objects/BatchedMesh.js | 6 +++++- src/objects/InstancedMesh.js | 8 ++++---- src/objects/Mesh.js | 8 ++++++++ src/objects/SkinnedMesh.js | 4 ++-- src/scenes/Scene.js | 8 ++++---- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/core/RenderTarget.js b/src/core/RenderTarget.js index 1c6275b6b23f68..f0a53f274dbaaa 100644 --- a/src/core/RenderTarget.js +++ b/src/core/RenderTarget.js @@ -470,6 +470,10 @@ class RenderTarget extends EventDispatcher { } + } else { + + this.depthTexture = null; + } this.samples = source.samples; diff --git a/src/objects/BatchedMesh.js b/src/objects/BatchedMesh.js index e0b6ada1db4ecc..1bfc912883adc7 100644 --- a/src/objects/BatchedMesh.js +++ b/src/objects/BatchedMesh.js @@ -1487,11 +1487,15 @@ class BatchedMesh extends Mesh { this._matricesTexture = source._matricesTexture.clone(); this._matricesTexture.image.data = this._matricesTexture.image.data.slice(); - if ( this._colorsTexture !== null ) { + if ( source._colorsTexture !== null ) { this._colorsTexture = source._colorsTexture.clone(); this._colorsTexture.image.data = this._colorsTexture.image.data.slice(); + } else { + + this._colorsTexture = null; + } return this; diff --git a/src/objects/InstancedMesh.js b/src/objects/InstancedMesh.js index d55dc5d1311200..7b83268e52bf66 100644 --- a/src/objects/InstancedMesh.js +++ b/src/objects/InstancedMesh.js @@ -185,13 +185,13 @@ class InstancedMesh extends Mesh { this.instanceMatrix.copy( source.instanceMatrix ); - if ( source.morphTexture !== null ) this.morphTexture = source.morphTexture.clone(); - if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone(); + this.morphTexture = source.morphTexture !== null ? source.morphTexture.clone() : null; + this.instanceColor = source.instanceColor !== null ? source.instanceColor.clone() : null; this.count = source.count; - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; + this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; return this; diff --git a/src/objects/Mesh.js b/src/objects/Mesh.js index b80d3de8f403a6..907fd78f4fecb8 100644 --- a/src/objects/Mesh.js +++ b/src/objects/Mesh.js @@ -115,12 +115,20 @@ class Mesh extends Object3D { this.morphTargetInfluences = source.morphTargetInfluences.slice(); + } else { + + this.morphTargetInfluences = undefined; + } if ( source.morphTargetDictionary !== undefined ) { this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); + } else { + + this.morphTargetDictionary = undefined; + } this.material = Array.isArray( source.material ) ? source.material.slice() : source.material; diff --git a/src/objects/SkinnedMesh.js b/src/objects/SkinnedMesh.js index 04280944f5a68e..bc9559231d6df8 100644 --- a/src/objects/SkinnedMesh.js +++ b/src/objects/SkinnedMesh.js @@ -168,8 +168,8 @@ class SkinnedMesh extends Mesh { this.skeleton = source.skeleton; - if ( source.boundingBox !== null ) this.boundingBox = source.boundingBox.clone(); - if ( source.boundingSphere !== null ) this.boundingSphere = source.boundingSphere.clone(); + this.boundingBox = source.boundingBox !== null ? source.boundingBox.clone() : null; + this.boundingSphere = source.boundingSphere !== null ? source.boundingSphere.clone() : null; return this; diff --git a/src/scenes/Scene.js b/src/scenes/Scene.js index 21bcb96007df7b..faad091e4be815 100644 --- a/src/scenes/Scene.js +++ b/src/scenes/Scene.js @@ -124,9 +124,9 @@ class Scene extends Object3D { super.copy( source, recursive ); - if ( source.background !== null ) this.background = source.background.clone(); - if ( source.environment !== null ) this.environment = source.environment.clone(); - if ( source.fog !== null ) this.fog = source.fog.clone(); + this.background = source.background !== null ? source.background.clone() : null; + this.environment = source.environment !== null ? source.environment.clone() : null; + this.fog = source.fog !== null ? source.fog.clone() : null; this.backgroundBlurriness = source.backgroundBlurriness; this.backgroundIntensity = source.backgroundIntensity; @@ -135,7 +135,7 @@ class Scene extends Object3D { this.environmentIntensity = source.environmentIntensity; this.environmentRotation.copy( source.environmentRotation ); - if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); + this.overrideMaterial = source.overrideMaterial !== null ? source.overrideMaterial.clone() : null; this.matrixAutoUpdate = source.matrixAutoUpdate; From a29944d363416ca5b7f170673c92566c094ab6e9 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 28 Sep 2026 16:31:38 +0200 Subject: [PATCH 4/4] WebGPURenderer: Derive `viewport` and `stencil` flags from render context. (#34692) --- src/renderers/common/Renderer.js | 13 +++++++------ src/renderers/webgl-fallback/WebGLBackend.js | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 3ec08793a62f59..678541a917ccfb 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -1839,10 +1839,6 @@ class Renderer { } - this.getDrawingBufferSize( _drawingBufferSize ); - - _screen.set( 0, 0, _drawingBufferSize.width, _drawingBufferSize.height ); - const minDepth = ( viewport.minDepth === undefined ) ? 0 : viewport.minDepth; const maxDepth = ( viewport.maxDepth === undefined ) ? 1 : viewport.maxDepth; @@ -1851,10 +1847,8 @@ class Renderer { renderContext.viewportValue.height >>= activeMipmapLevel; renderContext.viewportValue.minDepth = minDepth; renderContext.viewportValue.maxDepth = maxDepth; - renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false; renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor(); - renderContext.scissor = canvasTarget._scissorTest && renderContext.scissorValue.equals( _screen ) === false; renderContext.scissorValue.width >>= activeMipmapLevel; renderContext.scissorValue.height >>= activeMipmapLevel; @@ -1912,6 +1906,8 @@ class Renderer { } else { + this.getDrawingBufferSize( _drawingBufferSize ); + renderContext.textures = null; renderContext.depthTexture = null; renderContext.width = _drawingBufferSize.width; @@ -1944,6 +1940,11 @@ class Renderer { } + _screen.set( 0, 0, renderContext.width, renderContext.height ); + + renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false; + renderContext.scissor = canvasTarget._scissorTest && renderContext.scissorValue.equals( _screen ) === false; + // this._background.update( sceneRef, renderList, renderContext ); diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index 4d01e2748891be..915ca6e787f91b 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -491,7 +491,7 @@ class WebGLBackend extends Backend { } else { - const { width, height } = this.getDrawingBufferSize(); + const { width, height } = renderContext; state.viewport( 0, 0, width, height ); } @@ -502,7 +502,7 @@ class WebGLBackend extends Backend { } else { - const { width, height } = this.getDrawingBufferSize(); + const { width, height } = renderContext; state.scissor( 0, 0, width, height ); } @@ -603,7 +603,7 @@ class WebGLBackend extends Backend { } else { - const { width, height } = this.getDrawingBufferSize(); + const { width, height } = previousContext; state.viewport( 0, 0, width, height ); } @@ -614,7 +614,7 @@ class WebGLBackend extends Backend { } else { - const { width, height } = this.getDrawingBufferSize(); + const { width, height } = previousContext; state.scissor( 0, 0, width, height ); }