@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.
- package/dist/dist.dev.js +6555 -2373
- package/dist/dist.min.js +2984 -237
- package/dist/index.cjs +4356 -268
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/preprocessor/preprocessor.d.ts +11 -1
- package/dist/lib/preprocessor/preprocessor.d.ts.map +1 -1
- package/dist/lib/preprocessor/preprocessor.js +47 -2
- package/dist/lib/preprocessor/preprocessor.js.map +1 -1
- package/dist/lib/shader-assembler.d.ts +10 -10
- package/dist/lib/shader-assembler.d.ts.map +1 -1
- package/dist/lib/shader-assembler.js +36 -20
- package/dist/lib/shader-assembler.js.map +1 -1
- package/dist/lib/shader-assembly/assemble-shaders.d.ts +28 -7
- package/dist/lib/shader-assembly/assemble-shaders.d.ts.map +1 -1
- package/dist/lib/shader-assembly/assemble-shaders.js +133 -15
- package/dist/lib/shader-assembly/assemble-shaders.js.map +1 -1
- package/dist/lib/shader-assembly/platform-info.d.ts +2 -0
- package/dist/lib/shader-assembly/platform-info.d.ts.map +1 -1
- package/dist/lib/shader-assembly/shader-hooks.d.ts +1 -1
- package/dist/lib/shader-assembly/shader-hooks.d.ts.map +1 -1
- package/dist/lib/shader-assembly/shader-hooks.js +6 -3
- package/dist/lib/shader-assembly/shader-hooks.js.map +1 -1
- package/dist/lib/shader-assembly/shader-injections.d.ts +4 -1
- package/dist/lib/shader-assembly/shader-injections.d.ts.map +1 -1
- package/dist/lib/shader-assembly/shader-injections.js +68 -11
- package/dist/lib/shader-assembly/shader-injections.js.map +1 -1
- package/dist/lib/shader-assembly/shader-plugin-varyings.d.ts +15 -0
- package/dist/lib/shader-assembly/shader-plugin-varyings.d.ts.map +1 -0
- package/dist/lib/shader-assembly/shader-plugin-varyings.js +412 -0
- package/dist/lib/shader-assembly/shader-plugin-varyings.js.map +1 -0
- package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.d.ts +12 -0
- package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.d.ts.map +1 -0
- package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.js +237 -0
- package/dist/lib/shader-assembly/shader-plugin-vertex-inputs.js.map +1 -0
- package/dist/lib/shader-module/shader-module.d.ts +3 -0
- package/dist/lib/shader-module/shader-module.d.ts.map +1 -1
- package/dist/lib/shader-module/shader-module.js.map +1 -1
- package/dist/lib/shader-module/shader-pass-pipeline.d.ts +14 -0
- package/dist/lib/shader-module/shader-pass-pipeline.d.ts.map +1 -0
- package/dist/lib/shader-module/shader-pass-pipeline.js +5 -0
- package/dist/lib/shader-module/shader-pass-pipeline.js.map +1 -0
- package/dist/lib/shader-module/shader-pass.d.ts +21 -4
- package/dist/lib/shader-module/shader-pass.d.ts.map +1 -1
- package/dist/lib/shader-plugin.d.ts +68 -0
- package/dist/lib/shader-plugin.d.ts.map +1 -0
- package/dist/lib/shader-plugin.js +98 -0
- package/dist/lib/shader-plugin.js.map +1 -0
- package/dist/lib/utils/assert.js +1 -1
- package/dist/lib/utils/assert.js.map +1 -1
- package/dist/modules/color/float-colors.d.ts +111 -5
- package/dist/modules/color/float-colors.d.ts.map +1 -1
- package/dist/modules/color/float-colors.js +200 -34
- package/dist/modules/color/float-colors.js.map +1 -1
- package/dist/modules/engine/clip/clip.d.ts +12 -0
- package/dist/modules/engine/clip/clip.d.ts.map +1 -0
- package/dist/modules/engine/clip/clip.js +129 -0
- package/dist/modules/engine/clip/clip.js.map +1 -0
- package/dist/modules/engine/filter/filter.d.ts +12 -0
- package/dist/modules/engine/filter/filter.d.ts.map +1 -0
- package/dist/modules/engine/filter/filter.js +88 -0
- package/dist/modules/engine/filter/filter.js.map +1 -0
- package/dist/modules/geospatial/dggs/dggs-wgsl.d.ts +3 -0
- package/dist/modules/geospatial/dggs/dggs-wgsl.d.ts.map +1 -0
- package/dist/modules/geospatial/dggs/dggs-wgsl.js +1892 -0
- package/dist/modules/geospatial/dggs/dggs-wgsl.js.map +1 -0
- package/dist/modules/geospatial/dggs/dggs.d.ts +6 -0
- package/dist/modules/geospatial/dggs/dggs.d.ts.map +1 -0
- package/dist/modules/geospatial/dggs/dggs.js +10 -0
- package/dist/modules/geospatial/dggs/dggs.js.map +1 -0
- package/dist/modules/lighting/water-material/water-material.d.ts +28 -0
- package/dist/modules/lighting/water-material/water-material.d.ts.map +1 -0
- package/dist/modules/lighting/water-material/water-material.js +179 -0
- package/dist/modules/lighting/water-material/water-material.js.map +1 -0
- package/dist/modules/lighting/water-material/water-shaders-glsl.d.ts +3 -0
- package/dist/modules/lighting/water-material/water-shaders-glsl.d.ts.map +1 -0
- package/dist/modules/lighting/water-material/water-shaders-glsl.js +232 -0
- package/dist/modules/lighting/water-material/water-shaders-glsl.js.map +1 -0
- package/dist/modules/lighting/water-material/water-shaders-wgsl.d.ts +2 -0
- package/dist/modules/lighting/water-material/water-shaders-wgsl.d.ts.map +1 -0
- package/dist/modules/lighting/water-material/water-shaders-wgsl.js +230 -0
- package/dist/modules/lighting/water-material/water-shaders-wgsl.js.map +1 -0
- package/dist/modules/math/fp64/fp64-arithmetic-wgsl.d.ts +2 -1
- package/dist/modules/math/fp64/fp64-arithmetic-wgsl.d.ts.map +1 -1
- package/dist/modules/math/fp64/fp64-arithmetic-wgsl.js +268 -0
- package/dist/modules/math/fp64/fp64-arithmetic-wgsl.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +50 -3
- package/src/lib/preprocessor/preprocessor.ts +68 -3
- package/src/lib/shader-assembler.ts +49 -20
- package/src/lib/shader-assembly/assemble-shaders.ts +242 -30
- package/src/lib/shader-assembly/platform-info.ts +2 -0
- package/src/lib/shader-assembly/shader-hooks.ts +7 -3
- package/src/lib/shader-assembly/shader-injections.ts +96 -11
- package/src/lib/shader-assembly/shader-plugin-varyings.ts +524 -0
- package/src/lib/shader-assembly/shader-plugin-vertex-inputs.ts +279 -0
- package/src/lib/shader-module/shader-module.ts +3 -0
- package/src/lib/shader-module/shader-pass-pipeline.ts +19 -0
- package/src/lib/shader-module/shader-pass.ts +29 -4
- package/src/lib/shader-plugin.ts +214 -0
- package/src/lib/utils/assert.ts +1 -1
- package/src/modules/color/float-colors.ts +272 -37
- package/src/modules/engine/clip/clip.ts +149 -0
- package/src/modules/engine/filter/filter.ts +107 -0
- package/src/modules/geospatial/dggs/dggs-wgsl.ts +1892 -0
- package/src/modules/geospatial/dggs/dggs.ts +12 -0
- package/src/modules/lighting/water-material/water-material.ts +234 -0
- package/src/modules/lighting/water-material/water-shaders-glsl.ts +233 -0
- package/src/modules/lighting/water-material/water-shaders-wgsl.ts +230 -0
- 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
|
+
}
|