UNPKG

@luma.gl/shadertools

Version:

Shader module system for luma.gl

4 lines 372 kB
{ "version": 3, "sources": ["../src/index.ts", "../src/lib/utils/assert.ts", "../src/lib/filters/prop-types.ts", "../src/module-injectors.ts", "../src/lib/shader-assembly/shader-injections.ts", "../src/lib/shader-module/shader-module.ts", "../src/lib/shader-module/shader-module-dependencies.ts", "../src/lib/shader-module/shader-module-uniform-layout.ts", "../src/lib/shader-assembly/platform-defines.ts", "../src/lib/shader-transpiler/transpile-glsl-shader.ts", "../src/lib/shader-assembly/shader-hooks.ts", "../src/lib/glsl-utils/get-shader-info.ts", "../src/lib/shader-assembly/wgsl-binding-scan.ts", "../src/lib/shader-assembly/wgsl-binding-debug.ts", "../src/lib/shader-assembly/assemble-shaders.ts", "../src/lib/preprocessor/preprocessor.ts", "../src/lib/shader-assembler.ts", "../src/lib/glsl-utils/shader-utils.ts", "../src/lib/shader-generator/utils/capitalize.ts", "../src/lib/shader-generator/glsl/generate-glsl.ts", "../src/lib/shader-generator/wgsl/generate-wgsl.ts", "../src/lib/shader-generator/generate-shader.ts", "../src/modules/math/fp16/fp16-utils.ts", "../src/modules/math/fp64/fp64-utils.ts", "../src/lib/color/normalize-byte-colors.ts", "../src/modules/math/random/random.ts", "../src/modules/math/fp32/fp32.ts", "../src/modules/math/fp64/fp64-arithmetic-glsl.ts", "../src/modules/math/fp64/fp64-arithmetic-wgsl.ts", "../src/modules/math/fp64/fp64-functions-glsl.ts", "../src/modules/math/fp64/fp64.ts", "../src/modules/color/float-colors.ts", "../src/modules/engine/picking/picking.ts", "../src/modules/engine/skin/skin.ts", "../src/modules/lighting/lights/lighting.ts", "../src/modules/lighting/lights/lighting-glsl.ts", "../src/modules/lighting/lights/lighting-wgsl.ts", "../src/modules/lighting/ibl/ibl.ts", "../src/modules/lighting/no-material/dirlight.ts", "../src/modules/lighting/lambert-material/lambert-shaders-wgsl.ts", "../src/modules/lighting/lambert-material/lambert-shaders-glsl.ts", "../src/modules/lighting/lambert-material/lambert-material.ts", "../src/modules/lighting/phong-material/phong-shaders-glsl.ts", "../src/modules/lighting/phong-material/phong-shaders-wgsl.ts", "../src/modules/lighting/gouraud-material/gouraud-material.ts", "../src/modules/lighting/phong-material/phong-material.ts", "../src/modules/lighting/pbr-material/pbr-material-glsl.ts", "../src/modules/lighting/pbr-material/pbr-material-wgsl.ts", "../src/modules/lighting/pbr-material/pbr-projection.ts", "../src/modules/lighting/pbr-material/pbr-material.ts", "../src/modules/lighting/pbr-material/pbr-scene.ts"], "sourcesContent": ["// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// shadertools exports\n\n/**\n * Marks GLSL shaders for syntax highlighting: glsl`...`\n * Install https://marketplace.visualstudio.com/items?itemName=boyswan.glsl-literal\n */\nexport type {PlatformInfo} from './lib/shader-assembly/platform-info';\nexport type {ShaderBindingDebugRow} from './lib/shader-assembly/wgsl-binding-debug';\n\n// ShaderModules\n\nexport type {ShaderModule} from './lib/shader-module/shader-module';\nexport type {ShaderPass} from './lib/shader-module/shader-pass';\nexport type {ShaderModuleUniformValue, UniformTypes} from './lib/utils/uniform-types';\n\nexport {initializeShaderModule, initializeShaderModules} from './lib/shader-module/shader-module';\nexport {getShaderModuleUniforms} from './lib/shader-module/shader-module';\nexport {getShaderModuleDependencies} from './lib/shader-module/shader-module-dependencies';\nexport {checkShaderModuleDeprecations} from './lib/shader-module/shader-module';\nexport type {\n GLSLUniformBlockInfo,\n ShaderModuleUniformLayoutStage,\n ShaderModuleUniformLayoutValidationResult\n} from './lib/shader-module/shader-module-uniform-layout';\nexport {\n getGLSLUniformBlocks,\n getShaderModuleUniformBlockFields,\n getShaderModuleUniformBlockName,\n getShaderModuleUniformLayoutValidationResult,\n validateShaderModuleUniformLayout,\n warnIfGLSLUniformBlocksAreNotStd140\n} from './lib/shader-module/shader-module-uniform-layout';\n\nexport {getShaderModuleSource} from './lib/shader-assembly/assemble-shaders';\n\nexport {resolveModules as _resolveModules} from './lib/shader-module/shader-module-dependencies';\nexport {getDependencyGraph as _getDependencyGraph} from './lib/shader-module/shader-module-dependencies';\n\n// ShaderAssembler\nexport {ShaderAssembler} from './lib/shader-assembler';\nexport type {ShaderHook} from './lib/shader-assembly/shader-hooks';\nexport type {ShaderInjection} from './lib/shader-assembly/shader-injections';\n\n// SHADER HELPERS\n\n// Shader source introspection\nexport {getShaderInfo} from './lib/glsl-utils/get-shader-info';\nexport {\n getQualifierDetails,\n getPassthroughFS,\n typeToChannelSuffix,\n typeToChannelCount,\n convertToVec4\n} from './lib/glsl-utils/shader-utils';\n\n// EXPERIMENTAL - Do not use in production applications\nexport type {ShaderGenerationOptions} from './lib/shader-generator/generate-shader';\nexport {generateShaderForModule} from './lib/shader-generator/generate-shader';\nexport {capitalize} from './lib/shader-generator/utils/capitalize';\n\n// TEST EXPORTS - Do not use in production applications\nexport {preprocess} from './lib/preprocessor/preprocessor';\nexport {assembleGLSLShaderPair} from './lib/shader-assembly/assemble-shaders';\nexport {combineInjects} from './lib/shader-assembly/shader-injections';\n\n// data utils\nexport {toHalfFloat, fromHalfFloat} from './modules/math/fp16/fp16-utils';\nexport {fp64ify, fp64LowPart, fp64ifyMatrix4} from './modules/math/fp64/fp64-utils';\nexport {\n normalizeByteColor3,\n normalizeByteColor4,\n resolveUseByteColors\n} from './lib/color/normalize-byte-colors';\n\n// math libraries\nexport {random} from './modules/math/random/random';\n\nexport {fp32} from './modules/math/fp32/fp32';\nexport {fp64, fp64arithmetic} from './modules/math/fp64/fp64';\nexport type {FloatColorsProps, FloatColorsUniforms} from './modules/color/float-colors';\nexport {floatColors} from './modules/color/float-colors';\n\n// engine shader modules\n\n// projection\n// export type {ProjectionUniforms} from './modules/engine/project/project';\n// export {projection} from './modules/engine/project/project';\nexport type {PickingProps, PickingUniforms} from './modules/engine/picking/picking';\nexport {picking} from './modules/engine/picking/picking';\nexport {skin} from './modules/engine/skin/skin';\n\n// lighting\nexport {\n type Light,\n type AmbientLight,\n type PointLight,\n type SpotLight,\n type DirectionalLight,\n type LightingLightUniform\n} from './modules/lighting/lights/lighting';\n\nexport type {LightingProps, LightingUniforms} from './modules/lighting/lights/lighting';\nexport {lighting} from './modules/lighting/lights/lighting';\nexport type {IBLBindings} from './modules/lighting/ibl/ibl';\nexport {ibl} from './modules/lighting/ibl/ibl';\nexport {dirlight} from './modules/lighting/no-material/dirlight';\nexport type {LambertMaterialProps} from './modules/lighting/lambert-material/lambert-material';\nexport {lambertMaterial} from './modules/lighting/lambert-material/lambert-material';\nexport type {GouraudMaterialProps} from './modules/lighting/gouraud-material/gouraud-material';\nexport {gouraudMaterial} from './modules/lighting/gouraud-material/gouraud-material';\nexport type {PhongMaterialProps} from './modules/lighting/phong-material/phong-material';\nexport {phongMaterial} from './modules/lighting/phong-material/phong-material';\nexport type {\n PBRMaterialBindings,\n PBRMaterialProps,\n PBRMaterialUniforms\n} from './modules/lighting/pbr-material/pbr-material';\nexport type {\n PBRSceneBindings,\n PBRSceneProps,\n PBRSceneUniforms\n} from './modules/lighting/pbr-material/pbr-scene';\nexport type {PBRProjectionProps} from './modules/lighting/pbr-material/pbr-projection';\n\nexport {pbrMaterial} from './modules/lighting/pbr-material/pbr-material';\nexport {pbrScene} from './modules/lighting/pbr-material/pbr-scene';\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// Recommendation is to ignore message but current test suite checks agains the\n// message so keep it for now.\nexport function assert(condition: unknown, message?: string): void {\n if (!condition) {\n const error = new Error(message || 'shadertools: assertion failed.');\n Error.captureStackTrace?.(error, assert);\n throw error;\n }\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {assert} from '../utils/assert';\n\n/**\n * For use by shader module and shader pass writers to describe the types of the\n * properties they expose (properties ultimately map to uniforms).\n */\nexport type PropType =\n | {\n type?: string;\n value?: unknown;\n max?: number;\n min?: number;\n softMax?: number;\n softMin?: number;\n hint?: string;\n /** @deprecated internal uniform */\n private?: boolean;\n }\n | number;\n\n/**\n * Internal property validators generated by processing the prop types ,\n * The `validate()` method can be used to validate the type of properties passed in to\n * shader module or shader pass\n */\nexport type PropValidator = {\n type: string;\n value?: unknown;\n max?: number;\n min?: number;\n private?: boolean;\n validate?(value: unknown, propDef: PropValidator): boolean;\n};\n\n/** Minimal validators for number and array types */\nconst DEFAULT_PROP_VALIDATORS: Record<string, PropValidator> = {\n number: {\n type: 'number',\n validate(value: unknown, propType: PropType) {\n return (\n Number.isFinite(value) &&\n typeof propType === 'object' &&\n (propType.max === undefined || (value as number) <= propType.max) &&\n (propType.min === undefined || (value as number) >= propType.min)\n );\n }\n },\n array: {\n type: 'array',\n validate(value: unknown, propType: PropType) {\n return Array.isArray(value) || ArrayBuffer.isView(value);\n }\n }\n};\n\n/**\n * Parse a list of property types into property definitions that can be used to validate\n * values passed in by applications.\n * @param propTypes\n * @returns\n */\nexport function makePropValidators(\n propTypes: Record<string, PropType>\n): Record<string, PropValidator> {\n const propValidators: Record<string, PropValidator> = {};\n for (const [name, propType] of Object.entries(propTypes)) {\n propValidators[name] = makePropValidator(propType);\n }\n return propValidators;\n}\n\n/**\n * Validate a map of user supplied properties against a map of validators\n * Inject default values when user doesn't supply a property\n * @param properties\n * @param propValidators\n * @returns\n */\nexport function getValidatedProperties(\n properties: Record<string, unknown>,\n propValidators: Record<string, PropValidator>,\n errorMessage: string\n): Record<string, unknown> {\n const validated: Record<string, unknown> = {};\n\n for (const [key, propsValidator] of Object.entries(propValidators)) {\n if (properties && key in properties && !propsValidator.private) {\n if (propsValidator.validate) {\n assert(\n propsValidator.validate(properties[key], propsValidator),\n `${errorMessage}: invalid ${key}`\n );\n }\n validated[key] = properties[key];\n } else {\n // property not supplied - use default value\n validated[key] = propsValidator.value;\n }\n }\n\n // TODO - warn for unused properties that don't match a validator?\n\n return validated;\n}\n\n/**\n * Creates a property validator for a prop type. Either contains:\n * - a valid prop type object ({type, ...})\n * - or just a default value, in which case type and name inference is used\n */\nfunction makePropValidator(propType: PropType): PropValidator {\n let type = getTypeOf(propType);\n\n if (type !== 'object') {\n return {value: propType, ...DEFAULT_PROP_VALIDATORS[type], type};\n }\n\n // Special handling for objects\n if (typeof propType === 'object') {\n if (!propType) {\n return {type: 'object', value: null};\n }\n if (propType.type !== undefined) {\n return {...propType, ...DEFAULT_PROP_VALIDATORS[propType.type], type: propType.type};\n }\n // If no type and value this object is likely the value\n if (propType.value === undefined) {\n return {type: 'object', value: propType};\n }\n\n type = getTypeOf(propType.value);\n return {...propType, ...DEFAULT_PROP_VALIDATORS[type], type};\n }\n\n throw new Error('props');\n}\n\n/**\n * \"improved\" version of javascript typeof that can distinguish arrays and null values\n */\nfunction getTypeOf(value: unknown): string {\n if (Array.isArray(value) || ArrayBuffer.isView(value)) {\n return 'array';\n }\n return typeof value;\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nexport const MODULE_INJECTORS_VS = /* glsl */ `\\\n#ifdef MODULE_LOGDEPTH\n logdepth_adjustPosition(gl_Position);\n#endif\n`;\n\nexport const MODULE_INJECTORS_FS = /* glsl */ `\\\n#ifdef MODULE_MATERIAL\n fragColor = material_filterColor(fragColor);\n#endif\n\n#ifdef MODULE_LIGHTING\n fragColor = lighting_filterColor(fragColor);\n#endif\n\n#ifdef MODULE_FOG\n fragColor = fog_filterColor(fragColor);\n#endif\n\n#ifdef MODULE_PICKING\n fragColor = picking_filterHighlightColor(fragColor);\n fragColor = picking_filterPickingColor(fragColor);\n#endif\n\n#ifdef MODULE_LOGDEPTH\n logdepth_setFragDepth();\n#endif\n`;\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {MODULE_INJECTORS_VS, MODULE_INJECTORS_FS} from '../../module-injectors';\nimport {assert} from '../utils/assert';\n\n// TODO - experimental\nconst MODULE_INJECTORS = {\n vertex: MODULE_INJECTORS_VS,\n fragment: MODULE_INJECTORS_FS\n};\n\nconst REGEX_START_OF_MAIN = /void\\s+main\\s*\\([^)]*\\)\\s*\\{\\n?/; // Beginning of main\nconst REGEX_END_OF_MAIN = /}\\n?[^{}]*$/; // End of main, assumes main is last function\nconst fragments: string[] = [];\n\nexport const DECLARATION_INJECT_MARKER = '__LUMA_INJECT_DECLARATIONS__';\n\n/**\n *\n */\nexport type ShaderInjection = {\n injection: string;\n order: number;\n};\n\n/**\n * ShaderInjections, parsed and split per shader\n */\nexport type ShaderInjections = {\n vertex: Record<string, ShaderInjection>;\n fragment: Record<string, ShaderInjection>;\n};\n\n/**\n *\n */\nexport function normalizeInjections(\n injections: Record<string, string | ShaderInjection>\n): ShaderInjections {\n const result: ShaderInjections = {vertex: {}, fragment: {}};\n\n for (const hook in injections) {\n let injection = injections[hook];\n const stage = getHookStage(hook);\n if (typeof injection === 'string') {\n injection = {\n order: 0,\n injection\n };\n }\n\n result[stage][hook] = injection;\n }\n\n return result;\n}\n\nfunction getHookStage(hook: string): 'vertex' | 'fragment' {\n const type = hook.slice(0, 2);\n switch (type) {\n case 'vs':\n return 'vertex';\n case 'fs':\n return 'fragment';\n default:\n throw new Error(type);\n }\n}\n\n/**\n// A minimal shader injection/templating system.\n// RFC: https://github.com/visgl/luma.gl/blob/7.0-release/dev-docs/RFCs/v6.0/shader-injection-rfc.md\n * @param source \n * @param type \n * @param inject \n * @param injectStandardStubs \n * @returns \n */\n// eslint-disable-next-line complexity\nexport function injectShader(\n source: string,\n stage: 'vertex' | 'fragment',\n inject: Record<string, ShaderInjection[]>,\n injectStandardStubs = false\n): string {\n const isVertex = stage === 'vertex';\n\n for (const key in inject) {\n const fragmentData = inject[key];\n fragmentData.sort((a: ShaderInjection, b: ShaderInjection): number => a.order - b.order);\n fragments.length = fragmentData.length;\n for (let i = 0, len = fragmentData.length; i < len; ++i) {\n fragments[i] = fragmentData[i].injection;\n }\n const fragmentString = `${fragments.join('\\n')}\\n`;\n switch (key) {\n // declarations are injected before the main function\n case 'vs:#decl':\n if (isVertex) {\n source = source.replace(DECLARATION_INJECT_MARKER, fragmentString);\n }\n break;\n // inject code at the beginning of the main function\n case 'vs:#main-start':\n if (isVertex) {\n source = source.replace(REGEX_START_OF_MAIN, (match: string) => match + fragmentString);\n }\n break;\n // inject code at the end of main function\n case 'vs:#main-end':\n if (isVertex) {\n source = source.replace(REGEX_END_OF_MAIN, (match: string) => fragmentString + match);\n }\n break;\n // declarations are injected before the main function\n case 'fs:#decl':\n if (!isVertex) {\n source = source.replace(DECLARATION_INJECT_MARKER, fragmentString);\n }\n break;\n // inject code at the beginning of the main function\n case 'fs:#main-start':\n if (!isVertex) {\n source = source.replace(REGEX_START_OF_MAIN, (match: string) => match + fragmentString);\n }\n break;\n // inject code at the end of main function\n case 'fs:#main-end':\n if (!isVertex) {\n source = source.replace(REGEX_END_OF_MAIN, (match: string) => fragmentString + match);\n }\n break;\n\n default:\n // TODO(Tarek): I think this usage should be deprecated.\n\n // inject code after key, leaving key in place\n source = source.replace(key, (match: string) => match + fragmentString);\n }\n }\n\n // Remove if it hasn't already been replaced\n source = source.replace(DECLARATION_INJECT_MARKER, '');\n\n // Finally, if requested, insert an automatic module injector chunk\n if (injectStandardStubs) {\n source = source.replace(/\\}\\s*$/, (match: string) => match + MODULE_INJECTORS[stage]);\n }\n\n return source;\n}\n\n// Takes an array of inject objects and combines them into one\nexport function combineInjects(injects: any[]): Record<string, string> {\n const result: Record<string, string> = {};\n assert(Array.isArray(injects) && injects.length > 1);\n injects.forEach(inject => {\n for (const key in inject) {\n result[key] = result[key] ? `${result[key]}\\n${inject[key]}` : inject[key];\n }\n });\n return result;\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {UniformFormat} from '../../types';\nimport {\n PropType,\n PropValidator,\n makePropValidators,\n getValidatedProperties\n} from '../filters/prop-types';\nimport type {ShaderModuleUniformValue, UniformTypes, UniformValue} from '../utils/uniform-types';\nimport {ShaderInjection, normalizeInjections} from '../shader-assembly/shader-injections';\n\n// To avoid dependency on core module, do not import `Binding` type.\n// The ShaderModule is not concerned with the type of `Binding`,\n// it is the repsonsibility of `splitUniformsAndBindings` in\n// ShaderInputs to type the result of `getUniforms()`\ntype Binding = unknown; // import type {Binding} from '@luma.gl/core';\n\nexport type UniformInfo = {\n format?: UniformFormat;\n} & PropType;\n\nexport type ShaderModuleBindingLayout = {\n name: string;\n group: number;\n};\n\n// Helper types\ntype BindingKeys<T> = {[K in keyof T]: T[K] extends UniformValue ? never : K}[keyof T];\ntype UniformKeys<T> = {[K in keyof T]: T[K] extends UniformValue ? K : never}[keyof T];\nexport type PickBindings<T> = {[K in BindingKeys<Required<T>>]: T[K]};\nexport type PickUniforms<T> = {[K in UniformKeys<Required<T>>]: T[K]};\n\n/**\n * A shader module definition object\n *\n * @note Needs to be initialized with `initializeShaderModules`\n * @note `UniformsT` & `BindingsT` are deduced from `PropsT` by default. If\n * a custom type for `UniformsT` is used, `BindingsT` should be also be provided.\n */\nexport type ShaderModule<\n PropsT extends Record<string, any> = Record<string, any>,\n UniformsT extends Record<string, ShaderModuleUniformValue> = PickUniforms<PropsT>,\n BindingsT extends Record<string, Binding> = PickBindings<PropsT>\n> = {\n /** Used for type inference not for values */\n props?: PropsT;\n /** Used for type inference, not currently used for values */\n uniforms?: UniformsT;\n /** Used for type inference, not currently used for values */\n bindings?: BindingsT;\n /** Logical bind-group assignment for bindings declared by this module */\n bindingLayout?: readonly ShaderModuleBindingLayout[];\n /** Preferred starting binding slot for this module's WGSL `@binding(auto)` declarations. */\n firstBindingSlot?: number;\n\n name: string;\n\n /** WGSL code */\n source?: string;\n /** GLSL fragment shader code */\n fs?: string;\n /** GLSL vertex shader code */\n vs?: string;\n\n /** Uniform shader types @note: Both order and types MUST match uniform block declarations in shader */\n uniformTypes?: Required<UniformTypes<UniformsT>>; // Record<keyof UniformsT, UniformFormat>;\n /** Uniform JS prop types */\n propTypes?: Record<keyof UniformsT, UniformInfo>;\n /** Default uniform values */\n defaultUniforms?: Required<UniformsT>; // Record<keyof UniformsT, UniformValue>;\n\n /** Function that maps props to uniforms & bindings */\n getUniforms?: (\n props: Partial<PropsT>,\n prevUniforms?: UniformsT\n ) => Partial<UniformsT & BindingsT>;\n\n defines?: Record<string, boolean | number>;\n /** Injections */\n inject?: Record<string, string | {injection: string; order: number}>;\n dependencies?: ShaderModule<any, any>[];\n /** Information on deprecated properties */\n deprecations?: ShaderModuleDeprecation[];\n\n /** The instance field contains information that is generated at run-time */\n instance?: {\n propValidators?: Record<string, PropValidator>;\n parsedDeprecations: ShaderModuleDeprecation[];\n\n normalizedInjections: {\n vertex: Record<string, ShaderInjection>;\n fragment: Record<string, ShaderInjection>;\n };\n };\n};\n\n/** Use to generate deprecations when shader module is used */\nexport type ShaderModuleDeprecation = {\n type: string;\n regex?: RegExp;\n new: string;\n old: string;\n deprecated?: boolean;\n};\n\n// SHNDER MODULE API\n\nexport function initializeShaderModules(modules: ShaderModule[]): void {\n modules.map((module: ShaderModule) => initializeShaderModule(module));\n}\n\nexport function initializeShaderModule(module: ShaderModule): void {\n if (module.instance) {\n return;\n }\n\n initializeShaderModules(module.dependencies || []);\n\n const {\n propTypes = {},\n deprecations = [],\n // defines = {},\n inject = {}\n } = module;\n\n const instance: Required<ShaderModule>['instance'] = {\n normalizedInjections: normalizeInjections(inject),\n parsedDeprecations: parseDeprecationDefinitions(deprecations)\n };\n\n if (propTypes) {\n instance.propValidators = makePropValidators(propTypes);\n }\n\n module.instance = instance;\n\n // TODO(ib) - we need to apply the original prop types to the default uniforms\n let defaultProps: ShaderModule['props'] = {};\n if (propTypes) {\n defaultProps = Object.entries(propTypes).reduce(\n (obj: ShaderModule['props'], [key, propType]) => {\n // @ts-expect-error\n const value = propType?.value;\n if (value) {\n // @ts-expect-error\n obj[key] = value;\n }\n return obj;\n },\n {} as ShaderModule['props']\n );\n }\n\n module.defaultUniforms = {...module.defaultUniforms, ...defaultProps} as any;\n}\n\n/** Convert module props to uniforms */\nexport function getShaderModuleUniforms<\n ShaderModuleT extends ShaderModule<\n Record<string, unknown>,\n Record<string, ShaderModuleUniformValue>\n >\n>(\n module: ShaderModuleT,\n props?: ShaderModuleT['props'],\n oldUniforms?: ShaderModuleT['uniforms']\n): Record<string, Binding | ShaderModuleUniformValue> {\n initializeShaderModule(module);\n\n const uniforms = oldUniforms || {...module.defaultUniforms};\n // If module has a getUniforms function, use it\n if (props && module.getUniforms) {\n return module.getUniforms(props, uniforms);\n }\n\n // Build uniforms from the uniforms array\n // @ts-expect-error\n return getValidatedProperties(props, module.instance?.propValidators, module.name);\n}\n\n/* TODO this looks like it was unused code\n _defaultGetUniforms(opts: Record<string, any> = {}): Record<string, any> {\n const uniforms: Record<string, any> = {};\n const propTypes = this.uniforms;\n\n for (const key in propTypes) {\n const propDef = propTypes[key];\n if (key in opts && !propDef.private) {\n if (propDef.validate) {\n assert(propDef.validate(opts[key], propDef), `${this.name}: invalid ${key}`);\n }\n uniforms[key] = opts[key];\n } else {\n uniforms[key] = propDef.value;\n }\n }\n\n return uniforms;\n }\n}\n*/\n// Warn about deprecated uniforms or functions\nexport function checkShaderModuleDeprecations(\n shaderModule: ShaderModule,\n shaderSource: string,\n log: any\n): void {\n shaderModule.deprecations?.forEach(def => {\n if (def.regex?.test(shaderSource)) {\n if (def.deprecated) {\n log.deprecated(def.old, def.new)();\n } else {\n log.removed(def.old, def.new)();\n }\n }\n });\n}\n\n// HELPERS\n\nfunction parseDeprecationDefinitions(deprecations: ShaderModuleDeprecation[]) {\n deprecations.forEach(def => {\n switch (def.type) {\n case 'function':\n def.regex = new RegExp(`\\\\b${def.old}\\\\(`);\n break;\n default:\n def.regex = new RegExp(`${def.type} ${def.old};`);\n }\n });\n\n return deprecations;\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {ShaderModule, initializeShaderModules} from './shader-module';\n\n// import type {ShaderModule} from '../shader-module/shader-module';\n\ntype AbstractModule = {\n name: string;\n dependencies?: AbstractModule[];\n};\n\n/**\n * Takes a list of shader module names and returns a new list of\n * shader module names that includes all dependencies, sorted so\n * that modules that are dependencies of other modules come first.\n *\n * If the shader glsl code from the returned modules is concatenated\n * in the reverse order, it is guaranteed that all functions be resolved and\n * that all function and variable definitions come before use.\n *\n * @param modules - Array of modules (inline modules or module names)\n * @return - Array of modules\n */\nexport function getShaderModuleDependencies<T extends AbstractModule>(modules: T[]): T[] {\n initializeShaderModules(modules);\n const moduleMap: Record<string, T> = {};\n const moduleDepth: Record<string, number> = {};\n getDependencyGraph({modules, level: 0, moduleMap, moduleDepth});\n\n // Return a reverse sort so that dependencies come before the modules that use them\n const dependencies = Object.keys(moduleDepth)\n .sort((a, b) => moduleDepth[b] - moduleDepth[a])\n .map(name => moduleMap[name]);\n initializeShaderModules(dependencies);\n return dependencies;\n}\n\n/**\n * Recursively checks module dependencies to calculate dependency level of each module.\n *\n * @param options.modules - Array of modules\n * @param options.level - Current level\n * @param options.moduleMap -\n * @param options.moduleDepth - Current level\n * @return - Map of module name to its level\n */\n// Adds another level of dependencies to the result map\nexport function getDependencyGraph<T extends AbstractModule>(options: {\n modules: T[];\n level: number;\n moduleMap: Record<string, T>;\n moduleDepth: Record<string, number>;\n}) {\n const {modules, level, moduleMap, moduleDepth} = options;\n if (level >= 5) {\n throw new Error('Possible loop in shader dependency graph');\n }\n\n // Update level on all current modules\n for (const module of modules) {\n moduleMap[module.name] = module;\n if (moduleDepth[module.name] === undefined || moduleDepth[module.name] < level) {\n moduleDepth[module.name] = level;\n }\n }\n\n // Recurse\n for (const module of modules) {\n if (module.dependencies) {\n getDependencyGraph({modules: module.dependencies, level: level + 1, moduleMap, moduleDepth});\n }\n }\n}\n\n/**\n * Takes a list of shader module names and returns a new list of\n * shader module names that includes all dependencies, sorted so\n * that modules that are dependencies of other modules come first.\n *\n * If the shader glsl code from the returned modules is concatenated\n * in the reverse order, it is guaranteed that all functions be resolved and\n * that all function and variable definitions come before use.\n *\n * @param modules - Array of modules (inline modules or module names)\n * @return - Array of modules\n */\nexport function getShaderDependencies(modules: ShaderModule[]): ShaderModule[] {\n initializeShaderModules(modules);\n const moduleMap: Record<string, ShaderModule> = {};\n const moduleDepth: Record<string, number> = {};\n getDependencyGraph({modules, level: 0, moduleMap, moduleDepth});\n\n // Return a reverse sort so that dependencies come before the modules that use them\n modules = Object.keys(moduleDepth)\n .sort((a, b) => moduleDepth[b] - moduleDepth[a])\n .map(name => moduleMap[name]);\n initializeShaderModules(modules);\n return modules;\n}\n\n// DEPRECATED\n\n/**\n * Instantiate shader modules and resolve any dependencies\n * @deprecated Use getShaderDpendencies\n */\nexport function resolveModules(modules: ShaderModule[]): ShaderModule[] {\n return getShaderDependencies(modules);\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport type {ShaderModule} from './shader-module';\nimport {assert} from '../utils/assert';\n\n/**\n * Shader stages supported by shader-module uniform-layout validation helpers.\n */\nexport type ShaderModuleUniformLayoutStage = 'vertex' | 'fragment' | 'wgsl';\n\n/**\n * Describes the result of comparing declared `uniformTypes` with the fields\n * found in a shader uniform block.\n */\nexport type ShaderModuleUniformLayoutValidationResult = {\n /** Name of the shader module being validated. */\n moduleName: string;\n /** Expected block name derived from the shader module name. */\n uniformBlockName: string;\n /** Shader stage that was inspected. */\n stage: ShaderModuleUniformLayoutStage;\n /** Field names declared by the module metadata. */\n expectedUniformNames: string[];\n /** Field names parsed from the shader source. */\n actualUniformNames: string[];\n /** Whether the declared and parsed field lists match exactly. */\n matches: boolean;\n};\n\n/**\n * Parsed information about one GLSL uniform block declaration.\n */\nexport type GLSLUniformBlockInfo = {\n /** Declared block type name. */\n blockName: string;\n /** Optional instance name that follows the block declaration. */\n instanceName: string | null;\n /** Raw layout qualifier text, if present. */\n layoutQualifier: string | null;\n /** Whether any explicit layout qualifier was present. */\n hasLayoutQualifier: boolean;\n /** Whether the block explicitly declares `layout(std140)`. */\n isStd140: boolean;\n /** Raw source text inside the block braces. */\n body: string;\n};\n\n/**\n * Logging surface used by validation and warning helpers.\n */\ntype Logger = {\n /** Error logger compatible with luma's deferred log API. */\n error?: (...args: unknown[]) => () => unknown;\n /** Warning logger compatible with luma's deferred log API. */\n warn?: (...args: unknown[]) => () => unknown;\n};\n\n/**\n * Matches one field declaration inside a GLSL uniform block body.\n */\nconst GLSL_UNIFORM_BLOCK_FIELD_REGEXP =\n /^(?:uniform\\s+)?(?:(?:lowp|mediump|highp)\\s+)?[A-Za-z0-9_]+(?:<[^>]+>)?\\s+([A-Za-z0-9_]+)(?:\\s*\\[[^\\]]+\\])?\\s*;/;\n/**\n * Matches full GLSL uniform block declarations, including optional layout qualifiers.\n */\nconst GLSL_UNIFORM_BLOCK_REGEXP =\n /((?: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;\n\n/**\n * Returns the uniform block type name expected for the supplied shader module.\n */\nexport function getShaderModuleUniformBlockName(module: ShaderModule): string {\n return `${module.name}Uniforms`;\n}\n\n/**\n * Returns the ordered field names parsed from a shader module's uniform block.\n *\n * @returns `null` when the stage has no source or the expected block is absent.\n */\nexport function getShaderModuleUniformBlockFields(\n module: ShaderModule,\n stage: ShaderModuleUniformLayoutStage\n): string[] | null {\n const shaderSource =\n stage === 'wgsl' ? module.source : stage === 'vertex' ? module.vs : module.fs;\n\n if (!shaderSource) {\n return null;\n }\n\n const uniformBlockName = getShaderModuleUniformBlockName(module);\n return extractShaderUniformBlockFieldNames(\n shaderSource,\n stage === 'wgsl' ? 'wgsl' : 'glsl',\n uniformBlockName\n );\n}\n\n/**\n * Computes the validation result for a shader module's declared and parsed\n * uniform-block field lists.\n *\n * @returns `null` when the module has no declared uniform types or no matching block.\n */\nexport function getShaderModuleUniformLayoutValidationResult(\n module: ShaderModule,\n stage: ShaderModuleUniformLayoutStage\n): ShaderModuleUniformLayoutValidationResult | null {\n const expectedUniformNames = Object.keys(module.uniformTypes || {});\n if (!expectedUniformNames.length) {\n return null;\n }\n\n const actualUniformNames = getShaderModuleUniformBlockFields(module, stage);\n if (!actualUniformNames) {\n return null;\n }\n\n return {\n moduleName: module.name,\n uniformBlockName: getShaderModuleUniformBlockName(module),\n stage,\n expectedUniformNames,\n actualUniformNames,\n matches: areStringArraysEqual(expectedUniformNames, actualUniformNames)\n };\n}\n\n/**\n * Validates that a shader module's parsed uniform block matches `uniformTypes`.\n *\n * When a mismatch is detected, the helper logs a formatted error and optionally\n * throws via {@link assert}.\n */\nexport function validateShaderModuleUniformLayout(\n module: ShaderModule,\n stage: ShaderModuleUniformLayoutStage,\n options: {\n log?: Logger;\n throwOnError?: boolean;\n } = {}\n): ShaderModuleUniformLayoutValidationResult | null {\n const validationResult = getShaderModuleUniformLayoutValidationResult(module, stage);\n if (!validationResult || validationResult.matches) {\n return validationResult;\n }\n\n const message = formatShaderModuleUniformLayoutError(validationResult);\n options.log?.error?.(message, validationResult)();\n\n if (options.throwOnError !== false) {\n assert(false, message);\n }\n\n return validationResult;\n}\n\n/**\n * Parses all GLSL uniform blocks in a shader source string.\n */\nexport function getGLSLUniformBlocks(shaderSource: string): GLSLUniformBlockInfo[] {\n const blocks: GLSLUniformBlockInfo[] = [];\n const uncommentedSource = stripShaderComments(shaderSource);\n\n for (const sourceMatch of uncommentedSource.matchAll(GLSL_UNIFORM_BLOCK_REGEXP)) {\n const layoutQualifier = sourceMatch[1]?.trim() || null;\n blocks.push({\n blockName: sourceMatch[2],\n body: sourceMatch[3],\n instanceName: sourceMatch[4] || null,\n layoutQualifier,\n hasLayoutQualifier: Boolean(layoutQualifier),\n isStd140: Boolean(\n layoutQualifier && /\\blayout\\s*\\([^)]*\\bstd140\\b[^)]*\\)/.exec(layoutQualifier)\n )\n });\n }\n\n return blocks;\n}\n\n/**\n * Emits warnings for GLSL uniform blocks that do not explicitly declare\n * `layout(std140)`.\n *\n * @returns The list of parsed blocks that were considered non-compliant.\n */\nexport function warnIfGLSLUniformBlocksAreNotStd140(\n shaderSource: string,\n stage: Exclude<ShaderModuleUniformLayoutStage, 'wgsl'>,\n log?: Logger,\n context?: {label?: string}\n): GLSLUniformBlockInfo[] {\n const nonStd140Blocks = getGLSLUniformBlocks(shaderSource).filter(block => !block.isStd140);\n const seenBlockNames = new Set<string>();\n\n for (const block of nonStd140Blocks) {\n if (seenBlockNames.has(block.blockName)) {\n continue;\n }\n seenBlockNames.add(block.blockName);\n\n const shaderLabel = context?.label ? `${context.label} ` : '';\n const actualLayout = block.hasLayoutQualifier\n ? `declares ${normalizeWhitespace(block.layoutQualifier!)} instead of layout(std140)`\n : 'does not declare layout(std140)';\n const message = `${shaderLabel}${stage} shader uniform block ${\n block.blockName\n } ${actualLayout}. luma.gl host-side shader block packing assumes explicit layout(std140) for GLSL uniform blocks. Add \\`layout(std140)\\` to the block declaration.`;\n log?.warn?.(message, block)();\n }\n\n return nonStd140Blocks;\n}\n\n/**\n * Extracts field names from the named GLSL or WGSL uniform block/struct.\n */\nfunction extractShaderUniformBlockFieldNames(\n shaderSource: string,\n language: 'glsl' | 'wgsl',\n uniformBlockName: string\n): string[] | null {\n const sourceBody =\n language === 'wgsl'\n ? extractWGSLStructBody(shaderSource, uniformBlockName)\n : extractGLSLUniformBlockBody(shaderSource, uniformBlockName);\n\n if (!sourceBody) {\n return null;\n }\n\n const fieldNames: string[] = [];\n\n for (const sourceLine of sourceBody.split('\\n')) {\n const line = sourceLine.replace(/\\/\\/.*$/, '').trim();\n if (!line || line.startsWith('#')) {\n continue;\n }\n\n const fieldMatch =\n language === 'wgsl'\n ? line.match(/^([A-Za-z0-9_]+)\\s*:/)\n : line.match(GLSL_UNIFORM_BLOCK_FIELD_REGEXP);\n\n if (fieldMatch) {\n fieldNames.push(fieldMatch[1]);\n }\n }\n\n return fieldNames;\n}\n\n/**\n * Extracts the body of a WGSL struct with the supplied name.\n */\nfunction extractWGSLStructBody(shaderSource: string, uniformBlockName: string): string | null {\n const structMatch = new RegExp(`\\\\bstruct\\\\s+${uniformBlockName}\\\\b`, 'm').exec(shaderSource);\n if (!structMatch) {\n return null;\n }\n\n const openBraceIndex = shaderSource.indexOf('{', structMatch.index);\n if (openBraceIndex < 0) {\n return null;\n }\n\n let braceDepth = 0;\n for (let index = openBraceIndex; index < shaderSource.length; index++) {\n const character = shaderSource[index];\n if (character === '{') {\n braceDepth++;\n continue;\n }\n if (character !== '}') {\n continue;\n }\n\n braceDepth--;\n if (braceDepth === 0) {\n return shaderSource.slice(openBraceIndex + 1, index);\n }\n }\n\n return null;\n}\n\n/**\n * Extracts the body of a named GLSL uniform block from shader source.\n */\nfunction extractGLSLUniformBlockBody(\n shaderSource: string,\n uniformBlockName: string\n): string | null {\n const block = getGLSLUniformBlocks(shaderSource).find(\n candidate => candidate.blockName === uniformBlockName\n );\n return block?.body || null;\n}\n\n/**\n * Returns `true` when the two arrays contain the same strings in the same order.\n */\nfunction areStringArraysEqual(leftValues: string[], rightValues: string[]): boolean {\n if (leftValues.length !== rightValues.length) {\n return false;\n }\n\n for (let valueIndex = 0; valueIndex < leftValues.length; valueIndex++) {\n if (leftValues[valueIndex] !== rightValues[valueIndex]) {\n return false;\n }\n }\n\n return true;\n}\n\n/**\n * Formats the standard validation error message for a shader-module layout mismatch.\n */\nfunction formatShaderModuleUniformLayoutError(\n validationResult: ShaderModuleUniformLayoutValidationResult\n): string {\n const {expectedUniformNames, actualUniformNames} = validationResult;\n const missingUniformNames = expectedUniformNames.filter(\n uniformName => !actualUniformNames.includes(uniformName)\n );\n const unexpectedUniformNames = actualUniformNames.filter(\n uniformName => !expectedUniformNames.includes(uniformName)\n );\n const mismatchDetails = [\n `Expected ${expectedUniformNames.length} fields, found ${actualUniformNames.length}.`\n ];\n const firstMismatchDescription = getFirstUniformMismatchDescription(\n expectedUniformNames,\n actualUniformNames\n );\n if (firstMismatchDescription) {\n mismatchDetails.push(firstMismatchDescription);\n }\n if (missingUniformNames.length) {\n mismatchDetails.push(\n `Missing from shader block (${missingUniformNames.length}): ${formatUniformNameList(\n missingUniformNames\n )}.`\n );\n }\n if (unexpectedUniformNames.length) {\n mismatchDetails.push(\n `Unexpected in shader block (${unexpectedUniformNames.length}): ${formatUniformNameList(\n unexpectedUniformNames\n )}.`\n );\n }\n if (\n expectedUniformNames.length <= 12 &&\n actualUniformNames.length <= 12 &&\n (missingUniformNames.length || unexpectedUniformNames.length)\n ) {\n mismatchDetails.push(`Expected: ${expectedUniformNames.join(', ')}.`);\n mismatchDetails.push(`Actual: ${actualUniformNames.join(', ')}.`);\n }\n\n return `${validationResult.moduleName}: ${validationResult.stage} shader uniform block ${\n validationResult.uniformBlockName\n } does not match module.uniformTypes. ${mismatchDetails.join(' ')}`;\n}\n\n/**\n * Removes line and block comments from shader source before lightweight parsing.\n */\nfunction stripShaderComments(shaderSource: string): string {\n return shaderSource.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/\\/\\/.*$/gm, '');\n}\n\n/**\n * Collapses repeated whitespace in a layout qualifier for log-friendly output.\n */\nfunction normalizeWhitespace(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\nfunction getFirstUniformMismatchDescription(\n expectedUniformNames: string[],\n actualUniformNames: string[]\n): string | null {\n const minimumLength = Math.min(expectedUniformNames.length, actualUniformNames.length);\n for (let index = 0; index < minimumLength; index++) {\n if (expectedUniformNames[index] !== actualUniformNames[index]) {\n return `First mismatch at field ${index + 1}: expected ${\n expectedUniformNames[index]\n }, found ${actualUniformNames[index]}.`;\n }\n }\n\n if (expectedUniformNames.length > actualUniformNames.length) {\n return `Shader block ends after field ${actualUniformNames.length}; expected next field ${\n expectedUniformNames[actualUniformNames.length]\n }.`;\n }\n if (actualUniformNames.length > expectedUniformNames.length) {\n return `Shader block has extra field ${actualUniformNames.length}: ${\n actualUniformNames[expectedUniformNames.length]\n }.`;\n }\n\n return null;\n}\n\nfunction formatUniformNameList(uniformNames: string[], maxNames = 8): string {\n if (uniformNames.length <= maxNames) {\n return uniformNames.join(', ');\n }\n\n const remainingCount = uniformNames.length - maxNames;\n return `${uniformNames.slice(0, maxNames).join(', ')}, ... (${remainingCount} more)`;\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {PlatformInfo} from './platform-info';\n\n/** Adds defines to help identify GPU architecture / platform */\nexport function getPlatformShaderDefines(platformInfo: PlatformInfo): string {\n switch (platformInfo?.gpu.toLowerCase()) {\n case 'apple':\n return /* glsl */ `\\\n#define APPLE_GPU\n// Apple optimizes away the calculation necessary for emulated fp64\n#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1\n#define LUMA_FP32_TAN_PRECISION_WORKAROUND 1\n// Intel GPU doesn't have full 32 bits precision in same cases, causes overflow\n#define LUMA_FP64_HIGH_BITS_OVERFLOW_WORKAROUND 1\n`;\n\n case 'nvidia':\n return /* glsl */ `\\\n#define NVIDIA_GPU\n// Nvidia optimizes away the calculation necessary for emulated fp64\n#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1\n`;\n\n case 'intel':\n return /* glsl */ `\\\n#define INTEL_GPU\n// Intel optimizes away the calculation necessary for emulated fp64\n#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1\n// Intel's built-in 'tan' function doesn't have acceptable precision\n#define LUMA_FP32_TAN_PRECISION_WORKAROUND 1\n// Intel GPU doesn't have full 32 bits precision in same cases, causes overflow\n#define LUMA_FP64_HIGH_BITS_OVERFLOW_WORKAROUND 1\n`;\n\n case 'amd':\n // AMD Does not eliminate fp64 code\n return /* glsl */ `\\\n#define AMD_GPU\n`;\n\n default:\n // We don't know what GPU it is, could be that the GPU driver or\n // browser is not implementing UNMASKED_RENDERER constant and not\n // reporting a correct name\n return /* glsl */ `\\\n#define DEFAULT_GPU\n// Prevent driver from optimizing away the calculation necessary for emulated fp64\n#define LUMA_FP64_CODE_ELIMINATION_WORKAROUND 1\n// Headless Chrome's software shader 'tan' function doesn't have acceptable precision\n#define LUMA_FP32_TAN_PRECISION_WORKAROUND 1\n// If the GPU doesn't have full 32 bits precision, will causes overflow\n#define LUMA_FP64_HIGH_BITS_OVERFLOW_WORKAROUND 1\n`;\n }\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// TRANSPILATION TABLES\n\n/**\n * Transpiles GLSL 3.00 shader source code to target GLSL version (3.00 or 1.00)\n *\n * @note We always run transpiler even if same version e.g. 3.00 => 3.00\n * @note For texture sampling transpilation, apps need to use non-standard texture* calls in GLSL 3.00 source\n * RFC: https://github.com/visgl/luma.gl/blob/7.0-release/dev-docs/RFCs/v6.0/portable-glsl-300-rfc.md\n */\nexport function transpileGLSLShader(source: string, stage: 'vertex' | 'fragment'): string {\n const sourceGLSLVersion = Number(source.match(/^#version[ \\t]+(\\d+)/m)?.[1] || 100);\n if (sourceGLSLVersion !== 300) {\n // TODO - we splurge on a longer error message to help deck.gl custom layer developers\n throw new Error('luma.gl v9 only supports GLSL 3.00 shader sources');\n }\n\n switch (stage) {\n case 'vertex':\n source = convertShader(source, ES300_VERTEX_REPLACEMENTS);\n return source;\n case 'fragment':\n source = convertShader(source, ES300_FRAGMENT_REPLACEMENTS);\n return source;\n default:\n // Unknown shader stage\n throw new Error(stage);\n }\n}\n\ntype GLSLReplacement = [RegExp, string];\n\n/** Simple regex replacements for GLSL ES 1.00 syntax that has changed in GLSL ES 3.00 */\nconst ES300_REPLACEMENTS: GLSLReplacement[] = [\n // Fix poorly formatted version directive\n [/^(#version[ \\t]+(100|300[ \\t]+es))?[ \\t]*\\n/, '#version 300 es\\n'],\n // The individual `texture...()` functions were replaced with `texture()` overloads\n [/\\btexture(2D|2DProj|Cube)Lod(EXT)?\\(/g, 'textureLod('],\n [/\\btexture(2D|2DProj|Cube)(EXT)?\\(/g, 'texture(']\n];\n\nconst ES300_VERTEX_REPLACEMENTS: GLSLReplacement[] = [\n ...ES300_REPLACEMENTS,\n // `attribute` keyword replaced with `in`\n [makeVariableTextRegExp('attribute'), 'in $1'],\n // `varying` keyword replaced with `out`\n [makeVariableTextRegExp('varying'), 'out $1']\n];\n\n/** Simple regex replacements for GLSL ES 1.00 syntax that has changed in GLSL ES 3.00 */\nconst ES300_FRAGMENT_REPLACEMENTS: GLSLReplacement[] = [\n ...ES300_REPLACEMENTS,\n // `varying` keyword replaced with `in`\n [makeVariableTextRegExp('varying'), 'in $1']\n];\n\nfunction convertShader(source: string, replacements: GLSLReplacement[]) {\n for (const [pattern, replacement] of replacements) {\n source = source.replace(pattern, replacement);\n }\n return source;\n}\n\n/**\n * Creates a regexp that tests for a specific variable type\n * @example\n * should match:\n * in float weight;\n * out vec4 positions[2];\n * should not match:\n * void f(out float a, in float b) {}\n */\nfunction makeVariableTextRegExp(qualifier: 'attribute' | 'varying' | 'in' | 'out'): RegExp {\n return new RegExp(`\\\\b${qualifier}[ \\\\t]+(\\\\w+[ \\\\t]+\\\\w+(\\\\[\\\\w+\\\\])?;)`, 'g');\n}\n", "// luma.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {ShaderInjection} from './shader-injections';\n\n// A normalized hook function\n/**\n * The shader hook mechanism allows the application to create shaders\n * that can be automatically extended by the shader modules the application\n * includes.\n *\n * A shader hook function that shader modules can inject code into.\n * Shaders can call these functions, which will be no-ops by default.\n *\n * If a shader module injects code it will be executed upon the hook\n * function call.\n */\nexport type ShaderHook = {\n /** `vs:` or `fs:` followed by the name and arguments of the function, e.g. `vs:MYHOOK_func(inout vec4 value)`. Hook name without arguments\n will also be used as the name of the shader hook */\n hook: string;\n /** Code always included at the beginning of a hook function */\n header: string;\n /** Code always included at the end of a hook function */\n footer: string;\n /** To Be Documented */\n signature?: string;\n};\n\n/** Normalized shader hooks per shader */\nexport type ShaderHooks = {\n /** Normalized shader hooks for vertex shader */\n vertex: Record<string, ShaderHook>;\n /** Normalized shader hooks for fragment shader */\n fragment: Record<string, ShaderHook>;\n};\n\n/** Generate hook source code */\nexport function getShaderHooks(\n hookFunctions: Record<string, ShaderHook>,\n hookInjections: Record<string, ShaderInjection[]>\n): string {\n let result = '';\n for (const h