@luma.gl/shadertools
Version:
Shader module system for luma.gl
1,553 lines (1,519 loc) • 470 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if (typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if (typeof define === 'function' && define.amd) define([], factory);
else if (typeof exports === 'object') exports['luma'] = factory();
else root['luma'] = factory();})(globalThis, function () {
"use strict";
var __exports__ = (() => {
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
// external-global-plugin:@luma.gl/core
var require_core = __commonJS({
"external-global-plugin:@luma.gl/core"(exports, module) {
module.exports = globalThis.luma;
}
});
// bundle.ts
var bundle_exports = {};
__export(bundle_exports, {
ShaderAssembler: () => ShaderAssembler,
_getDependencyGraph: () => getDependencyGraph,
_resolveModules: () => resolveModules,
assembleGLSLShaderPair: () => assembleGLSLShaderPair,
capitalize: () => capitalize,
checkShaderModuleDeprecations: () => checkShaderModuleDeprecations,
combineInjects: () => combineInjects,
convertToVec4: () => convertToVec4,
dirlight: () => dirlight,
dirlight1: () => dirlight2,
fp32: () => fp32,
fp64: () => fp64,
fp64LowPart: () => fp64LowPart,
fp64arithmetic: () => fp64arithmetic,
fp64ify: () => fp64ify,
fp64ifyMatrix4: () => fp64ifyMatrix4,
fromHalfFloat: () => fromHalfFloat,
generateShaderForModule: () => generateShaderForModule,
geometry1: () => geometry,
getPassthroughFS: () => getPassthroughFS,
getQualifierDetails: () => getQualifierDetails,
getShaderInfo: () => getShaderInfo,
getShaderLayoutFromWGSL: () => getShaderLayoutFromWGSL,
getShaderModuleDependencies: () => getShaderModuleDependencies,
getShaderModuleSource: () => getShaderModuleSource,
getShaderModuleUniforms: () => getShaderModuleUniforms,
gouraudLighting: () => gouraudLighting,
gouraudMaterial: () => gouraudMaterial,
initializeShaderModule: () => initializeShaderModule,
initializeShaderModules: () => initializeShaderModules,
lighting: () => lighting,
lights1: () => lights,
pbr: () => pbr,
pbrMaterial: () => pbrMaterial,
phongLighting: () => phongLighting,
phongMaterial: () => phongMaterial,
picking: () => picking,
preprocess: () => preprocess,
project1: () => project,
random: () => random,
toHalfFloat: () => toHalfFloat,
typeToChannelCount: () => typeToChannelCount,
typeToChannelSuffix: () => typeToChannelSuffix
});
__reExport(bundle_exports, __toESM(require_core(), 1));
// src/lib/utils/assert.ts
function assert(condition, message) {
if (!condition) {
throw new Error(message || "shadertools: assertion failed.");
}
}
// src/lib/filters/prop-types.ts
var DEFAULT_PROP_VALIDATORS = {
number: {
type: "number",
validate(value, propType) {
return Number.isFinite(value) && typeof propType === "object" && (propType.max === void 0 || value <= propType.max) && (propType.min === void 0 || value >= propType.min);
}
},
array: {
type: "array",
validate(value, propType) {
return Array.isArray(value) || ArrayBuffer.isView(value);
}
}
};
function makePropValidators(propTypes) {
const propValidators = {};
for (const [name, propType] of Object.entries(propTypes)) {
propValidators[name] = makePropValidator(propType);
}
return propValidators;
}
function getValidatedProperties(properties, propValidators, errorMessage) {
const validated = {};
for (const [key, propsValidator] of Object.entries(propValidators)) {
if (properties && key in properties && !propsValidator.private) {
if (propsValidator.validate) {
assert(
propsValidator.validate(properties[key], propsValidator),
`${errorMessage}: invalid ${key}`
);
}
validated[key] = properties[key];
} else {
validated[key] = propsValidator.value;
}
}
return validated;
}
function makePropValidator(propType) {
let type = getTypeOf(propType);
if (type !== "object") {
return { value: propType, ...DEFAULT_PROP_VALIDATORS[type], type };
}
if (typeof propType === "object") {
if (!propType) {
return { type: "object", value: null };
}
if (propType.type !== void 0) {
return { ...propType, ...DEFAULT_PROP_VALIDATORS[propType.type], type: propType.type };
}
if (propType.value === void 0) {
return { type: "object", value: propType };
}
type = getTypeOf(propType.value);
return { ...propType, ...DEFAULT_PROP_VALIDATORS[type], type };
}
throw new Error("props");
}
function getTypeOf(value) {
if (Array.isArray(value) || ArrayBuffer.isView(value)) {
return "array";
}
return typeof value;
}
// src/module-injectors.ts
var MODULE_INJECTORS_VS = (
/* glsl */
`#ifdef MODULE_LOGDEPTH
logdepth_adjustPosition(gl_Position);
#endif
`
);
var MODULE_INJECTORS_FS = (
/* glsl */
`#ifdef MODULE_MATERIAL
fragColor = material_filterColor(fragColor);
#endif
#ifdef MODULE_LIGHTING
fragColor = lighting_filterColor(fragColor);
#endif
#ifdef MODULE_FOG
fragColor = fog_filterColor(fragColor);
#endif
#ifdef MODULE_PICKING
fragColor = picking_filterHighlightColor(fragColor);
fragColor = picking_filterPickingColor(fragColor);
#endif
#ifdef MODULE_LOGDEPTH
logdepth_setFragDepth();
#endif
`
);
// src/lib/shader-assembly/shader-injections.ts
var MODULE_INJECTORS = {
vertex: MODULE_INJECTORS_VS,
fragment: MODULE_INJECTORS_FS
};
var REGEX_START_OF_MAIN = /void\s+main\s*\([^)]*\)\s*\{\n?/;
var REGEX_END_OF_MAIN = /}\n?[^{}]*$/;
var fragments = [];
var DECLARATION_INJECT_MARKER = "__LUMA_INJECT_DECLARATIONS__";
function normalizeInjections(injections) {
const result = { vertex: {}, fragment: {} };
for (const hook in injections) {
let injection = injections[hook];
const stage = getHookStage(hook);
if (typeof injection === "string") {
injection = {
order: 0,
injection
};
}
result[stage][hook] = injection;
}
return result;
}
function getHookStage(hook) {
const type = hook.slice(0, 2);
switch (type) {
case "vs":
return "vertex";
case "fs":
return "fragment";
default:
throw new Error(type);
}
}
function injectShader(source, stage, inject, injectStandardStubs = false) {
const isVertex = stage === "vertex";
for (const key in inject) {
const fragmentData = inject[key];
fragmentData.sort((a2, b2) => a2.order - b2.order);
fragments.length = fragmentData.length;
for (let i2 = 0, len = fragmentData.length; i2 < len; ++i2) {
fragments[i2] = fragmentData[i2].injection;
}
const fragmentString = `${fragments.join("\n")}
`;
switch (key) {
case "vs:#decl":
if (isVertex) {
source = source.replace(DECLARATION_INJECT_MARKER, fragmentString);
}
break;
case "vs:#main-start":
if (isVertex) {
source = source.replace(REGEX_START_OF_MAIN, (match) => match + fragmentString);
}
break;
case "vs:#main-end":
if (isVertex) {
source = source.replace(REGEX_END_OF_MAIN, (match) => fragmentString + match);
}
break;
case "fs:#decl":
if (!isVertex) {
source = source.replace(DECLARATION_INJECT_MARKER, fragmentString);
}
break;
case "fs:#main-start":
if (!isVertex) {
source = source.replace(REGEX_START_OF_MAIN, (match) => match + fragmentString);
}
break;
case "fs:#main-end":
if (!isVertex) {
source = source.replace(REGEX_END_OF_MAIN, (match) => fragmentString + match);
}
break;
default:
source = source.replace(key, (match) => match + fragmentString);
}
}
source = source.replace(DECLARATION_INJECT_MARKER, "");
if (injectStandardStubs) {
source = source.replace(/\}\s*$/, (match) => match + MODULE_INJECTORS[stage]);
}
return source;
}
function combineInjects(injects) {
const result = {};
assert(Array.isArray(injects) && injects.length > 1);
injects.forEach((inject) => {
for (const key in inject) {
result[key] = result[key] ? `${result[key]}
${inject[key]}` : inject[key];
}
});
return result;
}
// src/lib/shader-module/shader-module.ts
function initializeShaderModules(modules) {
modules.map((module) => initializeShaderModule(module));
}
function initializeShaderModule(module) {
if (module.instance) {
return;
}
initializeShaderModules(module.dependencies || []);
const {
propTypes = {},
deprecations = [],
// defines = {},
inject = {}
} = module;
const instance = {
normalizedInjections: normalizeInjections(inject),
parsedDeprecations: parseDeprecationDefinitions(deprecations)
};
if (propTypes) {
instance.propValidators = makePropValidators(propTypes);
}
module.instance = instance;
let defaultProps = {};
if (propTypes) {
defaultProps = Object.entries(propTypes).reduce(
(obj, [key, propType]) => {
const value = propType?.value;
if (value) {
obj[key] = value;
}
return obj;
},
{}
);
}
module.defaultUniforms = { ...module.defaultUniforms, ...defaultProps };
}
function getShaderModuleUniforms(module, props, oldUniforms) {
initializeShaderModule(module);
const uniforms = oldUniforms || { ...module.defaultUniforms };
if (props && module.getUniforms) {
return module.getUniforms(props, uniforms);
}
return getValidatedProperties(props, module.instance?.propValidators, module.name);
}
function checkShaderModuleDeprecations(shaderModule, shaderSource, log3) {
shaderModule.deprecations?.forEach((def) => {
if (def.regex?.test(shaderSource)) {
if (def.deprecated) {
log3.deprecated(def.old, def.new)();
} else {
log3.removed(def.old, def.new)();
}
}
});
}
function 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;
}
// src/lib/shader-module/shader-module-dependencies.ts
function getShaderModuleDependencies(modules) {
initializeShaderModules(modules);
const moduleMap = {};
const moduleDepth = {};
getDependencyGraph({ modules, level: 0, moduleMap, moduleDepth });
const dependencies = Object.keys(moduleDepth).sort((a2, b2) => moduleDepth[b2] - moduleDepth[a2]).map((name) => moduleMap[name]);
initializeShaderModules(dependencies);
return dependencies;
}
function getDependencyGraph(options) {
const { modules, level, moduleMap, moduleDepth } = options;
if (level >= 5) {
throw new Error("Possible loop in shader dependency graph");
}
for (const module of modules) {
moduleMap[module.name] = module;
if (moduleDepth[module.name] === void 0 || moduleDepth[module.name] < level) {
moduleDepth[module.name] = level;
}
}
for (const module of modules) {
if (module.dependencies) {
getDependencyGraph({ modules: module.dependencies, level: level + 1, moduleMap, moduleDepth });
}
}
}
function getShaderDependencies(modules) {
initializeShaderModules(modules);
const moduleMap = {};
const moduleDepth = {};
getDependencyGraph({ modules, level: 0, moduleMap, moduleDepth });
modules = Object.keys(moduleDepth).sort((a2, b2) => moduleDepth[b2] - moduleDepth[a2]).map((name) => moduleMap[name]);
initializeShaderModules(modules);
return modules;
}
function resolveModules(modules) {
return getShaderDependencies(modules);
}
// src/lib/shader-assembly/platform-defines.ts
function getPlatformShaderDefines(platformInfo) {
switch (platformInfo?.gpu.toLowerCase()) {
case "apple":
return (
/* glsl */
`#define APPLE_GPU
// Apple optimizes away the calculation necessary for emulated fp64
#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1
#define LUMA_FP32_TAN_PRECISION_WORKAROUND 1
// Intel GPU doesn't have full 32 bits precision in same cases, causes overflow
#define LUMA_FP64_HIGH_BITS_OVERFLOW_WORKAROUND 1
`
);
case "nvidia":
return (
/* glsl */
`#define NVIDIA_GPU
// Nvidia optimizes away the calculation necessary for emulated fp64
#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1
`
);
case "intel":
return (
/* glsl */
`#define INTEL_GPU
// Intel optimizes away the calculation necessary for emulated fp64
#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1
// Intel's built-in 'tan' function doesn't have acceptable precision
#define LUMA_FP32_TAN_PRECISION_WORKAROUND 1
// Intel GPU doesn't have full 32 bits precision in same cases, causes overflow
#define LUMA_FP64_HIGH_BITS_OVERFLOW_WORKAROUND 1
`
);
case "amd":
return (
/* glsl */
`#define AMD_GPU
`
);
default:
return (
/* glsl */
`#define DEFAULT_GPU
// Prevent driver from optimizing away the calculation necessary for emulated fp64
#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1
// Headless Chrome's software shader 'tan' function doesn't have acceptable precision
#define LUMA_FP32_TAN_PRECISION_WORKAROUND 1
// If the GPU doesn't have full 32 bits precision, will causes overflow
#define LUMA_FP64_HIGH_BITS_OVERFLOW_WORKAROUND 1
`
);
}
}
// src/lib/shader-transpiler/transpile-glsl-shader.ts
function transpileGLSLShader(source, stage) {
const sourceGLSLVersion = Number(source.match(/^#version[ \t]+(\d+)/m)?.[1] || 100);
if (sourceGLSLVersion !== 300) {
throw new Error("luma.gl v9 only supports GLSL 3.00 shader sources");
}
switch (stage) {
case "vertex":
source = convertShader(source, ES300_VERTEX_REPLACEMENTS);
return source;
case "fragment":
source = convertShader(source, ES300_FRAGMENT_REPLACEMENTS);
return source;
default:
throw new Error(stage);
}
}
var ES300_REPLACEMENTS = [
// Fix poorly formatted version directive
[/^(#version[ \t]+(100|300[ \t]+es))?[ \t]*\n/, "#version 300 es\n"],
// The individual `texture...()` functions were replaced with `texture()` overloads
[/\btexture(2D|2DProj|Cube)Lod(EXT)?\(/g, "textureLod("],
[/\btexture(2D|2DProj|Cube)(EXT)?\(/g, "texture("]
];
var ES300_VERTEX_REPLACEMENTS = [
...ES300_REPLACEMENTS,
// `attribute` keyword replaced with `in`
[makeVariableTextRegExp("attribute"), "in $1"],
// `varying` keyword replaced with `out`
[makeVariableTextRegExp("varying"), "out $1"]
];
var ES300_FRAGMENT_REPLACEMENTS = [
...ES300_REPLACEMENTS,
// `varying` keyword replaced with `in`
[makeVariableTextRegExp("varying"), "in $1"]
];
function convertShader(source, replacements) {
for (const [pattern, replacement] of replacements) {
source = source.replace(pattern, replacement);
}
return source;
}
function makeVariableTextRegExp(qualifier) {
return new RegExp(`\\b${qualifier}[ \\t]+(\\w+[ \\t]+\\w+(\\[\\w+\\])?;)`, "g");
}
// src/lib/shader-assembly/shader-hooks.ts
function getShaderHooks(hookFunctions, hookInjections) {
let result = "";
for (const hookName in hookFunctions) {
const hookFunction = hookFunctions[hookName];
result += `void ${hookFunction.signature} {
`;
if (hookFunction.header) {
result += ` ${hookFunction.header}`;
}
if (hookInjections[hookName]) {
const injections = hookInjections[hookName];
injections.sort((a2, b2) => a2.order - b2.order);
for (const injection of injections) {
result += ` ${injection.injection}
`;
}
}
if (hookFunction.footer) {
result += ` ${hookFunction.footer}`;
}
result += "}\n";
}
return result;
}
function normalizeShaderHooks(hookFunctions) {
const result = { vertex: {}, fragment: {} };
for (const hookFunction of hookFunctions) {
let opts;
let hook;
if (typeof hookFunction !== "string") {
opts = hookFunction;
hook = opts.hook;
} else {
opts = {};
hook = hookFunction;
}
hook = hook.trim();
const [shaderStage, signature] = hook.split(":");
const name = hook.replace(/\(.+/, "");
const normalizedHook = Object.assign(opts, { signature });
switch (shaderStage) {
case "vs":
result.vertex[name] = normalizedHook;
break;
case "fs":
result.fragment[name] = normalizedHook;
break;
default:
throw new Error(shaderStage);
}
}
return result;
}
// src/lib/glsl-utils/get-shader-info.ts
function getShaderInfo(source, defaultName) {
return {
name: getShaderName(source, defaultName),
language: "glsl",
version: getShaderVersion(source)
};
}
function getShaderName(shader, defaultName = "unnamed") {
const SHADER_NAME_REGEXP = /#define[^\S\r\n]*SHADER_NAME[^\S\r\n]*([A-Za-z0-9_-]+)\s*/;
const match = SHADER_NAME_REGEXP.exec(shader);
return match ? match[1] : defaultName;
}
function getShaderVersion(source) {
let version = 100;
const words = source.match(/[^\s]+/g);
if (words && words.length >= 2 && words[0] === "#version") {
const parsedVersion = parseInt(words[1], 10);
if (Number.isFinite(parsedVersion)) {
version = parsedVersion;
}
}
if (version !== 100 && version !== 300) {
throw new Error(`Invalid GLSL version ${version}`);
}
return version;
}
// src/lib/shader-assembly/assemble-shaders.ts
var INJECT_SHADER_DECLARATIONS = `
${DECLARATION_INJECT_MARKER}
`;
var FRAGMENT_SHADER_PROLOGUE = (
/* glsl */
`precision highp float;
`
);
function assembleWGSLShader(options) {
const modules = getShaderModuleDependencies(options.modules || []);
return {
source: assembleShaderWGSL(options.platformInfo, {
...options,
source: options.source,
stage: "vertex",
modules
}),
getUniforms: assembleGetUniforms(modules)
};
}
function assembleGLSLShaderPair(options) {
const { vs: vs6, fs: fs8 } = options;
const modules = getShaderModuleDependencies(options.modules || []);
return {
vs: assembleShaderGLSL(options.platformInfo, {
...options,
source: vs6,
stage: "vertex",
modules
}),
fs: assembleShaderGLSL(options.platformInfo, {
...options,
// @ts-expect-error
source: fs8,
stage: "fragment",
modules
}),
getUniforms: assembleGetUniforms(modules)
};
}
function assembleShaderWGSL(platformInfo, options) {
const {
// id,
source,
stage,
modules,
// defines = {},
hookFunctions = [],
inject = {},
log: log3
} = options;
assert(typeof source === "string", "shader source must be a string");
const coreSource = source;
let assembledSource = "";
const hookFunctionMap = normalizeShaderHooks(hookFunctions);
const hookInjections = {};
const declInjections = {};
const mainInjections = {};
for (const key in inject) {
const injection = typeof inject[key] === "string" ? { injection: inject[key], order: 0 } : inject[key];
const match = /^(v|f)s:(#)?([\w-]+)$/.exec(key);
if (match) {
const hash = match[2];
const name = match[3];
if (hash) {
if (name === "decl") {
declInjections[key] = [injection];
} else {
mainInjections[key] = [injection];
}
} else {
hookInjections[key] = [injection];
}
} else {
mainInjections[key] = [injection];
}
}
const modulesToInject = modules;
for (const module of modulesToInject) {
if (log3) {
checkShaderModuleDeprecations(module, coreSource, log3);
}
const moduleSource = getShaderModuleSource(module, "wgsl");
assembledSource += moduleSource;
const injections = module.injections?.[stage] || {};
for (const key in injections) {
const match = /^(v|f)s:#([\w-]+)$/.exec(key);
if (match) {
const name = match[2];
const injectionType = name === "decl" ? declInjections : mainInjections;
injectionType[key] = injectionType[key] || [];
injectionType[key].push(injections[key]);
} else {
hookInjections[key] = hookInjections[key] || [];
hookInjections[key].push(injections[key]);
}
}
}
assembledSource += INJECT_SHADER_DECLARATIONS;
assembledSource = injectShader(assembledSource, stage, declInjections);
assembledSource += getShaderHooks(hookFunctionMap[stage], hookInjections);
assembledSource += coreSource;
assembledSource = injectShader(assembledSource, stage, mainInjections);
return assembledSource;
}
function assembleShaderGLSL(platformInfo, options) {
const {
id,
source,
stage,
language = "glsl",
modules,
defines = {},
hookFunctions = [],
inject = {},
prologue = true,
log: log3
} = options;
assert(typeof source === "string", "shader source must be a string");
const sourceVersion = language === "glsl" ? getShaderInfo(source).version : -1;
const targetVersion = platformInfo.shaderLanguageVersion;
const sourceVersionDirective = sourceVersion === 100 ? "#version 100" : "#version 300 es";
const sourceLines = source.split("\n");
const coreSource = sourceLines.slice(1).join("\n");
const allDefines = {};
modules.forEach((module) => {
Object.assign(allDefines, module.defines);
});
Object.assign(allDefines, defines);
let assembledSource = "";
switch (language) {
case "wgsl":
break;
case "glsl":
assembledSource = prologue ? `${sourceVersionDirective}
// ----- PROLOGUE -------------------------
${getShaderNameDefine({ id, source, stage })}
${`#define SHADER_TYPE_${stage.toUpperCase()}`}
${getPlatformShaderDefines(platformInfo)}
${stage === "fragment" ? FRAGMENT_SHADER_PROLOGUE : ""}
// ----- APPLICATION DEFINES -------------------------
${getApplicationDefines(allDefines)}
` : `${sourceVersionDirective}
`;
break;
}
const hookFunctionMap = normalizeShaderHooks(hookFunctions);
const hookInjections = {};
const declInjections = {};
const mainInjections = {};
for (const key in inject) {
const injection = typeof inject[key] === "string" ? { injection: inject[key], order: 0 } : inject[key];
const match = /^(v|f)s:(#)?([\w-]+)$/.exec(key);
if (match) {
const hash = match[2];
const name = match[3];
if (hash) {
if (name === "decl") {
declInjections[key] = [injection];
} else {
mainInjections[key] = [injection];
}
} else {
hookInjections[key] = [injection];
}
} else {
mainInjections[key] = [injection];
}
}
for (const module of modules) {
if (log3) {
checkShaderModuleDeprecations(module, coreSource, log3);
}
const moduleSource = getShaderModuleSource(module, stage);
assembledSource += moduleSource;
const injections = module.instance?.normalizedInjections[stage] || {};
for (const key in injections) {
const match = /^(v|f)s:#([\w-]+)$/.exec(key);
if (match) {
const name = match[2];
const injectionType = name === "decl" ? declInjections : mainInjections;
injectionType[key] = injectionType[key] || [];
injectionType[key].push(injections[key]);
} else {
hookInjections[key] = hookInjections[key] || [];
hookInjections[key].push(injections[key]);
}
}
}
assembledSource += "// ----- MAIN SHADER SOURCE -------------------------";
assembledSource += INJECT_SHADER_DECLARATIONS;
assembledSource = injectShader(assembledSource, stage, declInjections);
assembledSource += getShaderHooks(hookFunctionMap[stage], hookInjections);
assembledSource += coreSource;
assembledSource = injectShader(assembledSource, stage, mainInjections);
if (language === "glsl" && sourceVersion !== targetVersion) {
assembledSource = transpileGLSLShader(assembledSource, stage);
}
return assembledSource.trim();
}
function assembleGetUniforms(modules) {
return function getUniforms8(opts) {
const uniforms = {};
for (const module of modules) {
const moduleUniforms = module.getUniforms?.(opts, uniforms);
Object.assign(uniforms, moduleUniforms);
}
return uniforms;
};
}
function getShaderNameDefine(options) {
const { id, source, stage } = options;
const injectShaderName = id && source.indexOf("SHADER_NAME") === -1;
return injectShaderName ? `
#define SHADER_NAME ${id}_${stage}` : "";
}
function getApplicationDefines(defines = {}) {
let sourceText = "";
for (const define in defines) {
const value = defines[define];
if (value || Number.isFinite(value)) {
sourceText += `#define ${define.toUpperCase()} ${defines[define]}
`;
}
}
return sourceText;
}
function getShaderModuleSource(module, stage) {
let moduleSource;
switch (stage) {
case "vertex":
moduleSource = module.vs || "";
break;
case "fragment":
moduleSource = module.fs || "";
break;
case "wgsl":
moduleSource = module.source || "";
break;
default:
assert(false);
}
if (!module.name) {
throw new Error("Shader module must have a name");
}
const moduleName = module.name.toUpperCase().replace(/[^0-9a-z]/gi, "_");
let source = `// ----- MODULE ${module.name} ---------------
`;
if (stage !== "wgsl") {
source += `#define MODULE_${moduleName}
`;
}
source += `${moduleSource}
`;
return source;
}
// src/lib/preprocessor/preprocessor.ts
var IFDEF_REGEXP = /^\s*\#\s*ifdef\s*([a-zA-Z_]+)\s*$/;
var ENDIF_REGEXP = /^\s*\#\s*endif\s*$/;
function preprocess(source, options) {
const lines = source.split("\n");
const output = [];
let conditional = true;
let currentDefine = null;
for (const line of lines) {
const matchIf = line.match(IFDEF_REGEXP);
const matchEnd = line.match(ENDIF_REGEXP);
if (matchIf) {
currentDefine = matchIf[1];
conditional = Boolean(options?.defines?.[currentDefine]);
} else if (matchEnd) {
conditional = true;
} else if (conditional) {
output.push(line);
}
}
return output.join("\n");
}
// src/lib/shader-assembler.ts
var _ShaderAssembler = class {
/** Hook functions */
_hookFunctions = [];
/** Shader modules */
_defaultModules = [];
/**
* A default shader assembler instance - the natural place to register default modules and hooks
* @returns
*/
static getDefaultShaderAssembler() {
_ShaderAssembler.defaultShaderAssembler = _ShaderAssembler.defaultShaderAssembler || new _ShaderAssembler();
return _ShaderAssembler.defaultShaderAssembler;
}
/**
* Add a default module that does not have to be provided with every call to assembleShaders()
*/
addDefaultModule(module) {
if (!this._defaultModules.find(
(m2) => m2.name === (typeof module === "string" ? module : module.name)
)) {
this._defaultModules.push(module);
}
}
/**
* Remove a default module
*/
removeDefaultModule(module) {
const moduleName = typeof module === "string" ? module : module.name;
this._defaultModules = this._defaultModules.filter((m2) => m2.name !== moduleName);
}
/**
* Register a shader hook
* @param hook
* @param opts
*/
addShaderHook(hook, opts) {
if (opts) {
hook = Object.assign(opts, { hook });
}
this._hookFunctions.push(hook);
}
/**
* Assemble a WGSL unified shader
* @param platformInfo
* @param props
* @returns
*/
assembleWGSLShader(props) {
const modules = this._getModuleList(props.modules);
const hookFunctions = this._hookFunctions;
const { source, getUniforms: getUniforms8 } = assembleWGSLShader({
...props,
// @ts-expect-error
source: props.source,
modules,
hookFunctions
});
const preprocessedSource = props.platformInfo.shaderLanguage === "wgsl" ? preprocess(source) : source;
return { source: preprocessedSource, getUniforms: getUniforms8, modules };
}
/**
* Assemble a pair of shaders into a single shader program
* @param platformInfo
* @param props
* @returns
*/
assembleGLSLShaderPair(props) {
const modules = this._getModuleList(props.modules);
const hookFunctions = this._hookFunctions;
const assembled = assembleGLSLShaderPair({
...props,
// @ts-expect-error
vs: props.vs,
// @ts-expect-error
fs: props.fs,
modules,
hookFunctions
});
return { ...assembled, modules };
}
/**
* Dedupe and combine with default modules
*/
_getModuleList(appModules = []) {
const modules = new Array(this._defaultModules.length + appModules.length);
const seen = {};
let count = 0;
for (let i2 = 0, len = this._defaultModules.length; i2 < len; ++i2) {
const module = this._defaultModules[i2];
const name = module.name;
modules[count++] = module;
seen[name] = true;
}
for (let i2 = 0, len = appModules.length; i2 < len; ++i2) {
const module = appModules[i2];
const name = module.name;
if (!seen[name]) {
modules[count++] = module;
seen[name] = true;
}
}
modules.length = count;
initializeShaderModules(modules);
return modules;
}
};
var ShaderAssembler = _ShaderAssembler;
/** Default ShaderAssembler instance */
__publicField(ShaderAssembler, "defaultShaderAssembler");
// src/lib/glsl-utils/shader-utils.ts
var FS_GLES = (
/* glsl */
`out vec4 transform_output;
void main() {
transform_output = vec4(0);
}`
);
var FS300 = `#version 300 es
${FS_GLES}`;
function getQualifierDetails(line, qualifiers) {
qualifiers = Array.isArray(qualifiers) ? qualifiers : [qualifiers];
const words = line.replace(/^\s+/, "").split(/\s+/);
const [qualifier, type, definition] = words;
if (!qualifiers.includes(qualifier) || !type || !definition) {
return null;
}
const name = definition.split(";")[0];
return { qualifier, type, name };
}
function getPassthroughFS(options) {
const { input, inputChannels, output } = options || {};
if (!input) {
return FS300;
}
if (!inputChannels) {
throw new Error("inputChannels");
}
const inputType = channelCountToType(inputChannels);
const outputValue = convertToVec4(input, inputChannels);
return `#version 300 es
in ${inputType} ${input};
out vec4 ${output};
void main() {
${output} = ${outputValue};
}`;
}
function typeToChannelSuffix(type) {
switch (type) {
case "float":
return "x";
case "vec2":
return "xy";
case "vec3":
return "xyz";
case "vec4":
return "xyzw";
default:
throw new Error(type);
}
}
function typeToChannelCount(type) {
switch (type) {
case "float":
return 1;
case "vec2":
return 2;
case "vec3":
return 3;
case "vec4":
return 4;
default:
throw new Error(type);
}
}
function channelCountToType(channels) {
switch (channels) {
case 1:
return "float";
case 2:
return "vec2";
case 3:
return "vec3";
case 4:
return "vec4";
default:
throw new Error(`invalid channels: ${channels}`);
}
}
function convertToVec4(variable, channels) {
switch (channels) {
case 1:
return `vec4(${variable}, 0.0, 0.0, 1.0)`;
case 2:
return `vec4(${variable}, 0.0, 1.0)`;
case 3:
return `vec4(${variable}, 1.0)`;
case 4:
return variable;
default:
throw new Error(`invalid channels: ${channels}`);
}
}
// src/lib/shader-generator/utils/capitalize.ts
function capitalize(str) {
return typeof str === "string" ? str.charAt(0).toUpperCase() + str.slice(1) : str;
}
// src/lib/shader-generator/glsl/generate-glsl.ts
function generateGLSLForModule(module, options) {
return generateGLSLUniformDeclarations(module, options);
}
function generateGLSLUniformDeclarations(module, options) {
const glsl = [];
switch (options.uniforms) {
case "scoped-interface-blocks":
case "unscoped-interface-blocks":
glsl.push(`uniform ${capitalize(module.name)} {`);
break;
case "uniforms":
}
for (const [uniformName, uniformFormat] of Object.entries(module.uniformTypes || {})) {
const glslUniformType = getGLSLUniformType(uniformFormat);
switch (options.uniforms) {
case "scoped-interface-blocks":
glsl.push(` ${glslUniformType} ${uniformName};`);
break;
case "unscoped-interface-blocks":
glsl.push(` ${glslUniformType} ${module.name}_${uniformName};`);
break;
case "uniforms":
glsl.push(`uniform ${glslUniformType} ${module.name}_${uniformName};`);
}
}
switch (options.uniforms) {
case "scoped-interface-blocks":
glsl.push(`} ${module.name};`);
break;
case "unscoped-interface-blocks":
glsl.push("};");
break;
case "uniforms":
}
glsl.push("");
return glsl.join("\n");
}
function getGLSLUniformType(uniformFormat) {
const UNIFORM_TYPE_TO_GLSL = {
f32: "float",
i32: "int",
u32: "uint",
"vec2<f32>": "vec2",
"vec3<f32>": "vec3",
"vec4<f32>": "vec4",
"vec2<i32>": "ivec2",
"vec3<i32>": "ivec3",
"vec4<i32>": "ivec4",
"vec2<u32>": "uvec2",
"vec3<u32>": "uvec3",
"vec4<u32>": "uvec4",
"mat2x2<f32>": "mat2",
"mat2x3<f32>": "mat2x3",
"mat2x4<f32>": "mat2x4",
"mat3x2<f32>": "mat3x2",
"mat3x3<f32>": "mat3",
"mat3x4<f32>": "mat3x4",
"mat4x2<f32>": "mat4x2",
"mat4x3<f32>": "mat4x3",
"mat4x4<f32>": "mat4"
};
const glsl = UNIFORM_TYPE_TO_GLSL[uniformFormat];
return glsl;
}
// src/lib/shader-generator/wgsl/generate-wgsl.ts
function generateWGSLForModule(module, options) {
return generateWGSLUniformDeclarations(module, options);
}
function generateWGSLUniformDeclarations(module, options) {
const wgsl = [];
wgsl.push(`struct ${capitalize(module.name)} {`);
for (const [uniformName, uniformFormat] of Object.entries(module?.uniformTypes || {})) {
const wgslUniformType = uniformFormat;
wgsl.push(` ${uniformName} : ${wgslUniformType};`);
}
wgsl.push("};");
wgsl.push(`var<uniform> ${module.name} : ${capitalize(module.name)};`);
return wgsl.join("\n");
}
// src/lib/shader-generator/generate-shader.ts
function generateShaderForModule(module, options) {
switch (options.shaderLanguage) {
case "glsl":
return generateGLSLForModule(module, options);
case "wgsl":
return generateWGSLForModule(module, options);
}
}
// src/lib/wgsl/get-shader-layout-wgsl.ts
var import_core = __toESM(require_core(), 1);
// node_modules/wgsl_reflect/wgsl_reflect.module.js
var e = class {
constructor(e2, t2) {
this.name = e2, this.attributes = t2, this.size = 0;
}
get isArray() {
return false;
}
get isStruct() {
return false;
}
get isTemplate() {
return false;
}
getTypeName() {
return this.name;
}
};
var t = class {
constructor(e2, t2, n2) {
this.name = e2, this.type = t2, this.attributes = n2, this.offset = 0, this.size = 0;
}
get isArray() {
return this.type.isArray;
}
get isStruct() {
return this.type.isStruct;
}
get isTemplate() {
return this.type.isTemplate;
}
get align() {
return this.type.isStruct ? this.type.align : 0;
}
get members() {
return this.type.isStruct ? this.type.members : null;
}
get format() {
return this.type.isArray || this.type.isTemplate ? this.type.format : null;
}
get count() {
return this.type.isArray ? this.type.count : 0;
}
get stride() {
return this.type.isArray ? this.type.stride : this.size;
}
};
var n = class extends e {
constructor(e2, t2) {
super(e2, t2), this.members = [], this.align = 0, this.startLine = -1, this.endLine = -1, this.inUse = false;
}
get isStruct() {
return true;
}
};
var s = class extends e {
constructor(e2, t2) {
super(e2, t2), this.count = 0, this.stride = 0;
}
get isArray() {
return true;
}
};
var r = class extends e {
constructor(e2, t2, n2, s2) {
super(e2, n2), this.format = t2, this.access = s2;
}
get isTemplate() {
return true;
}
getTypeName() {
let e2 = this.name;
if (null !== this.format) {
if ("vec2" === e2 || "vec3" === e2 || "vec4" === e2 || "mat2x2" === e2 || "mat2x3" === e2 || "mat2x4" === e2 || "mat3x2" === e2 || "mat3x3" === e2 || "mat3x4" === e2 || "mat4x2" === e2 || "mat4x3" === e2 || "mat4x4" === e2) {
if ("f32" === this.format.name)
return e2 += "f", e2;
if ("i32" === this.format.name)
return e2 += "i", e2;
if ("u32" === this.format.name)
return e2 += "u", e2;
if ("bool" === this.format.name)
return e2 += "b", e2;
if ("f16" === this.format.name)
return e2 += "h", e2;
}
e2 += `<${this.format.name}>`;
} else if ("vec2" === e2 || "vec3" === e2 || "vec4" === e2)
return e2;
return e2;
}
};
var a;
((e2) => {
e2[e2.Uniform = 0] = "Uniform", e2[e2.Storage = 1] = "Storage", e2[e2.Texture = 2] = "Texture", e2[e2.Sampler = 3] = "Sampler", e2[e2.StorageTexture = 4] = "StorageTexture";
})(a || (a = {}));
var i = class {
constructor(e2, t2, n2, s2, r2, a2, i2) {
this.name = e2, this.type = t2, this.group = n2, this.binding = s2, this.attributes = r2, this.resourceType = a2, this.access = i2;
}
get isArray() {
return this.type.isArray;
}
get isStruct() {
return this.type.isStruct;
}
get isTemplate() {
return this.type.isTemplate;
}
get size() {
return this.type.size;
}
get align() {
return this.type.isStruct ? this.type.align : 0;
}
get members() {
return this.type.isStruct ? this.type.members : null;
}
get format() {
return this.type.isArray || this.type.isTemplate ? this.type.format : null;
}
get count() {
return this.type.isArray ? this.type.count : 0;
}
get stride() {
return this.type.isArray ? this.type.stride : this.size;
}
};
var o = class {
constructor(e2, t2) {
this.name = e2, this.type = t2;
}
};
var l = class {
constructor(e2, t2, n2, s2) {
this.name = e2, this.type = t2, this.locationType = n2, this.location = s2, this.interpolation = null;
}
};
var c = class {
constructor(e2, t2, n2, s2) {
this.name = e2, this.type = t2, this.locationType = n2, this.location = s2;
}
};
var u = class {
constructor(e2, t2, n2, s2) {
this.name = e2, this.type = t2, this.attributes = n2, this.id = s2;
}
};
var h = class {
constructor(e2, t2, n2) {
this.name = e2, this.type = t2, this.attributes = n2;
}
};
var f = class {
constructor(e2, t2 = null, n2) {
this.stage = null, this.inputs = [], this.outputs = [], this.arguments = [], this.returnType = null, this.resources = [], this.overrides = [], this.startLine = -1, this.endLine = -1, this.inUse = false, this.calls = /* @__PURE__ */ new Set(), this.name = e2, this.stage = t2, this.attributes = n2;
}
};
var p = class {
constructor() {
this.vertex = [], this.fragment = [], this.compute = [];
}
};
var d = new Float32Array(1);
var m = new Int32Array(d.buffer);
var _ = new Uint16Array(1);
function g(e2) {
d[0] = e2;
const t2 = m[0], n2 = t2 >> 31 & 1;
let s2 = t2 >> 23 & 255, r2 = 8388607 & t2;
if (255 === s2)
return _[0] = n2 << 15 | 31744 | (0 !== r2 ? 512 : 0), _[0];
if (0 === s2) {
if (0 === r2)
return _[0] = n2 << 15, _[0];
r2 |= 8388608;
let e3 = 113;
for (; !(8388608 & r2); )
r2 <<= 1, e3--;
return s2 = 127 - e3, r2 &= 8388607, s2 > 0 ? (r2 = (r2 >> 126 - s2) + (r2 >> 127 - s2 & 1), _[0] = n2 << 15 | s2 << 10 | r2 >> 13, _[0]) : (_[0] = n2 << 15, _[0]);
}
return s2 = s2 - 127 + 15, s2 >= 31 ? (_[0] = n2 << 15 | 31744, _[0]) : s2 <= 0 ? s2 < -10 ? (_[0] = n2 << 15, _[0]) : (r2 = (8388608 | r2) >> 1 - s2, _[0] = n2 << 15 | r2 >> 13, _[0]) : (r2 >>= 13, _[0] = n2 << 15 | s2 << 10 | r2, _[0]);
}
var x = new Uint32Array(1);
var y = new Float32Array(x.buffer, 0, 1);
function b(e2) {
const t2 = 112 + (e2 >> 6 & 31) << 23 | (63 & e2) << 17;
return x[0] = t2, y[0];
}
function v(e2, t2, n2, s2, r2, a2, i2, o2, l2) {
const c2 = s2 * (i2 >>= r2) * (a2 >>= r2) + n2 * i2 + t2 * o2;
switch (l2) {
case "r8unorm":
return [w(e2, c2, "8unorm", 1)[0]];
case "r8snorm":
return [w(e2, c2, "8snorm", 1)[0]];
case "r8uint":
return [w(e2, c2, "8uint", 1)[0]];
case "r8sint":
return [w(e2, c2, "8sint", 1)[0]];
case "rg8unorm": {
const t3 = w(e2, c2, "8unorm", 2);
return [t3[0], t3[1]];
}
case "rg8snorm": {
const t3 = w(e2, c2, "8snorm", 2);
return [t3[0], t3[1]];
}
case "rg8uint": {
const t3 = w(e2, c2, "8uint", 2);
return [t3[0], t3[1]];
}
case "rg8sint": {
const t3 = w(e2, c2, "8sint", 2);
return [t3[0], t3[1]];
}
case "rgba8unorm-srgb":
case "rgba8unorm": {
const t3 = w(e2, c2, "8unorm", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba8snorm": {
const t3 = w(e2, c2, "8snorm", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba8uint": {
const t3 = w(e2, c2, "8uint", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba8sint": {
const t3 = w(e2, c2, "8sint", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "bgra8unorm-srgb":
case "bgra8unorm": {
const t3 = w(e2, c2, "8unorm", 4);
return [t3[2], t3[1], t3[0], t3[3]];
}
case "r16uint":
return [w(e2, c2, "16uint", 1)[0]];
case "r16sint":
return [w(e2, c2, "16sint", 1)[0]];
case "r16float":
return [w(e2, c2, "16float", 1)[0]];
case "rg16uint": {
const t3 = w(e2, c2, "16uint", 2);
return [t3[0], t3[1]];
}
case "rg16sint": {
const t3 = w(e2, c2, "16sint", 2);
return [t3[0], t3[1]];
}
case "rg16float": {
const t3 = w(e2, c2, "16float", 2);
return [t3[0], t3[1]];
}
case "rgba16uint": {
const t3 = w(e2, c2, "16uint", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba16sint": {
const t3 = w(e2, c2, "16sint", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba16float": {
const t3 = w(e2, c2, "16float", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "r32uint":
return [w(e2, c2, "32uint", 1)[0]];
case "r32sint":
return [w(e2, c2, "32sint", 1)[0]];
case "depth16unorm":
case "depth24plus":
case "depth24plus-stencil8":
case "depth32float":
case "depth32float-stencil8":
case "r32float":
return [w(e2, c2, "32float", 1)[0]];
case "rg32uint": {
const t3 = w(e2, c2, "32uint", 2);
return [t3[0], t3[1]];
}
case "rg32sint": {
const t3 = w(e2, c2, "32sint", 2);
return [t3[0], t3[1]];
}
case "rg32float": {
const t3 = w(e2, c2, "32float", 2);
return [t3[0], t3[1]];
}
case "rgba32uint": {
const t3 = w(e2, c2, "32uint", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba32sint": {
const t3 = w(e2, c2, "32sint", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rgba32float": {
const t3 = w(e2, c2, "32float", 4);
return [t3[0], t3[1], t3[2], t3[3]];
}
case "rg11b10ufloat": {
const t3 = new Uint32Array(e2.buffer, c2, 1)[0], n3 = (4192256 & t3) >> 11, s3 = (4290772992 & t3) >> 22;
return [b(2047 & t3), b(n3), function(e3) {
const t4 = 112 + (e3 >> 5 & 31) << 23 | (31 & e3) << 18;
return x[0] = t4, y[0];
}(s3), 1];
}
}
return null;
}
function w(e2, t2, n2, s2) {
const r2 = [0, 0, 0, 0];
for (let c2 = 0; c2 < s2; ++c2)
switch (n2) {
case "8unorm":
r2[c2] = e2[t2] / 255, t2++;
break;
case "8snorm":
r2[c2] = e2[t2] / 255 * 2 - 1, t2++;
break;
case "8uint":
r2[c2] = e2[t2], t2++;
break;
case "8sint":
r2[c2] = e2[t2] - 127, t2++;
break;
case "16uint":
r2[c2] = e2[t2] | e2[t2 + 1] << 8, t2 += 2;
break;
case "16sint":
r2[c2] = (e2[t2] | e2[t2 + 1] << 8) - 32768, t2 += 2;
break;
case "16float":
r2[c2] = (a2 = e2[t2] | e2[t2 + 1] << 8, i2 = void 0, o2 = void 0, l2 = void 0, i2 = (32768 & a2) >> 15, l2 = 1023 & a2, 0 == (o2 = (31744 & a2) >> 10) ? (i2 ? -1 : 1) * Math.pow(2, -14) * (l2 / Math.pow(2, 10)) : 31 == o2 ? l2 ? NaN : 1 / 0 * (i2 ? -1 : 1) : (i2 ? -1 : 1) * Math.pow(2, o2 - 15) * (1 + l2 / Math.pow(2, 10))), t2 += 2;
break;
case "32uint":
case "32sint":
r2[c2] = e2[t2] | e2[t2 + 1] << 8 | e2[t2 + 2] << 16 | e2[t2 + 3] << 24, t2 += 4;
break;
case "32float":
r2[c2] = new Float32Array(e2.buffer, t2, 1)[0], t2 += 4;
}
var a2, i2, o2, l2;
return r2;
}
function k(e2, t2, n2, s2, r2) {
for (let a2 = 0; a2 < s2; ++a2)
switch (n2) {
case "8unorm":
e2[t2] = 255 * r2[a2], t2++;
break;
case "8snorm":
e2[t2] = 0.5 * (r2[a2] + 1) * 255, t2++;
break;
case "8uint":
e2[t2] = r2[a2], t2++;
break;