@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.
@@ -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;AAqDhD;;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;IAyCvB,OAAO,CAAC,uBAAuB;IA4D/B,OAAO,CAAC,iBAAiB;IAgEzB,OAAO,CAAC,qBAAqB;IAsC7B,OAAO,CAAC,eAAe;IAoBvB,OAAO,CAAC,cAAc;IAiJtB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,aAAa;IAmHrB;;;;;;;;OAQG;IACH,OAAO,CAAC,wBAAwB;CAWnC"}
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, code) {
128
+ _processDefines(block, renameWork) {
129
129
  const defines = block.getShaderProgram().fragment.defines;
130
130
  if (!defines) {
131
- return code;
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
- // Replace the define name in the main shader code
158
- code = code.replace(defName, newDefName);
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
- _processHelperFunctions(block, code) {
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 code;
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
- const regexFindCurName = new RegExp(DecorateSymbol(funcName), "g");
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
- const newVarName = existingFunction?.remappedName ?? DecorateSymbol(this._makeSymbolUnique(funcName));
180
- // If the function name, regardless of params, wasn't found, add the rename mapping to our list
181
- if (!existingFunction) {
182
- replaceFuncNames.push([regexFindCurName, newVarName]);
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 this exact overload wasn't found, add it to the list of remapped symbols so it'll be emitted in
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: newVarName,
231
+ remappedName,
195
232
  params: func.params,
196
- declaration: funcCode,
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, code, varDecl, declarations, hasValue = false, forceSingleInstance = false) {
240
+ _processVariables(block, renameWork, varDecl, declarations, hasValue = false, forceSingleInstance = false) {
206
241
  if (!declarations) {
207
- return [code, []];
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
- code = code.replace(new RegExp(DecorateSymbol(varName), "g"), newVarName);
284
+ renameWork.symbolRenames.push({
285
+ from: DecorateSymbol(varName),
286
+ to: newVarName,
287
+ });
250
288
  }
251
289
  match = rx.exec(declarations);
252
290
  }
253
- return [code, samplerList];
291
+ return samplerList;
254
292
  }
255
- _processSampleTexture(block, code, sampler, samplers, inputTextureBlock) {
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
- let match = rx.exec(code);
278
- while (match !== null) {
279
- const uv = match[1];
280
- code = code.substring(0, match.index) + `texture2D(${newSamplerName}, ${uv})` + code.substring(match.index + match[0].length);
281
- match = rx.exec(code);
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
- if (this._currentOutputTextureOptions === undefined) {
305
- this._currentOutputTextureOptions = block.outputTextureOptions;
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
- const shaderProgram = block.getShaderProgram();
308
- if (!shaderProgram) {
309
- throw new Error(`Shader program not found for block "${block.name}"!`);
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
- // We get the shader code of the main function only
312
- let code = GetShaderFragmentCode(shaderProgram, true);
313
- this._vertexShaderCode = this._vertexShaderCode ?? shaderProgram.vertex;
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
- // Replaces the main function name by the new one
324
- code = code.replace(shaderFuncName, newShaderFuncName);
325
- // Removes the vUV declaration if it exists
326
- code = code.replace(/varying\s+vec2\s+vUV\s*;/g, "");
327
- // Replaces the texture2D calls by __sampleTexture for easier processing
328
- code = code.replace(/(?<!\w)texture2D\s*\(/g, " __sampleTexture(");
329
- // Processes the defines to make them unique
330
- code = this._processDefines(block, code);
331
- // Processes the functions other than the main function
332
- code = this._processHelperFunctions(block, code);
333
- // Processes the constants to make them unique
334
- code = this._processVariables(block, code, "const", shaderProgram.fragment.const, true)[0];
335
- // Processes the uniform inputs to make them unique. Also extract the list of samplers
336
- let samplerList = [];
337
- [code, samplerList] = this._processVariables(block, code, "uniform", shaderProgram.fragment.uniform, false);
338
- let additionalSamplers = [];
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
- else if (this._forceUnoptimized || !this._canBeOptimized(parentBlock)) {
364
- // the block connected to this input cannot be optimized: we must directly sample its output texture
365
- code = this._processSampleTexture(block, code, samplerName, samplers);
366
- let stackItem = this._blockToStackItem.get(parentBlock);
367
- if (!stackItem) {
368
- stackItem = {
369
- inputsToConnectTo: [],
370
- outputConnectionPoint: input.connectedTo,
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
- let parentFuncName;
381
- if (this._blockToMainFunctionName.has(parentBlock)) {
382
- // The parent block has already been processed. We can directly use the main function name
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
- this._mainFunctionNameToCode.set(newShaderFuncName, code);
412
- return newShaderFuncName;
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
- throw `Unhandled block type! blockType=${block.blockType}`;
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 codeFunctions = "";
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
- codeFunctions += s.declaration + "\n";
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 = codeFunctions + code;
610
+ code = codeHelperFunctionPrototypes + code + codeHelperFunctions;
492
611
  if (ShowDebugData) {
493
612
  code = code.replace(/^ {16}/gm, "");
494
613
  code = code.replace(/\r/g, "");