playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
216 lines (215 loc) • 6.99 kB
JavaScript
import { hashCode } from "../../core/hash.js";
import { version, revision } from "../../core/core.js";
import { Shader } from "../../platform/graphics/shader.js";
import { SHADER_FORWARD, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS } from "../constants.js";
import { ShaderPass } from "../shader-pass.js";
import { StandardMaterialOptions } from "../materials/standard-material-options.js";
import { CameraShaderParams } from "../camera-shader-params.js";
class ProgramLibrary {
processedCache = /* @__PURE__ */ new Map();
definitionsCache = /* @__PURE__ */ new Map();
_generators = /* @__PURE__ */ new Map();
constructor(device, standardMaterial) {
this._device = device;
this._isClearingCache = false;
this._precached = false;
this._programsCollection = [];
this._defaultStdMatOption = new StandardMaterialOptions();
this._defaultStdMatOptionMin = new StandardMaterialOptions();
const defaultCameraShaderParams = new CameraShaderParams();
standardMaterial.shaderOptBuilder.updateRef(
this._defaultStdMatOption,
{},
defaultCameraShaderParams,
standardMaterial,
null,
[],
SHADER_FORWARD,
null
);
standardMaterial.shaderOptBuilder.updateMinRef(
this._defaultStdMatOptionMin,
{},
standardMaterial,
null,
SHADER_SHADOW,
null
);
device.on("destroy:shader", (shader) => {
this.removeFromCache(shader);
});
}
destroy() {
this.clearCache();
}
register(name, generator) {
if (!this._generators.has(name)) {
this._generators.set(name, generator);
}
}
unregister(name) {
if (this._generators.has(name)) {
this._generators.delete(name);
}
}
isRegistered(name) {
return this._generators.has(name);
}
generateShaderDefinition(generator, name, key, options) {
let def = this.definitionsCache.get(key);
if (!def) {
let lights;
if (options.litOptions?.lights) {
lights = options.litOptions.lights;
options.litOptions.lights = lights.map((l) => {
const lcopy = l.clone ? l.clone() : l;
lcopy.key = l.key;
return lcopy;
});
}
this.storeNewProgram(name, options);
if (options.litOptions?.lights) {
options.litOptions.lights = lights;
}
if (this._precached) {
}
const device = this._device;
def = generator.createShaderDefinition(device, options);
def.name = def.name ?? (options.pass ? `${name}-pass:${options.pass}` : name);
this.definitionsCache.set(key, def);
}
return def;
}
getCachedShader(key) {
return this.processedCache.get(key);
}
setCachedShader(key, shader) {
this.processedCache.set(key, shader);
}
getProgram(name, options, processingOptions, userMaterialId) {
const generator = this._generators.get(name);
if (!generator) {
return null;
}
const generationKeyString = generator.generateKey(options);
const generationKey = hashCode(generationKeyString);
const processingKeyString = processingOptions.generateKey(this._device);
const processingKey = hashCode(processingKeyString);
const totalKey = `${generationKey}#${processingKey}`;
let processedShader = this.getCachedShader(totalKey);
if (!processedShader) {
const generatedShaderDef = this.generateShaderDefinition(generator, name, generationKey, options);
let passName = "";
let shaderPassInfo;
if (options.pass !== void 0) {
shaderPassInfo = ShaderPass.get(this._device).getByIndex(options.pass);
passName = `-${shaderPassInfo.name}`;
}
this._device.fire("shader:generate", {
userMaterialId,
shaderPassInfo,
definition: generatedShaderDef
});
const shaderDefinition = {
name: `${generatedShaderDef.name}${passName}-proc`,
attributes: generatedShaderDef.attributes,
vshader: generatedShaderDef.vshader,
vincludes: generatedShaderDef.vincludes,
fincludes: generatedShaderDef.fincludes,
fshader: generatedShaderDef.fshader,
processingOptions,
shaderLanguage: generatedShaderDef.shaderLanguage,
meshUniformBufferFormat: generatedShaderDef.meshUniformBufferFormat,
meshBindGroupFormat: generatedShaderDef.meshBindGroupFormat
};
processedShader = new Shader(this._device, shaderDefinition);
this.setCachedShader(totalKey, processedShader);
}
return processedShader;
}
storeNewProgram(name, options) {
let opt = {};
if (name === "standard") {
const defaultMat = this._getDefaultStdMatOptions(options.pass);
for (const p in options) {
if (options.hasOwnProperty(p) && defaultMat[p] !== options[p] || p === "pass") {
opt[p] = options[p];
}
}
for (const p in options.litOptions) {
opt[p] = options.litOptions[p];
}
} else {
opt = options;
}
this._programsCollection.push(JSON.stringify({ name, options: opt }));
}
// run pc.getProgramLibrary(device).dumpPrograms(); from browser console to build shader options script
dumpPrograms() {
let text = "let device = pc.app ? pc.app.graphicsDevice : pc.Application.getApplication().graphicsDevice;\n";
text += "let shaders = [";
if (this._programsCollection[0]) {
text += `
${this._programsCollection[0]}`;
}
for (let i = 1; i < this._programsCollection.length; ++i) {
text += `,
${this._programsCollection[i]}`;
}
text += "\n];\n";
text += "pc.getProgramLibrary(device).precompile(shaders);\n";
text += `if (pc.version != "${version}" || pc.revision != "${revision}")
`;
text += ' console.warn("precompile-shaders.js: engine version mismatch, rebuild shaders lib with current engine");';
const element = document.createElement("a");
element.setAttribute("href", `data:text/plain;charset=utf-8,${encodeURIComponent(text)}`);
element.setAttribute("download", "precompile-shaders.js");
element.style.display = "none";
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
clearCache() {
this._isClearingCache = true;
this.processedCache.forEach((shader) => {
shader.destroy();
});
this.processedCache.clear();
this._isClearingCache = false;
}
removeFromCache(shader) {
if (this._isClearingCache) {
return;
}
this.processedCache.forEach((cachedShader, key) => {
if (shader === cachedShader) {
this.processedCache.delete(key);
}
});
}
_getDefaultStdMatOptions(pass) {
const shaderPassInfo = ShaderPass.get(this._device).getByIndex(pass);
return pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow ? this._defaultStdMatOptionMin : this._defaultStdMatOption;
}
precompile(cache) {
if (cache) {
const shaders = new Array(cache.length);
for (let i = 0; i < cache.length; i++) {
if (cache[i].name === "standard") {
const opt = cache[i].options;
const defaultMat = this._getDefaultStdMatOptions(opt.pass);
for (const p in defaultMat) {
if (defaultMat.hasOwnProperty(p) && opt[p] === void 0) {
opt[p] = defaultMat[p];
}
}
}
shaders[i] = this.getProgram(cache[i].name, cache[i].options);
}
}
this._precached = true;
}
}
export {
ProgramLibrary
};