@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 ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
package/.prettierrc.js ADDED
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ singleQuote: true,
3
+ };
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # Shaderfrog Core
2
+
3
+ Experimental. Do not use this.
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ presets: [
3
+ ['@babel/preset-env', { targets: { node: 'current' } }],
4
+ '@babel/preset-typescript',
5
+ ],
6
+ };
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
+ };