Skip to content

Commit

Permalink
[Fix] Avoid additional shaders creation caused by out of order lights (
Browse files Browse the repository at this point in the history
…#5550)

* [Fix] Avoid additional shaders creation caused by out of order lights

* don’t include vertex format to be part of processing key on WebGL

* updates

---------

Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
  • Loading branch information
mvaligursky and Martin Valigursky committed Aug 4, 2023
1 parent 773a48d commit 864b4bd
Show file tree
Hide file tree
Showing 6 changed files with 46 additions and 22 deletions.
16 changes: 11 additions & 5 deletions src/platform/graphics/shader-processor-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,22 @@ class ShaderProcessorOptions {
}

/**
* Generate unique key represending the processing options.
* Generate unique key representing the processing options.
*
* @param {import('./graphics-device.js').GraphicsDevice} device - The device.
* @returns {string} - Returns the key.
*/
generateKey() {
generateKey(device) {
// TODO: Optimize. Uniform and BindGroup formats should have their keys evaluated in their
// constructors, and here we should simply concatenate those.
return JSON.stringify(this.uniformFormats) +
JSON.stringify(this.bindGroupFormats) +
this.vertexFormat?.renderingHashString;
let key = JSON.stringify(this.uniformFormats) + JSON.stringify(this.bindGroupFormats);

// WebGPU shaders are processed per vertex format
if (device.isWebGPU) {
key += this.vertexFormat?.renderingHashString;
}

return key;
}
}

Expand Down
18 changes: 13 additions & 5 deletions src/scene/composition/layer-composition.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,25 @@ class LayerComposition extends EventHandler {

// function which splits list of lights on a a target object into separate lists of lights based on light type
_splitLightsArray(target) {
const lights = target._lights;
target._splitLights[LIGHTTYPE_DIRECTIONAL].length = 0;
target._splitLights[LIGHTTYPE_OMNI].length = 0;
target._splitLights[LIGHTTYPE_SPOT].length = 0;

const splitLights = target._splitLights;
splitLights[LIGHTTYPE_DIRECTIONAL].length = 0;
splitLights[LIGHTTYPE_OMNI].length = 0;
splitLights[LIGHTTYPE_SPOT].length = 0;

const lights = target._lights;
for (let i = 0; i < lights.length; i++) {
const light = lights[i];
if (light.enabled) {
target._splitLights[light._type].push(light);
splitLights[light._type].push(light);
}
}

// sort the lights by their key, as the order of lights is used to generate shader generation key,
// and this avoids new shaders being generated when lights are reordered
splitLights[LIGHTTYPE_DIRECTIONAL].sort((a, b) => a.key - b.key);
splitLights[LIGHTTYPE_OMNI].sort((a, b) => a.key - b.key);
splitLights[LIGHTTYPE_SPOT].sort((a, b) => a.key - b.key);
}

_update(device, clusteredLightingEnabled = false) {
Expand Down
16 changes: 14 additions & 2 deletions src/scene/shader-lib/program-library.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Debug } from '../../core/debug.js';
import { hashCode } from '../../core/hash.js';
import { version, revision } from '../../core/core.js';

import { Shader } from '../../platform/graphics/shader.js';
Expand Down Expand Up @@ -119,8 +120,12 @@ class ProgramLibrary {

// we have a key for shader source code generation, a key for its further processing to work with
// uniform buffers, and a final key to get the processed shader from the cache
const generationKey = generator.generateKey(options);
const processingKey = processingOptions.generateKey();
const generationKeyString = generator.generateKey(options);
const generationKey = hashCode(generationKeyString);

const processingKeyString = processingOptions.generateKey(this._device);
const processingKey = hashCode(processingKeyString);

const totalKey = `${generationKey}#${processingKey}`;

// do we have final processed shader
Expand Down Expand Up @@ -158,6 +163,13 @@ class ProgramLibrary {

// add new shader to the processed cache
processedShader = new Shader(this._device, shaderDefinition);

// keep the keys in the debug mode
Debug.call(() => {
processedShader._generationKey = generationKeyString;
processedShader._processingKey = processingKeyString;
});

this.setCachedShader(totalKey, processedShader);
}

Expand Down
8 changes: 4 additions & 4 deletions src/scene/shader-lib/programs/lit-options-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@ const LitOptionsUtils = {
}
return key + options[key];
})
.join("");
.join("\n");
},

generateLightsKey(options) {
return options.lights.map((light) => {
return (!options.clusteredLightingEnabled || light._type === LIGHTTYPE_DIRECTIONAL) ? light.key : "";
return 'lights:' + options.lights.map((light) => {
return (!options.clusteredLightingEnabled || light._type === LIGHTTYPE_DIRECTIONAL) ? `${light.key},` : '';
}).join("");
},

generateChunksKey(options) {
return Object.keys(options.chunks ?? {})
return 'chunks:\n' + Object.keys(options.chunks ?? {})
.sort()
.map(key => key + options.chunks[key])
.join("");
Expand Down
3 changes: 1 addition & 2 deletions src/scene/shader-lib/programs/lit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { hashCode } from '../../../core/hash.js';
import { ChunkBuilder } from '../chunk-builder.js';
import { LitShader } from './lit-shader.js';
import { LitOptionsUtils } from './lit-options-utils.js';
Expand All @@ -19,7 +18,7 @@ const lit = {
options.shaderChunk +
LitOptionsUtils.generateKey(options.litOptions);

return hashCode(key);
return key;
},

/**
Expand Down
7 changes: 3 additions & 4 deletions src/scene/shader-lib/programs/standard.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { hashCode } from '../../../core/hash.js';
import { Debug } from '../../../core/debug.js';

import {
Expand Down Expand Up @@ -40,11 +39,11 @@ const standard = {
props = buildPropertiesList(options);
}

const key = "standard" +
props.map(prop => prop + options[prop]).join("") +
const key = "standard:\n" +
props.map(prop => prop + options[prop]).join('\n') +
LitOptionsUtils.generateKey(options.litOptions);

return hashCode(key);
return key;
},

// get the value to replace $UV with in Map Shader functions
Expand Down

0 comments on commit 864b4bd

Please sign in to comment.