@babylonjs/smart-filters 1.0.13 → 8.19.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 (259) 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 +9 -0
  13. package/dist/blockFoundation/customShaderBlock.d.ts.map +1 -1
  14. package/dist/blockFoundation/customShaderBlock.js +47 -7
  15. package/dist/blockFoundation/customShaderBlock.js.map +1 -1
  16. package/dist/blockFoundation/disableableShaderBlock.d.ts.map +1 -1
  17. package/dist/blockFoundation/disableableShaderBlock.js +4 -4
  18. package/dist/blockFoundation/disableableShaderBlock.js.map +1 -1
  19. package/dist/blockFoundation/index.js +0 -2
  20. package/dist/blockFoundation/index.js.map +1 -1
  21. package/dist/blockFoundation/inputBlock.d.ts +8 -8
  22. package/dist/blockFoundation/inputBlock.d.ts.map +1 -1
  23. package/dist/blockFoundation/inputBlock.deserializer.d.ts +1 -1
  24. package/dist/blockFoundation/inputBlock.deserializer.d.ts.map +1 -1
  25. package/dist/blockFoundation/inputBlock.deserializer.js +1 -1
  26. package/dist/blockFoundation/inputBlock.deserializer.js.map +1 -1
  27. package/dist/blockFoundation/inputBlock.js +7 -7
  28. package/dist/blockFoundation/inputBlock.js.map +1 -1
  29. package/dist/blockFoundation/inputBlock.serialization.types.d.ts.map +1 -1
  30. package/dist/blockFoundation/inputBlock.serializer.d.ts +1 -1
  31. package/dist/blockFoundation/inputBlock.serializer.js +26 -28
  32. package/dist/blockFoundation/inputBlock.serializer.js.map +1 -1
  33. package/dist/blockFoundation/outputBlock.d.ts +4 -9
  34. package/dist/blockFoundation/outputBlock.d.ts.map +1 -1
  35. package/dist/blockFoundation/outputBlock.js +13 -14
  36. package/dist/blockFoundation/outputBlock.js.map +1 -1
  37. package/dist/blockFoundation/shaderBlock.d.ts +4 -4
  38. package/dist/blockFoundation/shaderBlock.d.ts.map +1 -1
  39. package/dist/blockFoundation/shaderBlock.js +13 -19
  40. package/dist/blockFoundation/shaderBlock.js.map +1 -1
  41. package/dist/blockFoundation/textureOptions.d.ts +1 -1
  42. package/dist/blockFoundation/textureOptions.js +1 -1
  43. package/dist/command/command.d.ts +1 -1
  44. package/dist/command/command.js +1 -1
  45. package/dist/command/commandBuffer.d.ts +1 -1
  46. package/dist/command/commandBuffer.d.ts.map +1 -1
  47. package/dist/command/commandBuffer.js +2 -2
  48. package/dist/command/commandBufferDebugger.d.ts +2 -2
  49. package/dist/command/commandBufferDebugger.d.ts.map +1 -1
  50. package/dist/command/commandBufferDebugger.js +1 -1
  51. package/dist/command/commandBufferDebugger.js.map +1 -1
  52. package/dist/command/index.d.ts +3 -1
  53. package/dist/command/index.d.ts.map +1 -1
  54. package/dist/command/index.js +5 -1
  55. package/dist/command/index.js.map +1 -1
  56. package/dist/connection/connectionPoint.d.ts +3 -3
  57. package/dist/connection/connectionPoint.d.ts.map +1 -1
  58. package/dist/connection/connectionPoint.js +2 -2
  59. package/dist/connection/connectionPoint.js.map +1 -1
  60. package/dist/connection/connectionPointCompatibilityState.d.ts +1 -1
  61. package/dist/connection/connectionPointCompatibilityState.js +1 -1
  62. package/dist/connection/connectionPointType.d.ts +3 -3
  63. package/dist/connection/connectionPointType.d.ts.map +1 -1
  64. package/dist/connection/connectionPointWithDefault.d.ts +3 -3
  65. package/dist/connection/connectionPointWithDefault.d.ts.map +1 -1
  66. package/dist/connection/connectionPointWithDefault.js.map +1 -1
  67. package/dist/connection/index.d.ts +2 -1
  68. package/dist/connection/index.d.ts.map +1 -1
  69. package/dist/connection/index.js +3 -3
  70. package/dist/connection/index.js.map +1 -1
  71. package/dist/editorUtils/editableInPropertyPage.d.ts +2 -2
  72. package/dist/editorUtils/editableInPropertyPage.d.ts.map +1 -1
  73. package/dist/editorUtils/editableInPropertyPage.js +2 -2
  74. package/dist/editorUtils/editableInPropertyPage.js.map +1 -1
  75. package/dist/editorUtils/index.d.ts +1 -0
  76. package/dist/editorUtils/index.d.ts.map +1 -1
  77. package/dist/editorUtils/index.js +2 -0
  78. package/dist/editorUtils/index.js.map +1 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +1 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/optimization/dependencyGraph.js.map +1 -1
  83. package/dist/optimization/optimizedShaderBlock.d.ts +4 -4
  84. package/dist/optimization/optimizedShaderBlock.d.ts.map +1 -1
  85. package/dist/optimization/optimizedShaderBlock.js.map +1 -1
  86. package/dist/optimization/smartFilterOptimizer.d.ts +2 -2
  87. package/dist/optimization/smartFilterOptimizer.d.ts.map +1 -1
  88. package/dist/optimization/smartFilterOptimizer.js +34 -57
  89. package/dist/optimization/smartFilterOptimizer.js.map +1 -1
  90. package/dist/runtime/index.d.ts +2 -1
  91. package/dist/runtime/index.d.ts.map +1 -1
  92. package/dist/runtime/index.js +3 -3
  93. package/dist/runtime/index.js.map +1 -1
  94. package/dist/runtime/renderTargetGenerator.d.ts +1 -1
  95. package/dist/runtime/renderTargetGenerator.d.ts.map +1 -1
  96. package/dist/runtime/renderTargetGenerator.js +5 -7
  97. package/dist/runtime/renderTargetGenerator.js.map +1 -1
  98. package/dist/runtime/shaderRuntime.d.ts +7 -7
  99. package/dist/runtime/shaderRuntime.d.ts.map +1 -1
  100. package/dist/runtime/shaderRuntime.js +7 -8
  101. package/dist/runtime/shaderRuntime.js.map +1 -1
  102. package/dist/runtime/smartFilterRuntime.d.ts +3 -3
  103. package/dist/runtime/smartFilterRuntime.d.ts.map +1 -1
  104. package/dist/runtime/smartFilterRuntime.js.map +1 -1
  105. package/dist/runtime/strongRef.d.ts +1 -1
  106. package/dist/runtime/strongRef.js +1 -1
  107. package/dist/serialization/importCustomBlockDefinition.d.ts +1 -1
  108. package/dist/serialization/importCustomBlockDefinition.js +8 -9
  109. package/dist/serialization/importCustomBlockDefinition.js.map +1 -1
  110. package/dist/serialization/index.d.ts +1 -0
  111. package/dist/serialization/index.d.ts.map +1 -1
  112. package/dist/serialization/index.js +3 -0
  113. package/dist/serialization/index.js.map +1 -1
  114. package/dist/serialization/serializedBlockDefinition.d.ts +2 -2
  115. package/dist/serialization/serializedBlockDefinition.d.ts.map +1 -1
  116. package/dist/serialization/serializedShaderBlockDefinition.d.ts +1 -1
  117. package/dist/serialization/serializedShaderBlockDefinition.d.ts.map +1 -1
  118. package/dist/serialization/serializedSmartFilter.d.ts +1 -1
  119. package/dist/serialization/serializedSmartFilter.d.ts.map +1 -1
  120. package/dist/serialization/smartFilterDeserializer.d.ts +6 -6
  121. package/dist/serialization/smartFilterDeserializer.d.ts.map +1 -1
  122. package/dist/serialization/smartFilterDeserializer.js +9 -12
  123. package/dist/serialization/smartFilterDeserializer.js.map +1 -1
  124. package/dist/serialization/smartFilterSerializer.d.ts +2 -2
  125. package/dist/serialization/smartFilterSerializer.d.ts.map +1 -1
  126. package/dist/serialization/smartFilterSerializer.js +10 -13
  127. package/dist/serialization/smartFilterSerializer.js.map +1 -1
  128. package/dist/serialization/v1/defaultBlockSerializer.d.ts +2 -2
  129. package/dist/serialization/v1/defaultBlockSerializer.d.ts.map +1 -1
  130. package/dist/serialization/v1/defaultBlockSerializer.js +1 -1
  131. package/dist/serialization/v1/index.d.ts +1 -0
  132. package/dist/serialization/v1/index.d.ts.map +1 -1
  133. package/dist/serialization/v1/index.js +2 -0
  134. package/dist/serialization/v1/index.js.map +1 -1
  135. package/dist/serialization/v1/shaderBlockSerialization.types.d.ts +3 -3
  136. package/dist/serialization/v1/shaderBlockSerialization.types.d.ts.map +1 -1
  137. package/dist/serialization/v1/smartFilterSerialization.types.d.ts +2 -2
  138. package/dist/serialization/v1/smartFilterSerialization.types.d.ts.map +1 -1
  139. package/dist/smartFilter.d.ts +8 -8
  140. package/dist/smartFilter.d.ts.map +1 -1
  141. package/dist/smartFilter.js +6 -7
  142. package/dist/smartFilter.js.map +1 -1
  143. package/dist/utils/buildTools/buildShaders.d.ts +3 -2
  144. package/dist/utils/buildTools/buildShaders.d.ts.map +1 -1
  145. package/dist/utils/buildTools/buildShaders.js +7 -6
  146. package/dist/utils/buildTools/buildShaders.js.map +1 -1
  147. package/dist/utils/buildTools/convertGlslIntoBlock.d.ts +4 -2
  148. package/dist/utils/buildTools/convertGlslIntoBlock.d.ts.map +1 -1
  149. package/dist/utils/buildTools/convertGlslIntoBlock.js +136 -141
  150. package/dist/utils/buildTools/convertGlslIntoBlock.js.map +1 -1
  151. package/dist/utils/buildTools/convertGlslIntoShaderProgram.d.ts +4 -4
  152. package/dist/utils/buildTools/convertGlslIntoShaderProgram.d.ts.map +1 -1
  153. package/dist/utils/buildTools/convertGlslIntoShaderProgram.js +55 -48
  154. package/dist/utils/buildTools/convertGlslIntoShaderProgram.js.map +1 -1
  155. package/dist/utils/buildTools/convertShaders.d.ts +8 -5
  156. package/dist/utils/buildTools/convertShaders.d.ts.map +1 -1
  157. package/dist/utils/buildTools/convertShaders.js +38 -14
  158. package/dist/utils/buildTools/convertShaders.js.map +1 -1
  159. package/dist/utils/buildTools/shaderConverter.d.ts +4 -4
  160. package/dist/utils/buildTools/shaderConverter.d.ts.map +1 -1
  161. package/dist/utils/buildTools/shaderConverter.js +17 -21
  162. package/dist/utils/buildTools/shaderConverter.js.map +1 -1
  163. package/dist/utils/buildTools/watchShaders.d.ts +3 -2
  164. package/dist/utils/buildTools/watchShaders.d.ts.map +1 -1
  165. package/dist/utils/buildTools/watchShaders.js +10 -8
  166. package/dist/utils/buildTools/watchShaders.js.map +1 -1
  167. package/dist/utils/index.d.ts +1 -0
  168. package/dist/utils/index.d.ts.map +1 -1
  169. package/dist/utils/index.js +2 -1
  170. package/dist/utils/index.js.map +1 -1
  171. package/dist/utils/renderTargetUtils.d.ts +3 -3
  172. package/dist/utils/renderTargetUtils.d.ts.map +1 -1
  173. package/dist/utils/renderTargetUtils.js +4 -4
  174. package/dist/utils/renderTargetUtils.js.map +1 -1
  175. package/dist/utils/shaderCodeUtils.d.ts +8 -8
  176. package/dist/utils/shaderCodeUtils.d.ts.map +1 -1
  177. package/dist/utils/shaderCodeUtils.js +16 -28
  178. package/dist/utils/shaderCodeUtils.js.map +1 -1
  179. package/dist/utils/textureLoaders.d.ts +2 -2
  180. package/dist/utils/textureLoaders.d.ts.map +1 -1
  181. package/dist/utils/textureLoaders.js +2 -3
  182. package/dist/utils/textureLoaders.js.map +1 -1
  183. package/dist/utils/textureUtils.d.ts +5 -5
  184. package/dist/utils/textureUtils.d.ts.map +1 -1
  185. package/dist/utils/textureUtils.js +1 -1
  186. package/dist/utils/textureUtils.js.map +1 -1
  187. package/dist/version.d.ts +2 -2
  188. package/dist/version.js +2 -2
  189. package/license.md +21 -21
  190. package/package.json +13 -8
  191. package/src/blockFoundation/aggregateBlock.ts +148 -151
  192. package/src/blockFoundation/baseBlock.ts +339 -364
  193. package/src/blockFoundation/customAggregateBlock.ts +88 -104
  194. package/src/blockFoundation/customShaderBlock.ts +362 -326
  195. package/src/blockFoundation/disableableShaderBlock.ts +91 -100
  196. package/src/blockFoundation/index.ts +9 -9
  197. package/src/blockFoundation/inputBlock.deserializer.ts +72 -97
  198. package/src/blockFoundation/inputBlock.serialization.types.ts +126 -132
  199. package/src/blockFoundation/inputBlock.serializer.ts +150 -150
  200. package/src/blockFoundation/inputBlock.ts +181 -192
  201. package/src/blockFoundation/outputBlock.ts +144 -151
  202. package/src/blockFoundation/shaderBlock.ts +156 -163
  203. package/src/blockFoundation/textureOptions.ts +57 -57
  204. package/src/command/command.ts +59 -59
  205. package/src/command/commandBuffer.ts +71 -71
  206. package/src/command/commandBufferDebugger.ts +14 -14
  207. package/src/command/index.ts +7 -3
  208. package/src/connection/connectionPoint.ts +205 -214
  209. package/src/connection/connectionPointCompatibilityState.ts +31 -31
  210. package/src/connection/connectionPointType.ts +45 -45
  211. package/src/connection/connectionPointWithDefault.ts +27 -35
  212. package/src/connection/index.ts +8 -9
  213. package/src/editorUtils/editableInPropertyPage.ts +106 -106
  214. package/src/editorUtils/index.ts +3 -1
  215. package/src/index.ts +16 -15
  216. package/src/optimization/dependencyGraph.ts +96 -96
  217. package/src/optimization/index.ts +1 -1
  218. package/src/optimization/optimizedShaderBlock.ts +131 -134
  219. package/src/optimization/smartFilterOptimizer.ts +757 -825
  220. package/src/runtime/index.ts +8 -6
  221. package/src/runtime/renderTargetGenerator.ts +222 -248
  222. package/src/runtime/shaderRuntime.ts +174 -173
  223. package/src/runtime/smartFilterRuntime.ts +112 -112
  224. package/src/runtime/strongRef.ts +18 -18
  225. package/src/serialization/importCustomBlockDefinition.ts +86 -86
  226. package/src/serialization/index.ts +10 -7
  227. package/src/serialization/serializedBlockDefinition.ts +12 -12
  228. package/src/serialization/serializedShaderBlockDefinition.ts +7 -7
  229. package/src/serialization/serializedSmartFilter.ts +6 -6
  230. package/src/serialization/smartFilterDeserializer.ts +190 -220
  231. package/src/serialization/smartFilterSerializer.ts +110 -121
  232. package/src/serialization/v1/defaultBlockSerializer.ts +21 -21
  233. package/src/serialization/v1/index.ts +4 -2
  234. package/src/serialization/v1/shaderBlockSerialization.types.ts +85 -85
  235. package/src/serialization/v1/smartFilterSerialization.types.ts +129 -133
  236. package/src/smartFilter.ts +255 -260
  237. package/src/utils/buildTools/buildShaders.ts +14 -13
  238. package/src/utils/buildTools/convertGlslIntoBlock.ts +370 -385
  239. package/src/utils/buildTools/convertGlslIntoShaderProgram.ts +173 -172
  240. package/src/utils/buildTools/convertShaders.ts +65 -43
  241. package/src/utils/buildTools/recordVersionNumber.js +24 -0
  242. package/src/utils/buildTools/shaderConverter.ts +466 -478
  243. package/src/utils/buildTools/watchShaders.ts +44 -42
  244. package/src/utils/index.ts +4 -2
  245. package/src/utils/renderTargetUtils.ts +30 -35
  246. package/src/utils/shaderCodeUtils.ts +192 -207
  247. package/src/utils/textureLoaders.ts +31 -43
  248. package/src/utils/textureUtils.ts +28 -32
  249. package/src/version.ts +2 -2
  250. package/dist/utils/buildTools/determineVersion.d.ts +0 -36
  251. package/dist/utils/buildTools/determineVersion.d.ts.map +0 -1
  252. package/dist/utils/buildTools/determineVersion.js +0 -109
  253. package/dist/utils/buildTools/determineVersion.js.map +0 -1
  254. package/dist/utils/buildTools/versionUp.d.ts +0 -2
  255. package/dist/utils/buildTools/versionUp.d.ts.map +0 -1
  256. package/dist/utils/buildTools/versionUp.js +0 -53
  257. package/dist/utils/buildTools/versionUp.js.map +0 -1
  258. package/src/utils/buildTools/determineVersion.ts +0 -128
  259. package/src/utils/buildTools/versionUp.ts +0 -61
@@ -1,478 +1,466 @@
1
- import type { Nullable } from "@babylonjs/core/types";
2
- import { Logger } from "@babylonjs/core/Misc/logger.js";
3
- import type { ShaderCode, ShaderFunction } from "./shaderCode.types";
4
- import { ConnectionPointType } from "../../connection/connectionPointType.js";
5
- import { BlockDisableStrategy } from "../../blockFoundation/disableableShaderBlock.js";
6
-
7
- // Note: creating a global RegExp object is risky, because it holds state (e.g. lastIndex) that has to be
8
- // cleared at the right time to ensure correctness, which is easy to forget to do.
9
- // Instead, for regular expressions used in multiple places, we define the string and options once, and create
10
- // a new RegExp object from them when needed, to ensure state isn't accidentally reused.
11
-
12
- // Matches a function's name and its parameters
13
- const GetFunctionHeaderRegExString = `\\S*\\w+\\s+(\\w+)\\s*\\((.*?)\\)\\s*\\{`;
14
- const GetFunctionHeaderRegExOptions = "g";
15
-
16
- // Matches a #define statement line, capturing its name
17
- const GetDefineRegExString = `^\\S*#define\\s+(\\w+).*$`;
18
- const GetDefineRegExOptions = "gm";
19
-
20
- const ReservedSymbols = ["main"];
21
-
22
- /**
23
- * Describes the supported metadata properties for a uniform
24
- */
25
- export type UniformMetadataProperties = {
26
- /**
27
- * If supplied, the default value to use for the corresponding input connection point
28
- */
29
- default?: any;
30
-
31
- /**
32
- * If supplied, the input will be automatically bound to this value, instead of creating an input connection point
33
- * @see InputAutoBindV1 for possible values.
34
- */
35
- autoBind?: string;
36
- };
37
-
38
- /**
39
- * Describes a uniform in a shader
40
- */
41
- export type UniformMetadata = {
42
- /**
43
- * The original name of the uniform (not renamed)
44
- */
45
- name: string;
46
-
47
- /**
48
- * The type string of the uniform
49
- */
50
- type: ConnectionPointType;
51
-
52
- /**
53
- * Optional properties of the uniform
54
- */
55
- properties?: UniformMetadataProperties;
56
- };
57
-
58
- /**
59
- * Information about a fragment shader
60
- */
61
- export type FragmentShaderInfo = {
62
- /**
63
- * If supplied, the blockType to use for the block
64
- */
65
- blockType?: string;
66
-
67
- /**
68
- * If supplied, the namespace of the block
69
- */
70
- namespace: Nullable<string>;
71
-
72
- /**
73
- * If true, optimization should be disabled for this shader
74
- */
75
- disableOptimization?: boolean;
76
-
77
- /**
78
- * If supplied, the strategy to use for making this block disableable
79
- */
80
- blockDisableStrategy?: BlockDisableStrategy;
81
-
82
- /**
83
- * The shader code
84
- */
85
- shaderCode: ShaderCode;
86
-
87
- /**
88
- * The set of uniforms
89
- */
90
- uniforms: UniformMetadata[];
91
- };
92
-
93
- /**
94
- * Parses a fragment shader
95
- * @param fragmentShader - The fragment shader to process
96
- * @returns The processed fragment shader
97
- */
98
- export function parseFragmentShader(fragmentShader: string): FragmentShaderInfo {
99
- const { header, fragmentShaderWithoutHeader } = readHeader(fragmentShader);
100
- fragmentShader = fragmentShaderWithoutHeader;
101
- const blockType = header?.[SmartFilterBlockTypeKey] || undefined;
102
- const namespace = header?.namespace || null;
103
-
104
- // Read the uniforms
105
- const uniforms: UniformMetadata[] = [];
106
- const uniformRegExp = new RegExp(/(\/\/\s*\{.*\}\s*(?:\r\n|\r|\n)+)?(uniform .*)/gm);
107
- const uniformGroups = fragmentShader.matchAll(uniformRegExp);
108
- for (const matches of uniformGroups) {
109
- const annotationJSON = matches[1];
110
- const uniformLine = matches[2];
111
-
112
- if (!uniformLine) {
113
- throw new Error("Uniform line not found");
114
- }
115
-
116
- const uniformLineMatches = new RegExp(/^uniform\s+(\w+)\s+(\w+)\s*;?/gm).exec(uniformLine);
117
- if (!uniformLineMatches || uniformLineMatches.length < 3) {
118
- throw new Error(`Uniforms must have a type and a name: '${uniformLine}'`);
119
- }
120
- const uniformTypeString = uniformLineMatches[1];
121
- const uniformName = uniformLineMatches[2];
122
-
123
- if (!uniformTypeString) {
124
- throw new Error(`Uniforms must have a type: '${uniformLine}'`);
125
- }
126
- if (!uniformName) {
127
- throw new Error(`Uniforms must have a name: '${uniformLine}'`);
128
- }
129
-
130
- // Convert to ConnectionPointType
131
- let type: ConnectionPointType;
132
- switch (uniformTypeString) {
133
- case "float":
134
- type = ConnectionPointType.Float;
135
- break;
136
- case "sampler2D":
137
- type = ConnectionPointType.Texture;
138
- break;
139
- case "vec3":
140
- type = ConnectionPointType.Color3;
141
- break;
142
- case "vec4":
143
- type = ConnectionPointType.Color4;
144
- break;
145
- case "bool":
146
- type = ConnectionPointType.Boolean;
147
- break;
148
- case "vec2":
149
- type = ConnectionPointType.Vector2;
150
- break;
151
- default:
152
- throw new Error(`Unsupported uniform type: '${uniformTypeString}'`);
153
- }
154
-
155
- uniforms.push({
156
- name: uniformName,
157
- type,
158
- properties: annotationJSON ? JSON.parse(annotationJSON.replace("//", "").trim()) : undefined,
159
- });
160
-
161
- if (annotationJSON) {
162
- // Strip out any annotation so it isn't mistaken for function bodies
163
- fragmentShader = fragmentShader.replace(annotationJSON, "");
164
- }
165
- }
166
-
167
- const fragmentShaderWithNoFunctionBodies = removeFunctionBodies(fragmentShader);
168
-
169
- // Collect uniform, const, and function names which need to be decorated
170
- // eslint-disable-next-line prettier/prettier
171
- const uniformNames = uniforms.map((uniform) => uniform.name);
172
- Logger.Log(`Uniforms found: ${JSON.stringify(uniforms)}`);
173
- const consts = [...fragmentShader.matchAll(/\S*const\s+\w*\s+(\w*)\s*=.*;/g)].map((match) => match[1]);
174
- Logger.Log(`Consts found: ${JSON.stringify(consts)}`);
175
- const defineNames = [...fragmentShader.matchAll(new RegExp(GetDefineRegExString, GetDefineRegExOptions))].map(
176
- (match) => match[1]
177
- );
178
- Logger.Log(`Defines found: ${JSON.stringify(defineNames)}`);
179
- const functionNames = [
180
- ...fragmentShaderWithNoFunctionBodies.matchAll(
181
- new RegExp(GetFunctionHeaderRegExString, GetFunctionHeaderRegExOptions)
182
- ),
183
- ].map((match) => match[1]);
184
- Logger.Log(`Functions found: ${JSON.stringify(functionNames)}`);
185
-
186
- // Decorate the uniforms, consts, defines, and functions
187
- const symbolsToDecorate = [...uniformNames, ...consts, ...defineNames, ...functionNames];
188
- let fragmentShaderWithRenamedSymbols = fragmentShader;
189
- for (const symbol of symbolsToDecorate) {
190
- if (!symbol) {
191
- continue;
192
- }
193
- if (ReservedSymbols.indexOf(symbol) !== -1) {
194
- throw new Error(`Symbol "${symbol}" is reserved and cannot be used`);
195
- }
196
- const regex = new RegExp(`(?<=\\W+)${symbol}(?=\\W+)`, "gs");
197
- fragmentShaderWithRenamedSymbols = fragmentShaderWithRenamedSymbols.replace(regex, `_${symbol}_`);
198
- }
199
- Logger.Log(`${symbolsToDecorate.length} symbol(s) renamed`);
200
-
201
- // Extract all the uniforms
202
- const finalUniforms = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(uniform\s.*)/gm)].map(
203
- (match) => match[1]
204
- );
205
-
206
- // Extract all the consts
207
- const finalConsts = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(const\s.*)/gm)].map((match) => match[1]);
208
-
209
- // Extract all the defines
210
- const finalDefines = [
211
- ...fragmentShaderWithRenamedSymbols.matchAll(new RegExp(GetDefineRegExString, GetDefineRegExOptions)),
212
- ].map((match) => match[0]);
213
-
214
- // Find the main input
215
- const mainInputs = [...fragmentShaderWithRenamedSymbols.matchAll(/\S*uniform.*\s(\w*);\s*\/\/\s*main/gm)].map(
216
- (match) => match[1]
217
- );
218
- if (mainInputs.length > 1) {
219
- throw new Error("Shaders may have no more than 1 main input");
220
- }
221
- const mainInputTexture = mainInputs[0];
222
-
223
- // Extract all the functions
224
- const { extractedFunctions, mainFunctionName } = extractFunctions(fragmentShaderWithRenamedSymbols);
225
-
226
- const shaderCode: ShaderCode = {
227
- uniform: finalUniforms.join("\n"),
228
- mainFunctionName,
229
- mainInputTexture,
230
- functions: extractedFunctions,
231
- defines: finalDefines,
232
- };
233
-
234
- if (finalConsts.length > 0) {
235
- shaderCode.const = finalConsts.join("\n");
236
- }
237
-
238
- return {
239
- blockType,
240
- namespace,
241
- shaderCode,
242
- uniforms,
243
- disableOptimization: !!header?.disableOptimization,
244
- blockDisableStrategy: header?.blockDisableStrategy,
245
- };
246
- }
247
-
248
- /**
249
- * Extracts all functions from the shader
250
- * @param fragment - The shader code to process
251
- * @returns A list of functions
252
- */
253
- function extractFunctions(fragment: string): {
254
- /**
255
- * The extracted functions
256
- */
257
- extractedFunctions: ShaderFunction[];
258
-
259
- /**
260
- * The name of the main function
261
- */
262
- mainFunctionName: string;
263
- } {
264
- const extractedFunctions: ShaderFunction[] = [];
265
- let mainFunctionName: string | undefined;
266
- let pos = 0;
267
-
268
- const getFunctionHeaderRegEx = new RegExp(GetFunctionHeaderRegExString, GetFunctionHeaderRegExOptions);
269
-
270
- while (pos < fragment.length) {
271
- // Match the next available function header in the fragment code
272
- getFunctionHeaderRegEx.lastIndex = pos;
273
- const match = getFunctionHeaderRegEx.exec(fragment);
274
- if (!match) {
275
- break;
276
- }
277
-
278
- const functionName = match[1];
279
- if (!functionName) {
280
- throw new Error("No function name found in shader code");
281
- }
282
-
283
- const functionParams = match[2] || "";
284
-
285
- // Store start index of the function definition
286
- const startIndex = match.index;
287
-
288
- // Balance braces to find end of function, starting just after the opening `{`
289
- let endIndex = match.index + match[0].length;
290
- let depth = 1;
291
- while (depth > 0 && endIndex < fragment.length) {
292
- if (fragment[endIndex] === "{") {
293
- depth++;
294
- } else if (fragment[endIndex] === "}") {
295
- depth--;
296
- }
297
- endIndex++;
298
- }
299
-
300
- if (depth !== 0) {
301
- throw new Error(`Mismatched curly braces found near: ${functionName}`);
302
- }
303
-
304
- // Finally, process the function code
305
- let functionCode = fragment.substring(startIndex, endIndex).trim();
306
-
307
- // Check if this function is the main function
308
- if (functionCode.includes("// main")) {
309
- if (mainFunctionName) {
310
- throw new Error("Multiple main functions found in shader code");
311
- }
312
- mainFunctionName = functionName;
313
- functionCode = functionCode.replace("// main", "");
314
- }
315
-
316
- extractedFunctions.push({
317
- name: functionName,
318
- code: functionCode,
319
- params: functionParams.trim(),
320
- });
321
-
322
- pos = endIndex;
323
- }
324
-
325
- if (!mainFunctionName) {
326
- throw new Error("No main function found in shader code");
327
- }
328
-
329
- return { extractedFunctions, mainFunctionName };
330
- }
331
-
332
- /**
333
- * Removes all function bodies from the shader code, leaving just curly braces behind at the top level
334
- * @param input - The shader code to process
335
- * @returns The shader code with all function bodies removed
336
- */
337
- function removeFunctionBodies(input: string): string {
338
- let output: string = "";
339
- let depth: number = 0;
340
-
341
- for (let pos = 0; pos < input.length; pos++) {
342
- if (input[pos] === "{") {
343
- depth++;
344
- // Special case - if we just hit the first { then include it
345
- if (depth === 1) {
346
- output += "{";
347
- }
348
- } else if (input[pos] === "}") {
349
- depth--;
350
- // Special case - if we just hit the last } then include it
351
- if (depth === 0) {
352
- output += "}";
353
- }
354
- } else if (depth === 0) {
355
- output += input[pos];
356
- }
357
- }
358
-
359
- if (depth !== 0) {
360
- Logger.Error("Unbalanced curly braces in shader code");
361
- }
362
-
363
- return output;
364
- }
365
-
366
- const SmartFilterBlockTypeKey = "smartFilterBlockType";
367
-
368
- /**
369
- * The format of the header we expect in a GLSL shader
370
- */
371
- type GlslHeader = {
372
- /**
373
- * The block type to use for the shader, required when converting to a
374
- * SerializedBlockDefinition, but not when exporting to a .ts file
375
- * to be included in a hardcoded block definition
376
- */
377
- [SmartFilterBlockTypeKey]: string;
378
-
379
- /**
380
- * The namespace to use for the block
381
- */
382
- namespace?: string;
383
-
384
- /**
385
- * If true, optimization should be disabled for this shader
386
- */
387
- disableOptimization?: boolean;
388
-
389
- /**
390
- * If supplied, this will be an instance of DisableableShaderBlock, with this BlockDisableStrategy
391
- * In the GLSL file, use the string key of the BlockDisableStrategy (e.g. "AutoSample").
392
- */
393
- blockDisableStrategy?: BlockDisableStrategy;
394
- };
395
-
396
- /**
397
- * Reads the GlslHeader from the shader code
398
- * @param fragmentShader - The shader code to read
399
- * @returns - The GlslHeader if found, otherwise null
400
- */
401
- function readHeader(fragmentShader: string): {
402
- /**
403
- * The glsl header, or null if there wasn't one
404
- */
405
- header: Nullable<GlslHeader>;
406
-
407
- /**
408
- * The fragment shader code with the header removed if there was one
409
- */
410
- fragmentShaderWithoutHeader: string;
411
- } {
412
- const singleLineHeaderMatch = new RegExp(/^\n*\s*\/\/\s*(\{.*\})/g).exec(fragmentShader);
413
- if (singleLineHeaderMatch && singleLineHeaderMatch[1]) {
414
- return {
415
- header: parseHeader(singleLineHeaderMatch[1].trim()),
416
- fragmentShaderWithoutHeader: fragmentShader.replace(singleLineHeaderMatch[0], ""),
417
- };
418
- }
419
-
420
- const multiLineHeaderMatch = new RegExp(/^\n*\s*\/\*\s*(\{.*\})\s*\*\//gs).exec(fragmentShader);
421
- if (multiLineHeaderMatch && multiLineHeaderMatch[1]) {
422
- return {
423
- header: parseHeader(multiLineHeaderMatch[1].trim()),
424
- fragmentShaderWithoutHeader: fragmentShader.replace(multiLineHeaderMatch[0], ""),
425
- };
426
- }
427
-
428
- return {
429
- header: null,
430
- fragmentShaderWithoutHeader: fragmentShader,
431
- };
432
- }
433
-
434
- /**
435
- * Parses the header from a string into a GlslHeader object
436
- * @param header - The header string to parse
437
- * @returns - The GlslHeader if the header is valid, otherwise null
438
- */
439
- function parseHeader(header: string): Nullable<GlslHeader> {
440
- const parsedObject = JSON.parse(header);
441
-
442
- if (!parsedObject || typeof parsedObject !== "object") {
443
- return null;
444
- }
445
-
446
- // Check for required properties
447
- if (!parsedObject[SmartFilterBlockTypeKey]) {
448
- throw new Error("Missing required property: " + SmartFilterBlockTypeKey);
449
- }
450
-
451
- const glslHeader = parsedObject as GlslHeader;
452
-
453
- // Fix up the disableStrategy to be a BlockDisableStrategy
454
- if (glslHeader.blockDisableStrategy) {
455
- const rawStrategyValue = glslHeader.blockDisableStrategy;
456
- switch (rawStrategyValue as unknown as string) {
457
- case "Manual":
458
- glslHeader.blockDisableStrategy = BlockDisableStrategy.Manual;
459
- break;
460
- case "AutoSample":
461
- glslHeader.blockDisableStrategy = BlockDisableStrategy.AutoSample;
462
- break;
463
- default:
464
- throw new Error(`Invalid disableStrategy: ${rawStrategyValue}`);
465
- }
466
- }
467
-
468
- return glslHeader;
469
- }
470
-
471
- /**
472
- * Determines if a fragment shader has the GLSL header required for parsing
473
- * @param fragmentShader - The fragment shader to check
474
- * @returns True if the fragment shader has the GLSL header
475
- */
476
- export function hasGlslHeader(fragmentShader: string): boolean {
477
- return fragmentShader.indexOf(SmartFilterBlockTypeKey) !== -1;
478
- }
1
+ import type { Nullable } from "core/types.js";
2
+ import { Logger } from "core/Misc/logger.js";
3
+ import type { ShaderCode, ShaderFunction } from "./shaderCode.types.js";
4
+ import { ConnectionPointType } from "../../connection/connectionPointType.js";
5
+ import { BlockDisableStrategy } from "../../blockFoundation/disableableShaderBlock.js";
6
+
7
+ // Note: creating a global RegExp object is risky, because it holds state (e.g. lastIndex) that has to be
8
+ // cleared at the right time to ensure correctness, which is easy to forget to do.
9
+ // Instead, for regular expressions used in multiple places, we define the string and options once, and create
10
+ // a new RegExp object from them when needed, to ensure state isn't accidentally reused.
11
+
12
+ // Matches a function's name and its parameters
13
+ const GetFunctionHeaderRegExString = `\\S*\\w+\\s+(\\w+)\\s*\\((.*?)\\)\\s*\\{`;
14
+ const GetFunctionHeaderRegExOptions = "g";
15
+
16
+ // Matches a #define statement line, capturing its name
17
+ const GetDefineRegExString = `^\\S*#define\\s+(\\w+).*$`;
18
+ const GetDefineRegExOptions = "gm";
19
+
20
+ const ReservedSymbols = ["main"];
21
+
22
+ /**
23
+ * Describes the supported metadata properties for a uniform
24
+ */
25
+ export type UniformMetadataProperties = {
26
+ /**
27
+ * If supplied, the default value to use for the corresponding input connection point
28
+ */
29
+ default?: any;
30
+
31
+ /**
32
+ * If supplied, the input will be automatically bound to this value, instead of creating an input connection point
33
+ * @see InputAutoBindV1 for possible values.
34
+ */
35
+ autoBind?: string;
36
+ };
37
+
38
+ /**
39
+ * Describes a uniform in a shader
40
+ */
41
+ export type UniformMetadata = {
42
+ /**
43
+ * The original name of the uniform (not renamed)
44
+ */
45
+ name: string;
46
+
47
+ /**
48
+ * The type string of the uniform
49
+ */
50
+ type: ConnectionPointType;
51
+
52
+ /**
53
+ * Optional properties of the uniform
54
+ */
55
+ properties?: UniformMetadataProperties;
56
+ };
57
+
58
+ /**
59
+ * Information about a fragment shader
60
+ */
61
+ export type FragmentShaderInfo = {
62
+ /**
63
+ * If supplied, the blockType to use for the block
64
+ */
65
+ blockType?: string;
66
+
67
+ /**
68
+ * If supplied, the namespace of the block
69
+ */
70
+ namespace: Nullable<string>;
71
+
72
+ /**
73
+ * If true, optimization should be disabled for this shader
74
+ */
75
+ disableOptimization?: boolean;
76
+
77
+ /**
78
+ * If supplied, the strategy to use for making this block disableable
79
+ */
80
+ blockDisableStrategy?: BlockDisableStrategy;
81
+
82
+ /**
83
+ * The shader code
84
+ */
85
+ shaderCode: ShaderCode;
86
+
87
+ /**
88
+ * The set of uniforms
89
+ */
90
+ uniforms: UniformMetadata[];
91
+ };
92
+
93
+ /**
94
+ * Parses a fragment shader
95
+ * @param fragmentShader - The fragment shader to process
96
+ * @returns The processed fragment shader
97
+ */
98
+ export function ParseFragmentShader(fragmentShader: string): FragmentShaderInfo {
99
+ const { header, fragmentShaderWithoutHeader } = ReadHeader(fragmentShader);
100
+ fragmentShader = fragmentShaderWithoutHeader;
101
+ const blockType = header?.[SmartFilterBlockTypeKey] || undefined;
102
+ const namespace = header?.namespace || null;
103
+
104
+ // Read the uniforms
105
+ const uniforms: UniformMetadata[] = [];
106
+ const uniformRegExp = new RegExp(/(\/\/\s*\{.*\}\s*(?:\r\n|\r|\n)+)?(uniform .*)/gm);
107
+ const uniformGroups = fragmentShader.matchAll(uniformRegExp);
108
+ for (const matches of uniformGroups) {
109
+ const annotationJSON = matches[1];
110
+ const uniformLine = matches[2];
111
+
112
+ if (!uniformLine) {
113
+ throw new Error("Uniform line not found");
114
+ }
115
+
116
+ const uniformLineMatches = new RegExp(/^uniform\s+(\w+)\s+(\w+)\s*;?/gm).exec(uniformLine);
117
+ if (!uniformLineMatches || uniformLineMatches.length < 3) {
118
+ throw new Error(`Uniforms must have a type and a name: '${uniformLine}'`);
119
+ }
120
+ const uniformTypeString = uniformLineMatches[1];
121
+ const uniformName = uniformLineMatches[2];
122
+
123
+ if (!uniformTypeString) {
124
+ throw new Error(`Uniforms must have a type: '${uniformLine}'`);
125
+ }
126
+ if (!uniformName) {
127
+ throw new Error(`Uniforms must have a name: '${uniformLine}'`);
128
+ }
129
+
130
+ // Convert to ConnectionPointType
131
+ let type: ConnectionPointType;
132
+ switch (uniformTypeString) {
133
+ case "float":
134
+ type = ConnectionPointType.Float;
135
+ break;
136
+ case "sampler2D":
137
+ type = ConnectionPointType.Texture;
138
+ break;
139
+ case "vec3":
140
+ type = ConnectionPointType.Color3;
141
+ break;
142
+ case "vec4":
143
+ type = ConnectionPointType.Color4;
144
+ break;
145
+ case "bool":
146
+ type = ConnectionPointType.Boolean;
147
+ break;
148
+ case "vec2":
149
+ type = ConnectionPointType.Vector2;
150
+ break;
151
+ default:
152
+ throw new Error(`Unsupported uniform type: '${uniformTypeString}'`);
153
+ }
154
+
155
+ uniforms.push({
156
+ name: uniformName,
157
+ type,
158
+ properties: annotationJSON ? JSON.parse(annotationJSON.replace("//", "").trim()) : undefined,
159
+ });
160
+
161
+ if (annotationJSON) {
162
+ // Strip out any annotation so it isn't mistaken for function bodies
163
+ fragmentShader = fragmentShader.replace(annotationJSON, "");
164
+ }
165
+ }
166
+
167
+ const fragmentShaderWithNoFunctionBodies = RemoveFunctionBodies(fragmentShader);
168
+
169
+ // Collect uniform, const, and function names which need to be decorated
170
+ // eslint-disable-next-line prettier/prettier
171
+ const uniformNames = uniforms.map((uniform) => uniform.name);
172
+ Logger.Log(`Uniforms found: ${JSON.stringify(uniforms)}`);
173
+ const consts = [...fragmentShader.matchAll(/\S*const\s+\w*\s+(\w*)\s*=.*;/g)].map((match) => match[1]);
174
+ Logger.Log(`Consts found: ${JSON.stringify(consts)}`);
175
+ const defineNames = [...fragmentShader.matchAll(new RegExp(GetDefineRegExString, GetDefineRegExOptions))].map((match) => match[1]);
176
+ Logger.Log(`Defines found: ${JSON.stringify(defineNames)}`);
177
+ const functionNames = [...fragmentShaderWithNoFunctionBodies.matchAll(new RegExp(GetFunctionHeaderRegExString, GetFunctionHeaderRegExOptions))].map((match) => match[1]);
178
+ Logger.Log(`Functions found: ${JSON.stringify(functionNames)}`);
179
+
180
+ // Decorate the uniforms, consts, defines, and functions
181
+ const symbolsToDecorate = [...uniformNames, ...consts, ...defineNames, ...functionNames];
182
+ let fragmentShaderWithRenamedSymbols = fragmentShader;
183
+ for (const symbol of symbolsToDecorate) {
184
+ if (!symbol) {
185
+ continue;
186
+ }
187
+ if (ReservedSymbols.indexOf(symbol) !== -1) {
188
+ throw new Error(`Symbol "${symbol}" is reserved and cannot be used`);
189
+ }
190
+ const regex = new RegExp(`(?<=\\W+)${symbol}(?=\\W+)`, "gs");
191
+ fragmentShaderWithRenamedSymbols = fragmentShaderWithRenamedSymbols.replace(regex, `_${symbol}_`);
192
+ }
193
+ Logger.Log(`${symbolsToDecorate.length} symbol(s) renamed`);
194
+
195
+ // Extract all the uniforms
196
+ const finalUniforms = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(uniform\s.*)/gm)].map((match) => match[1]);
197
+
198
+ // Extract all the consts
199
+ const finalConsts = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(const\s.*)/gm)].map((match) => match[1]);
200
+
201
+ // Extract all the defines
202
+ const finalDefines = [...fragmentShaderWithRenamedSymbols.matchAll(new RegExp(GetDefineRegExString, GetDefineRegExOptions))].map((match) => match[0]);
203
+
204
+ // Find the main input
205
+ const mainInputs = [...fragmentShaderWithRenamedSymbols.matchAll(/\S*uniform.*\s(\w*);\s*\/\/\s*main/gm)].map((match) => match[1]);
206
+ if (mainInputs.length > 1) {
207
+ throw new Error("Shaders may have no more than 1 main input");
208
+ }
209
+ const mainInputTexture = mainInputs[0];
210
+
211
+ // Extract all the functions
212
+ const { extractedFunctions, mainFunctionName } = ExtractFunctions(fragmentShaderWithRenamedSymbols);
213
+
214
+ const shaderCode: ShaderCode = {
215
+ uniform: finalUniforms.join("\n"),
216
+ mainFunctionName,
217
+ mainInputTexture,
218
+ functions: extractedFunctions,
219
+ defines: finalDefines,
220
+ };
221
+
222
+ if (finalConsts.length > 0) {
223
+ shaderCode.const = finalConsts.join("\n");
224
+ }
225
+
226
+ return {
227
+ blockType,
228
+ namespace,
229
+ shaderCode,
230
+ uniforms,
231
+ disableOptimization: !!header?.disableOptimization,
232
+ blockDisableStrategy: header?.blockDisableStrategy,
233
+ };
234
+ }
235
+
236
+ /**
237
+ * Extracts all functions from the shader
238
+ * @param fragment - The shader code to process
239
+ * @returns A list of functions
240
+ */
241
+ function ExtractFunctions(fragment: string): {
242
+ /**
243
+ * The extracted functions
244
+ */
245
+ extractedFunctions: ShaderFunction[];
246
+
247
+ /**
248
+ * The name of the main function
249
+ */
250
+ mainFunctionName: string;
251
+ } {
252
+ const extractedFunctions: ShaderFunction[] = [];
253
+ let mainFunctionName: string | undefined;
254
+ let pos = 0;
255
+
256
+ const getFunctionHeaderRegEx = new RegExp(GetFunctionHeaderRegExString, GetFunctionHeaderRegExOptions);
257
+
258
+ while (pos < fragment.length) {
259
+ // Match the next available function header in the fragment code
260
+ getFunctionHeaderRegEx.lastIndex = pos;
261
+ const match = getFunctionHeaderRegEx.exec(fragment);
262
+ if (!match) {
263
+ break;
264
+ }
265
+
266
+ const functionName = match[1];
267
+ if (!functionName) {
268
+ throw new Error("No function name found in shader code");
269
+ }
270
+
271
+ const functionParams = match[2] || "";
272
+
273
+ // Store start index of the function definition
274
+ const startIndex = match.index;
275
+
276
+ // Balance braces to find end of function, starting just after the opening `{`
277
+ let endIndex = match.index + match[0].length;
278
+ let depth = 1;
279
+ while (depth > 0 && endIndex < fragment.length) {
280
+ if (fragment[endIndex] === "{") {
281
+ depth++;
282
+ } else if (fragment[endIndex] === "}") {
283
+ depth--;
284
+ }
285
+ endIndex++;
286
+ }
287
+
288
+ if (depth !== 0) {
289
+ throw new Error(`Mismatched curly braces found near: ${functionName}`);
290
+ }
291
+
292
+ // Finally, process the function code
293
+ let functionCode = fragment.substring(startIndex, endIndex).trim();
294
+
295
+ // Check if this function is the main function
296
+ if (functionCode.includes("// main")) {
297
+ if (mainFunctionName) {
298
+ throw new Error("Multiple main functions found in shader code");
299
+ }
300
+ mainFunctionName = functionName;
301
+ functionCode = functionCode.replace("// main", "");
302
+ }
303
+
304
+ extractedFunctions.push({
305
+ name: functionName,
306
+ code: functionCode,
307
+ params: functionParams.trim(),
308
+ });
309
+
310
+ pos = endIndex;
311
+ }
312
+
313
+ if (!mainFunctionName) {
314
+ throw new Error("No main function found in shader code");
315
+ }
316
+
317
+ return { extractedFunctions, mainFunctionName };
318
+ }
319
+
320
+ /**
321
+ * Removes all function bodies from the shader code, leaving just curly braces behind at the top level
322
+ * @param input - The shader code to process
323
+ * @returns The shader code with all function bodies removed
324
+ */
325
+ function RemoveFunctionBodies(input: string): string {
326
+ let output: string = "";
327
+ let depth: number = 0;
328
+
329
+ for (let pos = 0; pos < input.length; pos++) {
330
+ if (input[pos] === "{") {
331
+ depth++;
332
+ // Special case - if we just hit the first { then include it
333
+ if (depth === 1) {
334
+ output += "{";
335
+ }
336
+ } else if (input[pos] === "}") {
337
+ depth--;
338
+ // Special case - if we just hit the last } then include it
339
+ if (depth === 0) {
340
+ output += "}";
341
+ }
342
+ } else if (depth === 0) {
343
+ output += input[pos];
344
+ }
345
+ }
346
+
347
+ if (depth !== 0) {
348
+ Logger.Error("Unbalanced curly braces in shader code");
349
+ }
350
+
351
+ return output;
352
+ }
353
+
354
+ const SmartFilterBlockTypeKey = "smartFilterBlockType";
355
+
356
+ /**
357
+ * The format of the header we expect in a GLSL shader
358
+ */
359
+ type GlslHeader = {
360
+ /**
361
+ * The block type to use for the shader, required when converting to a
362
+ * SerializedBlockDefinition, but not when exporting to a .ts file
363
+ * to be included in a hardcoded block definition
364
+ */
365
+ [SmartFilterBlockTypeKey]: string;
366
+
367
+ /**
368
+ * The namespace to use for the block
369
+ */
370
+ namespace?: string;
371
+
372
+ /**
373
+ * If true, optimization should be disabled for this shader
374
+ */
375
+ disableOptimization?: boolean;
376
+
377
+ /**
378
+ * If supplied, this will be an instance of DisableableShaderBlock, with this BlockDisableStrategy
379
+ * In the GLSL file, use the string key of the BlockDisableStrategy (e.g. "AutoSample").
380
+ */
381
+ blockDisableStrategy?: BlockDisableStrategy;
382
+ };
383
+
384
+ /**
385
+ * Reads the GlslHeader from the shader code
386
+ * @param fragmentShader - The shader code to read
387
+ * @returns - The GlslHeader if found, otherwise null
388
+ */
389
+ function ReadHeader(fragmentShader: string): {
390
+ /**
391
+ * The glsl header, or null if there wasn't one
392
+ */
393
+ header: Nullable<GlslHeader>;
394
+
395
+ /**
396
+ * The fragment shader code with the header removed if there was one
397
+ */
398
+ fragmentShaderWithoutHeader: string;
399
+ } {
400
+ const singleLineHeaderMatch = new RegExp(/^\n*\s*\/\/\s*(\{.*\})/g).exec(fragmentShader);
401
+ if (singleLineHeaderMatch && singleLineHeaderMatch[1]) {
402
+ return {
403
+ header: ParseHeader(singleLineHeaderMatch[1].trim()),
404
+ fragmentShaderWithoutHeader: fragmentShader.replace(singleLineHeaderMatch[0], ""),
405
+ };
406
+ }
407
+
408
+ const multiLineHeaderMatch = new RegExp(/^\n*\s*\/\*\s*(\{.*\})\s*\*\//gs).exec(fragmentShader);
409
+ if (multiLineHeaderMatch && multiLineHeaderMatch[1]) {
410
+ return {
411
+ header: ParseHeader(multiLineHeaderMatch[1].trim()),
412
+ fragmentShaderWithoutHeader: fragmentShader.replace(multiLineHeaderMatch[0], ""),
413
+ };
414
+ }
415
+
416
+ return {
417
+ header: null,
418
+ fragmentShaderWithoutHeader: fragmentShader,
419
+ };
420
+ }
421
+
422
+ /**
423
+ * Parses the header from a string into a GlslHeader object
424
+ * @param header - The header string to parse
425
+ * @returns - The GlslHeader if the header is valid, otherwise null
426
+ */
427
+ function ParseHeader(header: string): Nullable<GlslHeader> {
428
+ const parsedObject = JSON.parse(header);
429
+
430
+ if (!parsedObject || typeof parsedObject !== "object") {
431
+ return null;
432
+ }
433
+
434
+ // Check for required properties
435
+ if (!parsedObject[SmartFilterBlockTypeKey]) {
436
+ throw new Error("Missing required property: " + SmartFilterBlockTypeKey);
437
+ }
438
+
439
+ const glslHeader = parsedObject as GlslHeader;
440
+
441
+ // Fix up the disableStrategy to be a BlockDisableStrategy
442
+ if (glslHeader.blockDisableStrategy) {
443
+ const rawStrategyValue = glslHeader.blockDisableStrategy;
444
+ switch (rawStrategyValue as unknown as string) {
445
+ case "Manual":
446
+ glslHeader.blockDisableStrategy = BlockDisableStrategy.Manual;
447
+ break;
448
+ case "AutoSample":
449
+ glslHeader.blockDisableStrategy = BlockDisableStrategy.AutoSample;
450
+ break;
451
+ default:
452
+ throw new Error(`Invalid disableStrategy: ${rawStrategyValue}`);
453
+ }
454
+ }
455
+
456
+ return glslHeader;
457
+ }
458
+
459
+ /**
460
+ * Determines if a fragment shader has the GLSL header required for parsing
461
+ * @param fragmentShader - The fragment shader to check
462
+ * @returns True if the fragment shader has the GLSL header
463
+ */
464
+ export function HasGlslHeader(fragmentShader: string): boolean {
465
+ return fragmentShader.indexOf(SmartFilterBlockTypeKey) !== -1;
466
+ }