UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

216 lines (215 loc) 6.99 kB
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 };