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 );