From 3105f51c85ae342cf5f425a945370fa888fbe939 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 17 Apr 2026 10:30:05 +0200 Subject: [PATCH 1/5] Loaders, Editor: Improve handling of assets with unicode characters. (#33301) --- editor/js/Loader.js | 22 +++++++++++++++++++++- src/loaders/LoadingManager.js | 5 +++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/editor/js/Loader.js b/editor/js/Loader.js index 896a0a591cec2c..f45d5950fcee4d 100644 --- a/editor/js/Loader.js +++ b/editor/js/Loader.js @@ -45,6 +45,14 @@ function Loader( editor ) { while ( normalized.startsWith( '../' ) ) normalized = normalized.slice( 3 ); while ( normalized.startsWith( '/' ) ) normalized = normalized.slice( 1 ); + try { + + normalized = decodeURIComponent( normalized ); + + } catch ( e ) { /* malformed URI — keep as-is */ } + + normalized = normalized.normalize( 'NFC' ); + return normalized; }; @@ -956,10 +964,22 @@ function Loader( editor ) { const zip = unzipSync( new Uint8Array( contents ) ); + // Build a lookup map with NFC-normalized keys to handle + // unicode normalization differences (e.g. NFD vs NFC) + + const zipLookup = {}; + + for ( const path in zip ) { + + zipLookup[ path.normalize( 'NFC' ) ] = zip[ path ]; + + } + const manager = new THREE.LoadingManager(); manager.setURLModifier( function ( url ) { - const file = zip[ url ]; + const normalized = decodeURIComponent( url ).normalize( 'NFC' ); + const file = zipLookup[ normalized ]; if ( file ) { diff --git a/src/loaders/LoadingManager.js b/src/loaders/LoadingManager.js index 02c3991d488a38..97301477fc455c 100644 --- a/src/loaders/LoadingManager.js +++ b/src/loaders/LoadingManager.js @@ -156,6 +156,11 @@ class LoadingManager { */ this.resolveURL = function ( url ) { + // Normalize to NFC so that Unicode URIs (e.g. from glTF) + // are percent-encoded correctly per RFC 3987. + + url = url.normalize( 'NFC' ); + if ( urlModifier ) { return urlModifier( url ); From 91eec1a56eb260b2412cce6581aa8dc9cd595cff Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 17 Apr 2026 11:45:52 +0200 Subject: [PATCH 2/5] Update constants.js Update revision. --- src/constants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants.js b/src/constants.js index 550129337fb21b..d6ea488f9e3604 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,4 +1,4 @@ -export const REVISION = '184'; +export const REVISION = '185dev'; /** * Represents mouse buttons and interaction types in context of controls. From 3c084b0ec678f996a4c5630d5f991575f654d6f1 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 17 Apr 2026 12:23:32 +0200 Subject: [PATCH 3/5] Global: Remove deprecated code. (#33407) --- src/animation/AnimationClip.js | 139 --------------------------------- src/nodes/math/OperatorNode.js | 20 ----- 2 files changed, 159 deletions(-) diff --git a/src/animation/AnimationClip.js b/src/animation/AnimationClip.js index f23cf6c5e233b8..ee5bd919b209cd 100644 --- a/src/animation/AnimationClip.js +++ b/src/animation/AnimationClip.js @@ -8,7 +8,6 @@ import { StringKeyframeTrack } from './tracks/StringKeyframeTrack.js'; import { VectorKeyframeTrack } from './tracks/VectorKeyframeTrack.js'; import { generateUUID } from '../math/MathUtils.js'; import { NormalAnimationBlendMode } from '../constants.js'; -import { warn, error } from '../utils.js'; /** * A reusable set of keyframe tracks which represent an animation. @@ -295,144 +294,6 @@ class AnimationClip { } - /** - * Parses the `animation.hierarchy` format and returns a new animation clip. - * - * @static - * @deprecated since r175. - * @param {Object} animation - A serialized animation clip as JSON. - * @param {Array} bones - An array of bones. - * @return {?AnimationClip} The new animation clip. - */ - static parseAnimation( animation, bones ) { - - warn( 'AnimationClip: parseAnimation() is deprecated and will be removed with r185' ); - - if ( ! animation ) { - - error( 'AnimationClip: No animation in JSONLoader data.' ); - return null; - - } - - const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { - - // only return track if there are actually keys. - if ( animationKeys.length !== 0 ) { - - const times = []; - const values = []; - - AnimationUtils.flattenJSON( animationKeys, times, values, propertyName ); - - // empty keys are filtered out, so check again - if ( times.length !== 0 ) { - - destTracks.push( new trackType( trackName, times, values ) ); - - } - - } - - }; - - const tracks = []; - - const clipName = animation.name || 'default'; - const fps = animation.fps || 30; - const blendMode = animation.blendMode; - - // automatic length determination in AnimationClip. - let duration = animation.length || - 1; - - const hierarchyTracks = animation.hierarchy || []; - - for ( let h = 0; h < hierarchyTracks.length; h ++ ) { - - const animationKeys = hierarchyTracks[ h ].keys; - - // skip empty tracks - if ( ! animationKeys || animationKeys.length === 0 ) continue; - - // process morph targets - if ( animationKeys[ 0 ].morphTargets ) { - - // figure out all morph targets used in this track - const morphTargetNames = {}; - - let k; - - for ( k = 0; k < animationKeys.length; k ++ ) { - - if ( animationKeys[ k ].morphTargets ) { - - for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { - - morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; - - } - - } - - } - - // create a track for each morph target with all zero - // morphTargetInfluences except for the keys in which - // the morphTarget is named. - for ( const morphTargetName in morphTargetNames ) { - - const times = []; - const values = []; - - for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { - - const animationKey = animationKeys[ k ]; - - times.push( animationKey.time ); - values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); - - } - - tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); - - } - - duration = morphTargetNames.length * fps; - - } else { - - // ...assume skeletal animation - - const boneName = '.bones[' + bones[ h ].name + ']'; - - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.position', - animationKeys, 'pos', tracks ); - - addNonemptyTrack( - QuaternionKeyframeTrack, boneName + '.quaternion', - animationKeys, 'rot', tracks ); - - addNonemptyTrack( - VectorKeyframeTrack, boneName + '.scale', - animationKeys, 'scl', tracks ); - - } - - } - - if ( tracks.length === 0 ) { - - return null; - - } - - const clip = new this( clipName, duration, tracks, blendMode ); - - return clip; - - } - /** * Sets the duration of this clip to the duration of its longest keyframe track. * diff --git a/src/nodes/math/OperatorNode.js b/src/nodes/math/OperatorNode.js index c06a906ba73c4d..2d044b495cee3c 100644 --- a/src/nodes/math/OperatorNode.js +++ b/src/nodes/math/OperatorNode.js @@ -1,8 +1,6 @@ import { WebGLCoordinateSystem } from '../../constants.js'; import TempNode from '../core/TempNode.js'; -import StackTrace from '../core/StackTrace.js'; import { addMethodChaining, Fn, int, nodeProxyIntent } from '../tsl/TSLCore.js'; -import { warn } from '../../utils.js'; const _vectorOperators = { '==': 'equal', @@ -732,21 +730,3 @@ addMethodChaining( 'incrementBefore', incrementBefore ); addMethodChaining( 'decrementBefore', decrementBefore ); addMethodChaining( 'increment', increment ); addMethodChaining( 'decrement', decrement ); - -/** - * @tsl - * @function - * @deprecated since r175. Use {@link mod} instead. - * - * @param {Node} a - The first input. - * @param {Node} b - The second input. - * @returns {OperatorNode} - */ -export const modInt = ( a, b ) => { // @deprecated, r175 - - warn( 'TSL: "modInt()" is deprecated. Use "mod( int( ... ) )" instead.', new StackTrace() ); - return mod( int( a ), int( b ) ); - -}; - -addMethodChaining( 'modInt', modInt ); From 1453d4ecbb2130559db0c2105738f3f28462073f Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 17 Apr 2026 12:30:14 +0200 Subject: [PATCH 4/5] Revert "PassNode: Fix depthTexture creation when `depthBuffer: false`" (#33408) --- src/nodes/display/PassNode.js | 33 ++++++++++---------------------- src/renderers/common/Textures.js | 28 +++++++++++---------------- 2 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index 7fbe603fc60cd5..a75f1fd4f9f0cb 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -251,22 +251,14 @@ class PassNode extends TempNode { */ this._height = 1; + const depthTexture = new DepthTexture(); + depthTexture.isRenderTargetTexture = true; + //depthTexture.type = FloatType; + depthTexture.name = 'depth'; + const renderTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType, ...options, } ); renderTarget.texture.name = 'output'; - - let depthTexture = null; - - if ( this.scope === PassNode.DEPTH || options.depthBuffer !== false ) { - - depthTexture = new DepthTexture(); - depthTexture.isRenderTargetTexture = true; - //depthTexture.type = FloatType; - depthTexture.name = 'depth'; - - renderTarget.depthTexture = depthTexture; - - } - + renderTarget.depthTexture = depthTexture; /** * The pass's render target. @@ -318,18 +310,13 @@ class PassNode extends TempNode { * A dictionary holding the internal result textures. * * @private - * @type {{ output: Texture, depth?: DepthTexture }} + * @type {Object} */ this._textures = { - output: renderTarget.texture + output: renderTarget.texture, + depth: depthTexture }; - if ( depthTexture !== null ) { - - this._textures.depth = depthTexture; - - } - /** * A dictionary holding the internal texture nodes. * @@ -770,7 +757,7 @@ class PassNode extends TempNode { this.renderTarget.texture.type = renderer.getOutputBufferType(); - if ( renderer.reversedDepthBuffer === true && this.renderTarget.depthTexture !== null ) { + if ( renderer.reversedDepthBuffer === true ) { this.renderTarget.depthTexture.type = FloatType; diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js index 51d13d1cf3b0ef..32c4b07b42f53d 100644 --- a/src/renderers/common/Textures.js +++ b/src/renderers/common/Textures.js @@ -78,30 +78,24 @@ class Textures extends DataMap { const mipWidth = size.width >> activeMipmapLevel; const mipHeight = size.height >> activeMipmapLevel; + let depthTexture = renderTarget.depthTexture || depthTextureMips[ activeMipmapLevel ]; const useDepthTexture = renderTarget.depthBuffer === true || renderTarget.stencilBuffer === true; - let depthTexture = null; let textureNeedsUpdate = false; - if ( useDepthTexture ) { + if ( depthTexture === undefined && useDepthTexture ) { - depthTexture = renderTarget.depthTexture || depthTextureMips[ activeMipmapLevel ]; + depthTexture = new DepthTexture(); - if ( depthTexture === undefined ) { + depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat; + depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType + depthTexture.image.width = mipWidth; + depthTexture.image.height = mipHeight; + depthTexture.image.depth = size.depth; + depthTexture.renderTarget = renderTarget; + depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1; - depthTexture = new DepthTexture(); - - depthTexture.format = renderTarget.stencilBuffer ? DepthStencilFormat : DepthFormat; - depthTexture.type = renderTarget.stencilBuffer ? UnsignedInt248Type : UnsignedIntType; // FloatType - depthTexture.image.width = mipWidth; - depthTexture.image.height = mipHeight; - depthTexture.image.depth = size.depth; - depthTexture.renderTarget = renderTarget; - depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1; - - depthTextureMips[ activeMipmapLevel ] = depthTexture; - - } + depthTextureMips[ activeMipmapLevel ] = depthTexture; } From 4286974d495716570fd66670c44553d09d58e040 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 17 Apr 2026 12:31:02 +0200 Subject: [PATCH 5/5] Revert "Backend: Introduce `createUniformBuffer()`" (#33409) --- examples/jsm/inspector/tabs/Timeline.js | 24 +--- src/renderers/common/Backend.js | 16 --- src/renderers/common/Bindings.js | 106 ++++++------------ src/renderers/webgl-fallback/WebGLBackend.js | 59 ++++------ src/renderers/webgpu/WebGPUBackend.js | 66 +---------- .../webgpu/utils/WebGPUBindingUtils.js | 37 ++++++ 6 files changed, 92 insertions(+), 216 deletions(-) diff --git a/examples/jsm/inspector/tabs/Timeline.js b/examples/jsm/inspector/tabs/Timeline.js index 25e20e50dbd451..9874dc5cdab886 100644 --- a/examples/jsm/inspector/tabs/Timeline.js +++ b/examples/jsm/inspector/tabs/Timeline.js @@ -1044,29 +1044,11 @@ class Timeline extends Tab { case 'updateBindings': { const bindGroup = args[ 0 ]; + const details = { group: bindGroup.name || 'unknown' }; - const details = { - group: bindGroup.name || 'unknown', - count: bindGroup.bindings.length - }; - - return details; - - } - - case 'createUniformBuffer': - case 'destroyUniformBuffer': { - - const binding = args[ 0 ]; - - const details = { - group: binding.groupNode.name || 'unknown', - size: binding.byteLength + ' bytes' - }; - - if ( binding.name !== details.group ) { + if ( bindGroup.bindings ) { - details.name = binding.name; + details.count = bindGroup.bindings.length; } diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index c0631941e33b35..faf3d77a86bfec 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -389,22 +389,6 @@ class Backend { */ createStorageAttribute( /*attribute*/ ) { } - /** - * Creates a uniform buffer. - * - * @abstract - * @param {Buffer} uniformBuffer - The uniform buffer. - */ - createUniformBuffer( /*uniformBuffer*/ ) { } - - /** - * Destroys a uniform buffer. - * - * @abstract - * @param {Buffer} uniformBuffer - The uniform buffer. - */ - destroyUniformBuffer( /*uniformBuffer*/ ) { } - /** * Updates the GPU buffer of a shader attribute. * diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js index fdce475f38385a..3ccc9191836b40 100644 --- a/src/renderers/common/Bindings.js +++ b/src/renderers/common/Bindings.js @@ -77,39 +77,39 @@ class Bindings extends DataMap { */ getForRender( renderObject ) { - return this._getBindings( renderObject, renderObject.getBindings() ); + const bindings = renderObject.getBindings(); - } + for ( const bindGroup of bindings ) { - /** - * Returns the bind groups for the given compute node. - * - * @param {Node} computeNode - The compute node. - * @return {Array} The bind groups. - */ - getForCompute( computeNode ) { + const groupData = this.get( bindGroup ); - return this._getBindings( computeNode, this.nodes.getForCompute( computeNode ).bindings ); + if ( groupData.bindGroup === undefined ) { - } + // each object defines an array of bindings (ubos, textures, samplers etc.) - _getBindings( object, bindings ) { + this._init( bindGroup ); - const data = this.get( object ); + this.backend.createBindings( bindGroup, bindings, 0 ); - if ( data.bindings !== bindings ) { + groupData.bindGroup = bindGroup; - if ( data.bindings !== undefined ) { + } - this._updateBindingsUsage( data.bindings, - 1 ); + } - } + return bindings; - this._updateBindingsUsage( bindings, 1 ); + } - data.bindings = bindings; + /** + * Returns the bind groups for the given compute node. + * + * @param {Node} computeNode - The compute node. + * @return {Array} The bind groups. + */ + getForCompute( computeNode ) { - } + const bindings = this.nodes.getForCompute( computeNode ).bindings; for ( const bindGroup of bindings ) { @@ -160,7 +160,14 @@ class Bindings extends DataMap { */ deleteForCompute( computeNode ) { - this._deleteBindings( computeNode ); + const bindings = this.nodes.getForCompute( computeNode ).bindings; + + for ( const bindGroup of bindings ) { + + this.backend.deleteBindGroupData( bindGroup ); + this.delete( bindGroup ); + + } } @@ -171,57 +178,12 @@ class Bindings extends DataMap { */ deleteForRender( renderObject ) { - this._deleteBindings( renderObject ); - - } - - _deleteBindings( object ) { - - const data = this.get( object ); - - if ( data.bindings !== undefined ) { - - this._updateBindingsUsage( data.bindings, - 1 ); - - data.bindings = undefined; - - } - - } - - _updateBindingsUsage( bindings, delta ) { + const bindings = renderObject.getBindings(); for ( const bindGroup of bindings ) { - const groupData = this.get( bindGroup ); - - groupData.usedTimes = ( groupData.usedTimes || 0 ) + delta; - - for ( const binding of bindGroup.bindings ) { - - if ( binding.isUniformBuffer ) { - - const bindingData = this.get( binding ); - - bindingData.usedTimes = ( bindingData.usedTimes || 0 ) + delta; - - if ( bindingData.usedTimes === 0 ) { - - this.backend.destroyUniformBuffer( binding ); - this.delete( binding ); - - } - - } - - } - - if ( groupData.usedTimes === 0 ) { - - this.backend.deleteBindGroupData( bindGroup ); - this.delete( bindGroup ); - - } + this.backend.deleteBindGroupData( bindGroup ); + this.delete( bindGroup ); } @@ -251,11 +213,7 @@ class Bindings extends DataMap { for ( const binding of bindGroup.bindings ) { - if ( binding.isUniformBuffer ) { - - this.backend.createUniformBuffer( binding ); - - } else if ( binding.isSampledTexture ) { + if ( binding.isSampledTexture ) { this.textures.updateTexture( binding.texture ); diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index f63ee32669d2f0..dc73ec3de0501a 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -1834,9 +1834,24 @@ class WebGLBackend extends Backend { if ( binding.isUniformsGroup || binding.isUniformBuffer ) { const array = binding.buffer; - const bufferGPU = map.bufferGPU; + let { bufferGPU } = this.get( array ); - gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + if ( bufferGPU === undefined ) { + + // create + + bufferGPU = gl.createBuffer(); + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + gl.bufferData( gl.UNIFORM_BUFFER, array.byteLength, gl.DYNAMIC_DRAW ); + + this.set( array, { bufferGPU } ); + + } else { + + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); + + } // update @@ -1868,6 +1883,8 @@ class WebGLBackend extends Backend { } + map.bufferGPU = bufferGPU; + this.set( binding, map ); } else if ( binding.isSampledTexture ) { @@ -1934,44 +1951,6 @@ class WebGLBackend extends Backend { // attributes - /** - * Creates a uniform buffer. - * - * @param {Buffer} uniformBuffer - The uniform buffer. - */ - createUniformBuffer( uniformBuffer ) { - - const uniformBufferData = this.get( uniformBuffer ); - - if ( uniformBufferData.bufferGPU === undefined ) { - - const gl = this.gl; - const array = uniformBuffer.buffer; - - uniformBufferData.bufferGPU = gl.createBuffer(); - - gl.bindBuffer( gl.UNIFORM_BUFFER, uniformBufferData.bufferGPU ); - gl.bufferData( gl.UNIFORM_BUFFER, array.byteLength, gl.DYNAMIC_DRAW ); - - } - - } - - /** - * Destroys the GPU data for the given uniform buffer. - * - * @param {Buffer} uniformBuffer - The uniform buffer. - */ - destroyUniformBuffer( uniformBuffer ) { - - const uniformBufferData = this.get( uniformBuffer ); - - this.gl.deleteBuffer( uniformBufferData.bufferGPU ); - - this.delete( uniformBuffer ); - - } - /** * Creates the GPU buffer of an indexed shader attribute. * diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 6d9bdfa2e1a5bb..0320d99d03572c 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -2,7 +2,7 @@ import 'https://greggman.github.io/webgpu-avoid-redundant-state-setting/webgpu-check-redundant-state-setting.js'; //*/ -import { GPUFeatureName, GPULoadOp, GPUStoreOp, GPUIndexFormat, GPUTextureViewDimension, GPUFeatureMap, GPUShaderStage } from './utils/WebGPUConstants.js'; +import { GPUFeatureName, GPULoadOp, GPUStoreOp, GPUIndexFormat, GPUTextureViewDimension, GPUFeatureMap } from './utils/WebGPUConstants.js'; import WGSLNodeBuilder from './nodes/WGSLNodeBuilder.js'; import Backend from '../common/Backend.js'; @@ -2178,70 +2178,6 @@ class WebGPUBackend extends Backend { // bindings - /** - * Creates a uniform buffer. - * - * @param {Buffer} uniformBuffer - The uniform buffer. - */ - createUniformBuffer( uniformBuffer ) { - - const uniformBufferData = this.get( uniformBuffer ); - - if ( uniformBufferData.buffer === undefined ) { - - const byteLength = uniformBuffer.byteLength; - - const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; - - const visibilities = []; - - if ( uniformBuffer.visibility & GPUShaderStage.VERTEX ) { - - visibilities.push( 'vertex' ); - - } - - if ( uniformBuffer.visibility & GPUShaderStage.FRAGMENT ) { - - visibilities.push( 'fragment' ); - - } - - if ( uniformBuffer.visibility & GPUShaderStage.COMPUTE ) { - - visibilities.push( 'compute' ); - - } - - const bufferVisibility = `(${visibilities.join( ',' )})`; - - const bufferGPU = this.device.createBuffer( { - label: `bindingBuffer${uniformBuffer.id}_${uniformBuffer.name}_${bufferVisibility}`, - size: byteLength, - usage: usage - } ); - - uniformBufferData.buffer = bufferGPU; - - } - - } - - /** - * Destroys the GPU data for the given uniform buffer. - * - * @param {Buffer} uniformBuffer - The uniform buffer. - */ - destroyUniformBuffer( uniformBuffer ) { - - const uniformBufferData = this.get( uniformBuffer ); - - uniformBufferData.buffer.destroy(); - - this.delete( uniformBuffer ); - - } - /** * Creates bindings from the given bind group definition. * diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index 7871c35c99ad18..2f9488d22deacc 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -283,6 +283,43 @@ class WebGPUBindingUtils { const bindingData = backend.get( binding ); + if ( bindingData.buffer === undefined ) { + + const byteLength = binding.byteLength; + + const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + + const visibilities = []; + if ( binding.visibility & GPUShaderStage.VERTEX ) { + + visibilities.push( 'vertex' ); + + } + + if ( binding.visibility & GPUShaderStage.FRAGMENT ) { + + visibilities.push( 'fragment' ); + + } + + if ( binding.visibility & GPUShaderStage.COMPUTE ) { + + visibilities.push( 'compute' ); + + } + + const bufferVisibility = `(${visibilities.join( ',' )})`; + + const bufferGPU = device.createBuffer( { + label: `bindingBuffer${binding.id}_${binding.name}_${bufferVisibility}`, + size: byteLength, + usage: usage + } ); + + bindingData.buffer = bufferGPU; + + } + entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } ); } else if ( binding.isStorageBuffer ) {