diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index ff616efe8e7fc3..2ba911e6e78ba4 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -725,6 +725,14 @@ class Backend { } + /** + * Delete GPU data associated with a bind group. + * + * @abstract + * @param {BindGroup} bindGroup - The bind group. + */ + deleteBindGroupData( /*bindGroup*/ ) { } + /** * Deletes an object from the internal data structure. * diff --git a/src/renderers/common/Bindings.js b/src/renderers/common/Bindings.js index 89339b20f078f5..f7675a18046e38 100644 --- a/src/renderers/common/Bindings.js +++ b/src/renderers/common/Bindings.js @@ -164,6 +164,7 @@ class Bindings extends DataMap { for ( const bindGroup of bindings ) { + this.backend.deleteBindGroupData( bindGroup ); this.delete( bindGroup ); } @@ -181,6 +182,7 @@ class Bindings extends DataMap { for ( const bindGroup of bindings ) { + this.backend.deleteBindGroupData( bindGroup ); this.delete( bindGroup ); } diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 93cdc4cc9f6a93..6467b4d40ae947 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -1652,7 +1652,9 @@ class WebGPUBackend extends Backend { data[ 0 ] = i; - const bindGroupIndex = this.bindingUtils.createBindGroupIndex( data, bindingsData.layout ); + const { layoutGPU } = bindingsData.layout; + + const bindGroupIndex = this.bindingUtils.createBindGroupIndex( data, layoutGPU ); indexesGPU.push( bindGroupIndex ); @@ -2119,6 +2121,17 @@ class WebGPUBackend extends Backend { } + /** + * Delete data associated with the current bind group. + * + * @param {BindGroup} bindGroup - The bind group. + */ + deleteBindGroupData( bindGroup ) { + + this.bindingUtils.deleteBindGroupData( bindGroup ); + + } + /** * Updates the given bind group definition. * @@ -2474,6 +2487,7 @@ class WebGPUBackend extends Backend { dispose() { this.textureUtils.dispose(); + this.bindingUtils.dispose(); } diff --git a/src/renderers/webgpu/utils/WebGPUBindingUtils.js b/src/renderers/webgpu/utils/WebGPUBindingUtils.js index cfe8db92f7f2bf..8a72f09df009e2 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -7,6 +7,37 @@ import { FloatType, IntType, UnsignedIntType } from '../../../constants.js'; import { NodeAccess } from '../../../nodes/core/constants.js'; import { isTypedArray, error } from '../../../utils.js'; +/** +* Class representing a WebGPU bind group layout. +* +*/ +class BindGroupLayout { + + /** + * Constructs a new BindGroupLayout. + * + * @param {GPUBindGroupLayout} layoutGPU - A GPU Bind Group Layout. + */ + constructor( layoutGPU ) { + + /** + * The current GPUBindGroupLayout + * + * @type {GPUBindGroupLayout} + */ + this.layoutGPU = layoutGPU; + + /** + * The number of bind groups that use the current GPUBindGroupLayout + * + * @type {number} + */ + this.usedTimes = 0; + + } + +} + /** * A WebGPU backend utility module for managing bindings. * @@ -34,11 +65,11 @@ class WebGPUBindingUtils { this.backend = backend; /** - * A cache for managing bind group layouts. + * A cache that maps combinations of layout entries to existing bind group layouts. * - * @type {WeakMap,GPUBindGroupLayout>} + * @type {Map} */ - this.bindGroupLayoutCache = new WeakMap(); + this.bindGroupLayoutCache = new Map(); } @@ -53,185 +84,33 @@ class WebGPUBindingUtils { const backend = this.backend; const device = backend.device; - const entries = []; - - let index = 0; - - for ( const binding of bindGroup.bindings ) { - - const bindingGPU = { - binding: index ++, - visibility: binding.visibility - }; - - if ( binding.isUniformBuffer || binding.isStorageBuffer ) { - - const buffer = {}; // GPUBufferBindingLayout - - if ( binding.isStorageBuffer ) { - - if ( binding.visibility & GPUShaderStage.COMPUTE ) { - - // compute - - if ( binding.access === NodeAccess.READ_WRITE || binding.access === NodeAccess.WRITE_ONLY ) { - - buffer.type = GPUBufferBindingType.Storage; - - } else { - - buffer.type = GPUBufferBindingType.ReadOnlyStorage; - - } - - } else { - - buffer.type = GPUBufferBindingType.ReadOnlyStorage; - - } - - } - - bindingGPU.buffer = buffer; - - } else if ( binding.isSampledTexture && binding.store ) { - - const storageTexture = {}; // GPUStorageTextureBindingLayout - storageTexture.format = this.backend.get( binding.texture ).texture.format; - - const access = binding.access; - - if ( access === NodeAccess.READ_WRITE ) { - - storageTexture.access = GPUStorageTextureAccess.ReadWrite; - - } else if ( access === NodeAccess.WRITE_ONLY ) { - - storageTexture.access = GPUStorageTextureAccess.WriteOnly; - - } else { - - storageTexture.access = GPUStorageTextureAccess.ReadOnly; - - } - - if ( binding.texture.isArrayTexture ) { - - storageTexture.viewDimension = GPUTextureViewDimension.TwoDArray; - - } else if ( binding.texture.is3DTexture ) { - - storageTexture.viewDimension = GPUTextureViewDimension.ThreeD; - - } - - bindingGPU.storageTexture = storageTexture; - - } else if ( binding.isSampledTexture ) { - - const texture = {}; // GPUTextureBindingLayout - - const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); - - if ( primarySamples > 1 ) { - - texture.multisampled = true; - - if ( ! binding.texture.isDepthTexture ) { - - texture.sampleType = GPUTextureSampleType.UnfilterableFloat; - - } - - } - - if ( binding.texture.isDepthTexture ) { - - if ( backend.compatibilityMode && binding.texture.compareFunction === null ) { - - texture.sampleType = GPUTextureSampleType.UnfilterableFloat; - - } else { - - texture.sampleType = GPUTextureSampleType.Depth; - - } - - } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture ) { - - const type = binding.texture.type; - - if ( type === IntType ) { - - texture.sampleType = GPUTextureSampleType.SInt; - - } else if ( type === UnsignedIntType ) { - - texture.sampleType = GPUTextureSampleType.UInt; - - } else if ( type === FloatType ) { - - if ( this.backend.hasFeature( 'float32-filterable' ) ) { - - texture.sampleType = GPUTextureSampleType.Float; - - } else { - - texture.sampleType = GPUTextureSampleType.UnfilterableFloat; - - } - - } - - } - - if ( binding.isSampledCubeTexture ) { - - texture.viewDimension = GPUTextureViewDimension.Cube; - - } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { - - texture.viewDimension = GPUTextureViewDimension.TwoDArray; - - } else if ( binding.isSampledTexture3D ) { - - texture.viewDimension = GPUTextureViewDimension.ThreeD; - - } - - bindingGPU.texture = texture; - - } else if ( binding.isSampler ) { + const bindingsData = backend.get( bindGroup ); - const sampler = {}; // GPUSamplerBindingLayout + // When current bind group has already been assigned a layout + if ( bindingsData.bindGroupLayout !== undefined ) { - if ( binding.texture.isDepthTexture ) { + return bindingsData.bindGroupLayout.layoutGPU; - if ( binding.texture.compareFunction !== null ) { + } - sampler.type = GPUSamplerBindingType.Comparison; + const entries = this._createBindingsLayoutEntries( bindGroup ); - } else if ( backend.compatibilityMode ) { + const bindGroupLayoutKey = JSON.stringify( entries ); - sampler.type = GPUSamplerBindingType.NonFiltering; + let bindGroupLayout = this.bindGroupLayoutCache.get( bindGroupLayoutKey ); - } - - } + if ( bindGroupLayout === undefined ) { - bindingGPU.sampler = sampler; - - } else { - - error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` ); - - } - - entries.push( bindingGPU ); + bindGroupLayout = new BindGroupLayout( device.createBindGroupLayout( { entries } ) ); + this.bindGroupLayoutCache.set( bindGroupLayoutKey, bindGroupLayout ); } - return device.createBindGroupLayout( { entries } ); + bindingsData.layout = bindGroupLayout; + bindingsData.layout.usedTimes ++; + bindingsData.layoutKey = bindGroupLayoutKey; + + return bindGroupLayout.layoutGPU; } @@ -245,19 +124,12 @@ class WebGPUBindingUtils { */ createBindings( bindGroup, bindings, cacheIndex, version = 0 ) { - const { backend, bindGroupLayoutCache } = this; + const { backend } = this; const bindingsData = backend.get( bindGroup ); // setup (static) binding layout and (dynamic) binding group - let bindLayoutGPU = bindGroupLayoutCache.get( bindGroup.bindingsReference ); - - if ( bindLayoutGPU === undefined ) { - - bindLayoutGPU = this.createBindingsLayout( bindGroup ); - bindGroupLayoutCache.set( bindGroup.bindingsReference, bindLayoutGPU ); - - } + const bindLayoutGPU = this.createBindingsLayout( bindGroup ); let bindGroupGPU; @@ -292,7 +164,6 @@ class WebGPUBindingUtils { } bindingsData.group = bindGroupGPU; - bindingsData.layout = bindLayoutGPU; } @@ -354,10 +225,10 @@ class WebGPUBindingUtils { * Creates a GPU bind group for the camera index. * * @param {Uint32Array} data - The index data. - * @param {GPUBindGroupLayout} layout - The GPU bind group layout. + * @param {GPUBindGroupLayout} layoutGPU - The GPU bind group layout. * @return {GPUBindGroup} The GPU bind group. */ - createBindGroupIndex( data, layout ) { + createBindGroupIndex( data, layoutGPU ) { const backend = this.backend; const device = backend.device; @@ -377,7 +248,7 @@ class WebGPUBindingUtils { return device.createBindGroup( { label: 'bindGroupCameraIndex_' + index, - layout, + layout: layoutGPU, entries } ); @@ -538,6 +409,242 @@ class WebGPUBindingUtils { } + /** + * Creates a bind group layout entry for the given binding. + * + * @param {Binding} binding - The binding. + * @param {number} index - The index of the bind group layout entry in the bind group layout. + * @return {GPUBindGroupLayoutEntry} The bind group layout entry. + */ + _createBindingLayoutEntry( binding, index ) { + + const backend = this.backend; + + const bindingGPU = { + binding: index, + visibility: binding.visibility + }; + + if ( binding.isUniformBuffer || binding.isStorageBuffer ) { + + const buffer = {}; // GPUBufferBindingLayout + + if ( binding.isStorageBuffer ) { + + if ( binding.visibility & GPUShaderStage.COMPUTE ) { + + // compute + + if ( binding.access === NodeAccess.READ_WRITE || binding.access === NodeAccess.WRITE_ONLY ) { + + buffer.type = GPUBufferBindingType.Storage; + + } else { + + buffer.type = GPUBufferBindingType.ReadOnlyStorage; + + } + + } else { + + buffer.type = GPUBufferBindingType.ReadOnlyStorage; + + } + + } + + bindingGPU.buffer = buffer; + + } else if ( binding.isSampledTexture && binding.store ) { + + const storageTexture = {}; // GPUStorageTextureBindingLayout + storageTexture.format = this.backend.get( binding.texture ).texture.format; + + const access = binding.access; + + if ( access === NodeAccess.READ_WRITE ) { + + storageTexture.access = GPUStorageTextureAccess.ReadWrite; + + } else if ( access === NodeAccess.WRITE_ONLY ) { + + storageTexture.access = GPUStorageTextureAccess.WriteOnly; + + } else { + + storageTexture.access = GPUStorageTextureAccess.ReadOnly; + + } + + if ( binding.texture.isArrayTexture ) { + + storageTexture.viewDimension = GPUTextureViewDimension.TwoDArray; + + } else if ( binding.texture.is3DTexture ) { + + storageTexture.viewDimension = GPUTextureViewDimension.ThreeD; + + } + + bindingGPU.storageTexture = storageTexture; + + } else if ( binding.isSampledTexture ) { + + const texture = {}; // GPUTextureBindingLayout + + const { primarySamples } = backend.utils.getTextureSampleData( binding.texture ); + + if ( primarySamples > 1 ) { + + texture.multisampled = true; + + if ( ! binding.texture.isDepthTexture ) { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } + + } + + if ( binding.texture.isDepthTexture ) { + + if ( backend.compatibilityMode && binding.texture.compareFunction === null ) { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } else { + + texture.sampleType = GPUTextureSampleType.Depth; + + } + + } else if ( binding.texture.isDataTexture || binding.texture.isDataArrayTexture || binding.texture.isData3DTexture ) { + + const type = binding.texture.type; + + if ( type === IntType ) { + + texture.sampleType = GPUTextureSampleType.SInt; + + } else if ( type === UnsignedIntType ) { + + texture.sampleType = GPUTextureSampleType.UInt; + + } else if ( type === FloatType ) { + + if ( this.backend.hasFeature( 'float32-filterable' ) ) { + + texture.sampleType = GPUTextureSampleType.Float; + + } else { + + texture.sampleType = GPUTextureSampleType.UnfilterableFloat; + + } + + } + + } + + if ( binding.isSampledCubeTexture ) { + + texture.viewDimension = GPUTextureViewDimension.Cube; + + } else if ( binding.texture.isArrayTexture || binding.texture.isDataArrayTexture || binding.texture.isCompressedArrayTexture ) { + + texture.viewDimension = GPUTextureViewDimension.TwoDArray; + + } else if ( binding.isSampledTexture3D ) { + + texture.viewDimension = GPUTextureViewDimension.ThreeD; + + } + + bindingGPU.texture = texture; + + } else if ( binding.isSampler ) { + + const sampler = {}; // GPUSamplerBindingLayout + + if ( binding.texture.isDepthTexture ) { + + if ( binding.texture.compareFunction !== null ) { + + sampler.type = GPUSamplerBindingType.Comparison; + + } else if ( backend.compatibilityMode ) { + + sampler.type = GPUSamplerBindingType.NonFiltering; + + } + + } + + bindingGPU.sampler = sampler; + + } else { + + error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` ); + + } + + return bindingGPU; + + } + + /** + * Creates a GPU bind group layout entries for the given bind group. + * + * @param {BindGroup} bindGroup - The bind group. + * @return {Array} The GPU bind group layout entries. + */ + _createBindingsLayoutEntries( bindGroup ) { + + const entries = []; + let index = 0; + + for ( const binding of bindGroup.bindings ) { + + entries.push( this._createBindingLayoutEntry( binding, index ) ); + index ++; + + } + + return entries; + + } + + /** + * Delete the data associated with a bind group. + * + * @param {BindGroup} bindGroup - The bind group. + */ + deleteBindGroupData( bindGroup ) { + + const { backend } = this; + + const bindingsData = backend.get( bindGroup ); + + // Decrement the layout reference's usedTimes attribute + bindingsData.layout.usedTimes --; + + // Remove reference from map + if ( bindingsData.layout.usedTimes === 0 ) { + + this.bindGroupLayoutCache.delete( bindingsData.layoutKey ); + + } + + bindingsData.layout = null; + + } + + dispose() { + + this.bindGroupLayoutCache.clear(); + + } + } export default WebGPUBindingUtils; diff --git a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js index 330d48cfb15545..5b1a2e4c4da2ad 100644 --- a/src/renderers/webgpu/utils/WebGPUPipelineUtils.js +++ b/src/renderers/webgpu/utils/WebGPUPipelineUtils.js @@ -106,8 +106,9 @@ class WebGPUPipelineUtils { for ( const bindGroup of renderObject.getBindings() ) { const bindingsData = backend.get( bindGroup ); + const { layoutGPU } = bindingsData.layout; - bindGroupLayouts.push( bindingsData.layout ); + bindGroupLayouts.push( layoutGPU ); } @@ -341,8 +342,9 @@ class WebGPUPipelineUtils { for ( const bindingsGroup of bindings ) { const bindingsData = backend.get( bindingsGroup ); + const { layoutGPU } = bindingsData.layout; - bindGroupLayouts.push( bindingsData.layout ); + bindGroupLayouts.push( layoutGPU ); }