playcanvas
Version:
PlayCanvas WebGL game engine
198 lines (195 loc) • 7.31 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_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 };