This example demonstrate the fetch of external element from a StorageBuffer.
-
This example demonstrates fetching an external element from a StorageBuffer.
+
The left canvas uses the WebGPU Backend, while the right uses the WebGL Backend.
import * as THREE from 'three';
- import { storageObject, If, vec3, uv, uint, float, Fn, instanceIndex } from 'three/tsl';
+ import { storageObject, If, vec3, uv, uint, float, Fn, instanceIndex, workgroupBarrier } from 'three/tsl';
const timestamps = {
webgpu: document.getElementById( 'timestamps' ),
@@ -107,7 +107,8 @@
for ( let i = 0; i < type.length; i ++ ) {
- const invertIndex = arrayBufferNodes[ i ].element( uint( size - 1 ).sub( instanceIndex ) );
+ const invertIndex = arrayBufferNodes[ i ].element( uint( size - 1 ).sub( instanceIndex ) ).toVar();
+ workgroupBarrier();
arrayBufferNodes[ i ].element( instanceIndex ).assign( invertIndex );
}
diff --git a/src/nodes/TSL.js b/src/nodes/TSL.js
index ca0f523abe1b1c..3275e00e9a911c 100644
--- a/src/nodes/TSL.js
+++ b/src/nodes/TSL.js
@@ -142,6 +142,8 @@ export * from './geometry/RangeNode.js';
// gpgpu
export * from './gpgpu/ComputeNode.js';
+export * from './gpgpu/BarrierNode.js';
+export * from './gpgpu/WorkgroupInfoNode.js';
// lighting
export * from './lighting/LightNode.js';
diff --git a/src/nodes/gpgpu/BarrierNode.js b/src/nodes/gpgpu/BarrierNode.js
new file mode 100644
index 00000000000000..683e8d6472666a
--- /dev/null
+++ b/src/nodes/gpgpu/BarrierNode.js
@@ -0,0 +1,40 @@
+import Node from '../core/Node.js';
+import { nodeProxy } from '../tsl/TSLCore.js';
+
+class BarrierNode extends Node {
+
+ constructor( scope ) {
+
+ super();
+
+ this.scope = scope;
+
+ }
+
+ generate( builder ) {
+
+ const { scope } = this;
+ const { renderer } = builder;
+
+ if ( renderer.backend.isWebGLBackend === true ) {
+
+ builder.addFlowCode( `\t// ${scope}Barrier \n` );
+
+ } else {
+
+ builder.addLineFlowCode( `${scope}Barrier()` );
+
+ }
+
+ }
+
+}
+
+export default BarrierNode;
+
+const barrier = nodeProxy( BarrierNode );
+
+export const workgroupBarrier = () => barrier( 'workgroup' ).append();
+export const storageBarrier = () => barrier( 'storage' ).append();
+export const textureBarrier = () => barrier( 'texture' ).append();
+
diff --git a/src/nodes/gpgpu/WorkgroupInfoNode.js b/src/nodes/gpgpu/WorkgroupInfoNode.js
new file mode 100644
index 00000000000000..2bbe3e8eafdb9a
--- /dev/null
+++ b/src/nodes/gpgpu/WorkgroupInfoNode.js
@@ -0,0 +1,100 @@
+import ArrayElementNode from '../utils/ArrayElementNode.js';
+import { nodeObject } from '../tsl/TSLCore.js';
+import Node from '../core/Node.js';
+
+class WorkgroupInfoElementNode extends ArrayElementNode {
+
+ constructor( workgroupInfoNode, indexNode ) {
+
+ super( workgroupInfoNode, indexNode );
+
+ this.isWorkgroupInfoElementNode = true;
+
+ }
+
+ generate( builder, output ) {
+
+ let snippet;
+
+ const isAssignContext = builder.context.assign;
+ snippet = super.generate( builder );
+
+ if ( isAssignContext !== true ) {
+
+ const type = this.getNodeType( builder );
+
+ snippet = builder.format( snippet, type, output );
+
+ }
+
+ // TODO: Possibly activate clip distance index on index access rather than from clipping context
+
+ return snippet;
+
+ }
+
+}
+
+
+class WorkgroupInfoNode extends Node {
+
+ constructor( scope, bufferType, bufferCount = 0 ) {
+
+ super( bufferType );
+
+ this.bufferType = bufferType;
+ this.bufferCount = bufferCount;
+
+ this.isWorkgroupInfoNode = true;
+
+ this.scope = scope;
+
+ }
+
+ label( name ) {
+
+ this.name = name;
+
+ return this;
+
+ }
+
+ getHash() {
+
+ return this.uuid;
+
+ }
+
+ setScope( scope ) {
+
+ this.scope = scope;
+
+ return this;
+
+ }
+
+ getInputType( /*builder*/ ) {
+
+ return `${this.scope}Array`;
+
+ }
+
+ element( indexNode ) {
+
+ return nodeObject( new WorkgroupInfoElementNode( this, indexNode ) );
+
+ }
+
+ generate( builder ) {
+
+ return builder.getScopedArray( this.name || `${this.scope}Array_${this.id}`, this.scope.toLowerCase(), this.bufferType, this.bufferCount );
+
+ }
+
+}
+
+export default WorkgroupInfoNode;
+
+export const workgroupArray = ( type, count ) => nodeObject( new WorkgroupInfoNode( 'Workgroup', type, count ) );
+
+
diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
index 68d3afd224367b..8bab3e7a5e288a 100644
--- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
+++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js
@@ -171,6 +171,8 @@ class WGSLNodeBuilder extends NodeBuilder {
this.directives = {};
+ this.scopedArrays = new Map();
+
}
needsToWorkingColorSpace( texture ) {
@@ -766,6 +768,45 @@ ${ flowData.code }
}
+ getScopedArray( name, scope, bufferType, bufferCount ) {
+
+ if ( this.scopedArrays.has( name ) === false ) {
+
+ this.scopedArrays.set( name, {
+ name,
+ scope,
+ bufferType,
+ bufferCount
+ } );
+
+ }
+
+ return name;
+
+ }
+
+ getScopedArrays( shaderStage ) {
+
+ if ( shaderStage !== 'compute' ) {
+
+ return;
+
+ }
+
+ const snippets = [];
+
+ for ( const { name, scope, bufferType, bufferCount } of this.scopedArrays.values() ) {
+
+ const type = this.getType( bufferType );
+
+ snippets.push( `var<${scope}> ${name}: array< ${type}, ${bufferCount} >;` );
+
+ }
+
+ return snippets.join( '\n' );
+
+ }
+
getAttributes( shaderStage ) {
const snippets = [];
@@ -1065,6 +1106,7 @@ ${ flowData.code }
stageData.vars = this.getVars( shaderStage );
stageData.codes = this.getCodes( shaderStage );
stageData.directives = this.getDirectives( shaderStage );
+ stageData.scopedArrays = this.getScopedArrays( shaderStage );
//
@@ -1291,6 +1333,9 @@ ${shaderData.directives}
// system
var
instanceIndex : u32;
+// locals
+${shaderData.scopedArrays}
+
// uniforms
${shaderData.uniforms}
diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js
index d13a933163a0a2..926954c6a40c13 100644
--- a/test/e2e/puppeteer.js
+++ b/test/e2e/puppeteer.js
@@ -124,6 +124,7 @@ const exceptionList = [
// Awaiting for WebGPU Backend support in Puppeteer
'webgpu_storage_buffer',
+ 'webgpu_compute_sort_bitonic',
// WebGPURenderer: Unknown problem
'webgpu_camera_logarithmicdepthbuffer',