diff --git a/examples/webgpu_performance.html b/examples/webgpu_performance.html
index 5c439f8094afa5..7d25f95c86eb72 100644
--- a/examples/webgpu_performance.html
+++ b/examples/webgpu_performance.html
@@ -30,6 +30,8 @@
import Stats from 'three/addons/libs/stats.module.js';
+ import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
@@ -37,9 +39,26 @@
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
let camera, scene, renderer, stats;
+ let model;
+
+ const options = { static: true };
init();
+ function setStatic( object, value ) {
+
+ object.traverse( child => {
+
+ if ( child.isMesh ) {
+
+ child.static = value;
+
+ }
+
+ } );
+
+ }
+
function init() {
const container = document.createElement( 'div' );
@@ -83,26 +102,36 @@
loader.load( 'dungeon_warkarma.glb', async function ( gltf ) {
- const model = gltf.scene;
+ model = gltf.scene;
// wait until the model can be added to the scene without blocking due to shader compilation
await renderer.compileAsync( model, camera, scene );
scene.add( model );
+
+ //
+
+ setStatic( model, options.static );
} );
} );
-
-
const controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 2;
controls.maxDistance = 60;
controls.target.set( 0, 0, - 0.2 );
controls.update();
+ // gui
+
+ const gui = new GUI();
+ gui.add( options, 'static' ).onChange( () => {
+
+ setStatic( model, options.static );
+
+ } );
window.addEventListener( 'resize', onWindowResize );
diff --git a/src/nodes/code/ScriptableNode.js b/src/nodes/code/ScriptableNode.js
index a035caf9bd1887..69ed08d145a15e 100644
--- a/src/nodes/code/ScriptableNode.js
+++ b/src/nodes/code/ScriptableNode.js
@@ -1,6 +1,7 @@
import Node from '../core/Node.js';
import { scriptableValue } from './ScriptableValueNode.js';
import { nodeProxy, float } from '../tsl/TSLBase.js';
+import { hashArray, hashString } from '../core/NodeUtils.js';
class Resources extends Map {
@@ -445,15 +446,15 @@ class ScriptableNode extends Node {
getCacheKey( force ) {
- const cacheKey = [ this.source, this.getDefaultOutputNode().getCacheKey( force ) ];
+ const values = [ hashString( this.source ), this.getDefaultOutputNode().getCacheKey( force ) ];
for ( const param in this.parameters ) {
- cacheKey.push( this.parameters[ param ].getCacheKey( force ) );
+ values.push( this.parameters[ param ].getCacheKey( force ) );
}
- return cacheKey.join( ',' );
+ return hashArray( values );
}
diff --git a/src/nodes/core/NodeUtils.js b/src/nodes/core/NodeUtils.js
index 9f2cb6cc7f562e..3611c09e509a82 100644
--- a/src/nodes/core/NodeUtils.js
+++ b/src/nodes/core/NodeUtils.js
@@ -5,26 +5,68 @@ import { Vector2 } from '../../math/Vector2.js';
import { Vector3 } from '../../math/Vector3.js';
import { Vector4 } from '../../math/Vector4.js';
+// cyrb53 (c) 2018 bryc (github.com/bryc). License: Public domain. Attribution appreciated.
+// A fast and simple 64-bit (or 53-bit) string hash function with decent collision resistance.
+// Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
+// See https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript/52171480#52171480
+// https://github.com/bryc/code/blob/master/jshash/experimental/cyrb53.js
+function cyrb53( value, seed = 0 ) {
+
+ let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
+
+ if ( value instanceof Array ) {
+
+ for ( let i = 0, val; i < value.length; i ++ ) {
+
+ val = value[ i ];
+ h1 = Math.imul( h1 ^ val, 2654435761 );
+ h2 = Math.imul( h2 ^ val, 1597334677 );
+
+ }
+
+ } else {
+
+ for ( let i = 0, ch; i < value.length; i ++ ) {
+
+ ch = value.charCodeAt( i );
+ h1 = Math.imul( h1 ^ ch, 2654435761 );
+ h2 = Math.imul( h2 ^ ch, 1597334677 );
+
+ }
+
+ }
+
+ h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 );
+ h1 ^= Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 );
+ h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 );
+ h2 ^= Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 );
+
+ return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 );
+
+}
+
+export const hashString = ( str ) => cyrb53( str );
+export const hashArray = ( array ) => cyrb53( array );
+export const hash = ( ...params ) => cyrb53( params );
+
export function getCacheKey( object, force = false ) {
- let cacheKey = '{';
+ const values = [];
if ( object.isNode === true ) {
- cacheKey += object.id;
+ values.push( object.id );
object = object.getSelf();
}
for ( const { property, childNode } of getNodeChildren( object ) ) {
- cacheKey += ',' + property.slice( 0, - 4 ) + ':' + childNode.getCacheKey( force );
+ values.push( values, cyrb53( property.slice( 0, - 4 ) ), childNode.getCacheKey( force ) );
}
- cacheKey += '}';
-
- return cacheKey;
+ return cyrb53( values );
}
diff --git a/src/nodes/display/ToneMappingNode.js b/src/nodes/display/ToneMappingNode.js
index b37c7f787cb276..641a6bab80a8c3 100644
--- a/src/nodes/display/ToneMappingNode.js
+++ b/src/nodes/display/ToneMappingNode.js
@@ -3,6 +3,7 @@ import { addMethodChaining, nodeObject, vec4 } from '../tsl/TSLCore.js';
import { rendererReference } from '../accessors/RendererReferenceNode.js';
import { NoToneMapping } from '../../constants.js';
+import { hash } from '../core/NodeUtils.js';
class ToneMappingNode extends TempNode {
@@ -25,10 +26,7 @@ class ToneMappingNode extends TempNode {
getCacheKey() {
- let cacheKey = super.getCacheKey();
- cacheKey = '{toneMapping:' + this.toneMapping + ',nodes:' + cacheKey + '}';
-
- return cacheKey;
+ return hash( super.getCacheKey(), this.toneMapping );
}
diff --git a/src/nodes/lighting/AnalyticLightNode.js b/src/nodes/lighting/AnalyticLightNode.js
index a2067fee7ee485..d920cdd3d13dc8 100644
--- a/src/nodes/lighting/AnalyticLightNode.js
+++ b/src/nodes/lighting/AnalyticLightNode.js
@@ -16,6 +16,7 @@ import { Loop } from '../utils/LoopNode.js';
import { screenCoordinate } from '../display/ScreenNode.js';
import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSystem } from '../../constants.js';
import { renderGroup } from '../core/UniformGroupNode.js';
+import { hash } from '../core/NodeUtils.js';
const BasicShadowMap = Fn( ( { depthTexture, shadowCoord } ) => {
@@ -238,7 +239,7 @@ class AnalyticLightNode extends LightingNode {
getCacheKey() {
- return super.getCacheKey() + '-' + ( this.light.id + '-' + ( this.light.castShadow ? '1' : '0' ) );
+ return hash( super.getCacheKey(), this.light.id, this.light.castShadow ? 1 : 0 );
}
diff --git a/src/renderers/common/ClippingContext.js b/src/renderers/common/ClippingContext.js
index a4ef37e4efb29d..a4624ff044ba98 100644
--- a/src/renderers/common/ClippingContext.js
+++ b/src/renderers/common/ClippingContext.js
@@ -1,6 +1,7 @@
import { Matrix3 } from '../../math/Matrix3.js';
import { Plane } from '../../math/Plane.js';
import { Vector4 } from '../../math/Vector4.js';
+import { hash } from '../../nodes/core/NodeUtils.js';
const _plane = /*@__PURE__*/ new Plane();
@@ -20,7 +21,7 @@ class ClippingContext {
this.parentVersion = 0;
this.viewNormalMatrix = new Matrix3();
- this.cacheKey = '';
+ this.cacheKey = 0;
}
@@ -95,7 +96,7 @@ class ClippingContext {
if ( update ) {
this.version ++;
- this.cacheKey = `${ this.globalClippingCount }:${ this.localClippingEnabled === undefined ? false : this.localClippingEnabled }:`;
+ this.cacheKey = hash( this.globalClippingCount, this.localClippingEnabled === true ? 1 : 0 );
}
@@ -165,7 +166,7 @@ class ClippingContext {
if ( update ) {
this.version += parent.version;
- this.cacheKey = parent.cacheKey + `:${ this.localClippingCount }:${ this.localClipIntersection === undefined ? false : this.localClipIntersection }`;
+ this.cacheKey = hash( parent.cacheKey, this.localClippingCount, this.localClipIntersection === true ? 1 : 0 );
}
diff --git a/src/renderers/common/RenderContext.js b/src/renderers/common/RenderContext.js
index 86d79d5e96740f..1f1d543a5b1c3d 100644
--- a/src/renderers/common/RenderContext.js
+++ b/src/renderers/common/RenderContext.js
@@ -1,4 +1,5 @@
import { Vector4 } from '../../math/Vector4.js';
+import { hashArray } from '../../nodes/core/NodeUtils.js';
let id = 0;
@@ -50,17 +51,15 @@ export function getCacheKey( renderContext ) {
const { textures, activeCubeFace } = renderContext;
- let key = '';
+ const values = [ activeCubeFace ];
for ( const texture of textures ) {
- key += texture.id + ',';
+ values.push( texture.id );
}
- key += activeCubeFace;
-
- return key;
+ return hashArray( values );
}
diff --git a/src/renderers/common/RenderObject.js b/src/renderers/common/RenderObject.js
index 7a8db20019a569..b41f427290da20 100644
--- a/src/renderers/common/RenderObject.js
+++ b/src/renderers/common/RenderObject.js
@@ -1,3 +1,4 @@
+import { hashString } from '../../nodes/core/NodeUtils.js';
import ClippingContext from './ClippingContext.js';
let _id = 0;
@@ -369,7 +370,7 @@ export default class RenderObject {
}
- return cacheKey;
+ return hashString( cacheKey );
}
@@ -383,13 +384,21 @@ export default class RenderObject {
// Environment Nodes Cache Key
- return this.object.receiveShadow + ',' + this._nodes.getCacheKey( this.scene, this.lightsNode );
+ let cacheKey = this._nodes.getCacheKey( this.scene, this.lightsNode );
+
+ if ( this.object.receiveShadow ) {
+
+ cacheKey += 1;
+
+ }
+
+ return cacheKey;
}
getCacheKey() {
- return this.getMaterialCacheKey() + ',' + this.getDynamicCacheKey();
+ return this.getMaterialCacheKey() + this.getDynamicCacheKey();
}
diff --git a/src/renderers/common/nodes/Nodes.js b/src/renderers/common/nodes/Nodes.js
index 6392b0eca76588..a4ab3d8c744087 100644
--- a/src/renderers/common/nodes/Nodes.js
+++ b/src/renderers/common/nodes/Nodes.js
@@ -6,6 +6,7 @@ import { NodeFrame } from '../../../nodes/Nodes.js';
import { objectGroup, renderGroup, frameGroup, cubeTexture, texture, rangeFog, densityFog, reference, normalWorld, pmremTexture, screenUV } from '../../../nodes/TSL.js';
import { CubeUVReflectionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../../constants.js';
+import { hashArray } from '../../../nodes/core/NodeUtils.js';
const outputNodeMap = new WeakMap();
@@ -226,15 +227,15 @@ class Nodes extends DataMap {
const environmentNode = this.getEnvironmentNode( scene );
const fogNode = this.getFogNode( scene );
- const cacheKey = [];
+ const values = [];
- if ( lightsNode ) cacheKey.push( lightsNode.getCacheKey( true ) );
- if ( environmentNode ) cacheKey.push( environmentNode.getCacheKey() );
- if ( fogNode ) cacheKey.push( fogNode.getCacheKey() );
+ if ( lightsNode ) values.push( lightsNode.getCacheKey( true ) );
+ if ( environmentNode ) values.push( environmentNode.getCacheKey() );
+ if ( fogNode ) values.push( fogNode.getCacheKey() );
cacheKeyData = {
callId,
- cacheKey: cacheKey.join( ',' )
+ cacheKey: hashArray( values )
};
this.callHashCache.set( chain, cacheKeyData );