@luma.gl/shaders 10.0.0-alpha.8 → 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 (59) hide show
  1. package/dist/dist.dev.js +189 -268
  2. package/dist/dist.min.js +119 -119
  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 +5 -16
  14. package/dist/lib/shader-assembly/assemble-glsl-shaders.d.ts +4 -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 +13 -18
  17. package/dist/lib/shader-assembly/assemble-glsl-shaders.js.map +1 -1
  18. package/dist/lib/shader-assembly/assemble-wgsl-shaders.cjs +5 -24
  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 +13 -26
  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 +7 -7
  27. package/dist/lib/shader-assembly/shader-assembly-types.d.ts.map +1 -1
  28. package/dist/lib/shader-assembly/shader-hooks.cjs +17 -36
  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 +20 -44
  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 +13 -38
  44. package/dist/lib/shader-plugin.d.ts +5 -30
  45. package/dist/lib/shader-plugin.d.ts.map +1 -1
  46. package/dist/lib/shader-plugin.js +17 -40
  47. package/dist/lib/shader-plugin.js.map +1 -1
  48. package/dist/wgsl.cjs.map +2 -2
  49. package/package.json +2 -2
  50. package/src/index.ts +5 -6
  51. package/src/lib/shader-assembler-base.ts +5 -6
  52. package/src/lib/shader-assembly/assemble-glsl-shaders.ts +25 -25
  53. package/src/lib/shader-assembly/assemble-wgsl-shaders.ts +27 -42
  54. package/src/lib/shader-assembly/shader-assembly-shared.ts +5 -5
  55. package/src/lib/shader-assembly/shader-assembly-types.ts +7 -7
  56. package/src/lib/shader-assembly/shader-hooks.ts +55 -82
  57. package/src/lib/shader-assembly/shader-injections.ts +52 -8
  58. package/src/lib/shader-module/shader-module.ts +7 -14
  59. package/src/lib/shader-plugin.ts +31 -90
@@ -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,13 +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
49
  /** Hook declarations contributed by resolved plugins. */
50
50
  pluginHooks?: readonly ShaderHook[];
51
51
  /** Code injections */
52
- inject?: Record<string, string | ShaderInjection>;
52
+ inject?: readonly ShaderInjection[] | Record<string, string | ResolvedShaderInjection>;
53
53
  /** Ordered code injections contributed by ShaderPlugin descriptors. */
54
- pluginInjections?: Record<string, ShaderInjection[]>;
54
+ pluginInjections?: Record<string, ResolvedShaderInjection[]>;
55
55
  /** Vertex inputs contributed by ShaderPlugin descriptors. */
56
56
  pluginVertexInputs?: Record<string, AttributeShaderType>;
57
57
  /** Inter-stage variables contributed by ShaderPlugin descriptors. */
@@ -81,13 +81,13 @@ export type AssembleStageOptions = {
81
81
  /** GLSL only: Overrides to be injected. In WGSL these are supplied during Pipeline creation time */
82
82
  constants?: Record<string, number>;
83
83
  /** Per-call hooks, appended after registered assembler hooks; matching names override for this call. */
84
- hookFunctions?: readonly (ShaderHook | string)[];
84
+ hookFunctions?: readonly ShaderHook[];
85
85
  /** Hook declarations contributed by resolved plugins. */
86
86
  pluginHooks?: readonly ShaderHook[];
87
87
  /** Code injections */
88
- inject?: Record<string, string | ShaderInjection>;
88
+ inject?: readonly ShaderInjection[] | Record<string, string | ResolvedShaderInjection>;
89
89
  /** Ordered code injections contributed by ShaderPlugin descriptors. */
90
- pluginInjections?: Record<string, ShaderInjection[]>;
90
+ pluginInjections?: Record<string, ResolvedShaderInjection[]>;
91
91
  /** Vertex inputs contributed by ShaderPlugin descriptors. */
92
92
  pluginVertexInputs?: Record<string, AttributeShaderType>;
93
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
+ };
32
30
 
33
- /** Shader hook declarations grouped by shader stage. */
31
+ /** Language-selected hook ready for source generation. */
32
+ type ResolvedShaderHook = ShaderHookImplementation & {signature: string};
33
+
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,37 +72,33 @@ 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[],
75
78
  pluginHooks: readonly ShaderHook[] = [],
76
- language?: 'glsl' | 'wgsl'
79
+ language: 'glsl' | 'wgsl' = 'glsl'
77
80
  ): ShaderHookRegistry {
78
81
  const result: ShaderHookRegistry = {vertex: {}, fragment: {}};
79
-
80
- for (const hookFunction of hookFunctions) {
81
- const text = typeof hookFunction === 'string' ? hookFunction : hookFunction.hook;
82
- const qualified = /^(glsl|wgsl):([\s\S]+)$/.exec(text);
83
- if (qualified && qualified[1] !== language) continue;
84
- addShaderHookToRegistry(
85
- result,
86
- qualified
87
- ? typeof hookFunction === 'string'
88
- ? qualified[2]
89
- : {...hookFunction, hook: qualified[2]}
90
- : hookFunction
91
- );
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
+ }
92
91
  }
93
-
94
92
  for (const hook of pluginHooks) {
95
- const normalized = normalizeShaderHooks([hook]);
93
+ const selected = normalizeShaderHooks([hook], [], language);
96
94
  for (const stage of ['vertex', 'fragment'] as const) {
97
- for (const [name, declaration] of Object.entries(normalized[stage])) {
95
+ for (const [name, declaration] of Object.entries(selected[stage])) {
98
96
  const existing = result[stage][name];
99
- if (existing && existing.signature !== declaration.signature) {
97
+ if (
98
+ existing &&
99
+ (existing.signature !== declaration.signature ||
100
+ (existing.returnType || 'void') !== (declaration.returnType || 'void'))
101
+ ) {
100
102
  throw new Error(`Conflicting ShaderPlugin hook ${name}`);
101
103
  }
102
104
  result[stage][name] = existing || declaration;
@@ -118,7 +120,7 @@ export function isShaderHookRegistered(registry: ShaderHookRegistry, hookName: s
118
120
  */
119
121
  export function validateShaderHookInjections(
120
122
  registry: ShaderHookRegistry,
121
- hookInjections: Record<string, ShaderInjection[]>,
123
+ hookInjections: Record<string, ResolvedShaderInjection[]>,
122
124
  stage?: 'vertex' | 'fragment'
123
125
  ): void {
124
126
  const stagePrefix = stage === 'vertex' ? 'vs:' : stage === 'fragment' ? 'fs:' : null;
@@ -139,32 +141,3 @@ export function validateShaderHookInjections(
139
141
  }
140
142
  }
141
143
  }
142
-
143
- function addShaderHookToRegistry(
144
- registry: ShaderHookRegistry,
145
- hookFunction: ShaderHook | string
146
- ): void {
147
- const options: ShaderHookOptions = typeof hookFunction === 'string' ? {} : hookFunction;
148
- const hook = (typeof hookFunction === 'string' ? hookFunction : hookFunction.hook).trim();
149
- const stageSeparatorIndex = hook.indexOf(':');
150
- const shaderStage = hook.slice(0, stageSeparatorIndex);
151
- const signature = hook.slice(stageSeparatorIndex + 1);
152
- const name = hook.split('(')[0].trim();
153
- const normalizedHook: Required<ShaderHook> = {
154
- hook: name,
155
- signature,
156
- header: options.header || '',
157
- footer: options.footer || ''
158
- };
159
-
160
- switch (shaderStage) {
161
- case 'vs':
162
- registry.vertex[name] = normalizedHook;
163
- break;
164
- case 'fs':
165
- registry.fragment[name] = normalizedHook;
166
- break;
167
- default:
168
- throw new Error(shaderStage);
169
- }
170
- }
@@ -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) {
@@ -3,47 +3,14 @@
3
3
  // SPDX-FileCopyrightText: Copyright (c) vis.gl contributors
4
4
 
5
5
  import type {ShaderHook} from './shader-assembly/shader-hooks';
6
- import type {ShaderInjection} from './shader-assembly/shader-injections';
6
+ import {
7
+ resolveShaderInjections,
8
+ type ShaderInjection,
9
+ type ResolvedShaderInjection
10
+ } from './shader-assembly/shader-injections';
7
11
  import type {ShaderModule} from './shader-module/shader-module';
8
12
  import {shaderTypeDecoder, type AttributeShaderType} from '../types';
9
13
 
10
- /** Named shader anchor or hook target accepted by ShaderPlugin injections. */
11
- export type ShaderPluginInjectionTarget =
12
- | 'vs:#decl'
13
- | 'vs:#main-start'
14
- | 'vs:#main-end'
15
- | 'fs:#decl'
16
- | 'fs:#main-start'
17
- | 'fs:#main-end'
18
- | `vs:${string}`
19
- | `fs:${string}`;
20
-
21
- /** Explicit stage and unqualified target for plugin object declarations. */
22
- export type ShaderPluginTarget = {
23
- /** Shader stage containing this extension point. */
24
- stage: 'vertex' | 'fragment';
25
- /** Unqualified hook name or structural anchor such as #main-start. */
26
- target: string;
27
- };
28
-
29
- /** Hook function declared by a plugin, with parameter signatures per language. */
30
- export type ShaderPluginHook = ShaderPluginTarget & {
31
- /** GLSL parameter list, including parentheses. */
32
- glsl?: string;
33
- /** WGSL parameter list, including parentheses. */
34
- wgsl?: string;
35
- };
36
-
37
- /** Named shader source injection contributed by a ShaderPlugin. */
38
- export type ShaderPluginInjection = ShaderPluginTarget & {
39
- /** GLSL source for this target. Omit to skip this injection when assembling GLSL. */
40
- glsl?: string;
41
- /** WGSL source for this target. Omit to skip this injection when assembling WGSL. */
42
- wgsl?: string;
43
- /** Relative injection order within the target. */
44
- order?: number;
45
- };
46
-
47
14
  /** Portable interpolation modes for ShaderPlugin inter-stage variables. */
48
15
  export type ShaderPluginInterStageVariableInterpolation = 'smooth' | 'flat';
49
16
 
@@ -70,9 +37,9 @@ export type ShaderPlugin = {
70
37
  /** Boolean or numeric preprocessor defines added to shader assembly. */
71
38
  defines?: Record<string, boolean | number>;
72
39
  /** Named injections as explicit-stage objects or optionally language-qualified string keys. */
73
- injections?: ShaderPluginInjection[] | Record<string, string>;
74
- /** Callable extension points as explicit-stage objects or optionally language-qualified signatures. */
75
- hooks?: (ShaderPluginHook | string)[];
40
+ injections?: ShaderInjection[] | Record<string, string>;
41
+ /** Callable extension points with a shared name and language-specific source. */
42
+ hooks?: ShaderHook[];
76
43
  /** Shader-facing vertex inputs added by this plugin. Buffer ownership remains with the caller. */
77
44
  vertexInputs?: Record<string, AttributeShaderType>;
78
45
  /** Values transported from the selected vertex entry point to the fragment entry point. */
@@ -88,16 +55,13 @@ export type ResolvedShaderPlugins = {
88
55
  /** Resolved boolean or numeric preprocessor defines. */
89
56
  defines: Record<string, boolean | number>;
90
57
  /** Resolved named code injections grouped by target. */
91
- injections: Record<string, ShaderInjection[]>;
58
+ injections: Record<string, ResolvedShaderInjection[]>;
92
59
  /** Resolved shader-facing vertex inputs in plugin declaration order. */
93
60
  vertexInputs: Record<string, AttributeShaderType>;
94
61
  /** Resolved inter-stage variables in plugin declaration order. */
95
62
  interStageVariables: Record<string, ResolvedShaderPluginInterStageVariable>;
96
63
  };
97
64
 
98
- const SHADER_PLUGIN_INJECTION_TARGET_REGEX =
99
- /^(vs|fs):(?:#(?:decl|main-start|main-end)|[A-Za-z_][\w-]*)$/;
100
-
101
65
  /** Resolve plugin interfaces and select injection source for one shader language. */
102
66
  export function resolveShaderPlugins(
103
67
  plugins: readonly ShaderPlugin[] = [],
@@ -198,29 +162,31 @@ function appendShaderPlugin(
198
162
  }
199
163
 
200
164
  for (const hook of plugin.hooks || []) {
201
- const declarationSource =
202
- typeof hook === 'string' ? selectShaderLanguage(hook, shaderLanguage) : undefined;
203
- if (typeof hook === 'string' && declarationSource === undefined) continue;
204
- const signature =
205
- typeof hook === 'string'
206
- ? declarationSource!.slice(declarationSource!.indexOf('('))
207
- : hook[shaderLanguage];
208
- if (signature === undefined) continue;
209
- const target =
210
- typeof hook === 'string' ? declarationSource!.split('(')[0] : getShaderPluginTarget(hook);
211
- if (!/^(vs|fs):[A-Za-z_][A-Za-z0-9_]*$/.test(target) || !/^\([^]*\)$/.test(signature.trim())) {
165
+ const implementation = hook[shaderLanguage];
166
+ if (!implementation) continue;
167
+ const target = `${hook.stage === 'vertex' ? 'vs' : 'fs'}:${hook.name}`;
168
+ if (!/^(vs|fs):[A-Za-z_][A-Za-z0-9_]*$/.test(target)) {
212
169
  throw new Error(`Invalid ShaderPlugin hook ${target}`);
213
170
  }
214
- const declaration = {hook: `${target}${signature.trim()}`};
215
- const existing = resolved.hooks.find(value => value.hook.split('(')[0] === target);
216
- if (existing && existing.hook !== declaration.hook) {
171
+ const existing = resolved.hooks.find(
172
+ value => value.stage === hook.stage && value.name === hook.name
173
+ );
174
+ const previous = existing?.[shaderLanguage];
175
+ if (
176
+ previous &&
177
+ (previous.parameters !== implementation.parameters ||
178
+ (previous.returnType || 'void') !== (implementation.returnType || 'void') ||
179
+ previous.header !== implementation.header ||
180
+ previous.footer !== implementation.footer)
181
+ ) {
217
182
  throw new Error(`Conflicting ShaderPlugin hook ${target}`);
218
183
  }
219
- if (!existing) resolved.hooks.push(declaration);
184
+ if (!existing)
185
+ resolved.hooks.push({name: hook.name, stage: hook.stage, [shaderLanguage]: implementation});
220
186
  }
221
187
  const pluginInjections = Array.isArray(plugin.injections)
222
188
  ? plugin.injections
223
- : Object.entries(plugin.injections || {}).flatMap(([key, source]): ShaderPluginInjection[] => {
189
+ : Object.entries(plugin.injections || {}).flatMap(([key, source]): ShaderInjection[] => {
224
190
  const target = selectShaderLanguage(key, shaderLanguage);
225
191
  if (target === undefined) return [];
226
192
  const [stage, ...name] = target.split(':');
@@ -234,20 +200,10 @@ function appendShaderPlugin(
234
200
  }
235
201
  ];
236
202
  });
237
- for (const injection of pluginInjections) {
238
- const source = injection[shaderLanguage];
239
- if (source === undefined) continue;
240
- const target = getShaderPluginTarget(injection);
241
- assertNamedShaderPluginInjectionTarget(target);
242
- if (!resolved.injections[target]) {
243
- resolved.injections[target] = [];
244
- }
245
- const injections = resolved.injections[target];
246
- injections.push({
247
- pluginName: plugin.name,
248
- injection: source,
249
- order: injection.order ?? 0
250
- });
203
+ const selectedInjections = resolveShaderInjections(pluginInjections, shaderLanguage, plugin.name);
204
+ for (const [target, injections] of Object.entries(selectedInjections)) {
205
+ resolved.injections[target] ||= [];
206
+ resolved.injections[target].push(...injections);
251
207
  }
252
208
  }
253
209
 
@@ -270,21 +226,6 @@ function normalizeShaderPluginInterStageVariable(
270
226
  return {type: varying.type, interpolation};
271
227
  }
272
228
 
273
- function assertNamedShaderPluginInjectionTarget(target: ShaderPluginInjectionTarget): void {
274
- if (!SHADER_PLUGIN_INJECTION_TARGET_REGEX.test(target)) {
275
- throw new Error(
276
- `ShaderPlugin injection target "${target}" must be a named shader anchor or hook`
277
- );
278
- }
279
- }
280
-
281
- function getShaderPluginTarget(value: ShaderPluginTarget): ShaderPluginInjectionTarget {
282
- if ((value.stage !== 'vertex' && value.stage !== 'fragment') || value.target.includes(':')) {
283
- throw new Error('ShaderPlugin object targets require an explicit stage and unqualified target');
284
- }
285
- return `${value.stage === 'vertex' ? 'vs' : 'fs'}:${value.target}` as ShaderPluginInjectionTarget;
286
- }
287
-
288
229
  function selectShaderLanguage(value: string, language: 'glsl' | 'wgsl'): string | undefined {
289
230
  const match = /^(glsl|wgsl):([\s\S]+)$/.exec(value);
290
231
  return match ? (match[1] === language ? match[2] : undefined) : value;