Skip to content

Commit 1806a15

Browse files
mvaligurskyMartin Valigursky
andauthored
Refactor shader cache implementation for the mesh instance (#6696)
Co-authored-by: Martin Valigursky <[email protected]>
1 parent 8dbea69 commit 1806a15

File tree

2 files changed

+45
-45
lines changed

2 files changed

+45
-45
lines changed

src/scene/materials/material.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ class Material {
8383
* The cache of shader variants generated for this material. The key represents the unique
8484
* variant, the value is the shader.
8585
*
86-
* @type {Map<string, import('../../platform/graphics/shader.js').Shader>}
86+
* @type {Map<number, import('../../platform/graphics/shader.js').Shader>}
8787
* @ignore
8888
*/
8989
variants = new Map();

src/scene/mesh-instance.js

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ import { GraphNode } from './graph-node.js';
2121
import { getDefaultMaterial } from './materials/default-material.js';
2222
import { LightmapCache } from './graphics/lightmap-cache.js';
2323
import { DebugGraphics } from '../platform/graphics/debug-graphics.js';
24+
import { hash32Fnv1a } from '../core/hash.js';
25+
import { array } from '../core/array-utils.js';
2426

2527
let id = 0;
2628
const _tmpAabb = new BoundingBox();
2729
const _tempBoneAabb = new BoundingBox();
2830
const _tempSphere = new BoundingSphere();
2931
const _meshSet = new Set();
3032

33+
// internal array used to evaluate the hash for the shader instance
34+
const lookupHashes = new Uint32Array(3);
35+
3136
/**
3237
* Internal data structure used to store data used by hardware instancing.
3338
*
@@ -72,6 +77,13 @@ class ShaderInstance {
7277
*/
7378
uniformBuffer = null;
7479

80+
/**
81+
* The full array of hashes used to lookup the pipeline, used in case of hash collision.
82+
*
83+
* @type {Uint32Array}
84+
*/
85+
hashes;
86+
7587
/**
7688
* Returns the mesh bind group for the shader.
7789
*
@@ -126,26 +138,6 @@ class ShaderInstance {
126138
}
127139
}
128140

129-
/**
130-
* An entry in the shader cache, representing shaders for this mesh instance and a specific shader
131-
* pass.
132-
*
133-
* @ignore
134-
*/
135-
class ShaderCacheEntry {
136-
/**
137-
* The shader instances. Looked up by lightHash, which represents an ordered set of lights.
138-
*
139-
* @type {Map<number, ShaderInstance>}
140-
*/
141-
shaderInstances = new Map();
142-
143-
destroy() {
144-
this.shaderInstances.forEach(instance => instance.destroy());
145-
this.shaderInstances.clear();
146-
}
147-
}
148-
149141
/**
150142
* Callback used by {@link Layer} to calculate the "sort distance" for a {@link MeshInstance},
151143
* which determines its place in the render order.
@@ -198,13 +190,11 @@ class MeshInstance {
198190
_material = null;
199191

200192
/**
201-
* An array of shader cache entries, indexed by the shader pass constant (SHADER_FORWARD..). The
202-
* value stores all shaders and bind groups for the shader pass for various light combinations.
193+
* The cache of shaders, indexed by a hash value.
203194
*
204-
* @type {Array<ShaderCacheEntry|null>}
205-
* @private
195+
* @type {Map<number, ShaderInstance>}
206196
*/
207-
_shaderCache = [];
197+
_shaderCache = new Map();
208198

209199
/** @ignore */
210200
id = id++;
@@ -511,11 +501,10 @@ class MeshInstance {
511501
* @ignore
512502
*/
513503
clearShaders() {
514-
const shaderCache = this._shaderCache;
515-
for (let i = 0; i < shaderCache.length; i++) {
516-
shaderCache[i]?.destroy();
517-
shaderCache[i] = null;
518-
}
504+
this._shaderCache.forEach((shaderInstance) => {
505+
shaderInstance.destroy();
506+
});
507+
this._shaderCache.clear();
519508
}
520509

521510
/**
@@ -535,24 +524,26 @@ class MeshInstance {
535524
*/
536525
getShaderInstance(shaderPass, lightHash, scene, viewUniformFormat, viewBindGroupFormat, sortedLights) {
537526

538-
let shaderInstance;
539-
let passEntry = this._shaderCache[shaderPass];
540-
if (passEntry) {
541-
shaderInstance = passEntry.shaderInstances.get(lightHash);
542-
} else {
543-
passEntry = new ShaderCacheEntry();
544-
this._shaderCache[shaderPass] = passEntry;
545-
}
527+
const shaderDefs = this._shaderDefs;
528+
529+
// unique hash for the required shader
530+
lookupHashes[0] = shaderPass;
531+
lookupHashes[1] = lightHash;
532+
lookupHashes[2] = shaderDefs;
533+
const hash = hash32Fnv1a(lookupHashes);
534+
535+
// look up the cache
536+
let shaderInstance = this._shaderCache.get(hash);
546537

547538
// cache miss in the shader cache of the mesh instance
548539
if (!shaderInstance) {
549540

550-
// get the shader from the material
551541
const mat = this._material;
552-
const shaderDefs = this._shaderDefs;
553-
const variantKey = shaderPass + '_' + shaderDefs + '_' + lightHash;
542+
543+
// get the shader from the material
554544
shaderInstance = new ShaderInstance();
555-
shaderInstance.shader = mat.variants.get(variantKey);
545+
shaderInstance.shader = mat.variants.get(hash);
546+
shaderInstance.hashes = new Uint32Array(lookupHashes);
556547

557548
// cache miss in the material variants
558549
if (!shaderInstance.shader) {
@@ -566,15 +557,24 @@ class MeshInstance {
566557
DebugGraphics.popGpuMarker(this.mesh.device);
567558

568559
// add it to the material variants cache
569-
mat.variants.set(variantKey, shader);
560+
mat.variants.set(hash, shader);
570561

571562
shaderInstance.shader = shader;
572563
}
573564

574565
// add it to the mesh instance cache
575-
passEntry.shaderInstances.set(lightHash, shaderInstance);
566+
this._shaderCache.set(hash, shaderInstance);
576567
}
577568

569+
Debug.call(() => {
570+
// due to a small number of shaders in the cache, and to avoid performance hit, we're not
571+
// handling the hash collision. This is very unlikely but still possible. Check and report
572+
// if it happens in the debug mode, allowing us to fix the issue.
573+
if (!array.equals(shaderInstance.hashes, lookupHashes)) {
574+
Debug.errorOnce('Hash collision in the shader cache for mesh instance. This is very unlikely but still possible. Please report this issue.');
575+
}
576+
});
577+
578578
return shaderInstance;
579579
}
580580

0 commit comments

Comments
 (0)