@babylonjs/smart-filters 0.5.0-alpha → 0.6.1-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 (120) 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.js +2 -2
  16. package/dist/blocks/shaderBlock.js.map +1 -1
  17. package/dist/command/command.d.ts +2 -3
  18. package/dist/command/command.d.ts.map +1 -1
  19. package/dist/command/command.js.map +1 -1
  20. package/dist/command/commandBufferDebugger.js +1 -1
  21. package/dist/command/commandBufferDebugger.js.map +1 -1
  22. package/dist/connection/connectionPointType.d.ts +4 -0
  23. package/dist/connection/connectionPointType.d.ts.map +1 -1
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/optimization/smartFilterOptimizer.js +3 -3
  29. package/dist/optimization/smartFilterOptimizer.js.map +1 -1
  30. package/dist/serialization/importCustomShaderBlockDefinition.d.ts +11 -0
  31. package/dist/serialization/importCustomShaderBlockDefinition.d.ts.map +1 -0
  32. package/dist/serialization/importCustomShaderBlockDefinition.js +80 -0
  33. package/dist/serialization/importCustomShaderBlockDefinition.js.map +1 -0
  34. package/dist/serialization/index.d.ts +1 -0
  35. package/dist/serialization/index.d.ts.map +1 -1
  36. package/dist/serialization/index.js +1 -0
  37. package/dist/serialization/index.js.map +1 -1
  38. package/dist/serialization/serializedBlockDefinition.d.ts +7 -0
  39. package/dist/serialization/serializedBlockDefinition.d.ts.map +1 -0
  40. package/dist/serialization/serializedBlockDefinition.js +2 -0
  41. package/dist/serialization/serializedBlockDefinition.js.map +1 -0
  42. package/dist/serialization/serializedSmartFilter.d.ts +1 -1
  43. package/dist/serialization/serializedSmartFilter.d.ts.map +1 -1
  44. package/dist/serialization/smartFilterDeserializer.d.ts +12 -4
  45. package/dist/serialization/smartFilterDeserializer.d.ts.map +1 -1
  46. package/dist/serialization/smartFilterDeserializer.js +63 -34
  47. package/dist/serialization/smartFilterDeserializer.js.map +1 -1
  48. package/dist/serialization/smartFilterSerializer.d.ts +2 -2
  49. package/dist/serialization/smartFilterSerializer.d.ts.map +1 -1
  50. package/dist/serialization/smartFilterSerializer.js +9 -6
  51. package/dist/serialization/smartFilterSerializer.js.map +1 -1
  52. package/dist/serialization/v1/blockSerialization.types.d.ts +55 -0
  53. package/dist/serialization/v1/blockSerialization.types.d.ts.map +1 -0
  54. package/dist/serialization/v1/blockSerialization.types.js +7 -0
  55. package/dist/serialization/v1/blockSerialization.types.js.map +1 -0
  56. package/dist/serialization/v1/defaultBlockSerializer.d.ts +1 -1
  57. package/dist/serialization/v1/defaultBlockSerializer.d.ts.map +1 -1
  58. package/dist/serialization/v1/defaultBlockSerializer.js +1 -1
  59. package/dist/serialization/v1/defaultBlockSerializer.js.map +1 -1
  60. package/dist/serialization/v1/index.d.ts +1 -1
  61. package/dist/serialization/v1/index.d.ts.map +1 -1
  62. package/dist/serialization/v1/index.js +1 -1
  63. package/dist/serialization/v1/index.js.map +1 -1
  64. package/dist/serialization/v1/{serialization.types.d.ts → smartFilterSerialization.types.d.ts} +12 -11
  65. package/dist/serialization/v1/smartFilterSerialization.types.d.ts.map +1 -0
  66. package/dist/serialization/v1/smartFilterSerialization.types.js +2 -0
  67. package/dist/serialization/v1/smartFilterSerialization.types.js.map +1 -0
  68. package/dist/utils/buildTools/buildShaders.js +1 -1
  69. package/dist/utils/buildTools/buildShaders.js.map +1 -1
  70. package/dist/utils/buildTools/convertShaderForHardcodedBlock.d.ts +13 -0
  71. package/dist/utils/buildTools/convertShaderForHardcodedBlock.d.ts.map +1 -0
  72. package/dist/utils/buildTools/convertShaderForHardcodedBlock.js +116 -0
  73. package/dist/utils/buildTools/convertShaderForHardcodedBlock.js.map +1 -0
  74. package/dist/utils/buildTools/shaderCode.types.d.ts +43 -0
  75. package/dist/utils/buildTools/shaderCode.types.d.ts.map +1 -0
  76. package/dist/utils/buildTools/shaderCode.types.js +2 -0
  77. package/dist/utils/buildTools/shaderCode.types.js.map +1 -0
  78. package/dist/utils/buildTools/shaderConverter.d.ts +56 -8
  79. package/dist/utils/buildTools/shaderConverter.d.ts.map +1 -1
  80. package/dist/utils/buildTools/shaderConverter.js +87 -137
  81. package/dist/utils/buildTools/shaderConverter.js.map +1 -1
  82. package/dist/utils/buildTools/watchShaders.js +2 -2
  83. package/dist/utils/buildTools/watchShaders.js.map +1 -1
  84. package/dist/utils/renderTargetUtils.js +3 -3
  85. package/dist/utils/renderTargetUtils.js.map +1 -1
  86. package/dist/utils/shaderCodeUtils.d.ts +1 -42
  87. package/dist/utils/shaderCodeUtils.d.ts.map +1 -1
  88. package/dist/utils/shaderCodeUtils.js.map +1 -1
  89. package/package.json +1 -1
  90. package/readme.md +19 -15
  91. package/src/blocks/baseBlock.ts +9 -0
  92. package/src/blocks/customShaderBlock.ts +217 -0
  93. package/src/blocks/inputBlock.deserializer.ts +1 -1
  94. package/src/blocks/inputBlock.serializer.ts +4 -4
  95. package/src/blocks/shaderBlock.ts +2 -2
  96. package/src/command/command.ts +2 -3
  97. package/src/command/commandBufferDebugger.ts +1 -1
  98. package/src/connection/connectionPointType.ts +11 -0
  99. package/src/index.ts +2 -0
  100. package/src/optimization/smartFilterOptimizer.ts +3 -3
  101. package/src/serialization/importCustomShaderBlockDefinition.ts +85 -0
  102. package/src/serialization/index.ts +1 -0
  103. package/src/serialization/serializedBlockDefinition.ts +7 -0
  104. package/src/serialization/serializedSmartFilter.ts +1 -1
  105. package/src/serialization/smartFilterDeserializer.ts +106 -52
  106. package/src/serialization/smartFilterSerializer.ts +11 -7
  107. package/src/serialization/v1/blockSerialization.types.ts +63 -0
  108. package/src/serialization/v1/defaultBlockSerializer.ts +2 -2
  109. package/src/serialization/v1/index.ts +1 -1
  110. package/src/serialization/v1/{serialization.types.ts → smartFilterSerialization.types.ts} +11 -10
  111. package/src/utils/buildTools/buildShaders.ts +1 -1
  112. package/src/utils/buildTools/convertShaderForHardcodedBlock.ts +149 -0
  113. package/src/utils/buildTools/shaderCode.types.ts +49 -0
  114. package/src/utils/buildTools/shaderConverter.ts +158 -178
  115. package/src/utils/buildTools/watchShaders.ts +2 -2
  116. package/src/utils/renderTargetUtils.ts +3 -3
  117. package/src/utils/shaderCodeUtils.ts +1 -50
  118. package/dist/serialization/v1/serialization.types.d.ts.map +0 -1
  119. package/dist/serialization/v1/serialization.types.js +0 -2
  120. package/dist/serialization/v1/serialization.types.js.map +0 -1
@@ -0,0 +1,149 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import { parseFragmentShader } from "./shaderConverter.js";
4
+
5
+ const TYPE_IMPORT_PATH = "@TYPE_IMPORT_PATH@";
6
+ const VERTEX_SHADER = "@VERTEX_SHADER@";
7
+ const UNIFORMS = "@UNIFORMS@";
8
+ const CONSTS_VALUE = "@CONSTS@";
9
+ const CONSTS_PROPERTY = "@CONSTS_PROPERTY@";
10
+ const MAIN_INPUT_NAME = "@MAIN_INPUT_NAME@";
11
+ const MAIN_FUNCTION_NAME = "@MAIN_FUNCTION_NAME@";
12
+ const FUNCTIONS = "@FUNCTIONS@";
13
+ const FUNCTION_NAME = "@FUNCTION_NAME@";
14
+ const FUNCTION_CODE = "@FUNCTION_CODE@";
15
+ const UNIFORM_NAMES = "@UNIFORM_NAMES@";
16
+
17
+ const ConstsTemplate = `
18
+ const: \`${CONSTS_VALUE}\`,`;
19
+
20
+ const FunctionTemplate = `
21
+ {
22
+ name: "${FUNCTION_NAME}",
23
+ code: \`
24
+ ${FUNCTION_CODE}
25
+ \`,
26
+ },`;
27
+
28
+ const CodeLinePrefix = " ";
29
+ const UniformLinePrefix = " ";
30
+ const ConstLinePrefix = " ";
31
+
32
+ const ShaderTemplate = `import type { ShaderProgram } from "${TYPE_IMPORT_PATH}";
33
+
34
+ /**
35
+ * The shader program for the block.
36
+ */
37
+ export const shaderProgram: ShaderProgram = {
38
+ vertex: ${VERTEX_SHADER},
39
+ fragment: {
40
+ uniform: \`${UNIFORMS}\`,${CONSTS_PROPERTY}
41
+ mainInputTexture: "${MAIN_INPUT_NAME}",
42
+ mainFunctionName: "${MAIN_FUNCTION_NAME}",
43
+ functions: [${FUNCTIONS}
44
+ ],
45
+ },
46
+ };
47
+
48
+ /**
49
+ * The uniform names for this shader, to be used in the shader binding so
50
+ * that the names are always in sync.
51
+ */
52
+ export const uniforms = {
53
+ ${UNIFORM_NAMES}
54
+ };
55
+ `;
56
+
57
+ const UniformNameLinePrefix = " ";
58
+
59
+ /**
60
+ * Converts a single shader to a .ts file which can be imported by a hardcoded block
61
+ * @param fragmentShaderPath - The path to the fragment file for the shader
62
+ * @param importPath - The path to import the ShaderProgram type from
63
+ */
64
+ export function convertShaderForHardcodedBlock(fragmentShaderPath: string, importPath: string): void {
65
+ console.log(`Processing fragment shader: ${fragmentShaderPath}`);
66
+
67
+ // See if there is a corresponding vertex shader
68
+ let vertexShader: string | undefined = undefined;
69
+ const vertexShaderPath = fragmentShaderPath.replace(".fragment.glsl", ".vertex.glsl");
70
+ if (fs.existsSync(vertexShaderPath)) {
71
+ vertexShader = fs.readFileSync(vertexShaderPath, "utf8");
72
+ }
73
+ if (vertexShader) {
74
+ console.log("Found vertex shader");
75
+ }
76
+
77
+ // Read the fragment shader
78
+ const fragmentShader = fs.readFileSync(fragmentShaderPath, "utf8");
79
+ const fragmentShaderInfo = parseFragmentShader(fragmentShader);
80
+
81
+ // Write the shader TS file
82
+ const shaderFile = fragmentShaderPath.replace(".fragment.glsl", ".shader.ts");
83
+ const functionsSection: string[] = [];
84
+ for (const shaderFunction of fragmentShaderInfo.shaderCode.functions) {
85
+ functionsSection.push(
86
+ FunctionTemplate.replace(FUNCTION_NAME, shaderFunction.name).replace(
87
+ FUNCTION_CODE,
88
+ addLinePrefixes(shaderFunction.code, CodeLinePrefix)
89
+ )
90
+ );
91
+ }
92
+ const finalContents = ShaderTemplate.replace(VERTEX_SHADER, vertexShader ? `\`${vertexShader}\`` : "undefined")
93
+ .replace(TYPE_IMPORT_PATH, importPath)
94
+ .replace(UNIFORMS, "\n" + addLinePrefixes(fragmentShaderInfo.shaderCode.uniform || "", UniformLinePrefix))
95
+ .replace(MAIN_FUNCTION_NAME, fragmentShaderInfo.shaderCode.mainFunctionName)
96
+ .replace(MAIN_INPUT_NAME, fragmentShaderInfo.shaderCode.mainInputTexture || '""')
97
+ .replace(
98
+ CONSTS_PROPERTY,
99
+ fragmentShaderInfo.shaderCode.const
100
+ ? ConstsTemplate.replace(
101
+ CONSTS_VALUE,
102
+ addLinePrefixes(fragmentShaderInfo.shaderCode.const, ConstLinePrefix)
103
+ )
104
+ : ""
105
+ )
106
+ .replace(FUNCTIONS, functionsSection.join(""))
107
+ .replace(
108
+ UNIFORM_NAMES,
109
+ addLinePrefixes(
110
+ fragmentShaderInfo.uniforms.map((u) => `${u.name}: "${u.name}",`).join("\n"),
111
+ UniformNameLinePrefix
112
+ )
113
+ );
114
+
115
+ fs.writeFileSync(shaderFile, finalContents);
116
+ }
117
+
118
+ /**
119
+ * Converts .fragment.glsl and vertex.glsl file pairs into .shader.ts files which export a ShaderProgram object.
120
+ * @param shaderPath - The path to the .glsl files to convert.
121
+ * @param importPath - The path to import the ShaderProgram type from.
122
+ */
123
+ export function convertShaders(shaderPath: string, importPath: string) {
124
+ // Get all files in the path
125
+ const allFiles = fs.readdirSync(shaderPath, { withFileTypes: true, recursive: true });
126
+
127
+ // Find all fragment shaders (excluding the template)
128
+ const fragmentShaderFiles = allFiles.filter(
129
+ (file) => file.isFile() && file.name.endsWith(".fragment.glsl") && !file.name.endsWith("template.fragment.glsl")
130
+ );
131
+
132
+ // Convert all shaders
133
+ for (const fragmentShaderFile of fragmentShaderFiles) {
134
+ convertShaderForHardcodedBlock(path.join(fragmentShaderFile.path, fragmentShaderFile.name), importPath);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Prefixes each line in the input
140
+ * @param input - The input string
141
+ * @param prefix - The prefix to add to each line
142
+ * @returns The input with each line prefixed
143
+ */
144
+ function addLinePrefixes(input: string, prefix: string): string {
145
+ return input
146
+ .split("\n")
147
+ .map((line) => prefix + line)
148
+ .join("\n");
149
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Describes a shader function.
3
+ */
4
+ export type ShaderFunction = {
5
+ /**
6
+ * The name of the function.
7
+ */
8
+ name: string;
9
+
10
+ /**
11
+ * The code of the function.
12
+ */
13
+ code: string;
14
+ };
15
+
16
+ /**
17
+ * Describes a shader code.
18
+ */
19
+ export type ShaderCode = {
20
+ /**
21
+ * The declaration of the const variables.
22
+ */
23
+ const?: string;
24
+
25
+ /**
26
+ * The declaration of the uniform variables.
27
+ */
28
+ uniform?: string;
29
+
30
+ /**
31
+ * The declaration of the uniform variables that should be common for all ShaderBlock instances using this shader code.
32
+ */
33
+ uniformSingle?: string;
34
+
35
+ /**
36
+ * The name of the main function.
37
+ */
38
+ mainFunctionName: string;
39
+
40
+ /**
41
+ * The name of the input texture which is passed through if the block is disabled.
42
+ */
43
+ mainInputTexture?: string;
44
+
45
+ /**
46
+ * The list of functions used in the shader.
47
+ */
48
+ functions: ShaderFunction[];
49
+ };
@@ -1,174 +1,116 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
-
4
- const TYPE_IMPORT_PATH = "@TYPE_IMPORT_PATH@";
5
- const VERTEX_SHADER = "@VERTEX_SHADER@";
6
- const UNIFORMS = "@UNIFORMS@";
7
- const CONSTS_VALUE = "@CONSTS@";
8
- const CONSTS_PROPERTY = "@CONSTS_PROPERTY@";
9
- const MAIN_INPUT_NAME = "@MAIN_INPUT_NAME@";
10
- const MAIN_FUNCTION_NAME = "@MAIN_FUNCTION_NAME@";
11
- const FUNCTIONS = "@FUNCTIONS@";
12
- const FUNCTION_NAME = "@FUNCTION_NAME@";
13
- const FUNCTION_CODE = "@FUNCTION_CODE@";
14
- const UNIFORM_NAMES = "@UNIFORM_NAMES@";
15
-
16
- const ConstsTemplate = `
17
- const: \`${CONSTS_VALUE}\`,`;
18
-
19
- const FunctionTemplate = `
20
- {
21
- name: "${FUNCTION_NAME}",
22
- code: \`
23
- ${FUNCTION_CODE}
24
- \`,
25
- },`;
26
-
27
- const CodeLinePrefix = " ";
28
- const UniformLinePrefix = " ";
29
- const ConstLinePrefix = " ";
30
-
31
- const ShaderTemplate = `import type { ShaderProgram } from "${TYPE_IMPORT_PATH}";
1
+ import type { Nullable } from "@babylonjs/core/types";
2
+ import type { ShaderCode, ShaderFunction } from "./shaderCode.types";
32
3
 
33
- /**
34
- * The shader program for the block.
35
- */
36
- export const shaderProgram: ShaderProgram = {
37
- vertex: ${VERTEX_SHADER},
38
- fragment: {
39
- uniform: \`${UNIFORMS}\`,${CONSTS_PROPERTY}
40
- mainInputTexture: "${MAIN_INPUT_NAME}",
41
- mainFunctionName: "${MAIN_FUNCTION_NAME}",
42
- functions: [${FUNCTIONS}
43
- ],
44
- },
45
- };
4
+ const GetFunctionNamesRegEx = /\S*\w+\s+(\w+)\s*\(/g;
46
5
 
47
6
  /**
48
- * The uniform names for this shader, to be used in the shader binding so
49
- * that the names are always in sync.
7
+ * Describes the supported metadata properties for a uniform
50
8
  */
51
- export const uniforms = {
52
- ${UNIFORM_NAMES}
9
+ export type UniformMetadataProperties = {
10
+ /**
11
+ * If supplied, the default value to use for the corresponding input connection point
12
+ */
13
+ default?: any;
53
14
  };
54
- `;
55
-
56
- const UniformNameLinePrefix = " ";
57
-
58
- const GetFunctionNamesRegEx = /\S*\w+\s+(\w+)\s*\(/g;
59
15
 
60
16
  /**
61
- * Converts a single shader
62
- * @param fragmentShaderPath - The path to the fragment file for the shader
63
- * @param importPath - The path to import the ShaderProgram type from
17
+ * Describes a uniform in a shader
64
18
  */
65
- export function convertShader(fragmentShaderPath: string, importPath: string): void {
66
- console.log(`Processing fragment shader: ${fragmentShaderPath}`);
67
-
68
- // See if there is a corresponding vertex shader
69
- let vertexShader: string | undefined = undefined;
70
- const vertexShaderPath = fragmentShaderPath.replace(".fragment.glsl", ".vertex.glsl");
71
- if (fs.existsSync(vertexShaderPath)) {
72
- vertexShader = fs.readFileSync(vertexShaderPath, "utf8");
73
- }
74
- if (vertexShader) {
75
- console.log("Found vertex shader");
76
- }
77
-
78
- // Read the fragment shader
79
- const fragmentShader = fs.readFileSync(fragmentShaderPath, "utf8");
80
-
81
- // Version check
82
- const versionsFound = [...fragmentShader.matchAll(/\/\/\s+\[Smart Filter Shader Version\]\s*=\s*(\d+)/g)].map(
83
- (match) => match[1]
84
- );
85
- let version: number = 1;
86
- if (versionsFound.length === 1 && versionsFound[0]) {
87
- version = parseInt(versionsFound[0]);
88
- }
89
- console.log(`Shader version: ${version}`);
90
-
91
- let fragmentShaderInfo: FragmentShaderInfo;
92
-
93
- switch (version) {
94
- case 1:
95
- {
96
- fragmentShaderInfo = processFragmentShaderV1(fragmentShader);
97
- }
98
- break;
99
- default: {
100
- throw new Error(`Unsupported shader version: ${version}`);
101
- }
102
- }
103
-
104
- // Write the shader TS file
105
- const shaderFile = fragmentShaderPath.replace(".fragment.glsl", ".shader.ts");
106
- const finalContents = ShaderTemplate.replace(VERTEX_SHADER, vertexShader ? `\`${vertexShader}\`` : "undefined")
107
- .replace(TYPE_IMPORT_PATH, importPath)
108
- .replace(UNIFORMS, "\n" + addLinePrefixes(fragmentShaderInfo.finalUniforms.join("\n"), UniformLinePrefix))
109
- .replace(MAIN_FUNCTION_NAME, fragmentShaderInfo.mainFunctionName)
110
- .replace(MAIN_INPUT_NAME, fragmentShaderInfo.mainInputName)
111
- .replace(
112
- CONSTS_PROPERTY,
113
- fragmentShaderInfo.finalConsts.length > 0
114
- ? ConstsTemplate.replace(
115
- CONSTS_VALUE,
116
- addLinePrefixes(fragmentShaderInfo.finalConsts.join("\n"), ConstLinePrefix)
117
- )
118
- : ""
119
- )
120
- .replace(FUNCTIONS, fragmentShaderInfo.extractedFunctions.join(""))
121
- .replace(UNIFORM_NAMES, addLinePrefixes(fragmentShaderInfo.uniformNames.join("\n"), UniformNameLinePrefix));
122
-
123
- fs.writeFileSync(shaderFile, finalContents);
124
- }
19
+ export type UniformMetadata = {
20
+ /**
21
+ * The original name of the uniform (not renamed)
22
+ */
23
+ name: string;
125
24
 
126
- /**
127
- * Information about a fragment shader
128
- */
129
- type FragmentShaderInfo = {
130
25
  /**
131
- * The lines of code which declare the uniforms
26
+ * The type string of the uniform
132
27
  */
133
- finalUniforms: (string | undefined)[];
28
+ type: string;
134
29
 
135
30
  /**
136
- * The main function name
31
+ * Optional properties of the uniform
137
32
  */
138
- mainFunctionName: string;
33
+ properties?: UniformMetadataProperties;
34
+ };
139
35
 
36
+ /**
37
+ * Information about a fragment shader
38
+ */
39
+ export type FragmentShaderInfo = {
140
40
  /**
141
- * The main input name
41
+ * If supplied, the blockType to use for the block
142
42
  */
143
- mainInputName: string;
43
+ blockType?: string;
144
44
 
145
45
  /**
146
- * The lines of code which declare the consts
46
+ * If true, optimization should be disabled for this shader
147
47
  */
148
- finalConsts: (string | undefined)[];
48
+ disableOptimization?: boolean;
149
49
 
150
50
  /**
151
- * The lines of code which declare the functions
51
+ * The shader code
152
52
  */
153
- extractedFunctions: string[];
53
+ shaderCode: ShaderCode;
154
54
 
155
55
  /**
156
- * The lines of code which declare the uniform names
56
+ * The set of uniforms
157
57
  */
158
- uniformNames: string[];
58
+ uniforms: UniformMetadata[];
159
59
  };
160
60
 
161
61
  /**
162
- * Processes a fragment shader
62
+ * Parses a fragment shader
163
63
  * @param fragmentShader - The fragment shader to process
164
64
  * @returns The processed fragment shader
165
65
  */
166
- function processFragmentShaderV1(fragmentShader: string): FragmentShaderInfo {
66
+ export function parseFragmentShader(fragmentShader: string): FragmentShaderInfo {
67
+ const { header, fragmentShaderWithoutHeader } = readHeader(fragmentShader);
68
+ fragmentShader = fragmentShaderWithoutHeader;
69
+ const blockType = header?.[SmartFilterBlockTypeKey] || undefined;
70
+
71
+ // Read the uniforms
72
+ const uniforms: UniformMetadata[] = [];
73
+ const uniformRegExp = new RegExp(/(\/\/\s*\{.*\}\s*(?:\r\n|\r|\n)+)?(uniform .*)/gm);
74
+ const uniformGroups = fragmentShader.matchAll(uniformRegExp);
75
+ for (const matches of uniformGroups) {
76
+ const annotationJSON = matches[1];
77
+ const uniformLine = matches[2];
78
+
79
+ if (!uniformLine) {
80
+ throw new Error("Uniform line not found");
81
+ }
82
+
83
+ const uniformLineMatches = new RegExp(/^uniform\s+(\w+)\s+(\w+)\s*;?/gm).exec(uniformLine);
84
+ if (!uniformLineMatches || uniformLineMatches.length < 3) {
85
+ throw new Error(`Uniforms must have a type and a name: '${uniformLine}'`);
86
+ }
87
+ const uniformTypeString = uniformLineMatches[1];
88
+ const uniformName = uniformLineMatches[2];
89
+
90
+ if (!uniformTypeString) {
91
+ throw new Error(`Uniforms must have a type: '${uniformLine}'`);
92
+ }
93
+ if (!uniformName) {
94
+ throw new Error(`Uniforms must have a name: '${uniformLine}'`);
95
+ }
96
+
97
+ uniforms.push({
98
+ name: uniformName,
99
+ type: uniformTypeString,
100
+ properties: annotationJSON ? JSON.parse(annotationJSON.replace("//", "").trim()) : undefined,
101
+ });
102
+
103
+ if (annotationJSON) {
104
+ // Strip out any annotation so it isn't mistaken for function bodies
105
+ fragmentShader = fragmentShader.replace(annotationJSON, "");
106
+ }
107
+ }
108
+
167
109
  const fragmentShaderWithNoFunctionBodies = removeFunctionBodies(fragmentShader);
168
110
 
169
111
  // Collect uniform, const, and function names which need to be decorated
170
112
  // eslint-disable-next-line prettier/prettier
171
- const uniforms = [...fragmentShader.matchAll(/\S*uniform.*\s(\w*);/g)].map((match) => match[1]);
113
+ const uniformNames = uniforms.map((uniform) => uniform.name);
172
114
  console.log(`Uniforms found: ${JSON.stringify(uniforms)}`);
173
115
  const consts = [...fragmentShader.matchAll(/\S*const\s+\w*\s+(\w*)\s*=.*;/g)].map((match) => match[1]);
174
116
  console.log(`Consts found: ${JSON.stringify(consts)}`);
@@ -178,14 +120,13 @@ function processFragmentShaderV1(fragmentShader: string): FragmentShaderInfo {
178
120
  console.log(`Functions found: ${JSON.stringify(functionNames)}`);
179
121
 
180
122
  // Decorate the uniforms, consts, and functions
181
- const symbolsToDecorate = [...uniforms, ...consts, ...functionNames];
123
+ const symbolsToDecorate = [...uniformNames, ...consts, ...functionNames];
182
124
  let fragmentShaderWithRenamedSymbols = fragmentShader;
183
125
  for (const symbol of symbolsToDecorate) {
184
126
  const regex = new RegExp(`(?<=\\W+)${symbol}(?=\\W+)`, "gs");
185
127
  fragmentShaderWithRenamedSymbols = fragmentShaderWithRenamedSymbols.replace(regex, `_${symbol}_`);
186
128
  }
187
129
  console.log(`${symbolsToDecorate.length} symbol(s) renamed`);
188
- const uniformNames = uniforms.map((uniform) => `${uniform}: "${uniform}",`);
189
130
 
190
131
  // Extract all the uniforms
191
132
  const finalUniforms = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(uniform\s.*)/gm)].map(
@@ -202,32 +143,28 @@ function processFragmentShaderV1(fragmentShader: string): FragmentShaderInfo {
202
143
  if (mainInputs.length !== 1 || !mainInputs[0]) {
203
144
  throw new Error("Exactly one main input must be defined in the shader");
204
145
  }
205
- const mainInputName = mainInputs[0];
146
+ const mainInputTexture = mainInputs[0];
206
147
 
207
148
  // Extract all the functions
208
149
  const { extractedFunctions, mainFunctionName } = extractFunctions(fragmentShaderWithRenamedSymbols);
209
150
 
210
- return {
211
- finalUniforms,
151
+ const shaderCode: ShaderCode = {
152
+ uniform: finalUniforms.join("\n"),
212
153
  mainFunctionName,
213
- mainInputName,
214
- finalConsts,
215
- extractedFunctions,
216
- uniformNames,
154
+ mainInputTexture,
155
+ functions: extractedFunctions,
217
156
  };
218
- }
219
157
 
220
- /**
221
- * Prefixes each line in the input
222
- * @param input - The input string
223
- * @param prefix - The prefix to add to each line
224
- * @returns The input with each line prefixed
225
- */
226
- function addLinePrefixes(input: string, prefix: string): string {
227
- return input
228
- .split("\n")
229
- .map((line) => prefix + line)
230
- .join("\n");
158
+ if (finalConsts.length > 0) {
159
+ shaderCode.const = finalConsts.join("\n");
160
+ }
161
+
162
+ return {
163
+ blockType,
164
+ shaderCode,
165
+ uniforms,
166
+ disableOptimization: !!header?.disableOptimizer,
167
+ };
231
168
  }
232
169
 
233
170
  /**
@@ -239,7 +176,7 @@ function extractFunctions(fragment: string): {
239
176
  /**
240
177
  * The extracted functions
241
178
  */
242
- extractedFunctions: string[];
179
+ extractedFunctions: ShaderFunction[];
243
180
 
244
181
  /**
245
182
  * The name of the main function
@@ -247,7 +184,7 @@ function extractFunctions(fragment: string): {
247
184
  mainFunctionName: string;
248
185
  } {
249
186
  const lines = fragment.split("\n");
250
- const extractedFunctions: string[] = [];
187
+ const extractedFunctions: ShaderFunction[] = [];
251
188
  let mainFunctionName: string | undefined;
252
189
  let inFunction = false;
253
190
  let depth = 0;
@@ -271,12 +208,11 @@ function extractFunctions(fragment: string): {
271
208
  inFunction = false;
272
209
  const { functionBody, functionName, isMainFunction } = processFunctionBody(currentFunction);
273
210
 
274
- extractedFunctions.push(
275
- FunctionTemplate.replace(FUNCTION_NAME, functionName).replace(
276
- FUNCTION_CODE,
277
- addLinePrefixes(functionBody, CodeLinePrefix)
278
- )
279
- );
211
+ extractedFunctions.push({
212
+ name: functionName,
213
+ code: functionBody,
214
+ });
215
+
280
216
  if (isMainFunction) {
281
217
  if (mainFunctionName) {
282
218
  throw new Error("Multiple main functions found in shader code");
@@ -365,24 +301,68 @@ function removeFunctionBodies(input: string): string {
365
301
  return output;
366
302
  }
367
303
 
304
+ const SmartFilterBlockTypeKey = "smartFilterBlockType";
305
+
368
306
  /**
369
- * Converts .fragment.glsl and vertex.glsl file pairs into .shader.ts files which export a ShaderProgram object.
370
- * @param shaderPath - The path to the .glsl files to convert.
371
- * @param importPath - The path to import the ShaderProgram type from.
307
+ * The format of the header we expect in a GLSL shader
372
308
  */
373
- export function convertShaders(shaderPath: string, importPath: string) {
374
- // Get all files in the path
375
- const allFiles = fs.readdirSync(shaderPath, { withFileTypes: true, recursive: true });
309
+ type GlslHeader = {
310
+ /**
311
+ * The block type to use for the shader, required when converting to a
312
+ * SerializedBlockDefinition, but not when exporting to a .ts file
313
+ * to be included in a hardcoded block definition
314
+ */
315
+ [SmartFilterBlockTypeKey]: string;
376
316
 
377
- // Find all fragment shaders (excluding the template)
378
- const fragmentShaderFiles = allFiles.filter(
379
- (file) => file.isFile() && file.name.endsWith(".fragment.glsl") && !file.name.endsWith("template.fragment.glsl")
380
- );
317
+ /**
318
+ * If true, optimization should be disabled for this shader
319
+ */
320
+ disableOptimizer?: boolean;
321
+ };
322
+
323
+ /**
324
+ * Reads the GlslHeader from the shader code
325
+ * @param fragmentShader - The shader code to read
326
+ * @returns - The GlslHeader if found, otherwise null
327
+ */
328
+ function readHeader(fragmentShader: string): {
329
+ /**
330
+ * The glsl header, or null if there wasn't one
331
+ */
332
+ header: Nullable<GlslHeader>;
381
333
 
382
- // Convert all shaders
383
- for (const fragmentShaderFile of fragmentShaderFiles) {
384
- convertShader(path.join(fragmentShaderFile.path, fragmentShaderFile.name), importPath);
334
+ /**
335
+ * The fragment shader code with the header removed if there was one
336
+ */
337
+ fragmentShaderWithoutHeader: string;
338
+ } {
339
+ const singleLineHeaderMatch = new RegExp(/^\/\/\s*(\{.*\})/gm).exec(fragmentShader);
340
+ if (singleLineHeaderMatch && singleLineHeaderMatch[1]) {
341
+ return {
342
+ header: JSON.parse(singleLineHeaderMatch[1].trim()),
343
+ fragmentShaderWithoutHeader: fragmentShader.replace(singleLineHeaderMatch[0], ""),
344
+ };
345
+ }
346
+
347
+ const multiLineHeaderMatch = new RegExp("^\\/\\*(.*)\\*\\/", "gs").exec(fragmentShader);
348
+ if (multiLineHeaderMatch && multiLineHeaderMatch[1]) {
349
+ return {
350
+ header: JSON.parse(multiLineHeaderMatch[1].trim()),
351
+ fragmentShaderWithoutHeader: fragmentShader.replace(multiLineHeaderMatch[0], ""),
352
+ };
385
353
  }
354
+
355
+ return {
356
+ header: null,
357
+ fragmentShaderWithoutHeader: fragmentShader,
358
+ };
386
359
  }
387
360
 
388
- // TODO: simple copy from shader file to .ts, get it to build (including import trick)
361
+ /**
362
+ * Determines if a fragment shader has the GLSL header required for parsing
363
+ * @param fragmentShader - The fragment shader to check
364
+ * @returns True if the fragment shader has the GLSL header
365
+ */
366
+ export function hasGlslHeader(fragmentShader: string): boolean {
367
+ return fragmentShader.indexOf(SmartFilterBlockTypeKey) !== -1;
368
+ }
@@ -6,7 +6,7 @@
6
6
  * @example node watchShaders.js <shaderPath> <importPath>
7
7
  */
8
8
 
9
- import { convertShader } from "./shaderConverter.js";
9
+ import { convertShaderForHardcodedBlock } from "./convertShaderForHardcodedBlock.js";
10
10
  import { watch } from "chokidar";
11
11
  import { extname } from "path";
12
12
 
@@ -29,7 +29,7 @@ if (externalArguments.length >= 2 && externalArguments[0] && externalArguments[1
29
29
  // Wrap in try-catch to prevent the watcher from crashing
30
30
  // if the new shader changes are invalid
31
31
  try {
32
- convertShader(file, importPath);
32
+ convertShaderForHardcodedBlock(file, importPath);
33
33
  console.log(`Successfully updated shader ${file}`);
34
34
  } catch (error) {
35
35
  console.error(`Failed to convert shader ${file}: ${error}`);
@@ -38,16 +38,16 @@ export function registerFinalRenderCommand(
38
38
  commandOwner: BaseBlock,
39
39
  shaderBlockRuntime: ShaderRuntime
40
40
  ): void {
41
- const commandOwnerClassName = commandOwner.getClassName();
41
+ const commandOwnerBlockType = commandOwner.blockType;
42
42
  if (renderTargetWrapper) {
43
43
  runtime.registerCommand(
44
- createCommand(`${commandOwnerClassName}.renderToFinalTexture`, commandOwner, () => {
44
+ createCommand(`${commandOwnerBlockType}.renderToFinalTexture`, commandOwner, () => {
45
45
  shaderBlockRuntime.renderToTexture(renderTargetWrapper);
46
46
  })
47
47
  );
48
48
  } else {
49
49
  runtime.registerCommand(
50
- createCommand(`${commandOwnerClassName}.renderToCanvas`, commandOwner, () => {
50
+ createCommand(`${commandOwnerBlockType}.renderToCanvas`, commandOwner, () => {
51
51
  shaderBlockRuntime.renderToCanvas();
52
52
  })
53
53
  );