From 4d38a9d8b1416abd550b9899cd245420dc675d2a Mon Sep 17 00:00:00 2001 From: aardgoose Date: Sat, 16 Dec 2023 16:53:17 +0000 Subject: [PATCH] prototye big buffer refactored and remove global state --- .../renderers/common/CommonUniformBuffer.js | 54 +++++++++++ examples/jsm/renderers/common/Renderer.js | 1 + .../jsm/renderers/common/UniformsGroup.js | 9 +- .../common/nodes/NodeUniformsGroup.js | 45 ++++++++- .../jsm/renderers/webgpu/WebGPUBackend.js | 18 +++- .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 6 +- .../webgpu/utils/WebGPUBindingUtils.js | 92 ++++++++++++++++--- 7 files changed, 205 insertions(+), 20 deletions(-) create mode 100644 examples/jsm/renderers/common/CommonUniformBuffer.js diff --git a/examples/jsm/renderers/common/CommonUniformBuffer.js b/examples/jsm/renderers/common/CommonUniformBuffer.js new file mode 100644 index 00000000000000..99479f1f131b29 --- /dev/null +++ b/examples/jsm/renderers/common/CommonUniformBuffer.js @@ -0,0 +1,54 @@ +class CommonUniformBuffer { + + constructor( bufferSize = 0, alignment = 0 ) { + + let buffer = null; + + if ( bufferSize > 0 ) { + + buffer = new Float32Array( bufferSize ); + + } + + // offset in bytes to first free buffer entry + + this.startFree = 0; + this.buffer = buffer; + this.aligment = alignment; + + } + + allocate( byteLength ) { + + if ( this.startFree + byteLength > this.byteLength ) { + + return false; + + } + + // uniformGroups within buffer must be aligned correctly per WebGPU spec. + const paddedByteLength = Math.ceil( byteLength / this.aligment ) * this.aligment; + const bpe = this.buffer.BYTES_PER_ELEMENT; + const buffer = this.buffer.subarray( this.startFree / bpe , ( this.startFree + byteLength ) / bpe ); + + this.startFree += paddedByteLength; + + return buffer; + + } + + get byteLength() { + + return this.buffer === null ? 0 : this.buffer.byteLength; + + } + + get arrayBuffer() { + + return this.buffer.buffer; + + } + +} + +export default CommonUniformBuffer; diff --git a/examples/jsm/renderers/common/Renderer.js b/examples/jsm/renderers/common/Renderer.js index 1dec53e008a386..b47d1560fd31ae 100644 --- a/examples/jsm/renderers/common/Renderer.js +++ b/examples/jsm/renderers/common/Renderer.js @@ -69,6 +69,7 @@ class Renderer { // nodes this.toneMappingNode = null; + this.commonBufferSize = 0; // internals diff --git a/examples/jsm/renderers/common/UniformsGroup.js b/examples/jsm/renderers/common/UniformsGroup.js index 21bb2cd33010f4..925e2b2f8be089 100644 --- a/examples/jsm/renderers/common/UniformsGroup.js +++ b/examples/jsm/renderers/common/UniformsGroup.js @@ -13,6 +13,9 @@ class UniformsGroup extends UniformBuffer { this.uniforms = []; + this._buffer = null; + this._byteLength = null; + } addUniform( uniform ) { @@ -57,6 +60,8 @@ class UniformsGroup extends UniformBuffer { get byteLength() { + if ( this._byteLength !== null ) return this._byteLength; + let offset = 0; // global buffer offset in bytes for ( let i = 0, l = this.uniforms.length; i < l; i ++ ) { @@ -92,7 +97,9 @@ class UniformsGroup extends UniformBuffer { } - return Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES; + this._byteLength = Math.ceil( offset / GPU_CHUNK_BYTES ) * GPU_CHUNK_BYTES; + + return this._byteLength; } diff --git a/examples/jsm/renderers/common/nodes/NodeUniformsGroup.js b/examples/jsm/renderers/common/nodes/NodeUniformsGroup.js index e44a6f93a9c6a8..f8b6ef871d504b 100644 --- a/examples/jsm/renderers/common/nodes/NodeUniformsGroup.js +++ b/examples/jsm/renderers/common/nodes/NodeUniformsGroup.js @@ -4,7 +4,7 @@ let id = 0; class NodeUniformsGroup extends UniformsGroup { - constructor( name, groupNode ) { + constructor( name, groupNode, commonUniformBuffer = null ) { super( name ); @@ -12,6 +12,8 @@ class NodeUniformsGroup extends UniformsGroup { this.groupNode = groupNode; this.isNodeUniformsGroup = true; + this.commonUniformBuffer = commonUniformBuffer; + this._isCommon = null; } @@ -21,6 +23,47 @@ class NodeUniformsGroup extends UniformsGroup { } + allocateCommon() { + + if ( this._isCommon === null ) { + + this._isCommon = false; + + if ( this.commonUniformBuffer !== null ) { + + const buffer = this.commonUniformBuffer.allocate( this.byteLength ); + + if ( buffer ) { + + this._buffer = buffer; + this._isCommon = true; + + } + + } + + } + + return this._isCommon; + + } + + get buffer() { + + if ( this._buffer === null ) { + + if ( ! this.allocateCommon() ) { + + return super.buffer; + + } + + } + + return this._buffer; + + } + getNodes() { const nodes = []; diff --git a/examples/jsm/renderers/webgpu/WebGPUBackend.js b/examples/jsm/renderers/webgpu/WebGPUBackend.js index 1c0e8e6de1b54d..1d4ffd2cb03dbd 100644 --- a/examples/jsm/renderers/webgpu/WebGPUBackend.js +++ b/examples/jsm/renderers/webgpu/WebGPUBackend.js @@ -8,6 +8,7 @@ import { GPUFeatureName, GPUTextureFormat, GPULoadOp, GPUStoreOp, GPUIndexFormat import WGSLNodeBuilder from './nodes/WGSLNodeBuilder.js'; import Backend from '../common/Backend.js'; +import CommonUniformBuffer from '../common/CommonUniformBuffer.js'; import WebGPUUtils from './utils/WebGPUUtils.js'; import WebGPUAttributeUtils from './utils/WebGPUAttributeUtils.js'; @@ -55,6 +56,7 @@ class WebGPUBackend extends Backend { this.pipelineUtils = new WebGPUPipelineUtils( this ); this.textureUtils = new WebGPUTextureUtils( this ); this.occludedResolveCache = new Map(); + this.commonUniformBuffer = null; } @@ -505,8 +507,9 @@ class WebGPUBackend extends Backend { this.prepareTimestampBuffer( renderContext, renderContextData.encoder ); - this.device.queue.submit( [ renderContextData.encoder.finish() ] ); + this.bindingUtils.endPass(); + this.device.queue.submit( [ renderContextData.encoder.finish() ] ); // @@ -775,6 +778,7 @@ class WebGPUBackend extends Backend { groupData.passEncoderGPU.end(); this.prepareTimestampBuffer( computeGroup, groupData.cmdEncoderGPU ); + this.bindingUtils.endPass(); this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] ); @@ -1117,13 +1121,21 @@ class WebGPUBackend extends Backend { buffer.unmap(); } } - + // node builder createNodeBuilder( object, renderer, scene = null ) { - return new WGSLNodeBuilder( object, renderer, scene ); + const size = this.renderer.commonBufferSize; + + if ( size > 0 && this.commonUniformBuffer === null ) { + + this.commonUniformBuffer = new CommonUniformBuffer( 256 * size, this.device.limits.minUniformBufferOffsetAlignment ); + + } + + return new WGSLNodeBuilder( object, renderer, scene, this.commonUniformBuffer ); } diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index 8413e2fadeaf21..85d3a30ffb32ea 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -130,7 +130,7 @@ fn threejs_repeatWrapping( uv : vec2, dimension : vec2 ) -> vec2 class WGSLNodeBuilder extends NodeBuilder { - constructor( object, renderer, scene = null ) { + constructor( object, renderer, scene = null, commonUniformBuffer = null ) { super( object, renderer, new WGSLNodeParser(), scene ); @@ -138,6 +138,8 @@ class WGSLNodeBuilder extends NodeBuilder { this.builtins = {}; + this.commonUniformBuffer = commonUniformBuffer; + } needsColorSpaceToLinear( texture ) { @@ -424,7 +426,7 @@ class WGSLNodeBuilder extends NodeBuilder { if ( uniformsGroup === undefined ) { - uniformsGroup = new NodeUniformsGroup( groupName, group ); + uniformsGroup = new NodeUniformsGroup( groupName, group, this.commonUniformBuffer ); uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); uniformsStage[ groupName ] = uniformsGroup; diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js index cc59d7074c60c0..96413b30a9feaa 100644 --- a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -9,6 +9,32 @@ class WebGPUBindingUtils { this.backend = backend; + this.lowwaterMark = Infinity; + this.highwaterMark = 0; + + this.commonBufferGPU = null; + + } + + getCommonBuffer( commonUniformBuffer ) { + + let bufferGPU = this.commonBufferGPU; + + if ( bufferGPU === null ) { + + bufferGPU = this.backend.device.createBuffer( { + label: 'bindingBuffer_common', + size: commonUniformBuffer.byteLength, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + } ); + + this.commonBufferGPU = bufferGPU; + this.commonUniformBuffer = commonUniformBuffer; + + } + + return bufferGPU + } createBindingsLayout( bindings ) { @@ -142,10 +168,18 @@ class WebGPUBindingUtils { const backend = this.backend; const device = backend.device; - const buffer = binding.buffer; - const bufferGPU = backend.get( binding ).buffer; + if ( binding.isNodeUniformsGroup && binding.allocateCommon() ) { + + const buffer = binding.buffer; - device.queue.writeBuffer( bufferGPU, 0, buffer, 0 ); + this.lowwaterMark = Math.min( this.lowwaterMark, buffer.byteOffset ); + this.highwaterMark = Math.max( this.highwaterMark, buffer.byteOffset + buffer.byteLength ); + + } else { + + const bufferGPU = backend.get( binding ).buffer; + device.queue.writeBuffer( bufferGPU, 0, binding.buffer, 0 ); + } } @@ -163,23 +197,42 @@ class WebGPUBindingUtils { const bindingData = backend.get( binding ); - if ( bindingData.buffer === undefined ) { + let resource; - const byteLength = binding.byteLength; + if ( binding.isNodeUniformsGroup && binding.allocateCommon() ) { - const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + const buffer = binding.buffer; - const bufferGPU = device.createBuffer( { - label: 'bindingBuffer_' + binding.name, - size: byteLength, - usage: usage - } ); + resource = { + label: 'bindingBufferCommon_' + binding.name, + buffer: this.getCommonBuffer( binding.commonUniformBuffer ), + offset: buffer.byteOffset, + size: buffer.byteLength + }; - bindingData.buffer = bufferGPU; + } else { + + if ( bindingData.buffer === undefined ) { + + const byteLength = binding.byteLength; + + const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST; + + const bufferGPU = device.createBuffer( { + label: 'bindingBuffer_' + binding.name, + size: byteLength, + usage: usage + } ); + + bindingData.buffer = bufferGPU; + + } + + resource = { buffer: bindingData.buffer }; } - entriesGPU.push( { binding: bindingPoint, resource: { buffer: bindingData.buffer } } ); + entriesGPU.push( { binding: bindingPoint, resource } ); } else if ( binding.isStorageBuffer ) { @@ -253,6 +306,19 @@ class WebGPUBindingUtils { } + endPass() { + + if ( this.commonBufferGPU === null || this.lowwaterMark === Infinity ) return; + + const device = this.backend.device; + + device.queue.writeBuffer( this.commonBufferGPU, this.lowwaterMark, this.commonUniformBuffer.arrayBuffer, this.lowwaterMark, this.highwaterMark - this.lowwaterMark ); + + this.lowwaterMark = Infinity; + this.highwaterMark = 0; + + } + } export default WebGPUBindingUtils;