@babylonjs/smart-filters 0.4.3-alpha → 0.6.0-alpha

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 (139) hide show
  1. package/dist/blocks/baseBlock.d.ts +6 -0
  2. package/dist/blocks/baseBlock.d.ts.map +1 -1
  3. package/dist/blocks/baseBlock.js +8 -0
  4. package/dist/blocks/baseBlock.js.map +1 -1
  5. package/dist/blocks/customShaderBlock.d.ts +55 -0
  6. package/dist/blocks/customShaderBlock.d.ts.map +1 -0
  7. package/dist/blocks/customShaderBlock.js +139 -0
  8. package/dist/blocks/customShaderBlock.js.map +1 -0
  9. package/dist/blocks/inputBlock.deserializer.d.ts +1 -1
  10. package/dist/blocks/inputBlock.deserializer.d.ts.map +1 -1
  11. package/dist/blocks/inputBlock.serializer.d.ts +1 -1
  12. package/dist/blocks/inputBlock.serializer.d.ts.map +1 -1
  13. package/dist/blocks/inputBlock.serializer.js +3 -3
  14. package/dist/blocks/inputBlock.serializer.js.map +1 -1
  15. package/dist/blocks/shaderBlock.d.ts +3 -7
  16. package/dist/blocks/shaderBlock.d.ts.map +1 -1
  17. package/dist/blocks/shaderBlock.js +45 -15
  18. package/dist/blocks/shaderBlock.js.map +1 -1
  19. package/dist/blocks/textureOptions.d.ts +47 -0
  20. package/dist/blocks/textureOptions.d.ts.map +1 -0
  21. package/dist/blocks/textureOptions.js +37 -0
  22. package/dist/blocks/textureOptions.js.map +1 -0
  23. package/dist/command/command.d.ts +2 -3
  24. package/dist/command/command.d.ts.map +1 -1
  25. package/dist/command/command.js.map +1 -1
  26. package/dist/command/commandBufferDebugger.js +1 -1
  27. package/dist/command/commandBufferDebugger.js.map +1 -1
  28. package/dist/connection/connectionPointType.d.ts +4 -0
  29. package/dist/connection/connectionPointType.d.ts.map +1 -1
  30. package/dist/editorUtils/editableInPropertyPage.d.ts +4 -0
  31. package/dist/editorUtils/editableInPropertyPage.d.ts.map +1 -1
  32. package/dist/editorUtils/editableInPropertyPage.js +1 -0
  33. package/dist/editorUtils/editableInPropertyPage.js.map +1 -1
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +2 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/optimization/smartFilterOptimizer.d.ts +1 -1
  39. package/dist/optimization/smartFilterOptimizer.d.ts.map +1 -1
  40. package/dist/optimization/smartFilterOptimizer.js +10 -9
  41. package/dist/optimization/smartFilterOptimizer.js.map +1 -1
  42. package/dist/runtime/renderTargetGenerator.d.ts +8 -1
  43. package/dist/runtime/renderTargetGenerator.d.ts.map +1 -1
  44. package/dist/runtime/renderTargetGenerator.js +37 -18
  45. package/dist/runtime/renderTargetGenerator.js.map +1 -1
  46. package/dist/serialization/importCustomShaderBlockDefinition.d.ts +11 -0
  47. package/dist/serialization/importCustomShaderBlockDefinition.d.ts.map +1 -0
  48. package/dist/serialization/importCustomShaderBlockDefinition.js +80 -0
  49. package/dist/serialization/importCustomShaderBlockDefinition.js.map +1 -0
  50. package/dist/serialization/index.d.ts +1 -0
  51. package/dist/serialization/index.d.ts.map +1 -1
  52. package/dist/serialization/index.js +1 -0
  53. package/dist/serialization/index.js.map +1 -1
  54. package/dist/serialization/serializedBlockDefinition.d.ts +7 -0
  55. package/dist/serialization/serializedBlockDefinition.d.ts.map +1 -0
  56. package/dist/serialization/serializedBlockDefinition.js +2 -0
  57. package/dist/serialization/serializedBlockDefinition.js.map +1 -0
  58. package/dist/serialization/serializedSmartFilter.d.ts +1 -1
  59. package/dist/serialization/serializedSmartFilter.d.ts.map +1 -1
  60. package/dist/serialization/smartFilterDeserializer.d.ts +12 -4
  61. package/dist/serialization/smartFilterDeserializer.d.ts.map +1 -1
  62. package/dist/serialization/smartFilterDeserializer.js +63 -34
  63. package/dist/serialization/smartFilterDeserializer.js.map +1 -1
  64. package/dist/serialization/smartFilterSerializer.d.ts +2 -2
  65. package/dist/serialization/smartFilterSerializer.d.ts.map +1 -1
  66. package/dist/serialization/smartFilterSerializer.js +9 -6
  67. package/dist/serialization/smartFilterSerializer.js.map +1 -1
  68. package/dist/serialization/v1/blockSerialization.types.d.ts +55 -0
  69. package/dist/serialization/v1/blockSerialization.types.d.ts.map +1 -0
  70. package/dist/serialization/v1/blockSerialization.types.js +7 -0
  71. package/dist/serialization/v1/blockSerialization.types.js.map +1 -0
  72. package/dist/serialization/v1/defaultBlockSerializer.d.ts +1 -1
  73. package/dist/serialization/v1/defaultBlockSerializer.d.ts.map +1 -1
  74. package/dist/serialization/v1/defaultBlockSerializer.js +1 -1
  75. package/dist/serialization/v1/defaultBlockSerializer.js.map +1 -1
  76. package/dist/serialization/v1/index.d.ts +1 -1
  77. package/dist/serialization/v1/index.d.ts.map +1 -1
  78. package/dist/serialization/v1/index.js +1 -1
  79. package/dist/serialization/v1/index.js.map +1 -1
  80. package/dist/serialization/v1/{serialization.types.d.ts → smartFilterSerialization.types.d.ts} +12 -11
  81. package/dist/serialization/v1/smartFilterSerialization.types.d.ts.map +1 -0
  82. package/dist/serialization/v1/smartFilterSerialization.types.js +2 -0
  83. package/dist/serialization/v1/smartFilterSerialization.types.js.map +1 -0
  84. package/dist/utils/buildTools/buildShaders.js +1 -1
  85. package/dist/utils/buildTools/buildShaders.js.map +1 -1
  86. package/dist/utils/buildTools/convertShaderForHardcodedBlock.d.ts +13 -0
  87. package/dist/utils/buildTools/convertShaderForHardcodedBlock.d.ts.map +1 -0
  88. package/dist/utils/buildTools/convertShaderForHardcodedBlock.js +116 -0
  89. package/dist/utils/buildTools/convertShaderForHardcodedBlock.js.map +1 -0
  90. package/dist/utils/buildTools/shaderCode.types.d.ts +43 -0
  91. package/dist/utils/buildTools/shaderCode.types.d.ts.map +1 -0
  92. package/dist/utils/buildTools/shaderCode.types.js +2 -0
  93. package/dist/utils/buildTools/shaderCode.types.js.map +1 -0
  94. package/dist/utils/buildTools/shaderConverter.d.ts +56 -8
  95. package/dist/utils/buildTools/shaderConverter.d.ts.map +1 -1
  96. package/dist/utils/buildTools/shaderConverter.js +87 -137
  97. package/dist/utils/buildTools/shaderConverter.js.map +1 -1
  98. package/dist/utils/buildTools/watchShaders.js +2 -2
  99. package/dist/utils/buildTools/watchShaders.js.map +1 -1
  100. package/dist/utils/renderTargetUtils.js +3 -3
  101. package/dist/utils/renderTargetUtils.js.map +1 -1
  102. package/dist/utils/shaderCodeUtils.d.ts +1 -42
  103. package/dist/utils/shaderCodeUtils.d.ts.map +1 -1
  104. package/dist/utils/shaderCodeUtils.js.map +1 -1
  105. package/package.json +1 -1
  106. package/readme.md +19 -15
  107. package/src/blocks/baseBlock.ts +9 -0
  108. package/src/blocks/customShaderBlock.ts +217 -0
  109. package/src/blocks/inputBlock.deserializer.ts +1 -1
  110. package/src/blocks/inputBlock.serializer.ts +4 -4
  111. package/src/blocks/shaderBlock.ts +36 -15
  112. package/src/blocks/textureOptions.ts +57 -0
  113. package/src/command/command.ts +2 -3
  114. package/src/command/commandBufferDebugger.ts +1 -1
  115. package/src/connection/connectionPointType.ts +11 -0
  116. package/src/editorUtils/editableInPropertyPage.ts +5 -0
  117. package/src/index.ts +2 -0
  118. package/src/optimization/smartFilterOptimizer.ts +11 -10
  119. package/src/runtime/renderTargetGenerator.ts +55 -20
  120. package/src/serialization/importCustomShaderBlockDefinition.ts +85 -0
  121. package/src/serialization/index.ts +1 -0
  122. package/src/serialization/serializedBlockDefinition.ts +7 -0
  123. package/src/serialization/serializedSmartFilter.ts +1 -1
  124. package/src/serialization/smartFilterDeserializer.ts +106 -52
  125. package/src/serialization/smartFilterSerializer.ts +11 -7
  126. package/src/serialization/v1/blockSerialization.types.ts +63 -0
  127. package/src/serialization/v1/defaultBlockSerializer.ts +2 -2
  128. package/src/serialization/v1/index.ts +1 -1
  129. package/src/serialization/v1/{serialization.types.ts → smartFilterSerialization.types.ts} +11 -10
  130. package/src/utils/buildTools/buildShaders.ts +1 -1
  131. package/src/utils/buildTools/convertShaderForHardcodedBlock.ts +149 -0
  132. package/src/utils/buildTools/shaderCode.types.ts +49 -0
  133. package/src/utils/buildTools/shaderConverter.ts +158 -178
  134. package/src/utils/buildTools/watchShaders.ts +2 -2
  135. package/src/utils/renderTargetUtils.ts +3 -3
  136. package/src/utils/shaderCodeUtils.ts +1 -50
  137. package/dist/serialization/v1/serialization.types.d.ts.map +0 -1
  138. package/dist/serialization/v1/serialization.types.js +0 -2
  139. package/dist/serialization/v1/serialization.types.js.map +0 -1
@@ -18,6 +18,7 @@ import {
18
18
  } from "../utils/shaderCodeUtils.js";
19
19
  import { DependencyGraph } from "./dependencyGraph.js";
20
20
  import { DisableableShaderBlock, BlockDisableStrategy } from "../blocks/disableableShaderBlock.js";
21
+ import { textureOptionsMatch, type OutputTextureOptions } from "../blocks/textureOptions.js";
21
22
 
22
23
  const showDebugData = false;
23
24
 
@@ -104,7 +105,7 @@ export class SmartFilterOptimizer {
104
105
  private _mainFunctionNameToCode: Map<string, string> = new Map();
105
106
  private _dependencyGraph: DependencyGraph<string> = new DependencyGraph<string>();
106
107
  private _vertexShaderCode: string | undefined;
107
- private _textureRatio: number | undefined;
108
+ private _currentOutputTextureOptions: OutputTextureOptions | undefined;
108
109
  private _forceUnoptimized: boolean = false;
109
110
 
110
111
  /**
@@ -224,7 +225,7 @@ export class SmartFilterOptimizer {
224
225
  this._mainFunctionNameToCode = new Map();
225
226
  this._dependencyGraph = new DependencyGraph();
226
227
  this._vertexShaderCode = undefined;
227
- this._textureRatio = undefined;
228
+ this._currentOutputTextureOptions = undefined;
228
229
  this._forceUnoptimized = false;
229
230
  }
230
231
 
@@ -266,7 +267,7 @@ export class SmartFilterOptimizer {
266
267
  s.type === "function" &&
267
268
  s.name === funcName &&
268
269
  s.owners[0] &&
269
- s.owners[0].getClassName() === block.getClassName()
270
+ s.owners[0].blockType === block.blockType
270
271
  );
271
272
 
272
273
  const newVarName = existingRemapped?.remappedName ?? decorateSymbol(this._makeSymbolUnique(funcName));
@@ -334,7 +335,7 @@ export class SmartFilterOptimizer {
334
335
  s.type === varDecl &&
335
336
  s.name === varName &&
336
337
  s.owners[0] &&
337
- s.owners[0].getClassName() === block.getClassName()
338
+ s.owners[0].blockType === block.blockType
338
339
  );
339
340
  if (existingRemapped && singleInstance) {
340
341
  newVarName = existingRemapped.remappedName;
@@ -424,7 +425,7 @@ export class SmartFilterOptimizer {
424
425
  return false;
425
426
  }
426
427
 
427
- if (block.textureRatio !== this._textureRatio) {
428
+ if (!textureOptionsMatch(block.outputTextureOptions, this._currentOutputTextureOptions)) {
428
429
  return false;
429
430
  }
430
431
  }
@@ -442,8 +443,8 @@ export class SmartFilterOptimizer {
442
443
  const block = outputConnectionPoint.ownerBlock;
443
444
 
444
445
  if (block instanceof ShaderBlock) {
445
- if (this._textureRatio === undefined) {
446
- this._textureRatio = block.textureRatio;
446
+ if (this._currentOutputTextureOptions === undefined) {
447
+ this._currentOutputTextureOptions = block.outputTextureOptions;
447
448
  }
448
449
 
449
450
  const shaderProgram = block.getShaderProgram();
@@ -590,7 +591,7 @@ export class SmartFilterOptimizer {
590
591
  return newShaderFuncName;
591
592
  }
592
593
 
593
- throw `Unhandled block type! className=${block.getClassName()}`;
594
+ throw `Unhandled block type! blockType=${block.blockType}`;
594
595
  }
595
596
 
596
597
  private _saveBlockStackState(): void {
@@ -712,8 +713,8 @@ export class SmartFilterOptimizer {
712
713
  },
713
714
  });
714
715
 
715
- if (this._textureRatio !== undefined) {
716
- optimizedBlock.textureRatio = this._textureRatio;
716
+ if (this._currentOutputTextureOptions !== undefined) {
717
+ optimizedBlock.outputTextureOptions = this._currentOutputTextureOptions;
717
718
  }
718
719
 
719
720
  optimizedBlock.setShaderBindings(Array.from(blockOwnerToShaderBinding.values()));
@@ -1,6 +1,7 @@
1
1
  import type { ThinTexture } from "@babylonjs/core/Materials/Textures/thinTexture";
2
2
  import type { Nullable } from "@babylonjs/core/types";
3
3
  import { ThinRenderTargetTexture } from "@babylonjs/core/Materials/Textures/thinRenderTargetTexture.js";
4
+ import type { RenderTargetCreationOptions } from "@babylonjs/core/Materials/Textures/textureCreationOptions";
4
5
 
5
6
  import type { BaseBlock } from "../blocks/baseBlock";
6
7
  import type { InitializationData, SmartFilter } from "../smartFilter";
@@ -8,6 +9,7 @@ import type { InternalSmartFilterRuntime } from "./smartFilterRuntime";
8
9
  import { ShaderBlock } from "../blocks/shaderBlock.js";
9
10
  import { createStrongRef } from "./strongRef.js";
10
11
  import { ConnectionPointType } from "../connection/connectionPointType.js";
12
+ import type { OutputTextureOptions } from "../blocks/textureOptions";
11
13
 
12
14
  /**
13
15
  * @internal
@@ -29,7 +31,9 @@ interface RefCountedTexture {
29
31
  */
30
32
  export class RenderTargetGenerator {
31
33
  private _optimize: boolean;
32
- private _renderTargetPool: Map<number, Set<RefCountedTexture>>;
34
+ private _renderTargetPool: Map<string, Set<RefCountedTexture>>;
35
+ private _textureOptionsHashCache = new Map<ShaderBlock, string>();
36
+
33
37
  private _numTargetsCreated;
34
38
 
35
39
  /**
@@ -63,11 +67,17 @@ export class RenderTargetGenerator {
63
67
  }
64
68
 
65
69
  let refCountedTexture: Nullable<RefCountedTexture> = null;
70
+ const textureOptionsHash = this._getTextureOptionsHash(block);
66
71
 
67
72
  // We assign a texture to the output of the block only if this is not the last block in the chain,
68
73
  // i.e. not the block connected to the smart output block (in which case the output of the block is to the canvas and not a texture).
69
74
  if (!block.output.endpoints.some((cp) => cp.ownerBlock === smartFilter.output.ownerBlock)) {
70
- refCountedTexture = this._getTexture(initializationData.runtime, block.textureRatio, smartFilter);
75
+ refCountedTexture = this._getTexture(
76
+ initializationData.runtime,
77
+ block.outputTextureOptions,
78
+ textureOptionsHash,
79
+ smartFilter
80
+ );
71
81
 
72
82
  if (!block.output.runtimeData) {
73
83
  const runtimeOutput = createStrongRef(refCountedTexture.texture);
@@ -98,17 +108,21 @@ export class RenderTargetGenerator {
98
108
  connectedBlock.output.runtimeData &&
99
109
  connectedBlock.output.runtimeData.value
100
110
  ) {
101
- this._releaseTexture(connectedBlock.output.runtimeData.value, connectedBlock.textureRatio);
111
+ this._releaseTexture(
112
+ connectedBlock.output.runtimeData.value,
113
+ this._getTextureOptionsHash(connectedBlock)
114
+ );
102
115
  }
103
116
  }
104
117
  }
105
118
  }
106
119
  );
107
120
  this._renderTargetPool.clear();
121
+ this._textureOptionsHashCache.clear();
108
122
  }
109
123
 
110
- private _findAvailableTexture(ratio: number): Nullable<RefCountedTexture> {
111
- const refCountedTextures = this._renderTargetPool.get(ratio);
124
+ private _findAvailableTexture(textureOptionsHash: string): Nullable<RefCountedTexture> {
125
+ const refCountedTextures = this._renderTargetPool.get(textureOptionsHash);
112
126
  if (!refCountedTextures) {
113
127
  return null;
114
128
  }
@@ -124,27 +138,28 @@ export class RenderTargetGenerator {
124
138
 
125
139
  private _getTexture(
126
140
  runtime: InternalSmartFilterRuntime,
127
- ratio: number,
141
+ textureOptions: OutputTextureOptions,
142
+ textureOptionsHash: string,
128
143
  smartFilter: SmartFilter
129
144
  ): RefCountedTexture {
130
145
  if (!this._optimize) {
131
146
  this._numTargetsCreated++;
132
147
  return {
133
- texture: this._createTexture(runtime, smartFilter, ratio),
148
+ texture: this._createTexture(runtime, smartFilter, textureOptions),
134
149
  refCount: 0,
135
150
  };
136
151
  }
137
152
 
138
- let refCountedTextures = this._renderTargetPool.get(ratio);
153
+ let refCountedTextures = this._renderTargetPool.get(textureOptionsHash);
139
154
  if (!refCountedTextures) {
140
155
  refCountedTextures = new Set();
141
- this._renderTargetPool.set(ratio, refCountedTextures);
156
+ this._renderTargetPool.set(textureOptionsHash, refCountedTextures);
142
157
  }
143
158
 
144
- let refCountedTexture = this._findAvailableTexture(ratio);
159
+ let refCountedTexture = this._findAvailableTexture(textureOptionsHash);
145
160
  if (!refCountedTexture) {
146
161
  refCountedTexture = {
147
- texture: this._createTexture(runtime, smartFilter, ratio),
162
+ texture: this._createTexture(runtime, smartFilter, textureOptions),
148
163
  refCount: 0,
149
164
  };
150
165
  refCountedTextures.add(refCountedTexture);
@@ -154,14 +169,16 @@ export class RenderTargetGenerator {
154
169
  return refCountedTexture;
155
170
  }
156
171
 
157
- private _releaseTexture(texture: ThinTexture, ratio: number) {
172
+ private _releaseTexture(texture: ThinTexture, textureOptionsHash: string) {
158
173
  if (!this._optimize) {
159
174
  return;
160
175
  }
161
176
 
162
- const refCountedTextures = this._renderTargetPool.get(ratio);
177
+ const refCountedTextures = this._renderTargetPool.get(textureOptionsHash);
163
178
  if (!refCountedTextures) {
164
- throw new Error(`_releaseTextureToPool: Trying to add a texture to a non existing pool ${ratio}!`);
179
+ throw new Error(
180
+ `_releaseTexture: Trying to release a texture from a non existing pool ${textureOptionsHash}!`
181
+ );
165
182
  }
166
183
 
167
184
  for (const refCountedTexture of refCountedTextures) {
@@ -171,29 +188,31 @@ export class RenderTargetGenerator {
171
188
  }
172
189
  }
173
190
 
174
- throw new Error(`_releaseTextureToPool: Can't find the texture in the pool ${ratio}!`);
191
+ throw new Error(`_releaseTexture: Can't find the texture in the pool ${textureOptionsHash}!`);
175
192
  }
176
193
 
177
194
  /**
178
195
  * Creates an offscreen texture to hold on the result of the block rendering.
179
196
  * @param runtime - The current runtime we create the texture for
180
197
  * @param smartFilter - The smart filter the texture is created for
181
- * @param ratio - The ratio of the texture to create compared to the final output
198
+ * @param textureOptions - The options to use to create the texture
182
199
  * @returns The render target texture
183
200
  */
184
201
  private _createTexture(
185
202
  runtime: InternalSmartFilterRuntime,
186
203
  smartFilter: SmartFilter,
187
- ratio: number
204
+ textureOptions: OutputTextureOptions
188
205
  ): ThinRenderTargetTexture {
189
206
  const engine = runtime.engine;
190
207
 
191
208
  // We are only rendering full screen post process without depth or stencil information
192
- const setup = {
209
+ const setup: RenderTargetCreationOptions = {
193
210
  generateDepthBuffer: false,
194
211
  generateStencilBuffer: false,
195
212
  generateMipMaps: false,
196
213
  samplingMode: 2, // Babylon Constants.TEXTURE_LINEAR_LINEAR,
214
+ format: textureOptions.format,
215
+ type: textureOptions.type,
197
216
  };
198
217
 
199
218
  // Get the smartFilter output size - either from the output block's renderTargetTexture or the engine's render size
@@ -207,8 +226,8 @@ export class RenderTargetGenerator {
207
226
  outputHeight = engine.getRenderHeight(true);
208
227
  }
209
228
  const size = {
210
- width: Math.floor(outputWidth * ratio),
211
- height: Math.floor(outputHeight * ratio),
229
+ width: Math.floor(outputWidth * textureOptions.ratio),
230
+ height: Math.floor(outputHeight * textureOptions.ratio),
212
231
  };
213
232
 
214
233
  // Creates frame buffers for effects
@@ -219,6 +238,22 @@ export class RenderTargetGenerator {
219
238
  finalRenderTarget.wrapU = 0;
220
239
  finalRenderTarget.wrapV = 0;
221
240
 
241
+ finalRenderTarget.anisotropicFilteringLevel = 1;
242
+
222
243
  return finalRenderTarget;
223
244
  }
245
+
246
+ /**
247
+ * Gets a textureOptionsHash for a block, using a cache to avoid recomputing it.
248
+ * @param block - The block to get the texture options hash for
249
+ * @returns The texture options hash for the block
250
+ */
251
+ private _getTextureOptionsHash(block: ShaderBlock): string {
252
+ let textureOptionsHash = this._textureOptionsHashCache.get(block);
253
+ if (textureOptionsHash === undefined) {
254
+ textureOptionsHash = JSON.stringify(block.outputTextureOptions);
255
+ this._textureOptionsHashCache.set(block, textureOptionsHash);
256
+ }
257
+ return textureOptionsHash;
258
+ }
224
259
  }
@@ -0,0 +1,85 @@
1
+ import { ConnectionPointType } from "../connection/connectionPointType.js";
2
+ import { hasGlslHeader, parseFragmentShader } from "../utils/buildTools/shaderConverter.js";
3
+ import type { SerializedBlockDefinition } from "./serializedBlockDefinition.js";
4
+ import type { SerializedInputConnectionPointV1 } from "./v1/blockSerialization.types.js";
5
+
6
+ /**
7
+ * Imports a serialized custom shader block definition. Supports importing a JSON string
8
+ * of an SerializedBlockDefinition object, or a glsl shader with the required annotations
9
+ * so it can be converted to a SerializedBlockDefinition object.
10
+ * See readme.md for more information.
11
+ * @param serializedBlockDefinition - The serialized block definition - either a SerializedBlockDefinition object in a JSON string, or an annotated glsl shader
12
+ * @returns The serialized block definition
13
+ */
14
+ export function importCustomShaderBlockDefinition(serializedBlockDefinition: string): SerializedBlockDefinition {
15
+ if (hasGlslHeader(serializedBlockDefinition)) {
16
+ return importAnnotatedGlsl(serializedBlockDefinition);
17
+ } else {
18
+ // Assume this is a serialized SerializedBlockDefinition object
19
+ return JSON.parse(serializedBlockDefinition);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Converts a fragment shader .glsl file to an SerializedBlockDefinition instance for use
25
+ * as a CustomShaderBlock. The .glsl file must contain certain annotations to be imported.
26
+ * See readme.md for more information.
27
+ * @param fragmentShader - The contents of the .glsl fragment shader file
28
+ * @returns The serialized block definition
29
+ */
30
+ function importAnnotatedGlsl(fragmentShader: string): SerializedBlockDefinition {
31
+ const fragmentShaderInfo = parseFragmentShader(fragmentShader);
32
+
33
+ if (!fragmentShaderInfo.blockType) {
34
+ throw new Error("blockType must be defined");
35
+ }
36
+
37
+ // Calculate the input connection points
38
+ const inputConnectionPoints: SerializedInputConnectionPointV1[] = [];
39
+ for (const uniform of fragmentShaderInfo.uniforms) {
40
+ // Convert to ConnectionPointType
41
+ let type: ConnectionPointType;
42
+ switch (uniform.type) {
43
+ case "float":
44
+ type = ConnectionPointType.Float;
45
+ break;
46
+ case "sampler2D":
47
+ type = ConnectionPointType.Texture;
48
+ break;
49
+ case "vec3":
50
+ type = ConnectionPointType.Color3;
51
+ break;
52
+ case "vec4":
53
+ type = ConnectionPointType.Color4;
54
+ break;
55
+ case "bool":
56
+ type = ConnectionPointType.Boolean;
57
+ break;
58
+ case "vec2":
59
+ type = ConnectionPointType.Vector2;
60
+ break;
61
+ default:
62
+ throw new Error(`Unsupported uniform type: '${uniform.type}'`);
63
+ }
64
+
65
+ // Add to input connection point list
66
+ const inputConnectionPoint: SerializedInputConnectionPointV1 = {
67
+ name: uniform.name,
68
+ type,
69
+ };
70
+ if (inputConnectionPoint.type !== ConnectionPointType.Texture && uniform.properties?.default !== undefined) {
71
+ inputConnectionPoint.defaultValue = uniform.properties.default;
72
+ }
73
+ inputConnectionPoints.push(inputConnectionPoint);
74
+ }
75
+
76
+ return {
77
+ formatVersion: 1,
78
+ blockType: fragmentShaderInfo.blockType,
79
+ shaderProgram: {
80
+ fragment: fragmentShaderInfo.shaderCode,
81
+ },
82
+ inputConnectionPoints,
83
+ disableOptimization: !!fragmentShaderInfo.disableOptimization,
84
+ };
85
+ }
@@ -2,3 +2,4 @@ export * from "./v1/index.js";
2
2
  export * from "./serializedSmartFilter.js";
3
3
  export * from "./smartFilterDeserializer.js";
4
4
  export * from "./smartFilterSerializer.js";
5
+ export * from "./serializedBlockDefinition.js";
@@ -0,0 +1,7 @@
1
+ import type { SerializedBlockDefinitionV1 } from "./v1/blockSerialization.types";
2
+
3
+ /**
4
+ * Type union of all versions of serialized SmartFilter block definitions
5
+ * A block definition is an object which is used to create a CustomShaderBlock instance.
6
+ */
7
+ export type SerializedBlockDefinition = SerializedBlockDefinitionV1;
@@ -1,4 +1,4 @@
1
- import type { SerializedSmartFilterV1 } from "./v1/serialization.types";
1
+ import type { SerializedSmartFilterV1 } from "./v1/smartFilterSerialization.types";
2
2
 
3
3
  /**
4
4
  * Type union of all versions of serialized SmartFilters
@@ -6,13 +6,22 @@ import { OutputBlock } from "../blocks/outputBlock.js";
6
6
  import type { ThinEngine } from "@babylonjs/core/Engines/thinEngine";
7
7
  import { InputBlock } from "../blocks/inputBlock.js";
8
8
  import type {
9
- DeserializeBlockV1,
10
9
  ISerializedBlockV1,
11
10
  ISerializedConnectionV1,
12
11
  OptionalBlockDeserializerV1,
13
12
  SerializedSmartFilterV1,
14
- } from "./v1/serialization.types";
13
+ } from "./v1/smartFilterSerialization.types";
15
14
  import { UniqueIdGenerator } from "../utils/uniqueIdGenerator.js";
15
+ import type { Nullable } from "@babylonjs/core/types";
16
+
17
+ /**
18
+ * A function that creates a block instance of the given class block type, or return null if it cannot.
19
+ */
20
+ export type BlockFactory = (
21
+ smartFilter: SmartFilter,
22
+ engine: ThinEngine,
23
+ serializedBlock: ISerializedBlockV1
24
+ ) => Promise<Nullable<BaseBlock>>;
16
25
 
17
26
  /**
18
27
  * Deserializes serialized SmartFilters. The caller passes in a map of block deserializers it wants to use,
@@ -20,39 +29,17 @@ import { UniqueIdGenerator } from "../utils/uniqueIdGenerator.js";
20
29
  * The deserializer supports versioned serialized SmartFilters.
21
30
  */
22
31
  export class SmartFilterDeserializer {
23
- private readonly _blockDeserializersV1: Map<string, DeserializeBlockV1> = new Map();
32
+ private readonly _blockFactory: BlockFactory;
33
+ private readonly _customInputBlockDeserializer?: OptionalBlockDeserializerV1;
24
34
 
25
35
  /**
26
36
  * Creates a new SmartFilterDeserializer
27
- * @param blockDeserializers - The map of block serializers to use, beyond those for the core blocks
37
+ * @param blockFactory - A function that creates a block of the given class name, or returns null if it cannot
28
38
  * @param customInputBlockDeserializer - An optional custom deserializer for InputBlocks - if supplied and it returns null, the default deserializer will be used
29
39
  */
30
- public constructor(
31
- blockDeserializers: Map<string, DeserializeBlockV1>,
32
- customInputBlockDeserializer?: OptionalBlockDeserializerV1
33
- ) {
34
- this._blockDeserializersV1 = blockDeserializers;
35
-
36
- this._blockDeserializersV1.set(
37
- InputBlock.ClassName,
38
- async (smartFilter: SmartFilter, serializedBlock: ISerializedBlockV1, engine: ThinEngine) => {
39
- if (customInputBlockDeserializer) {
40
- const customDeserializerResult = await customInputBlockDeserializer(
41
- smartFilter,
42
- serializedBlock,
43
- engine
44
- );
45
- if (customDeserializerResult !== null) {
46
- return customDeserializerResult;
47
- }
48
- }
49
- return inputBlockDeserializer(smartFilter, serializedBlock);
50
- }
51
- );
52
-
53
- this._blockDeserializersV1.set(OutputBlock.ClassName, (smartFilter: SmartFilter) =>
54
- Promise.resolve(smartFilter.output.ownerBlock)
55
- );
40
+ public constructor(blockFactory: BlockFactory, customInputBlockDeserializer?: OptionalBlockDeserializerV1) {
41
+ this._blockFactory = blockFactory;
42
+ this._customInputBlockDeserializer = customInputBlockDeserializer;
56
43
  }
57
44
 
58
45
  /**
@@ -63,7 +50,14 @@ export class SmartFilterDeserializer {
63
50
  */
64
51
  public async deserialize(engine: ThinEngine, smartFilterJson: any): Promise<SmartFilter> {
65
52
  const serializedSmartFilter: SerializedSmartFilter = smartFilterJson;
66
- switch (serializedSmartFilter.version) {
53
+
54
+ // Back-compat for the rename of version to formatVersion, didn't warrant a new version
55
+ if ((serializedSmartFilter as any).version && serializedSmartFilter.formatVersion === undefined) {
56
+ serializedSmartFilter.formatVersion = (serializedSmartFilter as any).version;
57
+ delete (serializedSmartFilter as any).version;
58
+ }
59
+
60
+ switch (serializedSmartFilter.formatVersion) {
67
61
  case 1:
68
62
  return await this._deserializeV1(engine, serializedSmartFilter);
69
63
  }
@@ -85,31 +79,28 @@ export class SmartFilterDeserializer {
85
79
 
86
80
  // Deserialize the blocks
87
81
  const blockDeserializationWork: Promise<void>[] = [];
82
+ const blockDefinitionsWhichCouldNotBeDeserialized: string[] = [];
88
83
  serializedSmartFilter.blocks.forEach((serializedBlock: ISerializedBlockV1) => {
89
- const blockDeserializer = this._blockDeserializersV1.get(serializedBlock.className);
90
- if (!blockDeserializer) {
91
- throw new Error(`No deserializer found for block type ${serializedBlock.className}`);
92
- }
93
84
  blockDeserializationWork.push(
94
- blockDeserializer(smartFilter, serializedBlock, engine).then((newBlock) => {
95
- // Deserializers are not responsible for setting the uniqueId or comments.
96
- // This is so they don't have to be passed into the constructors when programmatically creating
97
- // blocks, and so each deserializer doesn't have to remember to do it.
98
- newBlock.uniqueId = serializedBlock.uniqueId;
99
- newBlock.comments = serializedBlock.comments;
100
-
101
- // We need to ensure any uniqueIds generated in the future (e.g. a new block is added to the SmartFilter)
102
- // are higher than this one.
103
- UniqueIdGenerator.EnsureIdsGreaterThan(newBlock.uniqueId);
104
-
105
- // Save in the map
106
- blockIdMap.set(newBlock.uniqueId, newBlock);
107
- blockNameMap.set(newBlock.name, newBlock);
108
- })
85
+ this._deserializeBlockV1(
86
+ smartFilter,
87
+ serializedBlock,
88
+ engine,
89
+ blockDefinitionsWhichCouldNotBeDeserialized,
90
+ blockIdMap,
91
+ blockNameMap
92
+ )
109
93
  );
110
94
  });
111
95
  await Promise.all(blockDeserializationWork);
112
96
 
97
+ // If any block definitions could not be deserialized, throw an error
98
+ if (blockDefinitionsWhichCouldNotBeDeserialized.length > 0) {
99
+ throw new Error(
100
+ `Could not deserialize the following block definitions: ${blockDefinitionsWhichCouldNotBeDeserialized.join(", ")}`
101
+ );
102
+ }
103
+
113
104
  // Deserialize the connections
114
105
  serializedSmartFilter.connections.forEach((connection: ISerializedConnectionV1) => {
115
106
  // Find the source block and its connection point's connectTo function
@@ -121,7 +112,9 @@ export class SmartFilterDeserializer {
121
112
  if (!sourceBlock) {
122
113
  throw new Error(`Source block ${connection.outputBlock} not found`);
123
114
  }
124
- const sourceConnectionPoint = (sourceBlock as any)[connection.outputConnectionPoint];
115
+ const sourceConnectionPoint = sourceBlock.outputs.find(
116
+ (output) => output.name === connection.outputConnectionPoint
117
+ );
125
118
  if (!sourceConnectionPoint || typeof sourceConnectionPoint.connectTo !== "function") {
126
119
  throw new Error(
127
120
  `Block ${connection.outputBlock} does not have an connection point named ${connection.outputConnectionPoint}`
@@ -137,7 +130,10 @@ export class SmartFilterDeserializer {
137
130
  if (!targetBlock) {
138
131
  throw new Error(`Target block ${connection.inputBlock} not found`);
139
132
  }
140
- const targetConnectionPoint = (targetBlock as any)[connection.inputConnectionPoint];
133
+
134
+ const targetConnectionPoint = targetBlock.inputs.find(
135
+ (input) => input.name === connection.inputConnectionPoint
136
+ );
141
137
  if (!targetConnectionPoint || typeof targetConnectionPoint !== "object") {
142
138
  throw new Error(
143
139
  `Block ${connection.inputBlock} does not have a connection point named ${connection.inputConnectionPoint}`
@@ -150,4 +146,62 @@ export class SmartFilterDeserializer {
150
146
 
151
147
  return smartFilter;
152
148
  }
149
+
150
+ private async _deserializeBlockV1(
151
+ smartFilter: SmartFilter,
152
+ serializedBlock: ISerializedBlockV1,
153
+ engine: ThinEngine,
154
+ blockTypesWhichCouldNotBeDeserialized: string[],
155
+ blockIdMap: Map<number, BaseBlock>,
156
+ blockNameMap: Map<string, BaseBlock>
157
+ ): Promise<void> {
158
+ let newBlock: Nullable<BaseBlock> = null;
159
+
160
+ // Back compat for early Smart Filter V1 serialization where the blockType was stored in className
161
+ // Not worth creating a new version for this, as it's only used in the deserializer
162
+ if ((serializedBlock as any).className && !serializedBlock.blockType) {
163
+ serializedBlock.blockType = (serializedBlock as any).className;
164
+ }
165
+
166
+ // Get the instance of the block
167
+ switch (serializedBlock.blockType) {
168
+ case InputBlock.ClassName:
169
+ {
170
+ if (this._customInputBlockDeserializer) {
171
+ newBlock = await this._customInputBlockDeserializer(smartFilter, serializedBlock, engine);
172
+ }
173
+ if (newBlock === null) {
174
+ newBlock = inputBlockDeserializer(smartFilter, serializedBlock);
175
+ }
176
+ }
177
+ break;
178
+ case OutputBlock.ClassName:
179
+ {
180
+ newBlock = smartFilter.output.ownerBlock;
181
+ }
182
+ break;
183
+ default: {
184
+ // If it's not an input or output block, use the provided block factory
185
+ newBlock = await this._blockFactory(smartFilter, engine, serializedBlock);
186
+ if (!newBlock) {
187
+ blockTypesWhichCouldNotBeDeserialized.push(serializedBlock.blockType);
188
+ return;
189
+ }
190
+ }
191
+ }
192
+
193
+ // Deserializers are not responsible for setting the uniqueId or comments.
194
+ // This is so they don't have to be passed into the constructors when programmatically creating
195
+ // blocks, and so each deserializer doesn't have to remember to do it.
196
+ newBlock.uniqueId = serializedBlock.uniqueId;
197
+ newBlock.comments = serializedBlock.comments;
198
+
199
+ // We need to ensure any uniqueIds generated in the future (e.g. a new block is added to the SmartFilter)
200
+ // are higher than this one.
201
+ UniqueIdGenerator.EnsureIdsGreaterThan(newBlock.uniqueId);
202
+
203
+ // Save in the map
204
+ blockIdMap.set(newBlock.uniqueId, newBlock);
205
+ blockNameMap.set(newBlock.name, newBlock);
206
+ }
153
207
  }
@@ -10,7 +10,8 @@ import type {
10
10
  ISerializedConnectionV1,
11
11
  SerializeBlockV1,
12
12
  SerializedSmartFilterV1,
13
- } from "./v1/serialization.types";
13
+ } from "./v1/smartFilterSerialization.types";
14
+ import { CustomShaderBlock } from "../blocks/customShaderBlock.js";
14
15
 
15
16
  /**
16
17
  * Determines if two serialized connection points are equivalent to each other
@@ -37,17 +38,17 @@ export class SmartFilterSerializer {
37
38
 
38
39
  /**
39
40
  * Creates a new SmartFilterSerializer
40
- * @param blocksUsingDefaultSerialization - A list of the classNames of blocks which can use default serialization (they only have ConnectionPoint properties and no constructor parameters)
41
+ * @param blocksUsingDefaultSerialization - A list of the blockType of blocks which can use default serialization (they only have ConnectionPoint properties and no constructor parameters)
41
42
  * @param additionalBlockSerializers - An array of block serializers to use, beyond those for the core blocks
42
43
  */
43
44
  public constructor(blocksUsingDefaultSerialization: string[], additionalBlockSerializers: IBlockSerializerV1[]) {
44
- this._blockSerializers.set(inputBlockSerializer.className, inputBlockSerializer.serialize);
45
+ this._blockSerializers.set(inputBlockSerializer.blockType, inputBlockSerializer.serialize);
45
46
  this._blockSerializers.set(OutputBlock.ClassName, defaultBlockSerializer);
46
47
  blocksUsingDefaultSerialization.forEach((block) => {
47
48
  this._blockSerializers.set(block, defaultBlockSerializer);
48
49
  });
49
50
  additionalBlockSerializers.forEach((serializer) =>
50
- this._blockSerializers.set(serializer.className, serializer.serialize)
51
+ this._blockSerializers.set(serializer.blockType, serializer.serialize)
51
52
  );
52
53
  }
53
54
 
@@ -61,9 +62,12 @@ export class SmartFilterSerializer {
61
62
 
62
63
  const blocks = smartFilter.attachedBlocks.map((block: BaseBlock) => {
63
64
  // Serialize the block itself
64
- const serializeFn = this._blockSerializers.get(block.getClassName());
65
+ const serializeFn =
66
+ block.getClassName() === CustomShaderBlock.ClassName
67
+ ? defaultBlockSerializer
68
+ : this._blockSerializers.get(block.blockType);
65
69
  if (!serializeFn) {
66
- throw new Error(`No serializer was provided for a block of type ${block.getClassName()}`);
70
+ throw new Error(`No serializer was provided for a block of type ${block.blockType}`);
67
71
  }
68
72
  const serializedBlock: ISerializedBlockV1 = serializeFn(block);
69
73
 
@@ -102,7 +106,7 @@ export class SmartFilterSerializer {
102
106
  });
103
107
 
104
108
  return {
105
- version: 1,
109
+ formatVersion: 1,
106
110
  name: smartFilter.name,
107
111
  comments: smartFilter.comments,
108
112
  editorData: smartFilter.editorData,