@luma.gl/shadertools 9.3.6 → 9.4.0-alpha.1

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 (113) hide show
  1. package/dist/dist.dev.js +6555 -2373
  2. package/dist/dist.min.js +2984 -237
  3. package/dist/index.cjs +4356 -268
  4. package/dist/index.cjs.map +4 -4
  5. package/dist/index.d.ts +13 -3
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +6 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/lib/preprocessor/preprocessor.d.ts +11 -1
  10. package/dist/lib/preprocessor/preprocessor.d.ts.map +1 -1
  11. package/dist/lib/preprocessor/preprocessor.js +47 -2
  12. package/dist/lib/preprocessor/preprocessor.js.map +1 -1
  13. package/dist/lib/shader-assembler.d.ts +10 -10
  14. package/dist/lib/shader-assembler.d.ts.map +1 -1
  15. package/dist/lib/shader-assembler.js +36 -20
  16. package/dist/lib/shader-assembler.js.map +1 -1
  17. package/dist/lib/shader-assembly/assemble-shaders.d.ts +28 -7
  18. package/dist/lib/shader-assembly/assemble-shaders.d.ts.map +1 -1
  19. package/dist/lib/shader-assembly/assemble-shaders.js +133 -15
  20. package/dist/lib/shader-assembly/assemble-shaders.js.map +1 -1
  21. package/dist/lib/shader-assembly/platform-info.d.ts +2 -0
  22. package/dist/lib/shader-assembly/platform-info.d.ts.map +1 -1
  23. package/dist/lib/shader-assembly/shader-hooks.d.ts +1 -1
  24. package/dist/lib/shader-assembly/shader-hooks.d.ts.map +1 -1
  25. package/dist/lib/shader-assembly/shader-hooks.js +6 -3
  26. package/dist/lib/shader-assembly/shader-hooks.js.map +1 -1
  27. package/dist/lib/shader-assembly/shader-injections.d.ts +4 -1
  28. package/dist/lib/shader-assembly/shader-injections.d.ts.map +1 -1
  29. package/dist/lib/shader-assembly/shader-injections.js +68 -11
  30. package/dist/lib/shader-assembly/shader-injections.js.map +1 -1
  31. package/dist/lib/shader-assembly/shader-plugin-varyings.d.ts +15 -0
  32. package/dist/lib/shader-assembly/shader-plugin-varyings.d.ts.map +1 -0
  33. package/dist/lib/shader-assembly/shader-plugin-varyings.js +412 -0
  34. package/dist/lib/shader-assembly/shader-plugin-varyings.js.map +1 -0
  35. package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.d.ts +12 -0
  36. package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.d.ts.map +1 -0
  37. package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.js +237 -0
  38. package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.js.map +1 -0
  39. package/dist/lib/shader-module/shader-module.d.ts +3 -0
  40. package/dist/lib/shader-module/shader-module.d.ts.map +1 -1
  41. package/dist/lib/shader-module/shader-module.js.map +1 -1
  42. package/dist/lib/shader-module/shader-pass-pipeline.d.ts +14 -0
  43. package/dist/lib/shader-module/shader-pass-pipeline.d.ts.map +1 -0
  44. package/dist/lib/shader-module/shader-pass-pipeline.js +5 -0
  45. package/dist/lib/shader-module/shader-pass-pipeline.js.map +1 -0
  46. package/dist/lib/shader-module/shader-pass.d.ts +21 -4
  47. package/dist/lib/shader-module/shader-pass.d.ts.map +1 -1
  48. package/dist/lib/shader-plugin.d.ts +68 -0
  49. package/dist/lib/shader-plugin.d.ts.map +1 -0
  50. package/dist/lib/shader-plugin.js +98 -0
  51. package/dist/lib/shader-plugin.js.map +1 -0
  52. package/dist/lib/utils/assert.js +1 -1
  53. package/dist/lib/utils/assert.js.map +1 -1
  54. package/dist/modules/color/float-colors.d.ts +111 -5
  55. package/dist/modules/color/float-colors.d.ts.map +1 -1
  56. package/dist/modules/color/float-colors.js +200 -34
  57. package/dist/modules/color/float-colors.js.map +1 -1
  58. package/dist/modules/engine/clip/clip.d.ts +12 -0
  59. package/dist/modules/engine/clip/clip.d.ts.map +1 -0
  60. package/dist/modules/engine/clip/clip.js +129 -0
  61. package/dist/modules/engine/clip/clip.js.map +1 -0
  62. package/dist/modules/engine/filter/filter.d.ts +12 -0
  63. package/dist/modules/engine/filter/filter.d.ts.map +1 -0
  64. package/dist/modules/engine/filter/filter.js +88 -0
  65. package/dist/modules/engine/filter/filter.js.map +1 -0
  66. package/dist/modules/geospatial/dggs/dggs-wgsl.d.ts +3 -0
  67. package/dist/modules/geospatial/dggs/dggs-wgsl.d.ts.map +1 -0
  68. package/dist/modules/geospatial/dggs/dggs-wgsl.js +1892 -0
  69. package/dist/modules/geospatial/dggs/dggs-wgsl.js.map +1 -0
  70. package/dist/modules/geospatial/dggs/dggs.d.ts +6 -0
  71. package/dist/modules/geospatial/dggs/dggs.d.ts.map +1 -0
  72. package/dist/modules/geospatial/dggs/dggs.js +10 -0
  73. package/dist/modules/geospatial/dggs/dggs.js.map +1 -0
  74. package/dist/modules/lighting/water-material/water-material.d.ts +28 -0
  75. package/dist/modules/lighting/water-material/water-material.d.ts.map +1 -0
  76. package/dist/modules/lighting/water-material/water-material.js +179 -0
  77. package/dist/modules/lighting/water-material/water-material.js.map +1 -0
  78. package/dist/modules/lighting/water-material/water-shaders-glsl.d.ts +3 -0
  79. package/dist/modules/lighting/water-material/water-shaders-glsl.d.ts.map +1 -0
  80. package/dist/modules/lighting/water-material/water-shaders-glsl.js +232 -0
  81. package/dist/modules/lighting/water-material/water-shaders-glsl.js.map +1 -0
  82. package/dist/modules/lighting/water-material/water-shaders-wgsl.d.ts +2 -0
  83. package/dist/modules/lighting/water-material/water-shaders-wgsl.d.ts.map +1 -0
  84. package/dist/modules/lighting/water-material/water-shaders-wgsl.js +230 -0
  85. package/dist/modules/lighting/water-material/water-shaders-wgsl.js.map +1 -0
  86. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.d.ts +2 -1
  87. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.d.ts.map +1 -1
  88. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.js +268 -0
  89. package/dist/modules/math/fp64/fp64-arithmetic-wgsl.js.map +1 -1
  90. package/package.json +2 -2
  91. package/src/index.ts +50 -3
  92. package/src/lib/preprocessor/preprocessor.ts +68 -3
  93. package/src/lib/shader-assembler.ts +49 -20
  94. package/src/lib/shader-assembly/assemble-shaders.ts +242 -30
  95. package/src/lib/shader-assembly/platform-info.ts +2 -0
  96. package/src/lib/shader-assembly/shader-hooks.ts +7 -3
  97. package/src/lib/shader-assembly/shader-injections.ts +96 -11
  98. package/src/lib/shader-assembly/shader-plugin-varyings.ts +524 -0
  99. package/src/lib/shader-assembly/shader-plugin-vertex-inputs.ts +279 -0
  100. package/src/lib/shader-module/shader-module.ts +3 -0
  101. package/src/lib/shader-module/shader-pass-pipeline.ts +19 -0
  102. package/src/lib/shader-module/shader-pass.ts +29 -4
  103. package/src/lib/shader-plugin.ts +214 -0
  104. package/src/lib/utils/assert.ts +1 -1
  105. package/src/modules/color/float-colors.ts +272 -37
  106. package/src/modules/engine/clip/clip.ts +149 -0
  107. package/src/modules/engine/filter/filter.ts +107 -0
  108. package/src/modules/geospatial/dggs/dggs-wgsl.ts +1892 -0
  109. package/src/modules/geospatial/dggs/dggs.ts +12 -0
  110. package/src/modules/lighting/water-material/water-material.ts +234 -0
  111. package/src/modules/lighting/water-material/water-shaders-glsl.ts +233 -0
  112. package/src/modules/lighting/water-material/water-shaders-wgsl.ts +230 -0
  113. package/src/modules/math/fp64/fp64-arithmetic-wgsl.ts +268 -0
@@ -0,0 +1,524 @@
1
+ // luma.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import {shaderTypeDecoder} from '@luma.gl/core';
6
+ import type {ResolvedShaderPluginVarying} from '../shader-plugin';
7
+ import {getGLSLAttributeType} from './shader-plugin-vertex-inputs';
8
+
9
+ export type ShaderPluginVaryingAssembly = {
10
+ source: string;
11
+ declarations: string;
12
+ vertexInitialization: string;
13
+ fragmentInitialization: string;
14
+ };
15
+
16
+ /** Generate one stage of the GLSL interface contributed by shader plugins. */
17
+ export function assembleShaderPluginVaryingsGLSL(
18
+ source: string,
19
+ stage: 'vertex' | 'fragment',
20
+ varyings: Record<string, ResolvedShaderPluginVarying>
21
+ ): {declarations: string; initialization: string} {
22
+ const declarations: string[] = [];
23
+ const initialization: string[] = [];
24
+ for (const [name, varying] of Object.entries(varyings)) {
25
+ assertGLSLVaryingIsNew(source, name);
26
+ const qualifier = varying.interpolation === 'flat' ? 'flat ' : '';
27
+ const direction = stage === 'vertex' ? 'out' : 'in';
28
+ declarations.push(`${qualifier}${direction} ${getGLSLAttributeType(varying.type)} ${name};`);
29
+ if (stage === 'vertex') {
30
+ initialization.push(`${name} = ${getGLSLZeroValue(varying.type)};`);
31
+ }
32
+ }
33
+ return {declarations: declarations.join('\n'), initialization: initialization.join('\n')};
34
+ }
35
+
36
+ /** Add plugin varyings to selected WGSL vertex and fragment entry points. */
37
+ export function assembleShaderPluginVaryingsWGSL(
38
+ source: string,
39
+ vertexEntryPoint: string,
40
+ fragmentEntryPoint: string,
41
+ varyings: Record<string, ResolvedShaderPluginVarying>
42
+ ): ShaderPluginVaryingAssembly {
43
+ const entries = Object.entries(varyings);
44
+ if (entries.length === 0) {
45
+ return {
46
+ source,
47
+ declarations: '',
48
+ vertexInitialization: '',
49
+ fragmentInitialization: ''
50
+ };
51
+ }
52
+
53
+ let transformedSource = source;
54
+ let vertexFunction = getWGSLFunctionRange(transformedSource, vertexEntryPoint, 'vertex');
55
+ const vertexOutputType = getWGSLNamedReturnType(transformedSource, vertexFunction);
56
+ let fragmentFunction = getWGSLFunctionRange(transformedSource, fragmentEntryPoint, 'fragment');
57
+ const fragmentInput = getWGSLFragmentStructInput(transformedSource, fragmentFunction);
58
+ const vertexOutputStruct = getWGSLStructRange(transformedSource, vertexOutputType);
59
+ const fragmentInputStruct = getWGSLStructRange(transformedSource, fragmentInput.type);
60
+
61
+ const interfaceNames = new Set([
62
+ ...getWGSLDeclaredNames(vertexFunction.parameters),
63
+ ...getWGSLDeclaredNames(vertexOutputStruct.body),
64
+ ...getWGSLDeclaredNames(fragmentFunction.parameters),
65
+ ...getWGSLDeclaredNames(fragmentInputStruct.body)
66
+ ]);
67
+ const usedLocations = new Set([
68
+ ...getWGSLLocations(vertexOutputStruct.body),
69
+ ...getWGSLLocations(fragmentInputStruct.body)
70
+ ]);
71
+ const fields: string[] = [];
72
+ const declarations: string[] = [];
73
+ const vertexInitialization: string[] = [];
74
+ const fragmentInitialization: string[] = [];
75
+
76
+ for (const [name, varying] of entries) {
77
+ if (interfaceNames.has(name) || hasWGSLModuleVariable(transformedSource, name)) {
78
+ throw new Error(
79
+ `ShaderPlugin varying "${name}" conflicts with existing WGSL stage I/O or a module variable`
80
+ );
81
+ }
82
+ const location = getFirstUnusedLocation(usedLocations);
83
+ usedLocations.add(location);
84
+ const interpolation = varying.interpolation === 'flat' ? ' @interpolate(flat)' : '';
85
+ fields.push(` @location(${location})${interpolation} ${name}: ${varying.type},`);
86
+ declarations.push(`var<private> ${name}: ${varying.type};`);
87
+ vertexInitialization.push(`${name} = ${getWGSLZeroValue(varying.type)};`);
88
+ fragmentInitialization.push(`${name} = ${fragmentInput.name}.${name};`);
89
+ }
90
+
91
+ assertWGSLStructConstructorsAreLocal(
92
+ transformedSource,
93
+ vertexOutputType,
94
+ vertexFunction.openBrace,
95
+ vertexFunction.closeBrace
96
+ );
97
+ transformedSource = appendWGSLConstructorArgumentsInFunction(
98
+ transformedSource,
99
+ vertexOutputType,
100
+ vertexFunction,
101
+ entries.map(([name]) => name)
102
+ );
103
+ vertexFunction = getWGSLFunctionRange(transformedSource, vertexEntryPoint, 'vertex');
104
+ transformedSource = rewriteWGSLVertexReturns(
105
+ transformedSource,
106
+ vertexFunction,
107
+ entries.map(([name]) => name)
108
+ );
109
+
110
+ const structNames =
111
+ vertexOutputType === fragmentInput.type
112
+ ? [vertexOutputType]
113
+ : [vertexOutputType, fragmentInput.type];
114
+ const structInsertions = structNames
115
+ .map(structName => getWGSLStructRange(transformedSource, structName).closeBrace)
116
+ .sort((left, right) => right - left);
117
+ for (const insertionIndex of structInsertions) {
118
+ transformedSource =
119
+ transformedSource.slice(0, insertionIndex) +
120
+ `${fields.join('\n')}\n` +
121
+ transformedSource.slice(insertionIndex);
122
+ }
123
+
124
+ fragmentFunction = getWGSLFunctionRange(transformedSource, fragmentEntryPoint, 'fragment');
125
+ const fragmentInputPattern = new RegExp(`\\b${escapeRegExp(fragmentInput.name)}\\s*:`);
126
+ if (!fragmentInputPattern.test(fragmentFunction.parameters)) {
127
+ throw new Error(`Unable to preserve WGSL fragment input "${fragmentInput.name}"`);
128
+ }
129
+
130
+ return {
131
+ source: transformedSource,
132
+ declarations: declarations.join('\n'),
133
+ vertexInitialization: vertexInitialization.join('\n'),
134
+ fragmentInitialization: fragmentInitialization.join('\n')
135
+ };
136
+ }
137
+
138
+ type WGSLFunctionRange = {
139
+ openParenthesis: number;
140
+ closeParenthesis: number;
141
+ openBrace: number;
142
+ closeBrace: number;
143
+ parameters: string;
144
+ };
145
+
146
+ type WGSLStructRange = {
147
+ openBrace: number;
148
+ closeBrace: number;
149
+ body: string;
150
+ };
151
+
152
+ function getWGSLFunctionRange(
153
+ source: string,
154
+ entryPoint: string,
155
+ stage: 'vertex' | 'fragment'
156
+ ): WGSLFunctionRange {
157
+ const functionPattern = new RegExp(`\\bfn\\s+${escapeRegExp(entryPoint)}\\s*\\(`, 'g');
158
+ const match = functionPattern.exec(source);
159
+ if (!match) {
160
+ throw new Error(`ShaderPlugin varyings require WGSL ${stage} entry point "${entryPoint}"`);
161
+ }
162
+ const openParenthesis = source.indexOf('(', match.index);
163
+ const closeParenthesis = findMatchingDelimiter(source, openParenthesis, '(', ')');
164
+ const openBrace = source.indexOf('{', closeParenthesis);
165
+ const closeBrace = findMatchingDelimiter(source, openBrace, '{', '}');
166
+ if (closeParenthesis < 0 || openBrace < 0 || closeBrace < 0) {
167
+ throw new Error(`Unable to parse WGSL ${stage} entry point "${entryPoint}"`);
168
+ }
169
+ return {
170
+ openParenthesis,
171
+ closeParenthesis,
172
+ openBrace,
173
+ closeBrace,
174
+ parameters: source.slice(openParenthesis + 1, closeParenthesis)
175
+ };
176
+ }
177
+
178
+ function getWGSLNamedReturnType(source: string, functionRange: WGSLFunctionRange): string {
179
+ const resultSource = source.slice(functionRange.closeParenthesis + 1, functionRange.openBrace);
180
+ const match = /->\s*([A-Za-z_][\w]*)\s*$/.exec(resultSource.trim());
181
+ if (!match || getWGSLStructRangeOrNull(source, match[1]) === null) {
182
+ throw new Error(
183
+ 'ShaderPlugin varyings require the WGSL vertex entry point to return a named struct'
184
+ );
185
+ }
186
+ return match[1];
187
+ }
188
+
189
+ function getWGSLFragmentStructInput(
190
+ source: string,
191
+ functionRange: WGSLFunctionRange
192
+ ): {name: string; type: string} {
193
+ const candidates: {name: string; type: string}[] = [];
194
+ for (const parameter of splitTopLevel(functionRange.parameters, ',')) {
195
+ const match =
196
+ /(?:@[A-Za-z_][\w]*(?:\([^)]*\))?\s*)*([A-Za-z_][\w]*)\s*:\s*([A-Za-z_][\w]*)\s*$/.exec(
197
+ parameter.trim()
198
+ );
199
+ if (match && getWGSLStructRangeOrNull(source, match[2])) {
200
+ candidates.push({name: match[1], type: match[2]});
201
+ }
202
+ }
203
+ if (candidates.length !== 1) {
204
+ throw new Error(
205
+ `ShaderPlugin varyings require exactly one named WGSL fragment input struct; found ${candidates.length}`
206
+ );
207
+ }
208
+ return candidates[0];
209
+ }
210
+
211
+ function getWGSLStructRange(source: string, structName: string): WGSLStructRange {
212
+ const range = getWGSLStructRangeOrNull(source, structName);
213
+ if (!range) {
214
+ throw new Error(`Unable to find WGSL stage I/O struct "${structName}"`);
215
+ }
216
+ return range;
217
+ }
218
+
219
+ function getWGSLStructRangeOrNull(source: string, structName: string): WGSLStructRange | null {
220
+ const structPattern = new RegExp(`\\bstruct\\s+${escapeRegExp(structName)}\\s*\\{`, 'g');
221
+ const match = structPattern.exec(source);
222
+ if (!match) {
223
+ return null;
224
+ }
225
+ const openBrace = source.indexOf('{', match.index);
226
+ const closeBrace = findMatchingDelimiter(source, openBrace, '{', '}');
227
+ return closeBrace < 0
228
+ ? null
229
+ : {openBrace, closeBrace, body: source.slice(openBrace + 1, closeBrace)};
230
+ }
231
+
232
+ function assertWGSLStructConstructorsAreLocal(
233
+ source: string,
234
+ structName: string,
235
+ functionStart: number,
236
+ functionEnd: number
237
+ ): void {
238
+ const constructorPattern = new RegExp(`\\b${escapeRegExp(structName)}\\s*\\(`, 'g');
239
+ let match = constructorPattern.exec(source);
240
+ while (match) {
241
+ if (match.index < functionStart || match.index > functionEnd) {
242
+ throw new Error(
243
+ `ShaderPlugin varying output struct "${structName}" is constructed outside the selected vertex entry point`
244
+ );
245
+ }
246
+ match = constructorPattern.exec(source);
247
+ }
248
+ }
249
+
250
+ function appendWGSLConstructorArgumentsInFunction(
251
+ source: string,
252
+ structName: string,
253
+ functionRange: WGSLFunctionRange,
254
+ varyingNames: string[]
255
+ ): string {
256
+ const constructorPattern = new RegExp(`\\b${escapeRegExp(structName)}\\s*\\(`, 'g');
257
+ const insertions: Array<{openParenthesis: number; closeParenthesis: number}> = [];
258
+ let match = constructorPattern.exec(source);
259
+ while (match) {
260
+ if (match.index > functionRange.openBrace && match.index < functionRange.closeBrace) {
261
+ const openParenthesis = source.indexOf('(', match.index);
262
+ const closeParenthesis = findMatchingDelimiter(source, openParenthesis, '(', ')');
263
+ if (closeParenthesis < 0 || closeParenthesis > functionRange.closeBrace) {
264
+ throw new Error(`Unable to parse WGSL output constructor "${structName}"`);
265
+ }
266
+ insertions.push({openParenthesis, closeParenthesis});
267
+ }
268
+ match = constructorPattern.exec(source);
269
+ }
270
+ for (const insertion of insertions.sort(
271
+ (left, right) => right.closeParenthesis - left.closeParenthesis
272
+ )) {
273
+ const existingArguments = source
274
+ .slice(insertion.openParenthesis + 1, insertion.closeParenthesis)
275
+ .trim();
276
+ const separator = existingArguments ? ', ' : '';
277
+ source =
278
+ source.slice(0, insertion.closeParenthesis) +
279
+ separator +
280
+ varyingNames.join(', ') +
281
+ source.slice(insertion.closeParenthesis);
282
+ }
283
+ return source;
284
+ }
285
+
286
+ function rewriteWGSLVertexReturns(
287
+ source: string,
288
+ functionRange: WGSLFunctionRange,
289
+ varyingNames: string[]
290
+ ): string {
291
+ const returns = findWGSLReturnStatements(
292
+ source,
293
+ functionRange.openBrace + 1,
294
+ functionRange.closeBrace
295
+ );
296
+ for (let returnIndex = returns.length - 1; returnIndex >= 0; returnIndex--) {
297
+ const statement = returns[returnIndex];
298
+ const expression = source.slice(statement.expressionStart, statement.semicolon).trim();
299
+ if (!expression) {
300
+ throw new Error('ShaderPlugin varying vertex entry point cannot use an empty return');
301
+ }
302
+ const outputName = `_luma_vertexOutput${returnIndex}`;
303
+ const assignments = varyingNames.map(name => `${outputName}.${name} = ${name};`).join('\n');
304
+ const replacement = `{\nvar ${outputName} = ${expression};\n${assignments}\nreturn ${outputName};\n}`;
305
+ source = source.slice(0, statement.start) + replacement + source.slice(statement.semicolon + 1);
306
+ }
307
+ return source;
308
+ }
309
+
310
+ function findWGSLReturnStatements(
311
+ source: string,
312
+ startIndex: number,
313
+ endIndex: number
314
+ ): Array<{start: number; expressionStart: number; semicolon: number}> {
315
+ const statements: Array<{start: number; expressionStart: number; semicolon: number}> = [];
316
+ let index = startIndex;
317
+ while (index < endIndex) {
318
+ index = skipWGSLTrivia(source, index, endIndex);
319
+ if (
320
+ source.slice(index, index + 6) === 'return' &&
321
+ !/[A-Za-z0-9_]/.test(source[index + 6] || '')
322
+ ) {
323
+ const expressionStart = index + 6;
324
+ const semicolon = findStatementSemicolon(source, expressionStart, endIndex);
325
+ if (semicolon < 0) {
326
+ throw new Error('Unable to parse WGSL return statement in selected vertex entry point');
327
+ }
328
+ statements.push({start: index, expressionStart, semicolon});
329
+ index = semicolon + 1;
330
+ } else {
331
+ index++;
332
+ }
333
+ }
334
+ return statements;
335
+ }
336
+
337
+ function findStatementSemicolon(source: string, startIndex: number, endIndex: number): number {
338
+ let parenthesisDepth = 0;
339
+ let bracketDepth = 0;
340
+ for (let index = startIndex; index < endIndex; index++) {
341
+ const skippedIndex = skipWGSLTrivia(source, index, endIndex);
342
+ if (skippedIndex !== index) {
343
+ index = skippedIndex - 1;
344
+ continue;
345
+ }
346
+ const character = source[index];
347
+ if (character === '(') parenthesisDepth++;
348
+ if (character === ')') parenthesisDepth--;
349
+ if (character === '[') bracketDepth++;
350
+ if (character === ']') bracketDepth--;
351
+ if (character === ';' && parenthesisDepth === 0 && bracketDepth === 0) return index;
352
+ }
353
+ return -1;
354
+ }
355
+
356
+ function skipWGSLTrivia(source: string, startIndex: number, endIndex: number): number {
357
+ let index = startIndex;
358
+ if (source[index] === '/' && source[index + 1] === '/') {
359
+ const newline = source.indexOf('\n', index + 2);
360
+ return newline < 0 || newline > endIndex ? endIndex : newline + 1;
361
+ }
362
+ if (source[index] === '/' && source[index + 1] === '*') {
363
+ let depth = 1;
364
+ index += 2;
365
+ while (index < endIndex && depth > 0) {
366
+ if (source[index] === '/' && source[index + 1] === '*') {
367
+ depth++;
368
+ index += 2;
369
+ } else if (source[index] === '*' && source[index + 1] === '/') {
370
+ depth--;
371
+ index += 2;
372
+ } else {
373
+ index++;
374
+ }
375
+ }
376
+ }
377
+ return index;
378
+ }
379
+
380
+ function splitTopLevel(source: string, delimiter: string): string[] {
381
+ const parts: string[] = [];
382
+ let startIndex = 0;
383
+ let parenthesisDepth = 0;
384
+ let angleDepth = 0;
385
+ for (let index = 0; index < source.length; index++) {
386
+ const character = source[index];
387
+ if (character === '(') parenthesisDepth++;
388
+ if (character === ')') parenthesisDepth--;
389
+ if (character === '<') angleDepth++;
390
+ if (character === '>') angleDepth--;
391
+ if (character === delimiter && parenthesisDepth === 0 && angleDepth === 0) {
392
+ parts.push(source.slice(startIndex, index));
393
+ startIndex = index + 1;
394
+ }
395
+ }
396
+ parts.push(source.slice(startIndex));
397
+ return parts;
398
+ }
399
+
400
+ function getWGSLLocations(source: string): number[] {
401
+ const locations: number[] = [];
402
+ const locationPattern = /@location\s*\(\s*(\d+)\s*\)/g;
403
+ let match = locationPattern.exec(source);
404
+ while (match) {
405
+ locations.push(Number(match[1]));
406
+ match = locationPattern.exec(source);
407
+ }
408
+ return locations;
409
+ }
410
+
411
+ function getWGSLDeclaredNames(source: string): string[] {
412
+ const names: string[] = [];
413
+ const namePattern = /(?:^|,)\s*(?:@[A-Za-z_][\w]*(?:\([^)]*\))?\s*)*([A-Za-z_][\w]*)\s*:/gm;
414
+ let match = namePattern.exec(source);
415
+ while (match) {
416
+ names.push(match[1]);
417
+ match = namePattern.exec(source);
418
+ }
419
+ return names;
420
+ }
421
+
422
+ function hasWGSLModuleVariable(source: string, name: string): boolean {
423
+ const variablePattern = new RegExp(
424
+ `\\b(?:var(?:<[^>]+>)?|let|const)\\s+${escapeRegExp(name)}\\b`,
425
+ 'g'
426
+ );
427
+ let match = variablePattern.exec(source);
428
+ while (match) {
429
+ if (getBraceDepthAt(source, match.index) === 0) {
430
+ return true;
431
+ }
432
+ match = variablePattern.exec(source);
433
+ }
434
+ return false;
435
+ }
436
+
437
+ function getFirstUnusedLocation(usedLocations: Set<number>): number {
438
+ let location = 0;
439
+ while (usedLocations.has(location)) location++;
440
+ return location;
441
+ }
442
+
443
+ function getGLSLZeroValue(type: ResolvedShaderPluginVarying['type']): string {
444
+ const {primitiveType, components} = shaderTypeDecoder.getAttributeShaderTypeInfo(type);
445
+ const scalar = primitiveType === 'u32' ? '0u' : primitiveType === 'i32' ? '0' : '0.0';
446
+ return components === 1 ? scalar : `${getGLSLAttributeType(type)}(${scalar})`;
447
+ }
448
+
449
+ function getWGSLZeroValue(type: ResolvedShaderPluginVarying['type']): string {
450
+ const {primitiveType, components} = shaderTypeDecoder.getAttributeShaderTypeInfo(type);
451
+ const scalar = `${primitiveType}(0)`;
452
+ return components === 1 ? scalar : `${type}(${scalar})`;
453
+ }
454
+
455
+ function assertGLSLVaryingIsNew(source: string, name: string): void {
456
+ const declaration = new RegExp(
457
+ `\\b(?:flat\\s+|smooth\\s+)?(?:in|out|varying)\\s+` +
458
+ `(?:(?:lowp|mediump|highp)\\s+)?[A-Za-z_][A-Za-z0-9_]*\\s+` +
459
+ `${escapeRegExp(name)}\\s*(?:\\[|;)`
460
+ );
461
+ if (declaration.test(source)) {
462
+ throw new Error(`ShaderPlugin varying "${name}" conflicts with existing GLSL stage I/O`);
463
+ }
464
+ }
465
+
466
+ function findMatchingDelimiter(
467
+ source: string,
468
+ openIndex: number,
469
+ openCharacter: string,
470
+ closeCharacter: string
471
+ ): number {
472
+ let depth = 0;
473
+ let blockCommentDepth = 0;
474
+ let lineComment = false;
475
+ for (let index = openIndex; index < source.length; index++) {
476
+ const character = source[index];
477
+ const nextCharacter = source[index + 1];
478
+ if (lineComment) {
479
+ if (character === '\n') lineComment = false;
480
+ continue;
481
+ }
482
+ if (blockCommentDepth > 0) {
483
+ if (character === '/' && nextCharacter === '*') {
484
+ blockCommentDepth++;
485
+ index++;
486
+ } else if (character === '*' && nextCharacter === '/') {
487
+ blockCommentDepth--;
488
+ index++;
489
+ }
490
+ continue;
491
+ }
492
+ if (character === '/' && nextCharacter === '/') {
493
+ lineComment = true;
494
+ index++;
495
+ continue;
496
+ }
497
+ if (character === '/' && nextCharacter === '*') {
498
+ blockCommentDepth = 1;
499
+ index++;
500
+ continue;
501
+ }
502
+ if (character === openCharacter) depth++;
503
+ if (character === closeCharacter && --depth === 0) return index;
504
+ }
505
+ return -1;
506
+ }
507
+
508
+ function getBraceDepthAt(source: string, endIndex: number): number {
509
+ let depth = 0;
510
+ for (let index = 0; index < endIndex; index++) {
511
+ const skippedIndex = skipWGSLTrivia(source, index, endIndex);
512
+ if (skippedIndex !== index) {
513
+ index = skippedIndex - 1;
514
+ continue;
515
+ }
516
+ if (source[index] === '{') depth++;
517
+ if (source[index] === '}') depth--;
518
+ }
519
+ return depth;
520
+ }
521
+
522
+ function escapeRegExp(value: string): string {
523
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
524
+ }