Skip to content

Commit bd03102

Browse files
Ne0nWindssunag
andauthored
TSL Transpiler: Support struct definitions and declarations (#32300)
Co-authored-by: sunag <sunagbrasil@gmail.com>
1 parent 47a70dc commit bd03102

File tree

6 files changed

+181
-9
lines changed

6 files changed

+181
-9
lines changed

examples/jsm/transpiler/AST.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ export class ASTNode {
4646

4747
}
4848

49+
getProgram() {
50+
51+
let current = this;
52+
53+
while ( current.parent !== null ) {
54+
55+
current = current.parent;
56+
57+
}
58+
59+
return current.isProgram === true ? current : null;
60+
61+
}
62+
4963
getParent( parents = [] ) {
5064

5165
if ( this.parent === null ) {
@@ -114,6 +128,7 @@ export class Program extends ASTNode {
114128
super();
115129

116130
this.body = body;
131+
this.structTypes = new Map();
117132

118133
this.isProgram = true;
119134

@@ -629,3 +644,32 @@ export class SwitchCase extends ASTNode {
629644
}
630645

631646
}
647+
648+
// helper class for StructDefinition
649+
export class StructMember {
650+
651+
constructor( type, name ) {
652+
653+
this.type = type;
654+
this.name = name;
655+
this.isStructMember = true;
656+
657+
}
658+
659+
}
660+
661+
export class StructDefinition extends ASTNode {
662+
663+
constructor( name, members = [] ) {
664+
665+
super();
666+
667+
this.name = name;
668+
this.members = members;
669+
this.isStructDefinition = true;
670+
671+
this.initialize();
672+
673+
}
674+
675+
}

examples/jsm/transpiler/GLSLDecoder.js

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Program, FunctionDeclaration, Switch, For, AccessorElements, Ternary, Varying, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, String, FunctionCall, Return, Accessor, Uniform, Discard, SwitchCase, Continue, Break, While, Comment } from './AST.js';
1+
import { Program, FunctionDeclaration, Switch, For, AccessorElements, Ternary, Varying, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, String, FunctionCall, Return, Accessor, Uniform, Discard, SwitchCase, Continue, Break, While, Comment, StructMember, StructDefinition } from './AST.js';
22

3-
import { isType } from './TranspilerUtils.js';
3+
import { isBuiltinType } from './TranspilerUtils.js';
44

55
const unaryOperators = [
66
'+', '-', '~', '!', '++', '--'
@@ -255,6 +255,7 @@ class GLSLDecoder {
255255
this.index = 0;
256256
this.tokenizer = null;
257257
this.keywords = [];
258+
this.structTypes = new Map();
258259

259260
this.addPolyfill( 'gl_FragCoord', 'vec3 gl_FragCoord = vec3( screenCoordinate.x, screenCoordinate.y.oneMinus(), screenCoordinate.z );' );
260261

@@ -780,6 +781,54 @@ class GLSLDecoder {
780781

781782
}
782783

784+
parseStructDefinition() {
785+
786+
const tokens = this.readTokensUntil( ';' );
787+
788+
const structName = tokens[ 1 ].str;
789+
790+
if ( tokens[ 2 ].str !== '{' ) {
791+
792+
throw new Error( 'Expected \'{\' after struct name ' );
793+
794+
}
795+
796+
const structMembers = [];
797+
for ( let i = 3; i < tokens.length - 2; i += 3 ) {
798+
799+
const typeToken = tokens[ i ];
800+
const nameToken = tokens[ i + 1 ];
801+
802+
if ( typeToken.type != 'literal' || nameToken.type != 'literal' ) {
803+
804+
throw new Error( 'Invalid struct declaration' );
805+
806+
}
807+
808+
if ( tokens[ i + 2 ].str !== ';' ) {
809+
810+
throw new Error( 'Missing \';\' after struct member name' );
811+
812+
}
813+
814+
const member = new StructMember( typeToken.str, nameToken.str );
815+
structMembers.push( member );
816+
817+
}
818+
819+
if ( tokens[ tokens.length - 2 ].str !== '}' ) {
820+
821+
throw new Error( 'Missing closing \'}\' for struct ' + structName );
822+
823+
}
824+
825+
const definition = new StructDefinition( structName, structMembers );
826+
this.structTypes.set( structName, definition );
827+
828+
return definition;
829+
830+
}
831+
783832
parseReturn() {
784833

785834
this.readToken(); // skip 'return'
@@ -827,7 +876,9 @@ class GLSLDecoder {
827876

828877
let initialization;
829878

830-
if ( initializationTokens[ 0 ] && isType( initializationTokens[ 0 ].str ) ) {
879+
const firstToken = initializationTokens[ 0 ];
880+
881+
if ( firstToken && ( isBuiltinType( firstToken.str ) || this.structTypes.has( firstToken.str ) ) ) {
831882

832883
initialization = this.parseVariablesFromToken( initializationTokens );
833884

@@ -1079,7 +1130,11 @@ class GLSLDecoder {
10791130

10801131
statement = this.parseVarying();
10811132

1082-
} else if ( isType( token.str ) ) {
1133+
} else if ( token.str === 'struct' ) {
1134+
1135+
statement = this.parseStructDefinition();
1136+
1137+
} else if ( isBuiltinType( token.str ) || this.structTypes.has( token.str ) ) {
10831138

10841139
if ( this.getToken( 2 ).str === '(' ) {
10851140

@@ -1159,7 +1214,9 @@ class GLSLDecoder {
11591214
this.tokenizer = new Tokenizer( polyfill + source ).tokenize();
11601215

11611216
const body = this.parseBlock();
1217+
11621218
const program = new Program( body );
1219+
program.structTypes = this.structTypes;
11631220

11641221
return program;
11651222

examples/jsm/transpiler/ShaderToyDecoder.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class ShaderToyDecoder extends GLSLDecoder {
3838
node.body.unshift( new VariableDeclaration( 'vec4', 'fragColor' ) );
3939
node.body.push( new Return( fragColor ) );
4040

41+
node.initialize();
42+
4143
}
4244

4345
return node;

examples/jsm/transpiler/TSLEncoder.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,10 @@ class TSLEncoder {
314314

315315
code = this.emitVarying( node );
316316

317+
} else if ( node.isStructDefinition ) {
318+
319+
code = this.emitStructDefinition( node );
320+
317321
} else if ( node.isTernary ) {
318322

319323
code = this.emitTernary( node );
@@ -669,9 +673,19 @@ ${ this.tab }} )`;
669673

670674
} else {
671675

672-
varStr += ` = property( '${ type }' )`;
676+
const program = node.getProgram();
677+
678+
if ( program.structTypes.has( type ) ) {
679+
680+
varStr += ` = ${ type }()`;
681+
682+
} else {
683+
684+
varStr += ` = property( '${ type }' )`;
673685

674-
this.addImport( 'property' );
686+
this.addImport( 'property' );
687+
688+
}
675689

676690
}
677691

@@ -702,6 +716,34 @@ ${ this.tab }} )`;
702716

703717
}
704718

719+
emitStructDefinition( node ) {
720+
721+
const { name, members } = node;
722+
723+
this.addImport( 'struct' );
724+
725+
let structString = `const ${ name } = struct( {\n`;
726+
727+
for ( let i = 0; i < members.length; i += 1 ) {
728+
729+
const member = members[ i ];
730+
731+
structString += `${this.tab}\t${member.name}: '${member.type}'`;
732+
733+
if ( i != members.length - 1 ) {
734+
735+
structString += ',\n';
736+
737+
}
738+
739+
}
740+
741+
structString += `\n${this.tab}}, \'${name}\' )`;
742+
743+
return structString;
744+
745+
}
746+
705747
emitOverloadingFunction( nodes ) {
706748

707749
const { name } = nodes[ 0 ];

examples/jsm/transpiler/TranspilerUtils.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export function isExpression( st ) {
22

3-
return st.isFunctionDeclaration !== true && st.isFor !== true && st.isWhile !== true && st.isConditional !== true && st.isSwitch !== true;
3+
return st.isFunctionDeclaration !== true && st.isFor !== true && st.isWhile !== true && st.isConditional !== true && st.isSwitch !== true && st.isStructDefinition !== true;
44

55
}
66

@@ -10,9 +10,9 @@ export function isPrimitive( value ) {
1010

1111
}
1212

13-
export function isType( str ) {
13+
export function isBuiltinType( str ) {
1414

15-
return /void|bool|float|u?int|mat[234]|mat[234]x[234]|(u|i|b)?vec[234]/.test( str );
15+
return /^(void|bool|float|u?int|mat[234]|mat[234]x[234]|(u|i|b)?vec[234])$/.test( str );
1616

1717
}
1818

examples/jsm/transpiler/WGSLEncoder.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,10 @@ class WGSLEncoder {
300300
this.varyings.push( node );
301301
return ''; // Defer emission to the header
302302

303+
} else if ( node.isStructDefinition ) {
304+
305+
code = this.emitStructDefinition( node );
306+
303307
} else if ( node.isTernary ) {
304308

305309
const cond = this.emitExpression( node.cond );
@@ -584,6 +588,29 @@ class WGSLEncoder {
584588

585589
}
586590

591+
emitStructDefinition( node ) {
592+
593+
const { name, members } = node;
594+
595+
let structString = `struct ${ name } {\n`;
596+
597+
for ( let i = 0; i < members.length; i += 1 ) {
598+
599+
const member = members[ i ];
600+
601+
structString += `${ this.tab }\t${ member.name }: ${ this.getWgslType( member.type ) }`;
602+
603+
const delimiter = ( i != members.length - 1 ) ? ',\n' : '\n';
604+
structString += delimiter;
605+
606+
}
607+
608+
structString += this.tab + '}';
609+
610+
return structString;
611+
612+
}
613+
587614
emitFunction( node ) {
588615

589616
const name = node.name;

0 commit comments

Comments
 (0)