@shaderfrog/core 0.0.1
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.
- package/.eslintrc.json +3 -0
- package/.prettierrc.js +3 -0
- package/README.md +3 -0
- package/babel.config.js +6 -0
- package/package.json +47 -0
- package/src/ast/manipulate.ts +392 -0
- package/src/ast/shader-sections.ts +323 -0
- package/src/core/engine.ts +214 -0
- package/src/core/file.js +53 -0
- package/src/core/graph.ts +1007 -0
- package/src/core/nodes/code-nodes.ts +66 -0
- package/src/core/nodes/core-node.ts +48 -0
- package/src/core/nodes/data-nodes.ts +344 -0
- package/src/core/nodes/edge.ts +23 -0
- package/src/core/nodes/engine-node.ts +266 -0
- package/src/core/strategy.ts +520 -0
- package/src/core.test.ts +312 -0
- package/src/plugins/babylon/bablyengine.ts +670 -0
- package/src/plugins/babylon/examples.ts +512 -0
- package/src/plugins/babylon/importers.ts +69 -0
- package/src/plugins/babylon/index.ts +6 -0
- package/src/plugins/three/examples.ts +680 -0
- package/src/plugins/three/importers.ts +18 -0
- package/src/plugins/three/index.ts +6 -0
- package/src/plugins/three/threngine.tsx +571 -0
- package/src/util/ensure.ts +10 -0
- package/src/util/id.ts +2 -0
package/.eslintrc.json
ADDED
package/.prettierrc.js
ADDED
package/README.md
ADDED
package/babel.config.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shaderfrog/core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Shaderfrog core",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/ShaderFrog/core.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "Andrew Ray",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/ShaderFrog/core/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/ShaderFrog/core#readme",
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@babel/core": "^7.21.8",
|
|
21
|
+
"@babel/preset-env": "^7.21.5",
|
|
22
|
+
"@babel/preset-typescript": "^7.21.5",
|
|
23
|
+
"@types/jest": "^29.5.1",
|
|
24
|
+
"@types/lodash.groupby": "^4.6.7",
|
|
25
|
+
"babel-jest": "^29.5.0",
|
|
26
|
+
"babylonjs": "^6.2.0",
|
|
27
|
+
"jest": "^29.5.0",
|
|
28
|
+
"prettier": "^2.8.8",
|
|
29
|
+
"three": "^0.152.2",
|
|
30
|
+
"typescript": "^5.0.4"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"lodash.groupby": "^4.6.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"babylonjs": ">= 4",
|
|
37
|
+
"three": ">= 100"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"babylonjs": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"three": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions to work with ASTs
|
|
3
|
+
*/
|
|
4
|
+
import { parser, generate } from '@shaderfrog/glsl-parser';
|
|
5
|
+
import {
|
|
6
|
+
visit,
|
|
7
|
+
AstNode,
|
|
8
|
+
NodeVisitors,
|
|
9
|
+
ExpressionStatementNode,
|
|
10
|
+
FunctionNode,
|
|
11
|
+
AssignmentNode,
|
|
12
|
+
DeclarationStatementNode,
|
|
13
|
+
KeywordNode,
|
|
14
|
+
DeclarationNode,
|
|
15
|
+
} from '@shaderfrog/glsl-parser/ast';
|
|
16
|
+
import { Program } from '@shaderfrog/glsl-parser/ast';
|
|
17
|
+
import { ShaderStage } from '../core/graph';
|
|
18
|
+
|
|
19
|
+
export const findVec4Constructor = (ast: AstNode): AstNode | undefined => {
|
|
20
|
+
let parent: AstNode | undefined;
|
|
21
|
+
const visitors: NodeVisitors = {
|
|
22
|
+
function_call: {
|
|
23
|
+
enter: (path) => {
|
|
24
|
+
if (
|
|
25
|
+
'specifier' in path.node.identifier &&
|
|
26
|
+
path.node.identifier?.specifier?.token === 'vec4'
|
|
27
|
+
) {
|
|
28
|
+
parent = path.findParent((p) => 'right' in p.node)?.node;
|
|
29
|
+
path.skip();
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
visit(ast, visitors);
|
|
35
|
+
return parent;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const findAssignmentTo = (
|
|
39
|
+
ast: AstNode | Program,
|
|
40
|
+
assignTo: string
|
|
41
|
+
): ExpressionStatementNode | undefined => {
|
|
42
|
+
let assign: ExpressionStatementNode | undefined;
|
|
43
|
+
const visitors: NodeVisitors = {
|
|
44
|
+
expression_statement: {
|
|
45
|
+
enter: (path) => {
|
|
46
|
+
if (path.node.expression?.left?.identifier === assignTo) {
|
|
47
|
+
assign = path.node;
|
|
48
|
+
}
|
|
49
|
+
path.skip();
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
visit(ast, visitors);
|
|
54
|
+
return assign;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const findDeclarationOf = (
|
|
58
|
+
ast: AstNode | Program,
|
|
59
|
+
declarationOf: string
|
|
60
|
+
): DeclarationNode | undefined => {
|
|
61
|
+
let declaration: DeclarationNode | undefined;
|
|
62
|
+
const visitors: NodeVisitors = {
|
|
63
|
+
declaration_statement: {
|
|
64
|
+
enter: (path) => {
|
|
65
|
+
const foundDecl = path.node.declaration?.declarations?.find(
|
|
66
|
+
(decl: any) => decl?.identifier?.identifier === declarationOf
|
|
67
|
+
);
|
|
68
|
+
if (foundDecl) {
|
|
69
|
+
declaration = foundDecl;
|
|
70
|
+
}
|
|
71
|
+
path.skip();
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
visit(ast, visitors);
|
|
76
|
+
return declaration;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const from2To3 = (ast: Program, stage: ShaderStage) => {
|
|
80
|
+
const glOut = 'fragmentColor';
|
|
81
|
+
// TODO: add this back in when there's only one after the merge
|
|
82
|
+
// ast.program.unshift({
|
|
83
|
+
// type: 'preprocessor',
|
|
84
|
+
// line: '#version 300 es',
|
|
85
|
+
// _: '\n',
|
|
86
|
+
// });
|
|
87
|
+
if (stage === 'fragment') {
|
|
88
|
+
ast.program.unshift({
|
|
89
|
+
type: 'declaration_statement',
|
|
90
|
+
declaration: {
|
|
91
|
+
type: 'declarator_list',
|
|
92
|
+
specified_type: {
|
|
93
|
+
type: 'fully_specified_type',
|
|
94
|
+
qualifiers: [{ type: 'keyword', token: 'out', whitespace: ' ' }],
|
|
95
|
+
specifier: {
|
|
96
|
+
type: 'type_specifier',
|
|
97
|
+
specifier: { type: 'keyword', token: 'vec4', whitespace: ' ' },
|
|
98
|
+
quantifier: null,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
declarations: [
|
|
102
|
+
{
|
|
103
|
+
type: 'declaration',
|
|
104
|
+
identifier: {
|
|
105
|
+
type: 'identifier',
|
|
106
|
+
identifier: glOut,
|
|
107
|
+
whitespace: undefined,
|
|
108
|
+
},
|
|
109
|
+
quantifier: null,
|
|
110
|
+
operator: undefined,
|
|
111
|
+
initializer: undefined,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
commas: [],
|
|
115
|
+
},
|
|
116
|
+
semi: { type: 'literal', literal: ';', whitespace: '\n ' },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
visit(ast, {
|
|
120
|
+
function_call: {
|
|
121
|
+
enter: (path) => {
|
|
122
|
+
const identifier = path.node.identifier;
|
|
123
|
+
if (
|
|
124
|
+
'specifier' in identifier &&
|
|
125
|
+
identifier.specifier?.identifier === 'texture2D'
|
|
126
|
+
) {
|
|
127
|
+
identifier.specifier.identifier = 'texture';
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
identifier: {
|
|
132
|
+
enter: (path) => {
|
|
133
|
+
if (path.node.identifier === 'gl_FragColor') {
|
|
134
|
+
path.node.identifier = glOut;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
keyword: {
|
|
139
|
+
enter: (path) => {
|
|
140
|
+
if (
|
|
141
|
+
(path.node.token === 'attribute' || path.node.token === 'varying') &&
|
|
142
|
+
path.findParent((path) => path.node.type === 'declaration_statement')
|
|
143
|
+
) {
|
|
144
|
+
path.node.token =
|
|
145
|
+
stage === 'vertex' && path.node.token === 'varying' ? 'out' : 'in';
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const outDeclaration = (name: string): Object => ({
|
|
153
|
+
type: 'declaration_statement',
|
|
154
|
+
declaration: {
|
|
155
|
+
type: 'declarator_list',
|
|
156
|
+
specified_type: {
|
|
157
|
+
type: 'fully_specified_type',
|
|
158
|
+
qualifiers: [{ type: 'keyword', token: 'out', whitespace: ' ' }],
|
|
159
|
+
specifier: {
|
|
160
|
+
type: 'type_specifier',
|
|
161
|
+
specifier: { type: 'keyword', token: 'vec4', whitespace: ' ' },
|
|
162
|
+
quantifier: null,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
declarations: [
|
|
166
|
+
{
|
|
167
|
+
type: 'declaration',
|
|
168
|
+
identifier: {
|
|
169
|
+
type: 'identifier',
|
|
170
|
+
identifier: name,
|
|
171
|
+
whitespace: undefined,
|
|
172
|
+
},
|
|
173
|
+
quantifier: null,
|
|
174
|
+
operator: undefined,
|
|
175
|
+
initializer: undefined,
|
|
176
|
+
},
|
|
177
|
+
],
|
|
178
|
+
commas: [],
|
|
179
|
+
},
|
|
180
|
+
semi: { type: 'literal', literal: ';', whitespace: '\n ' },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
export const makeStatement = (stmt: string): AstNode => {
|
|
184
|
+
// console.log(stmt);
|
|
185
|
+
let ast;
|
|
186
|
+
try {
|
|
187
|
+
ast = parser.parse(
|
|
188
|
+
`${stmt};
|
|
189
|
+
`,
|
|
190
|
+
{ quiet: true }
|
|
191
|
+
);
|
|
192
|
+
} catch (error: any) {
|
|
193
|
+
console.error({ stmt, error });
|
|
194
|
+
throw new Error(`Error parsing stmt "${stmt}": ${error?.message}`);
|
|
195
|
+
}
|
|
196
|
+
// console.log(util.inspect(ast, false, null, true));
|
|
197
|
+
return ast.program[0];
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
export const makeFnStatement = (fnStmt: string): AstNode => {
|
|
201
|
+
let ast;
|
|
202
|
+
try {
|
|
203
|
+
ast = parser.parse(
|
|
204
|
+
`
|
|
205
|
+
void main() {
|
|
206
|
+
${fnStmt};
|
|
207
|
+
}`,
|
|
208
|
+
{ quiet: true }
|
|
209
|
+
);
|
|
210
|
+
} catch (error: any) {
|
|
211
|
+
console.error({ fnStmt, error });
|
|
212
|
+
throw new Error(`Error parsing fnStmt "${fnStmt}": ${error?.message}`);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// console.log(util.inspect(ast, false, null, true));
|
|
216
|
+
return (ast.program[0] as FunctionNode).body.statements[0];
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const makeExpression = (expr: string): AstNode => {
|
|
220
|
+
let ast;
|
|
221
|
+
try {
|
|
222
|
+
ast = parser.parse(
|
|
223
|
+
`void main() {
|
|
224
|
+
a = ${expr};
|
|
225
|
+
}`,
|
|
226
|
+
{ quiet: true }
|
|
227
|
+
);
|
|
228
|
+
} catch (error: any) {
|
|
229
|
+
console.error({ expr, error });
|
|
230
|
+
throw new Error(`Error parsing expr "${expr}": ${error?.message}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// console.log(util.inspect(ast, false, null, true));
|
|
234
|
+
return (ast.program[0] as FunctionNode).body.statements[0].expression.right;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const makeExpressionWithScopes = (expr: string): Program => {
|
|
238
|
+
let ast: Program;
|
|
239
|
+
try {
|
|
240
|
+
ast = parser.parse(
|
|
241
|
+
`void main() {
|
|
242
|
+
${expr};
|
|
243
|
+
}`,
|
|
244
|
+
{ quiet: true }
|
|
245
|
+
);
|
|
246
|
+
} catch (error: any) {
|
|
247
|
+
console.error({ expr, error });
|
|
248
|
+
throw new Error(`Error parsing expr "${expr}": ${error?.message}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// console.log(util.inspect(ast, false, null, true));
|
|
252
|
+
return {
|
|
253
|
+
type: 'program',
|
|
254
|
+
// Set the main() fn body scope as the global one
|
|
255
|
+
scopes: [ast.scopes[1]],
|
|
256
|
+
program: [(ast.program[0] as FunctionNode).body.statements[0].expression],
|
|
257
|
+
};
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export const findFn = (ast: Program, name: string): FunctionNode | undefined =>
|
|
261
|
+
ast.program.find(
|
|
262
|
+
(stmt): stmt is FunctionNode =>
|
|
263
|
+
stmt.type === 'function' && stmt.prototype.header.name.identifier === name
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
export const returnGlPosition = (fnName: string, ast: Program): void =>
|
|
267
|
+
convertVertexMain(fnName, ast, 'vec4', (assign) => assign.expression.right);
|
|
268
|
+
|
|
269
|
+
export const returnGlPositionHardCoded = (
|
|
270
|
+
fnName: string,
|
|
271
|
+
ast: Program,
|
|
272
|
+
returnType: string,
|
|
273
|
+
hardCodedReturn: string
|
|
274
|
+
): void =>
|
|
275
|
+
convertVertexMain(fnName, ast, returnType, () =>
|
|
276
|
+
makeExpression(hardCodedReturn)
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
export const returnGlPositionVec3Right = (fnName: string, ast: Program): void =>
|
|
280
|
+
convertVertexMain(fnName, ast, 'vec3', (assign) => {
|
|
281
|
+
let found: AstNode | undefined;
|
|
282
|
+
visit(assign, {
|
|
283
|
+
function_call: {
|
|
284
|
+
enter: (path) => {
|
|
285
|
+
const { node } = path;
|
|
286
|
+
if (
|
|
287
|
+
// @ts-ignore
|
|
288
|
+
node?.identifier?.specifier?.token === 'vec4' &&
|
|
289
|
+
node?.args?.[2]?.token?.includes('1.')
|
|
290
|
+
) {
|
|
291
|
+
found = node.args[0];
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
if (!found) {
|
|
297
|
+
console.error(generate(ast));
|
|
298
|
+
throw new Error(
|
|
299
|
+
'Could not find position assignment to convert to return!'
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return found;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const convertVertexMain = (
|
|
306
|
+
fnName: string,
|
|
307
|
+
ast: Program,
|
|
308
|
+
returnType: string,
|
|
309
|
+
generateRight: (positionAssign: ExpressionStatementNode) => AstNode
|
|
310
|
+
) => {
|
|
311
|
+
const mainReturnVar = `frogOut`;
|
|
312
|
+
|
|
313
|
+
const main = findFn(ast, fnName);
|
|
314
|
+
if (!main) {
|
|
315
|
+
throw new Error(`No ${fnName} fn found!`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Convert the main function to one that returns
|
|
319
|
+
(main.prototype.header.returnType.specifier.specifier as KeywordNode).token =
|
|
320
|
+
returnType;
|
|
321
|
+
|
|
322
|
+
// Find the gl_position assignment line
|
|
323
|
+
const assign = main.body.statements.find(
|
|
324
|
+
(stmt: AstNode) =>
|
|
325
|
+
stmt.type === 'expression_statement' &&
|
|
326
|
+
stmt.expression.left?.identifier === 'gl_Position'
|
|
327
|
+
);
|
|
328
|
+
if (!assign) {
|
|
329
|
+
throw new Error(`No gl position assign found in main fn!`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const rtnStmt = makeFnStatement(
|
|
333
|
+
`${returnType} ${mainReturnVar} = 1.0`
|
|
334
|
+
) as DeclarationStatementNode;
|
|
335
|
+
rtnStmt.declaration.declarations[0].initializer = generateRight(assign);
|
|
336
|
+
|
|
337
|
+
main.body.statements.splice(main.body.statements.indexOf(assign), 1, rtnStmt);
|
|
338
|
+
main.body.statements.push(makeFnStatement(`return ${mainReturnVar}`));
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export const convert300MainToReturn = (fnName: string, ast: Program): void => {
|
|
342
|
+
const mainReturnVar = `frogOut`;
|
|
343
|
+
|
|
344
|
+
// Find the output variable, as in "pc_fragColor" from "out highp vec4 pc_fragColor;"
|
|
345
|
+
let outName: string | undefined;
|
|
346
|
+
ast.program.find((line, index) => {
|
|
347
|
+
if (
|
|
348
|
+
line.type === 'declaration_statement' &&
|
|
349
|
+
line.declaration?.specified_type?.qualifiers?.find(
|
|
350
|
+
(n: KeywordNode) => n.token === 'out'
|
|
351
|
+
) &&
|
|
352
|
+
line.declaration.specified_type.specifier.specifier.token === 'vec4'
|
|
353
|
+
) {
|
|
354
|
+
// Remove the out declaration
|
|
355
|
+
ast.program.splice(index, 1);
|
|
356
|
+
outName = line.declaration.declarations[0].identifier.identifier;
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
if (!outName) {
|
|
361
|
+
console.error(generate(ast));
|
|
362
|
+
throw new Error('No "out vec4" line found in the fragment shader');
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
visit(ast, {
|
|
366
|
+
identifier: {
|
|
367
|
+
enter: (path) => {
|
|
368
|
+
if (path.node.identifier === outName) {
|
|
369
|
+
path.node.identifier = mainReturnVar;
|
|
370
|
+
// @ts-ignore
|
|
371
|
+
path.node.doNotDescope = true; // hack because this var is in the scope which gets renamed later
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
function: {
|
|
376
|
+
enter: (path) => {
|
|
377
|
+
if (path.node.prototype.header.name.identifier === fnName) {
|
|
378
|
+
(
|
|
379
|
+
path.node.prototype.header.returnType.specifier
|
|
380
|
+
.specifier as KeywordNode
|
|
381
|
+
).token = 'vec4';
|
|
382
|
+
path.node.body.statements.unshift(
|
|
383
|
+
makeFnStatement(`vec4 ${mainReturnVar}`)
|
|
384
|
+
);
|
|
385
|
+
path.node.body.statements.push(
|
|
386
|
+
makeFnStatement(`return ${mainReturnVar}`)
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
};
|