@luma.gl/shadertools 9.3.0-alpha.6 → 9.3.0-alpha.8

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 (152) hide show
  1. package/dist/dist.dev.js +2550 -330
  2. package/dist/dist.min.js +1803 -283
  3. package/dist/index.cjs +2495 -358
  4. package/dist/index.cjs.map +4 -4
  5. package/dist/index.d.ts +9 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/preprocessor/preprocessor.d.ts.map +1 -1
  10. package/dist/lib/preprocessor/preprocessor.js +4 -3
  11. package/dist/lib/preprocessor/preprocessor.js.map +1 -1
  12. package/dist/lib/shader-assembler.d.ts +10 -0
  13. package/dist/lib/shader-assembler.d.ts.map +1 -1
  14. package/dist/lib/shader-assembler.js +12 -2
  15. package/dist/lib/shader-assembler.js.map +1 -1
  16. package/dist/lib/shader-assembly/assemble-shaders.d.ts +23 -2
  17. package/dist/lib/shader-assembly/assemble-shaders.d.ts.map +1 -1
  18. package/dist/lib/shader-assembly/assemble-shaders.js +211 -11
  19. package/dist/lib/shader-assembly/assemble-shaders.js.map +1 -1
  20. package/dist/lib/shader-assembly/wgsl-binding-debug.d.ts +37 -0
  21. package/dist/lib/shader-assembly/wgsl-binding-debug.d.ts.map +1 -0
  22. package/dist/lib/shader-assembly/wgsl-binding-debug.js +140 -0
  23. package/dist/lib/shader-assembly/wgsl-binding-debug.js.map +1 -0
  24. package/dist/lib/shader-generator/glsl/generate-glsl.js +3 -0
  25. package/dist/lib/shader-generator/glsl/generate-glsl.js.map +1 -1
  26. package/dist/lib/shader-generator/wgsl/generate-wgsl.d.ts.map +1 -1
  27. package/dist/lib/shader-generator/wgsl/generate-wgsl.js +3 -0
  28. package/dist/lib/shader-generator/wgsl/generate-wgsl.js.map +1 -1
  29. package/dist/lib/shader-module/shader-module-uniform-layout.d.ts +22 -0
  30. package/dist/lib/shader-module/shader-module-uniform-layout.d.ts.map +1 -0
  31. package/dist/lib/shader-module/shader-module-uniform-layout.js +112 -0
  32. package/dist/lib/shader-module/shader-module-uniform-layout.js.map +1 -0
  33. package/dist/lib/shader-module/shader-module.d.ts +11 -5
  34. package/dist/lib/shader-module/shader-module.d.ts.map +1 -1
  35. package/dist/lib/shader-module/shader-module.js.map +1 -1
  36. package/dist/lib/utils/uniform-types.d.ts +11 -7
  37. package/dist/lib/utils/uniform-types.d.ts.map +1 -1
  38. package/dist/modules/engine/picking/picking.d.ts +3 -0
  39. package/dist/modules/engine/picking/picking.d.ts.map +1 -1
  40. package/dist/modules/engine/picking/picking.js +3 -0
  41. package/dist/modules/engine/picking/picking.js.map +1 -1
  42. package/dist/modules/engine/skin/skin.d.ts +7 -6
  43. package/dist/modules/engine/skin/skin.d.ts.map +1 -1
  44. package/dist/modules/engine/skin/skin.js +3 -5
  45. package/dist/modules/engine/skin/skin.js.map +1 -1
  46. package/dist/modules/lighting/gouraud-material/gouraud-material.d.ts +1 -0
  47. package/dist/modules/lighting/gouraud-material/gouraud-material.d.ts.map +1 -1
  48. package/dist/modules/lighting/gouraud-material/gouraud-material.js +3 -0
  49. package/dist/modules/lighting/gouraud-material/gouraud-material.js.map +1 -1
  50. package/dist/modules/lighting/ibl/ibl.d.ts +26 -0
  51. package/dist/modules/lighting/ibl/ibl.d.ts.map +1 -0
  52. package/dist/modules/lighting/ibl/ibl.js +33 -0
  53. package/dist/modules/lighting/ibl/ibl.js.map +1 -0
  54. package/dist/modules/lighting/lambert-material/lambert-material.d.ts +10 -0
  55. package/dist/modules/lighting/lambert-material/lambert-material.d.ts.map +1 -0
  56. package/dist/modules/lighting/lambert-material/lambert-material.js +33 -0
  57. package/dist/modules/lighting/lambert-material/lambert-material.js.map +1 -0
  58. package/dist/modules/lighting/lambert-material/lambert-shaders-glsl.d.ts +3 -0
  59. package/dist/modules/lighting/lambert-material/lambert-shaders-glsl.d.ts.map +1 -0
  60. package/dist/modules/lighting/lambert-material/lambert-shaders-glsl.js +60 -0
  61. package/dist/modules/lighting/lambert-material/lambert-shaders-glsl.js.map +1 -0
  62. package/dist/modules/lighting/lambert-material/lambert-shaders-wgsl.d.ts +2 -0
  63. package/dist/modules/lighting/lambert-material/lambert-shaders-wgsl.d.ts.map +1 -0
  64. package/dist/modules/lighting/lambert-material/lambert-shaders-wgsl.js +73 -0
  65. package/dist/modules/lighting/lambert-material/lambert-shaders-wgsl.js.map +1 -0
  66. package/dist/modules/lighting/lights/lighting-glsl.d.ts +1 -1
  67. package/dist/modules/lighting/lights/lighting-glsl.d.ts.map +1 -1
  68. package/dist/modules/lighting/lights/lighting-glsl.js +43 -55
  69. package/dist/modules/lighting/lights/lighting-glsl.js.map +1 -1
  70. package/dist/modules/lighting/lights/lighting-wgsl.d.ts +1 -1
  71. package/dist/modules/lighting/lights/lighting-wgsl.d.ts.map +1 -1
  72. package/dist/modules/lighting/lights/lighting-wgsl.js +43 -65
  73. package/dist/modules/lighting/lights/lighting-wgsl.js.map +1 -1
  74. package/dist/modules/lighting/lights/lighting.d.ts +104 -86
  75. package/dist/modules/lighting/lights/lighting.d.ts.map +1 -1
  76. package/dist/modules/lighting/lights/lighting.js +96 -83
  77. package/dist/modules/lighting/lights/lighting.js.map +1 -1
  78. package/dist/modules/lighting/no-material/dirlight.d.ts +7 -2
  79. package/dist/modules/lighting/no-material/dirlight.d.ts.map +1 -1
  80. package/dist/modules/lighting/no-material/dirlight.js +3 -1
  81. package/dist/modules/lighting/no-material/dirlight.js.map +1 -1
  82. package/dist/modules/lighting/pbr-material/pbr-material-glsl.d.ts +1 -1
  83. package/dist/modules/lighting/pbr-material/pbr-material-glsl.d.ts.map +1 -1
  84. package/dist/modules/lighting/pbr-material/pbr-material-glsl.js +524 -28
  85. package/dist/modules/lighting/pbr-material/pbr-material-glsl.js.map +1 -1
  86. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.d.ts +2 -2
  87. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.d.ts.map +1 -1
  88. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.js +706 -50
  89. package/dist/modules/lighting/pbr-material/pbr-material-wgsl.js.map +1 -1
  90. package/dist/modules/lighting/pbr-material/pbr-material.d.ts +110 -61
  91. package/dist/modules/lighting/pbr-material/pbr-material.d.ts.map +1 -1
  92. package/dist/modules/lighting/pbr-material/pbr-material.js +85 -9
  93. package/dist/modules/lighting/pbr-material/pbr-material.js.map +1 -1
  94. package/dist/modules/lighting/pbr-material/pbr-projection.d.ts.map +1 -1
  95. package/dist/modules/lighting/pbr-material/pbr-projection.js +2 -1
  96. package/dist/modules/lighting/pbr-material/pbr-projection.js.map +1 -1
  97. package/dist/modules/lighting/phong-material/phong-material.d.ts +1 -0
  98. package/dist/modules/lighting/phong-material/phong-material.d.ts.map +1 -1
  99. package/dist/modules/lighting/phong-material/phong-material.js +4 -0
  100. package/dist/modules/lighting/phong-material/phong-material.js.map +1 -1
  101. package/dist/modules/lighting/phong-material/phong-shaders-glsl.d.ts +2 -2
  102. package/dist/modules/lighting/phong-material/phong-shaders-glsl.d.ts.map +1 -1
  103. package/dist/modules/lighting/phong-material/phong-shaders-glsl.js +15 -4
  104. package/dist/modules/lighting/phong-material/phong-shaders-glsl.js.map +1 -1
  105. package/dist/modules/lighting/phong-material/phong-shaders-wgsl.d.ts +1 -1
  106. package/dist/modules/lighting/phong-material/phong-shaders-wgsl.d.ts.map +1 -1
  107. package/dist/modules/lighting/phong-material/phong-shaders-wgsl.js +36 -5
  108. package/dist/modules/lighting/phong-material/phong-shaders-wgsl.js.map +1 -1
  109. package/dist/modules/math/fp64/fp64-arithmetic-glsl.d.ts +1 -1
  110. package/dist/modules/math/fp64/fp64-arithmetic-glsl.d.ts.map +1 -1
  111. package/dist/modules/math/fp64/fp64-arithmetic-glsl.js +41 -10
  112. package/dist/modules/math/fp64/fp64-arithmetic-glsl.js.map +1 -1
  113. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.d.ts +2 -0
  114. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.d.ts.map +1 -0
  115. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.js +212 -0
  116. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.js.map +1 -0
  117. package/dist/modules/math/fp64/fp64.d.ts +1 -0
  118. package/dist/modules/math/fp64/fp64.d.ts.map +1 -1
  119. package/dist/modules/math/fp64/fp64.js +8 -2
  120. package/dist/modules/math/fp64/fp64.js.map +1 -1
  121. package/package.json +3 -3
  122. package/src/index.ts +19 -2
  123. package/src/lib/preprocessor/preprocessor.ts +6 -3
  124. package/src/lib/shader-assembler.ts +17 -2
  125. package/src/lib/shader-assembly/assemble-shaders.ts +377 -12
  126. package/src/lib/shader-assembly/wgsl-binding-debug.ts +216 -0
  127. package/src/lib/shader-generator/glsl/generate-glsl.ts +7 -1
  128. package/src/lib/shader-generator/wgsl/generate-wgsl.ts +6 -0
  129. package/src/lib/shader-module/shader-module-uniform-layout.ts +194 -0
  130. package/src/lib/shader-module/shader-module.ts +16 -6
  131. package/src/lib/utils/uniform-types.ts +24 -9
  132. package/src/modules/engine/picking/picking.ts +3 -0
  133. package/src/modules/engine/skin/skin.ts +3 -5
  134. package/src/modules/lighting/gouraud-material/gouraud-material.ts +4 -0
  135. package/src/modules/lighting/ibl/ibl.ts +44 -0
  136. package/src/modules/lighting/lambert-material/lambert-material.ts +42 -0
  137. package/src/modules/lighting/lambert-material/lambert-shaders-glsl.ts +61 -0
  138. package/src/modules/lighting/lambert-material/lambert-shaders-wgsl.ts +73 -0
  139. package/src/modules/lighting/lights/lighting-glsl.ts +43 -55
  140. package/src/modules/lighting/lights/lighting-wgsl.ts +43 -65
  141. package/src/modules/lighting/lights/lighting.ts +186 -123
  142. package/src/modules/lighting/no-material/dirlight.ts +3 -1
  143. package/src/modules/lighting/pbr-material/pbr-material-glsl.ts +524 -28
  144. package/src/modules/lighting/pbr-material/pbr-material-wgsl.ts +706 -50
  145. package/src/modules/lighting/pbr-material/pbr-material.ts +111 -18
  146. package/src/modules/lighting/pbr-material/pbr-projection.ts +2 -1
  147. package/src/modules/lighting/phong-material/phong-material.ts +5 -0
  148. package/src/modules/lighting/phong-material/phong-shaders-glsl.ts +15 -4
  149. package/src/modules/lighting/phong-material/phong-shaders-wgsl.ts +36 -5
  150. package/src/modules/math/fp64/fp64-arithmetic-glsl.ts +41 -10
  151. package/src/modules/math/fp64/fp64-arithmetic-wgsl.ts +212 -0
  152. package/src/modules/math/fp64/fp64.ts +9 -3
@@ -8,13 +8,24 @@ import {getPlatformShaderDefines} from './platform-defines';
8
8
  import {injectShader, DECLARATION_INJECT_MARKER} from './shader-injections';
9
9
  import {transpileGLSLShader} from '../shader-transpiler/transpile-glsl-shader';
10
10
  import {checkShaderModuleDeprecations} from '../shader-module/shader-module';
11
+ import {validateShaderModuleUniformLayout} from '../shader-module/shader-module-uniform-layout';
11
12
  import type {ShaderInjection} from './shader-injections';
12
13
  import type {ShaderModule} from '../shader-module/shader-module';
13
14
  import {ShaderHook, normalizeShaderHooks, getShaderHooks} from './shader-hooks';
14
15
  import {assert} from '../utils/assert';
15
16
  import {getShaderInfo} from '../glsl-utils/get-shader-info';
17
+ import {getShaderBindingDebugRowsFromWGSL, type ShaderBindingDebugRow} from './wgsl-binding-debug';
16
18
 
17
19
  const INJECT_SHADER_DECLARATIONS = `\n\n${DECLARATION_INJECT_MARKER}\n`;
20
+ const MODULE_WGSL_BINDING_DECLARATION_REGEXES = [
21
+ /@binding\(\s*(auto|\d+)\s*\)\s*@group\(\s*(\d+)\s*\)\s*(var(?:<[^>]+>)?\s+([A-Za-z_][A-Za-z0-9_]*))/g,
22
+ /@group\(\s*(\d+)\s*\)\s*@binding\(\s*(auto|\d+)\s*\)\s*(var(?:<[^>]+>)?\s+([A-Za-z_][A-Za-z0-9_]*))/g
23
+ ] as const;
24
+ const WGSL_BINDING_DECLARATION_REGEXES = [
25
+ /@binding\(\s*(\d+)\s*\)\s*@group\(\s*(\d+)\s*\)\s*(var(?:<[^>]+>)?\s+([A-Za-z_][A-Za-z0-9_]*))/g,
26
+ /@group\(\s*(\d+)\s*\)\s*@binding\(\s*(\d+)\s*\)\s*(var(?:<[^>]+>)?\s+([A-Za-z_][A-Za-z0-9_]*))/g
27
+ ] as const;
28
+ const RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT = 100;
18
29
 
19
30
  /**
20
31
  * Precision prologue to inject before functions are injected in shader
@@ -78,6 +89,8 @@ type AssembleStageOptions = {
78
89
  prologue?: boolean;
79
90
  /** logger object */
80
91
  log?: any;
92
+ /** @internal Stable per-assembler WGSL binding assignments. */
93
+ _bindingRegistry?: Map<string, number>;
81
94
  };
82
95
 
83
96
  export type HookFunction = {hook: string; header: string; footer: string; signature?: string};
@@ -94,21 +107,28 @@ export function assembleWGSLShader(
94
107
  options: AssembleShaderOptions & {
95
108
  /** Single WGSL shader */
96
109
  source: string;
110
+ /** @internal Stable per-assembler WGSL binding assignments. */
111
+ _bindingRegistry?: Map<string, number>;
97
112
  }
98
113
  ): {
99
114
  source: string;
100
115
  getUniforms: GetUniformsFunc;
116
+ bindingAssignments: {moduleName: string; name: string; group: number; location: number}[];
117
+ bindingTable: ShaderBindingDebugRow[];
101
118
  } {
102
119
  const modules = getShaderModuleDependencies(options.modules || []);
120
+ const {source, bindingAssignments} = assembleShaderWGSL(options.platformInfo, {
121
+ ...options,
122
+ source: options.source,
123
+ stage: 'vertex',
124
+ modules
125
+ });
103
126
 
104
127
  return {
105
- source: assembleShaderWGSL(options.platformInfo, {
106
- ...options,
107
- source: options.source,
108
- stage: 'vertex',
109
- modules
110
- }),
111
- getUniforms: assembleGetUniforms(modules)
128
+ source,
129
+ getUniforms: assembleGetUniforms(modules),
130
+ bindingAssignments,
131
+ bindingTable: getShaderBindingDebugRowsFromWGSL(source, bindingAssignments)
112
132
  };
113
133
  }
114
134
 
@@ -155,7 +175,10 @@ export function assembleGLSLShaderPair(
155
175
  * @param options
156
176
  * @returns
157
177
  */
158
- export function assembleShaderWGSL(platformInfo: PlatformInfo, options: AssembleStageOptions) {
178
+ export function assembleShaderWGSL(
179
+ platformInfo: PlatformInfo,
180
+ options: AssembleStageOptions
181
+ ): {source: string; bindingAssignments: WGSLBindingAssignment[]} {
159
182
  const {
160
183
  // id,
161
184
  source,
@@ -226,12 +249,29 @@ export function assembleShaderWGSL(platformInfo: PlatformInfo, options: Assemble
226
249
 
227
250
  // TODO - hack until shadertool modules support WebGPU
228
251
  const modulesToInject = modules;
252
+ const usedBindingsByGroup = getUsedBindingsByGroupFromApplicationWGSL(coreSource);
253
+ const reservedBindingKeysByGroup = reserveRegisteredModuleBindings(
254
+ modulesToInject,
255
+ options._bindingRegistry,
256
+ usedBindingsByGroup
257
+ );
258
+ const bindingAssignments: WGSLBindingAssignment[] = [];
229
259
 
230
260
  for (const module of modulesToInject) {
231
261
  if (log) {
232
262
  checkShaderModuleDeprecations(module, coreSource, log);
233
263
  }
234
- const moduleSource = getShaderModuleSource(module, 'wgsl');
264
+ const relocation = relocateWGSLModuleBindings(
265
+ getShaderModuleSource(module, 'wgsl', log),
266
+ module,
267
+ {
268
+ usedBindingsByGroup,
269
+ bindingRegistry: options._bindingRegistry,
270
+ reservedBindingKeysByGroup
271
+ }
272
+ );
273
+ bindingAssignments.push(...relocation.bindingAssignments);
274
+ const moduleSource = relocation.source;
235
275
  // Add the module source, and a #define that declares it presence
236
276
  assembledSource += moduleSource;
237
277
 
@@ -256,6 +296,7 @@ export function assembleShaderWGSL(platformInfo: PlatformInfo, options: Assemble
256
296
  assembledSource = injectShader(assembledSource, stage, declInjections);
257
297
 
258
298
  assembledSource += getShaderHooks(hookFunctionMap[stage], hookInjections);
299
+ assembledSource += formatWGSLBindingAssignmentComments(bindingAssignments);
259
300
 
260
301
  // Add the version directive and actual source of this shader
261
302
  assembledSource += coreSource;
@@ -263,7 +304,9 @@ export function assembleShaderWGSL(platformInfo: PlatformInfo, options: Assemble
263
304
  // Apply any requested shader injections
264
305
  assembledSource = injectShader(assembledSource, stage, mainInjections);
265
306
 
266
- return assembledSource;
307
+ assertNoUnresolvedAutoBindings(assembledSource);
308
+
309
+ return {source: assembledSource, bindingAssignments};
267
310
  }
268
311
 
269
312
  /**
@@ -379,7 +422,7 @@ ${getApplicationDefines(allDefines)}
379
422
  if (log) {
380
423
  checkShaderModuleDeprecations(module, coreSource, log);
381
424
  }
382
- const moduleSource = getShaderModuleSource(module, stage);
425
+ const moduleSource = getShaderModuleSource(module, stage, log);
383
426
  // Add the module source, and a #define that declares it presence
384
427
  assembledSource += moduleSource;
385
428
 
@@ -476,7 +519,8 @@ function getApplicationDefines(defines: Record<string, boolean> = {}): string {
476
519
  /** Extracts the source code chunk for the specified shader type from the named shader module */
477
520
  export function getShaderModuleSource(
478
521
  module: ShaderModule,
479
- stage: 'vertex' | 'fragment' | 'wgsl'
522
+ stage: 'vertex' | 'fragment' | 'wgsl',
523
+ log?: any
480
524
  ): string {
481
525
  let moduleSource;
482
526
  switch (stage) {
@@ -496,6 +540,9 @@ export function getShaderModuleSource(
496
540
  if (!module.name) {
497
541
  throw new Error('Shader module must have a name');
498
542
  }
543
+
544
+ validateShaderModuleUniformLayout(module, stage, {log});
545
+
499
546
  const moduleName = module.name.toUpperCase().replace(/[^0-9a-z]/gi, '_');
500
547
  let source = `\
501
548
  // ----- MODULE ${module.name} ---------------
@@ -508,6 +555,324 @@ export function getShaderModuleSource(
508
555
  return source;
509
556
  }
510
557
 
558
+ type BindingRelocationContext = {
559
+ usedBindingsByGroup: Map<number, Set<number>>;
560
+ bindingRegistry?: Map<string, number>;
561
+ reservedBindingKeysByGroup: Map<number, Map<number, string>>;
562
+ };
563
+
564
+ type WGSLBindingAssignment = {
565
+ moduleName: string;
566
+ name: string;
567
+ group: number;
568
+ location: number;
569
+ };
570
+
571
+ type WGSLRelocationState = {
572
+ sawSupportedBindingDeclaration: boolean;
573
+ nextHintedBindingLocation: number | null;
574
+ };
575
+
576
+ type WGSLRelocationParams = {
577
+ isBindingFirst: boolean;
578
+ module: ShaderModule;
579
+ context: BindingRelocationContext;
580
+ bindingAssignments: WGSLBindingAssignment[];
581
+ relocationState: WGSLRelocationState;
582
+ };
583
+
584
+ function getUsedBindingsByGroupFromApplicationWGSL(source: string): Map<number, Set<number>> {
585
+ const usedBindingsByGroup = new Map<number, Set<number>>();
586
+
587
+ for (const regex of WGSL_BINDING_DECLARATION_REGEXES) {
588
+ regex.lastIndex = 0;
589
+ let match: RegExpExecArray | null;
590
+ while ((match = regex.exec(source))) {
591
+ const isBindingFirst = regex === WGSL_BINDING_DECLARATION_REGEXES[0];
592
+ const location = Number(match[isBindingFirst ? 1 : 2]);
593
+ const group = Number(match[isBindingFirst ? 2 : 1]);
594
+ const name = match[4];
595
+
596
+ validateApplicationWGSLBinding(group, location, name);
597
+ registerUsedBindingLocation(
598
+ usedBindingsByGroup,
599
+ group,
600
+ location,
601
+ `application binding "${name}"`
602
+ );
603
+ }
604
+ }
605
+
606
+ return usedBindingsByGroup;
607
+ }
608
+
609
+ function relocateWGSLModuleBindings(
610
+ moduleSource: string,
611
+ module: ShaderModule,
612
+ context: BindingRelocationContext
613
+ ): {source: string; bindingAssignments: WGSLBindingAssignment[]} {
614
+ const bindingAssignments: WGSLBindingAssignment[] = [];
615
+ const relocationState: WGSLRelocationState = {
616
+ sawSupportedBindingDeclaration: false,
617
+ nextHintedBindingLocation:
618
+ typeof module.firstBindingSlot === 'number' ? module.firstBindingSlot : null
619
+ };
620
+
621
+ let relocatedSource = relocateWGSLModuleBindingsWithRegex(
622
+ moduleSource,
623
+ MODULE_WGSL_BINDING_DECLARATION_REGEXES[0],
624
+ {isBindingFirst: true, module, context, bindingAssignments, relocationState}
625
+ );
626
+ relocatedSource = relocateWGSLModuleBindingsWithRegex(
627
+ relocatedSource,
628
+ MODULE_WGSL_BINDING_DECLARATION_REGEXES[1],
629
+ {isBindingFirst: false, module, context, bindingAssignments, relocationState}
630
+ );
631
+
632
+ if (moduleSource.includes('@binding(auto)') && !relocationState.sawSupportedBindingDeclaration) {
633
+ throw new Error(
634
+ `Unsupported @binding(auto) declaration form in module "${module.name}". ` +
635
+ 'Use "@group(N) @binding(auto) var ..." or "@binding(auto) @group(N) var ..." on a single line.'
636
+ );
637
+ }
638
+
639
+ return {source: relocatedSource, bindingAssignments};
640
+ }
641
+
642
+ function relocateWGSLModuleBindingsWithRegex(
643
+ source: string,
644
+ regex: RegExp,
645
+ params: WGSLRelocationParams
646
+ ): string {
647
+ return source.replace(regex, (...replaceArguments) =>
648
+ relocateWGSLModuleBindingMatch(replaceArguments, params)
649
+ );
650
+ }
651
+
652
+ function relocateWGSLModuleBindingMatch(
653
+ replaceArguments: unknown[],
654
+ params: WGSLRelocationParams
655
+ ): string {
656
+ const {isBindingFirst, module, context, bindingAssignments, relocationState} = params;
657
+ relocationState.sawSupportedBindingDeclaration = true;
658
+
659
+ const match = replaceArguments[0] as string;
660
+ const bindingToken = replaceArguments[isBindingFirst ? 1 : 2] as string;
661
+ const groupToken = replaceArguments[isBindingFirst ? 2 : 1] as string;
662
+ const name = replaceArguments[4] as string;
663
+ const group = Number(groupToken);
664
+
665
+ if (bindingToken === 'auto') {
666
+ const registryKey = getBindingRegistryKey(group, module.name, name);
667
+ const registryLocation = context.bindingRegistry?.get(registryKey);
668
+ const location =
669
+ registryLocation !== undefined
670
+ ? registryLocation
671
+ : relocationState.nextHintedBindingLocation === null
672
+ ? allocateAutoBindingLocation(group, context.usedBindingsByGroup)
673
+ : allocateAutoBindingLocation(
674
+ group,
675
+ context.usedBindingsByGroup,
676
+ relocationState.nextHintedBindingLocation
677
+ );
678
+ validateModuleWGSLBinding(module.name, group, location, name);
679
+ if (
680
+ registryLocation !== undefined &&
681
+ claimReservedBindingLocation(context.reservedBindingKeysByGroup, group, location, registryKey)
682
+ ) {
683
+ bindingAssignments.push({moduleName: module.name, name, group, location});
684
+ return match.replace(/@binding\(\s*auto\s*\)/, `@binding(${location})`);
685
+ }
686
+ registerUsedBindingLocation(
687
+ context.usedBindingsByGroup,
688
+ group,
689
+ location,
690
+ `module "${module.name}" binding "${name}"`
691
+ );
692
+ context.bindingRegistry?.set(registryKey, location);
693
+ bindingAssignments.push({moduleName: module.name, name, group, location});
694
+ if (relocationState.nextHintedBindingLocation !== null && registryLocation === undefined) {
695
+ relocationState.nextHintedBindingLocation = location + 1;
696
+ }
697
+ return match.replace(/@binding\(\s*auto\s*\)/, `@binding(${location})`);
698
+ }
699
+
700
+ const location = Number(bindingToken);
701
+ validateModuleWGSLBinding(module.name, group, location, name);
702
+ registerUsedBindingLocation(
703
+ context.usedBindingsByGroup,
704
+ group,
705
+ location,
706
+ `module "${module.name}" binding "${name}"`
707
+ );
708
+ bindingAssignments.push({moduleName: module.name, name, group, location});
709
+ return match;
710
+ }
711
+
712
+ function reserveRegisteredModuleBindings(
713
+ modules: ShaderModule[],
714
+ bindingRegistry: Map<string, number> | undefined,
715
+ usedBindingsByGroup: Map<number, Set<number>>
716
+ ): Map<number, Map<number, string>> {
717
+ const reservedBindingKeysByGroup = new Map<number, Map<number, string>>();
718
+ if (!bindingRegistry) {
719
+ return reservedBindingKeysByGroup;
720
+ }
721
+
722
+ for (const module of modules) {
723
+ for (const binding of getModuleWGSLBindingDeclarations(module)) {
724
+ const registryKey = getBindingRegistryKey(binding.group, module.name, binding.name);
725
+ const location = bindingRegistry.get(registryKey);
726
+ if (location !== undefined) {
727
+ const reservedBindingKeys =
728
+ reservedBindingKeysByGroup.get(binding.group) || new Map<number, string>();
729
+ const existingReservation = reservedBindingKeys.get(location);
730
+ if (existingReservation && existingReservation !== registryKey) {
731
+ throw new Error(
732
+ `Duplicate WGSL binding reservation for modules "${existingReservation}" and "${registryKey}": group ${binding.group}, binding ${location}.`
733
+ );
734
+ }
735
+
736
+ registerUsedBindingLocation(
737
+ usedBindingsByGroup,
738
+ binding.group,
739
+ location,
740
+ `registered module binding "${registryKey}"`
741
+ );
742
+ reservedBindingKeys.set(location, registryKey);
743
+ reservedBindingKeysByGroup.set(binding.group, reservedBindingKeys);
744
+ }
745
+ }
746
+ }
747
+
748
+ return reservedBindingKeysByGroup;
749
+ }
750
+
751
+ function claimReservedBindingLocation(
752
+ reservedBindingKeysByGroup: Map<number, Map<number, string>>,
753
+ group: number,
754
+ location: number,
755
+ registryKey: string
756
+ ): boolean {
757
+ const reservedBindingKeys = reservedBindingKeysByGroup.get(group);
758
+ if (!reservedBindingKeys) {
759
+ return false;
760
+ }
761
+
762
+ const reservedKey = reservedBindingKeys.get(location);
763
+ if (!reservedKey) {
764
+ return false;
765
+ }
766
+ if (reservedKey !== registryKey) {
767
+ throw new Error(
768
+ `Registered module binding "${registryKey}" collided with "${reservedKey}": group ${group}, binding ${location}.`
769
+ );
770
+ }
771
+ return true;
772
+ }
773
+
774
+ function getModuleWGSLBindingDeclarations(module: ShaderModule): {name: string; group: number}[] {
775
+ const declarations: {name: string; group: number}[] = [];
776
+ const moduleSource = module.source || '';
777
+
778
+ for (const regex of MODULE_WGSL_BINDING_DECLARATION_REGEXES) {
779
+ regex.lastIndex = 0;
780
+ let match: RegExpExecArray | null;
781
+ while ((match = regex.exec(moduleSource))) {
782
+ const isBindingFirst = regex === MODULE_WGSL_BINDING_DECLARATION_REGEXES[0];
783
+ declarations.push({
784
+ name: match[4],
785
+ group: Number(match[isBindingFirst ? 2 : 1])
786
+ });
787
+ }
788
+ }
789
+
790
+ return declarations;
791
+ }
792
+
793
+ function validateApplicationWGSLBinding(group: number, location: number, name: string): void {
794
+ if (group === 0 && location >= RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT) {
795
+ throw new Error(
796
+ `Application binding "${name}" in group 0 uses reserved binding ${location}. ` +
797
+ `Application-owned explicit group-0 bindings must stay below ${RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT}.`
798
+ );
799
+ }
800
+ }
801
+
802
+ function validateModuleWGSLBinding(
803
+ moduleName: string,
804
+ group: number,
805
+ location: number,
806
+ name: string
807
+ ): void {
808
+ if (group === 0 && location < RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT) {
809
+ throw new Error(
810
+ `Module "${moduleName}" binding "${name}" in group 0 uses reserved application binding ${location}. ` +
811
+ `Module-owned explicit group-0 bindings must be ${RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT} or higher.`
812
+ );
813
+ }
814
+ }
815
+
816
+ function registerUsedBindingLocation(
817
+ usedBindingsByGroup: Map<number, Set<number>>,
818
+ group: number,
819
+ location: number,
820
+ label: string
821
+ ): void {
822
+ const usedBindings = usedBindingsByGroup.get(group) || new Set<number>();
823
+ if (usedBindings.has(location)) {
824
+ throw new Error(
825
+ `Duplicate WGSL binding assignment for ${label}: group ${group}, binding ${location}.`
826
+ );
827
+ }
828
+ usedBindings.add(location);
829
+ usedBindingsByGroup.set(group, usedBindings);
830
+ }
831
+
832
+ function allocateAutoBindingLocation(
833
+ group: number,
834
+ usedBindingsByGroup: Map<number, Set<number>>,
835
+ preferredBindingLocation?: number
836
+ ): number {
837
+ const usedBindings = usedBindingsByGroup.get(group) || new Set<number>();
838
+ let nextBinding =
839
+ preferredBindingLocation ??
840
+ (group === 0
841
+ ? RESERVED_APPLICATION_GROUP_0_BINDING_LIMIT
842
+ : usedBindings.size > 0
843
+ ? Math.max(...usedBindings) + 1
844
+ : 0);
845
+
846
+ while (usedBindings.has(nextBinding)) {
847
+ nextBinding++;
848
+ }
849
+
850
+ return nextBinding;
851
+ }
852
+
853
+ function assertNoUnresolvedAutoBindings(source: string): void {
854
+ if (/@binding\(\s*auto\s*\)/.test(source)) {
855
+ throw new Error('Unresolved @binding(auto) remained in assembled WGSL source.');
856
+ }
857
+ }
858
+
859
+ function formatWGSLBindingAssignmentComments(bindingAssignments: WGSLBindingAssignment[]): string {
860
+ if (bindingAssignments.length === 0) {
861
+ return '';
862
+ }
863
+
864
+ let source = '// ----- MODULE WGSL BINDING ASSIGNMENTS ---------------\n';
865
+ for (const bindingAssignment of bindingAssignments) {
866
+ source += `// ${bindingAssignment.moduleName}.${bindingAssignment.name} -> @group(${bindingAssignment.group}) @binding(${bindingAssignment.location})\n`;
867
+ }
868
+ source += '\n';
869
+ return source;
870
+ }
871
+
872
+ function getBindingRegistryKey(group: number, moduleName: string, bindingName: string): string {
873
+ return `${group}:${moduleName}:${bindingName}`;
874
+ }
875
+
511
876
  /*
512
877
  function getHookFunctions(
513
878
  hookFunctions: Record<string, HookFunction>,
@@ -0,0 +1,216 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ type ShaderBindingAssignment = {
6
+ moduleName: string;
7
+ name: string;
8
+ group: number;
9
+ location: number;
10
+ };
11
+
12
+ const WGSL_BINDING_DEBUG_REGEXES = [
13
+ /@binding\(\s*(\d+)\s*\)\s*@group\(\s*(\d+)\s*\)\s*var(?:<([^>]+)>)?\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^;]+);/g,
14
+ /@group\(\s*(\d+)\s*\)\s*@binding\(\s*(\d+)\s*\)\s*var(?:<([^>]+)>)?\s+([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([^;]+);/g
15
+ ] as const;
16
+
17
+ /** One debug row describing a WGSL binding in the assembled shader source. */
18
+ export type ShaderBindingDebugRow = {
19
+ /** Binding name as declared in WGSL. */
20
+ name: string;
21
+ /** Bind-group index. */
22
+ group: number;
23
+ /** Binding slot within the bind group. */
24
+ binding: number;
25
+ /** Resource kind inferred from the WGSL declaration. */
26
+ kind:
27
+ | 'uniform'
28
+ | 'storage'
29
+ | 'read-only-storage'
30
+ | 'texture'
31
+ | 'sampler'
32
+ | 'storage-texture'
33
+ | 'unknown';
34
+ /** Whether the binding came from application WGSL or a shader module. */
35
+ owner: 'application' | 'module';
36
+ /** Shader module name when the binding was contributed by a module. */
37
+ moduleName?: string;
38
+ /** Full WGSL resource type text from the declaration. */
39
+ resourceType?: string;
40
+ /** WGSL access mode when cheaply available. */
41
+ access?: string;
42
+ /** Texture view dimension when cheaply available. */
43
+ viewDimension?: string;
44
+ /** Texture sample type when cheaply available. */
45
+ sampleType?: string;
46
+ /** Sampler kind when cheaply available. */
47
+ samplerKind?: string;
48
+ /** Whether the texture is multisampled when cheaply available. */
49
+ multisampled?: boolean;
50
+ };
51
+
52
+ /** Builds a stable, table-friendly binding summary from assembled WGSL source. */
53
+ export function getShaderBindingDebugRowsFromWGSL(
54
+ source: string,
55
+ bindingAssignments: ShaderBindingAssignment[] = []
56
+ ): ShaderBindingDebugRow[] {
57
+ const assignmentMap = new Map<string, string>();
58
+ for (const bindingAssignment of bindingAssignments) {
59
+ assignmentMap.set(
60
+ getBindingAssignmentKey(
61
+ bindingAssignment.name,
62
+ bindingAssignment.group,
63
+ bindingAssignment.location
64
+ ),
65
+ bindingAssignment.moduleName
66
+ );
67
+ }
68
+
69
+ const rows: ShaderBindingDebugRow[] = [];
70
+ for (const regex of WGSL_BINDING_DEBUG_REGEXES) {
71
+ regex.lastIndex = 0;
72
+ let match: RegExpExecArray | null;
73
+ while ((match = regex.exec(source))) {
74
+ const isBindingFirst = regex === WGSL_BINDING_DEBUG_REGEXES[0];
75
+ const binding = Number(match[isBindingFirst ? 1 : 2]);
76
+ const group = Number(match[isBindingFirst ? 2 : 1]);
77
+ const accessDeclaration = match[3]?.trim();
78
+ const name = match[4];
79
+ const resourceType = match[5].trim();
80
+ const moduleName = assignmentMap.get(getBindingAssignmentKey(name, group, binding));
81
+
82
+ rows.push(
83
+ normalizeShaderBindingDebugRow({
84
+ name,
85
+ group,
86
+ binding,
87
+ owner: moduleName ? 'module' : 'application',
88
+ moduleName,
89
+ accessDeclaration,
90
+ resourceType
91
+ })
92
+ );
93
+ }
94
+ }
95
+
96
+ return rows.sort((left, right) => {
97
+ if (left.group !== right.group) {
98
+ return left.group - right.group;
99
+ }
100
+ if (left.binding !== right.binding) {
101
+ return left.binding - right.binding;
102
+ }
103
+ return left.name.localeCompare(right.name);
104
+ });
105
+ }
106
+
107
+ function normalizeShaderBindingDebugRow(row: {
108
+ name: string;
109
+ group: number;
110
+ binding: number;
111
+ owner: 'application' | 'module';
112
+ moduleName?: string;
113
+ accessDeclaration?: string;
114
+ resourceType: string;
115
+ }): ShaderBindingDebugRow {
116
+ const baseRow: ShaderBindingDebugRow = {
117
+ name: row.name,
118
+ group: row.group,
119
+ binding: row.binding,
120
+ owner: row.owner,
121
+ kind: 'unknown',
122
+ moduleName: row.moduleName,
123
+ resourceType: row.resourceType
124
+ };
125
+
126
+ if (row.accessDeclaration) {
127
+ const access = row.accessDeclaration.split(',').map(value => value.trim());
128
+ if (access[0] === 'uniform') {
129
+ return {...baseRow, kind: 'uniform', access: 'uniform'};
130
+ }
131
+ if (access[0] === 'storage') {
132
+ const storageAccess = access[1] || 'read_write';
133
+ return {
134
+ ...baseRow,
135
+ kind: storageAccess === 'read' ? 'read-only-storage' : 'storage',
136
+ access: storageAccess
137
+ };
138
+ }
139
+ }
140
+
141
+ if (row.resourceType === 'sampler' || row.resourceType === 'sampler_comparison') {
142
+ return {
143
+ ...baseRow,
144
+ kind: 'sampler',
145
+ samplerKind: row.resourceType === 'sampler_comparison' ? 'comparison' : 'filtering'
146
+ };
147
+ }
148
+
149
+ if (row.resourceType.startsWith('texture_storage_')) {
150
+ return {
151
+ ...baseRow,
152
+ kind: 'storage-texture',
153
+ access: getStorageTextureAccess(row.resourceType),
154
+ viewDimension: getTextureViewDimension(row.resourceType)
155
+ };
156
+ }
157
+
158
+ if (row.resourceType.startsWith('texture_')) {
159
+ return {
160
+ ...baseRow,
161
+ kind: 'texture',
162
+ viewDimension: getTextureViewDimension(row.resourceType),
163
+ sampleType: getTextureSampleType(row.resourceType),
164
+ multisampled: row.resourceType.startsWith('texture_multisampled_')
165
+ };
166
+ }
167
+
168
+ return baseRow;
169
+ }
170
+
171
+ function getBindingAssignmentKey(name: string, group: number, binding: number): string {
172
+ return `${group}:${binding}:${name}`;
173
+ }
174
+
175
+ function getTextureViewDimension(resourceType: string): string | undefined {
176
+ if (resourceType.includes('cube_array')) {
177
+ return 'cube-array';
178
+ }
179
+ if (resourceType.includes('2d_array')) {
180
+ return '2d-array';
181
+ }
182
+ if (resourceType.includes('cube')) {
183
+ return 'cube';
184
+ }
185
+ if (resourceType.includes('3d')) {
186
+ return '3d';
187
+ }
188
+ if (resourceType.includes('2d')) {
189
+ return '2d';
190
+ }
191
+ if (resourceType.includes('1d')) {
192
+ return '1d';
193
+ }
194
+ return undefined;
195
+ }
196
+
197
+ function getTextureSampleType(resourceType: string): string | undefined {
198
+ if (resourceType.startsWith('texture_depth_')) {
199
+ return 'depth';
200
+ }
201
+ if (resourceType.includes('<i32>')) {
202
+ return 'sint';
203
+ }
204
+ if (resourceType.includes('<u32>')) {
205
+ return 'uint';
206
+ }
207
+ if (resourceType.includes('<f32>')) {
208
+ return 'float';
209
+ }
210
+ return undefined;
211
+ }
212
+
213
+ function getStorageTextureAccess(resourceType: string): string | undefined {
214
+ const match = /,\s*([A-Za-z_][A-Za-z0-9_]*)\s*>$/.exec(resourceType);
215
+ return match?.[1];
216
+ }