diff --git a/.github/workflows/codeql-code-scanning.yml b/.github/workflows/codeql-code-scanning.yml index 89413aac676ec4..26197c0ae1574a 100644 --- a/.github/workflows/codeql-code-scanning.yml +++ b/.github/workflows/codeql-code-scanning.yml @@ -30,16 +30,16 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4 + uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 with: languages: ${{ matrix.language }} config-file: ./.github/codeql-config.yml queries: security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@c10b8064de6f491fea524254123dbe5e09572f13 # v4 + uses: github/codeql-action/autobuild@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4 + uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4 with: category: "/language:${{matrix.language}}" diff --git a/examples/jsm/inspector/tabs/Memory.js b/examples/jsm/inspector/tabs/Memory.js index 9c10d5d7a676a2..d9337671d47b3e 100644 --- a/examples/jsm/inspector/tabs/Memory.js +++ b/examples/jsm/inspector/tabs/Memory.js @@ -70,6 +70,9 @@ class Memory extends Tab { this.textures = new Item( 'Textures', createValueSpan(), createValueSpan() ); this.memoryStats.add( this.textures ); + this.uniformBuffers = new Item( 'Uniform Buffers', createValueSpan(), createValueSpan() ); + this.memoryStats.add( this.uniformBuffers ); + this.graph = graph; } @@ -100,6 +103,7 @@ class Memory extends Tab { setText( this.attributes.data[ 1 ], memory.attributes.toString() ); setText( this.attributes.data[ 2 ], formatBytes( memory.attributesSize ) ); + setText( this.geometries.data[ 1 ], memory.geometries.toString() ); setText( this.indexAttributes.data[ 1 ], memory.indexAttributes.toString() ); @@ -118,9 +122,13 @@ class Memory extends Tab { setText( this.storageAttributes.data[ 1 ], memory.storageAttributes.toString() ); setText( this.storageAttributes.data[ 2 ], formatBytes( memory.storageAttributesSize ) ); + setText( this.textures.data[ 1 ], memory.textures.toString() ); setText( this.textures.data[ 2 ], formatBytes( memory.texturesSize ) ); + setText( this.uniformBuffers.data[ 1 ], memory.uniformBuffers.toString() ); + setText( this.uniformBuffers.data[ 2 ], formatBytes( memory.uniformBuffersSize ) ); + } } diff --git a/examples/jsm/inspector/tabs/Timeline.js b/examples/jsm/inspector/tabs/Timeline.js index 9874dc5cdab886..25e20e50dbd451 100644 --- a/examples/jsm/inspector/tabs/Timeline.js +++ b/examples/jsm/inspector/tabs/Timeline.js @@ -1044,11 +1044,29 @@ class Timeline extends Tab { case 'updateBindings': { const bindGroup = args[ 0 ]; - const details = { group: bindGroup.name || 'unknown' }; - if ( bindGroup.bindings ) { + 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 ) { - details.count = bindGroup.bindings.length; + details.name = binding.name; } diff --git a/examples/jsm/lighting/LightProbeGrid.js b/examples/jsm/lighting/LightProbeGrid.js index b8f39173ed85cd..b0644f065c3a4f 100644 --- a/examples/jsm/lighting/LightProbeGrid.js +++ b/examples/jsm/lighting/LightProbeGrid.js @@ -55,6 +55,9 @@ const ATLAS_PADDING = 1; * A 3D grid of L2 Spherical Harmonic irradiance probes that provides * position-dependent diffuse global illumination. * + * Note that this class can only be used with {@link WebGLRenderer}. + * A version for {@link WebGPURenderer} will be added at a later point. + * * All seven packed SH sub-volumes are stored in a **single** RGBA * `WebGL3DRenderTarget` using a texture-atlas layout along the Z axis. * Each sub-volume occupies `( nz + 2 )` atlas slices: one padding slice at diff --git a/src/materials/nodes/manager/NodeMaterialObserver.js b/src/materials/nodes/manager/NodeMaterialObserver.js index e8bdf78d7f2c17..089b9ed47a6826 100644 --- a/src/materials/nodes/manager/NodeMaterialObserver.js +++ b/src/materials/nodes/manager/NodeMaterialObserver.js @@ -216,7 +216,7 @@ class NodeMaterialObserver { } - data.lights = this.getLightsData( renderObject.lightsNode.getLights() ); + data.lights = this.getLightsData( renderObject.lightsNode.getLights(), [] ); this.renderObjects.set( renderObject, data ); @@ -629,9 +629,9 @@ class NodeMaterialObserver { * @param {Array} materialLights - The material lights. * @return {Array} The lights data for the given material lights. */ - getLightsData( materialLights ) { + getLightsData( materialLights, lights ) { - const lights = []; + lights.length = 0; for ( const light of materialLights ) { @@ -658,23 +658,25 @@ class NodeMaterialObserver { */ getLights( lightsNode, renderId ) { - if ( _lightsCache.has( lightsNode ) ) { + let cached = _lightsCache.get( lightsNode ); - const cached = _lightsCache.get( lightsNode ); + if ( cached === undefined ) { - if ( cached.renderId === renderId ) { + cached = { renderId: - 1, lightsData: [] }; + _lightsCache.set( lightsNode, cached ); - return cached.lightsData; + } - } + if ( cached.renderId === renderId ) { - } + return cached.lightsData; - const lightsData = this.getLightsData( lightsNode.getLights() ); + } - _lightsCache.set( lightsNode, { renderId, lightsData } ); + cached.renderId = renderId; + this.getLightsData( lightsNode.getLights(), cached.lightsData ); - return lightsData; + return cached.lightsData; } diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index faf3d77a86bfec..c0631941e33b35 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -389,6 +389,22 @@ 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 3ccc9191836b40..f2f906f55e9479 100644 --- a/src/renderers/common/Bindings.js +++ b/src/renderers/common/Bindings.js @@ -79,21 +79,15 @@ class Bindings extends DataMap { const bindings = renderObject.getBindings(); - for ( const bindGroup of bindings ) { - - const groupData = this.get( bindGroup ); + const renderObjectData = this.get( renderObject ); - if ( groupData.bindGroup === undefined ) { + if ( renderObjectData.initialized !== true ) { - // each object defines an array of bindings (ubos, textures, samplers etc.) + // bind groups are created once per object - this._init( bindGroup ); + this._createBindings( bindings ); - this.backend.createBindings( bindGroup, bindings, 0 ); - - groupData.bindGroup = bindGroup; - - } + renderObjectData.initialized = true; } @@ -110,20 +104,15 @@ class Bindings extends DataMap { getForCompute( computeNode ) { const bindings = this.nodes.getForCompute( computeNode ).bindings; + const computeNodeData = this.get( computeNode ); - for ( const bindGroup of bindings ) { - - const groupData = this.get( bindGroup ); - - if ( groupData.bindGroup === undefined ) { + if ( computeNodeData.initialized !== true ) { - this._init( bindGroup ); + // bind groups are created once per object - this.backend.createBindings( bindGroup, bindings, 0 ); + this._createBindings( bindings ); - groupData.bindGroup = bindGroup; - - } + computeNodeData.initialized = true; } @@ -162,12 +151,9 @@ class Bindings extends DataMap { const bindings = this.nodes.getForCompute( computeNode ).bindings; - for ( const bindGroup of bindings ) { + this._destroyBindings( bindings ); - this.backend.deleteBindGroupData( bindGroup ); - this.delete( bindGroup ); - - } + this.delete( computeNode ); } @@ -180,53 +166,103 @@ class Bindings extends DataMap { const bindings = renderObject.getBindings(); - for ( const bindGroup of bindings ) { - - this.backend.deleteBindGroupData( bindGroup ); - this.delete( bindGroup ); + this._destroyBindings( bindings ); - } + this.delete( renderObject ); } /** - * Updates the given array of bindings. + * Creates the bindings for the given array of bindings. * * @param {Array} bindings - The bind groups. */ - _updateBindings( bindings ) { + _createBindings( bindings ) { for ( const bindGroup of bindings ) { - this._update( bindGroup, bindings ); + // binding group + + const groupData = this.get( bindGroup ); + + if ( groupData.bindGroup === undefined ) { + + // initialize + + for ( const binding of bindGroup.bindings ) { + + if ( binding.isUniformBuffer ) { + + this.backend.createUniformBuffer( binding ); + this.info.createUniformBuffer( binding ); + + } else if ( binding.isSampledTexture ) { + + this.textures.updateTexture( binding.texture ); + + } else if ( binding.isSampler ) { + + this.textures.updateSampler( binding.texture ); + + } else if ( binding.isStorageBuffer ) { + + const attribute = binding.attribute; + const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE; + + this.attributes.update( attribute, attributeType ); + + } + + } + + // each object defines an array of bindings (ubos, textures, samplers etc.) + + this.backend.createBindings( bindGroup, bindings, 0 ); + + groupData.bindGroup = bindGroup; + groupData.usedTimes = 1; + + } else { + + groupData.usedTimes ++; + + } } } /** - * Initializes the given bind group. + * Deletes the given array of bindings. * - * @param {BindGroup} bindGroup - The bind group to initialize. + * @param {Array} bindings - The bind groups. */ - _init( bindGroup ) { + _destroyBindings( bindings ) { - for ( const binding of bindGroup.bindings ) { + for ( const bindGroup of bindings ) { - if ( binding.isSampledTexture ) { + const groupData = this.get( bindGroup ); + groupData.usedTimes --; - this.textures.updateTexture( binding.texture ); + if ( groupData.usedTimes === 0 ) { - } else if ( binding.isSampler ) { + for ( const binding of bindGroup.bindings ) { - this.textures.updateSampler( binding.texture ); + if ( binding.isUniformBuffer ) { - } else if ( binding.isStorageBuffer ) { + this.backend.destroyUniformBuffer( binding ); + this.info.destroyUniformBuffer( binding ); - const attribute = binding.attribute; - const attributeType = attribute.isIndirectStorageBufferAttribute ? AttributeType.INDIRECT : AttributeType.STORAGE; + // release arrays - this.attributes.update( attribute, attributeType ); + binding.release(); + + } + + } + + this.backend.deleteBindGroupData( bindGroup ); + this.delete( bindGroup ); } @@ -234,6 +270,21 @@ class Bindings extends DataMap { } + /** + * Updates the given array of bindings. + * + * @param {Array} bindings - The bind groups. + */ + _updateBindings( bindings ) { + + for ( const bindGroup of bindings ) { + + this._update( bindGroup, bindings ); + + } + + } + /** * Updates the given bind group. * diff --git a/src/renderers/common/Buffer.js b/src/renderers/common/Buffer.js index 6ed8fdab53b558..0ec459e3dd8219 100644 --- a/src/renderers/common/Buffer.js +++ b/src/renderers/common/Buffer.js @@ -122,6 +122,15 @@ class Buffer extends Binding { } + /** + * Releases the buffer. + */ + release() { + + this._buffer = null; + + } + } export default Buffer; diff --git a/src/renderers/common/Info.js b/src/renderers/common/Info.js index 2c2014ff59cb3b..1704c10fa712e3 100644 --- a/src/renderers/common/Info.js +++ b/src/renderers/common/Info.js @@ -94,42 +94,46 @@ class Info { * * @type {Object} * @readonly - * @property {number} geometries - The number of active geometries. - * @property {number} textures - The number of active textures. * @property {number} attributes - The number of active attributes. + * @property {number} attributesSize - The memory size of active attributes in bytes. + * @property {number} geometries - The number of active geometries. * @property {number} indexAttributes - The number of active index attributes. - * @property {number} storageAttributes - The number of active storage attributes. + * @property {number} indexAttributesSize - The memory size of active index attributes in bytes. * @property {number} indirectStorageAttributes - The number of active indirect storage attributes. - * @property {number} readbackBuffers - The number of active readback buffers. + * @property {number} indirectStorageAttributesSize - The memory size of active indirect storage attributes in bytes. * @property {number} programs - The number of active programs. + * @property {number} programsSize - The memory size of active programs in bytes. + * @property {number} readbackBuffers - The number of active readback buffers. + * @property {number} readbackBuffersSize - The memory size of active readback buffers in bytes. * @property {number} renderTargets - The number of active renderTargets. - * @property {number} total - The total memory size in bytes. - * @property {number} texturesSize - The memory size of active textures in bytes. - * @property {number} attributesSize - The memory size of active attributes in bytes. - * @property {number} indexAttributesSize - The memory size of active index attributes in bytes. + * @property {number} storageAttributes - The number of active storage attributes. * @property {number} storageAttributesSize - The memory size of active storage attributes in bytes. - * @property {number} indirectStorageAttributesSize - The memory size of active indirect storage attributes in bytes. - * @property {number} readbackBuffersSize - The memory size of active readback buffers in bytes. - * @property {number} programsSize - The memory size of active programs in bytes. + * @property {number} textures - The number of active textures. + * @property {number} texturesSize - The memory size of active textures in bytes. + * @property {number} uniformBuffers - The number of active uniform buffers. + * @property {number} uniformBuffersSize - The memory size of active uniform buffers in bytes. + * @property {number} total - The total memory size in bytes. */ this.memory = { - geometries: 0, - textures: 0, attributes: 0, + attributesSize: 0, + geometries: 0, indexAttributes: 0, - storageAttributes: 0, + indexAttributesSize: 0, indirectStorageAttributes: 0, - readbackBuffers: 0, + indirectStorageAttributesSize: 0, programs: 0, + programsSize: 0, + readbackBuffers: 0, + readbackBuffersSize: 0, renderTargets: 0, - total: 0, - texturesSize: 0, - attributesSize: 0, - indexAttributesSize: 0, + storageAttributes: 0, storageAttributesSize: 0, - indirectStorageAttributesSize: 0, - readbackBuffersSize: 0, - programsSize: 0 + textures: 0, + texturesSize: 0, + uniformBuffers: 0, + uniformBuffersSize: 0, + total: 0 }; /** @@ -365,6 +369,43 @@ class Info { } + /** + * Tracks a uniform buffer memory explicitly. + * + * @param {UniformBuffer} uniformBuffer - The uniform buffer to track. + */ + createUniformBuffer( uniformBuffer ) { + + const size = uniformBuffer.byteLength; + this.memoryMap.set( uniformBuffer, { size, type: 'uniformBuffers' } ); + + this.memory.uniformBuffers ++; + this.memory.total += size; + this.memory.uniformBuffersSize += size; + + } + + /** + * Tracks a uniform buffer memory explicitly. + * + * @param {UniformBuffer} uniformBuffer - The uniform buffer to track. + */ + destroyUniformBuffer( uniformBuffer ) { + + const data = this.memoryMap.get( uniformBuffer ); + + if ( data ) { + + this.memoryMap.delete( uniformBuffer ); + + this.memory.uniformBuffers --; + this.memory.total -= data.size; + this.memory.uniformBuffersSize -= data.size; + + } + + } + /** * Tracks program memory explicitly, updating counts and byte tracking. * diff --git a/src/renderers/common/UniformsGroup.js b/src/renderers/common/UniformsGroup.js index 1e88dff7665f87..0f29befde80f5e 100644 --- a/src/renderers/common/UniformsGroup.js +++ b/src/renderers/common/UniformsGroup.js @@ -238,6 +238,17 @@ class UniformsGroup extends UniformBuffer { } + /** + * Releases the buffer. + */ + release() { + + super.release(); + + this._values = null; + + } + /** * Updates a given uniform by calling an update method matching * the uniforms type. diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index dc73ec3de0501a..f63ee32669d2f0 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -1834,24 +1834,9 @@ class WebGLBackend extends Backend { if ( binding.isUniformsGroup || binding.isUniformBuffer ) { const array = binding.buffer; - let { bufferGPU } = this.get( array ); + const bufferGPU = map.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 ); - - } + gl.bindBuffer( gl.UNIFORM_BUFFER, bufferGPU ); // update @@ -1883,8 +1868,6 @@ class WebGLBackend extends Backend { } - map.bufferGPU = bufferGPU; - this.set( binding, map ); } else if ( binding.isSampledTexture ) { @@ -1951,6 +1934,44 @@ 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 0320d99d03572c..6d9bdfa2e1a5bb 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 } from './utils/WebGPUConstants.js'; +import { GPUFeatureName, GPULoadOp, GPUStoreOp, GPUIndexFormat, GPUTextureViewDimension, GPUFeatureMap, GPUShaderStage } from './utils/WebGPUConstants.js'; import WGSLNodeBuilder from './nodes/WGSLNodeBuilder.js'; import Backend from '../common/Backend.js'; @@ -2178,6 +2178,70 @@ 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 2f9488d22deacc..7871c35c99ad18 100644 --- a/src/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/src/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -283,43 +283,6 @@ 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 ) {