Browse Source

WebGPURenderer: Uniform Group (#27134)

* Renderer: UniformGroup

* cleanup
sunag 1 year ago
parent
commit
025b563b67

+ 1 - 0
examples/jsm/nodes/Nodes.js

@@ -30,6 +30,7 @@ export { default as ParameterNode, parameter } from './core/ParameterNode.js';
 export { default as PropertyNode, property, output, diffuseColor, roughness, metalness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, shininess, dashSize, gapSize, pointWidth } from './core/PropertyNode.js';
 export { default as PropertyNode, property, output, diffuseColor, roughness, metalness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, shininess, dashSize, gapSize, pointWidth } from './core/PropertyNode.js';
 export { default as StackNode, stack } from './core/StackNode.js';
 export { default as StackNode, stack } from './core/StackNode.js';
 export { default as TempNode } from './core/TempNode.js';
 export { default as TempNode } from './core/TempNode.js';
+export { default as UniformGroupNode, uniformGroup, objectGroup, renderGroup, frameGroup } from './core/UniformGroupNode.js';
 export { default as UniformNode, uniform } from './core/UniformNode.js';
 export { default as UniformNode, uniform } from './core/UniformNode.js';
 export { default as VaryingNode, varying } from './core/VaryingNode.js';
 export { default as VaryingNode, varying } from './core/VaryingNode.js';
 export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js';
 export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js';

+ 10 - 0
examples/jsm/nodes/accessors/CameraNode.js

@@ -1,14 +1,22 @@
 import Object3DNode from './Object3DNode.js';
 import Object3DNode from './Object3DNode.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeClass } from '../core/Node.js';
 import { label } from '../core/ContextNode.js';
 import { label } from '../core/ContextNode.js';
+import { NodeUpdateType } from '../core/constants.js';
+//import { sharedUniformGroup } from '../core/UniformGroupNode.js';
 import { nodeImmutable } from '../shadernode/ShaderNode.js';
 import { nodeImmutable } from '../shadernode/ShaderNode.js';
 
 
+//const cameraGroup = sharedUniformGroup( 'camera' );
+
 class CameraNode extends Object3DNode {
 class CameraNode extends Object3DNode {
 
 
 	constructor( scope = CameraNode.POSITION ) {
 	constructor( scope = CameraNode.POSITION ) {
 
 
 		super( scope );
 		super( scope );
 
 
+		this.updateType = NodeUpdateType.RENDER;
+
+		//this._uniformNode.groupNode = cameraGroup;
+
 	}
 	}
 
 
 	getNodeType( builder ) {
 	getNodeType( builder ) {
@@ -35,6 +43,8 @@ class CameraNode extends Object3DNode {
 		const uniformNode = this._uniformNode;
 		const uniformNode = this._uniformNode;
 		const scope = this.scope;
 		const scope = this.scope;
 
 
+		//cameraGroup.needsUpdate = true;
+
 		if ( scope === CameraNode.VIEW_MATRIX ) {
 		if ( scope === CameraNode.VIEW_MATRIX ) {
 
 
 			uniformNode.value = camera.matrixWorldInverse;
 			uniformNode.value = camera.matrixWorldInverse;

+ 11 - 2
examples/jsm/nodes/accessors/MaterialReferenceNode.js

@@ -1,5 +1,6 @@
 import ReferenceNode from './ReferenceNode.js';
 import ReferenceNode from './ReferenceNode.js';
-import { NodeUpdateType } from '../core/constants.js';
+//import { renderGroup } from '../core/UniformGroupNode.js';
+//import { NodeUpdateType } from '../core/constants.js';
 import { addNodeClass } from '../core/Node.js';
 import { addNodeClass } from '../core/Node.js';
 import { nodeObject } from '../shadernode/ShaderNode.js';
 import { nodeObject } from '../shadernode/ShaderNode.js';
 
 
@@ -11,10 +12,18 @@ class MaterialReferenceNode extends ReferenceNode {
 
 
 		this.material = material;
 		this.material = material;
 
 
-		this.updateType = NodeUpdateType.RENDER;
+		//this.updateType = NodeUpdateType.RENDER;
 
 
 	}
 	}
 
 
+	/*setNodeType( node ) {
+
+		super.setNodeType( node );
+
+		this.node.groupNode = renderGroup;
+
+	}*/
+
 	updateReference( frame ) {
 	updateReference( frame ) {
 
 
 		this.reference = this.material !== null ? this.material : frame.material;
 		this.reference = this.material !== null ? this.material : frame.material;

+ 1 - 1
examples/jsm/nodes/accessors/ModelNode.js

@@ -23,7 +23,7 @@ class ModelNode extends Object3DNode {
 export default ModelNode;
 export default ModelNode;
 
 
 export const modelDirection = nodeImmutable( ModelNode, ModelNode.DIRECTION );
 export const modelDirection = nodeImmutable( ModelNode, ModelNode.DIRECTION );
-export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX ).temp( 'ModelViewMatrix' );
+export const modelViewMatrix = nodeImmutable( ModelNode, ModelNode.VIEW_MATRIX ).label( 'modelViewMatrix' ).temp( 'ModelViewMatrix' );
 export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX );
 export const modelNormalMatrix = nodeImmutable( ModelNode, ModelNode.NORMAL_MATRIX );
 export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );
 export const modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX );
 export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION );
 export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION );

+ 39 - 1
examples/jsm/nodes/core/NodeBuilder.js

@@ -22,6 +22,9 @@ import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js';
 import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
 import { maxMipLevel } from '../utils/MaxMipLevelNode.js';
 
 
 import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
 import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js';
+import ChainMap from '../../renderers/common/ChainMap.js';
+
+const uniformsGroupCache = new ChainMap();
 
 
 const typeFromLength = new Map( [
 const typeFromLength = new Map( [
 	[ 2, 'vec2' ],
 	[ 2, 'vec2' ],
@@ -128,6 +131,41 @@ class NodeBuilder {
 
 
 	}
 	}
 
 
+	_getSharedBindings( bindings ) {
+
+		const shared = [];
+
+		for ( const binding of bindings ) {
+
+			if ( binding.shared === true ) {
+
+				// nodes is the chainmap key
+				const nodes = binding.getNodes();
+
+				let sharedBinding = uniformsGroupCache.get( nodes );
+
+				if ( sharedBinding === undefined ) {
+
+					uniformsGroupCache.set( nodes, binding );
+
+					sharedBinding = binding;
+
+				}
+
+				shared.push( sharedBinding );
+
+			} else {
+
+				shared.push( binding );
+
+			}
+
+		}
+
+		return shared;
+
+	}
+
 	getBindings() {
 	getBindings() {
 
 
 		let bindingsArray = this.bindingsArray;
 		let bindingsArray = this.bindingsArray;
@@ -136,7 +174,7 @@ class NodeBuilder {
 
 
 			const bindings = this.bindings;
 			const bindings = this.bindings;
 
 
-			this.bindingsArray = bindingsArray = ( this.material !== null ) ? [ ...bindings.vertex, ...bindings.fragment ] : bindings.compute;
+			this.bindingsArray = bindingsArray = this._getSharedBindings( ( this.material !== null ) ? [ ...bindings.vertex, ...bindings.fragment ] : bindings.compute );
 
 
 		}
 		}
 
 

+ 10 - 8
examples/jsm/nodes/core/NodeFrame.js

@@ -47,10 +47,10 @@ class NodeFrame {
 		const updateType = node.getUpdateBeforeType();
 		const updateType = node.getUpdateBeforeType();
 		const reference = node.updateReference( this );
 		const reference = node.updateReference( this );
 
 
-		const { frameMap, renderMap } = this._getMaps( this.updateBeforeMap, reference );
-
 		if ( updateType === NodeUpdateType.FRAME ) {
 		if ( updateType === NodeUpdateType.FRAME ) {
 
 
+			const { frameMap } = this._getMaps( this.updateBeforeMap, reference );
+
 			if ( frameMap.get( node ) !== this.frameId ) {
 			if ( frameMap.get( node ) !== this.frameId ) {
 
 
 				frameMap.set( node, this.frameId );
 				frameMap.set( node, this.frameId );
@@ -61,10 +61,11 @@ class NodeFrame {
 
 
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 
 
-			if ( renderMap.get( node ) !== this.renderId || frameMap.get( node ) !== this.frameId ) {
+			const { renderMap } = this._getMaps( this.updateBeforeMap, reference );
+
+			if ( renderMap.get( node ) !== this.renderId ) {
 
 
 				renderMap.set( node, this.renderId );
 				renderMap.set( node, this.renderId );
-				frameMap.set( node, this.frameId );
 
 
 				node.updateBefore( this );
 				node.updateBefore( this );
 
 
@@ -83,10 +84,10 @@ class NodeFrame {
 		const updateType = node.getUpdateType();
 		const updateType = node.getUpdateType();
 		const reference = node.updateReference( this );
 		const reference = node.updateReference( this );
 
 
-		const { frameMap, renderMap } = this._getMaps( this.updateMap, reference );
-
 		if ( updateType === NodeUpdateType.FRAME ) {
 		if ( updateType === NodeUpdateType.FRAME ) {
 
 
+			const { frameMap } = this._getMaps( this.updateMap, reference );
+
 			if ( frameMap.get( node ) !== this.frameId ) {
 			if ( frameMap.get( node ) !== this.frameId ) {
 
 
 				frameMap.set( node, this.frameId );
 				frameMap.set( node, this.frameId );
@@ -97,10 +98,11 @@ class NodeFrame {
 
 
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 
 
-			if ( renderMap.get( node ) !== this.renderId || frameMap.get( node ) !== this.frameId ) {
+			const { renderMap } = this._getMaps( this.updateMap, reference );
+
+			if ( renderMap.get( node ) !== this.renderId ) {
 
 
 				renderMap.set( node, this.renderId );
 				renderMap.set( node, this.renderId );
-				frameMap.set( node, this.frameId );
 
 
 				node.update( this );
 				node.update( this );
 
 

+ 12 - 0
examples/jsm/nodes/core/NodeUniform.js

@@ -23,6 +23,18 @@ class NodeUniform {
 
 
 	}
 	}
 
 
+	get id() {
+
+		return this.node.id;
+
+	}
+
+	get groupNode() {
+
+		return this.node.groupNode;
+
+	}
+
 }
 }
 
 
 export default NodeUniform;
 export default NodeUniform;

+ 13 - 0
examples/jsm/nodes/core/UniformGroup.js

@@ -0,0 +1,13 @@
+class UniformGroup {
+
+	constructor( name ) {
+
+		this.name = name;
+
+		this.isUniformGroup = true;
+
+	}
+
+}
+
+export default UniformGroup;

+ 36 - 0
examples/jsm/nodes/core/UniformGroupNode.js

@@ -0,0 +1,36 @@
+import Node from './Node.js';
+import { addNodeClass } from './Node.js';
+
+class UniformGroupNode extends Node {
+
+	constructor( name, shared = false ) {
+
+		super( 'string' );
+
+		this.name = name;
+		this.version = 0;
+
+		this.shared = shared;
+
+		this.isUniformGroup = true;
+
+	}
+
+	set needsUpdate( value ) {
+
+		if ( value === true ) this.version ++;
+
+	}
+
+}
+
+export const uniformGroup = ( name ) => new UniformGroupNode( name );
+export const sharedUniformGroup = ( name ) => new UniformGroupNode( name, true );
+
+export const frameGroup = sharedUniformGroup( 'frame' );
+export const renderGroup = sharedUniformGroup( 'render' );
+export const objectGroup = uniformGroup( 'object' );
+
+export default UniformGroupNode;
+
+addNodeClass( 'UniformGroupNode', UniformGroupNode );

+ 17 - 0
examples/jsm/nodes/core/UniformNode.js

@@ -1,4 +1,5 @@
 import InputNode from './InputNode.js';
 import InputNode from './InputNode.js';
+import { objectGroup } from './UniformGroupNode.js';
 import { addNodeClass } from './Node.js';
 import { addNodeClass } from './Node.js';
 import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js';
 import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js';
 
 
@@ -10,6 +11,22 @@ class UniformNode extends InputNode {
 
 
 		this.isUniformNode = true;
 		this.isUniformNode = true;
 
 
+		this.groupNode = objectGroup;
+
+	}
+
+	setGroup( group ) {
+
+		this.groupNode = group;
+
+		return this;
+
+	}
+
+	getGroup() {
+
+		return this.groupNode;
+
 	}
 	}
 
 
 	getUniformHash( builder ) {
 	getUniformHash( builder ) {

+ 1 - 0
examples/jsm/nodes/materials/MeshBasicNodeMaterial.js

@@ -13,6 +13,7 @@ class MeshBasicNodeMaterial extends NodeMaterial {
 		this.isMeshBasicNodeMaterial = true;
 		this.isMeshBasicNodeMaterial = true;
 
 
 		this.lights = false;
 		this.lights = false;
+		//this.normals = false; @TODO: normals usage by context
 
 
 		this.setDefaultValues( defaultValues );
 		this.setDefaultValues( defaultValues );
 
 

+ 10 - 21
examples/jsm/renderers/common/Bindings.js

@@ -16,8 +16,6 @@ class Bindings extends DataMap {
 
 
 		this.pipelines.bindings = this; // assign bindings to pipelines
 		this.pipelines.bindings = this; // assign bindings to pipelines
 
 
-		this.updateMap = new WeakMap();
-
 	}
 	}
 
 
 	getForRender( renderObject ) {
 	getForRender( renderObject ) {
@@ -100,24 +98,25 @@ class Bindings extends DataMap {
 
 
 		const { backend } = this;
 		const { backend } = this;
 
 
-		const updateMap = this.updateMap;
-		const callId = this.info.calls;
-
 		let needsBindingsUpdate = false;
 		let needsBindingsUpdate = false;
 
 
 		// iterate over all bindings and check if buffer updates or a new binding group is required
 		// iterate over all bindings and check if buffer updates or a new binding group is required
 
 
 		for ( const binding of bindings ) {
 		for ( const binding of bindings ) {
 
 
-			const isUpdated = updateMap.get( binding ) === callId;
+			if ( binding.isNodeUniformsGroup ) {
+
+				const updated = this.nodes.updateGroup( binding );
 
 
-			if ( isUpdated ) continue;
+				if ( ! updated ) continue;
+
+			}
 
 
 			if ( binding.isUniformBuffer ) {
 			if ( binding.isUniformBuffer ) {
 
 
-				const needsUpdate = binding.update();
+				const updated = binding.update();
 
 
-				if ( needsUpdate ) {
+				if ( updated ) {
 
 
 					backend.updateBinding( binding );
 					backend.updateBinding( binding );
 
 
@@ -127,9 +126,9 @@ class Bindings extends DataMap {
 
 
 				if ( binding.needsBindingsUpdate ) needsBindingsUpdate = true;
 				if ( binding.needsBindingsUpdate ) needsBindingsUpdate = true;
 
 
-				const needsUpdate = binding.update();
+				const updated = binding.update();
 
 
-				if ( needsUpdate ) {
+				if ( updated ) {
 
 
 					this.textures.updateTexture( binding.texture );
 					this.textures.updateTexture( binding.texture );
 
 
@@ -137,8 +136,6 @@ class Bindings extends DataMap {
 
 
 			}
 			}
 
 
-			updateMap.set( binding, callId );
-
 		}
 		}
 
 
 		if ( needsBindingsUpdate === true ) {
 		if ( needsBindingsUpdate === true ) {
@@ -151,14 +148,6 @@ class Bindings extends DataMap {
 
 
 	}
 	}
 
 
-	dispose() {
-
-		super.dispose();
-
-		this.updateMap = new WeakMap();
-
-	}
-
 }
 }
 
 
 export default Bindings;
 export default Bindings;

+ 3 - 3
examples/jsm/renderers/common/ChainMap.js

@@ -12,7 +12,7 @@ export default class ChainMap {
 
 
 			let map = this.weakMap;
 			let map = this.weakMap;
 
 
-			for ( let i = 0; i < keys.length - 1; i ++ ) {
+			for ( let i = 0; i < keys.length; i ++ ) {
 
 
 				map = map.get( keys[ i ] );
 				map = map.get( keys[ i ] );
 
 
@@ -36,7 +36,7 @@ export default class ChainMap {
 
 
 			let map = this.weakMap;
 			let map = this.weakMap;
 
 
-			for ( let i = 0; i < keys.length - 1; i ++ ) {
+			for ( let i = 0; i < keys.length; i ++ ) {
 
 
 				const key = keys[ i ];
 				const key = keys[ i ];
 
 
@@ -62,7 +62,7 @@ export default class ChainMap {
 
 
 			let map = this.weakMap;
 			let map = this.weakMap;
 
 
-			for ( let i = 0; i < keys.length - 1; i ++ ) {
+			for ( let i = 0; i < keys.length; i ++ ) {
 
 
 				map = map.get( keys[ i ] );
 				map = map.get( keys[ i ] );
 
 

+ 12 - 2
examples/jsm/renderers/common/Renderer.js

@@ -196,11 +196,11 @@ class Renderer {
 
 
 		//
 		//
 
 
-		nodeFrame.renderId ++;
-
 		this.info.calls ++;
 		this.info.calls ++;
 		this.info.render.calls ++;
 		this.info.render.calls ++;
 
 
+		nodeFrame.renderId = this.info.calls;
+
 		//
 		//
 
 
 		const coordinateSystem = this.coordinateSystem;
 		const coordinateSystem = this.coordinateSystem;
@@ -660,11 +660,17 @@ class Renderer {
 
 
 		if ( this._initialized === false ) await this.init();
 		if ( this._initialized === false ) await this.init();
 
 
+		const nodeFrame = this._nodes.nodeFrame;
+
+		const previousRenderId = nodeFrame.renderId;
+
 		//
 		//
 
 
 		this.info.calls ++;
 		this.info.calls ++;
 		this.info.compute.calls ++;
 		this.info.compute.calls ++;
 
 
+		nodeFrame.renderId = this.info.calls;
+
 		//
 		//
 
 
 		const backend = this.backend;
 		const backend = this.backend;
@@ -717,6 +723,10 @@ class Renderer {
 
 
 		backend.finishCompute( computeNodes );
 		backend.finishCompute( computeNodes );
 
 
+		//
+
+		nodeFrame.renderId = previousRenderId;
+
 	}
 	}
 
 
 	hasFeature( name ) {
 	hasFeature( name ) {

+ 10 - 2
examples/jsm/renderers/common/nodes/NodeBuilderState.js

@@ -20,9 +20,17 @@ class NodeBuilderState {
 
 
 		const bindingsArray = [];
 		const bindingsArray = [];
 
 
-		for ( const binding of this.bindings ) {
+		for ( const instanceBinding of this.bindings ) {
 
 
-			bindingsArray.push( binding.clone() );
+			let binding = instanceBinding;
+
+			if ( instanceBinding.shared !== true ) {
+
+				binding = instanceBinding.clone();
+
+			}
+
+			bindingsArray.push( binding );
 
 
 		}
 		}
 
 

+ 44 - 0
examples/jsm/renderers/common/nodes/NodeUniformsGroup.js

@@ -0,0 +1,44 @@
+import UniformsGroup from '../UniformsGroup.js';
+
+let id = 0;
+
+class NodeUniformsGroup extends UniformsGroup {
+
+	constructor( name, groupNode ) {
+
+		super( name );
+
+		this.id = id ++;
+		this.groupNode = groupNode;
+
+		this.isNodeUniformsGroup = true;
+
+	}
+
+	get shared() {
+
+		return this.groupNode.shared;
+
+	}
+
+	getNodes() {
+
+		const nodes = [];
+
+		for ( const uniform of this.uniforms ) {
+
+			const node = uniform.nodeUniform.node;
+
+			if ( ! node ) throw new Error( 'NodeUniformsGroup: Uniform has no node.' );
+
+			nodes.push( node );
+
+		}
+
+		return nodes;
+
+	}
+
+}
+
+export default NodeUniformsGroup;

+ 68 - 1
examples/jsm/renderers/common/nodes/Nodes.js

@@ -2,7 +2,7 @@ import DataMap from '../DataMap.js';
 import ChainMap from '../ChainMap.js';
 import ChainMap from '../ChainMap.js';
 import NodeBuilderState from './NodeBuilderState.js';
 import NodeBuilderState from './NodeBuilderState.js';
 import { NoToneMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
 import { NoToneMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
-import { NodeFrame, cubeTexture, texture, rangeFog, densityFog, reference, toneMapping, equirectUV, viewportBottomLeft, normalWorld } from '../../../nodes/Nodes.js';
+import { NodeFrame, objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, toneMapping, equirectUV, viewportBottomLeft, normalWorld } from '../../../nodes/Nodes.js';
 
 
 class Nodes extends DataMap {
 class Nodes extends DataMap {
 
 
@@ -15,6 +15,73 @@ class Nodes extends DataMap {
 		this.nodeFrame = new NodeFrame();
 		this.nodeFrame = new NodeFrame();
 		this.nodeBuilderCache = new Map();
 		this.nodeBuilderCache = new Map();
 		this.callHashCache = new ChainMap();
 		this.callHashCache = new ChainMap();
+		this.groupsData = new ChainMap();
+
+	}
+
+	updateGroup( nodeUniformsGroup ) {
+
+		const groupNode = nodeUniformsGroup.groupNode;
+		const name = groupNode.name;
+
+		// objectGroup is every updated
+
+		if ( name === objectGroup.name ) return true;
+
+		// renderGroup is updated once per render/compute call
+
+		if ( name === renderGroup.name ) {
+
+			const uniformsGroupData = this.get( nodeUniformsGroup );
+			const renderId = this.nodeFrame.renderId;
+
+			if ( uniformsGroupData.renderId !== renderId ) {
+
+				uniformsGroupData.renderId = renderId;
+
+				return true;
+
+			}
+
+			return false;
+
+		}
+
+		// frameGroup is updated once per frame
+
+		if ( name === frameGroup.name ) {
+
+			const uniformsGroupData = this.get( nodeUniformsGroup );
+			const frameId = this.nodeFrame.frameId;
+
+			if ( uniformsGroupData.frameId !== frameId ) {
+
+				uniformsGroupData.frameId = frameId;
+
+				return true;
+
+			}
+
+			return false;
+
+		}
+
+		// other groups are updated just when groupNode.needsUpdate is true
+
+		const groupChain = [ groupNode, nodeUniformsGroup ];
+
+		let groupData = this.groupsData.get( groupChain );
+		if ( groupData === undefined ) this.groupsData.set( groupChain, groupData = {} );
+
+		if ( groupData.version !== groupNode.version ) {
+
+			groupData.version = groupNode.version;
+
+			return true;
+
+		}
+
+		return false;
 
 
 	}
 	}
 
 

+ 19 - 8
examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js

@@ -1,7 +1,8 @@
 import { MathNode, GLSLNodeParser, NodeBuilder, NodeMaterial, FunctionNode } from '../../../nodes/Nodes.js';
 import { MathNode, GLSLNodeParser, NodeBuilder, NodeMaterial, FunctionNode } from '../../../nodes/Nodes.js';
 
 
 import UniformBuffer from '../../common/UniformBuffer.js';
 import UniformBuffer from '../../common/UniformBuffer.js';
-import UniformsGroup from '../../common/UniformsGroup.js';
+import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js';
+
 import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
 import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
 
 
 const glslMethods = {
 const glslMethods = {
@@ -25,7 +26,7 @@ class GLSLNodeBuilder extends NodeBuilder {
 
 
 		super( object, renderer, new GLSLNodeParser(), scene );
 		super( object, renderer, new GLSLNodeParser(), scene );
 
 
-		this.uniformsGroup = {};
+		this.uniformGroups = {};
 
 
 	}
 	}
 
 
@@ -138,7 +139,7 @@ ${ flowData.code }
 		const uniforms = this.uniforms[ shaderStage ];
 		const uniforms = this.uniforms[ shaderStage ];
 
 
 		const bindingSnippets = [];
 		const bindingSnippets = [];
-		const groupSnippets = [];
+		const uniformGroups = {};
 
 
 		for ( const uniform of uniforms ) {
 		for ( const uniform of uniforms ) {
 
 
@@ -192,6 +193,9 @@ ${ flowData.code }
 
 
 				snippet = '\t' + snippet;
 				snippet = '\t' + snippet;
 
 
+				const groupName = uniform.groupNode.name;
+				const groupSnippets = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = [] );
+
 				groupSnippets.push( snippet );
 				groupSnippets.push( snippet );
 
 
 			} else {
 			} else {
@@ -206,9 +210,11 @@ ${ flowData.code }
 
 
 		let output = '';
 		let output = '';
 
 
-		if ( groupSnippets.length > 0 ) {
+		for ( const name in uniformGroups ) {
+
+			const groupSnippets = uniformGroups[ name ];
 
 
-			output += this._getGLSLUniformStruct( shaderStage + 'NodeUniforms', groupSnippets.join( '\n' ) ) + '\n';
+			output += this._getGLSLUniformStruct( shaderStage + '_' + name, groupSnippets.join( '\n' ) ) + '\n';
 
 
 		}
 		}
 
 
@@ -551,14 +557,19 @@ void main() {
 
 
 			} else {
 			} else {
 
 
-				let uniformsGroup = this.uniformsGroup[ shaderStage ];
+				const group = node.groupNode;
+				const groupName = group.name;
+
+				const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} );
+
+				let uniformsGroup = uniformsStage[ groupName ];
 
 
 				if ( uniformsGroup === undefined ) {
 				if ( uniformsGroup === undefined ) {
 
 
-					uniformsGroup = new UniformsGroup( shaderStage + 'NodeUniforms' );
+					uniformsGroup = new NodeUniformsGroup( shaderStage + '_' + groupName, group );
 					//uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
 					//uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 
-					this.uniformsGroup[ shaderStage ] = uniformsGroup;
+					uniformsStage[ groupName ] = uniformsGroup;
 
 
 					this.bindings[ shaderStage ].push( uniformsGroup );
 					this.bindings[ shaderStage ].push( uniformsGroup );
 
 

+ 38 - 25
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -1,6 +1,6 @@
 import { NoColorSpace, FloatType } from 'three';
 import { NoColorSpace, FloatType } from 'three';
 
 
-import UniformsGroup from '../../common/UniformsGroup.js';
+import NodeUniformsGroup from '../../common/nodes/NodeUniformsGroup.js';
 
 
 import NodeSampler from '../../common/nodes/NodeSampler.js';
 import NodeSampler from '../../common/nodes/NodeSampler.js';
 import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
 import { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js';
@@ -98,7 +98,7 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 		super( object, renderer, new WGSLNodeParser(), scene );
 		super( object, renderer, new WGSLNodeParser(), scene );
 
 
-		this.uniformsGroup = {};
+		this.uniformGroups = {};
 
 
 		this.builtins = {
 		this.builtins = {
 			vertex: new Map(),
 			vertex: new Map(),
@@ -266,11 +266,11 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 			} else if ( type === 'buffer' || type === 'storageBuffer' ) {
 			} else if ( type === 'buffer' || type === 'storageBuffer' ) {
 
 
-				return `NodeBuffer_${node.node.id}.${name}`;
+				return `NodeBuffer_${ node.id }.${name}`;
 
 
 			} else {
 			} else {
 
 
-				return `NodeUniforms.${name}`;
+				return node.groupNode.name + '.' + name;
 
 
 			}
 			}
 
 
@@ -280,6 +280,12 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 
 	}
 	}
 
 
+	_getUniformGroupCount( shaderStage ) {
+
+		return Object.keys( this.uniforms[ shaderStage ] ).length;
+
+	}
+
 	getUniformFromNode( node, type, shaderStage, name = null ) {
 	getUniformFromNode( node, type, shaderStage, name = null ) {
 
 
 		const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
 		const uniformNode = super.getUniformFromNode( node, type, shaderStage, name );
@@ -308,22 +314,18 @@ class WGSLNodeBuilder extends NodeBuilder {
 				texture.store = node.isStoreTextureNode === true;
 				texture.store = node.isStoreTextureNode === true;
 				texture.setVisibility( gpuShaderStageLib[ shaderStage ] );
 				texture.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 
-				// add first textures in sequence and group for last
-				const lastBinding = bindings[ bindings.length - 1 ];
-				const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length;
-
 				if ( shaderStage === 'fragment' && this.isUnfilterable( node.value ) === false && texture.store === false ) {
 				if ( shaderStage === 'fragment' && this.isUnfilterable( node.value ) === false && texture.store === false ) {
 
 
 					const sampler = new NodeSampler( `${uniformNode.name}_sampler`, uniformNode.node );
 					const sampler = new NodeSampler( `${uniformNode.name}_sampler`, uniformNode.node );
 					sampler.setVisibility( gpuShaderStageLib[ shaderStage ] );
 					sampler.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 
-					bindings.splice( index, 0, sampler, texture );
+					bindings.push( sampler, texture );
 
 
 					uniformGPU = [ sampler, texture ];
 					uniformGPU = [ sampler, texture ];
 
 
 				} else {
 				} else {
 
 
-					bindings.splice( index, 0, texture );
+					bindings.push( texture );
 
 
 					uniformGPU = [ texture ];
 					uniformGPU = [ texture ];
 
 
@@ -335,24 +337,25 @@ class WGSLNodeBuilder extends NodeBuilder {
 				const buffer = new bufferClass( 'NodeBuffer_' + node.id, node.value );
 				const buffer = new bufferClass( 'NodeBuffer_' + node.id, node.value );
 				buffer.setVisibility( gpuShaderStageLib[ shaderStage ] );
 				buffer.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 
-				// add first textures in sequence and group for last
-				const lastBinding = bindings[ bindings.length - 1 ];
-				const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length;
-
-				bindings.splice( index, 0, buffer );
+				bindings.push( buffer );
 
 
 				uniformGPU = buffer;
 				uniformGPU = buffer;
 
 
 			} else {
 			} else {
 
 
-				let uniformsGroup = this.uniformsGroup[ shaderStage ];
+				const group = node.groupNode;
+				const groupName = group.name;
+
+				const uniformsStage = this.uniformGroups[ shaderStage ] || ( this.uniformGroups[ shaderStage ] = {} );
+
+				let uniformsGroup = uniformsStage[ groupName ];
 
 
 				if ( uniformsGroup === undefined ) {
 				if ( uniformsGroup === undefined ) {
 
 
-					uniformsGroup = new UniformsGroup( 'nodeUniforms' );
+					uniformsGroup = new NodeUniformsGroup( groupName, group );
 					uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
 					uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] );
 
 
-					this.uniformsGroup[ shaderStage ] = uniformsGroup;
+					uniformsStage[ groupName ] = uniformsGroup;
 
 
 					bindings.push( uniformsGroup );
 					bindings.push( uniformsGroup );
 
 
@@ -653,7 +656,8 @@ ${ flowData.code }
 
 
 		const bindingSnippets = [];
 		const bindingSnippets = [];
 		const bufferSnippets = [];
 		const bufferSnippets = [];
-		const groupSnippets = [];
+		const structSnippets = [];
+		const uniformGroups = {};
 
 
 		let index = this.bindingsOffset[ shaderStage ];
 		let index = this.bindingsOffset[ shaderStage ];
 
 
@@ -720,16 +724,22 @@ ${ flowData.code }
 			} else {
 			} else {
 
 
 				const vectorType = this.getType( this.getVectorType( uniform.type ) );
 				const vectorType = this.getType( this.getVectorType( uniform.type ) );
+				const groupName = uniform.groupNode.name;
+
+				const group = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = {
+					index: index ++,
+					snippets: []
+				} );
 
 
 				if ( Array.isArray( uniform.value ) === true ) {
 				if ( Array.isArray( uniform.value ) === true ) {
 
 
 					const length = uniform.value.length;
 					const length = uniform.value.length;
 
 
-					groupSnippets.push( `uniform ${vectorType}[ ${length} ] ${uniform.name}` );
+					group.snippets.push( `uniform ${vectorType}[ ${length} ] ${uniform.name}` );
 
 
 				} else {
 				} else {
 
 
-					groupSnippets.push( `\t${uniform.name} : ${ vectorType}` );
+					group.snippets.push( `\t${uniform.name} : ${ vectorType}` );
 
 
 				}
 				}
 
 
@@ -737,15 +747,18 @@ ${ flowData.code }
 
 
 		}
 		}
 
 
-		let code = bindingSnippets.join( '\n' );
-		code += bufferSnippets.join( '\n' );
+		for ( const name in uniformGroups ) {
 
 
-		if ( groupSnippets.length > 0 ) {
+			const group = uniformGroups[ name ];
 
 
-			code += this._getWGSLStructBinding( 'NodeUniforms', groupSnippets.join( ',\n' ), 'uniform', index ++ );
+			structSnippets.push( this._getWGSLStructBinding( name, group.snippets.join( ',\n' ), 'uniform', group.index ) );
 
 
 		}
 		}
 
 
+		let code = bindingSnippets.join( '\n' );
+		code += bufferSnippets.join( '\n' );
+		code += structSnippets.join( '\n' );
+
 		return code;
 		return code;
 
 
 	}
 	}

+ 2 - 2
examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js

@@ -91,7 +91,7 @@ class WebGPUBindingUtils {
 
 
 			} else {
 			} else {
 
 
-				console.error( 'WebGPUBindingUtils: Unsupported binding "${ binding }".' );
+				console.error( `WebGPUBindingUtils: Unsupported binding "${ binding }".` );
 
 
 			}
 			}
 
 
@@ -152,7 +152,7 @@ class WebGPUBindingUtils {
 					const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
 					const usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
 
 
 					const bufferGPU = device.createBuffer( {
 					const bufferGPU = device.createBuffer( {
-						label: 'bindingBuffer',
+						label: 'bindingBuffer_' + binding.name,
 						size: byteLength,
 						size: byteLength,
 						usage: usage
 						usage: usage
 					} );
 					} );