From 1bc93882c68c76e27034be5c9d71b29eec34f036 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 | 53 +++++++++++ examples/jsm/renderers/common/Renderer.js | 2 + .../jsm/renderers/common/UniformsGroup.js | 10 +- .../common/nodes/NodeUniformsGroup.js | 45 ++++++++- .../jsm/renderers/webgpu/WebGPUBackend.js | 24 ++++- .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 6 +- .../webgpu/utils/WebGPUBindingUtils.js | 92 ++++++++++++++++--- 7 files changed, 213 insertions(+), 19 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..4fd0c37fbf6fac --- /dev/null +++ b/examples/jsm/renderers/common/CommonUniformBuffer.js @@ -0,0 +1,53 @@ + +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 + length > 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 a5fbb9b7080c47..89696116542723 100644 --- a/examples/jsm/renderers/common/Renderer.js +++ b/examples/jsm/renderers/common/Renderer.js @@ -60,6 +60,8 @@ class Renderer { this.info = new Info(); + this.commonBufferSize = 0; + // internals this._pixelRatio = 1; diff --git a/examples/jsm/renderers/common/UniformsGroup.js b/examples/jsm/renderers/common/UniformsGroup.js index 58450ebb0f98df..cc1d428152e0b5 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 ++ ) { @@ -85,12 +90,13 @@ class UniformsGroup extends UniformBuffer { } uniform.offset = ( offset / this.bytesPerElement ); - offset += ( uniform.itemSize * this.bytesPerElement ); } - 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 6926a1101b4364..2525fdad6d9980 100644 --- a/examples/jsm/renderers/webgpu/WebGPUBackend.js +++ b/examples/jsm/renderers/webgpu/WebGPUBackend.js @@ -6,6 +6,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 { DepthFormat, WebGPUCoordinateSystem } from 'three'; @@ -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; } @@ -494,8 +496,12 @@ class WebGPUBackend extends Backend { } + this.bindingUtils.endPass(); + this.device.queue.submit( [ renderContextData.encoder.finish() ] ); + //this.device.queue.onSubmittedWorkDone().then( () => { performance.mark( 'render-end' ); } ); + // if ( renderContext.textures !== null ) { @@ -770,6 +776,9 @@ class WebGPUBackend extends Backend { const groupData = this.get( computeGroup ); groupData.passEncoderGPU.end(); + + this.bindingUtils.endPass(); + this.device.queue.submit( [ groupData.cmdEncoderGPU.finish() ] ); } @@ -1035,7 +1044,20 @@ class WebGPUBackend extends Backend { createNodeBuilder( object, renderer, scene = null ) { - return new WGSLNodeBuilder( object, renderer, scene ); + if ( this.commonUniformBuffer === null ) { + + const alignment = this.device.limits.minUniformBufferOffsetAlignment; + const size = this.renderer.commonBufferSize; + + if ( size > 0 ) { + + this.commonUniformBuffer = new CommonUniformBuffer( 256 * size, alignment ); + + } + + } + + 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 92de7565eeaed3..4aa738aea39eef 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -118,7 +118,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 ); @@ -126,6 +126,8 @@ class WGSLNodeBuilder extends NodeBuilder { this.builtins = {}; + this.commonUniformBuffer = commonUniformBuffer; + } needsColorSpaceToLinear( texture ) { @@ -391,7 +393,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 16169fc49d1d46..949bc87a767480 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 ) { @@ -128,10 +154,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 ); + } } @@ -149,23 +183,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 ) { @@ -239,6 +292,19 @@ class WebGPUBindingUtils { } + endPass() { + + const device = this.backend.device; + + if ( this.commonBufferGPU === null || this.lowwaterMark === Infinity ) return; + + device.queue.writeBuffer( this.commonBufferGPU, this.lowwaterMark, this.commonUniformBuffer.arrayBuffer, this.lowwaterMark ); + + this.lowwaterMark = Infinity; + this.highwaterMark = 0; + + } + } export default WebGPUBindingUtils;