@luma.gl/shadertools
Version:
Shader module system for luma.gl
1,407 lines (1,381 loc) • 286 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,
floatColors: () => floatColors,
fp32: () => fp32,
fp64: () => fp64,
fp64LowPart: () => fp64LowPart,
fp64arithmetic: () => fp64arithmetic,
fp64ify: () => fp64ify,
fp64ifyMatrix4: () => fp64ifyMatrix4,
fromHalfFloat: () => fromHalfFloat,
generateShaderForModule: () => generateShaderForModule,
getGLSLUniformBlocks: () => getGLSLUniformBlocks,
getPassthroughFS: () => getPassthroughFS,
getQualifierDetails: () => getQualifierDetails,
getShaderInfo: () => getShaderInfo,
getShaderModuleDependencies: () => getShaderModuleDependencies,
getShaderModuleSource: () => getShaderModuleSource,
getShaderModuleUniformBlockFields: () => getShaderModuleUniformBlockFields,
getShaderModuleUniformBlockName: () => getShaderModuleUniformBlockName,
getShaderModuleUniformLayoutValidationResult: () => getShaderModuleUniformLayoutValidationResult,
getShaderModuleUniforms: () => getShaderModuleUniforms,
gouraudMaterial: () => gouraudMaterial,
ibl: () => ibl,
initializeShaderModule: () => initializeShaderModule,
initializeShaderModules: () => initializeShaderModules,
lambertMaterial: () => lambertMaterial,
lighting: () => lighting,
normalizeByteColor3: () => normalizeByteColor3,
normalizeByteColor4: () => normalizeByteColor4,
pbrMaterial: () => pbrMaterial,
pbrScene: () => pbrScene,
phongMaterial: () => phongMaterial,
picking: () => picking,
preprocess: () => preprocess,
random: () => random,
resolveUseByteColors: () => resolveUseByteColors,
skin: () => skin,
toHalfFloat: () => toHalfFloat,
typeToChannelCount: () => typeToChannelCount,
typeToChannelSuffix: () => typeToChannelSuffix,
validateShaderModuleUniformLayout: () => validateShaderModuleUniformLayout,
warnIfGLSLUniformBlocksAreNotStd140: () => warnIfGLSLUniformBlocksAreNotStd140
});
__reExport(bundle_exports, __toESM(require_core(), 1));
// src/lib/utils/assert.ts
function assert(condition, message) {
if (!condition) {
const error = new Error(message || "shadertools: assertion failed.");
Error.captureStackTrace?.(error, assert);
throw error;
}
}
// 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(source4, stage, inject, injectStandardStubs = false) {
const isVertex = stage === "vertex";
for (const key in inject) {
const fragmentData = inject[key];
fragmentData.sort((a, b) => a.order - b.order);
fragments.length = fragmentData.length;
for (let i = 0, len = fragmentData.length; i < len; ++i) {
fragments[i] = fragmentData[i].injection;
}
const fragmentString = `${fragments.join("\n")}
`;
switch (key) {
case "vs:#decl":
if (isVertex) {
source4 = source4.replace(DECLARATION_INJECT_MARKER, fragmentString);
}
break;
case "vs:#main-start":
if (isVertex) {
source4 = source4.replace(REGEX_START_OF_MAIN, (match) => match + fragmentString);
}
break;
case "vs:#main-end":
if (isVertex) {
source4 = source4.replace(REGEX_END_OF_MAIN, (match) => fragmentString + match);
}
break;
case "fs:#decl":
if (!isVertex) {
source4 = source4.replace(DECLARATION_INJECT_MARKER, fragmentString);
}
break;
case "fs:#main-start":
if (!isVertex) {
source4 = source4.replace(REGEX_START_OF_MAIN, (match) => match + fragmentString);
}
break;
case "fs:#main-end":
if (!isVertex) {
source4 = source4.replace(REGEX_END_OF_MAIN, (match) => fragmentString + match);
}
break;
default:
source4 = source4.replace(key, (match) => match + fragmentString);
}
}
source4 = source4.replace(DECLARATION_INJECT_MARKER, "");
if (injectStandardStubs) {
source4 = source4.replace(/\}\s*$/, (match) => match + MODULE_INJECTORS[stage]);
}
return source4;
}
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, log2) {
shaderModule.deprecations?.forEach((def) => {
if (def.regex?.test(shaderSource)) {
if (def.deprecated) {
log2.deprecated(def.old, def.new)();
} else {
log2.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((a, b) => moduleDepth[b] - moduleDepth[a]).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((a, b) => moduleDepth[b] - moduleDepth[a]).map((name) => moduleMap[name]);
initializeShaderModules(modules);
return modules;
}
function resolveModules(modules) {
return getShaderDependencies(modules);
}
// src/lib/shader-module/shader-module-uniform-layout.ts
var GLSL_UNIFORM_BLOCK_FIELD_REGEXP = /^(?:uniform\s+)?(?:(?:lowp|mediump|highp)\s+)?[A-Za-z0-9_]+(?:<[^>]+>)?\s+([A-Za-z0-9_]+)(?:\s*\[[^\]]+\])?\s*;/;
var GLSL_UNIFORM_BLOCK_REGEXP = /((?:layout\s*\([^)]*\)\s*)*)uniform\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}\s*([A-Za-z_][A-Za-z0-9_]*)?\s*;/g;
function getShaderModuleUniformBlockName(module) {
return `${module.name}Uniforms`;
}
function getShaderModuleUniformBlockFields(module, stage) {
const shaderSource = stage === "wgsl" ? module.source : stage === "vertex" ? module.vs : module.fs;
if (!shaderSource) {
return null;
}
const uniformBlockName = getShaderModuleUniformBlockName(module);
return extractShaderUniformBlockFieldNames(
shaderSource,
stage === "wgsl" ? "wgsl" : "glsl",
uniformBlockName
);
}
function getShaderModuleUniformLayoutValidationResult(module, stage) {
const expectedUniformNames = Object.keys(module.uniformTypes || {});
if (!expectedUniformNames.length) {
return null;
}
const actualUniformNames = getShaderModuleUniformBlockFields(module, stage);
if (!actualUniformNames) {
return null;
}
return {
moduleName: module.name,
uniformBlockName: getShaderModuleUniformBlockName(module),
stage,
expectedUniformNames,
actualUniformNames,
matches: areStringArraysEqual(expectedUniformNames, actualUniformNames)
};
}
function validateShaderModuleUniformLayout(module, stage, options = {}) {
const validationResult = getShaderModuleUniformLayoutValidationResult(module, stage);
if (!validationResult || validationResult.matches) {
return validationResult;
}
const message = formatShaderModuleUniformLayoutError(validationResult);
options.log?.error?.(message, validationResult)();
if (options.throwOnError !== false) {
assert(false, message);
}
return validationResult;
}
function getGLSLUniformBlocks(shaderSource) {
const blocks = [];
const uncommentedSource = stripShaderComments(shaderSource);
for (const sourceMatch of uncommentedSource.matchAll(GLSL_UNIFORM_BLOCK_REGEXP)) {
const layoutQualifier = sourceMatch[1]?.trim() || null;
blocks.push({
blockName: sourceMatch[2],
body: sourceMatch[3],
instanceName: sourceMatch[4] || null,
layoutQualifier,
hasLayoutQualifier: Boolean(layoutQualifier),
isStd140: Boolean(
layoutQualifier && /\blayout\s*\([^)]*\bstd140\b[^)]*\)/.exec(layoutQualifier)
)
});
}
return blocks;
}
function warnIfGLSLUniformBlocksAreNotStd140(shaderSource, stage, log2, context) {
const nonStd140Blocks = getGLSLUniformBlocks(shaderSource).filter((block) => !block.isStd140);
const seenBlockNames = /* @__PURE__ */ new Set();
for (const block of nonStd140Blocks) {
if (seenBlockNames.has(block.blockName)) {
continue;
}
seenBlockNames.add(block.blockName);
const shaderLabel = context?.label ? `${context.label} ` : "";
const actualLayout = block.hasLayoutQualifier ? `declares ${normalizeWhitespace(block.layoutQualifier)} instead of layout(std140)` : "does not declare layout(std140)";
const message = `${shaderLabel}${stage} shader uniform block ${block.blockName} ${actualLayout}. luma.gl host-side shader block packing assumes explicit layout(std140) for GLSL uniform blocks. Add \`layout(std140)\` to the block declaration.`;
log2?.warn?.(message, block)();
}
return nonStd140Blocks;
}
function extractShaderUniformBlockFieldNames(shaderSource, language, uniformBlockName) {
const sourceBody = language === "wgsl" ? extractWGSLStructBody(shaderSource, uniformBlockName) : extractGLSLUniformBlockBody(shaderSource, uniformBlockName);
if (!sourceBody) {
return null;
}
const fieldNames = [];
for (const sourceLine of sourceBody.split("\n")) {
const line = sourceLine.replace(/\/\/.*$/, "").trim();
if (!line || line.startsWith("#")) {
continue;
}
const fieldMatch = language === "wgsl" ? line.match(/^([A-Za-z0-9_]+)\s*:/) : line.match(GLSL_UNIFORM_BLOCK_FIELD_REGEXP);
if (fieldMatch) {
fieldNames.push(fieldMatch[1]);
}
}
return fieldNames;
}
function extractWGSLStructBody(shaderSource, uniformBlockName) {
const structMatch = new RegExp(`\\bstruct\\s+${uniformBlockName}\\b`, "m").exec(shaderSource);
if (!structMatch) {
return null;
}
const openBraceIndex = shaderSource.indexOf("{", structMatch.index);
if (openBraceIndex < 0) {
return null;
}
let braceDepth = 0;
for (let index = openBraceIndex; index < shaderSource.length; index++) {
const character = shaderSource[index];
if (character === "{") {
braceDepth++;
continue;
}
if (character !== "}") {
continue;
}
braceDepth--;
if (braceDepth === 0) {
return shaderSource.slice(openBraceIndex + 1, index);
}
}
return null;
}
function extractGLSLUniformBlockBody(shaderSource, uniformBlockName) {
const block = getGLSLUniformBlocks(shaderSource).find(
(candidate) => candidate.blockName === uniformBlockName
);
return block?.body || null;
}
function areStringArraysEqual(leftValues, rightValues) {
if (leftValues.length !== rightValues.length) {
return false;
}
for (let valueIndex = 0; valueIndex < leftValues.length; valueIndex++) {
if (leftValues[valueIndex] !== rightValues[valueIndex]) {
return false;
}
}
return true;
}
function formatShaderModuleUniformLayoutError(validationResult) {
const { expectedUniformNames, actualUniformNames } = validationResult;
const missingUniformNames = expectedUniformNames.filter(
(uniformName) => !actualUniformNames.includes(uniformName)
);
const unexpectedUniformNames = actualUniformNames.filter(
(uniformName) => !expectedUniformNames.includes(uniformName)
);
const mismatchDetails = [
`Expected ${expectedUniformNames.length} fields, found ${actualUniformNames.length}.`
];
const firstMismatchDescription = getFirstUniformMismatchDescription(
expectedUniformNames,
actualUniformNames
);
if (firstMismatchDescription) {
mismatchDetails.push(firstMismatchDescription);
}
if (missingUniformNames.length) {
mismatchDetails.push(
`Missing from shader block (${missingUniformNames.length}): ${formatUniformNameList(
missingUniformNames
)}.`
);
}
if (unexpectedUniformNames.length) {
mismatchDetails.push(
`Unexpected in shader block (${unexpectedUniformNames.length}): ${formatUniformNameList(
unexpectedUniformNames
)}.`
);
}
if (expectedUniformNames.length <= 12 && actualUniformNames.length <= 12 && (missingUniformNames.length || unexpectedUniformNames.length)) {
mismatchDetails.push(`Expected: ${expectedUniformNames.join(", ")}.`);
mismatchDetails.push(`Actual: ${actualUniformNames.join(", ")}.`);
}
return `${validationResult.moduleName}: ${validationResult.stage} shader uniform block ${validationResult.uniformBlockName} does not match module.uniformTypes. ${mismatchDetails.join(" ")}`;
}
function stripShaderComments(shaderSource) {
return shaderSource.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
}
function normalizeWhitespace(value) {
return value.replace(/\s+/g, " ").trim();
}
function getFirstUniformMismatchDescription(expectedUniformNames, actualUniformNames) {
const minimumLength = Math.min(expectedUniformNames.length, actualUniformNames.length);
for (let index = 0; index < minimumLength; index++) {
if (expectedUniformNames[index] !== actualUniformNames[index]) {
return `First mismatch at field ${index + 1}: expected ${expectedUniformNames[index]}, found ${actualUniformNames[index]}.`;
}
}
if (expectedUniformNames.length > actualUniformNames.length) {
return `Shader block ends after field ${actualUniformNames.length}; expected next field ${expectedUniformNames[actualUniformNames.length]}.`;
}
if (actualUniformNames.length > expectedUniformNames.length) {
return `Shader block has extra field ${actualUniformNames.length}: ${actualUniformNames[expectedUniformNames.length]}.`;
}
return null;
}
function formatUniformNameList(uniformNames, maxNames = 8) {
if (uniformNames.length <= maxNames) {
return uniformNames.join(", ");
}
const remainingCount = uniformNames.length - maxNames;
return `${uniformNames.slice(0, maxNames).join(", ")}, ... (${remainingCount} more)`;
}
// 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(source4, stage) {
const sourceGLSLVersion = Number(source4.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":
source4 = convertShader(source4, ES300_VERTEX_REPLACEMENTS);
return source4;
case "fragment":
source4 = convertShader(source4, ES300_FRAGMENT_REPLACEMENTS);
return source4;
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(source4, replacements) {
for (const [pattern, replacement] of replacements) {
source4 = source4.replace(pattern, replacement);
}
return source4;
}
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((a, b) => a.order - b.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(source4, defaultName) {
return {
name: getShaderName(source4, defaultName),
language: "glsl",
version: getShaderVersion(source4)
};
}
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(source4) {
let version = 100;
const words = source4.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/wgsl-binding-scan.ts
var WGSL_BINDABLE_VARIABLE_PATTERN = "(?:var<\\s*(uniform|storage(?:\\s*,\\s*[A-Za-z_][A-Za-z0-9_]*)?)\\s*>|var)\\s+([A-Za-z_][A-Za-z0-9_]*)";
var WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN = "\\s*";
var MODULE_WGSL_BINDING_DECLARATION_REGEXES = [
new RegExp(
`@binding\\(\\s*(auto|\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}@group\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
),
new RegExp(
`@group\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}@binding\\(\\s*(auto|\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
)
];
var WGSL_BINDING_DECLARATION_REGEXES = [
new RegExp(
`@binding\\(\\s*(auto|\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}@group\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
),
new RegExp(
`@group\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}@binding\\(\\s*(auto|\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
)
];
var WGSL_EXPLICIT_BINDING_DECLARATION_REGEXES = [
new RegExp(
`@binding\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}@group\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
),
new RegExp(
`@group\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}@binding\\(\\s*(\\d+)\\s*\\)${WGSL_BINDING_DECLARATION_SEPARATOR_PATTERN}${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
)
];
var WGSL_AUTO_BINDING_DECLARATION_REGEXES = [
new RegExp(
`@binding\\(\\s*(auto)\\s*\\)\\s*@group\\(\\s*(\\d+)\\s*\\)\\s*${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
),
new RegExp(
`@group\\(\\s*(\\d+)\\s*\\)\\s*@binding\\(\\s*(auto)\\s*\\)\\s*${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
),
new RegExp(
`@binding\\(\\s*(auto)\\s*\\)\\s*@group\\(\\s*(\\d+)\\s*\\)(?:[\\s\\n\\r]*@[A-Za-z_][^\\n\\r]*)*[\\s\\n\\r]*${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
),
new RegExp(
`@group\\(\\s*(\\d+)\\s*\\)\\s*@binding\\(\\s*(auto)\\s*\\)(?:[\\s\\n\\r]*@[A-Za-z_][^\\n\\r]*)*[\\s\\n\\r]*${WGSL_BINDABLE_VARIABLE_PATTERN}`,
"g"
)
];
function maskWGSLComments(source4) {
const maskedCharacters = source4.split("");
let index = 0;
let blockCommentDepth = 0;
let inLineComment = false;
let inString = false;
let isEscaped = false;
while (index < source4.length) {
const character = source4[index];
const nextCharacter = source4[index + 1];
if (inString) {
if (isEscaped) {
isEscaped = false;
} else if (character === "\\") {
isEscaped = true;
} else if (character === '"') {
inString = false;
}
index++;
continue;
}
if (inLineComment) {
if (character === "\n" || character === "\r") {
inLineComment = false;
} else {
maskedCharacters[index] = " ";
}
index++;
continue;
}
if (blockCommentDepth > 0) {
if (character === "/" && nextCharacter === "*") {
maskedCharacters[index] = " ";
maskedCharacters[index + 1] = " ";
blockCommentDepth++;
index += 2;
continue;
}
if (character === "*" && nextCharacter === "/") {
maskedCharacters[index] = " ";
maskedCharacters[index + 1] = " ";
blockCommentDepth--;
index += 2;
continue;
}
if (character !== "\n" && character !== "\r") {
maskedCharacters[index] = " ";
}
index++;
continue;
}
if (character === '"') {
inString = true;
index++;
continue;
}
if (character === "/" && nextCharacter === "/") {
maskedCharacters[index] = " ";
maskedCharacters[index + 1] = " ";
inLineComment = true;
index += 2;
continue;
}
if (character === "/" && nextCharacter === "*") {
maskedCharacters[index] = " ";
maskedCharacters[index + 1] = " ";
blockCommentDepth = 1;
index += 2;
continue;
}
index++;
}
return maskedCharacters.join("");
}
function getWGSLBindingDeclarationMatches(source4, regexes) {
const maskedSource = maskWGSLComments(source4);
const matches = [];
for (const regex of regexes) {
regex.lastIndex = 0;
let match;
match = regex.exec(maskedSource);
while (match) {
const isBindingFirst = regex === regexes[0];
const index = match.index;
const length = match[0].length;
matches.push({
match: source4.slice(index, index + length),
index,
length,
bindingToken: match[isBindingFirst ? 1 : 2],
groupToken: match[isBindingFirst ? 2 : 1],
accessDeclaration: match[3]?.trim(),
name: match[4]
});
match = regex.exec(maskedSource);
}
}
return matches.sort((left, right) => left.index - right.index);
}
function replaceWGSLBindingDeclarationMatches(source4, regexes, replacer) {
const matches = getWGSLBindingDeclarationMatches(source4, regexes);
if (!matches.length) {
return source4;
}
let relocatedSource = "";
let lastIndex = 0;
for (const match of matches) {
relocatedSource += source4.slice(lastIndex, match.index);
relocatedSource += replacer(match);
lastIndex = match.index + match.length;
}
relocatedSource += source4.slice(lastIndex);
return relocatedSource;
}
function hasWGSLAutoBinding(source4) {
return /@binding\(\s*auto\s*\)/.test(maskWGSLComments(source4));
}
function getFirstWGSLAutoBindingDeclarationMatch(source4, regexes) {
const autoBindingRegexes = regexes === MODULE_WGSL_BINDING_DECLARATION_REGEXES || regexes === WGSL_BINDING_DECLARATION_REGEXES ? WGSL_AUTO_BINDING_DECLARATION_REGEXES : regexes;
return getWGSLBindingDeclarationMatches(source4, autoBindingRegexes).find(
(declarationMatch) => declarationMatch.bindingToken === "auto"
);
}
// src/lib/shader-assembly/wgsl-binding-debug.ts
var WGSL_BINDING_DEBUG_REGEXES = [
new RegExp(
`@binding\\(\\s*(\\d+)\\s*\\)\\s*@group\\(\\s*(\\d+)\\s*\\)\\s*${WGSL_BINDABLE_VARIABLE_PATTERN}\\s*:\\s*([^;]+);`,
"g"
),
new RegExp(
`@group\\(\\s*(\\d+)\\s*\\)\\s*@binding\\(\\s*(\\d+)\\s*\\)\\s*${WGSL_BINDABLE_VARIABLE_PATTERN}\\s*:\\s*([^;]+);`,
"g"
)
];
function getShaderBindingDebugRowsFromWGSL(source4, bindingAssignments = []) {
const maskedSource = maskWGSLComments(source4);
const assignmentMap = /* @__PURE__ */ new Map();
for (const bindingAssignment of bindingAssignments) {
assignmentMap.set(
getBindingAssignmentKey(
bindingAssignment.name,
bindingAssignment.group,
bindingAssignment.location
),
bindingAssignment.moduleName
);
}
const rows = [];
for (const regex of WGSL_BINDING_DEBUG_REGEXES) {
regex.lastIndex = 0;
let match;
match = regex.exec(maskedSource);
while (match) {
const isBindingFirst = regex === WGSL_BINDING_DEBUG_REGEXES[0];
const binding = Number(match[isBindingFirst ? 1 : 2]);
const group = Number(match[isBindingFirst ? 2 : 1]);
const accessDeclaration = match[3]?.trim();
const name = match[4];
const resourceType = match[5].trim();
const moduleName = assignmentMap.get(getBindingAssignmentKey(name, group, binding));
rows.push(
normalizeShaderBindingDebugRow({
name,
group,
binding,
owner: moduleName ? "module" : "application",
moduleName,
accessDeclaration,
resourceType
})
);
match = regex.exec(maskedSource);
}
}
return rows.sort((left, right) => {
if (left.group !== right.group) {
return left.group - right.group;
}
if (left.binding !== right.binding) {
return left.binding - right.binding;
}
return left.name.localeCompare(right.name);
});
}
function normalizeShaderBindingDebugRow(row) {
const baseRow = {
name: row.name,
group: row.group,
binding: row.binding,
owner: row.owner,
kind: "unknown",
moduleName: row.moduleName,
resourceType: row.resourceType
};
if (row.accessDeclaration) {
const access = row.accessDeclaration.split(",").map((value) => value.trim());
if (access[0] === "uniform") {
return { ...baseRow, kind: "uniform", access: "uniform" };
}
if (access[0] === "storage") {
const storageAccess = access[1] || "read_write";
return {
...baseRow,
kind: storageAccess === "read" ? "read-only-storage" : "storage",
access: storageAccess
};
}
}
if (row.resourceType === "sampler" || row.resourceType === "sampler_comparison") {
return {
...baseRow,
kind: "sampler",
samplerKind: row.resourceType === "sampler_comparison" ? "comparison" : "filtering"
};
}
if (row.resourceType.startsWith("texture_storage_")) {
return {
...baseRow,
kind: "storage-texture",
access: getStorageTextureAccess(row.resourceType),
viewDimension: getTextureViewDimension(row.resourceType)
};
}
if (row.resourceType.startsWith("texture_")) {
return {
...baseRow,
kind: "texture",
viewDimension: getTextureViewDimension(row.resourceType),
sampleType: getTextureSampleType(row.resourceType),
multisampled: row.resourceType.startsWith("texture_multisampled_")
};
}
return baseRow;
}
function getBindingAssignmentKey(name, group, binding) {
return `${group}:${binding}:${name}`;
}
function getTextureViewDimension(resourceType) {
if (resourceType.includes("cube_array")) {
return "cube-array";
}
if (resourceType.includes("2d_array")) {
return "2d-array";
}
if (resourceType.includes("cube")) {
return "cube";
}
if (resourceType.includes("3d")) {
return "3d";
}
if (resourceType.includes("2d")) {
return "2d";
}
if (resourceType.includes("1d")) {
return "1d";
}
return void 0;
}
function getTextureSampleType(resourceType) {
if (resourceType.startsWith("texture_depth_")) {
return "depth";
}
if (resourceType.includes("<i32>")) {
return "sint";
}
if (resourceType.includes("<u32>")) {
return "uint";
}
if (resourceType.includes("<f32>")) {
return "float";
}
return void 0;
}
function getStorageTextureAccess(resourceType) {
const match = /,\s*([A-Za-z_][A-Za-z0-9_]*)\s*>$/.exec(resourceType);
return match?.[1];
}
// src/lib/shader-assembly/assemble-shaders.ts
var INJECT_SHADER_DECLARATIONS = `
${DECLARATION_INJECT_MARKER}
`;
var RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT = 100;
var FRAGMENT_SHADER_PROLOGUE = (
/* glsl */
`precision highp float;
`
);
function assembleWGSLShader(options) {
const modules = getShaderModuleDependencies(options.modules || []);
const { source: source4, bindingAssignments } = assembleShaderWGSL(options.platformInfo, {
...options,
source: options.source,
stage: "vertex",
modules
});
return {
source: source4,
getUniforms: assembleGetUniforms(modules),
bindingAssignments,
bindingTable: getShaderBindingDebugRowsFromWGSL(source4, bindingAssignments)
};
}
function assembleGLSLShaderPair(options) {
const { vs: vs4, fs: fs5 } = options;
const modules = getShaderModuleDependencies(options.modules || []);
return {
vs: assembleShaderGLSL(options.platformInfo, {
...options,
source: vs4,
stage: "vertex",
modules
}),
fs: assembleShaderGLSL(options.platformInfo, {
...options,
// @ts-expect-error
source: fs5,
stage: "fragment",
modules
}),
getUniforms: assembleGetUniforms(modules)
};
}
function assembleShaderWGSL(platformInfo, options) {
const {
// id,
source: source4,
stage,
modules,
// defines = {},
hookFunctions = [],
inject = {},
log: log2
} = options;
assert(typeof source4 === "string", "shader source must be a string");
const coreSource = source4;
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;
const applicationRelocation = relocateWGSLApplicationBindings(coreSource);
const usedBindingsByGroup = getUsedBindingsByGroupFromApplicationWGSL(
applicationRelocation.source
);
const reservedBindingKeysByGroup = reserveRegisteredModuleBindings(
modulesToInject,
options._bindingRegistry,
usedBindingsByGroup
);
const bindingAssignments = [];
for (const module of modulesToInject) {
if (log2) {
checkShaderModuleDeprecations(module, coreSource, log2);
}
const relocation = relocateWGSLModuleBindings(
getShaderModuleSource(module, "wgsl", log2),
module,
{
usedBindingsByGroup,
bindingRegistry: options._bindingRegistry,
reservedBindingKeysByGroup
}
);
bindingAssignments.push(...relocation.bindingAssignments);
const moduleSource = relocation.source;
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 += formatWGSLBindingAssignmentComments(bindingAssignments);
assembledSource += applicationRelocation.source;
assembledSource = injectShader(assembledSource, stage, mainInjections);
assertNoUnresolvedAutoBindings(assembledSource);
return { source: assembledSource, bindingAssignments };
}
function assembleShaderGLSL(platformInfo, options) {
const {
source: source4,
stage,
language = "glsl",
modules,
defines = {},
hookFunctions = [],
inject = {},
prologue = true,
log: log2
} = options;
assert(typeof source4 === "string", "shader source must be a string");
const sourceVersion = language === "glsl" ? getShaderInfo(source4).version : -1;
const targetVersion = platformInfo.shaderLanguageVersion;
const sourceVersionDirective = sourceVersion === 100 ? "#version 100" : "#version 300 es";
const sourceLines = source4.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 -------------------------
${`#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 (log2) {
checkShaderModuleDeprecations(module, coreSource, log2);
}
const moduleSource = getShaderModuleSource(module, stage, log2);
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);
}
if (language === "glsl") {
warnIfGLSLUniformBlocksAreNotStd140(assembledSource, stage, log2);
}
return assembledSource.trim();
}
function assembleGetUniforms(modules) {
return function getUniforms4(opts) {
const uniforms = {};
for (const module of modules) {
const moduleUniforms = module.getUniforms?.(opts, uniforms);
Object.assign(uniforms, moduleUniforms);
}
return uniforms;
};
}
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, log2) {
let moduleSource;
switch (stage) {
case "vertex":
moduleSource = module.vs