diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 9da2651e18d203..b18f98a117ddb8 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/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 StackNode, stack } from './core/StackNode.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 VaryingNode, varying } from './core/VaryingNode.js'; export { default as OutputStructNode, outputStruct } from './core/OutputStructNode.js'; diff --git a/examples/jsm/nodes/accessors/CameraNode.js b/examples/jsm/nodes/accessors/CameraNode.js index 112eba8777a39b..22de8d5634697f 100644 --- a/examples/jsm/nodes/accessors/CameraNode.js +++ b/examples/jsm/nodes/accessors/CameraNode.js @@ -1,14 +1,22 @@ import Object3DNode from './Object3DNode.js'; import { addNodeClass } from '../core/Node.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'; +//const cameraGroup = sharedUniformGroup( 'camera' ); + class CameraNode extends Object3DNode { constructor( scope = CameraNode.POSITION ) { super( scope ); + this.updateType = NodeUpdateType.RENDER; + + //this._uniformNode.groupNode = cameraGroup; + } getNodeType( builder ) { @@ -35,6 +43,8 @@ class CameraNode extends Object3DNode { const uniformNode = this._uniformNode; const scope = this.scope; + //cameraGroup.needsUpdate = true; + if ( scope === CameraNode.VIEW_MATRIX ) { uniformNode.value = camera.matrixWorldInverse; diff --git a/examples/jsm/nodes/accessors/MaterialReferenceNode.js b/examples/jsm/nodes/accessors/MaterialReferenceNode.js index e519638c9c8ca3..aed8bb268bf698 100644 --- a/examples/jsm/nodes/accessors/MaterialReferenceNode.js +++ b/examples/jsm/nodes/accessors/MaterialReferenceNode.js @@ -1,5 +1,6 @@ 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 { nodeObject } from '../shadernode/ShaderNode.js'; @@ -11,10 +12,18 @@ class MaterialReferenceNode extends ReferenceNode { this.material = material; - this.updateType = NodeUpdateType.RENDER; + //this.updateType = NodeUpdateType.RENDER; } + /*setNodeType( node ) { + + super.setNodeType( node ); + + this.node.groupNode = renderGroup; + + }*/ + updateReference( frame ) { this.reference = this.material !== null ? this.material : frame.material; diff --git a/examples/jsm/nodes/accessors/ModelNode.js b/examples/jsm/nodes/accessors/ModelNode.js index 23694dcde0ef65..33244220ab64f2 100644 --- a/examples/jsm/nodes/accessors/ModelNode.js +++ b/examples/jsm/nodes/accessors/ModelNode.js @@ -23,7 +23,7 @@ class ModelNode extends Object3DNode { export default ModelNode; 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 modelWorldMatrix = nodeImmutable( ModelNode, ModelNode.WORLD_MATRIX ); export const modelPosition = nodeImmutable( ModelNode, ModelNode.POSITION ); diff --git a/examples/jsm/nodes/core/NodeBuilder.js b/examples/jsm/nodes/core/NodeBuilder.js index 6c9e495ad2ff75..57fd186718e1dc 100644 --- a/examples/jsm/nodes/core/NodeBuilder.js +++ b/examples/jsm/nodes/core/NodeBuilder.js @@ -22,6 +22,9 @@ import { getCurrentStack, setCurrentStack } from '../shadernode/ShaderNode.js'; import { maxMipLevel } from '../utils/MaxMipLevelNode.js'; import CubeRenderTarget from '../../renderers/common/CubeRenderTarget.js'; +import ChainMap from '../../renderers/common/ChainMap.js'; + +const uniformsGroupCache = new ChainMap(); const typeFromLength = new Map( [ [ 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() { let bindingsArray = this.bindingsArray; @@ -136,7 +174,7 @@ class NodeBuilder { 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 ); } diff --git a/examples/jsm/nodes/core/NodeFrame.js b/examples/jsm/nodes/core/NodeFrame.js index 2834531513a0ce..d7081c8b3c49e4 100644 --- a/examples/jsm/nodes/core/NodeFrame.js +++ b/examples/jsm/nodes/core/NodeFrame.js @@ -47,10 +47,10 @@ class NodeFrame { const updateType = node.getUpdateBeforeType(); const reference = node.updateReference( this ); - const { frameMap, renderMap } = this._getMaps( this.updateBeforeMap, reference ); - if ( updateType === NodeUpdateType.FRAME ) { + const { frameMap } = this._getMaps( this.updateBeforeMap, reference ); + if ( frameMap.get( node ) !== this.frameId ) { frameMap.set( node, this.frameId ); @@ -61,10 +61,11 @@ class NodeFrame { } 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 ); - frameMap.set( node, this.frameId ); node.updateBefore( this ); @@ -83,10 +84,10 @@ class NodeFrame { const updateType = node.getUpdateType(); const reference = node.updateReference( this ); - const { frameMap, renderMap } = this._getMaps( this.updateMap, reference ); - if ( updateType === NodeUpdateType.FRAME ) { + const { frameMap } = this._getMaps( this.updateMap, reference ); + if ( frameMap.get( node ) !== this.frameId ) { frameMap.set( node, this.frameId ); @@ -97,10 +98,11 @@ class NodeFrame { } 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 ); - frameMap.set( node, this.frameId ); node.update( this ); diff --git a/examples/jsm/nodes/core/NodeUniform.js b/examples/jsm/nodes/core/NodeUniform.js index 7733acd8e717eb..cfb37e23f5a77e 100644 --- a/examples/jsm/nodes/core/NodeUniform.js +++ b/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; diff --git a/examples/jsm/nodes/core/UniformGroup.js b/examples/jsm/nodes/core/UniformGroup.js new file mode 100644 index 00000000000000..aad6b65f44bba9 --- /dev/null +++ b/examples/jsm/nodes/core/UniformGroup.js @@ -0,0 +1,13 @@ +class UniformGroup { + + constructor( name ) { + + this.name = name; + + this.isUniformGroup = true; + + } + +} + +export default UniformGroup; diff --git a/examples/jsm/nodes/core/UniformGroupNode.js b/examples/jsm/nodes/core/UniformGroupNode.js new file mode 100644 index 00000000000000..f35a002bd1fa75 --- /dev/null +++ b/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 ); diff --git a/examples/jsm/nodes/core/UniformNode.js b/examples/jsm/nodes/core/UniformNode.js index 76f01739531e9e..5e0bfffa979dd9 100644 --- a/examples/jsm/nodes/core/UniformNode.js +++ b/examples/jsm/nodes/core/UniformNode.js @@ -1,4 +1,5 @@ import InputNode from './InputNode.js'; +import { objectGroup } from './UniformGroupNode.js'; import { addNodeClass } from './Node.js'; import { nodeObject, getConstNodeType } from '../shadernode/ShaderNode.js'; @@ -10,6 +11,22 @@ class UniformNode extends InputNode { this.isUniformNode = true; + this.groupNode = objectGroup; + + } + + setGroup( group ) { + + this.groupNode = group; + + return this; + + } + + getGroup() { + + return this.groupNode; + } getUniformHash( builder ) { diff --git a/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js b/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js index 870a98bdb2b00d..5babc5e2f042cb 100644 --- a/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js +++ b/examples/jsm/nodes/materials/MeshBasicNodeMaterial.js @@ -13,6 +13,7 @@ class MeshBasicNodeMaterial extends NodeMaterial { this.isMeshBasicNodeMaterial = true; this.lights = false; + //this.normals = false; @TODO: normals usage by context this.setDefaultValues( defaultValues ); diff --git a/examples/jsm/renderers/common/Bindings.js b/examples/jsm/renderers/common/Bindings.js index 53dc86e070b493..6e4f1431e9322e 100644 --- a/examples/jsm/renderers/common/Bindings.js +++ b/examples/jsm/renderers/common/Bindings.js @@ -16,8 +16,6 @@ class Bindings extends DataMap { this.pipelines.bindings = this; // assign bindings to pipelines - this.updateMap = new WeakMap(); - } getForRender( renderObject ) { @@ -100,24 +98,25 @@ class Bindings extends DataMap { const { backend } = this; - const updateMap = this.updateMap; - const callId = this.info.calls; - let needsBindingsUpdate = false; // iterate over all bindings and check if buffer updates or a new binding group is required 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 ) { - const needsUpdate = binding.update(); + const updated = binding.update(); - if ( needsUpdate ) { + if ( updated ) { backend.updateBinding( binding ); @@ -127,9 +126,9 @@ class Bindings extends DataMap { if ( binding.needsBindingsUpdate ) needsBindingsUpdate = true; - const needsUpdate = binding.update(); + const updated = binding.update(); - if ( needsUpdate ) { + if ( updated ) { this.textures.updateTexture( binding.texture ); @@ -137,8 +136,6 @@ class Bindings extends DataMap { } - updateMap.set( binding, callId ); - } if ( needsBindingsUpdate === true ) { @@ -151,14 +148,6 @@ class Bindings extends DataMap { } - dispose() { - - super.dispose(); - - this.updateMap = new WeakMap(); - - } - } export default Bindings; diff --git a/examples/jsm/renderers/common/ChainMap.js b/examples/jsm/renderers/common/ChainMap.js index 6b5c18e5cecc4a..3e7648e08d2126 100644 --- a/examples/jsm/renderers/common/ChainMap.js +++ b/examples/jsm/renderers/common/ChainMap.js @@ -12,7 +12,7 @@ export default class ChainMap { 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 ] ); @@ -36,7 +36,7 @@ export default class ChainMap { 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 ]; @@ -62,7 +62,7 @@ export default class ChainMap { 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 ] ); diff --git a/examples/jsm/renderers/common/Renderer.js b/examples/jsm/renderers/common/Renderer.js index c2479046b49eb2..4a13326bf92543 100644 --- a/examples/jsm/renderers/common/Renderer.js +++ b/examples/jsm/renderers/common/Renderer.js @@ -9,6 +9,7 @@ import RenderLists from './RenderLists.js'; import RenderContexts from './RenderContexts.js'; import Textures from './Textures.js'; import Background from './Background.js'; +import UniformGroupNode from '../../nodes/core/UniformGroupNode.js'; import Nodes from './nodes/Nodes.js'; import { Scene, Frustum, Matrix4, Vector2, Vector3, Vector4, Color, DoubleSide, BackSide, FrontSide, SRGBColorSpace, NoToneMapping } from 'three'; @@ -196,11 +197,11 @@ class Renderer { // - nodeFrame.renderId ++; - this.info.calls ++; this.info.render.calls ++; + nodeFrame.renderId = this.info.calls; + // const coordinateSystem = this.coordinateSystem; @@ -660,11 +661,17 @@ class Renderer { if ( this._initialized === false ) await this.init(); + const nodeFrame = this._nodes.nodeFrame; + + const previousRenderId = nodeFrame.renderId; + // this.info.calls ++; this.info.compute.calls ++; + nodeFrame.renderId = this.info.calls; + // const backend = this.backend; @@ -717,6 +724,10 @@ class Renderer { backend.finishCompute( computeNodes ); + // + + nodeFrame.renderId = previousRenderId; + } hasFeature( name ) { diff --git a/examples/jsm/renderers/common/nodes/NodeBuilderState.js b/examples/jsm/renderers/common/nodes/NodeBuilderState.js index 296e23ef231142..688796168c9223 100644 --- a/examples/jsm/renderers/common/nodes/NodeBuilderState.js +++ b/examples/jsm/renderers/common/nodes/NodeBuilderState.js @@ -20,9 +20,17 @@ class NodeBuilderState { 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 ); } diff --git a/examples/jsm/renderers/common/nodes/NodeUniformsGroup.js b/examples/jsm/renderers/common/nodes/NodeUniformsGroup.js new file mode 100644 index 00000000000000..e44a6f93a9c6a8 --- /dev/null +++ b/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; diff --git a/examples/jsm/renderers/common/nodes/Nodes.js b/examples/jsm/renderers/common/nodes/Nodes.js index d3cbb50b03227b..68046076c79021 100644 --- a/examples/jsm/renderers/common/nodes/Nodes.js +++ b/examples/jsm/renderers/common/nodes/Nodes.js @@ -2,7 +2,7 @@ import DataMap from '../DataMap.js'; import ChainMap from '../ChainMap.js'; import NodeBuilderState from './NodeBuilderState.js'; 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 { @@ -15,6 +15,73 @@ class Nodes extends DataMap { this.nodeFrame = new NodeFrame(); this.nodeBuilderCache = new Map(); 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; } diff --git a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js index 288c39757187c6..9e0bfa455d65a4 100644 --- a/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js +++ b/examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js @@ -1,7 +1,8 @@ import { MathNode, GLSLNodeParser, NodeBuilder, NodeMaterial, FunctionNode } from '../../../nodes/Nodes.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'; const glslMethods = { @@ -25,7 +26,7 @@ class GLSLNodeBuilder extends NodeBuilder { super( object, renderer, new GLSLNodeParser(), scene ); - this.uniformsGroup = {}; + this.uniformGroups = {}; } @@ -138,7 +139,7 @@ ${ flowData.code } const uniforms = this.uniforms[ shaderStage ]; const bindingSnippets = []; - const groupSnippets = []; + const uniformGroups = {}; for ( const uniform of uniforms ) { @@ -192,6 +193,9 @@ ${ flowData.code } snippet = '\t' + snippet; + const groupName = uniform.groupNode.name; + const groupSnippets = uniformGroups[ groupName ] || ( uniformGroups[ groupName ] = [] ); + groupSnippets.push( snippet ); } else { @@ -206,9 +210,11 @@ ${ flowData.code } 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 { - 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 ) { - uniformsGroup = new UniformsGroup( shaderStage + 'NodeUniforms' ); + uniformsGroup = new NodeUniformsGroup( shaderStage + '_' + groupName, group ); //uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); - this.uniformsGroup[ shaderStage ] = uniformsGroup; + uniformsStage[ groupName ] = uniformsGroup; this.bindings[ shaderStage ].push( uniformsGroup ); diff --git a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js index d8e703572ef399..87a347fcfc0645 100644 --- a/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -1,6 +1,6 @@ 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 { NodeSampledTexture, NodeSampledCubeTexture } from '../../common/nodes/NodeSampledTexture.js'; @@ -98,7 +98,7 @@ class WGSLNodeBuilder extends NodeBuilder { super( object, renderer, new WGSLNodeParser(), scene ); - this.uniformsGroup = {}; + this.uniformGroups = {}; this.builtins = { vertex: new Map(), @@ -266,11 +266,11 @@ class WGSLNodeBuilder extends NodeBuilder { } else if ( type === 'buffer' || type === 'storageBuffer' ) { - return `NodeBuffer_${node.node.id}.${name}`; + return `NodeBuffer_${ node.id }.${name}`; } 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 ) { const uniformNode = super.getUniformFromNode( node, type, shaderStage, name ); @@ -308,22 +314,18 @@ class WGSLNodeBuilder extends NodeBuilder { texture.store = node.isStoreTextureNode === true; 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 ) { const sampler = new NodeSampler( `${uniformNode.name}_sampler`, uniformNode.node ); sampler.setVisibility( gpuShaderStageLib[ shaderStage ] ); - bindings.splice( index, 0, sampler, texture ); + bindings.push( sampler, texture ); uniformGPU = [ sampler, texture ]; } else { - bindings.splice( index, 0, texture ); + bindings.push( texture ); uniformGPU = [ texture ]; @@ -335,24 +337,25 @@ class WGSLNodeBuilder extends NodeBuilder { const buffer = new bufferClass( 'NodeBuffer_' + node.id, node.value ); 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; } 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 ) { - uniformsGroup = new UniformsGroup( 'nodeUniforms' ); + uniformsGroup = new NodeUniformsGroup( groupName, group ); uniformsGroup.setVisibility( gpuShaderStageLib[ shaderStage ] ); - this.uniformsGroup[ shaderStage ] = uniformsGroup; + uniformsStage[ groupName ] = uniformsGroup; bindings.push( uniformsGroup ); @@ -653,7 +656,8 @@ ${ flowData.code } const bindingSnippets = []; const bufferSnippets = []; - const groupSnippets = []; + const structSnippets = []; + const uniformGroups = {}; let index = this.bindingsOffset[ shaderStage ]; @@ -720,16 +724,22 @@ ${ flowData.code } } else { 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 ) { const length = uniform.value.length; - groupSnippets.push( `uniform ${vectorType}[ ${length} ] ${uniform.name}` ); + group.snippets.push( `uniform ${vectorType}[ ${length} ] ${uniform.name}` ); } 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; } diff --git a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js index 1fa6a171375a3a..fb9639a5b372fa 100644 --- a/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js +++ b/examples/jsm/renderers/webgpu/utils/WebGPUBindingUtils.js @@ -91,7 +91,7 @@ class WebGPUBindingUtils { } 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 bufferGPU = device.createBuffer( { - label: 'bindingBuffer', + label: 'bindingBuffer_' + binding.name, size: byteLength, usage: usage } );