소스 검색

WebGPURenderer: New Cache System (#25750)

* FogNode: Update from construct API.

* webgpu_rtt: update example API

* WebGPURenderer: New Cache System

* WebGPURenderer: Update cacheKey after needsUpdate.

* final revision (1)

* RenderObject pipeline based ( 1/2 )

* RenderObject pipeline based ( 2/2 )

* cleanup

* fix possible overrideMaterial material with .wireframe and cleanup

* Revert "fix possible overrideMaterial material with .wireframe and cleanup"

This reverts commit 7dcd85e8a84eaf4c90ec7b7a9b3827c359addb1f.

* Revert "Revert "fix possible overrideMaterial material with .wireframe and cleanup""

This reverts commit 5370b40c1dddd384cdd9df49a4d1aec60a318336.

* preserve nodes if pipeline is removed

* WebGPURenderObjects: Move get*Node() to WebGPUNodes.
sunag 2 년 전
부모
커밋
8cc9f32a96

+ 5 - 4
examples/jsm/nodes/core/NodeBuilder.js

@@ -32,8 +32,8 @@ class NodeBuilder {
 	constructor( object, renderer, parser ) {
 
 		this.object = object;
-		this.material = object.material || null;
-		this.geometry = object.geometry || null;
+		this.material = object && ( object.material || null );
+		this.geometry = object && ( object.geometry || null );
 		this.renderer = renderer;
 		this.parser = parser;
 
@@ -41,9 +41,10 @@ class NodeBuilder {
 		this.updateNodes = [];
 		this.hashNodes = {};
 
-		this.scene = null;
 		this.lightsNode = null;
+		this.environmentNode = null;
 		this.fogNode = null;
+		this.toneMappingNode = null;
 
 		this.vertexShader = null;
 		this.fragmentShader = null;
@@ -63,7 +64,7 @@ class NodeBuilder {
 
 		this.context = {
 			keywords: new NodeKeywords(),
-			material: object.material,
+			material: this.material,
 			getMIPLevelAlgorithmNode: ( textureNode, levelNode ) => levelNode.mul( maxMipLevel( textureNode ) )
 		};
 

+ 1 - 1
examples/jsm/nodes/fog/FogExp2Node.js

@@ -20,7 +20,7 @@ class FogExp2Node extends FogNode {
 		const depthNode = positionView.z.negate();
 		const densityNode = this.densityNode;
 
-		this.factorNode = densityNode.mul( densityNode, depthNode, depthNode ).negate().exp().oneMinus();
+		return densityNode.mul( densityNode, depthNode, depthNode ).negate().exp().oneMinus();
 
 	}
 

+ 2 - 2
examples/jsm/nodes/fog/FogNode.js

@@ -20,9 +20,9 @@ class FogNode extends Node {
 
 	}
 
-	generate( builder ) {
+	construct() {
 
-		return this.factorNode.build( builder, 'float' );
+		return this.factorNode;
 
 	}
 

+ 1 - 1
examples/jsm/nodes/fog/FogRangeNode.js

@@ -19,7 +19,7 @@ class FogRangeNode extends FogNode {
 
 	construct() {
 
-		this.factorNode = smoothstep( this.nearNode, this.farNode, positionView.z.negate() );
+		return smoothstep( this.nearNode, this.farNode, positionView.z.negate() );
 
 	}
 

+ 7 - 57
examples/jsm/nodes/materials/NodeMaterial.js

@@ -1,4 +1,4 @@
-import { Material, ShaderMaterial, NoToneMapping } from 'three';
+import { Material, ShaderMaterial } from 'three';
 import { getNodeChildren, getCacheKey } from '../core/NodeUtils.js';
 import { attribute } from '../core/AttributeNode.js';
 import { diffuseColor } from '../core/PropertyNode.js';
@@ -8,13 +8,8 @@ import { modelViewProjection } from '../accessors/ModelViewProjectionNode.js';
 import { transformedNormalView } from '../accessors/NormalNode.js';
 import { instance } from '../accessors/InstanceNode.js';
 import { positionLocal } from '../accessors/PositionNode.js';
-import { reference } from '../accessors/ReferenceNode.js';
 import { skinning } from '../accessors/SkinningNode.js';
 import { texture } from '../accessors/TextureNode.js';
-import { cubeTexture } from '../accessors/CubeTextureNode.js';
-import { toneMapping } from '../display/ToneMappingNode.js';
-import { rangeFog } from '../fog/FogRangeNode.js';
-import { densityFog } from '../fog/FogExp2Node.js';
 import { lightsWithoutWrap } from '../lighting/LightsNode.js';
 import AONode from '../lighting/AONode.js';
 import EnvironmentNode from '../lighting/EnvironmentNode.js';
@@ -161,28 +156,7 @@ class NodeMaterial extends ShaderMaterial {
 
 	constructLights( builder ) {
 
-		let lightsNode = this.lightsNode || builder.lightsNode;
-		let envNode = this.envNode || builder.scene.environmentNode;
-
-		if ( envNode === undefined && builder.scene.environment ) {
-
-			const environment = builder.scene.environment;
-
-			if ( environment.isCubeTexture === true ) {
-
-				envNode = cubeTexture( environment );
-
-			} else if ( environment.isTexture === true ) {
-
-				envNode = texture( environment );
-
-			} else {
-
-				console.error( 'NodeMaterial: Unsupported environment configuration.', environment );
-
-			}
-
-		}
+		const envNode = this.envNode || builder.environmentNode;
 
 		const materialLightsNode = [];
 
@@ -198,6 +172,8 @@ class NodeMaterial extends ShaderMaterial {
 
 		}
 
+		let lightsNode = this.lightsNode || builder.lightsNode;
+
 		if ( materialLightsNode.length > 0 ) {
 
 			lightsNode = lightsWithoutWrap( [ ...lightsNode.lightNodes, ...materialLightsNode ] );
@@ -251,15 +227,9 @@ class NodeMaterial extends ShaderMaterial {
 
 		// TONE MAPPING
 
-		let toneMappingNode = renderer.toneMappingNode;
-
-		if ( ! toneMappingNode && renderer.toneMapping !== NoToneMapping ) {
+		const toneMappingNode = builder.toneMappingNode;
 
-			toneMappingNode = toneMapping( renderer.toneMapping, reference( 'toneMappingExposure', 'float', renderer ), outgoingLight );
-
-		}
-
-		if ( toneMappingNode && toneMappingNode.isNode === true ) {
+		if ( toneMappingNode ) {
 
 			outgoingLight = toneMappingNode.context( { color: outgoingLight } );
 
@@ -275,27 +245,7 @@ class NodeMaterial extends ShaderMaterial {
 
 		// FOG
 
-		let fogNode = builder.fogNode;
-
-		if ( ( fogNode && fogNode.isNode !== true ) && builder.scene.fog ) {
-
-			const fog = builder.scene.fog;
-
-			if ( fog.isFogExp2 ) {
-
-				fogNode = densityFog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
-
-			} else if ( fog.isFog ) {
-
-				fogNode = rangeFog( reference( 'color', 'color', fog ), reference( 'near', 'float', fog ), reference( 'far', 'float', fog ) );
-
-			} else {
-
-				console.error( 'NodeMaterial: Unsupported fog configuration.', fog );
-
-			}
-
-		}
+		const fogNode = builder.fogNode;
 
 		if ( fogNode ) outputNode = vec4( fogNode.mixAssign( outputNode.rgb ), outputNode.a );
 

+ 27 - 39
examples/jsm/renderers/webgpu/WebGPUBackground.js

@@ -1,17 +1,19 @@
 import { GPULoadOp, GPUStoreOp } from './constants.js';
-import { Color, Mesh, BoxGeometry, BackSide, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
-import { context, vec2, oneMinus, texture, cubeTexture, transformDirection, positionWorld, modelWorldMatrix, viewportBottomLeft, equirectUV, MeshBasicNodeMaterial } from 'three/nodes';
+import { Color, Mesh, BoxGeometry, BackSide } from 'three';
+import { context, transformDirection, positionWorld, modelWorldMatrix, MeshBasicNodeMaterial } from 'three/nodes';
 
 let _clearAlpha;
 const _clearColor = new Color();
 
 class WebGPUBackground {
 
-	constructor( renderer ) {
+	constructor( renderer, properties ) {
 
 		this.renderer = renderer;
+		this.properties = properties;
 
 		this.boxMesh = null;
+		this.boxMeshNode = null;
 
 		this.forceClear = false;
 
@@ -26,7 +28,7 @@ class WebGPUBackground {
 	update( renderList, scene ) {
 
 		const renderer = this.renderer;
-		const background = ( scene.isScene === true ) ? scene.backgroundNode || scene.background : null;
+		const background = ( scene.isScene === true ) ? scene.backgroundNode || this.properties.get( scene ).backgroundNode || scene.background : null;
 
 		let forceClear = this.forceClear;
 
@@ -45,7 +47,10 @@ class WebGPUBackground {
 			_clearAlpha = 1;
 			forceClear = true;
 
-		} else if ( background.isNode === true || background.isTexture === true ) {
+		} else if ( background.isNode === true ) {
+
+			const sceneProperties = this.properties.get( scene );
+			const backgroundNode = background;
 
 			_clearColor.copy( renderer._clearColor );
 			_clearAlpha = renderer._clearAlpha;
@@ -54,42 +59,13 @@ class WebGPUBackground {
 
 			if ( boxMesh === null ) {
 
-				let node = null;
-
-				if ( background.isCubeTexture === true ) {
-
-					node = cubeTexture( background, transformDirection( positionWorld, modelWorldMatrix ) );
-
-				} else if ( background.isTexture === true ) {
-
-					let nodeUV = null;
-
-					if ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping ) {
-
-						const dirNode = transformDirection( positionWorld, modelWorldMatrix );
-
-						nodeUV = equirectUV( dirNode );
-						nodeUV = vec2( nodeUV.x, oneMinus( nodeUV.y ) );
-
-					} else {
-
-						nodeUV = viewportBottomLeft;
-
-					}
-
-					node = texture( background, nodeUV );
-
-				} else /*if ( background.isNode === true )*/ {
-
-					node = context( background, {
-						// @TODO: Add Texture2D support using node context
-						getUVNode: () => transformDirection( positionWorld, modelWorldMatrix )
-					} );
-
-				}
+				this.boxMeshNode = context( backgroundNode, {
+					// @TODO: Add Texture2D support using node context
+					getUVNode: () => transformDirection( positionWorld, modelWorldMatrix )
+				} );
 
 				const nodeMaterial = new MeshBasicNodeMaterial();
-				nodeMaterial.colorNode = node;
+				nodeMaterial.colorNode = this.boxMeshNode;
 				nodeMaterial.side = BackSide;
 				nodeMaterial.depthTest = false;
 				nodeMaterial.depthWrite = false;
@@ -105,6 +81,18 @@ class WebGPUBackground {
 
 			}
 
+			const backgroundCacheKey = backgroundNode.getCacheKey();
+
+			if ( sceneProperties.backgroundMeshCacheKey !== backgroundCacheKey ) {
+
+				this.boxMeshNode.node = backgroundNode;
+
+				boxMesh.material.needsUpdate = true;
+
+				sceneProperties.backgroundMeshCacheKey = backgroundCacheKey;
+
+			}
+
 			renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null );
 
 		} else {

+ 21 - 18
examples/jsm/renderers/webgpu/WebGPUBindings.js

@@ -17,20 +17,20 @@ class WebGPUBindings {
 
 	}
 
-	get( object ) {
+	get( renderObject ) {
 
-		let data = this.uniformsData.get( object );
+		let data = this.uniformsData.get( renderObject );
 
 		if ( data === undefined ) {
 
 			// each object defines an array of bindings (ubos, textures, samplers etc.)
 
-			const nodeBuilder = this.nodes.get( object );
+			const nodeBuilder = this.nodes.get( renderObject );
 			const bindings = nodeBuilder.getBindings();
 
 			// setup (static) binding layout and (dynamic) binding group
 
-			const pipeline = object.isNode ? this.computePipelines.get( object ) : this.renderPipelines.get( object ).pipeline;
+			const pipeline = this.renderPipelines.get( renderObject ).pipeline;
 
 			const bindLayout = pipeline.getBindGroupLayout( 0 );
 			const bindGroup = this._createBindGroup( bindings, bindLayout );
@@ -41,7 +41,7 @@ class WebGPUBindings {
 				bindings: bindings
 			};
 
-			this.uniformsData.set( object, data );
+			this.uniformsData.set( renderObject, data );
 
 		}
 
@@ -49,25 +49,22 @@ class WebGPUBindings {
 
 	}
 
-	remove( object ) {
-
-		this.uniformsData.delete( object );
-
-	}
+	getForCompute( computeNode ) {
 
-	getForCompute( param ) {
-
-		let data = this.uniformsData.get( param );
+		let data = this.uniformsData.get( computeNode );
 
 		if ( data === undefined ) {
 
-			// bindings are not yet retrieved via node material
+			// each object defines an array of bindings (ubos, textures, samplers etc.)
 
-			const bindings = param.bindings !== undefined ? param.bindings.slice() : [];
+			const nodeBuilder = this.nodes.getForCompute( computeNode );
+			const bindings = nodeBuilder.getBindings();
 
-			const computePipeline = this.computePipelines.get( param );
+			// setup (static) binding layout and (dynamic) binding group
 
-			const bindLayout = computePipeline.getBindGroupLayout( 0 );
+			const pipeline = this.computePipelines.get( computeNode );
+
+			const bindLayout = pipeline.getBindGroupLayout( 0 );
 			const bindGroup = this._createBindGroup( bindings, bindLayout );
 
 			data = {
@@ -76,7 +73,7 @@ class WebGPUBindings {
 				bindings: bindings
 			};
 
-			this.uniformsData.set( param, data );
+			this.uniformsData.set( computeNode, data );
 
 		}
 
@@ -84,6 +81,12 @@ class WebGPUBindings {
 
 	}
 
+	remove( object ) {
+
+		this.uniformsData.delete( object );
+
+	}
+
 	update( object ) {
 
 		const textures = this.textures;

+ 1 - 1
examples/jsm/renderers/webgpu/WebGPUComputePipelines.js

@@ -32,7 +32,7 @@ class WebGPUComputePipelines {
 
 			// get shader
 
-			const nodeBuilder = this.nodes.get( computeNode );
+			const nodeBuilder = this.nodes.getForCompute( computeNode );
 			const computeShader = nodeBuilder.computeShader;
 
 			const shader = {

+ 111 - 82
examples/jsm/renderers/webgpu/WebGPUGeometries.js

@@ -14,169 +14,198 @@ function arrayNeedsUint32( array ) {
 
 }
 
-class WebGPUGeometries {
+function getWireframeVersion( geometry ) {
 
-	constructor( attributes, info ) {
+	return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version;
 
-		this.attributes = attributes;
-		this.info = info;
+}
 
-		this.geometries = new WeakMap();
-		this.wireframeGeometries = new WeakMap();
+function getWireframeIndex( geometry ) {
 
-	}
+	const indices = [];
 
-	has( geometry ) {
+	const geometryIndex = geometry.index;
+	const geometryPosition = geometry.attributes.position;
 
-		return this.geometries.has( geometry );
+	if ( geometryIndex !== null ) {
 
-	}
+		const array = geometryIndex.array;
+
+		for ( let i = 0, l = array.length; i < l; i += 3 ) {
+
+			const a = array[ i + 0 ];
+			const b = array[ i + 1 ];
+			const c = array[ i + 2 ];
 
-	update( geometry, wireframe = false ) {
+			indices.push( a, b, b, c, c, a );
 
-		const { geometries, attributes, info } = this;
+		}
 
-		if ( geometries.has( geometry ) === false ) {
+	} else {
 
-			const disposeCallback = onGeometryDispose.bind( this );
+		const array = geometryPosition.array;
 
-			geometries.set( geometry, disposeCallback );
+		for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
 
-			info.memory.geometries ++;
+			const a = i + 0;
+			const b = i + 1;
+			const c = i + 2;
 
-			geometry.addEventListener( 'dispose', disposeCallback );
+			indices.push( a, b, b, c, c, a );
 
 		}
 
-		const geometryAttributes = geometry.attributes;
+	}
 
-		for ( const name in geometryAttributes ) {
+	const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
+	attribute.version = getWireframeVersion( geometry );
 
-			attributes.update( geometryAttributes[ name ] );
+	return attribute;
 
-		}
+}
 
-		const index = this.getIndex( geometry, wireframe );
+class WebGPUGeometries {
 
-		if ( index !== null ) {
+	constructor( attributes, properties, info ) {
 
-			attributes.update( index, true );
+		this.attributes = attributes;
+		this.properties = properties;
+		this.info = info;
 
-		}
+		this.wireframes = new WeakMap();
+		this.geometryFrame = new WeakMap();
 
 	}
 
-	getIndex( geometry, wireframe = false ) {
+	has( renderObject ) {
 
-		let index = geometry.index;
+		const geometry = renderObject.geometry;
 
-		if ( wireframe ) {
+		return this.properties.has( geometry ) && this.properties.get( geometry ).initialized === true;
 
-			const wireframeGeometries = this.wireframeGeometries;
+	}
 
-			let wireframeAttribute = wireframeGeometries.get( geometry );
+	update( renderObject ) {
 
-			if ( wireframeAttribute === undefined ) {
+		if ( this.has( renderObject ) === false ) this.initGeometry( renderObject );
 
-				wireframeAttribute = this.getWireframeIndex( geometry );
+		this.updateFrameAttributes( renderObject );
 
-				wireframeGeometries.set( geometry, wireframeAttribute );
+	}
 
-			} else if ( wireframeAttribute.version !== this.getWireframeVersion( geometry ) ) {
+	initGeometry( renderObject ) {
 
-				this.attributes.remove( wireframeAttribute );
+		const geometry = renderObject.geometry;
+		const geometryProperties = this.properties.get( geometry );
 
-				wireframeAttribute = this.getWireframeIndex( geometry );
+		geometryProperties.initialized = true;
 
-				wireframeGeometries.set( geometry, wireframeAttribute );
+		const dispose = () => {
+
+			this.info.memory.geometries --;
+
+			const index = geometry.index;
+			const geometryAttributes = geometry.attributes;
+
+			if ( index !== null ) {
+
+				this.attributes.remove( index );
 
 			}
 
-			index = wireframeAttribute;
+			for ( const name in geometryAttributes ) {
 
-		}
+				this.attributes.remove( geometryAttributes[ name ] );
 
-		return index;
+			}
 
-	}
+			const wireframeAttribute = this.wireframes.get( geometry );
 
-	getWireframeIndex( geometry ) {
+			if ( wireframeAttribute !== undefined ) {
 
-		const indices = [];
+				this.attributes.remove( wireframeAttribute );
 
-		const geometryIndex = geometry.index;
-		const geometryPosition = geometry.attributes.position;
+			}
 
-		if ( geometryIndex !== null ) {
+			geometry.removeEventListener( 'dispose', dispose );
 
-			const array = geometryIndex.array;
+		};
 
-			for ( let i = 0, l = array.length; i < l; i += 3 ) {
+		this.info.memory.geometries ++;
 
-				const a = array[ i + 0 ];
-				const b = array[ i + 1 ];
-				const c = array[ i + 2 ];
+		geometry.addEventListener( 'dispose', dispose );
 
-				indices.push( a, b, b, c, c, a );
+	}
 
-			}
+	updateFrameAttributes( renderObject ) {
 
-		} else {
+		const frame = this.info.render.frame;
+		const geometry = renderObject.geometry;
 
-			const array = geometryPosition.array;
+		if ( this.geometryFrame.get( geometry ) !== frame ) {
 
-			for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
+			this.updateAttributes( renderObject );
 
-				const a = i + 0;
-				const b = i + 1;
-				const c = i + 2;
+			this.geometryFrame.set( geometry, frame );
 
-				indices.push( a, b, b, c, c, a );
+		}
 
-			}
+	}
+
+	updateAttributes( renderObject ) {
+
+		const geometry = renderObject.geometry;
+		const geometryAttributes = geometry.attributes;
+
+		for ( const name in geometryAttributes ) {
+
+			this.attributes.update( geometryAttributes[ name ] );
 
 		}
 
-		const attribute = new ( arrayNeedsUint32( indices ) ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
-		attribute.version = this.getWireframeVersion( geometry );
+		const index = this.getIndex( renderObject );
 
-		return attribute;
+		if ( index !== null ) {
+
+			this.attributes.update( index, true );
+
+		}
 
 	}
 
-	getWireframeVersion( geometry ) {
+	getIndex( renderObject ) {
 
-		return ( geometry.index !== null ) ? geometry.index.version : geometry.attributes.position.version;
+		const { geometry, material } = renderObject;
 
-	}
+		let index = geometry.index;
 
-}
+		if ( material.wireframe === true ) {
 
-function onGeometryDispose( event ) {
+			const wireframes = this.wireframes;
 
-	const geometry = event.target;
-	const disposeCallback = this.geometries.get( geometry );
+			let wireframeAttribute = wireframes.get( geometry );
 
-	this.geometries.delete( geometry );
+			if ( wireframeAttribute === undefined ) {
 
-	this.info.memory.geometries --;
+				wireframeAttribute = getWireframeIndex( geometry );
 
-	geometry.removeEventListener( 'dispose', disposeCallback );
+				wireframes.set( geometry, wireframeAttribute );
 
-	//
+			} else if ( wireframeAttribute.version !== getWireframeVersion( geometry ) ) {
 
-	const index = geometry.index;
-	const geometryAttributes = geometry.attributes;
+				this.attributes.remove( wireframeAttribute );
 
-	if ( index !== null ) {
+				wireframeAttribute = getWireframeIndex( geometry );
 
-		this.attributes.remove( index );
+				wireframes.set( geometry, wireframeAttribute );
 
-	}
+			}
 
-	for ( const name in geometryAttributes ) {
+			index = wireframeAttribute;
 
-		this.attributes.remove( geometryAttributes[ name ] );
+		}
+
+		return index;
 
 	}
 

+ 0 - 38
examples/jsm/renderers/webgpu/WebGPUObjects.js

@@ -1,38 +0,0 @@
-class WebGPUObjects {
-
-	constructor( geometries, info ) {
-
-		this.geometries = geometries;
-		this.info = info;
-
-		this.updateMap = new WeakMap();
-
-	}
-
-	update( object ) {
-
-		const geometry = object.geometry;
-		const updateMap = this.updateMap;
-		const frame = this.info.render.frame;
-
-		if ( this.geometries.has( geometry ) === false || updateMap.get( geometry ) !== frame ) {
-
-			const material = object.material;
-
-			this.geometries.update( geometry, material.wireframe === true );
-
-			updateMap.set( geometry, frame );
-
-		}
-
-	}
-
-	dispose() {
-
-		this.updateMap = new WeakMap();
-
-	}
-
-}
-
-export default WebGPUObjects;

+ 6 - 0
examples/jsm/renderers/webgpu/WebGPUProperties.js

@@ -27,6 +27,12 @@ class WebGPUProperties {
 
 	}
 
+	has( object ) {
+
+		return this.properties.has( object );
+
+	}
+
 	dispose() {
 
 		this.properties = new WeakMap();

+ 7 - 14
examples/jsm/renderers/webgpu/WebGPURenderLists.js

@@ -1,3 +1,5 @@
+import WebGPUWeakMap from './WebGPUWeakMap.js';
+
 function painterSortStable( a, b ) {
 
 	if ( a.groupOrder !== b.groupOrder ) {
@@ -156,31 +158,22 @@ class WebGPURenderLists {
 	constructor() {
 
 		this.lists = new WeakMap();
+		this.lists = new WebGPUWeakMap();
 
 	}
 
 	get( scene, camera ) {
 
 		const lists = this.lists;
+		const keys = [ scene, camera ];
 
-		const cameras = lists.get( scene );
-		let list;
+		let list = lists.get( keys );
 
-		if ( cameras === undefined ) {
+		if ( list === undefined ) {
 
 			list = new WebGPURenderList();
-			lists.set( scene, new WeakMap() );
-			lists.get( scene ).set( camera, list );
-
-		} else {
-
-			list = cameras.get( camera );
-			if ( list === undefined ) {
-
-				list = new WebGPURenderList();
-				cameras.set( camera, list );
+			lists.set( keys, list );
 
-			}
 
 		}
 

+ 40 - 0
examples/jsm/renderers/webgpu/WebGPURenderObject.js

@@ -0,0 +1,40 @@
+export default class WebGPURenderObject {
+
+	constructor( renderer, nodes, object, material, scene, camera, lightsNode ) {
+
+		this.renderer = renderer;
+		this.nodes = nodes;
+		this.object = object;
+		this.material = material;
+		this.scene = scene;
+		this.camera = camera;
+		this.lightsNode = lightsNode;
+
+		this.geometry = object.geometry;
+
+		this._materialVersion = - 1;
+		this._materialCacheKey = '';
+
+	}
+
+	getCacheKey() {
+
+		const { material, scene, lightsNode } = this;
+
+		if ( material.version !== this._materialVersion ) {
+
+			this._materialVersion = material.version;
+			this._materialCacheKey = material.customProgramCacheKey();
+
+		}
+
+		const cacheKey = [];
+
+		cacheKey.push( 'material:' + this._materialCacheKey );
+		cacheKey.push( 'nodes:' + this.nodes.getCacheKey( scene, lightsNode ) );
+
+		return '{' + cacheKey.join( ',' ) + '}';
+
+	}
+
+}

+ 65 - 0
examples/jsm/renderers/webgpu/WebGPURenderObjects.js

@@ -0,0 +1,65 @@
+import WebGPUWeakMap from './WebGPUWeakMap.js';
+import WebGPURenderObject from './WebGPURenderObject.js';
+import { PerspectiveCamera, OrthographicCamera } from 'three';
+
+const dummyCameras = {
+	PerspectiveCamera: new PerspectiveCamera(),
+	OrthographicCamera: new OrthographicCamera()
+};
+
+function getChainKeys( object, material, scene, camera, lightsNode ) {
+
+	// use dummy camera for optimize cache in case of use others cameras with the same type
+	return [ object, material, scene, dummyCameras[ camera.type ], lightsNode ];
+
+}
+
+class WebGPURenderObjects {
+
+	constructor( renderer, nodes, geometries, info ) {
+
+		this.renderer = renderer;
+		this.nodes = nodes;
+		this.geometries = geometries;
+		this.info = info;
+
+		this.cache = new WebGPUWeakMap();
+
+	}
+
+	get( object, material, scene, camera, lightsNode ) {
+
+		const chainKey = getChainKeys( object, material, scene, camera, lightsNode );
+
+		let renderObject = this.cache.get( chainKey );
+
+		if ( renderObject === undefined ) {
+
+			renderObject = new WebGPURenderObject( this.renderer, this.nodes, object, material, scene, camera, lightsNode );
+
+			this.cache.set( chainKey, renderObject );
+
+		}
+
+		return renderObject;
+
+	}
+
+	remove( object, material, scene, camera, lightsNode ) {
+
+		const chainKey = getChainKeys( object, material, scene, camera, lightsNode );
+
+		this.cache.delete( chainKey );
+
+	}
+
+	dispose() {
+
+		this.cache = new WebGPUWeakMap();
+		this.updateMap = new WeakMap();
+
+	}
+
+}
+
+export default WebGPURenderObjects;

+ 5 - 7
examples/jsm/renderers/webgpu/WebGPURenderPipeline.js

@@ -24,10 +24,9 @@ class WebGPURenderPipeline {
 
 	}
 
-	init( cacheKey, stageVertex, stageFragment, object, nodeBuilder ) {
+	init( cacheKey, stageVertex, stageFragment, renderObject, nodeBuilder ) {
 
-		const material = object.material;
-		const geometry = object.geometry;
+		const { object, material, geometry } = renderObject;
 
 		// determine shader attributes
 
@@ -85,7 +84,7 @@ class WebGPURenderPipeline {
 
 		//
 
-		const primitiveState = this._getPrimitiveState( object, material );
+		const primitiveState = this._getPrimitiveState( object, geometry, material );
 		const colorWriteMask = this._getColorWriteMask( material );
 		const depthCompare = this._getDepthCompare( material );
 		const colorFormat = this._utils.getCurrentColorFormat();
@@ -426,15 +425,14 @@ class WebGPURenderPipeline {
 
 	}
 
-	_getPrimitiveState( object, material ) {
+	_getPrimitiveState( object, geometry, material ) {
 
 		const descriptor = {};
 
-		descriptor.topology = this._utils.getPrimitiveTopology( object );
+		descriptor.topology = this._utils.getPrimitiveTopology( object, material );
 
 		if ( object.isLine === true && object.isLineSegments !== true ) {
 
-			const geometry = object.geometry;
 			const count = ( geometry.index ) ? geometry.index.count : geometry.attributes.position.count;
 			descriptor.stripIndexFormat = ( count > 65535 ) ? GPUIndexFormat.Uint32 : GPUIndexFormat.Uint16; // define data type for primitive restart value
 

+ 36 - 58
examples/jsm/renderers/webgpu/WebGPURenderPipelines.js

@@ -12,7 +12,7 @@ class WebGPURenderPipelines {
 		this.bindings = null;
 
 		this.pipelines = [];
-		this.objectCache = new WeakMap();
+		this.cache = new WeakMap();
 
 		this.stages = {
 			vertex: new Map(),
@@ -21,29 +21,22 @@ class WebGPURenderPipelines {
 
 	}
 
-	get( object ) {
+	get( renderObject ) {
 
 		const device = this.device;
+		const cache = this._getCache( renderObject );
 
-		const cache = this._getCache( object );
+		let currentPipeline = cache.currentPipeline;
 
-		let currentPipeline;
-
-		if ( this._needsUpdate( object, cache ) ) {
-
-			const material = object.material;
+		if ( this._needsUpdate( renderObject ) ) {
 
 			// release previous cache
 
-			if ( cache.currentPipeline !== undefined ) {
-
-				this._releaseObject( object );
-
-			}
+			this._releasePipeline( renderObject );
 
 			// get shader
 
-			const nodeBuilder = this.nodes.get( object );
+			const nodeBuilder = this.nodes.get( renderObject );
 
 			// programmable stages
 
@@ -67,7 +60,7 @@ class WebGPURenderPipelines {
 
 			// determine render pipeline
 
-			currentPipeline = this._acquirePipeline( stageVertex, stageFragment, object, nodeBuilder );
+			currentPipeline = this._acquirePipeline( stageVertex, stageFragment, renderObject );
 			cache.currentPipeline = currentPipeline;
 
 			// keep track of all used times
@@ -76,24 +69,22 @@ class WebGPURenderPipelines {
 			stageVertex.usedTimes ++;
 			stageFragment.usedTimes ++;
 
-			// events
-
-			material.addEventListener( 'dispose', cache.dispose );
+		}
 
-		} else {
+		return currentPipeline;
 
-			currentPipeline = cache.currentPipeline;
+	}
 
-		}
+	remove( renderObject ) {
 
-		return currentPipeline;
+		this._releasePipeline( renderObject );
 
 	}
 
 	dispose() {
 
 		this.pipelines = [];
-		this.objectCache = new WeakMap();
+		this.cache = new WeakMap();
 		this.shaderModules = {
 			vertex: new Map(),
 			fragment: new Map()
@@ -101,14 +92,14 @@ class WebGPURenderPipelines {
 
 	}
 
-	_acquirePipeline( stageVertex, stageFragment, object, nodeBuilder ) {
+	_acquirePipeline( stageVertex, stageFragment, renderObject ) {
 
 		let pipeline;
 		const pipelines = this.pipelines;
 
 		// check for existing pipeline
 
-		const cacheKey = this._computeCacheKey( stageVertex, stageFragment, object );
+		const cacheKey = this._computeCacheKey( stageVertex, stageFragment, renderObject );
 
 		for ( let i = 0, il = pipelines.length; i < il; i ++ ) {
 
@@ -126,7 +117,7 @@ class WebGPURenderPipelines {
 		if ( pipeline === undefined ) {
 
 			pipeline = new WebGPURenderPipeline( this.device, this.utils );
-			pipeline.init( cacheKey, stageVertex, stageFragment, object, nodeBuilder );
+			pipeline.init( cacheKey, stageVertex, stageFragment, renderObject, this.nodes.get( renderObject ) );
 
 			pipelines.push( pipeline );
 
@@ -136,9 +127,9 @@ class WebGPURenderPipelines {
 
 	}
 
-	_computeCacheKey( stageVertex, stageFragment, object ) {
+	_computeCacheKey( stageVertex, stageFragment, renderObject ) {
 
-		const material = object.material;
+		const { object, material } = renderObject;
 		const utils = this.utils;
 
 		const parameters = [
@@ -154,34 +145,21 @@ class WebGPURenderPipelines {
 			material.side,
 			utils.getSampleCount(),
 			utils.getCurrentEncoding(), utils.getCurrentColorFormat(), utils.getCurrentDepthStencilFormat(),
-			utils.getPrimitiveTopology( object )
+			utils.getPrimitiveTopology( object, material )
 		];
 
 		return parameters.join();
 
 	}
 
-	_getCache( object ) {
+	_getCache( renderObject ) {
 
-		let cache = this.objectCache.get( object );
+		let cache = this.cache.get( renderObject );
 
 		if ( cache === undefined ) {
 
-			cache = {
-
-				dispose: () => {
-
-					this._releaseObject( object );
-
-					this.objectCache.delete( object );
-
-					object.material.removeEventListener( 'dispose', cache.dispose );
-
-				}
-
-			};
-
-			this.objectCache.set( object, cache );
+			cache = {};
+			this.cache.set( renderObject, cache );
 
 		}
 
@@ -189,21 +167,16 @@ class WebGPURenderPipelines {
 
 	}
 
-	_releaseObject( object ) {
+	_releasePipeline( renderObject ) {
 
-		const cache = this.objectCache.get( object );
+		const cache = this._getCache( renderObject );
 
-		this._releasePipeline( cache.currentPipeline );
+		const pipeline = cache.currentPipeline;
 		delete cache.currentPipeline;
 
-		this.nodes.remove( object );
-		this.bindings.remove( object );
+		this.bindings.remove( renderObject );
 
-	}
-
-	_releasePipeline( pipeline ) {
-
-		if ( -- pipeline.usedTimes === 0 ) {
+		if ( pipeline && -- pipeline.usedTimes === 0 ) {
 
 			const pipelines = this.pipelines;
 
@@ -231,12 +204,17 @@ class WebGPURenderPipelines {
 
 	}
 
-	_needsUpdate( object, cache ) {
+	_needsUpdate( renderObject ) {
 
-		const material = object.material;
+		const cache = this._getCache( renderObject );
+		const material = renderObject.material;
 
 		let needsUpdate = false;
 
+		// check pipeline state
+
+		if ( cache.currentPipeline === undefined ) needsUpdate = true;
+
 		// check material state
 
 		if ( cache.material !== material || cache.materialVersion !== material.version ||

+ 64 - 16
examples/jsm/renderers/webgpu/WebGPURenderer.js

@@ -1,6 +1,6 @@
 import { GPUIndexFormat, GPUTextureFormat, GPUStoreOp } from './constants.js';
 import WebGPUAnimation from './WebGPUAnimation.js';
-import WebGPUObjects from './WebGPUObjects.js';
+import WebGPURenderObjects from './WebGPURenderObjects.js';
 import WebGPUAttributes from './WebGPUAttributes.js';
 import WebGPUGeometries from './WebGPUGeometries.js';
 import WebGPUInfo from './WebGPUInfo.js';
@@ -218,17 +218,17 @@ class WebGPURenderer {
 		this._info = new WebGPUInfo();
 		this._properties = new WebGPUProperties();
 		this._attributes = new WebGPUAttributes( device );
-		this._geometries = new WebGPUGeometries( this._attributes, this._info );
+		this._geometries = new WebGPUGeometries( this._attributes, this._properties, this._info );
 		this._textures = new WebGPUTextures( device, this._properties, this._info );
-		this._objects = new WebGPUObjects( this._geometries, this._info );
 		this._utils = new WebGPUUtils( this );
 		this._nodes = new WebGPUNodes( this, this._properties );
+		this._objects = new WebGPURenderObjects( this, this._nodes, this._geometries, this._info );
 		this._computePipelines = new WebGPUComputePipelines( device, this._nodes );
 		this._renderPipelines = new WebGPURenderPipelines( device, this._nodes, this._utils );
 		this._bindings = this._renderPipelines.bindings = new WebGPUBindings( device, this._info, this._properties, this._textures, this._renderPipelines, this._computePipelines, this._attributes, this._nodes );
 		this._renderLists = new WebGPURenderLists();
 		this._renderStates = new WebGPURenderStates();
-		this._background = new WebGPUBackground( this );
+		this._background = new WebGPUBackground( this, this._properties );
 
 		//
 
@@ -324,6 +324,13 @@ class WebGPURenderer {
 
 		//
 
+		this._nodes.updateEnvironment( scene );
+		this._nodes.updateFog( scene );
+		this._nodes.updateBackground( scene );
+		this._nodes.updateToneMapping();
+
+		//
+
 		this._background.update( this._currentRenderList, scene );
 
 		// start render pass
@@ -652,7 +659,7 @@ class WebGPURenderer {
 
 			// bind group
 
-			const bindGroup = this._bindings.get( computeNode ).group;
+			const bindGroup = this._bindings.getForCompute( computeNode ).group;
 			this._bindings.update( computeNode );
 			passEncoder.setBindGroup( 0, bindGroup );
 
@@ -826,39 +833,40 @@ class WebGPURenderer {
 
 		const info = this._info;
 
-		// send scene properties to object
+		material = scene.overrideMaterial !== null ? scene.overrideMaterial : material;
 
-		const objectProperties = this._properties.get( object );
+		//
 
-		objectProperties.lightsNode = lightsNode;
-		objectProperties.scene = scene;
+		object.onBeforeRender( this, scene, camera, geometry, material, group );
 
 		//
 
-		object.onBeforeRender( this, scene, camera, geometry, material, group );
+		const renderObject = this._getRenderObject( object, material, scene, camera, lightsNode );
+
+		//
 
 		object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
 		object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
 
 		// updates
 
-		this._nodes.update( object, camera );
-		this._bindings.update( object );
-		this._objects.update( object );
+		this._nodes.update( renderObject );
+		this._geometries.update( renderObject );
+		this._bindings.update( renderObject );
 
 		// pipeline
 
-		const renderPipeline = this._renderPipelines.get( object );
+		const renderPipeline = this._renderPipelines.get( renderObject );
 		passEncoder.setPipeline( renderPipeline.pipeline );
 
 		// bind group
 
-		const bindGroup = this._bindings.get( object ).group;
+		const bindGroup = this._bindings.get( renderObject ).group;
 		passEncoder.setBindGroup( 0, bindGroup );
 
 		// index
 
-		const index = this._geometries.getIndex( geometry, material.wireframe === true );
+		const index = this._geometries.getIndex( renderObject );
 
 		const hasIndex = ( index !== null );
 
@@ -900,6 +908,46 @@ class WebGPURenderer {
 
 	}
 
+	_getRenderObject( object, material, scene, camera, lightsNode ) {
+
+		const renderObject = this._objects.get( object, material, scene, camera, lightsNode );
+		const renderObjectProperties = this._properties.get( renderObject );
+
+		if ( renderObjectProperties.initialized !== true ) {
+
+			renderObjectProperties.initialized = true;
+
+			const dispose = () => {
+
+				this._renderPipelines.remove( renderObject );
+				this._nodes.remove( renderObject );
+				this._properties.remove( renderObject );
+
+				this._objects.remove( object, material, scene, camera, lightsNode );
+
+				renderObject.material.removeEventListener( 'dispose', dispose );
+
+			};
+
+			renderObject.material.addEventListener( 'dispose', dispose );
+
+		}
+
+		const cacheKey = renderObject.getCacheKey();
+
+		if ( renderObjectProperties.cacheKey !== cacheKey ) {
+
+			renderObjectProperties.cacheKey = cacheKey;
+
+			this._renderPipelines.remove( renderObject );
+			this._nodes.remove( renderObject );
+
+		}
+
+		return renderObject;
+
+	}
+
 	_setupIndexBuffer( index, encoder ) {
 
 		const buffer = this._attributes.get( index ).buffer;

+ 2 - 2
examples/jsm/renderers/webgpu/WebGPUUtils.js

@@ -61,10 +61,10 @@ class WebGPUUtils {
 
 	}
 
-	getPrimitiveTopology( object ) {
+	getPrimitiveTopology( object, material ) {
 
 		if ( object.isPoints ) return GPUPrimitiveTopology.PointList;
-		else if ( object.isLineSegments || ( object.isMesh && object.material.wireframe === true ) ) return GPUPrimitiveTopology.LineList;
+		else if ( object.isLineSegments || ( object.isMesh && material.wireframe === true ) ) return GPUPrimitiveTopology.LineList;
 		else if ( object.isLine ) return GPUPrimitiveTopology.LineStrip;
 		else if ( object.isMesh ) return GPUPrimitiveTopology.TriangleList;
 

+ 83 - 0
examples/jsm/renderers/webgpu/WebGPUWeakMap.js

@@ -0,0 +1,83 @@
+export default class WebGPUWeakMap extends WeakMap {
+
+	constructor() {
+
+		super();
+
+	}
+
+	get( keys ) {
+
+		if ( Array.isArray( keys ) ) {
+
+			let map = this;
+
+			for ( let i = 0; i < keys.length - 1; i ++ ) {
+
+				map = map.get( keys[ i ] );
+
+				if ( map === undefined ) return undefined;
+
+			}
+
+			return map.get( keys[ keys.length - 1 ] );
+
+		} else {
+
+			return super.get( keys );
+
+		}
+
+	}
+
+	set( keys, value ) {
+
+		if ( Array.isArray( keys ) ) {
+
+			let map = this;
+
+			for ( let i = 0; i < keys.length - 1; i ++ ) {
+
+				const key = keys[ i ];
+
+				if ( map.has( key ) === false ) map.set( key, new WeakMap() );
+
+				map = map.get( key );
+
+			}
+
+			return map.set( keys[ keys.length - 1 ], value );
+
+		} else {
+
+			return super.set( keys, value );
+
+		}
+
+	}
+
+	delete( keys ) {
+
+		if ( Array.isArray( keys ) ) {
+
+			let map = this;
+
+			for ( let i = 0; i < keys.length - 1; i ++ ) {
+
+				map = map.get( keys[ i ] );
+
+				if ( map === undefined ) return false;
+
+			}
+
+			return map.delete( keys[ keys.length - 1 ] );
+
+		} else {
+
+			return super.delete( keys );
+
+		}
+
+	}
+
+}

+ 224 - 20
examples/jsm/renderers/webgpu/nodes/WebGPUNodes.js

@@ -1,5 +1,6 @@
 import WebGPUNodeBuilder from './WebGPUNodeBuilder.js';
-import { NodeFrame } from 'three/nodes';
+import { NoToneMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from 'three';
+import { NodeFrame, vec2, cubeTexture, texture, rangeFog, densityFog, reference, toneMapping, positionWorld, modelWorldMatrix, transformDirection, equirectUV, oneMinus, viewportBottomLeft } from 'three/nodes';
 
 class WebGPUNodes {
 
@@ -12,24 +13,42 @@ class WebGPUNodes {
 
 	}
 
-	get( object ) {
+	get( renderObject ) {
 
-		const objectProperties = this.properties.get( object );
+		const renderObjectProperties = this.properties.get( renderObject );
 
-		let nodeBuilder = objectProperties.nodeBuilder;
+		let nodeBuilder = renderObjectProperties.nodeBuilder;
 
 		if ( nodeBuilder === undefined ) {
 
-			const scene = objectProperties.scene;
-			const lightsNode = objectProperties.lightsNode;
+			nodeBuilder = new WebGPUNodeBuilder( renderObject.object, this.renderer );
+			nodeBuilder.material = renderObject.material;
+			nodeBuilder.lightsNode = renderObject.lightsNode;
+			nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
+			nodeBuilder.fogNode = this.getFogNode( renderObject.scene );
+			nodeBuilder.toneMappingNode = this.getToneMappingNode();
+			nodeBuilder.build();
+
+			renderObjectProperties.nodeBuilder = nodeBuilder;
+
+		}
+
+		return nodeBuilder;
+
+	}
+
+	getForCompute( computeNode ) {
+
+		const computeProperties = this.properties.get( computeNode );
+
+		let nodeBuilder = computeProperties.nodeBuilder;
 
-			nodeBuilder = new WebGPUNodeBuilder( object, this.renderer );
-			nodeBuilder.lightsNode = lightsNode;
-			nodeBuilder.fogNode = scene ? scene.fogNode : undefined;
-			nodeBuilder.scene = scene;
+		if ( nodeBuilder === undefined ) {
+
+			nodeBuilder = new WebGPUNodeBuilder( computeNode, this.renderer );
 			nodeBuilder.build();
 
-			objectProperties.nodeBuilder = nodeBuilder;
+			computeProperties.nodeBuilder = nodeBuilder;
 
 		}
 
@@ -37,9 +56,9 @@ class WebGPUNodes {
 
 	}
 
-	remove( object ) {
+	remove( renderObject ) {
 
-		const objectProperties = this.properties.get( object );
+		const objectProperties = this.properties.get( renderObject );
 
 		delete objectProperties.nodeBuilder;
 
@@ -51,18 +70,203 @@ class WebGPUNodes {
 
 	}
 
-	update( object, camera ) {
+	getEnvironmentNode( scene ) {
+
+		return scene.environmentNode || this.properties.get( scene ).environmentNode || null;
+
+	}
+
+	getFogNode( scene ) {
+
+		return scene.fogNode || this.properties.get( scene ).fogNode || null;
+
+	}
+
+	getToneMappingNode() {
+
+		return this.renderer.toneMappingNode || this.properties.get( this.renderer ).toneMappingNode || null;
+
+	}
+
+
+	getCacheKey( scene, lightsNode ) {
+
+		const environmentNode = this.getEnvironmentNode( scene );
+		const fogNode = this.getFogNode( scene );
+		const toneMappingNode = this.getToneMappingNode();
+
+		const cacheKey = [];
+
+		if ( lightsNode ) cacheKey.push( 'lightsNode:' + lightsNode.getCacheKey() );
+		if ( environmentNode ) cacheKey.push( 'environmentNode:' + environmentNode.getCacheKey() );
+		if ( fogNode ) cacheKey.push( 'fogNode:' + fogNode.getCacheKey() );
+		if ( toneMappingNode ) cacheKey.push( 'toneMappingNode:' + toneMappingNode.getCacheKey() );
+
+		return '{' + cacheKey.join( ',' ) + '}';
+
+	}
+
+	updateToneMapping() {
 
 		const renderer = this.renderer;
-		const material = object.material;
+		const rendererProperties = this.properties.get( renderer );
+		const rendererToneMapping = renderer.toneMapping;
+
+		if ( rendererToneMapping !== NoToneMapping ) {
+
+			if ( rendererProperties.toneMappingCacheKey !== rendererToneMapping ) {
+
+				rendererProperties.toneMappingNode = toneMapping( renderer.toneMapping, reference( 'toneMappingExposure', 'float', renderer ) );
+				rendererProperties.toneMappingCacheKey = rendererToneMapping;
+
+			}
+
+		} else {
+
+			delete rendererProperties.toneMappingNode;
+			delete rendererProperties.toneMappingCacheKey;
+
+		}
+
+	}
+
+	updateBackground( scene ) {
+
+		const sceneProperties = this.properties.get( scene );
+		const background = scene.background;
+
+		if ( background ) {
+
+			if ( sceneProperties.backgroundCacheKey !== background.uuid ) {
+
+				let backgroundNode = null;
+
+				if ( background.isCubeTexture === true ) {
+
+					backgroundNode = cubeTexture( background, transformDirection( positionWorld, modelWorldMatrix ) );
+
+				} else if ( background.isTexture === true ) {
+
+					let nodeUV = null;
+
+					if ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping ) {
+
+						const dirNode = transformDirection( positionWorld, modelWorldMatrix );
+
+						nodeUV = equirectUV( dirNode );
+						nodeUV = vec2( nodeUV.x, oneMinus( nodeUV.y ) );
+
+					} else {
+
+						nodeUV = viewportBottomLeft;
+
+					}
+
+					backgroundNode = texture( background, nodeUV );
+
+				}
+
+				sceneProperties.backgroundNode = backgroundNode;
+				sceneProperties.backgroundCacheKey = background.uuid;
+
+			}
+
+		} else if ( sceneProperties.backgroundNode ) {
+
+			delete sceneProperties.backgroundNode;
+			delete sceneProperties.backgroundCacheKey;
+
+		}
+
+	}
+
+	updateFog( scene ) {
+
+		const sceneProperties = this.properties.get( scene );
+		const fog = scene.fog;
+
+		if ( fog ) {
+
+			if ( sceneProperties.fogCacheKey !== fog.uuid ) {
+
+				let fogNode = null;
+
+				if ( fog.isFogExp2 ) {
+
+					fogNode = densityFog( reference( 'color', 'color', fog ), reference( 'density', 'float', fog ) );
+
+				} else if ( fog.isFog ) {
+
+					fogNode = rangeFog( reference( 'color', 'color', fog ), reference( 'near', 'float', fog ), reference( 'far', 'float', fog ) );
+
+				} else {
+
+					console.error( 'WebGPUNodes: Unsupported fog configuration.', fog );
+
+				}
+
+				sceneProperties.fogNode = fogNode;
+				sceneProperties.fogCacheKey = fog.uuid;
+
+			}
+
+		} else {
+
+			delete sceneProperties.fogNode;
+			delete sceneProperties.fogCacheKey;
+
+		}
+
+	}
+
+	updateEnvironment( scene ) {
+
+		const sceneProperties = this.properties.get( scene );
+		const environment = scene.environment;
+
+		if ( environment ) {
+
+			if ( sceneProperties.environmentCacheKey !== environment.uuid ) {
+
+				let environmentNode = null;
+
+				if ( environment.isCubeTexture === true ) {
+
+					environmentNode = cubeTexture( environment );
+
+				} else if ( environment.isTexture === true ) {
+
+					environmentNode = texture( environment );
+
+				} else {
+
+					console.error( 'WebGPUNodes: Unsupported environment configuration.', environment );
+
+				}
+
+				sceneProperties.environmentNode = environmentNode;
+				sceneProperties.environmentCacheKey = environment.uuid;
+
+			}
+
+		} else if ( sceneProperties.environmentNode ) {
+
+			delete sceneProperties.environmentNode;
+			delete sceneProperties.environmentCacheKey;
+
+		}
+
+	}
+
+	update( renderObject ) {
 
-		const nodeBuilder = this.get( object );
+		const nodeBuilder = this.get( renderObject );
 		const nodeFrame = this.nodeFrame;
 
-		nodeFrame.object = object;
-		nodeFrame.camera = camera;
-		nodeFrame.renderer = renderer;
-		nodeFrame.material = material;
+		nodeFrame.object = renderObject.object;
+		nodeFrame.camera = renderObject.camera;
+		nodeFrame.renderer = renderObject.renderer;
+		nodeFrame.material = renderObject.material;
 
 		for ( const node of nodeBuilder.updateNodes ) {
 

+ 1 - 3
examples/webgpu_rtt.html

@@ -42,7 +42,6 @@
 			const dpr = window.devicePixelRatio;
 
 			init();
-			animate();
 
 			function init() {
 
@@ -79,6 +78,7 @@
 				renderer = new WebGPURenderer();
 				renderer.setPixelRatio( dpr );
 				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
 				document.body.appendChild( renderer.domElement );
 
 				textureRenderer = new WebGPUTextureRenderer( renderer );
@@ -125,8 +125,6 @@
 
 			function animate() {
 
-				requestAnimationFrame( animate );
-
 				box.rotation.x += 0.01;
 				box.rotation.y += 0.02;