Skip to content

Commit 8a2b9ab

Browse files
authored
Fix GLSL parsing issue due to a RegExp object being reused, resulting in function names sometimes not being decorated, and add UT launch.json config (#128)
1 parent 6f7b10f commit 8a2b9ab

File tree

3 files changed

+114
-25
lines changed

3 files changed

+114
-25
lines changed

.vscode/launch.json

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
55
"version": "0.2.0",
66
"configurations": [
7-
87
{
98
"type": "msedge",
109
"request": "launch",
@@ -40,21 +39,33 @@
4039
"outFiles": ["!**/node_modules/**"],
4140
"preLaunchTask": "Serve and watch SFE (Dev)"
4241
},
43-
// {
44-
// /*
45-
// Launch tests
46-
// https://jestjs.io/docs/troubleshooting#debugging-in-vs-code
47-
// */
48-
// "type": "node",
49-
// "request": "launch",
50-
// "name": "Run and Debug unit tests (Dev)",
51-
// "runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/jest/bin/jest.js", "--runInBand", "--selectProjects", "unit"],
52-
// "console": "integratedTerminal",
53-
// "internalConsoleOptions": "neverOpen",
54-
// "port": 9229,
55-
// // "outFiles": ["${workspaceRoot}/**/dist/**/*"], // debugging sources
56-
// // "envFile": "${workspaceRoot}/.env",
57-
// "preLaunchTask": "Build (Dev)"
58-
// },
42+
{
43+
"type": "node",
44+
"request": "launch",
45+
"name": "Run and Debug unit tests",
46+
"runtimeArgs": [
47+
"--inspect-brk",
48+
"${workspaceRoot}/node_modules/jest/bin/jest.js",
49+
"--runInBand",
50+
"--selectProjects",
51+
"unit"
52+
],
53+
"console": "integratedTerminal",
54+
"internalConsoleOptions": "neverOpen"
55+
},
56+
{
57+
"type": "node",
58+
"request": "launch",
59+
"name": "Run and Debug current unit test file",
60+
"runtimeArgs": [
61+
"--inspect-brk",
62+
"${workspaceRoot}/node_modules/jest/bin/jest.js",
63+
"--runInBand",
64+
"--testPathPattern",
65+
"${fileBasename}" // Run only the currently open file
66+
],
67+
"console": "integratedTerminal",
68+
"internalConsoleOptions": "neverOpen"
69+
}
5970
]
6071
}

packages/core/src/utils/buildTools/shaderConverter.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,19 @@ import type { ShaderCode, ShaderFunction } from "./shaderCode.types";
44
import { ConnectionPointType } from "../../connection/connectionPointType.js";
55
import { BlockDisableStrategy } from "../../blockFoundation/disableableShaderBlock.js";
66

7-
const GetFunctionHeaderRegEx = /\S*\w+\s+(\w+)\s*\((.*?)\)\s*\{/g; // Matches a function's name and its parameters
8-
const GetDefineRegEx = /^\S*#define\s+(\w+).*$/gm; // Matches a #define statement line, capturing its name
7+
// Note: creating a global RegExp object is risky, because it holds state (e.g. lastIndex) that has to be
8+
// cleared at the right time to ensure correctness, which is easy to forget to do.
9+
// Instead, for regular expressions used in multiple places, we define the string and options once, and create
10+
// a new RegExp object from them when needed, to ensure state isn't accidentally reused.
11+
12+
// Matches a function's name and its parameters
13+
const GetFunctionHeaderRegExString = `\\S*\\w+\\s+(\\w+)\\s*\\((.*?)\\)\\s*\\{`;
14+
const GetFunctionHeaderRegExOptions = "g";
15+
16+
// Matches a #define statement line, capturing its name
17+
const GetDefineRegExString = `^\\S*#define\\s+(\\w+).*$`;
18+
const GetDefineRegExOptions = "gm";
19+
920
const ReservedSymbols = ["main"];
1021

1122
/**
@@ -161,11 +172,15 @@ export function parseFragmentShader(fragmentShader: string): FragmentShaderInfo
161172
Logger.Log(`Uniforms found: ${JSON.stringify(uniforms)}`);
162173
const consts = [...fragmentShader.matchAll(/\S*const\s+\w*\s+(\w*)\s*=.*;/g)].map((match) => match[1]);
163174
Logger.Log(`Consts found: ${JSON.stringify(consts)}`);
164-
const defineNames = [...fragmentShader.matchAll(GetDefineRegEx)].map((match) => match[1]);
165-
Logger.Log(`Defines found: ${JSON.stringify(defineNames)}`);
166-
const functionNames = [...fragmentShaderWithNoFunctionBodies.matchAll(GetFunctionHeaderRegEx)].map(
175+
const defineNames = [...fragmentShader.matchAll(new RegExp(GetDefineRegExString, GetDefineRegExOptions))].map(
167176
(match) => match[1]
168177
);
178+
Logger.Log(`Defines found: ${JSON.stringify(defineNames)}`);
179+
const functionNames = [
180+
...fragmentShaderWithNoFunctionBodies.matchAll(
181+
new RegExp(GetFunctionHeaderRegExString, GetFunctionHeaderRegExOptions)
182+
),
183+
].map((match) => match[1]);
169184
Logger.Log(`Functions found: ${JSON.stringify(functionNames)}`);
170185

171186
// Decorate the uniforms, consts, defines, and functions
@@ -192,7 +207,9 @@ export function parseFragmentShader(fragmentShader: string): FragmentShaderInfo
192207
const finalConsts = [...fragmentShaderWithRenamedSymbols.matchAll(/^\s*(const\s.*)/gm)].map((match) => match[1]);
193208

194209
// Extract all the defines
195-
const finalDefines = [...fragmentShaderWithRenamedSymbols.matchAll(GetDefineRegEx)].map((match) => match[0]);
210+
const finalDefines = [
211+
...fragmentShaderWithRenamedSymbols.matchAll(new RegExp(GetDefineRegExString, GetDefineRegExOptions)),
212+
].map((match) => match[0]);
196213

197214
// Find the main input
198215
const mainInputs = [...fragmentShaderWithRenamedSymbols.matchAll(/\S*uniform.*\s(\w*);\s*\/\/\s*main/gm)].map(
@@ -248,10 +265,12 @@ function extractFunctions(fragment: string): {
248265
let mainFunctionName: string | undefined;
249266
let pos = 0;
250267

268+
const getFunctionHeaderRegEx = new RegExp(GetFunctionHeaderRegExString, GetFunctionHeaderRegExOptions);
269+
251270
while (pos < fragment.length) {
252271
// Match the next available function header in the fragment code
253-
GetFunctionHeaderRegEx.lastIndex = pos;
254-
const match = GetFunctionHeaderRegEx.exec(fragment);
272+
getFunctionHeaderRegEx.lastIndex = pos;
273+
const match = getFunctionHeaderRegEx.exec(fragment);
255274
if (!match) {
256275
break;
257276
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { importCustomBlockDefinition } from "../../dist/serialization/importCustomBlockDefinition.js";
2+
import { CustomShaderBlock } from "../../dist/blockFoundation/customShaderBlock.js";
3+
import { SmartFilter } from "../../dist/smartFilter.js";
4+
import { InputBlock } from "../../dist/blockFoundation/inputBlock.js";
5+
import { ConnectionPointType } from "../../dist/connection/connectionPointType.js";
6+
import { SmartFilterOptimizer } from "../../dist/optimization/smartFilterOptimizer.js";
7+
8+
const annotatedFragmentGeneral = `
9+
// { "smartFilterBlockType": "GeneralBlock", "namespace": "UnitTests" }
10+
uniform sampler2D input; // main
11+
uniform float intensity;
12+
13+
vec4 apply2(vec2 vUV){ // main
14+
vec4 color = texture2D(input, vUV);
15+
return color * intensity;
16+
}
17+
`;
18+
19+
describe("importCustomBlockDefinition", () => {
20+
describe("a block is parsed after a block where the string ends exactly at the end of the function definition", () => {
21+
const annotatedFragmentEndOfFunctionDefinitionIsEndOfString = `
22+
// { "smartFilterBlockType": "BlockWhereFunctionEndsAtEndOfString", "namespace": "UnitTests" }
23+
uniform sampler2D input; // main
24+
uniform float amount;
25+
26+
vec4 apply1(vec2 vUV){ // main
27+
vec4 color = texture2D(input, vUV);
28+
return color + amount;
29+
}`;
30+
31+
const customBlockDefinition1 = importCustomBlockDefinition(
32+
annotatedFragmentEndOfFunctionDefinitionIsEndOfString
33+
);
34+
const customBlockDefinition2 = importCustomBlockDefinition(annotatedFragmentGeneral);
35+
let smartFilter;
36+
37+
beforeEach(() => {
38+
smartFilter = new SmartFilter("test");
39+
const customBlock1 = CustomShaderBlock.Create(smartFilter, "block1", customBlockDefinition1);
40+
const customBlock2 = CustomShaderBlock.Create(smartFilter, "block2", customBlockDefinition2);
41+
42+
const inputTexture = new InputBlock(smartFilter, "inputTexture", ConnectionPointType.Texture, null);
43+
const inputFloat = new InputBlock(smartFilter, "inputFloat", ConnectionPointType.Float, 1.0);
44+
45+
inputTexture.output.connectTo(customBlock1.findInput("input"));
46+
inputFloat.output.connectTo(customBlock1.findInput("amount"));
47+
48+
customBlock1.output.connectTo(customBlock2.findInput("input"));
49+
inputFloat.output.connectTo(customBlock2.findInput("intensity"));
50+
51+
customBlock2.output.connectTo(smartFilter.output);
52+
});
53+
54+
it("a smart filter using those blocks can be optimized without error", () => {
55+
const optimizer = new SmartFilterOptimizer(smartFilter);
56+
expect(optimizer.optimize()).not.toBeNull();
57+
});
58+
});
59+
});

0 commit comments

Comments
 (0)