@luma.gl/shadertools
Version:
Shader module system for luma.gl
157 lines (136 loc) • 3.59 kB
JavaScript
import {assert} from '../utils';
import {parsePropTypes} from './filters/prop-types';
const VERTEX_SHADER = 'vs';
const FRAGMENT_SHADER = 'fs';
export default class ShaderModule {
constructor({
name,
vs,
fs,
dependencies = [],
uniforms,
getUniforms,
deprecations = [],
defines = {},
inject = {},
// DEPRECATED
vertexShader,
fragmentShader
}) {
assert(typeof name === 'string');
this.name = name;
this.vs = vs || vertexShader;
this.fs = fs || fragmentShader;
this.getModuleUniforms = getUniforms;
this.dependencies = dependencies;
this.deprecations = this._parseDeprecationDefinitions(deprecations);
this.defines = defines;
this.injections = normalizeInjections(inject);
if (uniforms) {
this.uniforms = parsePropTypes(uniforms);
}
}
// Extracts the source code chunk for the specified shader type from the named shader module
getModuleSource(type) {
let moduleSource;
switch (type) {
case VERTEX_SHADER:
moduleSource = this.vs || '';
break;
case FRAGMENT_SHADER:
moduleSource = this.fs || '';
break;
default:
assert(false);
}
return `\
#define MODULE_${this.name.toUpperCase().replace(/[^0-9a-z]/gi, '_')}
${moduleSource}\
// END MODULE_${this.name}
`;
}
getUniforms(opts, uniforms) {
if (this.getModuleUniforms) {
return this.getModuleUniforms(opts, uniforms);
}
// Build uniforms from the uniforms array
if (this.uniforms) {
return this._defaultGetUniforms(opts);
}
return {};
}
getDefines() {
return this.defines;
}
// Warn about deprecated uniforms or functions
checkDeprecations(shaderSource, log) {
this.deprecations.forEach(def => {
if (def.regex.test(shaderSource)) {
if (def.deprecated) {
log.deprecated(def.old, def.new)();
} else {
log.removed(def.old, def.new)();
}
}
});
}
_parseDeprecationDefinitions(deprecations) {
deprecations.forEach(def => {
switch (def.type) {
case 'function':
def.regex = new RegExp(`\\b${def.old}\\(`);
break;
default:
def.regex = new RegExp(`${def.type} ${def.old};`);
}
});
return deprecations;
}
_defaultGetUniforms(opts = {}) {
const uniforms = {};
const propTypes = this.uniforms;
for (const key in propTypes) {
const propDef = propTypes[key];
if (key in opts && !propDef.private) {
if (propDef.validate) {
assert(propDef.validate(opts[key], propDef), `${this.name}: invalid ${key}`);
}
uniforms[key] = opts[key];
} else {
uniforms[key] = propDef.value;
}
}
return uniforms;
}
}
// This utility mutates the original module
// Keeping for backward compatibility
// TODO - remove in v8
export function normalizeShaderModule(module) {
if (!module.normalized) {
module.normalized = true;
if (module.uniforms && !module.getUniforms) {
const shaderModule = new ShaderModule(module);
module.getUniforms = shaderModule.getUniforms.bind(shaderModule);
}
}
return module;
}
function normalizeInjections(injections) {
const result = {
vs: {},
fs: {}
};
for (const hook in injections) {
let injection = injections[hook];
const stage = hook.slice(0, 2);
if (typeof injection === 'string') {
injection = {
order: 0,
injection
};
}
result[stage][hook] = injection;
}
return result;
}