@babylonjs/smart-filters 8.25.0 → 8.25.2
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/optimization/smartFilterOptimizer.d.ts +43 -0
- package/dist/optimization/smartFilterOptimizer.d.ts.map +1 -1
- package/dist/optimization/smartFilterOptimizer.js +262 -143
- package/dist/optimization/smartFilterOptimizer.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/optimization/smartFilterOptimizer.ts +305 -158
- package/src/version.ts +1 -1
|
@@ -61,11 +61,54 @@ export declare class SmartFilterOptimizer {
|
|
|
61
61
|
private _initialize;
|
|
62
62
|
private _makeSymbolUnique;
|
|
63
63
|
private _processDefines;
|
|
64
|
+
/**
|
|
65
|
+
* Processes each helper function (any function that's not the main function), adding those to emit in the final
|
|
66
|
+
* block to _remappedSymbols and noting any necessary renames in renameWork. If a helper does not access any
|
|
67
|
+
* uniforms, it only needs to be emitted once regardless of how many instances of the block that define it are
|
|
68
|
+
* folded into the final optimized block.
|
|
69
|
+
* NOTE: so this function can know about the uniforms to test for them, it must be called after _processVariables.
|
|
70
|
+
* @param block - The block we are processing
|
|
71
|
+
* @param renameWork - The list of rename work to add to as needed
|
|
72
|
+
* @param samplerList - The list of sampler names
|
|
73
|
+
*/
|
|
64
74
|
private _processHelperFunctions;
|
|
65
75
|
private _processVariables;
|
|
66
76
|
private _processSampleTexture;
|
|
67
77
|
private _canBeOptimized;
|
|
68
78
|
private _optimizeBlock;
|
|
79
|
+
/**
|
|
80
|
+
* Replaces calls to __sampleTexture(foo, uv); with calls to a function bar(uv); for chaining optimized blocks together
|
|
81
|
+
* @param code - The code to process
|
|
82
|
+
* @param samplerName - The old name of the sampler
|
|
83
|
+
* @param functionName - The name of the function to call instead
|
|
84
|
+
* @returns The updated code
|
|
85
|
+
*/
|
|
86
|
+
private _replaceSampleTextureWithFunctionCall;
|
|
87
|
+
private _replaceSampleTextureWithTexture2DCall;
|
|
88
|
+
/**
|
|
89
|
+
* Processes all the functions, both main and helper functions, applying the renames and changes which have been collected
|
|
90
|
+
* @param block - The original block we are optimizing
|
|
91
|
+
* @param shaderProgram - The shader of the block we are optimizing
|
|
92
|
+
* @param renameWork - The rename work to apply
|
|
93
|
+
* @param newMainFunctionName - The new name for the main function
|
|
94
|
+
*/
|
|
95
|
+
private _processAllFunctions;
|
|
96
|
+
/**
|
|
97
|
+
* Applies all required changes specific to just the main function
|
|
98
|
+
* @param code - The code of the main function
|
|
99
|
+
* @param shaderProgram - The shader program containing the main function
|
|
100
|
+
* @param newMainFunctionName - The new name for the main function
|
|
101
|
+
* @returns The updated main function code
|
|
102
|
+
*/
|
|
103
|
+
private _processMainFunction;
|
|
104
|
+
/**
|
|
105
|
+
* Applies all required changes to a function (main or helper)
|
|
106
|
+
* @param block - The original block we are optimizing
|
|
107
|
+
* @param code - The code of the function
|
|
108
|
+
* @param renameWork - The rename work to apply
|
|
109
|
+
* @returns The updated function code
|
|
110
|
+
*/
|
|
111
|
+
private _processFunction;
|
|
69
112
|
private _saveBlockStackState;
|
|
70
113
|
private _restoreBlockStackState;
|
|
71
114
|
private _processBlock;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smartFilterOptimizer.d.ts","sourceRoot":"","sources":["../../src/optimization/smartFilterOptimizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,iCAAsB;AAG9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAIxE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"smartFilterOptimizer.d.ts","sourceRoot":"","sources":["../../src/optimization/smartFilterOptimizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,iCAAsB;AAG9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AAIxE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAiEhD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACpB;;OAEG;IACH,iBAAiB,EAAE,eAAe,EAAE,CAAC;IAErC;;OAEG;IACH,qBAAqB,EAAE,eAAe,CAAC;CAC1C,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,4BAA4B;IACzC;;OAEG;IACH,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAErC;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,kBAAkB,CAAc;IACxC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,iBAAiB,CAAwC;IACjE,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,sBAAsB,CAAwC;IAEtE,OAAO,CAAC,kBAAkB,CAAkC;IAC5D,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,wBAAwB,CAAqC;IACrE,OAAO,CAAC,uBAAuB,CAAkC;IACjE,OAAO,CAAC,gBAAgB,CAA0D;IAClF,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,4BAA4B,CAAmC;IACvE,OAAO,CAAC,iBAAiB,CAAkB;IAE3C;;;;OAIG;gBACS,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,EAAE,4BAA4B;IAQ5E;;;OAGG;IACI,QAAQ,IAAI,QAAQ,CAAC,WAAW,CAAC;IA8DxC,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,WAAW;IAWnB,OAAO,CAAC,iBAAiB;IAYzB,OAAO,CAAC,eAAe;IA0CvB;;;;;;;;;OASG;IACH,OAAO,CAAC,uBAAuB;IAoF/B,OAAO,CAAC,iBAAiB;IAmEzB,OAAO,CAAC,qBAAqB;IAsC7B,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,cAAc;IAoItB;;;;;;OAMG;IACH,OAAO,CAAC,qCAAqC;IAc7C,OAAO,CAAC,sCAAsC;IAa9C;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAe5B;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;;;;;OAMG;IACH,OAAO,CAAC,gBAAgB;IA4BxB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,aAAa;IAqHrB;;;;;;;;OAQG;IACH,OAAO,CAAC,wBAAwB;CAWnC"}
|
|
@@ -125,10 +125,10 @@ export class SmartFilterOptimizer {
|
|
|
125
125
|
}
|
|
126
126
|
return newVarName;
|
|
127
127
|
}
|
|
128
|
-
_processDefines(block,
|
|
128
|
+
_processDefines(block, renameWork) {
|
|
129
129
|
const defines = block.getShaderProgram().fragment.defines;
|
|
130
130
|
if (!defines) {
|
|
131
|
-
return
|
|
131
|
+
return;
|
|
132
132
|
}
|
|
133
133
|
for (const define of defines) {
|
|
134
134
|
const match = define.match(GetDefineRegEx);
|
|
@@ -154,57 +154,92 @@ export class SmartFilterOptimizer {
|
|
|
154
154
|
inputBlock: undefined,
|
|
155
155
|
});
|
|
156
156
|
}
|
|
157
|
-
//
|
|
158
|
-
|
|
157
|
+
// Note the rename to be used later in all the functions (main and helper)
|
|
158
|
+
renameWork.symbolRenames.push({
|
|
159
|
+
from: defName,
|
|
160
|
+
to: newDefName,
|
|
161
|
+
});
|
|
159
162
|
}
|
|
160
|
-
return code;
|
|
161
163
|
}
|
|
162
|
-
|
|
164
|
+
/**
|
|
165
|
+
* Processes each helper function (any function that's not the main function), adding those to emit in the final
|
|
166
|
+
* block to _remappedSymbols and noting any necessary renames in renameWork. If a helper does not access any
|
|
167
|
+
* uniforms, it only needs to be emitted once regardless of how many instances of the block that define it are
|
|
168
|
+
* folded into the final optimized block.
|
|
169
|
+
* NOTE: so this function can know about the uniforms to test for them, it must be called after _processVariables.
|
|
170
|
+
* @param block - The block we are processing
|
|
171
|
+
* @param renameWork - The list of rename work to add to as needed
|
|
172
|
+
* @param samplerList - The list of sampler names
|
|
173
|
+
*/
|
|
174
|
+
_processHelperFunctions(block, renameWork, samplerList) {
|
|
163
175
|
const functions = block.getShaderProgram().fragment.functions;
|
|
164
176
|
if (functions.length === 1) {
|
|
165
177
|
// There's only the main function, so we don't need to do anything
|
|
166
|
-
return
|
|
178
|
+
return;
|
|
167
179
|
}
|
|
168
|
-
const replaceFuncNames = [];
|
|
169
180
|
for (const func of functions) {
|
|
170
181
|
let funcName = func.name;
|
|
171
182
|
if (funcName === block.getShaderProgram().fragment.mainFunctionName) {
|
|
172
183
|
continue;
|
|
173
184
|
}
|
|
174
185
|
funcName = UndecorateSymbol(funcName);
|
|
175
|
-
|
|
186
|
+
// Test to see if this function accesses any uniforms
|
|
187
|
+
let uniformsAccessed = [];
|
|
188
|
+
for (const sampler of samplerList) {
|
|
189
|
+
if (func.code.includes(sampler)) {
|
|
190
|
+
uniformsAccessed.push(sampler);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const remappedSymbol of this._remappedSymbols) {
|
|
194
|
+
if (remappedSymbol.type === "uniform" &&
|
|
195
|
+
remappedSymbol.owners[0] &&
|
|
196
|
+
remappedSymbol.owners[0].blockType === block.blockType &&
|
|
197
|
+
func.code.includes(remappedSymbol.remappedName)) {
|
|
198
|
+
uniformsAccessed.push(remappedSymbol.remappedName);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Strip out any matches which are actually function params
|
|
202
|
+
const functionParams = func.params ? func.params.split(",").map((p) => p.trim().split(" ")[1]) : [];
|
|
203
|
+
uniformsAccessed = uniformsAccessed.filter((u) => !functionParams.includes(u));
|
|
204
|
+
// If it accessed any uniforms, throw an error
|
|
205
|
+
if (uniformsAccessed.length > 0) {
|
|
206
|
+
uniformsAccessed = uniformsAccessed.map((u) => (u[0] === DecorateChar ? UndecorateSymbol(u) : u));
|
|
207
|
+
throw new Error(`Helper function ${funcName} in blockType ${block.blockType} accesses uniform(s) ${uniformsAccessed.join(", ")} which is not supported. Pass them in instead.`);
|
|
208
|
+
}
|
|
209
|
+
// Look to see if we have an exact match including parameters of this function in the list of remapped symbols
|
|
176
210
|
const existingFunctionExactOverload = this._remappedSymbols.find((s) => s.type === "function" && s.name === funcName && s.params === func.params && s.owners[0] && s.owners[0].blockType === block.blockType);
|
|
211
|
+
// Look to see if we already have this function in the list of remapped symbols, regardless of parameters
|
|
177
212
|
const existingFunction = this._remappedSymbols.find((s) => s.type === "function" && s.name === funcName && s.owners[0] && s.owners[0].blockType === block.blockType);
|
|
178
213
|
// Get or create the remapped name, ignoring the parameter list
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if (!
|
|
182
|
-
|
|
214
|
+
let remappedName = existingFunction?.remappedName;
|
|
215
|
+
let createdNewName = false;
|
|
216
|
+
if (!remappedName) {
|
|
217
|
+
remappedName = DecorateSymbol(this._makeSymbolUnique(funcName));
|
|
218
|
+
createdNewName = true;
|
|
219
|
+
// Since we've created a new name add it to the list of symbol renames
|
|
220
|
+
renameWork.symbolRenames.push({
|
|
221
|
+
from: DecorateSymbol(funcName),
|
|
222
|
+
to: remappedName,
|
|
223
|
+
});
|
|
183
224
|
}
|
|
184
|
-
// If
|
|
185
|
-
// the final shader.
|
|
186
|
-
if (!existingFunctionExactOverload) {
|
|
187
|
-
let funcCode = func.code;
|
|
188
|
-
for (const [regex, replacement] of replaceFuncNames) {
|
|
189
|
-
funcCode = funcCode.replace(regex, replacement);
|
|
190
|
-
}
|
|
225
|
+
// If we created a new name, or if we didn't but this exact overload wasn't found,
|
|
226
|
+
// add it to the list of remapped symbols so it'll be emitted in the final shader.
|
|
227
|
+
if (createdNewName || !existingFunctionExactOverload) {
|
|
191
228
|
this._remappedSymbols.push({
|
|
192
229
|
type: "function",
|
|
193
230
|
name: funcName,
|
|
194
|
-
remappedName
|
|
231
|
+
remappedName,
|
|
195
232
|
params: func.params,
|
|
196
|
-
declaration:
|
|
233
|
+
declaration: func.code,
|
|
197
234
|
owners: [block],
|
|
198
235
|
inputBlock: undefined,
|
|
199
236
|
});
|
|
200
237
|
}
|
|
201
|
-
code = code.replace(regexFindCurName, newVarName);
|
|
202
238
|
}
|
|
203
|
-
return code;
|
|
204
239
|
}
|
|
205
|
-
_processVariables(block,
|
|
240
|
+
_processVariables(block, renameWork, varDecl, declarations, hasValue = false, forceSingleInstance = false) {
|
|
206
241
|
if (!declarations) {
|
|
207
|
-
return [
|
|
242
|
+
return [];
|
|
208
243
|
}
|
|
209
244
|
let rex = `${varDecl}\\s+(\\S+)\\s+${DecorateChar}(\\w+)${DecorateChar}\\s*`;
|
|
210
245
|
if (hasValue) {
|
|
@@ -246,14 +281,16 @@ export class SmartFilterOptimizer {
|
|
|
246
281
|
}
|
|
247
282
|
}
|
|
248
283
|
if (newVarName) {
|
|
249
|
-
|
|
284
|
+
renameWork.symbolRenames.push({
|
|
285
|
+
from: DecorateSymbol(varName),
|
|
286
|
+
to: newVarName,
|
|
287
|
+
});
|
|
250
288
|
}
|
|
251
289
|
match = rx.exec(declarations);
|
|
252
290
|
}
|
|
253
|
-
return
|
|
291
|
+
return samplerList;
|
|
254
292
|
}
|
|
255
|
-
_processSampleTexture(block,
|
|
256
|
-
const rx = new RegExp(`__sampleTexture\\s*\\(\\s*${DecorateChar}${sampler}${DecorateChar}\\s*,\\s*(.*?)\\s*\\)`);
|
|
293
|
+
_processSampleTexture(block, renameWork, sampler, samplers, inputTextureBlock) {
|
|
257
294
|
let newSamplerName = sampler;
|
|
258
295
|
const existingRemapped = this._remappedSymbols.find((s) => s.type === "sampler" && s.inputBlock && s.inputBlock === inputTextureBlock);
|
|
259
296
|
if (existingRemapped) {
|
|
@@ -274,13 +311,11 @@ export class SmartFilterOptimizer {
|
|
|
274
311
|
if (samplers.indexOf(newSamplerName) === -1) {
|
|
275
312
|
samplers.push(newSamplerName);
|
|
276
313
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
return code;
|
|
314
|
+
renameWork.samplerRenames.push({
|
|
315
|
+
from: sampler,
|
|
316
|
+
to: newSamplerName,
|
|
317
|
+
});
|
|
318
|
+
return UndecorateSymbol(newSamplerName);
|
|
284
319
|
}
|
|
285
320
|
_canBeOptimized(block) {
|
|
286
321
|
if (block.disableOptimization) {
|
|
@@ -300,118 +335,200 @@ export class SmartFilterOptimizer {
|
|
|
300
335
|
// Returns the name of the main function in the shader code
|
|
301
336
|
_optimizeBlock(optimizedBlock, outputConnectionPoint, samplers) {
|
|
302
337
|
const block = outputConnectionPoint.ownerBlock;
|
|
303
|
-
if (block instanceof ShaderBlock) {
|
|
304
|
-
|
|
305
|
-
|
|
338
|
+
if (!(block instanceof ShaderBlock)) {
|
|
339
|
+
throw `Unhandled block type! blockType=${block.blockType}`;
|
|
340
|
+
}
|
|
341
|
+
if (this._currentOutputTextureOptions === undefined) {
|
|
342
|
+
this._currentOutputTextureOptions = block.outputTextureOptions;
|
|
343
|
+
}
|
|
344
|
+
const shaderProgram = block.getShaderProgram();
|
|
345
|
+
if (!shaderProgram) {
|
|
346
|
+
throw new Error(`Shader program not found for block "${block.name}"!`);
|
|
347
|
+
}
|
|
348
|
+
this._vertexShaderCode = this._vertexShaderCode ?? shaderProgram.vertex;
|
|
349
|
+
// The operations we collect which we will apply to all functions of this block later
|
|
350
|
+
const renameWork = {
|
|
351
|
+
symbolRenames: [],
|
|
352
|
+
samplerRenames: [],
|
|
353
|
+
sampleToFunctionCallSwaps: [],
|
|
354
|
+
samplersToApplyAutoTo: [],
|
|
355
|
+
};
|
|
356
|
+
// Generates a unique name for the fragment main function (if not already generated)
|
|
357
|
+
const shaderFuncName = shaderProgram.fragment.mainFunctionName;
|
|
358
|
+
let newShaderFuncName = this._blockToMainFunctionName.get(block);
|
|
359
|
+
if (!newShaderFuncName) {
|
|
360
|
+
newShaderFuncName = UndecorateSymbol(shaderFuncName);
|
|
361
|
+
newShaderFuncName = DecorateSymbol(this._makeSymbolUnique(newShaderFuncName));
|
|
362
|
+
this._blockToMainFunctionName.set(block, newShaderFuncName);
|
|
363
|
+
this._dependencyGraph.addElement(newShaderFuncName);
|
|
364
|
+
}
|
|
365
|
+
// Processes the defines to make them unique
|
|
366
|
+
this._processDefines(block, renameWork);
|
|
367
|
+
// Processes the constants to make them unique
|
|
368
|
+
this._processVariables(block, renameWork, "const", shaderProgram.fragment.const, true);
|
|
369
|
+
// Processes the uniform inputs to make them unique. Also extract the list of samplers
|
|
370
|
+
let samplerList = [];
|
|
371
|
+
samplerList = this._processVariables(block, renameWork, "uniform", shaderProgram.fragment.uniform, false);
|
|
372
|
+
let additionalSamplers = [];
|
|
373
|
+
additionalSamplers = this._processVariables(block, renameWork, "uniform", shaderProgram.fragment.uniformSingle, false, true);
|
|
374
|
+
samplerList.push(...additionalSamplers);
|
|
375
|
+
// Processes the functions other than the main function - must be done after _processVariables()
|
|
376
|
+
this._processHelperFunctions(block, renameWork, samplerList);
|
|
377
|
+
// Processes the texture inputs
|
|
378
|
+
for (const sampler of samplerList) {
|
|
379
|
+
const samplerName = UndecorateSymbol(sampler);
|
|
380
|
+
const input = block.findInput(samplerName);
|
|
381
|
+
if (!input) {
|
|
382
|
+
// No connection point found corresponding to this texture: it must be a texture used internally by the filter (here we are assuming that the shader code is not bugged!)
|
|
383
|
+
this._processSampleTexture(block, renameWork, samplerName, samplers);
|
|
384
|
+
continue;
|
|
306
385
|
}
|
|
307
|
-
|
|
308
|
-
if (!
|
|
309
|
-
throw
|
|
386
|
+
// input found. Is it connected?
|
|
387
|
+
if (!input.connectedTo) {
|
|
388
|
+
throw `The connection point corresponding to the input named "${samplerName}" in block named "${block.name}" is not connected!`;
|
|
310
389
|
}
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// Generates a unique name for the fragment main function (if not already generated)
|
|
315
|
-
const shaderFuncName = shaderProgram.fragment.mainFunctionName;
|
|
316
|
-
let newShaderFuncName = this._blockToMainFunctionName.get(block);
|
|
317
|
-
if (!newShaderFuncName) {
|
|
318
|
-
newShaderFuncName = UndecorateSymbol(shaderFuncName);
|
|
319
|
-
newShaderFuncName = DecorateSymbol(this._makeSymbolUnique(newShaderFuncName));
|
|
320
|
-
this._blockToMainFunctionName.set(block, newShaderFuncName);
|
|
321
|
-
this._dependencyGraph.addElement(newShaderFuncName);
|
|
390
|
+
// If we are using the AutoSample strategy, we must preprocess the code that samples the texture
|
|
391
|
+
if (block instanceof DisableableShaderBlock && block.blockDisableStrategy === BlockDisableStrategy.AutoSample) {
|
|
392
|
+
renameWork.samplersToApplyAutoTo.push(sampler);
|
|
322
393
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
[code, additionalSamplers] = this._processVariables(block, code, "uniform", shaderProgram.fragment.uniformSingle, false, true);
|
|
340
|
-
samplerList.push(...additionalSamplers);
|
|
341
|
-
// Processes the texture inputs
|
|
342
|
-
for (const sampler of samplerList) {
|
|
343
|
-
const samplerName = UndecorateSymbol(sampler);
|
|
344
|
-
const input = block.findInput(samplerName);
|
|
345
|
-
if (!input) {
|
|
346
|
-
// No connection point found corresponding to this texture: it must be a texture used internally by the filter (here we are assuming that the shader code is not bugged!)
|
|
347
|
-
code = this._processSampleTexture(block, code, samplerName, samplers);
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
// input found. Is it connected?
|
|
351
|
-
if (!input.connectedTo) {
|
|
352
|
-
throw `The connection point corresponding to the input named "${samplerName}" in block named "${block.name}" is not connected!`;
|
|
353
|
-
}
|
|
354
|
-
// If we are using the AutoSample strategy, we must preprocess the code that samples the texture
|
|
355
|
-
if (block instanceof DisableableShaderBlock && block.blockDisableStrategy === BlockDisableStrategy.AutoSample) {
|
|
356
|
-
code = this._applyAutoSampleStrategy(code, sampler);
|
|
357
|
-
}
|
|
358
|
-
const parentBlock = input.connectedTo.ownerBlock;
|
|
359
|
-
if (IsTextureInputBlock(parentBlock)) {
|
|
360
|
-
// input is connected to an InputBlock of type "Texture": we must directly sample a texture
|
|
361
|
-
code = this._processSampleTexture(block, code, samplerName, samplers, parentBlock);
|
|
394
|
+
const parentBlock = input.connectedTo.ownerBlock;
|
|
395
|
+
if (IsTextureInputBlock(parentBlock)) {
|
|
396
|
+
// input is connected to an InputBlock of type "Texture": we must directly sample a texture
|
|
397
|
+
this._processSampleTexture(block, renameWork, samplerName, samplers, parentBlock);
|
|
398
|
+
}
|
|
399
|
+
else if (this._forceUnoptimized || !this._canBeOptimized(parentBlock)) {
|
|
400
|
+
// the block connected to this input cannot be optimized: we must directly sample its output texture
|
|
401
|
+
const uniqueSamplerName = this._processSampleTexture(block, renameWork, samplerName, samplers);
|
|
402
|
+
let stackItem = this._blockToStackItem.get(parentBlock);
|
|
403
|
+
if (!stackItem) {
|
|
404
|
+
stackItem = {
|
|
405
|
+
inputsToConnectTo: [],
|
|
406
|
+
outputConnectionPoint: input.connectedTo,
|
|
407
|
+
};
|
|
408
|
+
this._blockStack.push(stackItem);
|
|
409
|
+
this._blockToStackItem.set(parentBlock, stackItem);
|
|
362
410
|
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this._blockStack.push(stackItem);
|
|
373
|
-
this._blockToStackItem.set(parentBlock, stackItem);
|
|
374
|
-
}
|
|
375
|
-
// creates a new input connection point for the texture in the optimized block
|
|
376
|
-
const connectionPoint = optimizedBlock._registerInput(samplerName, ConnectionPointType.Texture);
|
|
377
|
-
stackItem.inputsToConnectTo.push(connectionPoint);
|
|
411
|
+
// creates a new input connection point for the texture in the optimized block
|
|
412
|
+
const connectionPoint = optimizedBlock._registerInput(uniqueSamplerName, ConnectionPointType.Texture);
|
|
413
|
+
stackItem.inputsToConnectTo.push(connectionPoint);
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
let parentFuncName;
|
|
417
|
+
if (this._blockToMainFunctionName.has(parentBlock)) {
|
|
418
|
+
// The parent block has already been processed. We can directly use the main function name
|
|
419
|
+
parentFuncName = this._blockToMainFunctionName.get(parentBlock);
|
|
378
420
|
}
|
|
379
421
|
else {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
parentFuncName = this._blockToMainFunctionName.get(parentBlock);
|
|
384
|
-
}
|
|
385
|
-
else {
|
|
386
|
-
// Recursively processes the block connected to this input to get the main function name of the parent block
|
|
387
|
-
parentFuncName = this._optimizeBlock(optimizedBlock, input.connectedTo, samplers);
|
|
388
|
-
this._dependencyGraph.addDependency(newShaderFuncName, parentFuncName);
|
|
389
|
-
}
|
|
390
|
-
// The texture samplerName is not used anymore by the block, as it is replaced by a call to the main function of the parent block
|
|
391
|
-
// We remap it to an non existent sampler name, because the code that binds the texture still exists in the ShaderBinding.bind function.
|
|
392
|
-
// We don't want this code to have any effect, as it could overwrite (and remove) the texture binding of another block using this same sampler name!
|
|
393
|
-
this._remappedSymbols.push({
|
|
394
|
-
type: "sampler",
|
|
395
|
-
name: samplerName,
|
|
396
|
-
remappedName: "L(° O °L)",
|
|
397
|
-
declaration: ``,
|
|
398
|
-
owners: [block],
|
|
399
|
-
inputBlock: undefined,
|
|
400
|
-
});
|
|
401
|
-
// We have to replace the call(s) to __sampleTexture by a call to the main function of the parent block
|
|
402
|
-
const rx = new RegExp(`__sampleTexture\\s*\\(\\s*${sampler}\\s*,\\s*(.*?)\\s*\\)`);
|
|
403
|
-
let match = rx.exec(code);
|
|
404
|
-
while (match !== null) {
|
|
405
|
-
const uv = match[1];
|
|
406
|
-
code = code.substring(0, match.index) + `${parentFuncName}(${uv})` + code.substring(match.index + match[0].length);
|
|
407
|
-
match = rx.exec(code);
|
|
408
|
-
}
|
|
422
|
+
// Recursively processes the block connected to this input to get the main function name of the parent block
|
|
423
|
+
parentFuncName = this._optimizeBlock(optimizedBlock, input.connectedTo, samplers);
|
|
424
|
+
this._dependencyGraph.addDependency(newShaderFuncName, parentFuncName);
|
|
409
425
|
}
|
|
426
|
+
// The texture samplerName is not used anymore by the block, as it is replaced by a call to the main function of the parent block
|
|
427
|
+
// We remap it to an non existent sampler name, because the code that binds the texture still exists in the ShaderBinding.bind function.
|
|
428
|
+
// We don't want this code to have any effect, as it could overwrite (and remove) the texture binding of another block using this same sampler name!
|
|
429
|
+
this._remappedSymbols.push({
|
|
430
|
+
type: "sampler",
|
|
431
|
+
name: samplerName,
|
|
432
|
+
remappedName: "L(° O °L)",
|
|
433
|
+
declaration: ``,
|
|
434
|
+
owners: [block],
|
|
435
|
+
inputBlock: undefined,
|
|
436
|
+
});
|
|
437
|
+
// We have to replace the call(s) to __sampleTexture by a call to the main function of the parent block
|
|
438
|
+
renameWork.sampleToFunctionCallSwaps.push({ from: DecorateSymbol(samplerName), to: parentFuncName });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
this._processAllFunctions(block, shaderProgram, renameWork, newShaderFuncName);
|
|
442
|
+
return newShaderFuncName;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Replaces calls to __sampleTexture(foo, uv); with calls to a function bar(uv); for chaining optimized blocks together
|
|
446
|
+
* @param code - The code to process
|
|
447
|
+
* @param samplerName - The old name of the sampler
|
|
448
|
+
* @param functionName - The name of the function to call instead
|
|
449
|
+
* @returns The updated code
|
|
450
|
+
*/
|
|
451
|
+
_replaceSampleTextureWithFunctionCall(code, samplerName, functionName) {
|
|
452
|
+
const rx = new RegExp(`__sampleTexture\\s*\\(\\s*${samplerName}\\s*,\\s*(.*?)\\s*\\)`);
|
|
453
|
+
let match = rx.exec(code);
|
|
454
|
+
while (match !== null) {
|
|
455
|
+
const uv = match[1];
|
|
456
|
+
code = code.substring(0, match.index) + `${functionName}(${uv})` + code.substring(match.index + match[0].length);
|
|
457
|
+
match = rx.exec(code);
|
|
458
|
+
}
|
|
459
|
+
return code;
|
|
460
|
+
}
|
|
461
|
+
_replaceSampleTextureWithTexture2DCall(code, sampler, newSamplerName) {
|
|
462
|
+
const rx = new RegExp(`__sampleTexture\\s*\\(\\s*${DecorateChar}${sampler}${DecorateChar}\\s*,\\s*(.*?)\\s*\\)`);
|
|
463
|
+
let match = rx.exec(code);
|
|
464
|
+
while (match !== null) {
|
|
465
|
+
const uv = match[1];
|
|
466
|
+
code = code.substring(0, match.index) + `texture2D(${newSamplerName}, ${uv})` + code.substring(match.index + match[0].length);
|
|
467
|
+
match = rx.exec(code);
|
|
468
|
+
}
|
|
469
|
+
return code;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Processes all the functions, both main and helper functions, applying the renames and changes which have been collected
|
|
473
|
+
* @param block - The original block we are optimizing
|
|
474
|
+
* @param shaderProgram - The shader of the block we are optimizing
|
|
475
|
+
* @param renameWork - The rename work to apply
|
|
476
|
+
* @param newMainFunctionName - The new name for the main function
|
|
477
|
+
*/
|
|
478
|
+
_processAllFunctions(block, shaderProgram, renameWork, newMainFunctionName) {
|
|
479
|
+
// Get the main function and process it
|
|
480
|
+
let declarationsAndMainFunction = GetShaderFragmentCode(shaderProgram, true);
|
|
481
|
+
declarationsAndMainFunction = this._processMainFunction(declarationsAndMainFunction, shaderProgram, newMainFunctionName);
|
|
482
|
+
declarationsAndMainFunction = this._processFunction(block, declarationsAndMainFunction, renameWork);
|
|
483
|
+
this._mainFunctionNameToCode.set(newMainFunctionName, declarationsAndMainFunction);
|
|
484
|
+
// Now process all the helper functions
|
|
485
|
+
this._remappedSymbols.forEach((remappedSymbol) => {
|
|
486
|
+
if (remappedSymbol.type === "function" && remappedSymbol.owners[0] && remappedSymbol.owners[0] === block) {
|
|
487
|
+
remappedSymbol.declaration = this._processFunction(block, remappedSymbol.declaration, renameWork);
|
|
410
488
|
}
|
|
411
|
-
|
|
412
|
-
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Applies all required changes specific to just the main function
|
|
493
|
+
* @param code - The code of the main function
|
|
494
|
+
* @param shaderProgram - The shader program containing the main function
|
|
495
|
+
* @param newMainFunctionName - The new name for the main function
|
|
496
|
+
* @returns The updated main function code
|
|
497
|
+
*/
|
|
498
|
+
_processMainFunction(code, shaderProgram, newMainFunctionName) {
|
|
499
|
+
// Replaces the main function name by the new one
|
|
500
|
+
code = code.replace(shaderProgram.fragment.mainFunctionName, newMainFunctionName);
|
|
501
|
+
// Removes the vUV declaration if it exists
|
|
502
|
+
code = code.replace(/varying\s+vec2\s+vUV\s*;/g, "");
|
|
503
|
+
return code;
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Applies all required changes to a function (main or helper)
|
|
507
|
+
* @param block - The original block we are optimizing
|
|
508
|
+
* @param code - The code of the function
|
|
509
|
+
* @param renameWork - The rename work to apply
|
|
510
|
+
* @returns The updated function code
|
|
511
|
+
*/
|
|
512
|
+
_processFunction(block, code, renameWork) {
|
|
513
|
+
// Replaces the texture2D calls by __sampleTexture for easier processing
|
|
514
|
+
code = code.replace(/(?<!\w)texture2D\s*\(/g, "__sampleTexture(");
|
|
515
|
+
for (const sampler of renameWork.samplersToApplyAutoTo) {
|
|
516
|
+
code = this._applyAutoSampleStrategy(code, sampler);
|
|
413
517
|
}
|
|
414
|
-
|
|
518
|
+
for (const rename of renameWork.symbolRenames) {
|
|
519
|
+
code = code.replace(new RegExp(`(?<!\\w)${rename.from}(?!\\w)`, "g"), rename.to);
|
|
520
|
+
}
|
|
521
|
+
for (const swap of renameWork.sampleToFunctionCallSwaps) {
|
|
522
|
+
code = this._replaceSampleTextureWithFunctionCall(code, swap.from, swap.to);
|
|
523
|
+
}
|
|
524
|
+
for (const rename of renameWork.samplerRenames) {
|
|
525
|
+
code = this._replaceSampleTextureWithTexture2DCall(code, rename.from, rename.to);
|
|
526
|
+
}
|
|
527
|
+
// Ensure all __sampleTexture( instances were replaced, and error out if not
|
|
528
|
+
if (code.indexOf("__sampleTexture(") > -1) {
|
|
529
|
+
throw new Error(`Could not optimize blockType ${block.blockType} because a texture2D() sampled something other than a uniform, which is unsupported`);
|
|
530
|
+
}
|
|
531
|
+
return code;
|
|
415
532
|
}
|
|
416
533
|
_saveBlockStackState() {
|
|
417
534
|
this._savedBlockStack = this._blockStack.slice();
|
|
@@ -456,7 +573,8 @@ export class SmartFilterOptimizer {
|
|
|
456
573
|
const codeDefines = [];
|
|
457
574
|
let codeUniforms = "";
|
|
458
575
|
let codeConsts = "";
|
|
459
|
-
let
|
|
576
|
+
let codeHelperFunctions = "";
|
|
577
|
+
let codeHelperFunctionPrototypes = "";
|
|
460
578
|
for (const s of this._remappedSymbols) {
|
|
461
579
|
switch (s.type) {
|
|
462
580
|
case "define":
|
|
@@ -470,7 +588,8 @@ export class SmartFilterOptimizer {
|
|
|
470
588
|
codeUniforms += s.declaration + "\n";
|
|
471
589
|
break;
|
|
472
590
|
case "function":
|
|
473
|
-
|
|
591
|
+
codeHelperFunctionPrototypes += s.declaration.replace(/{[\s\S]*$/, ";\n");
|
|
592
|
+
codeHelperFunctions += s.declaration + "\n";
|
|
474
593
|
break;
|
|
475
594
|
}
|
|
476
595
|
for (const block of s.owners) {
|
|
@@ -488,7 +607,7 @@ export class SmartFilterOptimizer {
|
|
|
488
607
|
}
|
|
489
608
|
}
|
|
490
609
|
// Builds and sets the final shader code
|
|
491
|
-
code =
|
|
610
|
+
code = codeHelperFunctionPrototypes + code + codeHelperFunctions;
|
|
492
611
|
if (ShowDebugData) {
|
|
493
612
|
code = code.replace(/^ {16}/gm, "");
|
|
494
613
|
code = code.replace(/\r/g, "");
|