Skip to content

Commit

Permalink
WebGPURenderer: Introduced .toConst(), Const(), Var() (#30251)
Browse files Browse the repository at this point in the history
* WebGPURenderer: Introduced variable.toLet()

* change API to toConst()

* const/let both backends?

* const/let handle both backends

* cleanup code

* more cleanup

* cleanup

* add NodeBuilder.isDeterministic() and updates

* readonly suffix

* improve naming

* added `Const`, `Var`

---------

Co-authored-by: sunag <[email protected]>
  • Loading branch information
RenaudRohlinger and sunag authored Jan 8, 2025
1 parent ac467cf commit 73eddcb
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 18 deletions.
3 changes: 3 additions & 0 deletions src/Three.TSL.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const colorSpaceToWorking = TSL.colorSpaceToWorking;
export const colorToDirection = TSL.colorToDirection;
export const compute = TSL.compute;
export const cond = TSL.cond;
export const Const = TSL.Const;
export const context = TSL.context;
export const convert = TSL.convert;
export const convertColorSpace = TSL.convertColorSpace;
Expand Down Expand Up @@ -219,6 +220,7 @@ export const lights = TSL.lights;
export const linearDepth = TSL.linearDepth;
export const linearToneMapping = TSL.linearToneMapping;
export const localId = TSL.localId;
export const globalId = TSL.globalId;
export const log = TSL.log;
export const log2 = TSL.log2;
export const logarithmicDepthToViewZ = TSL.logarithmicDepthToViewZ;
Expand Down Expand Up @@ -496,6 +498,7 @@ export const uv = TSL.uv;
export const uvec2 = TSL.uvec2;
export const uvec3 = TSL.uvec3;
export const uvec4 = TSL.uvec4;
export const Var = TSL.Var;
export const varying = TSL.varying;
export const varyingProperty = TSL.varyingProperty;
export const vec2 = TSL.vec2;
Expand Down
52 changes: 48 additions & 4 deletions src/nodes/core/NodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1604,23 +1604,38 @@ class NodeBuilder {
* @param {String?} name - The variable's name.
* @param {String} [type=node.getNodeType( this )] - The variable's type.
* @param {('vertex'|'fragment'|'compute'|'any')} [shaderStage=this.shaderStage] - The shader stage.
* @param {Boolean} [readOnly=false] - Whether the variable is read-only or not.
*
* @return {NodeVar} The node variable.
*/
getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage ) {
getVarFromNode( node, name = null, type = node.getNodeType( this ), shaderStage = this.shaderStage, readOnly = false ) {

const nodeData = this.getDataFromNode( node, shaderStage );

let nodeVar = nodeData.variable;

if ( nodeVar === undefined ) {

const idNS = readOnly ? '_const' : '_var';

const vars = this.vars[ shaderStage ] || ( this.vars[ shaderStage ] = [] );
const id = this.vars[ idNS ] || ( this.vars[ idNS ] = 0 );

if ( name === null ) {

name = ( readOnly ? 'nodeConst' : 'nodeVar' ) + id;

this.vars[ idNS ] ++;

}

if ( name === null ) name = 'nodeVar' + vars.length;
nodeVar = new NodeVar( name, type, readOnly );

nodeVar = new NodeVar( name, type );
if ( ! readOnly ) {

vars.push( nodeVar );
vars.push( nodeVar );

}

nodeData.variable = nodeVar;

Expand All @@ -1630,6 +1645,35 @@ class NodeBuilder {

}

/**
* Returns whether a Node or its flow is deterministic, useful for use in `const`.
*
* @param {Node} node - The varying node.
* @return {Boolean} Returns true if deterministic.
*/
isDeterministic( node ) {

if ( node.isMathNode ) {

return this.isDeterministic( node.aNode ) &&
( node.bNode ? this.isDeterministic( node.bNode ) : true ) &&
( node.cNode ? this.isDeterministic( node.cNode ) : true );

} else if ( node.isOperatorNode ) {

return this.isDeterministic( node.aNode ) &&
( node.bNode ? this.isDeterministic( node.bNode ) : true );

} else if ( node.isConstNode ) {

return true;

}

return false;

}

/**
* Returns an instance of {@link NodeVarying} for the given varying node.
*
Expand Down
10 changes: 9 additions & 1 deletion src/nodes/core/NodeVar.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ class NodeVar {
*
* @param {String} name - The name of the variable.
* @param {String} type - The type of the variable.
* @param {Boolean} [readOnly=false] - The read-only flag.
*/
constructor( name, type ) {
constructor( name, type, readOnly = false ) {

/**
* This flag can be used for type testing.
Expand All @@ -37,6 +38,13 @@ class NodeVar {
*/
this.type = type;

/**
* The read-only flag.
*
* @type {boolean}
*/
this.readOnly = readOnly;

}

}
Expand Down
82 changes: 75 additions & 7 deletions src/nodes/core/VarNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ class VarNode extends Node {
*
* @param {Node} node - The node for which a variable should be created.
* @param {String?} name - The name of the variable in the shader.
* @param {Boolean?} readOnly - The read-only flag.
*/
constructor( node, name = null ) {
constructor( node, name = null, readOnly = false ) {

super();

Expand Down Expand Up @@ -64,6 +65,15 @@ class VarNode extends Node {
*/
this.isVarNode = true;

/**
*
* The read-only flag.
*
* @type {Boolean}
* @default false
*/
this.readOnly = readOnly;

}

getHash( builder ) {
Expand All @@ -80,15 +90,50 @@ class VarNode extends Node {

generate( builder ) {

const { node, name } = this;
const { node, name, readOnly } = this;
const { renderer } = builder;

const isWebGPUBackend = renderer.backend.isWebGPUBackend === true;

let isDeterministic = false;
let shouldTreatAsReadOnly = false;

if ( readOnly ) {

isDeterministic = builder.isDeterministic( node );

const nodeVar = builder.getVarFromNode( this, name, builder.getVectorType( this.getNodeType( builder ) ) );
shouldTreatAsReadOnly = isWebGPUBackend ? readOnly : isDeterministic;

}

const vectorType = builder.getVectorType( this.getNodeType( builder ) );
const snippet = node.build( builder, vectorType );

const nodeVar = builder.getVarFromNode( this, name, vectorType, undefined, shouldTreatAsReadOnly );

const propertyName = builder.getPropertyName( nodeVar );

const snippet = node.build( builder, nodeVar.type );
let declarationPrefix = propertyName;

if ( shouldTreatAsReadOnly ) {

const type = builder.getType( nodeVar.type );

if ( isWebGPUBackend ) {

declarationPrefix = isDeterministic
? `const ${ propertyName }`
: `let ${ propertyName }`;

} else {

declarationPrefix = `const ${ type } ${ propertyName }`;

builder.addLineFlowCode( `${propertyName} = ${snippet}`, this );
}

}

builder.addLineFlowCode( `${ declarationPrefix } = ${ snippet }`, this );

return propertyName;

Expand All @@ -108,13 +153,36 @@ export default VarNode;
*/
const createVar = /*@__PURE__*/ nodeProxy( VarNode );

addMethodChaining( 'toVar', ( ...params ) => createVar( ...params ).append() );
/**
* TSL function for creating a var node.
*
* @function
* @param {Node} node - The node for which a variable should be created.
* @param {String?} name - The name of the variable in the shader.
* @returns {VarNode}
*/
export const Var = ( node, name = null ) => createVar( node, name ).append();

/**
* TSL function for creating a const node.
*
* @function
* @param {Node} node - The node for which a constant should be created.
* @param {String?} name - The name of the constant in the shader.
* @returns {VarNode}
*/
export const Const = ( node, name = null ) => createVar( node, name, true ).append();

// Method chaining

addMethodChaining( 'toVar', Var );
addMethodChaining( 'toConst', Const );

// Deprecated

export const temp = ( node ) => { // @deprecated, r170

console.warn( 'TSL: "temp" is deprecated. Use ".toVar()" instead.' );
console.warn( 'TSL: "temp( node )" is deprecated. Use "Var( node )" or "node.toVar()" instead.' );

return createVar( node );

Expand Down
8 changes: 8 additions & 0 deletions src/nodes/gpgpu/ComputeBuiltinNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ export const numWorkgroups = /*@__PURE__*/ computeBuiltin( 'numWorkgroups', 'uve
*/
export const workgroupId = /*@__PURE__*/ computeBuiltin( 'workgroupId', 'uvec3' );

/**
* TSL function for creating a `globalId` builtin node. A non-linearized 3-dimensional
* representation of the current invocation's position within a 3D global grid.
*
* @function
* @returns {ComputeBuiltinNode<uvec3>}
*/
export const globalId = /*@__PURE__*/ computeBuiltin( 'globalId', 'uvec3' );
/**
* TSL function for creating a `localId` builtin node. A non-linearized 3-dimensional
* representation of the current invocation's position within a 3D workgroup grid.
Expand Down
9 changes: 9 additions & 0 deletions src/nodes/math/MathNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ class MathNode extends TempNode {
*/
this.cNode = cNode;

/**
* This flag can be used for type testing.
*
* @type {Boolean}
* @readonly
* @default true
*/
this.isMathNode = true;

}

/**
Expand Down
9 changes: 9 additions & 0 deletions src/nodes/math/OperatorNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ class OperatorNode extends TempNode {
*/
this.bNode = bNode;

/**
* This flag can be used for type testing.
*
* @type {Boolean}
* @readonly
* @default true
*/
this.isOperatorNode = true;

}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/renderers/webgpu/WebGPUBackend.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ class WebGPUBackend extends Backend {

//

const encoder = device.createCommandEncoder( {} );
const encoder = device.createCommandEncoder( { label: 'clear' } );
const currentPass = encoder.beginRenderPass( {
colorAttachments,
depthStencilAttachment
Expand All @@ -973,11 +973,13 @@ class WebGPUBackend extends Backend {
const groupGPU = this.get( computeGroup );


const descriptor = {};
const descriptor = {
label: 'computeGroup_' + computeGroup.id
};

this.initTimestampQuery( computeGroup, descriptor );

groupGPU.cmdEncoderGPU = this.device.createCommandEncoder();
groupGPU.cmdEncoderGPU = this.device.createCommandEncoder( { label: 'computeGroup_' + computeGroup.id } );

groupGPU.passEncoderGPU = groupGPU.cmdEncoderGPU.beginComputePass( descriptor );

Expand Down
6 changes: 3 additions & 3 deletions src/renderers/webgpu/nodes/WGSLNodeBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -1364,7 +1364,7 @@ ${ flowData.code }

if ( shaderStage === 'compute' ) {

this.getBuiltin( 'global_invocation_id', 'id', 'vec3<u32>', 'attribute' );
this.getBuiltin( 'global_invocation_id', 'globalId', 'vec3<u32>', 'attribute' );
this.getBuiltin( 'workgroup_id', 'workgroupId', 'vec3<u32>', 'attribute' );
this.getBuiltin( 'local_invocation_id', 'localId', 'vec3<u32>', 'attribute' );
this.getBuiltin( 'num_workgroups', 'numWorkgroups', 'vec3<u32>', 'attribute' );
Expand Down Expand Up @@ -1729,7 +1729,7 @@ ${ flowData.code }

if ( flow.length > 0 ) flow += '\n';

flow += `\t// flow -> ${ slotName }\n\t`;
flow += `\t// flow -> ${ slotName }\n`;

}

Expand Down Expand Up @@ -2007,7 +2007,7 @@ ${shaderData.codes}
fn main( ${shaderData.attributes} ) {
// system
instanceIndex = id.x + id.y * numWorkgroups.x * u32(${workgroupSize}) + id.z * numWorkgroups.x * numWorkgroups.y * u32(${workgroupSize});
instanceIndex = globalId.x + globalId.y * numWorkgroups.x * u32(${workgroupSize}) + globalId.z * numWorkgroups.x * numWorkgroups.y * u32(${workgroupSize});
// vars
${shaderData.vars}
Expand Down

0 comments on commit 73eddcb

Please sign in to comment.