UNPKG

@luma.gl/shadertools

Version:

Shader module system for luma.gl

1,553 lines (1,519 loc) 470 kB
(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;