@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,150 @@
1
+ import { InputBlockBase, type InputBlock } from "./inputBlock.js";
2
+ import type { BaseBlock } from "./baseBlock.js";
3
+ import { ConnectionPointType } from "../connection/connectionPointType.js";
4
+ import type {
5
+ BooleanInputBlockData,
6
+ Color3InputBlockData,
7
+ Color4InputBlockData,
8
+ FloatInputBlockData,
9
+ SerializedInputBlockData,
10
+ TextureInputBlockData,
11
+ Vector2InputBlockData,
12
+ } from "./inputBlock.serialization.types.js";
13
+ import type { IBlockSerializerV1 } from "../serialization/v1/smartFilterSerialization.types.js";
14
+
15
+ /**
16
+ * Determines which generic type of InputBlock we are trying to serialize and calls the appropriate function
17
+ * to serialize the specifics for that type of InputBlock
18
+ * @param inputBlock - The InputBlock to serialize
19
+ * @returns Serialized data for the InputBlock
20
+ */
21
+ function SerializeInputBlockData(inputBlock: InputBlockBase): SerializedInputBlockData {
22
+ switch (inputBlock.type) {
23
+ case ConnectionPointType.Texture:
24
+ return SerializeTextureInputBlock(inputBlock as InputBlock<ConnectionPointType.Texture>);
25
+ case ConnectionPointType.Boolean:
26
+ return SerializeBooleanInputBlock(inputBlock as InputBlock<ConnectionPointType.Boolean>);
27
+ case ConnectionPointType.Float:
28
+ return SerializeFloatInputBlock(inputBlock as InputBlock<ConnectionPointType.Float>);
29
+ case ConnectionPointType.Color3:
30
+ return SerializeColor3InputBlock(inputBlock as InputBlock<ConnectionPointType.Color3>);
31
+ case ConnectionPointType.Color4:
32
+ return SerializeColor4InputBlock(inputBlock as InputBlock<ConnectionPointType.Color4>);
33
+ case ConnectionPointType.Vector2:
34
+ return SerializeVector2InputBlock(inputBlock as InputBlock<ConnectionPointType.Vector2>);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Generates the serialized data for a Texture InputBlock
40
+ * @param inputBlock - The Texture InputBlock to serialize
41
+ * @returns The serialized data for the InputBlock
42
+ */
43
+ function SerializeTextureInputBlock(inputBlock: InputBlock<ConnectionPointType.Texture>): TextureInputBlockData {
44
+ const internalTexture = inputBlock.runtimeValue.value?.getInternalTexture();
45
+ const forcedExtension = internalTexture?._extension ?? null;
46
+
47
+ let url = internalTexture?.url ?? null;
48
+ if (url === "" || !url) {
49
+ url = inputBlock.editorData?.url ?? null;
50
+ }
51
+
52
+ return {
53
+ inputType: ConnectionPointType.Texture,
54
+ url,
55
+ urlTypeHint: inputBlock.editorData?.urlTypeHint ?? null,
56
+ flipY: internalTexture?.invertY ?? null,
57
+ anisotropicFilteringLevel: internalTexture?.anisotropicFilteringLevel ?? null,
58
+ forcedExtension: forcedExtension !== "" ? forcedExtension : null,
59
+ appMetadata: inputBlock.appMetadata,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Generates the serialized data for a Boolean InputBlock
65
+ * @param inputBlock - The Boolean InputBlock to serialize
66
+ * @returns The serialized data for the InputBlock
67
+ */
68
+ function SerializeBooleanInputBlock(inputBlock: InputBlock<ConnectionPointType.Boolean>): BooleanInputBlockData {
69
+ return {
70
+ inputType: ConnectionPointType.Boolean,
71
+ value: inputBlock.runtimeValue.value,
72
+ appMetadata: inputBlock.appMetadata,
73
+ };
74
+ }
75
+
76
+ /**
77
+ * Generates the serialized data for a Float InputBlock
78
+ * @param inputBlock - The Float InputBlock to serialize
79
+ * @returns The serialized data for the InputBlock
80
+ */
81
+ function SerializeFloatInputBlock(inputBlock: InputBlock<ConnectionPointType.Float>): FloatInputBlockData {
82
+ return {
83
+ inputType: ConnectionPointType.Float,
84
+ value: inputBlock.runtimeValue.value,
85
+ animationType: inputBlock.editorData?.animationType ?? null,
86
+ valueDeltaPerMs: inputBlock.editorData?.valueDeltaPerMs ?? null,
87
+ min: inputBlock.editorData?.min ?? null,
88
+ max: inputBlock.editorData?.max ?? null,
89
+ appMetadata: inputBlock.appMetadata,
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Generates the serialized data for a Color3 InputBlock
95
+ * @param inputBlock - The Color3 InputBlock to serialize
96
+ * @returns The serialized data for the InputBlock
97
+ */
98
+ function SerializeColor3InputBlock(inputBlock: InputBlock<ConnectionPointType.Color3>): Color3InputBlockData {
99
+ return {
100
+ inputType: ConnectionPointType.Color3,
101
+ value: inputBlock.runtimeValue.value,
102
+ appMetadata: inputBlock.appMetadata,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Generates the serialized data for a Color4 InputBlock
108
+ * @param inputBlock - The Color4 InputBlock to serialize
109
+ * @returns The serialized data for the InputBlock
110
+ */
111
+ function SerializeColor4InputBlock(inputBlock: InputBlock<ConnectionPointType.Color4>): Color4InputBlockData {
112
+ return {
113
+ inputType: ConnectionPointType.Color4,
114
+ value: inputBlock.runtimeValue.value,
115
+ appMetadata: inputBlock.appMetadata,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Generates the serialized data for a Vector2 InputBlock
121
+ * @param inputBlock - The Vector2 InputBlock to serialize
122
+ * @returns The serialized data for the InputBlock
123
+ */
124
+ function SerializeVector2InputBlock(inputBlock: InputBlock<ConnectionPointType.Vector2>): Vector2InputBlockData {
125
+ return {
126
+ inputType: ConnectionPointType.Vector2,
127
+ value: inputBlock.runtimeValue.value,
128
+ appMetadata: inputBlock.appMetadata,
129
+ };
130
+ }
131
+
132
+ /**
133
+ * The V1 serializer for an InputBlock
134
+ */
135
+ export const InputBlockSerializer: IBlockSerializerV1 = {
136
+ blockType: InputBlockBase.ClassName,
137
+ serialize: (block: BaseBlock) => {
138
+ if (block.blockType !== InputBlockBase.ClassName) {
139
+ throw new Error("Was asked to serialize an unrecognized block type");
140
+ }
141
+ return {
142
+ name: block.name,
143
+ uniqueId: block.uniqueId,
144
+ blockType: InputBlockBase.ClassName,
145
+ namespace: null,
146
+ comments: block.comments,
147
+ data: SerializeInputBlockData(block as unknown as InputBlockBase),
148
+ };
149
+ },
150
+ };
@@ -0,0 +1,181 @@
1
+ import type { SmartFilter } from "../smartFilter.js";
2
+ import type { ConnectionPointValue } from "../connection/connectionPointType.js";
3
+ import type { RuntimeData } from "../connection/connectionPoint.js";
4
+ import type { ConnectionPointWithDefault } from "../connection/connectionPointWithDefault.js";
5
+ import type { DisableableShaderBlock } from "./disableableShaderBlock.js";
6
+ import { BaseBlock } from "./baseBlock.js";
7
+ import { CreateStrongRef } from "../runtime/strongRef.js";
8
+ import { ConnectionPointType } from "../connection/connectionPointType.js";
9
+ import type { Nullable } from "core/types.js";
10
+
11
+ /**
12
+ * Type predicate to check if value is a strong ref or a direct value
13
+ * @param value - The value to check
14
+ * @returns true if the value is a strong ref, otherwise false
15
+ */
16
+ function IsRuntimeData<U extends ConnectionPointType>(value: ConnectionPointValue<U> | RuntimeData<U>): value is RuntimeData<U> {
17
+ return value && (value as RuntimeData<ConnectionPointType>).value !== undefined;
18
+ }
19
+
20
+ /**
21
+ * Predicate to check if a block is a texture input block.
22
+ * @param block - The block to check
23
+ * @returns true if the block is a texture input block, otherwise false
24
+ */
25
+ export function IsTextureInputBlock(block: BaseBlock): block is InputBlock<ConnectionPointType.Texture> {
26
+ return (block as InputBlock<ConnectionPointType.Texture>).type === ConnectionPointType.Texture;
27
+ }
28
+
29
+ /**
30
+ * Predicate to check if a block is a disableable block.
31
+ * @param block - The block to check
32
+ * @returns true if the block is a disableable block, otherwise false
33
+ */
34
+ export function IsDisableableShaderBlock(block: BaseBlock): block is DisableableShaderBlock {
35
+ return (block as DisableableShaderBlock).disabled !== undefined;
36
+ }
37
+
38
+ /**
39
+ * This base class exists to provide a type that the serializer can use to represent
40
+ * any InputBlock without knowing the exact type it is.
41
+ */
42
+ export abstract class InputBlockBase extends BaseBlock {
43
+ /**
44
+ * The class name of the block.
45
+ */
46
+ public static override ClassName = "InputBlock";
47
+
48
+ /**
49
+ * The type of the input.
50
+ */
51
+ public abstract readonly type: ConnectionPointType;
52
+ }
53
+
54
+ /**
55
+ * Describes the editor data that can be stored with an InputBlock of a given type.
56
+ */
57
+ export type InputBlockEditorData<T extends ConnectionPointType> = T extends ConnectionPointType.Texture
58
+ ? {
59
+ /**
60
+ * The URL of the texture, or default if null.
61
+ */
62
+ url: Nullable<string>;
63
+
64
+ /**
65
+ * If supplied, gives a hint as to which type of texture the URL points to.
66
+ * Default is assumed to be "image"
67
+ */
68
+ urlTypeHint: Nullable<"image" | "video">;
69
+
70
+ /**
71
+ * The anisotropic filtering level of the texture, or default if null.
72
+ */
73
+ anisotropicFilteringLevel: Nullable<number>;
74
+
75
+ /**
76
+ * Whether the Y axis should be flipped, or default if null.
77
+ */
78
+ flipY: Nullable<boolean>;
79
+
80
+ /**
81
+ * The file extension to use, or default if null.
82
+ */
83
+ forcedExtension: Nullable<string>;
84
+ }
85
+ : T extends ConnectionPointType.Float
86
+ ? {
87
+ /**
88
+ * If supplied, how this should be animated by the editor. Will not affect runtime behavior.
89
+ */
90
+ animationType: Nullable<"time">;
91
+
92
+ /**
93
+ * If supplied, the amount to change the value per millisecond when animating.
94
+ */
95
+ valueDeltaPerMs: Nullable<number>;
96
+
97
+ /**
98
+ * The minimum value of the float, used for slider control.
99
+ */
100
+ min: Nullable<number>;
101
+
102
+ /**
103
+ * The maximum value of the float, used for slider control.
104
+ */
105
+ max: Nullable<number>;
106
+ }
107
+ : {};
108
+
109
+ /**
110
+ * This represents any inputs used in the graph.
111
+ *
112
+ * This is used to provide a way to connect the graph to the outside world.
113
+ *
114
+ * The value is dynamically set by the user.
115
+ */
116
+ export class InputBlock<U extends ConnectionPointType, V = unknown> extends InputBlockBase {
117
+ /**
118
+ * The output connection point of the block.
119
+ */
120
+ public readonly output: ConnectionPointWithDefault<U>;
121
+
122
+ /**
123
+ * The type of the input.
124
+ */
125
+ public readonly type: U;
126
+
127
+ /**
128
+ * Data used by the Editor to store options required for instantiating the block in the Editor.
129
+ */
130
+ public editorData: Nullable<InputBlockEditorData<U>> = null;
131
+
132
+ /**
133
+ * Metadata the hosting app wants to track for this input. For example, a hint for what data to
134
+ * assign to this input, or hints about how to draw dynamic UI to allow users to control this value.
135
+ */
136
+ public appMetadata: Nullable<V> = null;
137
+
138
+ /**
139
+ * Gets the current value of the input.
140
+ */
141
+ public get runtimeValue(): RuntimeData<U> {
142
+ return this.output.runtimeData;
143
+ }
144
+
145
+ /**
146
+ * Sets the current value of the input.
147
+ */
148
+ public set runtimeValue(value: RuntimeData<U>) {
149
+ this.output.runtimeData = value;
150
+ }
151
+
152
+ /**
153
+ * Creates a new InputBlock.
154
+ * @param smartFilter - The smart filter to add the block to
155
+ * @param name - The friendly name of the block
156
+ * @param type - The type of the input
157
+ * @param initialValue - The initial value of the input
158
+ * @remarks the initial value can either be a strong reference or a value
159
+ */
160
+ constructor(smartFilter: SmartFilter, name: string, type: U, initialValue: ConnectionPointValue<U> | RuntimeData<U>) {
161
+ super(smartFilter, name);
162
+ this.type = type;
163
+
164
+ // Creates the output connection point
165
+ this.output = this._registerOutputWithDefault("output", type, IsRuntimeData(initialValue) ? initialValue : CreateStrongRef(initialValue));
166
+
167
+ // Creates a strong reference to the initial value in case a reference has not been provided
168
+ if (IsRuntimeData(initialValue)) {
169
+ this.runtimeValue = initialValue;
170
+ } else {
171
+ this.runtimeValue = CreateStrongRef(initialValue);
172
+ }
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Unionized type of all the possible input types.
178
+ */
179
+ export type AnyInputBlock = {
180
+ [T in keyof typeof ConnectionPointType]: InputBlock<(typeof ConnectionPointType)[T]>;
181
+ }[keyof typeof ConnectionPointType];
@@ -0,0 +1,144 @@
1
+ import type { InitializationData, SmartFilter } from "../smartFilter.js";
2
+ import { ConnectionPointType } from "../connection/connectionPointType.js";
3
+ import { BaseBlock } from "./baseBlock.js";
4
+ import { ShaderBinding, ShaderRuntime } from "../runtime/shaderRuntime.js";
5
+ import type { Nullable } from "core/types.js";
6
+ import type { RenderTargetWrapper } from "core/Engines/renderTargetWrapper.js";
7
+ import { RegisterFinalRenderCommand } from "../utils/renderTargetUtils.js";
8
+ import type { RuntimeData } from "../connection/connectionPoint.js";
9
+ import type { Effect } from "core/Materials/effect.js";
10
+ import type { ShaderProgram } from "../utils/shaderCodeUtils.js";
11
+
12
+ /**
13
+ * The shader program for the block.
14
+ */
15
+ const BlockShaderProgram: ShaderProgram = {
16
+ vertex: undefined,
17
+ fragment: {
18
+ uniform: `
19
+ uniform sampler2D _input_; // main`,
20
+ mainInputTexture: "_input_",
21
+ mainFunctionName: "_copy_",
22
+ functions: [
23
+ {
24
+ name: "_copy_",
25
+ code: `
26
+ vec4 _copy_(vec2 vUV) {
27
+ return texture2D(_input_, vUV);
28
+ }
29
+
30
+ `,
31
+ },
32
+ ],
33
+ },
34
+ };
35
+
36
+ /**
37
+ * The uniform names for this shader, to be used in the shader binding so
38
+ * that the names are always in sync.
39
+ */
40
+ const Uniforms = {
41
+ input: "input",
42
+ };
43
+
44
+ /**
45
+ * The output block of a smart filter.
46
+ *
47
+ * Only the smart filter will internally create and host the output block.
48
+ * It should not be exported through the main index.ts module.
49
+ */
50
+ export class OutputBlock extends BaseBlock {
51
+ /**
52
+ * The class name of the block.
53
+ */
54
+ public static override ClassName = "OutputBlock";
55
+
56
+ /**
57
+ * Input connection point of the output block.
58
+ * This takes a texture as input.
59
+ */
60
+ public readonly input = this._registerInput("input", ConnectionPointType.Texture);
61
+
62
+ /**
63
+ * If supplied, the Smart Filter will render into this RenderTargetWrapper. Otherwise, it renders
64
+ * into the the canvas or WebGL context the ThinEngine is using for rendering.
65
+ */
66
+ public renderTargetWrapper: Nullable<RenderTargetWrapper> = null;
67
+
68
+ /**
69
+ * Create a new output block.
70
+ * @param smartFilter - The smart filter this block belongs to
71
+ */
72
+ constructor(smartFilter: SmartFilter) {
73
+ super(smartFilter, "output");
74
+ }
75
+
76
+ /**
77
+ * Prepares all blocks for runtime by traversing the graph.
78
+ */
79
+ public override prepareForRuntime(): void {
80
+ this.visit({}, (block: BaseBlock, _extraData: object) => {
81
+ if (block !== this) {
82
+ block.prepareForRuntime();
83
+ }
84
+ });
85
+ }
86
+
87
+ /**
88
+ * Propagates the runtime data for all graph blocks.
89
+ */
90
+ public override propagateRuntimeData(): void {
91
+ this.visit({}, (block: BaseBlock, _extraData: object) => {
92
+ if (block !== this) {
93
+ block.propagateRuntimeData();
94
+ }
95
+ });
96
+ this._confirmRuntimeDataSupplied(this.input);
97
+ }
98
+
99
+ /**
100
+ * Generates the commands needed to execute the block at runtime and gathers promises for initialization work
101
+ * @param initializationData - The initialization data to use
102
+ * @param finalOutput - Defines if the block is the final output of the smart filter
103
+ */
104
+ public override generateCommandsAndGatherInitPromises(initializationData: InitializationData, finalOutput: boolean): void {
105
+ // In the case that this OutputBlock is directly connected to a texture InputBlock, we must
106
+ // use a shader to copy the texture to the render target texture.
107
+ if (this.input.connectedTo?.ownerBlock.isInput && this.input.runtimeData) {
108
+ const runtime = initializationData.runtime;
109
+
110
+ const shaderBlockRuntime = new ShaderRuntime(runtime.effectRenderer, BlockShaderProgram, new OutputShaderBinding(this.input.runtimeData));
111
+ initializationData.initializationPromises.push(shaderBlockRuntime.onReadyAsync);
112
+ runtime.registerResource(shaderBlockRuntime);
113
+
114
+ RegisterFinalRenderCommand(this, runtime, this, shaderBlockRuntime);
115
+
116
+ super.generateCommandsAndGatherInitPromises(initializationData, finalOutput);
117
+ }
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Shader binding to use when the OutputBlock is directly connected to a texture InputBlock.
123
+ */
124
+ class OutputShaderBinding extends ShaderBinding {
125
+ private readonly _inputTexture: RuntimeData<ConnectionPointType.Texture>;
126
+
127
+ /**
128
+ * Creates a new shader binding instance.
129
+ * @param inputTexture - defines the input texture to copy
130
+ */
131
+ constructor(inputTexture: RuntimeData<ConnectionPointType.Texture>) {
132
+ super();
133
+ this._inputTexture = inputTexture;
134
+ }
135
+
136
+ /**
137
+ * Binds all the required data to the shader when rendering.
138
+ * @param effect - defines the effect to bind the data to
139
+ * @internal
140
+ */
141
+ public override bind(effect: Effect): void {
142
+ effect.setTexture(this.getRemappedName(Uniforms.input), this._inputTexture.value);
143
+ }
144
+ }
@@ -0,0 +1,156 @@
1
+ import type { ThinRenderTargetTexture } from "core/Materials/Textures/thinRenderTargetTexture.js";
2
+ import "core/Engines/Extensions/engine.renderTarget.js";
3
+
4
+ import type { InitializationData, SmartFilter } from "../smartFilter.js";
5
+ import type { ShaderProgram } from "../utils/shaderCodeUtils.js";
6
+ import type { ShaderBinding } from "../runtime/shaderRuntime.js";
7
+ import type { ConnectionPoint } from "../connection/connectionPoint.js";
8
+ import { ShaderRuntime } from "../runtime/shaderRuntime.js";
9
+ import { ConnectionPointType } from "../connection/connectionPointType.js";
10
+ import { CreateCommand } from "../command/command.js";
11
+ import { UndecorateSymbol } from "../utils/shaderCodeUtils.js";
12
+ import { RegisterFinalRenderCommand } from "../utils/renderTargetUtils.js";
13
+ import { BaseBlock } from "./baseBlock.js";
14
+ import { TextureFormat, TextureType, type OutputTextureOptions } from "./textureOptions.js";
15
+ import { EditableInPropertyPage, PropertyTypeForEdition } from "../editorUtils/editableInPropertyPage.js";
16
+ import type { Nullable } from "core/types.js";
17
+
18
+ const OutputTexturePropertiesGroupName = "OUTPUT TEXTURE PROPERTIES";
19
+
20
+ /**
21
+ * This is the base class for all shader blocks.
22
+ *
23
+ * It contains the redundant part of wrapping a shader for a full screen pass.
24
+ *
25
+ * The only required function to implement is the bind function.
26
+ */
27
+ export abstract class ShaderBlock extends BaseBlock {
28
+ /**
29
+ * The class name of the block.
30
+ */
31
+ public static override ClassName = "ShaderBlock";
32
+
33
+ /**
34
+ * Get the class instance that binds all the required data to the shader (effect) when rendering.
35
+ * It should throw an error if required inputs are missing.
36
+ * @returns The class instance that binds the data to the effect
37
+ */
38
+ public abstract getShaderBinding(): ShaderBinding;
39
+
40
+ /**
41
+ * The shader program (vertex and fragment code) to use to render the block
42
+ */
43
+ public static ShaderCode: ShaderProgram;
44
+
45
+ /**
46
+ * The output connection point of the block.
47
+ */
48
+ public readonly output = this._registerOutput("output", ConnectionPointType.Texture);
49
+
50
+ /**
51
+ * The options used when creating the texture this block outputs to
52
+ */
53
+ @EditableInPropertyPage("Ratio", PropertyTypeForEdition.Float, OutputTexturePropertiesGroupName, {
54
+ min: 0.1,
55
+ max: 10.0,
56
+ notifiers: { rebuild: true },
57
+ subPropertyName: "ratio",
58
+ })
59
+ @EditableInPropertyPage("Format", PropertyTypeForEdition.List, OutputTexturePropertiesGroupName, {
60
+ notifiers: { rebuild: true },
61
+ subPropertyName: "format",
62
+ options: [
63
+ { label: "R", value: TextureFormat.R },
64
+ { label: "RG", value: TextureFormat.RG },
65
+ { label: "RGBA", value: TextureFormat.RGBA },
66
+ ],
67
+ })
68
+ @EditableInPropertyPage("Type", PropertyTypeForEdition.List, OutputTexturePropertiesGroupName, {
69
+ notifiers: { rebuild: true },
70
+ subPropertyName: "type",
71
+ options: [
72
+ { label: "UByte", value: TextureType.UNSIGNED_BYTE },
73
+ { label: "Float", value: TextureType.FLOAT },
74
+ { label: "Half Float", value: TextureType.HALF_FLOAT },
75
+ ],
76
+ })
77
+ public outputTextureOptions: OutputTextureOptions = {
78
+ ratio: 1,
79
+ format: TextureFormat.RGBA,
80
+ type: TextureType.UNSIGNED_BYTE,
81
+ };
82
+
83
+ /**
84
+ * Disconnects the block from the graph.
85
+ * @param disconnectedConnections - Stores the connections that have been broken in the process. You can reconnect them later if needed.
86
+ */
87
+ public override disconnectFromGraph(disconnectedConnections?: [ConnectionPoint, ConnectionPoint][]): void {
88
+ const input = this._getConnectionForMainInputTexture();
89
+
90
+ for (const endpoint of this.output.endpoints) {
91
+ disconnectedConnections?.push([endpoint, this.output]);
92
+ input.connectTo(endpoint);
93
+ }
94
+ }
95
+
96
+ protected _getConnectionForMainInputTexture(): ConnectionPoint {
97
+ const mainInputTextureName = this.getShaderProgram().fragment.mainInputTexture;
98
+ if (!mainInputTextureName) {
99
+ throw `The block named "${this.name}" does not have a main input texture defined!`;
100
+ }
101
+
102
+ const mainInputTexture = this.findInput(UndecorateSymbol(mainInputTextureName));
103
+ if (!mainInputTexture || !mainInputTexture.connectedTo) {
104
+ throw `The main input texture "${mainInputTextureName}" of block named "${this.name}" is not connected!`;
105
+ }
106
+
107
+ return mainInputTexture.connectedTo;
108
+ }
109
+
110
+ /**
111
+ * Instantiates a new block.
112
+ * @param smartFilter - Defines the smart filter the block belongs to
113
+ * @param name - Defines the name of the block
114
+ * @param disableOptimization - Defines if the block should not be optimized (default: false)
115
+ */
116
+ constructor(smartFilter: SmartFilter, name: string, disableOptimization = false) {
117
+ super(smartFilter, name, disableOptimization);
118
+ }
119
+
120
+ /**
121
+ * Gets the shader program to use to render the block.
122
+ * @returns The shader program to use to render the block
123
+ */
124
+ public getShaderProgram() {
125
+ return (this.constructor as typeof ShaderBlock).ShaderCode;
126
+ }
127
+
128
+ /**
129
+ * Generates the commands needed to execute the block at runtime and gathers promises for initialization work
130
+ * @param initializationData - The initialization data to use
131
+ * @param finalOutput - Defines if the block is the final output of the smart filter
132
+ */
133
+ public override generateCommandsAndGatherInitPromises(initializationData: InitializationData, finalOutput: boolean): void {
134
+ const runtime = initializationData.runtime;
135
+ const shaderBlockRuntime = new ShaderRuntime(runtime.effectRenderer, this.getShaderProgram(), this.getShaderBinding());
136
+ initializationData.initializationPromises.push(shaderBlockRuntime.onReadyAsync);
137
+ runtime.registerResource(shaderBlockRuntime);
138
+
139
+ if (finalOutput) {
140
+ RegisterFinalRenderCommand(initializationData.outputBlock, runtime, this, shaderBlockRuntime);
141
+ } else {
142
+ const renderTargetTexture = this.output.runtimeData?.value as Nullable<ThinRenderTargetTexture>;
143
+ if (!renderTargetTexture) {
144
+ throw new Error(`${this.blockType} did not have a render target texture.`);
145
+ }
146
+
147
+ runtime.registerCommand(
148
+ CreateCommand(`${this.blockType}.render`, this, () => {
149
+ shaderBlockRuntime.renderToTargetTexture(renderTargetTexture);
150
+ })
151
+ );
152
+ }
153
+
154
+ super.generateCommandsAndGatherInitPromises(initializationData, finalOutput);
155
+ }
156
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The format of a texture - corresponds to the Babylon.js TextureFormat constants
3
+ */
4
+ export enum TextureFormat {
5
+ /** Babylon Constants.TEXTUREFORMAT_RGBA */
6
+ RGBA = 5,
7
+ /** Babylon Constants.TEXTUREFORMAT_R */
8
+ R = 6,
9
+ /** Babylon Constants.TEXTUREFORMAT_RG */
10
+ RG = 7,
11
+ }
12
+
13
+ /**
14
+ * The type of a texture - corresponds to the Babylon.js TextureType constants
15
+ */
16
+ export enum TextureType {
17
+ /** Babylon Constants.TEXTURETYPE_UNSIGNED_BYTE */
18
+ UNSIGNED_BYTE = 0,
19
+ /** Babylon Constants.TEXTURETYPE_FLOAT */
20
+ FLOAT = 1,
21
+ /** Babylon Constants.TEXTURETYPE_HALF_FLOAT */
22
+ HALF_FLOAT = 2,
23
+ }
24
+
25
+ // IMPORTANT: Update textureOptionsMatch() if you add more fields to OutputTextureOptions
26
+ /**
27
+ * Describes the requirements for the output texture of a shader block.
28
+ */
29
+ export type OutputTextureOptions = {
30
+ /**
31
+ * The texture size ratio (output size of this block / size of the Smart Filter output)
32
+ */
33
+ ratio: number;
34
+
35
+ /**
36
+ * The format of the texture
37
+ */
38
+ format: TextureFormat;
39
+
40
+ /**
41
+ * The type of the texture
42
+ */
43
+ type: TextureType;
44
+ };
45
+
46
+ /**
47
+ * Compares two OutputTextureOptions to see if they match.
48
+ * @param a - The first OutputTextureOptions
49
+ * @param b - The second OutputTextureOptions
50
+ * @returns True if the two options match, false otherwise
51
+ */
52
+ export function TextureOptionsMatch(a: OutputTextureOptions | undefined, b: OutputTextureOptions | undefined): boolean {
53
+ if (a === undefined || b === undefined) {
54
+ return false;
55
+ }
56
+ return a.ratio === b.ratio && a.format === b.format && a.type === b.type;
57
+ }