From a5d65ff820d09771b0f588b38afc1a2fc3d9fe62 Mon Sep 17 00:00:00 2001 From: sunag Date: Sat, 25 Jan 2025 17:16:12 -0300 Subject: [PATCH 01/13] introduce struct --- src/Three.TSL.js | 1 + src/nodes/Nodes.js | 2 + src/nodes/TSL.js | 1 + src/nodes/core/NodeBuilder.js | 64 ++++++++++- src/nodes/core/OutputStructNode.js | 28 +++-- src/nodes/core/StructNode.js | 107 ++++++++++++++++++ src/nodes/core/StructType.js | 13 +++ src/nodes/core/StructTypeNode.js | 75 +++++------- .../webgl-fallback/nodes/GLSLNodeBuilder.js | 40 ++++--- src/renderers/webgpu/nodes/WGSLNodeBuilder.js | 41 ++++--- 10 files changed, 276 insertions(+), 96 deletions(-) create mode 100644 src/nodes/core/StructNode.js create mode 100644 src/nodes/core/StructType.js diff --git a/src/Three.TSL.js b/src/Three.TSL.js index 800292da2b5587..064809e291855f 100644 --- a/src/Three.TSL.js +++ b/src/Three.TSL.js @@ -444,6 +444,7 @@ export const storageBarrier = TSL.storageBarrier; export const storageObject = TSL.storageObject; export const storageTexture = TSL.storageTexture; export const string = TSL.string; +export const struct = TSL.struct; export const sub = TSL.sub; export const subgroupIndex = TSL.subgroupIndex; export const subgroupSize = TSL.subgroupSize; diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index e913d0e94b2397..47018fac0fefcf 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -29,6 +29,8 @@ export { default as TempNode } from './core/TempNode.js'; export { default as UniformGroupNode } from './core/UniformGroupNode.js'; export { default as UniformNode } from './core/UniformNode.js'; export { default as VaryingNode } from './core/VaryingNode.js'; +export { default as StructNode } from './core/StructNode.js'; +export { default as StructTypeNode } from './core/StructTypeNode.js'; export { default as OutputStructNode } from './core/OutputStructNode.js'; export { default as MRTNode } from './core/MRTNode.js'; diff --git a/src/nodes/TSL.js b/src/nodes/TSL.js index d4e8963e73dfa5..7de041d58ee4dc 100644 --- a/src/nodes/TSL.js +++ b/src/nodes/TSL.js @@ -11,6 +11,7 @@ export * from './core/IndexNode.js'; export * from './core/ParameterNode.js'; export * from './core/PropertyNode.js'; export * from './core/StackNode.js'; +export * from './core/StructNode.js'; export * from './core/UniformGroupNode.js'; export * from './core/UniformNode.js'; export * from './core/VaryingNode.js'; diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js index 9eceed8e0d8fed..0ec7cdc2054f21 100644 --- a/src/nodes/core/NodeBuilder.js +++ b/src/nodes/core/NodeBuilder.js @@ -5,7 +5,7 @@ import NodeVar from './NodeVar.js'; import NodeCode from './NodeCode.js'; import NodeCache from './NodeCache.js'; import ParameterNode from './ParameterNode.js'; -import StructTypeNode from './StructTypeNode.js'; +import StructType from './StructType.js'; import FunctionNode from '../code/FunctionNode.js'; import NodeMaterial from '../../materials/nodes/NodeMaterial.js'; import { getTypeFromLength } from './NodeUtils.js'; @@ -1102,6 +1102,39 @@ class NodeBuilder { } + /** + * Generates the struct shader string. + * + * @param {String} type - The type. + * @param {Array} [membersLayout] - The count. + * @param {Array?} [values=null] - The default values. + * @return {String} The generated value as a shader string. + */ + generateStruct( type, membersLayout, values = null ) { + + const snippets = []; + + for ( const member of membersLayout ) { + + const { name, type } = member; + + if ( values && values[ name ] && values[ name ].isNode ) { + + snippets.push( values[ name ].build( this, type ) ); + + } else { + + snippets.push( this.generateConst( type ) ); + + } + + } + + return type + '( ' + snippets.join( ', ' ) + ' )'; + + } + + /** * Generates the shader string for the given type and value. * @@ -1593,14 +1626,15 @@ class NodeBuilder { } /** - * Returns an instance of {@link StructTypeNode} for the given output struct node. + * Returns an instance of {@link StructType} for the given output struct node. * * @param {OutputStructNode} node - The output struct node. - * @param {Array} types - The output struct types. + * @param {Array} membersLayout - The output struct types. + * @param {String?} [name=null] - The name of the struct. * @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage. - * @return {StructTypeNode} The struct type attribute. + * @return {StructType} The struct type attribute. */ - getStructTypeFromNode( node, types, shaderStage = this.shaderStage ) { + getStructTypeFromNode( node, membersLayout, name = null, shaderStage = this.shaderStage ) { const nodeData = this.getDataFromNode( node, shaderStage ); @@ -1610,7 +1644,9 @@ class NodeBuilder { const index = this.structs.index ++; - structType = new StructTypeNode( 'StructType' + index, types ); + if ( name === null ) name = 'StructType' + index; + + structType = new StructType( name, membersLayout ); this.structs[ shaderStage ].push( structType ); @@ -1622,6 +1658,22 @@ class NodeBuilder { } + /** + * Returns an instance of {@link StructType} for the given output struct node. + * + * @param {OutputStructNode} node - The output struct node. + * @param {Array} membersLayout - The output struct types. + * @return {StructType} The struct type attribute. + */ + getOutputStructTypeFromNode( node, membersLayout ) { + + const structType = this.getStructTypeFromNode( node, membersLayout, 'OutputType', 'fragment' ); + structType.output = true; + + return structType; + + } + /** * Returns an instance of {@link NodeUniform} for the given uniform node. * diff --git a/src/nodes/core/OutputStructNode.js b/src/nodes/core/OutputStructNode.js index 30c7495293d341..e1212161c115e2 100644 --- a/src/nodes/core/OutputStructNode.js +++ b/src/nodes/core/OutputStructNode.js @@ -44,24 +44,34 @@ class OutputStructNode extends Node { } - setup( builder ) { + getNodeType( builder ) { - super.setup( builder ); + const properties = builder.getNodeProperties( this ); - const members = this.members; - const types = []; + if ( properties.membersLayout === undefined ) { - for ( let i = 0; i < members.length; i ++ ) { + const members = this.members; + const membersLayout = []; + + for ( let i = 0; i < members.length; i ++ ) { + + const name = 'm' + i; + const type = members[ i ].getNodeType( builder ); + + membersLayout.push( { name, type, index: i } ); + + } - types.push( members[ i ].getNodeType( builder ) ); + properties.membersLayout = membersLayout; + properties.structType = builder.getOutputStructTypeFromNode( this, properties.membersLayout ); } - this.nodeType = builder.getStructTypeFromNode( this, types ).name; + return properties.structType.name; } - generate( builder, output ) { + generate( builder ) { const propertyName = builder.getOutputStructName(); const members = this.members; @@ -70,7 +80,7 @@ class OutputStructNode extends Node { for ( let i = 0; i < members.length; i ++ ) { - const snippet = members[ i ].build( builder, output ); + const snippet = members[ i ].build( builder ); builder.addLineFlowCode( `${ structPrefix }m${ i } = ${ snippet }`, this ); diff --git a/src/nodes/core/StructNode.js b/src/nodes/core/StructNode.js new file mode 100644 index 00000000000000..cf0d504ad6ef74 --- /dev/null +++ b/src/nodes/core/StructNode.js @@ -0,0 +1,107 @@ +import Node from './Node.js'; +import StructTypeNode from './StructTypeNode.js'; +import { nodeObject } from '../tsl/TSLCore.js'; + +/** @module StructNode **/ + +class InternalStructMemberNode extends Node { + + static get type() { + + return 'InternalStructMemberNode'; + + } + + constructor( structNode, member ) { + + super( member.type ); + + this._structNode = structNode; + this._member = member; + + this.isStructMemberNode = true; + + } + + get structNode() { + + return this._structNode; + + } + + get member() { + + return this._member; + + } + + generate( builder ) { + + const structName = this.structNode.build( builder ); + + return structName + '.' + this.member.name; + + } + +} + +class StructNode extends Node { + + static get type() { + + return 'StructNode'; + + } + + constructor( structLayoutNode, values ) { + + super( 'vec3' ); + + this.structLayoutNode = structLayoutNode; + this.values = values; + + for ( const member of structLayoutNode.membersLayout ) { + + this[ member.name ] = nodeObject( new InternalStructMemberNode( this, member ) ); + + } + + this.isStructNode = true; + + } + + getNodeType( builder ) { + + return this.structLayoutNode.getNodeType( builder ); + + } + + generate( builder ) { + + const nodeVar = builder.getVarFromNode( this ); + const structType = nodeVar.type; + const propertyName = builder.getPropertyName( nodeVar ); + + builder.addLineFlowCode( `${ propertyName } = ${ builder.generateStruct( structType, this.structLayoutNode.membersLayout, this.values ) }`, this ); + + return nodeVar.name; + + } + +} + +export default StructNode; + +export const struct = ( membersLayout ) => { + + const structLayout = new StructTypeNode( membersLayout ); + + const struct = ( values ) => { + + return nodeObject( new StructNode( structLayout, values ) ); + + }; + + return struct; + +}; diff --git a/src/nodes/core/StructType.js b/src/nodes/core/StructType.js new file mode 100644 index 00000000000000..6f3f3f2bcceb14 --- /dev/null +++ b/src/nodes/core/StructType.js @@ -0,0 +1,13 @@ +class StructType { + + constructor( name, members ) { + + this.name = name; + this.members = members; + this.output = false; + + } + +} + +export default StructType; diff --git a/src/nodes/core/StructTypeNode.js b/src/nodes/core/StructTypeNode.js index 3952f7f5c41bc2..b661b4d5459310 100644 --- a/src/nodes/core/StructTypeNode.js +++ b/src/nodes/core/StructTypeNode.js @@ -1,11 +1,23 @@ import Node from './Node.js'; -/** - * {@link NodeBuilder} is going to create instances of this class during the build process - * of nodes. They represent the final shader struct data that are going to be generated - * by the builder. A dictionary of struct types is maintained in {@link NodeBuilder#structs} - * for this purpose. - */ +/** @module StructTypeNode **/ + +function getMembersLayout( members ) { + + return Object.entries( members ).map( ( [ name, value ] ) => { + + if ( typeof value === 'string' ) { + + return { name, type: value, atomic: false }; + + } + + return { name, type: value.type, atomic: value.atomic || false }; + + } ); + +} + class StructTypeNode extends Node { static get type() { @@ -14,50 +26,21 @@ class StructTypeNode extends Node { } - /** - * Constructs a new struct type node. - * - * @param {String} name - The name of the struct. - * @param {Array} types - An array of types. - */ - constructor( name, types ) { - - super(); - - /** - * The name of the struct. - * - * @type {String} - */ - this.name = name; - - - /** - * An array of types. - * - * @type {Array} - */ - this.types = types; - - /** - * This flag can be used for type testing. - * - * @type {Boolean} - * @readonly - * @default true - */ - this.isStructTypeNode = true; + constructor( membersLayout ) { + + super( 'struct' ); + + this.membersLayout = getMembersLayout( membersLayout ); + + this.isStructLayoutNode = true; } - /** - * Returns the member types. - * - * @return {Array} The types. - */ - getMemberTypes() { + getNodeType( builder ) { + + const structType = builder.getStructTypeFromNode( this, this.membersLayout ); - return this.types; + return structType.name; } diff --git a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js index a425c61ef25f58..5124b4e63b8e87 100644 --- a/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js +++ b/src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js @@ -696,12 +696,10 @@ ${ flowData.code } getStructMembers( struct ) { const snippets = []; - const members = struct.getMemberTypes(); - for ( let i = 0; i < members.length; i ++ ) { + for ( const member of struct.members ) { - const member = members[ i ]; - snippets.push( `layout( location = ${i} ) out ${ member} m${i};` ); + snippets.push( `\t${ member.type } ${ member.name };` ); } @@ -720,25 +718,37 @@ ${ flowData.code } const snippets = []; const structs = this.structs[ shaderStage ]; - if ( structs.length === 0 ) { + const outputSnippet = []; - return 'layout( location = 0 ) out vec4 fragColor;\n'; + for ( const struct of structs ) { - } + if ( struct.output ) { + + for ( const member of struct.members ) { + + outputSnippet.push( `layout( location = ${ member.index } ) out ${ member.type } ${ member.name };` ); - for ( let index = 0, length = structs.length; index < length; index ++ ) { + } + + } else { - const struct = structs[ index ]; + let snippet = 'struct ' + struct.name + ' {\n'; + snippet += this.getStructMembers( struct ); + snippet += '\n};\n'; + + snippets.push( snippet ); + + } + + } - let snippet = '\n'; - snippet += this.getStructMembers( struct ); - snippet += '\n'; + if ( outputSnippet.length === 0 ) { - snippets.push( snippet ); + outputSnippet.push( 'layout( location = 0 ) out vec4 fragColor;' ); } - return snippets.join( '\n\n' ); + return '\n' + outputSnippet.join( '\n' ) + '\n\n' + snippets.join( '\n' ); } @@ -1156,6 +1166,7 @@ ${shaderData.varyings} // codes ${shaderData.codes} +// structs ${shaderData.structs} void main() { @@ -1245,6 +1256,7 @@ void main() { this.vertexShader = this._getGLSLVertexCode( shadersData.vertex ); this.fragmentShader = this._getGLSLFragmentCode( shadersData.fragment ); + console.log( this.fragmentShader ); } else { diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index 0776054446fb97..863b1bccefce7f 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -1424,18 +1424,14 @@ ${ flowData.code } getStructMembers( struct ) { const snippets = []; - const members = struct.getMemberTypes(); - for ( let i = 0; i < members.length; i ++ ) { + for ( const member of struct.members ) { - const member = members[ i ]; - snippets.push( `\t@location( ${i} ) m${i} : ${ member }` ); + const prefix = struct.output ? '@location( ' + member.index + ' ) ' : ''; - } - - const builtins = this.getBuiltins( 'output' ); + snippets.push( `\t${ prefix + member.name } : ${ this.getType( member.type ) }` ); - if ( builtins ) snippets.push( '\t' + builtins ); + } return snippets.join( ',\n' ); @@ -1449,26 +1445,29 @@ ${ flowData.code } */ getStructs( shaderStage ) { - const snippets = []; + let result = ''; + const structs = this.structs[ shaderStage ]; - for ( let index = 0, length = structs.length; index < length; index ++ ) { + if ( structs.length > 0 ) { - const struct = structs[ index ]; - const name = struct.name; + const snippets = []; - let snippet = `\struct ${ name } {\n`; - snippet += this.getStructMembers( struct ); - snippet += '\n}'; + for ( const struct of structs ) { + let snippet = `struct ${ struct.name } {\n`; + snippet += this.getStructMembers( struct ); + snippet += '\n};'; - snippets.push( snippet ); + snippets.push( snippet ); - snippets.push( `\nvar output : ${ name };\n\n` ); + } + + result = '\n' + snippets.join( '\n\n' ) + '\n'; } - return snippets.join( '\n\n' ); + return result; } @@ -1773,7 +1772,8 @@ ${ flowData.code } if ( isOutputStruct ) { - stageData.returnType = outputNode.nodeType; + stageData.returnType = outputNode.getNodeType( this ); + stageData.structs += 'var output : ' + stageData.returnType + ';'; flow += `return ${ flowSlotData.result };`; @@ -1787,7 +1787,7 @@ ${ flowData.code } stageData.returnType = 'OutputStruct'; stageData.structs += this._getWGSLStruct( 'OutputStruct', structSnippet ); - stageData.structs += '\nvar output : OutputStruct;\n\n'; + stageData.structs += '\nvar output : OutputStruct;'; flow += `output.color = ${ flowSlotData.result };\n\n\treturn output;`; @@ -1801,7 +1801,6 @@ ${ flowData.code } stageData.flow = flow; - } if ( this.material !== null ) { From 794ce674a4ef319b8079230b8c8bde396c9f20ef Mon Sep 17 00:00:00 2001 From: sunag Date: Sat, 25 Jan 2025 17:32:49 -0300 Subject: [PATCH 02/13] add alternative style --- src/nodes/core/StructNode.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/nodes/core/StructNode.js b/src/nodes/core/StructNode.js index cf0d504ad6ef74..e06fcb03e038dd 100644 --- a/src/nodes/core/StructNode.js +++ b/src/nodes/core/StructNode.js @@ -96,7 +96,31 @@ export const struct = ( membersLayout ) => { const structLayout = new StructTypeNode( membersLayout ); - const struct = ( values ) => { + const struct = ( ...params ) => { + + let values = null; + + if ( params.length > 0 ) { + + if ( params[ 0 ].isNode ) { + + values = {}; + + const names = Object.keys( membersLayout ); + + for ( let i = 0; i < params.length; i ++ ) { + + values[ names[ i ] ] = params[ i ]; + + } + + } else { + + values = params[ 0 ]; + + } + + } return nodeObject( new StructNode( structLayout, values ) ); From 06bf06565512bbedd2ad52cf82cac754f01ac46b Mon Sep 17 00:00:00 2001 From: sunag Date: Sat, 25 Jan 2025 18:38:58 -0300 Subject: [PATCH 03/13] added member support --- src/nodes/Nodes.js | 1 + src/nodes/core/Node.js | 13 ++++++ src/nodes/core/StackNode.js | 6 +++ src/nodes/core/StructNode.js | 53 +++---------------------- src/nodes/core/StructTypeNode.js | 8 ++++ src/nodes/tsl/TSLCore.js | 13 ++++++ src/nodes/utils/MemberNode.js | 68 ++++++++++++++++++++++++++++++++ 7 files changed, 115 insertions(+), 47 deletions(-) create mode 100644 src/nodes/utils/MemberNode.js diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index 47018fac0fefcf..c7bfbfe2604f6a 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -55,6 +55,7 @@ export { default as StorageArrayElementNode } from './utils/StorageArrayElementN export { default as TriplanarTexturesNode } from './utils/TriplanarTexturesNode.js'; export { default as ReflectorNode } from './utils/ReflectorNode.js'; export { default as RTTNode } from './utils/RTTNode.js'; +export { default as MemberNode } from './utils/MemberNode.js'; // accessors export { default as UniformArrayNode } from './accessors/UniformArrayNode.js'; diff --git a/src/nodes/core/Node.js b/src/nodes/core/Node.js index 5a2741e2e49694..647897b6a62baf 100644 --- a/src/nodes/core/Node.js +++ b/src/nodes/core/Node.js @@ -416,6 +416,19 @@ class Node extends EventDispatcher { } + /** + * Returns the node member type for the given name. + * + * @param {NodeBuilder} builder - The current node builder. + * @param {String} name - The name of the member. + * @return {String} The type of the node. + */ + getMemberType( /*uilder, name*/ ) { + + return 'void'; + + } + /** * Returns the node's type. * diff --git a/src/nodes/core/StackNode.js b/src/nodes/core/StackNode.js index ea9a8e0d686093..bcddf3ba0b6e9e 100644 --- a/src/nodes/core/StackNode.js +++ b/src/nodes/core/StackNode.js @@ -76,6 +76,12 @@ class StackNode extends Node { } + getMemberType( builder, name ) { + + return this.outputNode ? this.outputNode.getMemberType( builder, name ) : 'void'; + + } + /** * Adds a node to this stack. * diff --git a/src/nodes/core/StructNode.js b/src/nodes/core/StructNode.js index e06fcb03e038dd..feaf60907f6d0b 100644 --- a/src/nodes/core/StructNode.js +++ b/src/nodes/core/StructNode.js @@ -4,47 +4,6 @@ import { nodeObject } from '../tsl/TSLCore.js'; /** @module StructNode **/ -class InternalStructMemberNode extends Node { - - static get type() { - - return 'InternalStructMemberNode'; - - } - - constructor( structNode, member ) { - - super( member.type ); - - this._structNode = structNode; - this._member = member; - - this.isStructMemberNode = true; - - } - - get structNode() { - - return this._structNode; - - } - - get member() { - - return this._member; - - } - - generate( builder ) { - - const structName = this.structNode.build( builder ); - - return structName + '.' + this.member.name; - - } - -} - class StructNode extends Node { static get type() { @@ -60,12 +19,6 @@ class StructNode extends Node { this.structLayoutNode = structLayoutNode; this.values = values; - for ( const member of structLayoutNode.membersLayout ) { - - this[ member.name ] = nodeObject( new InternalStructMemberNode( this, member ) ); - - } - this.isStructNode = true; } @@ -76,6 +29,12 @@ class StructNode extends Node { } + getMemberType( builder, name ) { + + return this.structLayoutNode.getMemberType( builder, name ); + + } + generate( builder ) { const nodeVar = builder.getVarFromNode( this ); diff --git a/src/nodes/core/StructTypeNode.js b/src/nodes/core/StructTypeNode.js index b661b4d5459310..ffe56955852829 100644 --- a/src/nodes/core/StructTypeNode.js +++ b/src/nodes/core/StructTypeNode.js @@ -36,6 +36,14 @@ class StructTypeNode extends Node { } + getMemberType( builder, name ) { + + const member = this.membersLayout.find( m => m.name === name ); + + return member ? member.type : 'void'; + + } + getNodeType( builder ) { const structType = builder.getStructTypeFromNode( this, this.membersLayout ); diff --git a/src/nodes/tsl/TSLCore.js b/src/nodes/tsl/TSLCore.js index ac0961c123744e..94f02a851d1e01 100644 --- a/src/nodes/tsl/TSLCore.js +++ b/src/nodes/tsl/TSLCore.js @@ -6,6 +6,7 @@ import SplitNode from '../utils/SplitNode.js'; import SetNode from '../utils/SetNode.js'; import FlipNode from '../utils/FlipNode.js'; import ConstNode from '../core/ConstNode.js'; +import MemberNode from '../utils/MemberNode.js'; import { getValueFromType, getValueType } from '../core/NodeUtils.js'; /** @module TSLCore **/ @@ -112,6 +113,12 @@ const shaderNodeHandler = { return nodeObject( new ArrayElementNode( nodeObj, new ConstNode( Number( prop ), 'uint' ) ) ); + } else if ( /^get$/.test( prop ) === true ) { + + // accessing properties + + return ( value ) => nodeObject( new MemberNode( nodeObj, value ) ); + } } @@ -261,6 +268,12 @@ class ShaderCallNodeInternal extends Node { } + getMemberType( builder, name ) { + + return this.getOutputNode( builder ).getMemberType( builder, name ); + + } + call( builder ) { const { shaderNode, inputNodes } = this; diff --git a/src/nodes/utils/MemberNode.js b/src/nodes/utils/MemberNode.js new file mode 100644 index 00000000000000..c826ff57b57c4a --- /dev/null +++ b/src/nodes/utils/MemberNode.js @@ -0,0 +1,68 @@ +import Node from '../core/Node.js'; + +/** + * Base class for representing member access on an object-like + * node data structures. + * + * @augments Node + */ +class MemberNode extends Node { + + static get type() { + + return 'MemberNode'; + + } + + /** + * Constructs an array element node. + * + * @param {Node} node - The array-like node. + * @param {String} property - The property name. + */ + constructor( node, property ) { + + super(); + + /** + * The array-like node. + * + * @type {Node} + */ + this.node = node; + + /** + * The property name. + * + * @type {Node} + */ + this.property = property; + + /** + * This flag can be used for type testing. + * + * @type {Boolean} + * @readonly + * @default true + */ + this.isMemberNode = true; + + } + + getNodeType( builder ) { + + return this.node.getMemberType( builder, this.property ); + + } + + generate( builder ) { + + const propertyName = this.node.build( builder ); + + return propertyName + '.' + this.property; + + } + +} + +export default MemberNode; From 2c3a0d7eddfe7879b4c1e0290cf2c2d93593897c Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 27 Jan 2025 20:14:57 -0300 Subject: [PATCH 04/13] uniform buffer name revision --- src/renderers/webgpu/nodes/WGSLNodeBuilder.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index 863b1bccefce7f..29a678065a8675 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -758,7 +758,7 @@ class WGSLNodeBuilder extends NodeBuilder { } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { - return `NodeBuffer_${ node.id }.${name}`; + return name + '.value'; } else { @@ -899,7 +899,7 @@ class WGSLNodeBuilder extends NodeBuilder { if ( ( shaderStage === 'fragment' || shaderStage === 'compute' ) && this.isUnfilterable( node.value ) === false && texture.store === false ) { - const sampler = new NodeSampler( `${uniformNode.name}_sampler`, uniformNode.node, group ); + const sampler = new NodeSampler( `${ uniformNode.name }_sampler`, uniformNode.node, group ); sampler.setVisibility( gpuShaderStageLib[ shaderStage ] ); bindings.push( sampler, texture ); @@ -925,6 +925,8 @@ class WGSLNodeBuilder extends NodeBuilder { uniformGPU = buffer; + uniformNode.name = name ? name : 'NodeBuffer_' + uniformNode.id; + } else { const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} ); @@ -1661,7 +1663,7 @@ ${ flowData.code } const componentPrefix = this.getComponentTypeFromTexture( texture ).charAt( 0 ); - textureType = `texture${multisampled}_2d<${ componentPrefix }32>`; + textureType = `texture${ multisampled }_2d<${ componentPrefix }32>`; } @@ -1674,11 +1676,11 @@ ${ flowData.code } const bufferCount = bufferNode.bufferCount; const bufferCountSnippet = bufferCount > 0 && uniform.type === 'buffer' ? ', ' + bufferCount : ''; - const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${bufferType}>` : `${bufferType}`; - const bufferSnippet = `\t${ uniform.name } : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >\n`; + const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${ bufferType }>` : `${ bufferType }`; + const bufferSnippet = `\tvalue : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >`; const bufferAccessMode = bufferNode.isStorageBufferNode ? `storage, ${ this.getStorageAccess( bufferNode, shaderStage ) }` : 'uniform'; - bufferSnippets.push( this._getWGSLStructBinding( 'NodeBuffer_' + bufferNode.id, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); + bufferSnippets.push( this._getWGSLStructBinding( uniform.name, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); } else { @@ -1811,6 +1813,7 @@ ${ flowData.code } } else { this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) ); + //console.log( this.computeShader ); } @@ -2079,8 +2082,8 @@ ${vars} const structSnippet = this._getWGSLStruct( structName, vars ); return `${structSnippet} -@binding( ${binding} ) @group( ${group} ) -var<${access}> ${name} : ${structName};`; +@binding( ${ binding } ) @group( ${ group } ) +var<${access}> ${ name } : ${ structName };`; } From ca6d2a47268cfe668cef3c70f5f7746b28e936fc Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 03:05:04 -0300 Subject: [PATCH 05/13] add initial struct array support --- src/nodes/accessors/Arrays.js | 34 +++++++++-- src/nodes/accessors/StorageBufferNode.js | 38 +++++++++++-- src/nodes/core/StructNode.js | 7 ++- src/nodes/core/StructTypeNode.js | 26 ++++++++- src/nodes/utils/StorageArrayElementNode.js | 14 +++++ src/renderers/webgpu/nodes/WGSLNodeBuilder.js | 57 +++++++++++++++---- 6 files changed, 151 insertions(+), 25 deletions(-) diff --git a/src/nodes/accessors/Arrays.js b/src/nodes/accessors/Arrays.js index e516ca823aabbb..f235db08754c62 100644 --- a/src/nodes/accessors/Arrays.js +++ b/src/nodes/accessors/Arrays.js @@ -10,13 +10,24 @@ import { getLengthFromType, getTypedArrayFromType } from '../core/NodeUtils.js'; * * @function * @param {Number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument. - * @param {String} [type='float'] - The data type. + * @param {String|Struct} [type='float'] - The data type. * @returns {StorageBufferNode} */ export const attributeArray = ( count, type = 'float' ) => { - const itemSize = getLengthFromType( type ); - const typedArray = getTypedArrayFromType( type ); + let itemSize, typedArray; + + if ( type.isStruct === true ) { + + itemSize = type.layout.getLength(); + typedArray = getTypedArrayFromType( 'float' ); + + } else { + + itemSize = getLengthFromType( type ); + typedArray = getTypedArrayFromType( type ); + + } const buffer = new StorageBufferAttribute( count, itemSize, typedArray ); const node = storage( buffer, type, count ); @@ -30,13 +41,24 @@ export const attributeArray = ( count, type = 'float' ) => { * * @function * @param {Number|TypedArray} count - The data count. It is also valid to pass a typed array as an argument. - * @param {String} [type='float'] - The data type. + * @param {String|Struct} [type='float'] - The data type. * @returns {StorageBufferNode} */ export const instancedArray = ( count, type = 'float' ) => { - const itemSize = getLengthFromType( type ); - const typedArray = getTypedArrayFromType( type ); + let itemSize, typedArray; + + if ( type.isStruct === true ) { + + itemSize = type.layout.getLength(); + typedArray = getTypedArrayFromType( 'float' ); + + } else { + + itemSize = getLengthFromType( type ); + typedArray = getTypedArrayFromType( type ); + + } const buffer = new StorageInstancedBufferAttribute( count, itemSize, typedArray ); const node = storage( buffer, type, count ); diff --git a/src/nodes/accessors/StorageBufferNode.js b/src/nodes/accessors/StorageBufferNode.js index fcc2c172b42de7..af1f8f44ca9087 100644 --- a/src/nodes/accessors/StorageBufferNode.js +++ b/src/nodes/accessors/StorageBufferNode.js @@ -50,19 +50,30 @@ class StorageBufferNode extends BufferNode { * Constructs a new storage buffer node. * * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. - * @param {String?} [bufferType=null] - The buffer type (e.g. `'vec3'`). + * @param {String|Struct?} [bufferType=null] - The buffer type (e.g. `'vec3'`). * @param {Number} [bufferCount=0] - The buffer count. */ constructor( value, bufferType = null, bufferCount = 0 ) { - if ( bufferType === null && ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) ) { + let nodeType, structTypeNode = null; - bufferType = getTypeFromLength( value.itemSize ); + if ( bufferType && bufferType.isStruct ) { + + nodeType = 'struct'; + structTypeNode = bufferType.layout; + + } else if ( bufferType === null && ( value.isStorageBufferAttribute || value.isStorageInstancedBufferAttribute ) ) { + + nodeType = getTypeFromLength( value.itemSize ); bufferCount = value.count; + } else { + + nodeType = bufferType; + } - super( value, bufferType, bufferCount ); + super( value, nodeType, bufferCount ); /** * This flag can be used for type testing. @@ -73,6 +84,15 @@ class StorageBufferNode extends BufferNode { */ this.isStorageBufferNode = true; + + /** + * The buffer struct type. + * + * @type {structTypeNode?} + * @default null + */ + this.structTypeNode = structTypeNode; + /** * The access type of the texture node. * @@ -293,6 +313,12 @@ class StorageBufferNode extends BufferNode { */ getNodeType( builder ) { + if ( this.structTypeNode !== null ) { + + return this.structTypeNode.getNodeType( builder ); + + } + if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) { return super.getNodeType( builder ); @@ -313,6 +339,8 @@ class StorageBufferNode extends BufferNode { */ generate( builder ) { + if ( this.structTypeNode !== null ) this.structTypeNode.build( builder ); + if ( builder.isAvailable( 'storageBuffer' ) || builder.isAvailable( 'indirectStorageBuffer' ) ) { return super.generate( builder ); @@ -338,7 +366,7 @@ export default StorageBufferNode; * * @function * @param {StorageBufferAttribute|StorageInstancedBufferAttribute|BufferAttribute} value - The buffer data. - * @param {String?} [type=null] - The buffer type (e.g. `'vec3'`). + * @param {String|Struct?} [type=null] - The buffer type (e.g. `'vec3'`). * @param {Number} [count=0] - The buffer count. * @returns {StorageBufferNode} */ diff --git a/src/nodes/core/StructNode.js b/src/nodes/core/StructNode.js index feaf60907f6d0b..ad351c60ec3839 100644 --- a/src/nodes/core/StructNode.js +++ b/src/nodes/core/StructNode.js @@ -51,9 +51,9 @@ class StructNode extends Node { export default StructNode; -export const struct = ( membersLayout ) => { +export const struct = ( membersLayout, name = null ) => { - const structLayout = new StructTypeNode( membersLayout ); + const structLayout = new StructTypeNode( membersLayout, name ); const struct = ( ...params ) => { @@ -85,6 +85,9 @@ export const struct = ( membersLayout ) => { }; + struct.layout = structLayout; + struct.isStruct = true; + return struct; }; diff --git a/src/nodes/core/StructTypeNode.js b/src/nodes/core/StructTypeNode.js index ffe56955852829..930b9378949a5f 100644 --- a/src/nodes/core/StructTypeNode.js +++ b/src/nodes/core/StructTypeNode.js @@ -1,4 +1,5 @@ import Node from './Node.js'; +import { getLengthFromType } from './NodeUtils.js'; /** @module StructTypeNode **/ @@ -26,16 +27,31 @@ class StructTypeNode extends Node { } - constructor( membersLayout ) { + constructor( membersLayout, name = null ) { super( 'struct' ); this.membersLayout = getMembersLayout( membersLayout ); + this.name = name; this.isStructLayoutNode = true; } + getLength() { + + let length = 0; + + for ( const member of this.membersLayout ) { + + length += getLengthFromType( member.type ); + + } + + return length; + + } + getMemberType( builder, name ) { const member = this.membersLayout.find( m => m.name === name ); @@ -46,12 +62,18 @@ class StructTypeNode extends Node { getNodeType( builder ) { - const structType = builder.getStructTypeFromNode( this, this.membersLayout ); + const structType = builder.getStructTypeFromNode( this, this.membersLayout, this.name ); return structType.name; } + generate( builder ) { + + return this.getNodeType( builder ); + + } + } export default StructTypeNode; diff --git a/src/nodes/utils/StorageArrayElementNode.js b/src/nodes/utils/StorageArrayElementNode.js index ae2892549869cb..7cebcbf8b80f90 100644 --- a/src/nodes/utils/StorageArrayElementNode.js +++ b/src/nodes/utils/StorageArrayElementNode.js @@ -61,6 +61,20 @@ class StorageArrayElementNode extends ArrayElementNode { } + getMemberType( builder, name ) { + + const structTypeNode = this.storageBufferNode.structTypeNode; + + if ( structTypeNode ) { + + return structTypeNode.getMemberType( builder, name ); + + } + + return 'void'; + + } + setup( builder ) { if ( builder.isAvailable( 'storageBuffer' ) === false ) { diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index 29a678065a8675..e453b25011e0a8 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -758,6 +758,12 @@ class WGSLNodeBuilder extends NodeBuilder { } else if ( type === 'buffer' || type === 'storageBuffer' || type === 'indirectStorageBuffer' ) { + if ( this.isCustomStruct( node ) ) { + + return name; + + } + return name + '.value'; } else { @@ -1431,7 +1437,15 @@ ${ flowData.code } const prefix = struct.output ? '@location( ' + member.index + ' ) ' : ''; - snippets.push( `\t${ prefix + member.name } : ${ this.getType( member.type ) }` ); + let type = this.getType( member.type ); + + if ( member.atomic ) { + + type = 'atomic< ' + type + ' >'; + + } + + snippets.push( `\t${ prefix + member.name } : ${ type }` ); } @@ -1582,6 +1596,12 @@ ${ flowData.code } } + isCustomStruct( nodeUniform ) { + + return nodeUniform.value.isStorageBufferAttribute && nodeUniform.node.structTypeNode !== null; + + } + /** * Returns the uniforms of the given shader stage as a WGSL string. * @@ -1672,15 +1692,23 @@ ${ flowData.code } } else if ( uniform.type === 'buffer' || uniform.type === 'storageBuffer' || uniform.type === 'indirectStorageBuffer' ) { const bufferNode = uniform.node; - const bufferType = this.getType( bufferNode.bufferType ); + const bufferType = this.getType( bufferNode.getNodeType( this ) ); const bufferCount = bufferNode.bufferCount; - const bufferCountSnippet = bufferCount > 0 && uniform.type === 'buffer' ? ', ' + bufferCount : ''; - const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${ bufferType }>` : `${ bufferType }`; - const bufferSnippet = `\tvalue : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >`; const bufferAccessMode = bufferNode.isStorageBufferNode ? `storage, ${ this.getStorageAccess( bufferNode, shaderStage ) }` : 'uniform'; - bufferSnippets.push( this._getWGSLStructBinding( uniform.name, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); + if ( this.isCustomStruct( uniform ) ) { + + bufferSnippets.push( `@binding( ${ uniformIndexes.binding ++ } ) @group( ${ uniformIndexes.group } ) var<${ bufferAccessMode }> ${ uniform.name } : ${ bufferType };` ); + + } else { + + const bufferTypeSnippet = bufferNode.isAtomic ? `atomic<${ bufferType }>` : `${ bufferType }`; + const bufferSnippet = `\tvalue : array< ${ bufferTypeSnippet }${ bufferCountSnippet } >`; + + bufferSnippets.push( this._getWGSLStructBinding( uniform.name, bufferSnippet, bufferAccessMode, uniformIndexes.binding ++, uniformIndexes.group ) ); + + } } else { @@ -1726,6 +1754,8 @@ ${ flowData.code } for ( const shaderStage in shadersData ) { + this.shaderStage = shaderStage; + const stageData = shadersData[ shaderStage ]; stageData.uniforms = this.getUniforms( shaderStage ); stageData.attributes = this.getAttributes( shaderStage ); @@ -1805,6 +1835,8 @@ ${ flowData.code } } + this.shaderStage = null; + if ( this.material !== null ) { this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); @@ -1813,7 +1845,6 @@ ${ flowData.code } } else { this.computeShader = this._getWGSLComputeCode( shadersData.compute, ( this.object.workgroupSize || [ 64 ] ).join( ', ' ) ); - //console.log( this.computeShader ); } @@ -1943,6 +1974,9 @@ ${ flowData.code } // directives ${shaderData.directives} +// structs +${shaderData.structs} + // uniforms ${shaderData.uniforms} @@ -1982,12 +2016,12 @@ fn main( ${shaderData.attributes} ) -> VaryingsStruct { // global ${ diagnostics } -// uniforms -${shaderData.uniforms} - // structs ${shaderData.structs} +// uniforms +${shaderData.uniforms} + // codes ${shaderData.codes} @@ -2025,6 +2059,9 @@ var instanceIndex : u32; // locals ${shaderData.scopedArrays} +// structs +${shaderData.structs} + // uniforms ${shaderData.uniforms} From 62fa3f97b8121ff272342c6fa78e29e9c28bffaf Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 13:59:01 -0300 Subject: [PATCH 06/13] update to use struct --- examples/webgpu_compute_water.html | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/examples/webgpu_compute_water.html b/examples/webgpu_compute_water.html index 849899d74355c9..7a7997fdccf312 100644 --- a/examples/webgpu_compute_water.html +++ b/examples/webgpu_compute_water.html @@ -16,9 +16,9 @@ + + + + + \ No newline at end of file From b145cd20457053383db6b11cb2f3117bb204a076 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 14:08:43 -0300 Subject: [PATCH 09/13] add tags --- examples/tags.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/tags.json b/examples/tags.json index 1c508c2d1f4632..14b203880a8bb2 100644 --- a/examples/tags.json +++ b/examples/tags.json @@ -123,6 +123,7 @@ "webgpu_compute_sort_bitonic": [ "gpgpu" ], "webgpu_compute_texture": [ "gpgpu" ], "webgpu_compute_texture_pingpong": [ "gpgpu" ], + "webgpu_compute_water": [ "gpgpu", "struct" ], "webgpu_depth_texture": [ "renderTarget" ], "webgpu_loader_gltf_dispersion": [ "transmission" ], "webgpu_materials_lightmap": [ "shadow" ], From 46cb1dd4494dbc49a96875d0be23f7fb1a472825 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 14:09:43 -0300 Subject: [PATCH 10/13] cleanup --- examples/webgpu_struct_drawindirect.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/webgpu_struct_drawindirect.html b/examples/webgpu_struct_drawindirect.html index c511897622f621..4c2020b43c7993 100644 --- a/examples/webgpu_struct_drawindirect.html +++ b/examples/webgpu_struct_drawindirect.html @@ -138,7 +138,7 @@ instances: instances, index: instanceIndex, time: time - } ).compute( instances ); //not neccessary in this case but normally one wants to run through all instances + } ).compute( instances ); // not neccessary in this case but normally one wants to run through all instances const initDrawBuffer = wgslFn( ` fn compute( @@ -161,12 +161,12 @@ const vColor = varyingProperty( 'vec4', 'vColor' ); const positionShaderParams = { - position: attribute( 'position' ), - offset: attribute( 'offset' ), - color: attribute( 'color' ), - orientationStart: attribute( 'orientationStart' ), - orientationEnd: attribute( 'orientationEnd' ), - time: time + position: attribute( 'position' ), + offset: attribute( 'offset' ), + color: attribute( 'color' ), + orientationStart: attribute( 'orientationStart' ), + orientationEnd: attribute( 'orientationEnd' ), + time: time }; const positionShader = wgslFn( ` From 33b726649e417f3c986b6e9a3e98311d0b41fa74 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 14:10:14 -0300 Subject: [PATCH 11/13] cleanup --- examples/webgpu_struct_drawindirect.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/webgpu_struct_drawindirect.html b/examples/webgpu_struct_drawindirect.html index 4c2020b43c7993..56a572b4df6d4f 100644 --- a/examples/webgpu_struct_drawindirect.html +++ b/examples/webgpu_struct_drawindirect.html @@ -30,7 +30,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; - const renderer = new THREE.WebGPURenderer({ antialias: true }); + const renderer = new THREE.WebGPURenderer( { antialias: true } ); renderer.outputColorSpace = THREE.SRGBColorSpace; renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); From 6031f104bf65769d384d4a4ddb89088e6e602f28 Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 14:18:22 -0300 Subject: [PATCH 12/13] Update puppeteer.js --- test/e2e/puppeteer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index 8586551d2084a5..ccf5b5b37289cb 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -126,6 +126,7 @@ const exceptionList = [ // Awaiting for WebGPU Backend support in Puppeteer 'webgpu_storage_buffer', 'webgpu_compute_sort_bitonic', + 'webgpu_struct_drawindirect', // WebGPURenderer: Unknown problem 'webgpu_backdrop_water', From 0dca15e22232b61b9158954ab736ca27d57f672b Mon Sep 17 00:00:00 2001 From: sunag Date: Tue, 28 Jan 2025 14:20:28 -0300 Subject: [PATCH 13/13] Update webgpu_compute_water.html --- examples/webgpu_compute_water.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/webgpu_compute_water.html b/examples/webgpu_compute_water.html index ba9526730fb796..21aff32bc94f7a 100644 --- a/examples/webgpu_compute_water.html +++ b/examples/webgpu_compute_water.html @@ -302,7 +302,7 @@ } ); // Sphere Instance Storage - const sphereVelocityStorage = instancedArray( sphereArray, SphereStruct ).label( 'SphereVelocity' ); + const sphereVelocityStorage = instancedArray( sphereArray, SphereStruct ).label( 'SphereData' ); computeSphere = Fn( () => {