From 0d5e6d68526fe9be394e09e248a2b2feca08db2f Mon Sep 17 00:00:00 2001 From: Christian Helgeson <62450112+cmhhelgeson@users.noreply.github.com> Date: Tue, 25 Jun 2024 12:06:17 -0700 Subject: [PATCH] WebGPURenderer: Read/Write Only Storage Buffer Creation (#28435) * add writable * sketch * changes * Working solution * run linter * syntax fix * fixed formatting * add writable * sketch * changes * Working solution * run linter * syntax fix * fixed formatting * re-add storageImmutable to compute_geometry * switch .readOnly to .access in StorageBufferNode and NodeStorageBuffer * Made storage buffer code consistent with storage texture code and applied storageReadOnly to examples as reference in appropriate locations * Fixed unusued import in webgpu_compute_audio * Remove unused import * Removed un-needed references to 'storageReadOnlyBuffer' in WGSLNodeBuilder, added considerations for hypothetical write-only buffer access mode mentioned in PR conversation, added readOnly and writeOnly commands for creating a storageObject * fixed missing access * remove comments * fix missing import * revision * cleanup --------- Co-authored-by: sunag --- examples/jsm/nodes/Nodes.js | 2 +- .../jsm/nodes/accessors/StorageBufferNode.js | 24 +++++++++++- .../jsm/nodes/accessors/StorageTextureNode.js | 15 ++++++-- examples/jsm/renderers/common/Renderer.js | 1 + .../common/nodes/NodeStorageBuffer.js | 3 ++ .../renderers/webgpu/nodes/WGSLNodeBuilder.js | 38 ++++++++++++------- .../webgpu/utils/WebGPUBindingUtils.js | 4 +- examples/webgpu_compute_audio.html | 6 +-- examples/webgpu_compute_geometry.html | 4 +- examples/webgpu_compute_texture.html | 2 +- 10 files changed, 71 insertions(+), 28 deletions(-) diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index cfccddbea2c181..6243d695de0c5d 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -103,7 +103,7 @@ export { default as SceneNode, backgroundBlurriness, backgroundIntensity } from export { default as StorageBufferNode, storage, storageObject } from './accessors/StorageBufferNode.js'; export * from './accessors/TangentNode.js'; export { default as TextureNode, texture, textureLoad, /*textureLevel,*/ sampler } from './accessors/TextureNode.js'; -export { default as StorageTextureNode, storageTexture, textureStore, storageTextureReadOnly, storageTextureReadWrite } from './accessors/StorageTextureNode.js'; +export { default as StorageTextureNode, storageTexture, textureStore } from './accessors/StorageTextureNode.js'; export { default as Texture3DNode, texture3D } from './accessors/Texture3DNode.js'; export * from './accessors/UVNode.js'; export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; diff --git a/examples/jsm/nodes/accessors/StorageBufferNode.js b/examples/jsm/nodes/accessors/StorageBufferNode.js index 855f051c6635d7..19627829517b3c 100644 --- a/examples/jsm/nodes/accessors/StorageBufferNode.js +++ b/examples/jsm/nodes/accessors/StorageBufferNode.js @@ -4,6 +4,7 @@ import { addNodeClass } from '../core/Node.js'; import { nodeObject } from '../shadernode/ShaderNode.js'; import { varying } from '../core/VaryingNode.js'; import { storageElement } from '../utils/StorageArrayElementNode.js'; +import { GPUBufferBindingType } from '../../renderers/webgpu/utils/WebGPUConstants.js'; class StorageBufferNode extends BufferNode { @@ -13,6 +14,8 @@ class StorageBufferNode extends BufferNode { this.isStorageBufferNode = true; + this.access = GPUBufferBindingType.Storage; + this.bufferObject = false; this.bufferCount = bufferCount; @@ -76,9 +79,27 @@ class StorageBufferNode extends BufferNode { } + setAccess( value ) { + + this.access = value; + + return this; + + } + + toReadOnly() { + + return this.setAccess( GPUBufferBindingType.ReadOnlyStorage ); + + } + generate( builder ) { - if ( builder.isAvailable( 'storageBuffer' ) ) return super.generate( builder ); + if ( builder.isAvailable( 'storageBuffer' ) ) { + + return super.generate( builder ); + + } const nodeType = this.getNodeType( builder ); @@ -102,6 +123,7 @@ class StorageBufferNode extends BufferNode { export default StorageBufferNode; +// Read-Write Storage export const storage = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ) ); export const storageObject = ( value, type, count ) => nodeObject( new StorageBufferNode( value, type, count ).setBufferObject( true ) ); diff --git a/examples/jsm/nodes/accessors/StorageTextureNode.js b/examples/jsm/nodes/accessors/StorageTextureNode.js index 6e9f97f0051d06..fbaa982456da0a 100644 --- a/examples/jsm/nodes/accessors/StorageTextureNode.js +++ b/examples/jsm/nodes/accessors/StorageTextureNode.js @@ -57,6 +57,18 @@ class StorageTextureNode extends TextureNode { } + toReadOnly() { + + return this.setAccess( GPUStorageTextureAccess.ReadOnly ); + + } + + toWriteOnly() { + + return this.setAccess( GPUStorageTextureAccess.WriteOnly ); + + } + generateStore( builder ) { const properties = builder.getNodeProperties( this ); @@ -79,9 +91,6 @@ export default StorageTextureNode; export const storageTexture = nodeProxy( StorageTextureNode ); -export const storageTextureReadOnly = ( value, uvNode, storeNode ) => storageTexture( value, uvNode, storeNode ).setAccess( 'read-only' ); -export const storageTextureReadWrite = ( value, uvNode, storeNode ) => storageTexture( value, uvNode, storeNode ).setAccess( 'read-write' ); - export const textureStore = ( value, uvNode, storeNode ) => { const node = storageTexture( value, uvNode, storeNode ); diff --git a/examples/jsm/renderers/common/Renderer.js b/examples/jsm/renderers/common/Renderer.js index 2da84821d7163e..1cc18a4c2d6769 100644 --- a/examples/jsm/renderers/common/Renderer.js +++ b/examples/jsm/renderers/common/Renderer.js @@ -1089,6 +1089,7 @@ class Renderer { const pipelines = this._pipelines; const bindings = this._bindings; const nodes = this._nodes; + const computeList = Array.isArray( computeNodes ) ? computeNodes : [ computeNodes ]; if ( computeList[ 0 ] === undefined || computeList[ 0 ].isComputeNode !== true ) { diff --git a/examples/jsm/renderers/common/nodes/NodeStorageBuffer.js b/examples/jsm/renderers/common/nodes/NodeStorageBuffer.js index 67a436f729d0e0..8e5ae85ee5f63b 100644 --- a/examples/jsm/renderers/common/nodes/NodeStorageBuffer.js +++ b/examples/jsm/renderers/common/nodes/NodeStorageBuffer.js @@ -1,4 +1,5 @@ import StorageBuffer from '../StorageBuffer.js'; +import { GPUBufferBindingType } from '../../webgpu/utils/WebGPUConstants.js'; let _id = 0; @@ -9,8 +10,10 @@ class NodeStorageBuffer extends StorageBuffer { super( 'StorageBuffer_' + _id ++, nodeUniform ? nodeUniform.value : null ); this.nodeUniform = nodeUniform; + this.access = nodeUniform ? nodeUniform.access : GPUBufferBindingType.Storage this.groupNode = groupNode; + } get buffer() { diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index d649c66f20b112..e67f588923d9e8 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -13,8 +13,7 @@ import { NodeBuilder, CodeNode } from '../../../nodes/Nodes.js'; import { getFormat } from '../utils/WebGPUTextureUtils.js'; import WGSLNodeParser from './WGSLNodeParser.js'; -import { GPUStorageTextureAccess } from '../utils/WebGPUConstants.js'; - +import { GPUBufferBindingType, GPUStorageTextureAccess } from '../utils/WebGPUConstants.js'; // GPUShaderStage is not defined in browsers not supporting WebGPU const GPUShaderStage = self.GPUShaderStage; @@ -26,6 +25,7 @@ const gpuShaderStageLib = { }; const supports = { + instance: true, swizzleAssign: false, storageBuffer: true }; @@ -411,30 +411,38 @@ class WGSLNodeBuilder extends NodeBuilder { switch ( node.access ) { - case GPUStorageTextureAccess.ReadOnly: { + case GPUStorageTextureAccess.ReadOnly: return 'read'; - } - - case GPUStorageTextureAccess.WriteOnly: { + case GPUStorageTextureAccess.WriteOnly: return 'write'; - } - - default: { + default: return 'read_write'; - } - } } else { - // @TODO: Account for future read-only storage buffer pull request - return 'read_write'; + switch ( node.access ) { + + case GPUBufferBindingType.Storage: + + return 'read_write'; + + + case GPUBufferBindingType.ReadOnlyStorage: + + return 'read'; + + default: + + return 'write'; + + } } @@ -714,6 +722,7 @@ ${ flowData.code } snippet += this.getStructMembers( struct ); snippet += '\n}'; + snippets.push( snippet ); snippets.push( `\nvar output : ${ name };\n\n` ); @@ -880,7 +889,7 @@ ${ flowData.code } const bufferCountSnippet = bufferCount > 0 ? ', ' + bufferCount : ''; const bufferSnippet = `\t${ uniform.name } : array< ${ bufferType }${ bufferCountSnippet } >\n`; - const bufferAccessMode = bufferNode.isStorageBufferNode ? 'storage,read_write' : 'uniform'; + const bufferAccessMode = bufferNode.isStorageBufferNode ? `storage, ${ this.getStorageAccess( bufferNode ) }` : 'uniform'; bufferSnippets.push( this._getWGSLStructBinding( 'NodeBuffer_' + bufferNode.id, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); @@ -997,6 +1006,7 @@ ${ flowData.code } stageData.flow = flow; + } if ( this.material !== null ) { diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js index 13230bc645991a..a7b7c62220bf77 100644 --- a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -1,5 +1,5 @@ import { - GPUTextureAspect, GPUTextureViewDimension, GPUBufferBindingType, GPUTextureSampleType + GPUTextureAspect, GPUTextureViewDimension, GPUTextureSampleType } from './WebGPUConstants.js'; import { FloatType, IntType, UnsignedIntType } from 'three'; @@ -33,7 +33,7 @@ class WebGPUBindingUtils { if ( binding.isStorageBuffer ) { - buffer.type = GPUBufferBindingType.Storage; + buffer.type = binding.access; } diff --git a/examples/webgpu_compute_audio.html b/examples/webgpu_compute_audio.html index 18f6915fb61428..06f9ea1a883b06 100644 --- a/examples/webgpu_compute_audio.html +++ b/examples/webgpu_compute_audio.html @@ -29,7 +29,7 @@