UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

198 lines (195 loc) 7.31 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_SHADOW, SHADER_PICK, 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 { constructor(device, standardMaterial){ this.processedCache = new Map(); this.definitionsCache = new Map(); this._generators = new Map(); 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 !== undefined) { 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: 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: name, options: opt })); } dumpPrograms() { let text = 'let device = pc.app ? pc.app.graphicsDevice : pc.Application.getApplication().graphicsDevice;\n'; text += 'let shaders = ['; if (this._programsCollection[0]) { text += `\n\t${this._programsCollection[0]}`; } for(let i = 1; i < this._programsCollection.length; ++i){ text += `,\n\t${this._programsCollection[i]}`; } text += '\n];\n'; text += 'pc.getProgramLibrary(device).precompile(shaders);\n'; text += `if (pc.version != \"${version}\" || pc.revision != \"${revision}\")\n`; text += '\tconsole.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] === undefined) { opt[p] = defaultMat[p]; } } } shaders[i] = this.getProgram(cache[i].name, cache[i].options); } } this._precached = true; } } export { ProgramLibrary };