@onerjs/smart-filters 8.25.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 (66) hide show
  1. package/license.md +21 -0
  2. package/package.json +52 -0
  3. package/readme.md +9 -0
  4. package/src/IDisposable.ts +9 -0
  5. package/src/blockFoundation/aggregateBlock.ts +148 -0
  6. package/src/blockFoundation/baseBlock.ts +339 -0
  7. package/src/blockFoundation/customAggregateBlock.ts +88 -0
  8. package/src/blockFoundation/customShaderBlock.ts +362 -0
  9. package/src/blockFoundation/disableableShaderBlock.ts +91 -0
  10. package/src/blockFoundation/index.ts +9 -0
  11. package/src/blockFoundation/inputBlock.deserializer.ts +72 -0
  12. package/src/blockFoundation/inputBlock.serialization.types.ts +126 -0
  13. package/src/blockFoundation/inputBlock.serializer.ts +150 -0
  14. package/src/blockFoundation/inputBlock.ts +181 -0
  15. package/src/blockFoundation/outputBlock.ts +144 -0
  16. package/src/blockFoundation/shaderBlock.ts +156 -0
  17. package/src/blockFoundation/textureOptions.ts +57 -0
  18. package/src/command/command.ts +59 -0
  19. package/src/command/commandBuffer.ts +71 -0
  20. package/src/command/commandBufferDebugger.ts +14 -0
  21. package/src/command/index.ts +7 -0
  22. package/src/connection/connectionPoint.ts +205 -0
  23. package/src/connection/connectionPointCompatibilityState.ts +31 -0
  24. package/src/connection/connectionPointDirection.ts +9 -0
  25. package/src/connection/connectionPointType.ts +45 -0
  26. package/src/connection/connectionPointWithDefault.ts +27 -0
  27. package/src/connection/index.ts +8 -0
  28. package/src/editorUtils/editableInPropertyPage.ts +106 -0
  29. package/src/editorUtils/index.ts +3 -0
  30. package/src/index.ts +16 -0
  31. package/src/optimization/dependencyGraph.ts +96 -0
  32. package/src/optimization/index.ts +1 -0
  33. package/src/optimization/optimizedShaderBlock.ts +131 -0
  34. package/src/optimization/smartFilterOptimizer.ts +757 -0
  35. package/src/runtime/index.ts +8 -0
  36. package/src/runtime/renderTargetGenerator.ts +222 -0
  37. package/src/runtime/shaderRuntime.ts +174 -0
  38. package/src/runtime/smartFilterRuntime.ts +112 -0
  39. package/src/runtime/strongRef.ts +18 -0
  40. package/src/serialization/importCustomBlockDefinition.ts +86 -0
  41. package/src/serialization/index.ts +10 -0
  42. package/src/serialization/serializedBlockDefinition.ts +12 -0
  43. package/src/serialization/serializedShaderBlockDefinition.ts +7 -0
  44. package/src/serialization/serializedSmartFilter.ts +6 -0
  45. package/src/serialization/smartFilterDeserializer.ts +190 -0
  46. package/src/serialization/smartFilterSerializer.ts +110 -0
  47. package/src/serialization/v1/defaultBlockSerializer.ts +21 -0
  48. package/src/serialization/v1/index.ts +4 -0
  49. package/src/serialization/v1/shaderBlockSerialization.types.ts +85 -0
  50. package/src/serialization/v1/smartFilterSerialization.types.ts +129 -0
  51. package/src/smartFilter.ts +255 -0
  52. package/src/utils/buildTools/buildShaders.ts +14 -0
  53. package/src/utils/buildTools/convertGlslIntoBlock.ts +370 -0
  54. package/src/utils/buildTools/convertGlslIntoShaderProgram.ts +173 -0
  55. package/src/utils/buildTools/convertShaders.ts +65 -0
  56. package/src/utils/buildTools/recordVersionNumber.js +24 -0
  57. package/src/utils/buildTools/shaderCode.types.ts +59 -0
  58. package/src/utils/buildTools/shaderConverter.ts +466 -0
  59. package/src/utils/buildTools/watchShaders.ts +44 -0
  60. package/src/utils/index.ts +4 -0
  61. package/src/utils/renderTargetUtils.ts +30 -0
  62. package/src/utils/shaderCodeUtils.ts +192 -0
  63. package/src/utils/textureLoaders.ts +31 -0
  64. package/src/utils/textureUtils.ts +28 -0
  65. package/src/utils/uniqueIdGenerator.ts +28 -0
  66. package/src/version.ts +4 -0
@@ -0,0 +1,466 @@
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
+ }
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-console */
2
+ /**
3
+ * Watches all .glsl files under <shaderPath> and rebuilds them when changed.
4
+ * @param shaderPath - The path to the shaders to watch
5
+ * @param smartFiltersCorePath - The path to import the Smart Filters core from
6
+ * @param babylonCorePath - The path to import the Babylon core from (optional)
7
+ * @example node watchShaders.js <shaderPath> @babylonjs/smart-filters
8
+ */
9
+
10
+ import { watch } from "chokidar";
11
+ import { extname } from "path";
12
+ import { ConvertShader } from "./convertShaders.js";
13
+
14
+ const ExternalArguments = process.argv.slice(2);
15
+ if (ExternalArguments.length >= 2 && ExternalArguments[0] && ExternalArguments[1]) {
16
+ const shaderPath = ExternalArguments[0];
17
+ const smartFiltersCorePath = ExternalArguments[1];
18
+ const babylonCorePath = ExternalArguments[2];
19
+
20
+ watch(shaderPath).on("all", (event, file) => {
21
+ // Only process file changes and added files
22
+ if (event !== "change" && event !== "add") {
23
+ return;
24
+ }
25
+
26
+ // Only process .glsl files
27
+ if (extname(file) !== ".glsl") {
28
+ return;
29
+ }
30
+
31
+ console.log(`Change detected. Starting conversion...`);
32
+
33
+ // Wrap in try-catch to prevent the watcher from crashing
34
+ // if the new shader changes are invalid
35
+ try {
36
+ ConvertShader(file, smartFiltersCorePath, babylonCorePath);
37
+ console.log(`Successfully updated shader ${file}`);
38
+ } catch (error) {
39
+ console.error(`Failed to convert shader ${file}: ${error}`);
40
+ }
41
+
42
+ console.log(`Watching for changes in ${shaderPath}...`);
43
+ });
44
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./textureLoaders.js";
2
+ // Back compat for when camelCase was used
3
+ export { CreateImageTexture as createImageTexture } from "./textureLoaders.js";
4
+ export { type ShaderProgram } from "./shaderCodeUtils.js";
@@ -0,0 +1,30 @@
1
+ import { CreateCommand } from "../command/command.js";
2
+ import type { BaseBlock } from "../blockFoundation/baseBlock.js";
3
+ import type { ShaderRuntime } from "../runtime/shaderRuntime.js";
4
+ import type { InternalSmartFilterRuntime } from "../runtime/smartFilterRuntime.js";
5
+ import type { OutputBlock } from "../blockFoundation/outputBlock.js";
6
+
7
+ /**
8
+ * Registers the final command of the command queue - the one that draws to either the canvas or
9
+ * renderTargetTexture.
10
+ * @param outputBlock - The output block.
11
+ * @param runtime - The smart filter runtime to use.
12
+ * @param commandOwner - The owner of the command.
13
+ * @param shaderBlockRuntime - The shader block runtime to use.
14
+ */
15
+ export function RegisterFinalRenderCommand(outputBlock: OutputBlock, runtime: InternalSmartFilterRuntime, commandOwner: BaseBlock, shaderBlockRuntime: ShaderRuntime): void {
16
+ const commandOwnerBlockType = commandOwner.blockType;
17
+ if (outputBlock.renderTargetWrapper) {
18
+ runtime.registerCommand(
19
+ CreateCommand(`${commandOwnerBlockType}.renderToFinalTexture`, commandOwner, () => {
20
+ shaderBlockRuntime.renderToTargetWrapper(outputBlock);
21
+ })
22
+ );
23
+ } else {
24
+ runtime.registerCommand(
25
+ CreateCommand(`${commandOwnerBlockType}.renderToCanvas`, commandOwner, () => {
26
+ shaderBlockRuntime.renderToCanvas();
27
+ })
28
+ );
29
+ }
30
+ }