浏览代码

Reuse NodeBuilder (#26729)

* Reuse NodeBuilder

* UserDataNode: Fix reference

* Add NodeBuilderState

* revision

* Rename _cacheMaterial -> _propertyCache

* revision

* cleanup
sunag 1 年之前
父节点
当前提交
366e25eba1

+ 3 - 2
examples/jsm/nodes/accessors/ExtendedMaterialNode.js

@@ -40,10 +40,11 @@ class ExtendedMaterialNode extends MaterialNode {
 
 
 			if ( material.normalMap ) {
 			if ( material.normalMap ) {
 
 
-				node = normalMap( this.getTexture( builder, 'normalMap' ), materialReference( 'normalScale', 'vec2' ) );
+				node = normalMap( this.getTexture( 'normalMap' ), materialReference( 'normalScale', 'vec2' ) );
 
 
 			} else if ( material.bumpMap ) {
 			} else if ( material.bumpMap ) {
 
 
+				// @TODO: Replace material.bumpMap to this.getTexture( 'bumpMap' )
 				node = bumpMap( material.bumpMap, materialReference( 'bumpScale', 'float' ) );
 				node = bumpMap( material.bumpMap, materialReference( 'bumpScale', 'float' ) );
 
 
 			} else {
 			} else {
@@ -54,7 +55,7 @@ class ExtendedMaterialNode extends MaterialNode {
 
 
 		} else if ( scope === ExtendedMaterialNode.CLEARCOAT_NORMAL ) {
 		} else if ( scope === ExtendedMaterialNode.CLEARCOAT_NORMAL ) {
 
 
-			node = material.clearcoatNormalMap ? normalMap( this.getTexture( builder, 'clearcoatNormalMap' ), materialReference( 'clearcoatNormalScale', 'vec2' ) ) : normalView;
+			node = material.clearcoatNormalMap ? normalMap( this.getTexture( 'clearcoatNormalMap' ), materialReference( 'clearcoatNormalScale', 'vec2' ) ) : normalView;
 
 
 		}
 		}
 
 

+ 2 - 2
examples/jsm/nodes/accessors/LineMaterialNode.js

@@ -4,9 +4,9 @@ import { nodeImmutable } from '../shadernode/ShaderNode.js';
 
 
 class LineMaterialNode extends MaterialNode {
 class LineMaterialNode extends MaterialNode {
 
 
-	construct( builder ) {
+	construct( /*builder*/ ) {
 
 
-		return this.getFloat( builder, this.scope );
+		return this.getFloat( this.scope );
 
 
 	}
 	}
 
 

+ 32 - 44
examples/jsm/nodes/accessors/MaterialNode.js

@@ -3,7 +3,7 @@ import { reference } from './ReferenceNode.js';
 import { materialReference } from './MaterialReferenceNode.js';
 import { materialReference } from './MaterialReferenceNode.js';
 import { nodeImmutable, float } from '../shadernode/ShaderNode.js';
 import { nodeImmutable, float } from '../shadernode/ShaderNode.js';
 
 
-const cache = new WeakMap();
+const _propertyCache = new Map();
 
 
 class MaterialNode extends Node {
 class MaterialNode extends Node {
 
 
@@ -15,27 +15,15 @@ class MaterialNode extends Node {
 
 
 	}
 	}
 
 
-	getCache( builder, property, type ) {
+	getCache( property, type ) {
 
 
-		const material = builder.context.material;
-
-		let cacheMaterial = cache.get( material );
-
-		if ( cacheMaterial === undefined ) {
-
-			cacheMaterial = {};
-
-			cache.set( material, cacheMaterial );
-
-		}
-
-		let node = cacheMaterial[ property ];
+		let node = _propertyCache.get( property );
 
 
 		if ( node === undefined ) {
 		if ( node === undefined ) {
 
 
 			node = materialReference( property, type );
 			node = materialReference( property, type );
 
 
-			cacheMaterial[ property ] = node;
+			_propertyCache.set( property, node );
 
 
 		}
 		}
 
 
@@ -43,21 +31,21 @@ class MaterialNode extends Node {
 
 
 	}
 	}
 
 
-	getFloat( builder, property ) {
+	getFloat( property ) {
 
 
-		return this.getCache( builder, property, 'float' );
+		return this.getCache( property, 'float' );
 
 
 	}
 	}
 
 
-	getColor( builder, property ) {
+	getColor( property ) {
 
 
-		return this.getCache( builder, property, 'color' );
+		return this.getCache( property, 'color' );
 
 
 	}
 	}
 
 
-	getTexture( builder, property ) {
+	getTexture( property ) {
 
 
-		return this.getCache( builder, property, 'texture' );
+		return this.getCache( property, 'texture' );
 
 
 	}
 	}
 
 
@@ -70,19 +58,19 @@ class MaterialNode extends Node {
 
 
 		if ( scope === MaterialNode.ALPHA_TEST || scope === MaterialNode.SHININESS || scope === MaterialNode.REFLECTIVITY || scope === MaterialNode.ROTATION || scope === MaterialNode.IRIDESCENCE || scope === MaterialNode.IRIDESCENCE_IOR ) {
 		if ( scope === MaterialNode.ALPHA_TEST || scope === MaterialNode.SHININESS || scope === MaterialNode.REFLECTIVITY || scope === MaterialNode.ROTATION || scope === MaterialNode.IRIDESCENCE || scope === MaterialNode.IRIDESCENCE_IOR ) {
 
 
-			node = this.getFloat( builder, scope );
+			node = this.getFloat( scope );
 
 
 		} else if ( scope === MaterialNode.SPECULAR_COLOR ) {
 		} else if ( scope === MaterialNode.SPECULAR_COLOR ) {
 
 
-			node = this.getColor( builder, 'specular' );
+			node = this.getColor( 'specular' );
 
 
 		} else if ( scope === MaterialNode.COLOR ) {
 		} else if ( scope === MaterialNode.COLOR ) {
 
 
-			const colorNode = this.getColor( builder, 'color' );
+			const colorNode = this.getColor( 'color' );
 
 
 			if ( material.map && material.map.isTexture === true ) {
 			if ( material.map && material.map.isTexture === true ) {
 
 
-				node = colorNode.mul( this.getTexture( builder, 'map' ) );
+				node = colorNode.mul( this.getTexture( 'map' ) );
 
 
 			} else {
 			} else {
 
 
@@ -92,11 +80,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.OPACITY ) {
 		} else if ( scope === MaterialNode.OPACITY ) {
 
 
-			const opacityNode = this.getFloat( builder, 'opacity' );
+			const opacityNode = this.getFloat( 'opacity' );
 
 
 			if ( material.alphaMap && material.alphaMap.isTexture === true ) {
 			if ( material.alphaMap && material.alphaMap.isTexture === true ) {
 
 
-				node = opacityNode.mul( this.getTexture( builder, 'alphaMap' ) );
+				node = opacityNode.mul( this.getTexture( 'alphaMap' ) );
 
 
 			} else {
 			} else {
 
 
@@ -108,7 +96,7 @@ class MaterialNode extends Node {
 
 
 			if ( material.specularMap && material.specularMap.isTexture === true ) {
 			if ( material.specularMap && material.specularMap.isTexture === true ) {
 
 
-				node = this.getTexture( builder, 'specularMap' ).r;
+				node = this.getTexture( 'specularMap' ).r;
 
 
 			} else {
 			} else {
 
 
@@ -118,11 +106,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.ROUGHNESS ) {
 		} else if ( scope === MaterialNode.ROUGHNESS ) {
 
 
-			const roughnessNode = this.getFloat( builder, 'roughness' );
+			const roughnessNode = this.getFloat( 'roughness' );
 
 
 			if ( material.roughnessMap && material.roughnessMap.isTexture === true ) {
 			if ( material.roughnessMap && material.roughnessMap.isTexture === true ) {
 
 
-				node = roughnessNode.mul( this.getTexture( builder, 'roughnessMap' ).g );
+				node = roughnessNode.mul( this.getTexture( 'roughnessMap' ).g );
 
 
 			} else {
 			} else {
 
 
@@ -132,11 +120,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.METALNESS ) {
 		} else if ( scope === MaterialNode.METALNESS ) {
 
 
-			const metalnessNode = this.getFloat( builder, 'metalness' );
+			const metalnessNode = this.getFloat( 'metalness' );
 
 
 			if ( material.metalnessMap && material.metalnessMap.isTexture === true ) {
 			if ( material.metalnessMap && material.metalnessMap.isTexture === true ) {
 
 
-				node = metalnessNode.mul( this.getTexture( builder, 'metalnessMap' ).b );
+				node = metalnessNode.mul( this.getTexture( 'metalnessMap' ).b );
 
 
 			} else {
 			} else {
 
 
@@ -146,11 +134,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.EMISSIVE ) {
 		} else if ( scope === MaterialNode.EMISSIVE ) {
 
 
-			const emissiveNode = this.getColor( builder, 'emissive' );
+			const emissiveNode = this.getColor( 'emissive' );
 
 
 			if ( material.emissiveMap && material.emissiveMap.isTexture === true ) {
 			if ( material.emissiveMap && material.emissiveMap.isTexture === true ) {
 
 
-				node = emissiveNode.mul( this.getTexture( builder, 'emissiveMap' ) );
+				node = emissiveNode.mul( this.getTexture( 'emissiveMap' ) );
 
 
 			} else {
 			} else {
 
 
@@ -160,11 +148,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.CLEARCOAT ) {
 		} else if ( scope === MaterialNode.CLEARCOAT ) {
 
 
-			const clearcoatNode = this.getFloat( builder, 'clearcoat' );
+			const clearcoatNode = this.getFloat( 'clearcoat' );
 
 
 			if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) {
 			if ( material.clearcoatMap && material.clearcoatMap.isTexture === true ) {
 
 
-				node = clearcoatNode.mul( this.getTexture( builder, 'clearcoatMap' ).r );
+				node = clearcoatNode.mul( this.getTexture( 'clearcoatMap' ).r );
 
 
 			} else {
 			} else {
 
 
@@ -174,11 +162,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) {
 		} else if ( scope === MaterialNode.CLEARCOAT_ROUGHNESS ) {
 
 
-			const clearcoatRoughnessNode = this.getFloat( builder, 'clearcoatRoughness' );
+			const clearcoatRoughnessNode = this.getFloat( 'clearcoatRoughness' );
 
 
 			if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) {
 			if ( material.clearcoatRoughnessMap && material.clearcoatRoughnessMap.isTexture === true ) {
 
 
-				node = clearcoatRoughnessNode.mul( this.getTexture( builder, 'clearcoatRoughnessMap' ).r );
+				node = clearcoatRoughnessNode.mul( this.getTexture( 'clearcoatRoughnessMap' ).r );
 
 
 			} else {
 			} else {
 
 
@@ -188,11 +176,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.SHEEN ) {
 		} else if ( scope === MaterialNode.SHEEN ) {
 
 
-			const sheenNode = this.getColor( builder, 'sheenColor' ).mul( this.getFloat( builder, 'sheen' ) ); // Move this mul() to CPU
+			const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU
 
 
 			if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) {
 			if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) {
 
 
-				node = sheenNode.mul( this.getTexture( builder, 'sheenColorMap' ).rgb );
+				node = sheenNode.mul( this.getTexture( 'sheenColorMap' ).rgb );
 
 
 			} else {
 			} else {
 
 
@@ -202,11 +190,11 @@ class MaterialNode extends Node {
 
 
 		} else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) {
 		} else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) {
 
 
-			const sheenRoughnessNode = this.getFloat( builder, 'sheenRoughness' );
+			const sheenRoughnessNode = this.getFloat( 'sheenRoughness' );
 
 
 			if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) {
 			if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) {
 
 
-				node = sheenRoughnessNode.mul( this.getTexture( builder, 'sheenRoughnessMap' ).a );
+				node = sheenRoughnessNode.mul( this.getTexture( 'sheenRoughnessMap' ).a );
 
 
 			} else {
 			} else {
 
 
@@ -224,7 +212,7 @@ class MaterialNode extends Node {
 
 
 				const iridescenceThicknessMinimum = reference( 0, 'float', material.iridescenceThicknessRange );
 				const iridescenceThicknessMinimum = reference( 0, 'float', material.iridescenceThicknessRange );
 
 
-				node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( builder, 'iridescenceThicknessMap' ).g ).add( iridescenceThicknessMinimum );
+				node = iridescenceThicknessMaximum.sub( iridescenceThicknessMinimum ).mul( this.getTexture( 'iridescenceThicknessMap' ).g ).add( iridescenceThicknessMinimum );
 
 
 			} else {
 			} else {
 
 

+ 8 - 8
examples/jsm/nodes/accessors/MaterialReferenceNode.js

@@ -15,21 +15,21 @@ class MaterialReferenceNode extends ReferenceNode {
 
 
 	}
 	}
 
 
-	construct( builder ) {
+	updateReference( frame ) {
 
 
-		const material = this.material !== null ? this.material : builder.material;
+		this.reference = this.material !== null ? this.material : frame.material;
 
 
-		this.node.value = material[ this.property ];
-
-		return super.construct( builder );
+		return this.reference;
 
 
 	}
 	}
 
 
-	update( frame ) {
+	construct( builder ) {
 
 
-		this.object = this.material !== null ? this.material : frame.material;
+		const material = this.material !== null ? this.material : builder.material;
 
 
-		super.update( frame );
+		this.node.value = material[ this.property ];
+
+		return super.construct( builder );
 
 
 	}
 	}
 
 

+ 11 - 5
examples/jsm/nodes/accessors/ReferenceNode.js

@@ -15,6 +15,7 @@ class ReferenceNode extends Node {
 		this.uniformType = uniformType;
 		this.uniformType = uniformType;
 
 
 		this.object = object;
 		this.object = object;
+		this.reference = null;
 
 
 		this.node = null;
 		this.node = null;
 
 
@@ -24,6 +25,14 @@ class ReferenceNode extends Node {
 
 
 	}
 	}
 
 
+	updateReference( frame ) {
+
+		this.reference = this.object !== null ? this.object : frame.object;
+
+		return this.reference;
+
+	}
+
 	setNodeType( uniformType ) {
 	setNodeType( uniformType ) {
 
 
 		let node = null;
 		let node = null;
@@ -48,12 +57,9 @@ class ReferenceNode extends Node {
 
 
 	}
 	}
 
 
-	update( frame ) {
-
-		const object = this.object !== null ? this.object : frame.object;
-		const property = this.property;
+	update( /*frame*/ ) {
 
 
-		this.node.value = object[ property ];
+		this.node.value = this.reference[ this.property ];
 
 
 	}
 	}
 
 

+ 7 - 1
examples/jsm/nodes/accessors/TextureNode.js

@@ -53,6 +53,12 @@ class TextureNode extends UniformNode {
 
 
 	}
 	}
 
 
+	updateReference( /*frame*/ ) {
+
+		return this.value;
+
+	}
+
 	getTextureMatrix( uvNode ) {
 	getTextureMatrix( uvNode ) {
 
 
 		const texture = this.value;
 		const texture = this.value;
@@ -86,7 +92,7 @@ class TextureNode extends UniformNode {
 
 
 		if ( ! uvNode ) uvNode = this.getDefaultUV();
 		if ( ! uvNode ) uvNode = this.getDefaultUV();
 
 
-		if ( this.updateMatrix ) {
+		if ( this.updateMatrix === true ) {
 
 
 			uvNode = this.getTextureMatrix( uvNode );
 			uvNode = this.getTextureMatrix( uvNode );
 
 

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

@@ -14,7 +14,7 @@ class UserDataNode extends ReferenceNode {
 
 
 	update( frame ) {
 	update( frame ) {
 
 
-		this.object = this.userData !== null ? this.userData : frame.object.userData;
+		this.reference = this.userData !== null ? this.userData : frame.object.userData;
 
 
 		super.update( frame );
 		super.update( frame );
 
 

+ 8 - 2
examples/jsm/nodes/core/Node.js

@@ -40,6 +40,12 @@ class Node extends EventDispatcher {
 
 
 	}
 	}
 
 
+	updateReference() {
+
+		return this;
+
+	}
+
 	isGlobal( /*builder*/ ) {
 	isGlobal( /*builder*/ ) {
 
 
 		return false;
 		return false;
@@ -119,7 +125,7 @@ class Node extends EventDispatcher {
 
 
 	}
 	}
 
 
-	getReference( builder ) {
+	getShared( builder ) {
 
 
 		const hash = this.getHash( builder );
 		const hash = this.getHash( builder );
 		const nodeFromHash = builder.getNodeFromHash( hash );
 		const nodeFromHash = builder.getNodeFromHash( hash );
@@ -194,7 +200,7 @@ class Node extends EventDispatcher {
 
 
 	build( builder, output = null ) {
 	build( builder, output = null ) {
 
 
-		const refNode = this.getReference( builder );
+		const refNode = this.getShared( builder );
 
 
 		if ( this !== refNode ) {
 		if ( this !== refNode ) {
 
 

+ 0 - 14
examples/jsm/nodes/core/NodeBuilder.js

@@ -124,20 +124,6 @@ class NodeBuilder {
 
 
 	}
 	}
 
 
-	createBindings() {
-
-		const bindingsArray = [];
-
-		for ( const binding of this.getBindings() ) {
-
-			bindingsArray.push( binding.clone() );
-
-		}
-
-		return bindingsArray;
-
-	}
-
 	getBindings() {
 	getBindings() {
 
 
 		let bindingsArray = this.bindingsArray;
 		let bindingsArray = this.bindingsArray;

+ 37 - 14
examples/jsm/nodes/core/NodeFrame.js

@@ -12,10 +12,8 @@ class NodeFrame {
 
 
 		this.startTime = null;
 		this.startTime = null;
 
 
-		this.frameMap = new WeakMap();
-		this.frameBeforeMap = new WeakMap();
-		this.renderMap = new WeakMap();
-		this.renderBeforeMap = new WeakMap();
+		this.updateMap = new WeakMap();
+		this.updateBeforeMap = new WeakMap();
 
 
 		this.renderer = null;
 		this.renderer = null;
 		this.material = null;
 		this.material = null;
@@ -25,15 +23,37 @@ class NodeFrame {
 
 
 	}
 	}
 
 
+	_getMaps( referenceMap, nodeRef ) {
+
+		let maps = referenceMap.get( nodeRef );
+
+		if ( maps === undefined ) {
+
+			maps = {
+				renderMap: new WeakMap(),
+				frameMap: new WeakMap()
+			};
+
+			referenceMap.set( nodeRef, maps );
+
+		}
+
+		return maps;
+
+	}
+
 	updateBeforeNode( node ) {
 	updateBeforeNode( node ) {
 
 
 		const updateType = node.getUpdateBeforeType();
 		const updateType = node.getUpdateBeforeType();
+		const reference = node.updateReference( this );
+
+		const { frameMap, renderMap } = this._getMaps( this.updateBeforeMap, reference );
 
 
 		if ( updateType === NodeUpdateType.FRAME ) {
 		if ( updateType === NodeUpdateType.FRAME ) {
 
 
-			if ( this.frameBeforeMap.get( node ) !== this.frameId ) {
+			if ( frameMap.get( node ) !== this.frameId ) {
 
 
-				this.frameBeforeMap.set( node, this.frameId );
+				frameMap.set( node, this.frameId );
 
 
 				node.updateBefore( this );
 				node.updateBefore( this );
 
 
@@ -41,10 +61,10 @@ class NodeFrame {
 
 
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 
 
-			if ( this.renderBeforeMap.get( node ) !== this.renderId || this.frameBeforeMap.get( node ) !== this.frameId ) {
+			if ( renderMap.get( node ) !== this.renderId || frameMap.get( node ) !== this.frameId ) {
 
 
-				this.renderBeforeMap.set( node, this.renderId );
-				this.frameBeforeMap.set( node, this.frameId );
+				renderMap.set( node, this.renderId );
+				frameMap.set( node, this.frameId );
 
 
 				node.updateBefore( this );
 				node.updateBefore( this );
 
 
@@ -61,12 +81,15 @@ class NodeFrame {
 	updateNode( node ) {
 	updateNode( node ) {
 
 
 		const updateType = node.getUpdateType();
 		const updateType = node.getUpdateType();
+		const reference = node.updateReference( this );
+
+		const { frameMap, renderMap } = this._getMaps( this.updateMap, reference );
 
 
 		if ( updateType === NodeUpdateType.FRAME ) {
 		if ( updateType === NodeUpdateType.FRAME ) {
 
 
-			if ( this.frameMap.get( node ) !== this.frameId ) {
+			if ( frameMap.get( node ) !== this.frameId ) {
 
 
-				this.frameMap.set( node, this.frameId );
+				frameMap.set( node, this.frameId );
 
 
 				node.update( this );
 				node.update( this );
 
 
@@ -74,10 +97,10 @@ class NodeFrame {
 
 
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 		} else if ( updateType === NodeUpdateType.RENDER ) {
 
 
-			if ( this.renderMap.get( node ) !== this.renderId || this.frameMap.get( node ) !== this.frameId ) {
+			if ( renderMap.get( node ) !== this.renderId || frameMap.get( node ) !== this.frameId ) {
 
 
-				this.renderMap.set( node, this.renderId );
-				this.frameMap.set( node, this.frameId );
+				renderMap.set( node, this.renderId );
+				frameMap.set( node, this.frameId );
 
 
 				node.update( this );
 				node.update( this );
 
 

+ 2 - 2
examples/jsm/renderers/common/Bindings.js

@@ -48,9 +48,9 @@ class Bindings extends DataMap {
 
 
 		if ( data.bindings === undefined ) {
 		if ( data.bindings === undefined ) {
 
 
-			const nodeBuilder = this.nodes.getForCompute( computeNode );
+			const nodeBuilderState = this.nodes.getForCompute( computeNode );
 
 
-			const bindings = nodeBuilder.getBindings();
+			const bindings = nodeBuilderState.bindings;
 
 
 			data.bindings = bindings;
 			data.bindings = bindings;
 
 

+ 7 - 7
examples/jsm/renderers/common/Pipelines.js

@@ -109,31 +109,31 @@ class Pipelines extends DataMap {
 
 
 			// get shader
 			// get shader
 
 
-			const nodeBuilder = this.nodes.getForRender( renderObject );
+			const nodeBuilderState = renderObject.getNodeBuilderState();
 
 
 			// programmable stages
 			// programmable stages
 
 
-			let stageVertex = this.programs.vertex.get( nodeBuilder.vertexShader );
+			let stageVertex = this.programs.vertex.get( nodeBuilderState.vertexShader );
 
 
 			if ( stageVertex === undefined ) {
 			if ( stageVertex === undefined ) {
 
 
 				if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram );
 				if ( previousPipeline && previousPipeline.vertexProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.vertexProgram );
 
 
-				stageVertex = new ProgrammableStage( nodeBuilder.vertexShader, 'vertex' );
-				this.programs.vertex.set( nodeBuilder.vertexShader, stageVertex );
+				stageVertex = new ProgrammableStage( nodeBuilderState.vertexShader, 'vertex' );
+				this.programs.vertex.set( nodeBuilderState.vertexShader, stageVertex );
 
 
 				backend.createProgram( stageVertex );
 				backend.createProgram( stageVertex );
 
 
 			}
 			}
 
 
-			let stageFragment = this.programs.fragment.get( nodeBuilder.fragmentShader );
+			let stageFragment = this.programs.fragment.get( nodeBuilderState.fragmentShader );
 
 
 			if ( stageFragment === undefined ) {
 			if ( stageFragment === undefined ) {
 
 
 				if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram );
 				if ( previousPipeline && previousPipeline.fragmentProgram.usedTimes === 0 ) this._releaseProgram( previousPipeline.fragmentProgram );
 
 
-				stageFragment = new ProgrammableStage( nodeBuilder.fragmentShader, 'fragment' );
-				this.programs.fragment.set( nodeBuilder.fragmentShader, stageFragment );
+				stageFragment = new ProgrammableStage( nodeBuilderState.fragmentShader, 'fragment' );
+				this.programs.fragment.set( nodeBuilderState.fragmentShader, stageFragment );
 
 
 				backend.createProgram( stageFragment );
 				backend.createProgram( stageFragment );
 
 

+ 40 - 16
examples/jsm/renderers/common/RenderObject.js

@@ -18,15 +18,16 @@ export default class RenderObject {
 		this.context = renderContext;
 		this.context = renderContext;
 
 
 		this.geometry = object.geometry;
 		this.geometry = object.geometry;
+		this.version = material.version;
 
 
 		this.attributes = null;
 		this.attributes = null;
 		this.pipeline = null;
 		this.pipeline = null;
 		this.vertexBuffers = null;
 		this.vertexBuffers = null;
 
 
-		this._nodeBuilder = null;
+		this.initialCacheKey = this.getCacheKey();
+
+		this._nodeBuilderState = null;
 		this._bindings = null;
 		this._bindings = null;
-		this._materialVersion = - 1;
-		this._materialCacheKey = '';
 
 
 		this.onDispose = null;
 		this.onDispose = null;
 
 
@@ -42,15 +43,15 @@ export default class RenderObject {
 
 
 	}
 	}
 
 
-	getNodeBuilder() {
+	getNodeBuilderState() {
 
 
-		return this._nodeBuilder || ( this._nodeBuilder = this._nodes.getForRender( this ) );
+		return this._nodeBuilderState || ( this._nodeBuilderState = this._nodes.getForRender( this ) );
 
 
 	}
 	}
 
 
 	getBindings() {
 	getBindings() {
 
 
-		return this._bindings || ( this._bindings = this.getNodeBuilder().createBindings() );
+		return this._bindings || ( this._bindings = this.getNodeBuilderState().createBindings() );
 
 
 	}
 	}
 
 
@@ -70,7 +71,7 @@ export default class RenderObject {
 
 
 		if ( this.attributes !== null ) return this.attributes;
 		if ( this.attributes !== null ) return this.attributes;
 
 
-		const nodeAttributes = this.getNodeBuilder().getAttributesArray();
+		const nodeAttributes = this.getNodeBuilderState().nodeAttributes;
 		const geometry = this.geometry;
 		const geometry = this.geometry;
 
 
 		const attributes = [];
 		const attributes = [];
@@ -102,23 +103,46 @@ export default class RenderObject {
 
 
 	}
 	}
 
 
-	getCacheKey() {
+	getMaterialCacheKey() {
+
+		const material = this.material;
+
+		let cacheKey = material.customProgramCacheKey();
+
+		for ( const property in material ) {
+
+			if ( /^(is[A-Z])|^(visible|version|uuid|name|opacity|userData)$/.test( property ) ) continue;
+
+			let value = material[ property ];
+
+			if ( value !== null ) {
+
+				const type = typeof value;
 
 
-		const { material, scene, lightsNode } = this;
+				if ( type === 'number' ) value = value !== 0 ? '1' : '0'; // Convert to on/off, important for clearcoat, transmission, etc
+				else if ( type === 'object' ) value = '{}';
 
 
-		if ( material.version !== this._materialVersion ) {
+			}
 
 
-			this._materialVersion = material.version;
-			this._materialCacheKey = material.customProgramCacheKey();
+			cacheKey += /*property + ':' +*/ value + ',';
 
 
 		}
 		}
 
 
-		const cacheKey = [];
+		return cacheKey;
 
 
-		cacheKey.push( 'material:' + this._materialCacheKey );
-		cacheKey.push( 'nodes:' + this._nodes.getCacheKey( scene, lightsNode ) );
+	}
+
+	getNodesCacheKey() {
+
+		// Environment Nodes Cache Key
+
+		return this._nodes.getCacheKey( this.scene, this.lightsNode );
+
+	}
+
+	getCacheKey() {
 
 
-		return '{' + cacheKey.join( ',' ) + '}';
+		return `{material:${ this.getMaterialCacheKey() },nodes:${ this.getNodesCacheKey()}`;
 
 
 	}
 	}
 
 

+ 8 - 14
examples/jsm/renderers/common/RenderObjects.js

@@ -1,4 +1,3 @@
-import DataMap from './DataMap.js';
 import ChainMap from './ChainMap.js';
 import ChainMap from './ChainMap.js';
 import RenderObject from './RenderObject.js';
 import RenderObject from './RenderObject.js';
 
 
@@ -14,7 +13,6 @@ class RenderObjects {
 		this.info = info;
 		this.info = info;
 
 
 		this.chainMaps = {};
 		this.chainMaps = {};
-		this.dataMap = new DataMap();
 
 
 	}
 	}
 
 
@@ -33,14 +31,17 @@ class RenderObjects {
 
 
 		} else {
 		} else {
 
 
-			const data = this.dataMap.get( renderObject );
-			const cacheKey = renderObject.getCacheKey();
+			if ( renderObject.version !== material.version ) {
 
 
-			if ( data.cacheKey !== cacheKey ) {
+				renderObject.version = material.version;
 
 
-				renderObject.dispose();
+				if ( renderObject.initialCacheKey !== renderObject.getCacheKey() ) {
 
 
-				renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, passId );
+					renderObject.dispose();
+
+					renderObject = this.get( object, material, scene, camera, lightsNode, renderContext, passId );
+
+				}
 
 
 			}
 			}
 
 
@@ -59,24 +60,17 @@ class RenderObjects {
 	dispose() {
 	dispose() {
 
 
 		this.chainMaps = {};
 		this.chainMaps = {};
-		this.dataMap = new DataMap();
 
 
 	}
 	}
 
 
 	createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, passId ) {
 	createRenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext, passId ) {
 
 
 		const chainMap = this.getChainMap( passId );
 		const chainMap = this.getChainMap( passId );
-		const dataMap = this.dataMap;
 
 
 		const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext );
 		const renderObject = new RenderObject( nodes, geometries, renderer, object, material, scene, camera, lightsNode, renderContext );
 
 
-		const data = dataMap.get( renderObject );
-		data.cacheKey = renderObject.getCacheKey();
-
 		renderObject.onDispose = () => {
 		renderObject.onDispose = () => {
 
 
-			dataMap.delete( renderObject );
-
 			this.pipelines.delete( renderObject );
 			this.pipelines.delete( renderObject );
 			this.bindings.delete( renderObject );
 			this.bindings.delete( renderObject );
 			this.nodes.delete( renderObject );
 			this.nodes.delete( renderObject );

+ 4 - 2
examples/jsm/renderers/common/SampledTexture.js

@@ -28,9 +28,11 @@ class SampledTexture extends Binding {
 
 
 	update() {
 	update() {
 
 
-		if ( this.version !== this.texture.version ) {
+		const { texture, version } = this;
+
+		if ( version !== texture.version ) {
 
 
-			this.version = this.texture.version;
+			this.version = texture.version;
 
 
 			return true;
 			return true;
 
 

+ 35 - 0
examples/jsm/renderers/common/nodes/NodeBuilderState.js

@@ -0,0 +1,35 @@
+class NodeBuilderState {
+
+	constructor( vertexShader, fragmentShader, computeShader, nodeAttributes, bindings, updateNodes, updateBeforeNodes ) {
+
+		this.vertexShader = vertexShader;
+		this.fragmentShader = fragmentShader;
+		this.computeShader = computeShader;
+
+		this.nodeAttributes = nodeAttributes;
+		this.bindings = bindings;
+
+		this.updateNodes = updateNodes;
+		this.updateBeforeNodes = updateBeforeNodes;
+
+		this.usedTimes = 0;
+
+	}
+
+	createBindings() {
+
+		const bindingsArray = [];
+
+		for ( const binding of this.bindings ) {
+
+			bindingsArray.push( binding.clone() );
+
+		}
+
+		return bindingsArray;
+
+	}
+
+}
+
+export default NodeBuilderState;

+ 20 - 10
examples/jsm/renderers/common/nodes/NodeSampledTexture.js

@@ -1,4 +1,4 @@
-import { SampledTexture, SampledCubeTexture } from '../SampledTexture.js';
+import { SampledTexture } from '../SampledTexture.js';
 
 
 class NodeSampledTexture extends SampledTexture {
 class NodeSampledTexture extends SampledTexture {
 
 
@@ -10,27 +10,37 @@ class NodeSampledTexture extends SampledTexture {
 
 
 	}
 	}
 
 
-	getTexture() {
+	get needsBindingsUpdate() {
 
 
-		return this.textureNode.value;
+		return this.textureNode.value !== this.texture || super.needsBindingsUpdate;
 
 
 	}
 	}
 
 
-}
+	update() {
 
 
-class NodeSampledCubeTexture extends SampledCubeTexture {
+		const { textureNode } = this;
 
 
-	constructor( name, textureNode ) {
+		if ( this.texture !== textureNode.value ) {
 
 
-		super( name, textureNode ? textureNode.value : null );
+			this.texture = textureNode.value;
 
 
-		this.textureNode = textureNode;
+			return true;
+
+		}
+
+		return super.update();
 
 
 	}
 	}
 
 
-	getTexture() {
+}
+
+class NodeSampledCubeTexture extends NodeSampledTexture {
+
+	constructor( name, textureNode ) {
+
+		super( name, textureNode );
 
 
-		return this.textureNode.value;
+		this.isSampledCubeTexture = true;
 
 
 	}
 	}
 
 

+ 0 - 6
examples/jsm/renderers/common/nodes/NodeSampler.js

@@ -10,12 +10,6 @@ class NodeSampler extends Sampler {
 
 
 	}
 	}
 
 
-	getTexture() {
-
-		return this.textureNode.value;
-
-	}
-
 }
 }
 
 
 export default NodeSampler;
 export default NodeSampler;

+ 50 - 24
examples/jsm/renderers/common/nodes/Nodes.js

@@ -1,5 +1,5 @@
 import DataMap from '../DataMap.js';
 import DataMap from '../DataMap.js';
-import ChainMap from '../ChainMap.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, cubeTexture, texture, rangeFog, densityFog, reference, toneMapping, equirectUV, viewportBottomLeft, normalWorld } from '../../../nodes/Nodes.js';
 
 
@@ -12,15 +12,13 @@ class Nodes extends DataMap {
 		this.renderer = renderer;
 		this.renderer = renderer;
 		this.backend = backend;
 		this.backend = backend;
 		this.nodeFrame = new NodeFrame();
 		this.nodeFrame = new NodeFrame();
-		this.cache = new ChainMap();
+		this.nodeBuilderCache = new Map();
 
 
 	}
 	}
 
 
-	getForRenderChainKey( renderObject ) {
+	getForRenderCacheKey( renderObject ) {
 
 
-		const { object, material, lightsNode, context } = renderObject;
-
-		return [ object.geometry, material, lightsNode, context ];
+		return renderObject.initialCacheKey;
 
 
 	}
 	}
 
 
@@ -28,19 +26,19 @@ class Nodes extends DataMap {
 
 
 		const renderObjectData = this.get( renderObject );
 		const renderObjectData = this.get( renderObject );
 
 
-		let nodeBuilder = renderObjectData.nodeBuilder;
+		let nodeBuilderState = renderObjectData.nodeBuilderState;
 
 
-		if ( nodeBuilder === undefined ) {
+		if ( nodeBuilderState === undefined ) {
 
 
-			const { cache } = this;
+			const { nodeBuilderCache } = this;
 
 
-			const chainKey = this.getForRenderChainKey( renderObject );
+			const cacheKey = this.getForRenderCacheKey( renderObject );
 
 
-			nodeBuilder = cache.get( chainKey );
+			nodeBuilderState = nodeBuilderCache.get( cacheKey );
 
 
-			if ( nodeBuilder === undefined ) {
+			if ( nodeBuilderState === undefined ) {
 
 
-				nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer, renderObject.scene );
+				const nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer, renderObject.scene );
 				nodeBuilder.material = renderObject.material;
 				nodeBuilder.material = renderObject.material;
 				nodeBuilder.lightsNode = renderObject.lightsNode;
 				nodeBuilder.lightsNode = renderObject.lightsNode;
 				nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
 				nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
@@ -48,15 +46,19 @@ class Nodes extends DataMap {
 				nodeBuilder.toneMappingNode = this.getToneMappingNode();
 				nodeBuilder.toneMappingNode = this.getToneMappingNode();
 				nodeBuilder.build();
 				nodeBuilder.build();
 
 
-				cache.set( chainKey, nodeBuilder );
+				nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
+
+				nodeBuilderCache.set( cacheKey, nodeBuilderState );
 
 
 			}
 			}
 
 
-			renderObjectData.nodeBuilder = nodeBuilder;
+			nodeBuilderState.usedTimes ++;
+
+			renderObjectData.nodeBuilderState = nodeBuilderState;
 
 
 		}
 		}
 
 
-		return nodeBuilder;
+		return nodeBuilderState;
 
 
 	}
 	}
 
 
@@ -64,7 +66,14 @@ class Nodes extends DataMap {
 
 
 		if ( object.isRenderObject ) {
 		if ( object.isRenderObject ) {
 
 
-			this.cache.delete( this.getForRenderChainKey( object ) );
+			const nodeBuilderState = this.get( object ).nodeBuilderState;
+			nodeBuilderState.usedTimes --;
+
+			if ( nodeBuilderState.usedTimes === 0 ) {
+
+				this.nodeBuilderCache.delete( this.getForRenderCacheKey( object ) );
+
+			}
 
 
 		}
 		}
 
 
@@ -76,18 +85,34 @@ class Nodes extends DataMap {
 
 
 		const computeData = this.get( computeNode );
 		const computeData = this.get( computeNode );
 
 
-		let nodeBuilder = computeData.nodeBuilder;
+		let nodeBuilderState = computeData.nodeBuilderState;
 
 
-		if ( nodeBuilder === undefined ) {
+		if ( nodeBuilderState === undefined ) {
 
 
-			nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer );
+			const nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer );
 			nodeBuilder.build();
 			nodeBuilder.build();
 
 
-			computeData.nodeBuilder = nodeBuilder;
+			nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
+
+			computeData.nodeBuilderState = nodeBuilder;
 
 
 		}
 		}
 
 
-		return nodeBuilder;
+		return nodeBuilderState;
+
+	}
+
+	_createNodeBuilderState( nodeBuilder ) {
+
+		return new NodeBuilderState(
+			nodeBuilder.vertexShader,
+			nodeBuilder.fragmentShader,
+			nodeBuilder.computeShader,
+			nodeBuilder.getAttributesArray(),
+			nodeBuilder.getBindings(),
+			nodeBuilder.updateNodes,
+			nodeBuilder.updateBeforeNodes
+		);
 
 
 	}
 	}
 
 
@@ -326,7 +351,7 @@ class Nodes extends DataMap {
 	updateBefore( renderObject ) {
 	updateBefore( renderObject ) {
 
 
 		const nodeFrame = this.getNodeFrame( renderObject );
 		const nodeFrame = this.getNodeFrame( renderObject );
-		const nodeBuilder = renderObject.getNodeBuilder();
+		const nodeBuilder = renderObject.getNodeBuilderState();
 
 
 		for ( const node of nodeBuilder.updateBeforeNodes ) {
 		for ( const node of nodeBuilder.updateBeforeNodes ) {
 
 
@@ -341,7 +366,7 @@ class Nodes extends DataMap {
 	updateForRender( renderObject ) {
 	updateForRender( renderObject ) {
 
 
 		const nodeFrame = this.getNodeFrame( renderObject );
 		const nodeFrame = this.getNodeFrame( renderObject );
-		const nodeBuilder = renderObject.getNodeBuilder();
+		const nodeBuilder = renderObject.getNodeBuilderState();
 
 
 		for ( const node of nodeBuilder.updateNodes ) {
 		for ( const node of nodeBuilder.updateNodes ) {
 
 
@@ -356,6 +381,7 @@ class Nodes extends DataMap {
 		super.dispose();
 		super.dispose();
 
 
 		this.nodeFrame = new NodeFrame();
 		this.nodeFrame = new NodeFrame();
+		this.nodeBuilderCache = new Map();
 
 
 	}
 	}