@luma.gl/shaders 10.0.0-alpha.7 → 10.0.0-alpha.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/dist/dist.dev.js +233 -249
  2. package/dist/dist.min.js +124 -124
  3. package/dist/glsl.cjs.map +2 -2
  4. package/dist/index.cjs.map +3 -3
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/shader-assembler-base.cjs +3 -4
  9. package/dist/lib/shader-assembler-base.d.ts +4 -5
  10. package/dist/lib/shader-assembler-base.d.ts.map +1 -1
  11. package/dist/lib/shader-assembler-base.js +3 -4
  12. package/dist/lib/shader-assembler-base.js.map +1 -1
  13. package/dist/lib/shader-assembly/assemble-glsl-shaders.cjs +6 -17
  14. package/dist/lib/shader-assembly/assemble-glsl-shaders.d.ts +5 -4
  15. package/dist/lib/shader-assembly/assemble-glsl-shaders.d.ts.map +1 -1
  16. package/dist/lib/shader-assembly/assemble-glsl-shaders.js +14 -19
  17. package/dist/lib/shader-assembly/assemble-glsl-shaders.js.map +1 -1
  18. package/dist/lib/shader-assembly/assemble-wgsl-shaders.cjs +6 -25
  19. package/dist/lib/shader-assembly/assemble-wgsl-shaders.d.ts +5 -6
  20. package/dist/lib/shader-assembly/assemble-wgsl-shaders.d.ts.map +1 -1
  21. package/dist/lib/shader-assembly/assemble-wgsl-shaders.js +14 -27
  22. package/dist/lib/shader-assembly/assemble-wgsl-shaders.js.map +1 -1
  23. package/dist/lib/shader-assembly/shader-assembly-shared.d.ts +2 -2
  24. package/dist/lib/shader-assembly/shader-assembly-shared.d.ts.map +1 -1
  25. package/dist/lib/shader-assembly/shader-assembly-shared.js.map +1 -1
  26. package/dist/lib/shader-assembly/shader-assembly-types.d.ts +11 -7
  27. package/dist/lib/shader-assembly/shader-assembly-types.d.ts.map +1 -1
  28. package/dist/lib/shader-assembly/shader-hooks.cjs +26 -29
  29. package/dist/lib/shader-assembly/shader-hooks.d.ts +34 -32
  30. package/dist/lib/shader-assembly/shader-hooks.d.ts.map +1 -1
  31. package/dist/lib/shader-assembly/shader-hooks.js +29 -33
  32. package/dist/lib/shader-assembly/shader-hooks.js.map +1 -1
  33. package/dist/lib/shader-assembly/shader-injections.cjs +26 -2
  34. package/dist/lib/shader-assembly/shader-injections.d.ts +20 -7
  35. package/dist/lib/shader-assembly/shader-injections.d.ts.map +1 -1
  36. package/dist/lib/shader-assembly/shader-injections.js +24 -0
  37. package/dist/lib/shader-assembly/shader-injections.js.map +1 -1
  38. package/dist/lib/shader-module/shader-module.cjs +3 -9
  39. package/dist/lib/shader-module/shader-module.d.ts +4 -8
  40. package/dist/lib/shader-module/shader-module.d.ts.map +1 -1
  41. package/dist/lib/shader-module/shader-module.js +3 -6
  42. package/dist/lib/shader-module/shader-module.js.map +1 -1
  43. package/dist/lib/shader-plugin.cjs +38 -17
  44. package/dist/lib/shader-plugin.d.ts +9 -17
  45. package/dist/lib/shader-plugin.d.ts.map +1 -1
  46. package/dist/lib/shader-plugin.js +43 -16
  47. package/dist/lib/shader-plugin.js.map +1 -1
  48. package/dist/modules/engine/clip/clip.cjs +4 -2
  49. package/dist/modules/engine/clip/clip.d.ts.map +1 -1
  50. package/dist/modules/engine/clip/clip.js +4 -2
  51. package/dist/modules/engine/clip/clip.js.map +1 -1
  52. package/dist/modules/engine/filter/filter.cjs +2 -1
  53. package/dist/modules/engine/filter/filter.d.ts.map +1 -1
  54. package/dist/modules/engine/filter/filter.js +2 -1
  55. package/dist/modules/engine/filter/filter.js.map +1 -1
  56. package/dist/modules/lighting/lights/lighting.d.ts +1 -1
  57. package/dist/modules/lighting/lights/lighting.d.ts.map +1 -1
  58. package/dist/modules/lighting/pbr-material/pbr-material-glsl.cjs +2 -2
  59. package/dist/modules/lighting/pbr-material/pbr-material-glsl.d.ts +1 -1
  60. package/dist/modules/lighting/pbr-material/pbr-material-glsl.d.ts.map +1 -1
  61. package/dist/modules/lighting/pbr-material/pbr-material-glsl.js +2 -2
  62. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.cjs +2 -2
  63. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.d.ts +1 -1
  64. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.d.ts.map +1 -1
  65. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.js +2 -2
  66. package/dist/modules/lighting/pbr-material/pbr-material.d.ts +2 -2
  67. package/dist/types/texture-formats.d.ts +1 -1
  68. package/dist/types/texture-formats.d.ts.map +1 -1
  69. package/dist/wgsl.cjs.map +2 -2
  70. package/package.json +3 -3
  71. package/src/index.ts +5 -4
  72. package/src/lib/shader-assembler-base.ts +5 -6
  73. package/src/lib/shader-assembly/assemble-glsl-shaders.ts +27 -26
  74. package/src/lib/shader-assembly/assemble-wgsl-shaders.ts +28 -43
  75. package/src/lib/shader-assembly/shader-assembly-shared.ts +5 -5
  76. package/src/lib/shader-assembly/shader-assembly-types.ts +11 -7
  77. package/src/lib/shader-assembly/shader-hooks.ts +65 -68
  78. package/src/lib/shader-assembly/shader-injections.ts +52 -8
  79. package/src/lib/shader-module/shader-module.ts +7 -14
  80. package/src/lib/shader-plugin.ts +59 -48
  81. package/src/modules/engine/clip/clip.ts +4 -2
  82. package/src/modules/engine/filter/filter.ts +2 -1
  83. package/src/modules/lighting/lights/lighting.ts +1 -1
  84. package/src/modules/lighting/pbr-material/pbr-material-glsl.ts +2 -2
  85. package/src/modules/lighting/pbr-material/pbr-material-wgsl.ts +2 -2
  86. package/src/types/texture-formats.ts +2 -0
@@ -27,13 +27,12 @@ import {assert} from '../utils/assert';
27
27
  import {preprocess} from '../preprocessor/preprocessor';
28
28
  import {assembleShaderPluginVertexInputsWGSL} from './shader-plugin-vertex-inputs';
29
29
  import {assembleShaderPluginInterStageVariablesWGSL} from './shader-plugin-varyings';
30
+ import {normalizeShaderHooks, validateShaderHookInjections, getShaderHooks} from './shader-hooks';
30
31
  import {
31
- normalizeShaderHooks,
32
- validateShaderHookInjections,
33
- isShaderHookRegistered,
34
- getShaderHooks
35
- } from './shader-hooks';
36
- import {type ShaderInjection, injectShader} from './shader-injections';
32
+ type ResolvedShaderInjection,
33
+ injectShader,
34
+ resolveShaderInjections
35
+ } from './shader-injections';
37
36
  import {checkShaderModuleDeprecations, type ShaderModule} from '../shader-module/shader-module';
38
37
  import {
39
38
  getWGSLBindingDeclarationMatches,
@@ -159,18 +158,27 @@ export function assembleShaderWGSL(
159
158
  // `
160
159
  // `;
161
160
 
162
- const hookFunctionMap = normalizeShaderHooks(hookFunctions);
161
+ const hookFunctionMap = normalizeShaderHooks(hookFunctions, options.pluginHooks, 'wgsl');
163
162
 
164
163
  // Add source of dependent modules in resolved order
165
- const hookInjections: Record<string, ShaderInjection[]> = {};
166
- const declInjections: Record<string, ShaderInjection[]> = {};
167
- const mainInjections: Record<string, ShaderInjection[]> = {};
164
+ const hookInjections: Record<string, ResolvedShaderInjection[]> = {};
165
+ const declInjections: Record<string, ResolvedShaderInjection[]> = {};
166
+ const mainInjections: Record<string, ResolvedShaderInjection[]> = {};
168
167
 
169
168
  appendInjections(pluginInjections, hookInjections, declInjections, mainInjections);
170
169
 
171
- for (const key in inject) {
172
- const injection: ShaderInjection =
173
- typeof inject[key] === 'string' ? {injection: inject[key], order: 0} : inject[key];
170
+ const applicationInjections = Array.isArray(inject)
171
+ ? resolveShaderInjections(inject, 'wgsl')
172
+ : {};
173
+ appendInjections(applicationInjections, hookInjections, declInjections, mainInjections);
174
+ const injectionMap = Array.isArray(inject)
175
+ ? {}
176
+ : (inject as Record<string, string | ResolvedShaderInjection>);
177
+ for (const key in injectionMap) {
178
+ const injection: ResolvedShaderInjection =
179
+ typeof injectionMap[key] === 'string'
180
+ ? {injection: injectionMap[key], order: 0}
181
+ : injectionMap[key];
174
182
  const match = /^(v|f)s:(#)?([\w-]+)$/.exec(key);
175
183
  if (match) {
176
184
  const hash = match[2];
@@ -232,22 +240,6 @@ export function assembleShaderWGSL(
232
240
  checkShaderModuleDeprecations(module, coreSource, log);
233
241
  }
234
242
  assembledSource += moduleSource;
235
-
236
- const injections = getWGSLModuleInjections(module);
237
- for (const key in injections) {
238
- const match = /^(v|f)s:#([\w-]+)$/.exec(key);
239
- if (match) {
240
- const name = match[2];
241
- const injectionType = name === 'decl' ? declInjections : mainInjections;
242
- injectionType[key] = injectionType[key] || [];
243
- injectionType[key].push(injections[key]);
244
- } else if (isShaderHookRegistered(hookFunctionMap, key)) {
245
- // `ShaderModule.inject` is not language-specific and often carries GLSL-only hook
246
- // injections, so WGSL silently skips module injections into hooks it has not declared.
247
- hookInjections[key] = hookInjections[key] || [];
248
- hookInjections[key].push(injections[key]);
249
- }
250
- }
251
243
  }
252
244
 
253
245
  // For injectShader
@@ -282,8 +274,8 @@ export function assembleShaderWGSL(
282
274
  export function appendGeneratedVertexInputInjections(
283
275
  declarations: string,
284
276
  initialization: string,
285
- declarationInjections: Record<string, ShaderInjection[]>,
286
- mainInjections: Record<string, ShaderInjection[]>
277
+ declarationInjections: Record<string, ResolvedShaderInjection[]>,
278
+ mainInjections: Record<string, ResolvedShaderInjection[]>
287
279
  ): void {
288
280
  if (declarations) {
289
281
  declarationInjections['vs:#decl'] = declarationInjections['vs:#decl'] || [];
@@ -307,8 +299,8 @@ export function appendGeneratedVaryingInjections(
307
299
  vertexInitialization: string;
308
300
  fragmentInitialization: string;
309
301
  },
310
- declarationInjections: Record<string, ShaderInjection[]>,
311
- mainInjections: Record<string, ShaderInjection[]>
302
+ declarationInjections: Record<string, ResolvedShaderInjection[]>,
303
+ mainInjections: Record<string, ResolvedShaderInjection[]>
312
304
  ): void {
313
305
  if (assembly.declarations) {
314
306
  declarationInjections['vs:#decl'] = declarationInjections['vs:#decl'] || [];
@@ -333,16 +325,9 @@ export function appendGeneratedVaryingInjections(
333
325
  }
334
326
  }
335
327
 
336
- export function getWGSLModuleInjections(module: ShaderModule): Record<string, ShaderInjection> {
337
- return {
338
- ...(module.instance?.normalizedInjections.vertex || {}),
339
- ...(module.instance?.normalizedInjections.fragment || {})
340
- };
341
- }
342
-
343
328
  export function getWGSLDeclarationInjections(
344
- declarationInjections: Record<string, ShaderInjection[]>
345
- ): Record<string, ShaderInjection[]> {
329
+ declarationInjections: Record<string, ResolvedShaderInjection[]>
330
+ ): Record<string, ResolvedShaderInjection[]> {
346
331
  const unifiedDeclarations = [
347
332
  ...(declarationInjections['vs:#decl'] || []),
348
333
  ...(declarationInjections['fs:#decl'] || [])
@@ -353,7 +338,7 @@ export function getWGSLDeclarationInjections(
353
338
 
354
339
  export function getWGSLShaderHooks(
355
340
  hookFunctionMap: ReturnType<typeof normalizeShaderHooks>,
356
- hookInjections: Record<string, ShaderInjection[]>
341
+ hookInjections: Record<string, ResolvedShaderInjection[]>
357
342
  ): string {
358
343
  return (
359
344
  getShaderHooks(hookFunctionMap.vertex, hookInjections, 'wgsl') +
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
4
4
 
5
- import {DECLARATION_INJECT_MARKER, type ShaderInjection} from './shader-injections';
5
+ import {DECLARATION_INJECT_MARKER, type ResolvedShaderInjection} from './shader-injections';
6
6
  import {type ShaderModule} from '../shader-module/shader-module';
7
7
  import {assert} from '../utils/assert';
8
8
  import {validateShaderModuleUniformLayout} from '../shader-module/shader-module-uniform-layout';
@@ -31,10 +31,10 @@ export function assembleGetUniforms(modules: ShaderModule[]) {
31
31
  }
32
32
 
33
33
  export function appendInjections(
34
- injections: Record<string, ShaderInjection[]>,
35
- hookInjections: Record<string, ShaderInjection[]>,
36
- declInjections: Record<string, ShaderInjection[]>,
37
- mainInjections: Record<string, ShaderInjection[]>
34
+ injections: Record<string, ResolvedShaderInjection[]>,
35
+ hookInjections: Record<string, ResolvedShaderInjection[]>,
36
+ declInjections: Record<string, ResolvedShaderInjection[]>,
37
+ mainInjections: Record<string, ResolvedShaderInjection[]>
38
38
  ): void {
39
39
  for (const key in injections) {
40
40
  const match = /^(v|f)s:(#)?([\w-]+)$/.exec(key);
@@ -5,7 +5,7 @@
5
5
  import {type PlatformInfo} from './platform-info';
6
6
  import {type ShaderModule} from '../shader-module/shader-module';
7
7
  import {type ShaderHook} from './shader-hooks';
8
- import {type ShaderInjection} from './shader-injections';
8
+ import {type ShaderInjection, type ResolvedShaderInjection} from './shader-injections';
9
9
  import {type AttributeShaderType} from '../../types';
10
10
  import {type ResolvedShaderPluginInterStageVariable} from '../shader-plugin';
11
11
 
@@ -45,11 +45,13 @@ export type AssembleShaderOptions = {
45
45
  /** GLSL only: Overrides to be injected. In WGSL these are supplied during Pipeline creation time */
46
46
  constants?: Record<string, number>;
47
47
  /** Per-call hooks, appended after registered assembler hooks; matching names override for this call. */
48
- hookFunctions?: readonly (ShaderHook | string)[];
48
+ hookFunctions?: readonly ShaderHook[];
49
+ /** Hook declarations contributed by resolved plugins. */
50
+ pluginHooks?: readonly ShaderHook[];
49
51
  /** Code injections */
50
- inject?: Record<string, string | ShaderInjection>;
52
+ inject?: readonly ShaderInjection[] | Record<string, string | ResolvedShaderInjection>;
51
53
  /** Ordered code injections contributed by ShaderPlugin descriptors. */
52
- pluginInjections?: Record<string, ShaderInjection[]>;
54
+ pluginInjections?: Record<string, ResolvedShaderInjection[]>;
53
55
  /** Vertex inputs contributed by ShaderPlugin descriptors. */
54
56
  pluginVertexInputs?: Record<string, AttributeShaderType>;
55
57
  /** Inter-stage variables contributed by ShaderPlugin descriptors. */
@@ -79,11 +81,13 @@ export type AssembleStageOptions = {
79
81
  /** GLSL only: Overrides to be injected. In WGSL these are supplied during Pipeline creation time */
80
82
  constants?: Record<string, number>;
81
83
  /** Per-call hooks, appended after registered assembler hooks; matching names override for this call. */
82
- hookFunctions?: readonly (ShaderHook | string)[];
84
+ hookFunctions?: readonly ShaderHook[];
85
+ /** Hook declarations contributed by resolved plugins. */
86
+ pluginHooks?: readonly ShaderHook[];
83
87
  /** Code injections */
84
- inject?: Record<string, string | ShaderInjection>;
88
+ inject?: readonly ShaderInjection[] | Record<string, string | ResolvedShaderInjection>;
85
89
  /** Ordered code injections contributed by ShaderPlugin descriptors. */
86
- pluginInjections?: Record<string, ShaderInjection[]>;
90
+ pluginInjections?: Record<string, ResolvedShaderInjection[]>;
87
91
  /** Vertex inputs contributed by ShaderPlugin descriptors. */
88
92
  pluginVertexInputs?: Record<string, AttributeShaderType>;
89
93
  /** Inter-stage variables contributed by ShaderPlugin descriptors. */
@@ -2,51 +2,57 @@
2
2
  // SPDX-License-Identifier: MIT
3
3
  // SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
4
4
 
5
- import type {ShaderInjection} from './shader-injections';
5
+ import type {ResolvedShaderInjection} from './shader-injections';
6
6
 
7
- /**
8
- * The shader hook mechanism allows the application to create shaders
9
- * that can be automatically extended by the shader modules the application
10
- * includes.
11
- *
12
- * A shader hook function that shader modules can inject code into.
13
- * Shaders can call these functions, which will be no-ops by default.
14
- *
15
- * If a shader module injects code it will be executed upon the hook
16
- * function call.
17
- */
18
- export type ShaderHook = {
19
- /** `vs:` or `fs:` followed by the name and arguments of the function, e.g. `vs:MYHOOK_func(inout vec4 value)`. Hook name without arguments
20
- will also be used as the name of the shader hook */
21
- hook: string;
22
- /** Code always included at the beginning of a hook function */
7
+ /** Language-specific source for a callable shader hook. */
8
+ export type ShaderHookImplementation = {
9
+ /** Function parameters in the selected shader language, without parentheses. */
10
+ parameters: string;
11
+ /** Return type in this language. Omit for a hook that returns no value. */
12
+ returnType?: string;
13
+ /** Source included before injected code. */
23
14
  header?: string;
24
- /** Code always included at the end of a hook function */
15
+ /** Source included after injected code. */
25
16
  footer?: string;
26
- /** To Be Documented */
27
- signature?: string;
28
17
  };
29
18
 
30
- /** Optional source included before or after every injection in a hook function. */
31
- export type ShaderHookOptions = Pick<ShaderHook, 'header' | 'footer'>;
19
+ /** One logical extension point with independent GLSL and WGSL syntax. */
20
+ export type ShaderHook = {
21
+ /** Function name shared by both shader languages. */
22
+ name: string;
23
+ /** Shader stage that owns the hook. */
24
+ stage: 'vertex' | 'fragment';
25
+ /** GLSL implementation. Omit to skip this hook during GLSL assembly. */
26
+ glsl?: ShaderHookImplementation;
27
+ /** WGSL implementation. Omit to skip this hook during WGSL assembly. */
28
+ wgsl?: ShaderHookImplementation;
29
+ };
30
+
31
+ /** Language-selected hook ready for source generation. */
32
+ type ResolvedShaderHook = ShaderHookImplementation & {signature: string};
32
33
 
33
- /** Shader hook declarations grouped by shader stage. */
34
+ /** Normalized shader hooks per shader */
34
35
  export type ShaderHookRegistry = {
35
- vertex: Record<string, Required<ShaderHook>>;
36
- fragment: Record<string, Required<ShaderHook>>;
36
+ /** Normalized shader hooks for vertex shader */
37
+ vertex: Record<string, ResolvedShaderHook>;
38
+ /** Normalized shader hooks for fragment shader */
39
+ fragment: Record<string, ResolvedShaderHook>;
37
40
  };
38
41
 
39
42
  /** Generate hook source code */
40
43
  export function getShaderHooks(
41
- hookFunctions: Record<string, Required<ShaderHook>>,
42
- hookInjections: Record<string, ShaderInjection[]>,
44
+ hookFunctions: Record<string, ResolvedShaderHook>,
45
+ hookInjections: Record<string, ResolvedShaderInjection[]>,
43
46
  shaderLanguage: 'glsl' | 'wgsl' = 'glsl'
44
47
  ): string {
45
48
  let result = '';
46
49
  for (const hookName in hookFunctions) {
47
50
  const hookFunction = hookFunctions[hookName];
48
- const functionPrefix = shaderLanguage === 'wgsl' ? 'fn' : 'void';
49
- result += `${functionPrefix} ${hookFunction.signature} {\n`;
51
+ const returnType = hookFunction.returnType || 'void';
52
+ const functionPrefix = shaderLanguage === 'wgsl' ? 'fn' : returnType;
53
+ const returnSuffix =
54
+ shaderLanguage === 'wgsl' && returnType !== 'void' ? ` -> ${returnType}` : '';
55
+ result += `${functionPrefix} ${hookFunction.signature}${returnSuffix} {\n`;
50
56
  if (hookFunction.header) {
51
57
  result += ` ${hookFunction.header}`;
52
58
  }
@@ -66,19 +72,39 @@ export function getShaderHooks(
66
72
  return result;
67
73
  }
68
74
 
69
- /**
70
- * Parse string based hook functions and split them per shader stage.
71
- * A later declaration of the same stage-prefixed hook name replaces an earlier one.
72
- */
75
+ /** Select language syntax and merge compatible plugin declarations without mutating descriptors. */
73
76
  export function normalizeShaderHooks(
74
- hookFunctions: readonly (ShaderHook | string)[]
77
+ hookFunctions: readonly ShaderHook[],
78
+ pluginHooks: readonly ShaderHook[] = [],
79
+ language: 'glsl' | 'wgsl' = 'glsl'
75
80
  ): ShaderHookRegistry {
76
81
  const result: ShaderHookRegistry = {vertex: {}, fragment: {}};
77
-
78
- for (const hookFunction of hookFunctions) {
79
- addShaderHookToRegistry(result, hookFunction);
82
+ for (const hook of hookFunctions) {
83
+ const implementation = hook[language];
84
+ if (implementation) {
85
+ const prefix = hook.stage === 'vertex' ? 'vs' : 'fs';
86
+ result[hook.stage][`${prefix}:${hook.name}`] = {
87
+ ...implementation,
88
+ signature: `${hook.name}(${implementation.parameters})`
89
+ };
90
+ }
91
+ }
92
+ for (const hook of pluginHooks) {
93
+ const selected = normalizeShaderHooks([hook], [], language);
94
+ for (const stage of ['vertex', 'fragment'] as const) {
95
+ for (const [name, declaration] of Object.entries(selected[stage])) {
96
+ const existing = result[stage][name];
97
+ if (
98
+ existing &&
99
+ (existing.signature !== declaration.signature ||
100
+ (existing.returnType || 'void') !== (declaration.returnType || 'void'))
101
+ ) {
102
+ throw new Error(`Conflicting ShaderPlugin hook ${name}`);
103
+ }
104
+ result[stage][name] = existing || declaration;
105
+ }
106
+ }
80
107
  }
81
-
82
108
  return result;
83
109
  }
84
110
 
@@ -94,7 +120,7 @@ export function isShaderHookRegistered(registry: ShaderHookRegistry, hookName: s
94
120
  */
95
121
  export function validateShaderHookInjections(
96
122
  registry: ShaderHookRegistry,
97
- hookInjections: Record<string, ShaderInjection[]>,
123
+ hookInjections: Record<string, ResolvedShaderInjection[]>,
98
124
  stage?: 'vertex' | 'fragment'
99
125
  ): void {
100
126
  const stagePrefix = stage === 'vertex' ? 'vs:' : stage === 'fragment' ? 'fs:' : null;
@@ -115,32 +141,3 @@ export function validateShaderHookInjections(
115
141
  }
116
142
  }
117
143
  }
118
-
119
- function addShaderHookToRegistry(
120
- registry: ShaderHookRegistry,
121
- hookFunction: ShaderHook | string
122
- ): void {
123
- const options: ShaderHookOptions = typeof hookFunction === 'string' ? {} : hookFunction;
124
- const hook = (typeof hookFunction === 'string' ? hookFunction : hookFunction.hook).trim();
125
- const stageSeparatorIndex = hook.indexOf(':');
126
- const shaderStage = hook.slice(0, stageSeparatorIndex);
127
- const signature = hook.slice(stageSeparatorIndex + 1);
128
- const name = hook.replace(/\(.+/, '');
129
- const normalizedHook: Required<ShaderHook> = {
130
- hook: name,
131
- signature,
132
- header: options.header || '',
133
- footer: options.footer || ''
134
- };
135
-
136
- switch (shaderStage) {
137
- case 'vs':
138
- registry.vertex[name] = normalizedHook;
139
- break;
140
- case 'fs':
141
- registry.fragment[name] = normalizedHook;
142
- break;
143
- default:
144
- throw new Error(shaderStage);
145
- }
146
- }
@@ -17,29 +17,71 @@ const fragments: string[] = [];
17
17
 
18
18
  export const DECLARATION_INJECT_MARKER = '__LUMA_INJECT_DECLARATIONS__';
19
19
 
20
- /**
21
- *
22
- */
20
+ /** Language-specific source inserted at a named shader anchor or hook. */
23
21
  export type ShaderInjection = {
22
+ /** Shader stage receiving the injection. */
23
+ stage: 'vertex' | 'fragment';
24
+ /** Unqualified hook name or structural anchor such as #main-start. */
25
+ target: string;
26
+ /** GLSL source. Omit to skip this injection for GLSL assembly. */
27
+ glsl?: string;
28
+ /** WGSL source. Omit to skip this injection for WGSL assembly. */
29
+ wgsl?: string;
30
+ /** Relative order at the target; defaults to zero. */
31
+ order?: number;
32
+ };
33
+
34
+ /** Language-selected source and ordering used internally during shader assembly. */
35
+ export type ResolvedShaderInjection = {
24
36
  /** Plugin responsible for this injection, when supplied by a ShaderPlugin. */
25
37
  pluginName?: string;
26
38
  injection: string;
27
39
  order: number;
28
40
  };
29
41
 
42
+ /** Select language-specific injections while retaining repeated targets and declaration order. */
43
+ export function resolveShaderInjections(
44
+ declarations: readonly ShaderInjection[],
45
+ language: 'glsl' | 'wgsl',
46
+ pluginName?: string
47
+ ): Record<string, ResolvedShaderInjection[]> {
48
+ const resolved: Record<string, ResolvedShaderInjection[]> = {};
49
+ for (const declaration of declarations) {
50
+ const source = declaration[language];
51
+ if (source === undefined) continue;
52
+ if (
53
+ (declaration.stage !== 'vertex' && declaration.stage !== 'fragment') ||
54
+ declaration.target.includes(':')
55
+ ) {
56
+ throw new Error('Shader injection targets require an explicit stage and unqualified target');
57
+ }
58
+ const target = `${declaration.stage === 'vertex' ? 'vs' : 'fs'}:${declaration.target}`;
59
+ if (!/^(vs|fs):(?:#(?:decl|main-start|main-end)|[A-Za-z_][A-Za-z0-9_]*)$/.test(target)) {
60
+ throw new Error(`Shader injection target "${target}" must be a named shader anchor or hook`);
61
+ }
62
+ resolved[target] ||= [];
63
+ resolved[target].push({
64
+ injection: source,
65
+ order: declaration.order ?? 0,
66
+ ...(pluginName ? {pluginName} : {})
67
+ });
68
+ }
69
+ return resolved;
70
+ }
71
+
30
72
  /**
31
73
  * ShaderInjections, parsed and split per shader
32
74
  */
33
75
  export type ShaderInjections = {
34
- vertex: Record<string, ShaderInjection>;
35
- fragment: Record<string, ShaderInjection>;
76
+ vertex: Record<string, ResolvedShaderInjection>;
77
+ fragment: Record<string, ResolvedShaderInjection>;
36
78
  };
37
79
 
38
80
  /**
39
81
  *
40
82
  */
41
83
  export function normalizeInjections(
42
- injections: Record<string, string | ShaderInjection>
84
+ injections: Record<string, string | ResolvedShaderInjection>
43
85
  ): ShaderInjections {
44
86
  const result: ShaderInjections = {vertex: {}, fragment: {}};
45
87
 
@@ -84,7 +126,7 @@ function getHookStage(hook: string): 'vertex' | 'fragment' {
84
126
  export function injectShader(
85
127
  source: string,
86
128
  stage: 'vertex' | 'fragment',
87
- inject: Record<string, ShaderInjection[]>,
129
+ inject: Record<string, ResolvedShaderInjection[]>,
88
130
  injectStandardStubs = false,
89
131
  language: 'glsl' | 'wgsl' = 'glsl',
90
132
  entryPoints: {vertex?: string; fragment?: string} = {}
@@ -93,7 +135,9 @@ export function injectShader(
93
135
 
94
136
  for (const key in inject) {
95
137
  const fragmentData = inject[key];
96
- fragmentData.sort((a: ShaderInjection, b: ShaderInjection): number => a.order - b.order);
138
+ fragmentData.sort(
139
+ (a: ResolvedShaderInjection, b: ResolvedShaderInjection): number => a.order - b.order
140
+ );
97
141
  fragments.length = fragmentData.length;
98
142
  for (let i = 0, len = fragmentData.length; i < len; ++i) {
99
143
  fragments[i] = fragmentData[i].injection;
@@ -10,7 +10,7 @@ import {
10
10
  getValidatedProperties
11
11
  } from '../filters/prop-types';
12
12
  import type {ShaderModuleUniformValue, UniformTypes, UniformValue} from '../utils/uniform-types';
13
- import {ShaderInjection, normalizeInjections} from '../shader-assembly/shader-injections';
13
+ import type {ResolvedShaderInjection} from '../shader-assembly/shader-injections';
14
14
 
15
15
  import type {ShaderBinding as Binding} from '../../types';
16
16
 
@@ -83,8 +83,6 @@ export type ShaderModule<
83
83
 
84
84
  /** Boolean or numeric preprocessor defines contributed by this shader module. */
85
85
  defines?: Record<string, boolean | number>;
86
- /** Injections */
87
- inject?: Record<string, string | {injection: string; order: number}>;
88
86
  dependencies?: ShaderModule<any, any>[];
89
87
  /** Information on deprecated properties */
90
88
  deprecations?: ShaderModuleDeprecation[];
@@ -93,10 +91,10 @@ export type ShaderModule<
93
91
  instance?: {
94
92
  propValidators?: Record<string, PropValidator>;
95
93
  parsedDeprecations: ShaderModuleDeprecation[];
96
-
94
+ /** Empty compatibility value for consumers using older shadertools assembly. */
97
95
  normalizedInjections: {
98
- vertex: Record<string, ShaderInjection>;
99
- fragment: Record<string, ShaderInjection>;
96
+ vertex: Record<string, ResolvedShaderInjection>;
97
+ fragment: Record<string, ResolvedShaderInjection>;
100
98
  };
101
99
  };
102
100
  };
@@ -123,16 +121,11 @@ export function initializeShaderModule(module: ShaderModule): void {
123
121
 
124
122
  initializeShaderModules(module.dependencies || []);
125
123
 
126
- const {
127
- propTypes = {},
128
- deprecations = [],
129
- // defines = {},
130
- inject = {}
131
- } = module;
124
+ const {propTypes = {}, deprecations = []} = module;
132
125
 
133
126
  const instance: Required<ShaderModule>['instance'] = {
134
- normalizedInjections: normalizeInjections(inject),
135
- parsedDeprecations: parseDeprecationDefinitions(deprecations)
127
+ parsedDeprecations: parseDeprecationDefinitions(deprecations),
128
+ normalizedInjections: {vertex: {}, fragment: {}}
136
129
  };
137
130
 
138
131
  if (propTypes) {