@babylonjs/smart-filters 1.0.13 → 8.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/blockFoundation/aggregateBlock.d.ts +3 -3
  2. package/dist/blockFoundation/aggregateBlock.d.ts.map +1 -1
  3. package/dist/blockFoundation/aggregateBlock.js +1 -2
  4. package/dist/blockFoundation/aggregateBlock.js.map +1 -1
  5. package/dist/blockFoundation/baseBlock.d.ts +2 -2
  6. package/dist/blockFoundation/baseBlock.d.ts.map +1 -1
  7. package/dist/blockFoundation/baseBlock.js +5 -6
  8. package/dist/blockFoundation/baseBlock.js.map +1 -1
  9. package/dist/blockFoundation/customAggregateBlock.d.ts.map +1 -1
  10. package/dist/blockFoundation/customAggregateBlock.js +2 -2
  11. package/dist/blockFoundation/customAggregateBlock.js.map +1 -1
  12. package/dist/blockFoundation/customShaderBlock.d.ts.map +1 -1
  13. package/dist/blockFoundation/customShaderBlock.js +4 -6
  14. package/dist/blockFoundation/customShaderBlock.js.map +1 -1
  15. package/dist/blockFoundation/disableableShaderBlock.d.ts.map +1 -1
  16. package/dist/blockFoundation/disableableShaderBlock.js +4 -4
  17. package/dist/blockFoundation/disableableShaderBlock.js.map +1 -1
  18. package/dist/blockFoundation/index.js +0 -2
  19. package/dist/blockFoundation/index.js.map +1 -1
  20. package/dist/blockFoundation/inputBlock.d.ts +8 -8
  21. package/dist/blockFoundation/inputBlock.d.ts.map +1 -1
  22. package/dist/blockFoundation/inputBlock.deserializer.d.ts +1 -1
  23. package/dist/blockFoundation/inputBlock.deserializer.d.ts.map +1 -1
  24. package/dist/blockFoundation/inputBlock.deserializer.js +1 -1
  25. package/dist/blockFoundation/inputBlock.deserializer.js.map +1 -1
  26. package/dist/blockFoundation/inputBlock.js +7 -7
  27. package/dist/blockFoundation/inputBlock.js.map +1 -1
  28. package/dist/blockFoundation/inputBlock.serialization.types.d.ts.map +1 -1
  29. package/dist/blockFoundation/inputBlock.serializer.d.ts +1 -1
  30. package/dist/blockFoundation/inputBlock.serializer.js +26 -28
  31. package/dist/blockFoundation/inputBlock.serializer.js.map +1 -1
  32. package/dist/blockFoundation/outputBlock.d.ts +4 -9
  33. package/dist/blockFoundation/outputBlock.d.ts.map +1 -1
  34. package/dist/blockFoundation/outputBlock.js +13 -14
  35. package/dist/blockFoundation/outputBlock.js.map +1 -1
  36. package/dist/blockFoundation/shaderBlock.d.ts +4 -4
  37. package/dist/blockFoundation/shaderBlock.d.ts.map +1 -1
  38. package/dist/blockFoundation/shaderBlock.js +13 -19
  39. package/dist/blockFoundation/shaderBlock.js.map +1 -1
  40. package/dist/blockFoundation/textureOptions.d.ts +1 -1
  41. package/dist/blockFoundation/textureOptions.js +1 -1
  42. package/dist/command/command.d.ts +1 -1
  43. package/dist/command/command.js +1 -1
  44. package/dist/command/commandBuffer.d.ts +1 -1
  45. package/dist/command/commandBuffer.d.ts.map +1 -1
  46. package/dist/command/commandBuffer.js +2 -2
  47. package/dist/command/commandBufferDebugger.d.ts +2 -2
  48. package/dist/command/commandBufferDebugger.d.ts.map +1 -1
  49. package/dist/command/commandBufferDebugger.js +1 -1
  50. package/dist/command/commandBufferDebugger.js.map +1 -1
  51. package/dist/command/index.d.ts +3 -1
  52. package/dist/command/index.d.ts.map +1 -1
  53. package/dist/command/index.js +5 -1
  54. package/dist/command/index.js.map +1 -1
  55. package/dist/connection/connectionPoint.d.ts +3 -3
  56. package/dist/connection/connectionPoint.d.ts.map +1 -1
  57. package/dist/connection/connectionPoint.js +2 -2
  58. package/dist/connection/connectionPoint.js.map +1 -1
  59. package/dist/connection/connectionPointCompatibilityState.d.ts +1 -1
  60. package/dist/connection/connectionPointCompatibilityState.js +1 -1
  61. package/dist/connection/connectionPointType.d.ts +3 -3
  62. package/dist/connection/connectionPointType.d.ts.map +1 -1
  63. package/dist/connection/connectionPointWithDefault.d.ts +3 -3
  64. package/dist/connection/connectionPointWithDefault.d.ts.map +1 -1
  65. package/dist/connection/connectionPointWithDefault.js.map +1 -1
  66. package/dist/connection/index.d.ts +2 -1
  67. package/dist/connection/index.d.ts.map +1 -1
  68. package/dist/connection/index.js +3 -3
  69. package/dist/connection/index.js.map +1 -1
  70. package/dist/editorUtils/editableInPropertyPage.d.ts +2 -2
  71. package/dist/editorUtils/editableInPropertyPage.d.ts.map +1 -1
  72. package/dist/editorUtils/editableInPropertyPage.js +2 -2
  73. package/dist/editorUtils/editableInPropertyPage.js.map +1 -1
  74. package/dist/editorUtils/index.d.ts +1 -0
  75. package/dist/editorUtils/index.d.ts.map +1 -1
  76. package/dist/editorUtils/index.js +2 -0
  77. package/dist/editorUtils/index.js.map +1 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/index.js +1 -1
  80. package/dist/index.js.map +1 -1
  81. package/dist/optimization/dependencyGraph.js.map +1 -1
  82. package/dist/optimization/optimizedShaderBlock.d.ts +4 -4
  83. package/dist/optimization/optimizedShaderBlock.d.ts.map +1 -1
  84. package/dist/optimization/optimizedShaderBlock.js.map +1 -1
  85. package/dist/optimization/smartFilterOptimizer.d.ts +2 -2
  86. package/dist/optimization/smartFilterOptimizer.d.ts.map +1 -1
  87. package/dist/optimization/smartFilterOptimizer.js +34 -57
  88. package/dist/optimization/smartFilterOptimizer.js.map +1 -1
  89. package/dist/runtime/index.d.ts +2 -1
  90. package/dist/runtime/index.d.ts.map +1 -1
  91. package/dist/runtime/index.js +3 -3
  92. package/dist/runtime/index.js.map +1 -1
  93. package/dist/runtime/renderTargetGenerator.d.ts +1 -1
  94. package/dist/runtime/renderTargetGenerator.d.ts.map +1 -1
  95. package/dist/runtime/renderTargetGenerator.js +5 -7
  96. package/dist/runtime/renderTargetGenerator.js.map +1 -1
  97. package/dist/runtime/shaderRuntime.d.ts +7 -7
  98. package/dist/runtime/shaderRuntime.d.ts.map +1 -1
  99. package/dist/runtime/shaderRuntime.js +7 -8
  100. package/dist/runtime/shaderRuntime.js.map +1 -1
  101. package/dist/runtime/smartFilterRuntime.d.ts +3 -3
  102. package/dist/runtime/smartFilterRuntime.d.ts.map +1 -1
  103. package/dist/runtime/smartFilterRuntime.js.map +1 -1
  104. package/dist/runtime/strongRef.d.ts +1 -1
  105. package/dist/runtime/strongRef.js +1 -1
  106. package/dist/serialization/importCustomBlockDefinition.d.ts +1 -1
  107. package/dist/serialization/importCustomBlockDefinition.js +8 -9
  108. package/dist/serialization/importCustomBlockDefinition.js.map +1 -1
  109. package/dist/serialization/index.d.ts +1 -0
  110. package/dist/serialization/index.d.ts.map +1 -1
  111. package/dist/serialization/index.js +3 -0
  112. package/dist/serialization/index.js.map +1 -1
  113. package/dist/serialization/serializedBlockDefinition.d.ts +2 -2
  114. package/dist/serialization/serializedBlockDefinition.d.ts.map +1 -1
  115. package/dist/serialization/serializedShaderBlockDefinition.d.ts +1 -1
  116. package/dist/serialization/serializedShaderBlockDefinition.d.ts.map +1 -1
  117. package/dist/serialization/serializedSmartFilter.d.ts +1 -1
  118. package/dist/serialization/serializedSmartFilter.d.ts.map +1 -1
  119. package/dist/serialization/smartFilterDeserializer.d.ts +6 -6
  120. package/dist/serialization/smartFilterDeserializer.d.ts.map +1 -1
  121. package/dist/serialization/smartFilterDeserializer.js +9 -12
  122. package/dist/serialization/smartFilterDeserializer.js.map +1 -1
  123. package/dist/serialization/smartFilterSerializer.d.ts +2 -2
  124. package/dist/serialization/smartFilterSerializer.d.ts.map +1 -1
  125. package/dist/serialization/smartFilterSerializer.js +10 -13
  126. package/dist/serialization/smartFilterSerializer.js.map +1 -1
  127. package/dist/serialization/v1/defaultBlockSerializer.d.ts +2 -2
  128. package/dist/serialization/v1/defaultBlockSerializer.d.ts.map +1 -1
  129. package/dist/serialization/v1/defaultBlockSerializer.js +1 -1
  130. package/dist/serialization/v1/index.d.ts +1 -0
  131. package/dist/serialization/v1/index.d.ts.map +1 -1
  132. package/dist/serialization/v1/index.js +2 -0
  133. package/dist/serialization/v1/index.js.map +1 -1
  134. package/dist/serialization/v1/shaderBlockSerialization.types.d.ts +3 -3
  135. package/dist/serialization/v1/shaderBlockSerialization.types.d.ts.map +1 -1
  136. package/dist/serialization/v1/smartFilterSerialization.types.d.ts +2 -2
  137. package/dist/serialization/v1/smartFilterSerialization.types.d.ts.map +1 -1
  138. package/dist/smartFilter.d.ts +8 -8
  139. package/dist/smartFilter.d.ts.map +1 -1
  140. package/dist/smartFilter.js +6 -7
  141. package/dist/smartFilter.js.map +1 -1
  142. package/dist/utils/buildTools/buildShaders.d.ts +3 -2
  143. package/dist/utils/buildTools/buildShaders.d.ts.map +1 -1
  144. package/dist/utils/buildTools/buildShaders.js +7 -6
  145. package/dist/utils/buildTools/buildShaders.js.map +1 -1
  146. package/dist/utils/buildTools/convertGlslIntoBlock.d.ts +4 -2
  147. package/dist/utils/buildTools/convertGlslIntoBlock.d.ts.map +1 -1
  148. package/dist/utils/buildTools/convertGlslIntoBlock.js +136 -141
  149. package/dist/utils/buildTools/convertGlslIntoBlock.js.map +1 -1
  150. package/dist/utils/buildTools/convertGlslIntoShaderProgram.d.ts +4 -4
  151. package/dist/utils/buildTools/convertGlslIntoShaderProgram.d.ts.map +1 -1
  152. package/dist/utils/buildTools/convertGlslIntoShaderProgram.js +55 -48
  153. package/dist/utils/buildTools/convertGlslIntoShaderProgram.js.map +1 -1
  154. package/dist/utils/buildTools/convertShaders.d.ts +8 -5
  155. package/dist/utils/buildTools/convertShaders.d.ts.map +1 -1
  156. package/dist/utils/buildTools/convertShaders.js +38 -14
  157. package/dist/utils/buildTools/convertShaders.js.map +1 -1
  158. package/dist/utils/buildTools/shaderConverter.d.ts +4 -4
  159. package/dist/utils/buildTools/shaderConverter.d.ts.map +1 -1
  160. package/dist/utils/buildTools/shaderConverter.js +17 -21
  161. package/dist/utils/buildTools/shaderConverter.js.map +1 -1
  162. package/dist/utils/buildTools/watchShaders.d.ts +3 -2
  163. package/dist/utils/buildTools/watchShaders.d.ts.map +1 -1
  164. package/dist/utils/buildTools/watchShaders.js +10 -8
  165. package/dist/utils/buildTools/watchShaders.js.map +1 -1
  166. package/dist/utils/index.d.ts +1 -0
  167. package/dist/utils/index.d.ts.map +1 -1
  168. package/dist/utils/index.js +2 -1
  169. package/dist/utils/index.js.map +1 -1
  170. package/dist/utils/renderTargetUtils.d.ts +3 -3
  171. package/dist/utils/renderTargetUtils.d.ts.map +1 -1
  172. package/dist/utils/renderTargetUtils.js +4 -4
  173. package/dist/utils/renderTargetUtils.js.map +1 -1
  174. package/dist/utils/shaderCodeUtils.d.ts +8 -8
  175. package/dist/utils/shaderCodeUtils.d.ts.map +1 -1
  176. package/dist/utils/shaderCodeUtils.js +16 -28
  177. package/dist/utils/shaderCodeUtils.js.map +1 -1
  178. package/dist/utils/textureLoaders.d.ts +2 -2
  179. package/dist/utils/textureLoaders.d.ts.map +1 -1
  180. package/dist/utils/textureLoaders.js +2 -3
  181. package/dist/utils/textureLoaders.js.map +1 -1
  182. package/dist/utils/textureUtils.d.ts +5 -5
  183. package/dist/utils/textureUtils.d.ts.map +1 -1
  184. package/dist/utils/textureUtils.js +1 -1
  185. package/dist/utils/textureUtils.js.map +1 -1
  186. package/dist/version.d.ts +2 -2
  187. package/dist/version.js +2 -2
  188. package/license.md +21 -21
  189. package/package.json +13 -8
  190. package/src/blockFoundation/aggregateBlock.ts +148 -151
  191. package/src/blockFoundation/baseBlock.ts +339 -364
  192. package/src/blockFoundation/customAggregateBlock.ts +88 -104
  193. package/src/blockFoundation/customShaderBlock.ts +312 -326
  194. package/src/blockFoundation/disableableShaderBlock.ts +91 -100
  195. package/src/blockFoundation/index.ts +9 -9
  196. package/src/blockFoundation/inputBlock.deserializer.ts +72 -97
  197. package/src/blockFoundation/inputBlock.serialization.types.ts +126 -132
  198. package/src/blockFoundation/inputBlock.serializer.ts +150 -150
  199. package/src/blockFoundation/inputBlock.ts +181 -192
  200. package/src/blockFoundation/outputBlock.ts +144 -151
  201. package/src/blockFoundation/shaderBlock.ts +156 -163
  202. package/src/blockFoundation/textureOptions.ts +57 -57
  203. package/src/command/command.ts +59 -59
  204. package/src/command/commandBuffer.ts +71 -71
  205. package/src/command/commandBufferDebugger.ts +14 -14
  206. package/src/command/index.ts +7 -3
  207. package/src/connection/connectionPoint.ts +205 -214
  208. package/src/connection/connectionPointCompatibilityState.ts +31 -31
  209. package/src/connection/connectionPointType.ts +45 -45
  210. package/src/connection/connectionPointWithDefault.ts +27 -35
  211. package/src/connection/index.ts +8 -9
  212. package/src/editorUtils/editableInPropertyPage.ts +106 -106
  213. package/src/editorUtils/index.ts +3 -1
  214. package/src/index.ts +16 -15
  215. package/src/optimization/dependencyGraph.ts +96 -96
  216. package/src/optimization/index.ts +1 -1
  217. package/src/optimization/optimizedShaderBlock.ts +131 -134
  218. package/src/optimization/smartFilterOptimizer.ts +757 -825
  219. package/src/runtime/index.ts +8 -6
  220. package/src/runtime/renderTargetGenerator.ts +222 -248
  221. package/src/runtime/shaderRuntime.ts +174 -173
  222. package/src/runtime/smartFilterRuntime.ts +112 -112
  223. package/src/runtime/strongRef.ts +18 -18
  224. package/src/serialization/importCustomBlockDefinition.ts +86 -86
  225. package/src/serialization/index.ts +10 -7
  226. package/src/serialization/serializedBlockDefinition.ts +12 -12
  227. package/src/serialization/serializedShaderBlockDefinition.ts +7 -7
  228. package/src/serialization/serializedSmartFilter.ts +6 -6
  229. package/src/serialization/smartFilterDeserializer.ts +190 -220
  230. package/src/serialization/smartFilterSerializer.ts +110 -121
  231. package/src/serialization/v1/defaultBlockSerializer.ts +21 -21
  232. package/src/serialization/v1/index.ts +4 -2
  233. package/src/serialization/v1/shaderBlockSerialization.types.ts +85 -85
  234. package/src/serialization/v1/smartFilterSerialization.types.ts +129 -133
  235. package/src/smartFilter.ts +255 -260
  236. package/src/utils/buildTools/buildShaders.ts +14 -13
  237. package/src/utils/buildTools/convertGlslIntoBlock.ts +370 -385
  238. package/src/utils/buildTools/convertGlslIntoShaderProgram.ts +173 -172
  239. package/src/utils/buildTools/convertShaders.ts +65 -43
  240. package/src/utils/buildTools/recordVersionNumber.js +17 -0
  241. package/src/utils/buildTools/shaderConverter.ts +466 -478
  242. package/src/utils/buildTools/watchShaders.ts +44 -42
  243. package/src/utils/index.ts +4 -2
  244. package/src/utils/renderTargetUtils.ts +30 -35
  245. package/src/utils/shaderCodeUtils.ts +192 -207
  246. package/src/utils/textureLoaders.ts +31 -43
  247. package/src/utils/textureUtils.ts +28 -32
  248. package/src/version.ts +2 -2
  249. package/dist/utils/buildTools/determineVersion.d.ts +0 -36
  250. package/dist/utils/buildTools/determineVersion.d.ts.map +0 -1
  251. package/dist/utils/buildTools/determineVersion.js +0 -109
  252. package/dist/utils/buildTools/determineVersion.js.map +0 -1
  253. package/dist/utils/buildTools/versionUp.d.ts +0 -2
  254. package/dist/utils/buildTools/versionUp.d.ts.map +0 -1
  255. package/dist/utils/buildTools/versionUp.js +0 -53
  256. package/dist/utils/buildTools/versionUp.js.map +0 -1
  257. package/src/utils/buildTools/determineVersion.ts +0 -128
  258. package/src/utils/buildTools/versionUp.ts +0 -61
@@ -1,825 +1,757 @@
1
- import type { Nullable } from "@babylonjs/core/types";
2
- import { Logger } from "@babylonjs/core/Misc/logger.js";
3
-
4
- import type { ConnectionPoint } from "../connection/connectionPoint";
5
- import type { ShaderBinding } from "../runtime/shaderRuntime";
6
- import type { InputBlock } from "../blockFoundation/inputBlock";
7
- import type { BaseBlock } from "../blockFoundation/baseBlock";
8
- import { SmartFilter } from "../smartFilter.js";
9
- import { ConnectionPointType } from "../connection/connectionPointType.js";
10
- import { ShaderBlock } from "../blockFoundation/shaderBlock.js";
11
- import { isTextureInputBlock } from "../blockFoundation/inputBlock.js";
12
- import { OptimizedShaderBlock } from "./optimizedShaderBlock.js";
13
- import {
14
- AutoDisableMainInputColorName,
15
- decorateChar,
16
- decorateSymbol,
17
- getShaderFragmentCode,
18
- undecorateSymbol,
19
- } from "../utils/shaderCodeUtils.js";
20
- import { DependencyGraph } from "./dependencyGraph.js";
21
- import { DisableableShaderBlock, BlockDisableStrategy } from "../blockFoundation/disableableShaderBlock.js";
22
- import { textureOptionsMatch, type OutputTextureOptions } from "../blockFoundation/textureOptions.js";
23
-
24
- const GetDefineRegEx = /^\S*#define\s+(\w+).*$/; // Matches a #define statement line, capturing its decorated or undecorated name
25
- const showDebugData = false;
26
-
27
- /**
28
- * @internal
29
- */
30
- type RemappedSymbol = {
31
- /**
32
- * The type of the symbol.
33
- */
34
- type: "uniform" | "const" | "sampler" | "function" | "define";
35
-
36
- /**
37
- * The name of the symbol.
38
- */
39
- name: string;
40
-
41
- /**
42
- * The name of the symbol after it has been remapped.
43
- */
44
- remappedName: string;
45
-
46
- /**
47
- * For "function", this is the parameter list to differentiate between overloads.
48
- */
49
- params?: string;
50
-
51
- /**
52
- * The declaration of the symbol. For "function" it is the function code.
53
- */
54
- declaration: string;
55
-
56
- /**
57
- * The ShaderBlock(s) that owns the symbol.
58
- */
59
- owners: ShaderBlock[];
60
-
61
- /**
62
- * The InputBlock that owns the texture. Only used for type="sampler".
63
- */
64
- inputBlock: InputBlock<ConnectionPointType.Texture> | undefined;
65
- };
66
-
67
- /**
68
- * @internal
69
- */
70
- export type StackItem = {
71
- /**
72
- * The connection points to which to connect the output of the optimized block, once the optimized block has been created.
73
- */
74
- inputsToConnectTo: ConnectionPoint[];
75
-
76
- /**
77
- * The connection point to process.
78
- */
79
- outputConnectionPoint: ConnectionPoint;
80
- };
81
-
82
- /**
83
- * Options for the smart filter optimizer.
84
- */
85
- export interface ISmartFilterOptimizerOptions {
86
- /**
87
- * The maximum number of samplers allowed in the fragment shader. Default: 8
88
- */
89
- maxSamplersInFragmentShader?: number;
90
-
91
- /**
92
- * If true, the optimizer will remove the disabled blocks from the optimized smart filter. Default: false
93
- * It allows more aggressive optimizations, but removed blocks will no longer be available in the optimized smart filter.
94
- */
95
- removeDisabledBlocks?: boolean;
96
- }
97
-
98
- /**
99
- * Optimizes a smart filter by aggregating blocks whenever possible, to reduce the number of draw calls.
100
- */
101
- export class SmartFilterOptimizer {
102
- private _sourceSmartFilter: SmartFilter;
103
- private _options: ISmartFilterOptimizerOptions;
104
- private _blockStack: StackItem[] = [];
105
- private _blockToStackItem: Map<BaseBlock, StackItem> = new Map();
106
- private _savedBlockStack: StackItem[] = [];
107
- private _savedBlockToStackItem: Map<BaseBlock, StackItem> = new Map();
108
-
109
- private _symbolOccurrences: { [name: string]: number } = {};
110
- private _remappedSymbols: Array<RemappedSymbol> = [];
111
- private _blockToMainFunctionName: Map<BaseBlock, string> = new Map();
112
- private _mainFunctionNameToCode: Map<string, string> = new Map();
113
- private _dependencyGraph: DependencyGraph<string> = new DependencyGraph<string>();
114
- private _vertexShaderCode: string | undefined;
115
- private _currentOutputTextureOptions: OutputTextureOptions | undefined;
116
- private _forceUnoptimized: boolean = false;
117
-
118
- /**
119
- * Creates a new smart filter optimizer
120
- * @param smartFilter - The smart filter to optimize
121
- * @param options - Options for the optimizer
122
- */
123
- constructor(smartFilter: SmartFilter, options?: ISmartFilterOptimizerOptions) {
124
- this._sourceSmartFilter = smartFilter;
125
- this._options = {
126
- maxSamplersInFragmentShader: 8,
127
- ...options,
128
- };
129
- }
130
-
131
- /**
132
- * Optimizes the smart filter by aggregating blocks whenever possible, to lower the number of rendering passes
133
- * @returns The optimized smart filter, or null if the optimization failed
134
- */
135
- public optimize(): Nullable<SmartFilter> {
136
- this._blockStack = [];
137
- this._blockToStackItem = new Map();
138
-
139
- let newSmartFilter: Nullable<SmartFilter> = null;
140
-
141
- this._sourceSmartFilter._workWithAggregateFreeGraph(() => {
142
- if (
143
- this._sourceSmartFilter.output.connectedTo &&
144
- !isTextureInputBlock(this._sourceSmartFilter.output.connectedTo.ownerBlock)
145
- ) {
146
- const connectionsToReconnect: [ConnectionPoint, ConnectionPoint][] = [];
147
-
148
- if (this._options.removeDisabledBlocks) {
149
- // Need to propagate runtime data to ensure we can tell if a block is disabled
150
- this._sourceSmartFilter.output.ownerBlock.propagateRuntimeData();
151
-
152
- const alreadyVisitedBlocks = new Set<BaseBlock>();
153
- this._disconnectDisabledBlocks(
154
- this._sourceSmartFilter.output.connectedTo.ownerBlock,
155
- alreadyVisitedBlocks,
156
- connectionsToReconnect
157
- );
158
- }
159
-
160
- newSmartFilter = new SmartFilter(this._sourceSmartFilter.name + " - optimized");
161
-
162
- // We must recheck isTextureInputBlock because all shader blocks may have been disconnected by the previous code
163
- if (!isTextureInputBlock(this._sourceSmartFilter.output.connectedTo.ownerBlock)) {
164
- // Make sure all the connections in the graph have a runtimeData associated to them
165
- // Note that the value of the runtimeData may not be set yet, we just need the objects to be created and propagated correctly
166
- this._sourceSmartFilter.output.ownerBlock.prepareForRuntime();
167
- this._sourceSmartFilter.output.ownerBlock.propagateRuntimeData();
168
-
169
- const item: StackItem = {
170
- inputsToConnectTo: [newSmartFilter.output],
171
- outputConnectionPoint: this._sourceSmartFilter.output.connectedTo,
172
- };
173
-
174
- this._blockStack.push(item);
175
- this._blockToStackItem.set(item.outputConnectionPoint.ownerBlock, item);
176
-
177
- while (this._blockStack.length > 0) {
178
- const { inputsToConnectTo, outputConnectionPoint } = this._blockStack.pop()!;
179
-
180
- const newBlock = this._processBlock(newSmartFilter, outputConnectionPoint);
181
-
182
- if (newBlock) {
183
- for (const inputToConnectTo of inputsToConnectTo) {
184
- inputToConnectTo.connectTo(newBlock.output);
185
- }
186
- }
187
- }
188
- } else {
189
- newSmartFilter.output.connectTo(this._sourceSmartFilter.output.connectedTo);
190
- }
191
-
192
- if (this._options.removeDisabledBlocks) {
193
- // We must reconnect the connections that were reconnected differently by the disconnect process, so that the original graph is left unmodified
194
- for (const [input, connectedTo] of connectionsToReconnect) {
195
- input.connectTo(connectedTo);
196
- }
197
- }
198
- }
199
- });
200
-
201
- return newSmartFilter;
202
- }
203
-
204
- private _disconnectDisabledBlocks(
205
- block: BaseBlock,
206
- alreadyVisitedBlocks: Set<BaseBlock>,
207
- inputsToReconnect: [ConnectionPoint, ConnectionPoint][]
208
- ) {
209
- if (alreadyVisitedBlocks.has(block)) {
210
- return;
211
- }
212
-
213
- alreadyVisitedBlocks.add(block);
214
-
215
- for (const input of block.inputs) {
216
- if (!input.connectedTo || input.type !== ConnectionPointType.Texture) {
217
- continue;
218
- }
219
-
220
- this._disconnectDisabledBlocks(input.connectedTo.ownerBlock, alreadyVisitedBlocks, inputsToReconnect);
221
- }
222
-
223
- if (block instanceof DisableableShaderBlock && block.disabled.runtimeData.value) {
224
- block.disconnectFromGraph(inputsToReconnect);
225
- }
226
- }
227
-
228
- private _initialize() {
229
- this._symbolOccurrences = {};
230
- this._remappedSymbols = [];
231
- this._blockToMainFunctionName = new Map();
232
- this._mainFunctionNameToCode = new Map();
233
- this._dependencyGraph = new DependencyGraph();
234
- this._vertexShaderCode = undefined;
235
- this._currentOutputTextureOptions = undefined;
236
- this._forceUnoptimized = false;
237
- }
238
-
239
- private _makeSymbolUnique(symbolName: string): string {
240
- let newVarName = symbolName;
241
- if (!this._symbolOccurrences[symbolName]) {
242
- this._symbolOccurrences[symbolName] = 1;
243
- } else {
244
- this._symbolOccurrences[symbolName]++;
245
- newVarName += "_" + this._symbolOccurrences[symbolName];
246
- }
247
-
248
- return newVarName;
249
- }
250
-
251
- private _processDefines(block: ShaderBlock, code: string): string {
252
- const defines = block.getShaderProgram().fragment.defines;
253
- if (!defines) {
254
- return code;
255
- }
256
-
257
- for (const define of defines) {
258
- const match = define.match(GetDefineRegEx);
259
- const defName = match?.[1];
260
-
261
- if (!match || !defName) {
262
- continue;
263
- }
264
-
265
- // See if we have already processed this define for this block type
266
- const existingRemapped = this._remappedSymbols.find(
267
- (s) =>
268
- s.type === "define" &&
269
- s.name === defName &&
270
- s.owners[0] &&
271
- s.owners[0].blockType === block.blockType
272
- );
273
-
274
- let newDefName: string;
275
- if (existingRemapped) {
276
- newDefName = existingRemapped.remappedName;
277
- } else {
278
- // Add the new define to the remapped symbols list
279
- newDefName = decorateSymbol(this._makeSymbolUnique(undecorateSymbol(defName)));
280
-
281
- this._remappedSymbols.push({
282
- type: "define",
283
- name: defName,
284
- remappedName: newDefName,
285
- declaration: define.replace(defName, newDefName), // No need to reconstruct the declaration
286
- owners: [block],
287
- inputBlock: undefined,
288
- });
289
- }
290
-
291
- // Replace the define name in the main shader code
292
- code = code.replace(defName, newDefName);
293
- }
294
-
295
- return code;
296
- }
297
-
298
- private _processHelperFunctions(block: ShaderBlock, code: string): string {
299
- const functions = block.getShaderProgram().fragment.functions;
300
-
301
- if (functions.length === 1) {
302
- // There's only the main function, so we don't need to do anything
303
- return code;
304
- }
305
-
306
- const replaceFuncNames: Array<[RegExp, string]> = [];
307
-
308
- for (const func of functions) {
309
- let funcName = func.name;
310
-
311
- if (funcName === block.getShaderProgram().fragment.mainFunctionName) {
312
- continue;
313
- }
314
-
315
- funcName = undecorateSymbol(funcName);
316
-
317
- const regexFindCurName = new RegExp(decorateSymbol(funcName), "g");
318
-
319
- const existingFunctionExactOverload = this._remappedSymbols.find(
320
- (s) =>
321
- s.type === "function" &&
322
- s.name === funcName &&
323
- s.params === func.params &&
324
- s.owners[0] &&
325
- s.owners[0].blockType === block.blockType
326
- );
327
-
328
- const existingFunction = this._remappedSymbols.find(
329
- (s) =>
330
- s.type === "function" &&
331
- s.name === funcName &&
332
- s.owners[0] &&
333
- s.owners[0].blockType === block.blockType
334
- );
335
-
336
- // Get or create the remapped name, ignoring the parameter list
337
- const newVarName = existingFunction?.remappedName ?? decorateSymbol(this._makeSymbolUnique(funcName));
338
-
339
- // If the function name, regardless of params, wasn't found, add the rename mapping to our list
340
- if (!existingFunction) {
341
- replaceFuncNames.push([regexFindCurName, newVarName]);
342
- }
343
-
344
- // If this exact overload wasn't found, add it to the list of remapped symbols so it'll be emitted in
345
- // the final shader.
346
- if (!existingFunctionExactOverload) {
347
- let funcCode = func.code;
348
- for (const [regex, replacement] of replaceFuncNames) {
349
- funcCode = funcCode.replace(regex, replacement);
350
- }
351
-
352
- this._remappedSymbols.push({
353
- type: "function",
354
- name: funcName,
355
- remappedName: newVarName,
356
- params: func.params,
357
- declaration: funcCode,
358
- owners: [block],
359
- inputBlock: undefined,
360
- });
361
- }
362
-
363
- code = code.replace(regexFindCurName, newVarName);
364
- }
365
-
366
- return code;
367
- }
368
-
369
- private _processVariables(
370
- block: ShaderBlock,
371
- code: string,
372
- varDecl: "const" | "uniform",
373
- declarations?: string,
374
- hasValue = false,
375
- forceSingleInstance = false
376
- ): [string, Array<string>] {
377
- if (!declarations) {
378
- return [code, []];
379
- }
380
-
381
- let rex = `${varDecl}\\s+(\\S+)\\s+${decorateChar}(\\w+)${decorateChar}\\s*`;
382
- if (hasValue) {
383
- rex += "=\\s*(.+);";
384
- } else {
385
- rex += ";";
386
- }
387
-
388
- const samplerList = [];
389
- const rx = new RegExp(rex, "g");
390
-
391
- let match = rx.exec(declarations);
392
- while (match !== null) {
393
- const singleInstance = forceSingleInstance || varDecl === "const";
394
- const varType = match[1]!;
395
- const varName = match[2]!;
396
- const varValue = hasValue ? match[3]! : null;
397
-
398
- let newVarName: Nullable<string> = null;
399
-
400
- if (varType === "sampler2D") {
401
- samplerList.push(decorateSymbol(varName));
402
- } else {
403
- const existingRemapped = this._remappedSymbols.find(
404
- (s) =>
405
- s.type === varDecl &&
406
- s.name === varName &&
407
- s.owners[0] &&
408
- s.owners[0].blockType === block.blockType
409
- );
410
- if (existingRemapped && singleInstance) {
411
- newVarName = existingRemapped.remappedName;
412
- if (varDecl === "uniform") {
413
- existingRemapped.owners.push(block);
414
- }
415
- } else {
416
- newVarName = decorateSymbol(this._makeSymbolUnique(varName));
417
-
418
- this._remappedSymbols.push({
419
- type: varDecl,
420
- name: varName,
421
- remappedName: newVarName,
422
- declaration: `${varDecl} ${varType} ${newVarName}${hasValue ? " = " + varValue : ""};`,
423
- owners: [block],
424
- inputBlock: undefined,
425
- });
426
- }
427
- }
428
-
429
- if (newVarName) {
430
- code = code.replace(new RegExp(decorateSymbol(varName), "g"), newVarName);
431
- }
432
-
433
- match = rx.exec(declarations);
434
- }
435
-
436
- return [code, samplerList];
437
- }
438
-
439
- private _processSampleTexture(
440
- block: ShaderBlock,
441
- code: string,
442
- sampler: string,
443
- samplers: string[],
444
- inputTextureBlock?: InputBlock<ConnectionPointType.Texture>
445
- ): string {
446
- const rx = new RegExp(`sampleTexture\\s*\\(\\s*${decorateChar}${sampler}${decorateChar}\\s*,\\s*(.*?)\\s*\\)`);
447
-
448
- let newSamplerName = sampler;
449
-
450
- const existingRemapped = this._remappedSymbols.find(
451
- (s) => s.type === "sampler" && s.inputBlock && s.inputBlock === inputTextureBlock
452
- );
453
- if (existingRemapped) {
454
- // The texture is shared by multiple blocks. We must reuse the same sampler name
455
- newSamplerName = existingRemapped.remappedName;
456
- } else {
457
- newSamplerName = decorateSymbol(this._makeSymbolUnique(newSamplerName));
458
-
459
- this._remappedSymbols.push({
460
- type: "sampler",
461
- name: sampler,
462
- remappedName: newSamplerName,
463
- declaration: `uniform sampler2D ${newSamplerName};`,
464
- owners: [block],
465
- inputBlock: inputTextureBlock,
466
- });
467
- }
468
-
469
- if (samplers.indexOf(newSamplerName) === -1) {
470
- samplers.push(newSamplerName);
471
- }
472
-
473
- let match = rx.exec(code);
474
- while (match !== null) {
475
- const uv = match[1]!;
476
-
477
- code =
478
- code.substring(0, match.index) +
479
- `texture2D(${newSamplerName}, ${uv})` +
480
- code.substring(match.index + match[0]!.length);
481
-
482
- match = rx.exec(code);
483
- }
484
-
485
- return code;
486
- }
487
-
488
- private _canBeOptimized(block: BaseBlock): boolean {
489
- if (block.disableOptimization) {
490
- return false;
491
- }
492
-
493
- if (block instanceof ShaderBlock) {
494
- if (block.getShaderProgram().vertex !== this._vertexShaderCode) {
495
- return false;
496
- }
497
-
498
- if (!textureOptionsMatch(block.outputTextureOptions, this._currentOutputTextureOptions)) {
499
- return false;
500
- }
501
- }
502
-
503
- return true;
504
- }
505
-
506
- // Processes a block given one of its output connection point
507
- // Returns the name of the main function in the shader code
508
- private _optimizeBlock(
509
- optimizedBlock: OptimizedShaderBlock,
510
- outputConnectionPoint: ConnectionPoint,
511
- samplers: string[]
512
- ): string {
513
- const block = outputConnectionPoint.ownerBlock;
514
-
515
- if (block instanceof ShaderBlock) {
516
- if (this._currentOutputTextureOptions === undefined) {
517
- this._currentOutputTextureOptions = block.outputTextureOptions;
518
- }
519
-
520
- const shaderProgram = block.getShaderProgram();
521
-
522
- if (!shaderProgram) {
523
- throw new Error(`Shader program not found for block "${block.name}"!`);
524
- }
525
-
526
- // We get the shader code of the main function only
527
- let code = getShaderFragmentCode(shaderProgram, true);
528
-
529
- this._vertexShaderCode = this._vertexShaderCode ?? shaderProgram.vertex;
530
-
531
- // Generates a unique name for the fragment main function (if not already generated)
532
- const shaderFuncName = shaderProgram.fragment.mainFunctionName;
533
-
534
- let newShaderFuncName = this._blockToMainFunctionName.get(block);
535
-
536
- if (!newShaderFuncName) {
537
- newShaderFuncName = undecorateSymbol(shaderFuncName);
538
- newShaderFuncName = decorateSymbol(this._makeSymbolUnique(newShaderFuncName));
539
-
540
- this._blockToMainFunctionName.set(block, newShaderFuncName);
541
- this._dependencyGraph.addElement(newShaderFuncName);
542
- }
543
-
544
- // Replaces the main function name by the new one
545
- code = code.replace(shaderFuncName, newShaderFuncName);
546
-
547
- // Removes the vUV declaration if it exists
548
- code = code.replace(/varying\s+vec2\s+vUV\s*;/g, "");
549
-
550
- // Replaces the texture2D calls by sampleTexture for easier processing
551
- code = code.replace(/texture2D/g, "sampleTexture");
552
-
553
- // Processes the defines to make them unique
554
- code = this._processDefines(block, code);
555
-
556
- // Processes the functions other than the main function
557
- code = this._processHelperFunctions(block, code);
558
-
559
- // Processes the constants to make them unique
560
- code = this._processVariables(block, code, "const", shaderProgram.fragment.const, true)[0];
561
-
562
- // Processes the uniform inputs to make them unique. Also extract the list of samplers
563
- let samplerList: string[] = [];
564
- [code, samplerList] = this._processVariables(block, code, "uniform", shaderProgram.fragment.uniform, false);
565
-
566
- let additionalSamplers = [];
567
- [code, additionalSamplers] = this._processVariables(
568
- block,
569
- code,
570
- "uniform",
571
- shaderProgram.fragment.uniformSingle,
572
- false,
573
- true
574
- );
575
-
576
- samplerList.push(...additionalSamplers);
577
-
578
- // Processes the texture inputs
579
- for (const sampler of samplerList) {
580
- const samplerName = undecorateSymbol(sampler);
581
-
582
- const input = block.findInput(samplerName);
583
- if (!input) {
584
- // 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!)
585
- code = this._processSampleTexture(block, code, samplerName, samplers);
586
- continue;
587
- }
588
-
589
- // input found. Is it connected?
590
- if (!input.connectedTo) {
591
- throw `The connection point corresponding to the input named "${samplerName}" in block named "${block.name}" is not connected!`;
592
- }
593
-
594
- // If we are using the AutoSample strategy, we must preprocess the code that samples the texture
595
- if (
596
- block instanceof DisableableShaderBlock &&
597
- block.blockDisableStrategy === BlockDisableStrategy.AutoSample
598
- ) {
599
- code = this._applyAutoSampleStrategy(code, sampler);
600
- }
601
-
602
- const parentBlock = input.connectedTo.ownerBlock;
603
-
604
- if (isTextureInputBlock(parentBlock)) {
605
- // input is connected to an InputBlock of type "Texture": we must directly sample a texture
606
- code = this._processSampleTexture(block, code, samplerName, samplers, parentBlock);
607
- } else if (this._forceUnoptimized || !this._canBeOptimized(parentBlock)) {
608
- // the block connected to this input cannot be optimized: we must directly sample its output texture
609
- code = this._processSampleTexture(block, code, samplerName, samplers);
610
- let stackItem = this._blockToStackItem.get(parentBlock);
611
- if (!stackItem) {
612
- stackItem = {
613
- inputsToConnectTo: [],
614
- outputConnectionPoint: input.connectedTo,
615
- };
616
- this._blockStack.push(stackItem);
617
- this._blockToStackItem.set(parentBlock, stackItem);
618
- }
619
- // creates a new input connection point for the texture in the optimized block
620
- const connectionPoint = optimizedBlock._registerInput(samplerName, ConnectionPointType.Texture);
621
- stackItem.inputsToConnectTo.push(connectionPoint);
622
- } else {
623
- let parentFuncName: string;
624
-
625
- if (this._blockToMainFunctionName.has(parentBlock)) {
626
- // The parent block has already been processed. We can directly use the main function name
627
- parentFuncName = this._blockToMainFunctionName.get(parentBlock)!;
628
- } else {
629
- // Recursively processes the block connected to this input to get the main function name of the parent block
630
- parentFuncName = this._optimizeBlock(optimizedBlock, input.connectedTo, samplers);
631
- this._dependencyGraph.addDependency(newShaderFuncName, parentFuncName);
632
- }
633
-
634
- // 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
635
- // We remap it to an non existent sampler name, because the code that binds the texture still exists in the ShaderBinding.bind function.
636
- // 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!
637
- this._remappedSymbols.push({
638
- type: "sampler",
639
- name: samplerName,
640
- remappedName: "L(° O °L)",
641
- declaration: ``,
642
- owners: [block],
643
- inputBlock: undefined,
644
- });
645
-
646
- // We have to replace the call(s) to sampleTexture by a call to the main function of the parent block
647
- const rx = new RegExp(`sampleTexture\\s*\\(\\s*${sampler}\\s*,\\s*(.*?)\\s*\\)`);
648
-
649
- let match = rx.exec(code);
650
- while (match !== null) {
651
- const uv = match[1];
652
-
653
- code =
654
- code.substring(0, match.index) +
655
- `${parentFuncName}(${uv})` +
656
- code.substring(match.index + match[0]!.length);
657
- match = rx.exec(code);
658
- }
659
- }
660
- }
661
-
662
- this._mainFunctionNameToCode.set(newShaderFuncName, code);
663
-
664
- return newShaderFuncName;
665
- }
666
-
667
- throw `Unhandled block type! blockType=${block.blockType}`;
668
- }
669
-
670
- private _saveBlockStackState(): void {
671
- this._savedBlockStack = this._blockStack.slice();
672
- this._savedBlockToStackItem = new Map();
673
-
674
- for (const [key, value] of this._blockToStackItem) {
675
- value.inputsToConnectTo = value.inputsToConnectTo.slice();
676
- this._savedBlockToStackItem.set(key, value);
677
- }
678
- }
679
-
680
- private _restoreBlockStackState(): void {
681
- this._blockStack.length = 0;
682
- this._blockStack.push(...this._savedBlockStack);
683
-
684
- this._blockToStackItem.clear();
685
- for (const [key, value] of this._savedBlockToStackItem) {
686
- this._blockToStackItem.set(key, value);
687
- }
688
- }
689
-
690
- private _processBlock(newSmartFilter: SmartFilter, outputConnectionPoint: ConnectionPoint): Nullable<ShaderBlock> {
691
- this._saveBlockStackState();
692
- this._initialize();
693
-
694
- let optimizedBlock = new OptimizedShaderBlock(newSmartFilter, "optimized");
695
-
696
- const samplers: string[] = [];
697
- let mainFuncName = this._optimizeBlock(optimizedBlock, outputConnectionPoint, samplers);
698
-
699
- if (samplers.length > this._options.maxSamplersInFragmentShader!) {
700
- // Too many samplers for the optimized block.
701
- // We must force the unoptimized mode and regenerate the block, which will be unoptimized this time
702
- newSmartFilter.removeBlock(optimizedBlock);
703
-
704
- this._initialize();
705
-
706
- optimizedBlock = new OptimizedShaderBlock(newSmartFilter, "unoptimized");
707
-
708
- this._forceUnoptimized = true;
709
- samplers.length = 0;
710
-
711
- this._restoreBlockStackState();
712
-
713
- mainFuncName = this._optimizeBlock(optimizedBlock, outputConnectionPoint, samplers);
714
- }
715
-
716
- // Collects all the shader code
717
- let code = "";
718
- this._dependencyGraph.walk((element: string) => {
719
- code += this._mainFunctionNameToCode.get(element)! + "\n";
720
- });
721
-
722
- // Sets the remapping of the shader variables
723
- const blockOwnerToShaderBinding = new Map<ShaderBlock, ShaderBinding>();
724
-
725
- const codeDefines = [];
726
- let codeUniforms = "";
727
- let codeConsts = "";
728
- let codeFunctions = "";
729
-
730
- for (const s of this._remappedSymbols) {
731
- switch (s.type) {
732
- case "define":
733
- codeDefines.push(s.declaration);
734
- break;
735
- case "const":
736
- codeConsts += s.declaration + "\n";
737
- break;
738
- case "uniform":
739
- case "sampler":
740
- codeUniforms += s.declaration + "\n";
741
- break;
742
- case "function":
743
- codeFunctions += s.declaration + "\n";
744
- break;
745
- }
746
-
747
- for (const block of s.owners) {
748
- let shaderBinding = blockOwnerToShaderBinding.get(block);
749
- if (!shaderBinding) {
750
- shaderBinding = block.getShaderBinding();
751
- blockOwnerToShaderBinding.set(block, shaderBinding);
752
- }
753
-
754
- switch (s.type) {
755
- case "uniform":
756
- case "sampler":
757
- shaderBinding.addShaderVariableRemapping(decorateSymbol(s.name), s.remappedName);
758
- break;
759
- }
760
- }
761
- }
762
-
763
- // Builds and sets the final shader code
764
- code = codeFunctions + code;
765
- if (showDebugData) {
766
- code = code.replace(/^ {16}/gm, "");
767
- code = code!.replace(/\r/g, "");
768
- code = code!.replace(/\n(\n)*/g, "\n");
769
-
770
- Logger.Log(`=================== BLOCK (forceUnoptimized=${this._forceUnoptimized}) ===================`);
771
- Logger.Log(codeDefines.join("\n"));
772
- Logger.Log(codeUniforms);
773
- Logger.Log(codeConsts);
774
- Logger.Log(code);
775
- Logger.Log(`remappedSymbols=${this._remappedSymbols}`);
776
- Logger.Log(`samplers=${samplers}`);
777
- }
778
-
779
- optimizedBlock.setShaderProgram({
780
- vertex: this._vertexShaderCode,
781
- fragment: {
782
- defines: codeDefines,
783
- const: codeConsts,
784
- uniform: codeUniforms,
785
- mainFunctionName: mainFuncName,
786
- functions: [
787
- {
788
- name: mainFuncName,
789
- params: "",
790
- code,
791
- },
792
- ],
793
- },
794
- });
795
-
796
- if (this._currentOutputTextureOptions !== undefined) {
797
- optimizedBlock.outputTextureOptions = this._currentOutputTextureOptions;
798
- }
799
-
800
- optimizedBlock.setShaderBindings(Array.from(blockOwnerToShaderBinding.values()));
801
-
802
- return optimizedBlock;
803
- }
804
-
805
- /**
806
- * If this block used DisableStrategy.AutoSample, find all the sampleTexture calls which just pass the vUV,
807
- * skip the first one, and for all others replace with the local variable created by the DisableStrategy.AutoSample
808
- *
809
- * @param code - The shader code to process
810
- * @param sampler - The name of the sampler
811
- *
812
- * @returns The processed code
813
- */
814
- private _applyAutoSampleStrategy(code: string, sampler: string): string {
815
- let isFirstMatch = true;
816
- const rx = new RegExp(`sampleTexture\\s*\\(\\s*${sampler}\\s*,\\s*vUV\\s*\\)`, "g");
817
- return code.replace(rx, (match) => {
818
- if (isFirstMatch) {
819
- isFirstMatch = false;
820
- return match;
821
- }
822
- return decorateSymbol(AutoDisableMainInputColorName);
823
- });
824
- }
825
- }
1
+ import type { Nullable } from "core/types.js";
2
+ import { Logger } from "core/Misc/logger.js";
3
+
4
+ import type { ConnectionPoint } from "../connection/connectionPoint.js";
5
+ import type { ShaderBinding } from "../runtime/shaderRuntime.js";
6
+ import type { InputBlock } from "../blockFoundation/inputBlock.js";
7
+ import type { BaseBlock } from "../blockFoundation/baseBlock.js";
8
+ import { SmartFilter } from "../smartFilter.js";
9
+ import { ConnectionPointType } from "../connection/connectionPointType.js";
10
+ import { ShaderBlock } from "../blockFoundation/shaderBlock.js";
11
+ import { IsTextureInputBlock } from "../blockFoundation/inputBlock.js";
12
+ import { OptimizedShaderBlock } from "./optimizedShaderBlock.js";
13
+ import { AutoDisableMainInputColorName, DecorateChar, DecorateSymbol, GetShaderFragmentCode, UndecorateSymbol } from "../utils/shaderCodeUtils.js";
14
+ import { DependencyGraph } from "./dependencyGraph.js";
15
+ import { DisableableShaderBlock, BlockDisableStrategy } from "../blockFoundation/disableableShaderBlock.js";
16
+ import { TextureOptionsMatch, type OutputTextureOptions } from "../blockFoundation/textureOptions.js";
17
+
18
+ const GetDefineRegEx = /^\S*#define\s+(\w+).*$/; // Matches a #define statement line, capturing its decorated or undecorated name
19
+ const ShowDebugData = false;
20
+
21
+ /**
22
+ * @internal
23
+ */
24
+ type RemappedSymbol = {
25
+ /**
26
+ * The type of the symbol.
27
+ */
28
+ type: "uniform" | "const" | "sampler" | "function" | "define";
29
+
30
+ /**
31
+ * The name of the symbol.
32
+ */
33
+ name: string;
34
+
35
+ /**
36
+ * The name of the symbol after it has been remapped.
37
+ */
38
+ remappedName: string;
39
+
40
+ /**
41
+ * For "function", this is the parameter list to differentiate between overloads.
42
+ */
43
+ params?: string;
44
+
45
+ /**
46
+ * The declaration of the symbol. For "function" it is the function code.
47
+ */
48
+ declaration: string;
49
+
50
+ /**
51
+ * The ShaderBlock(s) that owns the symbol.
52
+ */
53
+ owners: ShaderBlock[];
54
+
55
+ /**
56
+ * The InputBlock that owns the texture. Only used for type="sampler".
57
+ */
58
+ inputBlock: InputBlock<ConnectionPointType.Texture> | undefined;
59
+ };
60
+
61
+ /**
62
+ * @internal
63
+ */
64
+ export type StackItem = {
65
+ /**
66
+ * The connection points to which to connect the output of the optimized block, once the optimized block has been created.
67
+ */
68
+ inputsToConnectTo: ConnectionPoint[];
69
+
70
+ /**
71
+ * The connection point to process.
72
+ */
73
+ outputConnectionPoint: ConnectionPoint;
74
+ };
75
+
76
+ /**
77
+ * Options for the smart filter optimizer.
78
+ */
79
+ export interface ISmartFilterOptimizerOptions {
80
+ /**
81
+ * The maximum number of samplers allowed in the fragment shader. Default: 8
82
+ */
83
+ maxSamplersInFragmentShader?: number;
84
+
85
+ /**
86
+ * If true, the optimizer will remove the disabled blocks from the optimized smart filter. Default: false
87
+ * It allows more aggressive optimizations, but removed blocks will no longer be available in the optimized smart filter.
88
+ */
89
+ removeDisabledBlocks?: boolean;
90
+ }
91
+
92
+ /**
93
+ * Optimizes a smart filter by aggregating blocks whenever possible, to reduce the number of draw calls.
94
+ */
95
+ export class SmartFilterOptimizer {
96
+ private _sourceSmartFilter: SmartFilter;
97
+ private _options: ISmartFilterOptimizerOptions;
98
+ private _blockStack: StackItem[] = [];
99
+ private _blockToStackItem: Map<BaseBlock, StackItem> = new Map();
100
+ private _savedBlockStack: StackItem[] = [];
101
+ private _savedBlockToStackItem: Map<BaseBlock, StackItem> = new Map();
102
+
103
+ private _symbolOccurrences: { [name: string]: number } = {};
104
+ private _remappedSymbols: Array<RemappedSymbol> = [];
105
+ private _blockToMainFunctionName: Map<BaseBlock, string> = new Map();
106
+ private _mainFunctionNameToCode: Map<string, string> = new Map();
107
+ private _dependencyGraph: DependencyGraph<string> = new DependencyGraph<string>();
108
+ private _vertexShaderCode: string | undefined;
109
+ private _currentOutputTextureOptions: OutputTextureOptions | undefined;
110
+ private _forceUnoptimized: boolean = false;
111
+
112
+ /**
113
+ * Creates a new smart filter optimizer
114
+ * @param smartFilter - The smart filter to optimize
115
+ * @param options - Options for the optimizer
116
+ */
117
+ constructor(smartFilter: SmartFilter, options?: ISmartFilterOptimizerOptions) {
118
+ this._sourceSmartFilter = smartFilter;
119
+ this._options = {
120
+ maxSamplersInFragmentShader: 8,
121
+ ...options,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Optimizes the smart filter by aggregating blocks whenever possible, to lower the number of rendering passes
127
+ * @returns The optimized smart filter, or null if the optimization failed
128
+ */
129
+ public optimize(): Nullable<SmartFilter> {
130
+ this._blockStack = [];
131
+ this._blockToStackItem = new Map();
132
+
133
+ let newSmartFilter: Nullable<SmartFilter> = null;
134
+
135
+ this._sourceSmartFilter._workWithAggregateFreeGraph(() => {
136
+ if (this._sourceSmartFilter.output.connectedTo && !IsTextureInputBlock(this._sourceSmartFilter.output.connectedTo.ownerBlock)) {
137
+ const connectionsToReconnect: [ConnectionPoint, ConnectionPoint][] = [];
138
+
139
+ if (this._options.removeDisabledBlocks) {
140
+ // Need to propagate runtime data to ensure we can tell if a block is disabled
141
+ this._sourceSmartFilter.output.ownerBlock.propagateRuntimeData();
142
+
143
+ const alreadyVisitedBlocks = new Set<BaseBlock>();
144
+ this._disconnectDisabledBlocks(this._sourceSmartFilter.output.connectedTo.ownerBlock, alreadyVisitedBlocks, connectionsToReconnect);
145
+ }
146
+
147
+ newSmartFilter = new SmartFilter(this._sourceSmartFilter.name + " - optimized");
148
+
149
+ // We must recheck isTextureInputBlock because all shader blocks may have been disconnected by the previous code
150
+ if (!IsTextureInputBlock(this._sourceSmartFilter.output.connectedTo.ownerBlock)) {
151
+ // Make sure all the connections in the graph have a runtimeData associated to them
152
+ // Note that the value of the runtimeData may not be set yet, we just need the objects to be created and propagated correctly
153
+ this._sourceSmartFilter.output.ownerBlock.prepareForRuntime();
154
+ this._sourceSmartFilter.output.ownerBlock.propagateRuntimeData();
155
+
156
+ const item: StackItem = {
157
+ inputsToConnectTo: [newSmartFilter.output],
158
+ outputConnectionPoint: this._sourceSmartFilter.output.connectedTo,
159
+ };
160
+
161
+ this._blockStack.push(item);
162
+ this._blockToStackItem.set(item.outputConnectionPoint.ownerBlock, item);
163
+
164
+ while (this._blockStack.length > 0) {
165
+ const { inputsToConnectTo, outputConnectionPoint } = this._blockStack.pop()!;
166
+
167
+ const newBlock = this._processBlock(newSmartFilter, outputConnectionPoint);
168
+
169
+ if (newBlock) {
170
+ for (const inputToConnectTo of inputsToConnectTo) {
171
+ inputToConnectTo.connectTo(newBlock.output);
172
+ }
173
+ }
174
+ }
175
+ } else {
176
+ newSmartFilter.output.connectTo(this._sourceSmartFilter.output.connectedTo);
177
+ }
178
+
179
+ if (this._options.removeDisabledBlocks) {
180
+ // We must reconnect the connections that were reconnected differently by the disconnect process, so that the original graph is left unmodified
181
+ for (const [input, connectedTo] of connectionsToReconnect) {
182
+ input.connectTo(connectedTo);
183
+ }
184
+ }
185
+ }
186
+ });
187
+
188
+ return newSmartFilter;
189
+ }
190
+
191
+ private _disconnectDisabledBlocks(block: BaseBlock, alreadyVisitedBlocks: Set<BaseBlock>, inputsToReconnect: [ConnectionPoint, ConnectionPoint][]) {
192
+ if (alreadyVisitedBlocks.has(block)) {
193
+ return;
194
+ }
195
+
196
+ alreadyVisitedBlocks.add(block);
197
+
198
+ for (const input of block.inputs) {
199
+ if (!input.connectedTo || input.type !== ConnectionPointType.Texture) {
200
+ continue;
201
+ }
202
+
203
+ this._disconnectDisabledBlocks(input.connectedTo.ownerBlock, alreadyVisitedBlocks, inputsToReconnect);
204
+ }
205
+
206
+ if (block instanceof DisableableShaderBlock && block.disabled.runtimeData.value) {
207
+ block.disconnectFromGraph(inputsToReconnect);
208
+ }
209
+ }
210
+
211
+ private _initialize() {
212
+ this._symbolOccurrences = {};
213
+ this._remappedSymbols = [];
214
+ this._blockToMainFunctionName = new Map();
215
+ this._mainFunctionNameToCode = new Map();
216
+ this._dependencyGraph = new DependencyGraph();
217
+ this._vertexShaderCode = undefined;
218
+ this._currentOutputTextureOptions = undefined;
219
+ this._forceUnoptimized = false;
220
+ }
221
+
222
+ private _makeSymbolUnique(symbolName: string): string {
223
+ let newVarName = symbolName;
224
+ if (!this._symbolOccurrences[symbolName]) {
225
+ this._symbolOccurrences[symbolName] = 1;
226
+ } else {
227
+ this._symbolOccurrences[symbolName]++;
228
+ newVarName += "_" + this._symbolOccurrences[symbolName];
229
+ }
230
+
231
+ return newVarName;
232
+ }
233
+
234
+ private _processDefines(block: ShaderBlock, code: string): string {
235
+ const defines = block.getShaderProgram().fragment.defines;
236
+ if (!defines) {
237
+ return code;
238
+ }
239
+
240
+ for (const define of defines) {
241
+ const match = define.match(GetDefineRegEx);
242
+ const defName = match?.[1];
243
+
244
+ if (!match || !defName) {
245
+ continue;
246
+ }
247
+
248
+ // See if we have already processed this define for this block type
249
+ const existingRemapped = this._remappedSymbols.find((s) => s.type === "define" && s.name === defName && s.owners[0] && s.owners[0].blockType === block.blockType);
250
+
251
+ let newDefName: string;
252
+ if (existingRemapped) {
253
+ newDefName = existingRemapped.remappedName;
254
+ } else {
255
+ // Add the new define to the remapped symbols list
256
+ newDefName = DecorateSymbol(this._makeSymbolUnique(UndecorateSymbol(defName)));
257
+
258
+ this._remappedSymbols.push({
259
+ type: "define",
260
+ name: defName,
261
+ remappedName: newDefName,
262
+ declaration: define.replace(defName, newDefName), // No need to reconstruct the declaration
263
+ owners: [block],
264
+ inputBlock: undefined,
265
+ });
266
+ }
267
+
268
+ // Replace the define name in the main shader code
269
+ code = code.replace(defName, newDefName);
270
+ }
271
+
272
+ return code;
273
+ }
274
+
275
+ private _processHelperFunctions(block: ShaderBlock, code: string): string {
276
+ const functions = block.getShaderProgram().fragment.functions;
277
+
278
+ if (functions.length === 1) {
279
+ // There's only the main function, so we don't need to do anything
280
+ return code;
281
+ }
282
+
283
+ const replaceFuncNames: Array<[RegExp, string]> = [];
284
+
285
+ for (const func of functions) {
286
+ let funcName = func.name;
287
+
288
+ if (funcName === block.getShaderProgram().fragment.mainFunctionName) {
289
+ continue;
290
+ }
291
+
292
+ funcName = UndecorateSymbol(funcName);
293
+
294
+ const regexFindCurName = new RegExp(DecorateSymbol(funcName), "g");
295
+
296
+ const existingFunctionExactOverload = this._remappedSymbols.find(
297
+ (s) => s.type === "function" && s.name === funcName && s.params === func.params && s.owners[0] && s.owners[0].blockType === block.blockType
298
+ );
299
+
300
+ const existingFunction = this._remappedSymbols.find((s) => s.type === "function" && s.name === funcName && s.owners[0] && s.owners[0].blockType === block.blockType);
301
+
302
+ // Get or create the remapped name, ignoring the parameter list
303
+ const newVarName = existingFunction?.remappedName ?? DecorateSymbol(this._makeSymbolUnique(funcName));
304
+
305
+ // If the function name, regardless of params, wasn't found, add the rename mapping to our list
306
+ if (!existingFunction) {
307
+ replaceFuncNames.push([regexFindCurName, newVarName]);
308
+ }
309
+
310
+ // If this exact overload wasn't found, add it to the list of remapped symbols so it'll be emitted in
311
+ // the final shader.
312
+ if (!existingFunctionExactOverload) {
313
+ let funcCode = func.code;
314
+ for (const [regex, replacement] of replaceFuncNames) {
315
+ funcCode = funcCode.replace(regex, replacement);
316
+ }
317
+
318
+ this._remappedSymbols.push({
319
+ type: "function",
320
+ name: funcName,
321
+ remappedName: newVarName,
322
+ params: func.params,
323
+ declaration: funcCode,
324
+ owners: [block],
325
+ inputBlock: undefined,
326
+ });
327
+ }
328
+
329
+ code = code.replace(regexFindCurName, newVarName);
330
+ }
331
+
332
+ return code;
333
+ }
334
+
335
+ private _processVariables(
336
+ block: ShaderBlock,
337
+ code: string,
338
+ varDecl: "const" | "uniform",
339
+ declarations?: string,
340
+ hasValue = false,
341
+ forceSingleInstance = false
342
+ ): [string, Array<string>] {
343
+ if (!declarations) {
344
+ return [code, []];
345
+ }
346
+
347
+ let rex = `${varDecl}\\s+(\\S+)\\s+${DecorateChar}(\\w+)${DecorateChar}\\s*`;
348
+ if (hasValue) {
349
+ rex += "=\\s*(.+);";
350
+ } else {
351
+ rex += ";";
352
+ }
353
+
354
+ const samplerList = [];
355
+ const rx = new RegExp(rex, "g");
356
+
357
+ let match = rx.exec(declarations);
358
+ while (match !== null) {
359
+ const singleInstance = forceSingleInstance || varDecl === "const";
360
+ const varType = match[1]!;
361
+ const varName = match[2]!;
362
+ const varValue = hasValue ? match[3]! : null;
363
+
364
+ let newVarName: Nullable<string> = null;
365
+
366
+ if (varType === "sampler2D") {
367
+ samplerList.push(DecorateSymbol(varName));
368
+ } else {
369
+ const existingRemapped = this._remappedSymbols.find((s) => s.type === varDecl && s.name === varName && s.owners[0] && s.owners[0].blockType === block.blockType);
370
+ if (existingRemapped && singleInstance) {
371
+ newVarName = existingRemapped.remappedName;
372
+ if (varDecl === "uniform") {
373
+ existingRemapped.owners.push(block);
374
+ }
375
+ } else {
376
+ newVarName = DecorateSymbol(this._makeSymbolUnique(varName));
377
+
378
+ this._remappedSymbols.push({
379
+ type: varDecl,
380
+ name: varName,
381
+ remappedName: newVarName,
382
+ declaration: `${varDecl} ${varType} ${newVarName}${hasValue ? " = " + varValue : ""};`,
383
+ owners: [block],
384
+ inputBlock: undefined,
385
+ });
386
+ }
387
+ }
388
+
389
+ if (newVarName) {
390
+ code = code.replace(new RegExp(DecorateSymbol(varName), "g"), newVarName);
391
+ }
392
+
393
+ match = rx.exec(declarations);
394
+ }
395
+
396
+ return [code, samplerList];
397
+ }
398
+
399
+ private _processSampleTexture(block: ShaderBlock, code: string, sampler: string, samplers: string[], inputTextureBlock?: InputBlock<ConnectionPointType.Texture>): string {
400
+ const rx = new RegExp(`sampleTexture\\s*\\(\\s*${DecorateChar}${sampler}${DecorateChar}\\s*,\\s*(.*?)\\s*\\)`);
401
+
402
+ let newSamplerName = sampler;
403
+
404
+ const existingRemapped = this._remappedSymbols.find((s) => s.type === "sampler" && s.inputBlock && s.inputBlock === inputTextureBlock);
405
+ if (existingRemapped) {
406
+ // The texture is shared by multiple blocks. We must reuse the same sampler name
407
+ newSamplerName = existingRemapped.remappedName;
408
+ } else {
409
+ newSamplerName = DecorateSymbol(this._makeSymbolUnique(newSamplerName));
410
+
411
+ this._remappedSymbols.push({
412
+ type: "sampler",
413
+ name: sampler,
414
+ remappedName: newSamplerName,
415
+ declaration: `uniform sampler2D ${newSamplerName};`,
416
+ owners: [block],
417
+ inputBlock: inputTextureBlock,
418
+ });
419
+ }
420
+
421
+ if (samplers.indexOf(newSamplerName) === -1) {
422
+ samplers.push(newSamplerName);
423
+ }
424
+
425
+ let match = rx.exec(code);
426
+ while (match !== null) {
427
+ const uv = match[1]!;
428
+
429
+ code = code.substring(0, match.index) + `texture2D(${newSamplerName}, ${uv})` + code.substring(match.index + match[0]!.length);
430
+
431
+ match = rx.exec(code);
432
+ }
433
+
434
+ return code;
435
+ }
436
+
437
+ private _canBeOptimized(block: BaseBlock): boolean {
438
+ if (block.disableOptimization) {
439
+ return false;
440
+ }
441
+
442
+ if (block instanceof ShaderBlock) {
443
+ if (block.getShaderProgram().vertex !== this._vertexShaderCode) {
444
+ return false;
445
+ }
446
+
447
+ if (!TextureOptionsMatch(block.outputTextureOptions, this._currentOutputTextureOptions)) {
448
+ return false;
449
+ }
450
+ }
451
+
452
+ return true;
453
+ }
454
+
455
+ // Processes a block given one of its output connection point
456
+ // Returns the name of the main function in the shader code
457
+ private _optimizeBlock(optimizedBlock: OptimizedShaderBlock, outputConnectionPoint: ConnectionPoint, samplers: string[]): string {
458
+ const block = outputConnectionPoint.ownerBlock;
459
+
460
+ if (block instanceof ShaderBlock) {
461
+ if (this._currentOutputTextureOptions === undefined) {
462
+ this._currentOutputTextureOptions = block.outputTextureOptions;
463
+ }
464
+
465
+ const shaderProgram = block.getShaderProgram();
466
+
467
+ if (!shaderProgram) {
468
+ throw new Error(`Shader program not found for block "${block.name}"!`);
469
+ }
470
+
471
+ // We get the shader code of the main function only
472
+ let code = GetShaderFragmentCode(shaderProgram, true);
473
+
474
+ this._vertexShaderCode = this._vertexShaderCode ?? shaderProgram.vertex;
475
+
476
+ // Generates a unique name for the fragment main function (if not already generated)
477
+ const shaderFuncName = shaderProgram.fragment.mainFunctionName;
478
+
479
+ let newShaderFuncName = this._blockToMainFunctionName.get(block);
480
+
481
+ if (!newShaderFuncName) {
482
+ newShaderFuncName = UndecorateSymbol(shaderFuncName);
483
+ newShaderFuncName = DecorateSymbol(this._makeSymbolUnique(newShaderFuncName));
484
+
485
+ this._blockToMainFunctionName.set(block, newShaderFuncName);
486
+ this._dependencyGraph.addElement(newShaderFuncName);
487
+ }
488
+
489
+ // Replaces the main function name by the new one
490
+ code = code.replace(shaderFuncName, newShaderFuncName);
491
+
492
+ // Removes the vUV declaration if it exists
493
+ code = code.replace(/varying\s+vec2\s+vUV\s*;/g, "");
494
+
495
+ // Replaces the texture2D calls by sampleTexture for easier processing
496
+ code = code.replace(/texture2D/g, "sampleTexture");
497
+
498
+ // Processes the defines to make them unique
499
+ code = this._processDefines(block, code);
500
+
501
+ // Processes the functions other than the main function
502
+ code = this._processHelperFunctions(block, code);
503
+
504
+ // Processes the constants to make them unique
505
+ code = this._processVariables(block, code, "const", shaderProgram.fragment.const, true)[0];
506
+
507
+ // Processes the uniform inputs to make them unique. Also extract the list of samplers
508
+ let samplerList: string[] = [];
509
+ [code, samplerList] = this._processVariables(block, code, "uniform", shaderProgram.fragment.uniform, false);
510
+
511
+ let additionalSamplers = [];
512
+ [code, additionalSamplers] = this._processVariables(block, code, "uniform", shaderProgram.fragment.uniformSingle, false, true);
513
+
514
+ samplerList.push(...additionalSamplers);
515
+
516
+ // Processes the texture inputs
517
+ for (const sampler of samplerList) {
518
+ const samplerName = UndecorateSymbol(sampler);
519
+
520
+ const input = block.findInput(samplerName);
521
+ if (!input) {
522
+ // 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!)
523
+ code = this._processSampleTexture(block, code, samplerName, samplers);
524
+ continue;
525
+ }
526
+
527
+ // input found. Is it connected?
528
+ if (!input.connectedTo) {
529
+ throw `The connection point corresponding to the input named "${samplerName}" in block named "${block.name}" is not connected!`;
530
+ }
531
+
532
+ // If we are using the AutoSample strategy, we must preprocess the code that samples the texture
533
+ if (block instanceof DisableableShaderBlock && block.blockDisableStrategy === BlockDisableStrategy.AutoSample) {
534
+ code = this._applyAutoSampleStrategy(code, sampler);
535
+ }
536
+
537
+ const parentBlock = input.connectedTo.ownerBlock;
538
+
539
+ if (IsTextureInputBlock(parentBlock)) {
540
+ // input is connected to an InputBlock of type "Texture": we must directly sample a texture
541
+ code = this._processSampleTexture(block, code, samplerName, samplers, parentBlock);
542
+ } else if (this._forceUnoptimized || !this._canBeOptimized(parentBlock)) {
543
+ // the block connected to this input cannot be optimized: we must directly sample its output texture
544
+ code = this._processSampleTexture(block, code, samplerName, samplers);
545
+ let stackItem = this._blockToStackItem.get(parentBlock);
546
+ if (!stackItem) {
547
+ stackItem = {
548
+ inputsToConnectTo: [],
549
+ outputConnectionPoint: input.connectedTo,
550
+ };
551
+ this._blockStack.push(stackItem);
552
+ this._blockToStackItem.set(parentBlock, stackItem);
553
+ }
554
+ // creates a new input connection point for the texture in the optimized block
555
+ const connectionPoint = optimizedBlock._registerInput(samplerName, ConnectionPointType.Texture);
556
+ stackItem.inputsToConnectTo.push(connectionPoint);
557
+ } else {
558
+ let parentFuncName: string;
559
+
560
+ if (this._blockToMainFunctionName.has(parentBlock)) {
561
+ // The parent block has already been processed. We can directly use the main function name
562
+ parentFuncName = this._blockToMainFunctionName.get(parentBlock)!;
563
+ } else {
564
+ // Recursively processes the block connected to this input to get the main function name of the parent block
565
+ parentFuncName = this._optimizeBlock(optimizedBlock, input.connectedTo, samplers);
566
+ this._dependencyGraph.addDependency(newShaderFuncName, parentFuncName);
567
+ }
568
+
569
+ // 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
570
+ // We remap it to an non existent sampler name, because the code that binds the texture still exists in the ShaderBinding.bind function.
571
+ // 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!
572
+ this._remappedSymbols.push({
573
+ type: "sampler",
574
+ name: samplerName,
575
+ remappedName: "L(° O °L)",
576
+ declaration: ``,
577
+ owners: [block],
578
+ inputBlock: undefined,
579
+ });
580
+
581
+ // We have to replace the call(s) to sampleTexture by a call to the main function of the parent block
582
+ const rx = new RegExp(`sampleTexture\\s*\\(\\s*${sampler}\\s*,\\s*(.*?)\\s*\\)`);
583
+
584
+ let match = rx.exec(code);
585
+ while (match !== null) {
586
+ const uv = match[1];
587
+
588
+ code = code.substring(0, match.index) + `${parentFuncName}(${uv})` + code.substring(match.index + match[0]!.length);
589
+ match = rx.exec(code);
590
+ }
591
+ }
592
+ }
593
+
594
+ this._mainFunctionNameToCode.set(newShaderFuncName, code);
595
+
596
+ return newShaderFuncName;
597
+ }
598
+
599
+ throw `Unhandled block type! blockType=${block.blockType}`;
600
+ }
601
+
602
+ private _saveBlockStackState(): void {
603
+ this._savedBlockStack = this._blockStack.slice();
604
+ this._savedBlockToStackItem = new Map();
605
+
606
+ for (const [key, value] of this._blockToStackItem) {
607
+ value.inputsToConnectTo = value.inputsToConnectTo.slice();
608
+ this._savedBlockToStackItem.set(key, value);
609
+ }
610
+ }
611
+
612
+ private _restoreBlockStackState(): void {
613
+ this._blockStack.length = 0;
614
+ this._blockStack.push(...this._savedBlockStack);
615
+
616
+ this._blockToStackItem.clear();
617
+ for (const [key, value] of this._savedBlockToStackItem) {
618
+ this._blockToStackItem.set(key, value);
619
+ }
620
+ }
621
+
622
+ private _processBlock(newSmartFilter: SmartFilter, outputConnectionPoint: ConnectionPoint): Nullable<ShaderBlock> {
623
+ this._saveBlockStackState();
624
+ this._initialize();
625
+
626
+ let optimizedBlock = new OptimizedShaderBlock(newSmartFilter, "optimized");
627
+
628
+ const samplers: string[] = [];
629
+ let mainFuncName = this._optimizeBlock(optimizedBlock, outputConnectionPoint, samplers);
630
+
631
+ if (samplers.length > this._options.maxSamplersInFragmentShader!) {
632
+ // Too many samplers for the optimized block.
633
+ // We must force the unoptimized mode and regenerate the block, which will be unoptimized this time
634
+ newSmartFilter.removeBlock(optimizedBlock);
635
+
636
+ this._initialize();
637
+
638
+ optimizedBlock = new OptimizedShaderBlock(newSmartFilter, "unoptimized");
639
+
640
+ this._forceUnoptimized = true;
641
+ samplers.length = 0;
642
+
643
+ this._restoreBlockStackState();
644
+
645
+ mainFuncName = this._optimizeBlock(optimizedBlock, outputConnectionPoint, samplers);
646
+ }
647
+
648
+ // Collects all the shader code
649
+ let code = "";
650
+ this._dependencyGraph.walk((element: string) => {
651
+ code += this._mainFunctionNameToCode.get(element)! + "\n";
652
+ });
653
+
654
+ // Sets the remapping of the shader variables
655
+ const blockOwnerToShaderBinding = new Map<ShaderBlock, ShaderBinding>();
656
+
657
+ const codeDefines = [];
658
+ let codeUniforms = "";
659
+ let codeConsts = "";
660
+ let codeFunctions = "";
661
+
662
+ for (const s of this._remappedSymbols) {
663
+ switch (s.type) {
664
+ case "define":
665
+ codeDefines.push(s.declaration);
666
+ break;
667
+ case "const":
668
+ codeConsts += s.declaration + "\n";
669
+ break;
670
+ case "uniform":
671
+ case "sampler":
672
+ codeUniforms += s.declaration + "\n";
673
+ break;
674
+ case "function":
675
+ codeFunctions += s.declaration + "\n";
676
+ break;
677
+ }
678
+
679
+ for (const block of s.owners) {
680
+ let shaderBinding = blockOwnerToShaderBinding.get(block);
681
+ if (!shaderBinding) {
682
+ shaderBinding = block.getShaderBinding();
683
+ blockOwnerToShaderBinding.set(block, shaderBinding);
684
+ }
685
+
686
+ switch (s.type) {
687
+ case "uniform":
688
+ case "sampler":
689
+ shaderBinding.addShaderVariableRemapping(DecorateSymbol(s.name), s.remappedName);
690
+ break;
691
+ }
692
+ }
693
+ }
694
+
695
+ // Builds and sets the final shader code
696
+ code = codeFunctions + code;
697
+ if (ShowDebugData) {
698
+ code = code.replace(/^ {16}/gm, "");
699
+ code = code!.replace(/\r/g, "");
700
+ code = code!.replace(/\n(\n)*/g, "\n");
701
+
702
+ Logger.Log(`=================== BLOCK (forceUnoptimized=${this._forceUnoptimized}) ===================`);
703
+ Logger.Log(codeDefines.join("\n"));
704
+ Logger.Log(codeUniforms);
705
+ Logger.Log(codeConsts);
706
+ Logger.Log(code);
707
+ Logger.Log(`remappedSymbols=${this._remappedSymbols}`);
708
+ Logger.Log(`samplers=${samplers}`);
709
+ }
710
+
711
+ optimizedBlock.setShaderProgram({
712
+ vertex: this._vertexShaderCode,
713
+ fragment: {
714
+ defines: codeDefines,
715
+ const: codeConsts,
716
+ uniform: codeUniforms,
717
+ mainFunctionName: mainFuncName,
718
+ functions: [
719
+ {
720
+ name: mainFuncName,
721
+ params: "",
722
+ code,
723
+ },
724
+ ],
725
+ },
726
+ });
727
+
728
+ if (this._currentOutputTextureOptions !== undefined) {
729
+ optimizedBlock.outputTextureOptions = this._currentOutputTextureOptions;
730
+ }
731
+
732
+ optimizedBlock.setShaderBindings(Array.from(blockOwnerToShaderBinding.values()));
733
+
734
+ return optimizedBlock;
735
+ }
736
+
737
+ /**
738
+ * If this block used DisableStrategy.AutoSample, find all the sampleTexture calls which just pass the vUV,
739
+ * skip the first one, and for all others replace with the local variable created by the DisableStrategy.AutoSample
740
+ *
741
+ * @param code - The shader code to process
742
+ * @param sampler - The name of the sampler
743
+ *
744
+ * @returns The processed code
745
+ */
746
+ private _applyAutoSampleStrategy(code: string, sampler: string): string {
747
+ let isFirstMatch = true;
748
+ const rx = new RegExp(`sampleTexture\\s*\\(\\s*${sampler}\\s*,\\s*vUV\\s*\\)`, "g");
749
+ return code.replace(rx, (match) => {
750
+ if (isFirstMatch) {
751
+ isFirstMatch = false;
752
+ return match;
753
+ }
754
+ return DecorateSymbol(AutoDisableMainInputColorName);
755
+ });
756
+ }
757
+ }