// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { assert } from "../utils/assert.js"; import { makePropValidators, getValidatedProperties } from "../filters/prop-types.js"; import { normalizeInjections } from "../shader-assembly/shader-injections.js"; let index = 1; /** An initialized ShaderModule, ready to use with `assembleShaders()` */ export class ShaderModuleInstance { name; vs; fs; getModuleUniforms; dependencies; deprecations; defines; injections; uniforms = {}; uniformTypes = {}; static instantiateModules(modules) { return modules.map((module) => { if (module instanceof ShaderModuleInstance) { return module; } assert(typeof module !== 'string', `Shader module use by name is deprecated. Import shader module '${JSON.stringify(module)}' and use it directly.`); if (!module.name) { // eslint-disable-next-line no-console console.warn('shader module has no name'); module.name = `shader-module-${index++}`; } const moduleObject = new ShaderModuleInstance(module); moduleObject.dependencies = ShaderModuleInstance.instantiateModules(module.dependencies || []); return moduleObject; }); } constructor(props) { const { name, vs, fs, dependencies = [], uniformTypes = {}, uniformPropTypes = {}, getUniforms, deprecations = [], defines = {}, inject = {} } = props; assert(typeof name === 'string'); this.name = name; this.vs = vs; this.fs = fs; this.getModuleUniforms = getUniforms; this.dependencies = ShaderModuleInstance.instantiateModules(dependencies); this.deprecations = this._parseDeprecationDefinitions(deprecations); this.defines = defines; this.injections = normalizeInjections(inject); this.uniformTypes = uniformTypes; if (uniformPropTypes) { this.uniforms = makePropValidators(uniformPropTypes); } } // Extracts the source code chunk for the specified shader type from the named shader module getModuleSource(stage) { let moduleSource; switch (stage) { case 'vertex': moduleSource = this.vs || ''; break; case 'fragment': moduleSource = this.fs || ''; break; default: assert(false); } const moduleName = this.name.toUpperCase().replace(/[^0-9a-z]/gi, '_'); return `\ // ----- MODULE ${this.name} --------------- #define MODULE_${moduleName} ${moduleSource}\ `; } getUniforms(userProps, uniforms) { if (this.getModuleUniforms) { return this.getModuleUniforms(userProps, uniforms); } // Build uniforms from the uniforms array return getValidatedProperties(userProps, this.uniforms, this.name); } 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; } }