1- import { d , ShaderGenerator , WgslGenerator } from 'typegpu' ;
1+ import { NodeTypeCatalog as NODE } from 'tinyest' ;
2+ import type { Return } from 'tinyest' ;
3+ import tgpu , { d , ShaderGenerator , WgslGenerator } from 'typegpu' ;
4+
5+ type ResolutionCtx = ShaderGenerator . ResolutionCtx ;
6+
7+ const UnknownData : typeof ShaderGenerator . UnknownData = ShaderGenerator . UnknownData ;
28
39// ----------
410// WGSL → GLSL type name mapping
511// ----------
612
713const WGSL_TO_GLSL_TYPE : Record < string , string > = {
14+ void : 'void' ,
815 f32 : 'float' ,
916 u32 : 'uint' ,
1017 i32 : 'int' ,
@@ -37,13 +44,43 @@ export function translateWgslTypeToGlsl(wgslType: string): string {
3744 return WGSL_TO_GLSL_TYPE [ wgslType ] ?? wgslType ;
3845}
3946
47+ /**
48+ * Resolves a struct and adds its declaration to the resolution context.
49+ * @param ctx - The resolution context.
50+ * @param struct - The struct to resolve.
51+ *
52+ * @returns The resolved struct name.
53+ */
54+ function resolveStruct ( ctx : ResolutionCtx , struct : d . WgslStruct ) {
55+ const id = ctx . makeUniqueIdentifier ( ShaderGenerator . getName ( struct ) , 'global' ) ;
56+
57+ ctx . addDeclaration ( `\
58+ struct ${ id } {
59+ ${ Object . entries ( struct . propTypes )
60+ . map ( ( [ prop , type ] ) => ` ${ ctx . resolve ( type ) . value } ${ prop } ;\n` )
61+ . join ( '' ) } \
62+ };` ) ;
63+
64+ return id ;
65+ }
66+
67+ const gl_PositionSnippet = tgpu [ '~unstable' ] . rawCodeSnippet ( 'gl_Position' , d . vec4f , 'private' ) ;
68+
69+ interface EntryFnState {
70+ structPropToVarMap : Record < string , string > ;
71+ outVars : { varName : string ; propName : string } [ ] ;
72+ }
73+
4074/**
4175 * A GLSL ES 3.0 shader generator that extends WgslGenerator.
4276 * Overrides `dataType` to emit GLSL type names instead of WGSL ones,
4377 * and overrides variable declaration emission to use `type name = rhs` syntax.
4478 */
4579export class GlslGenerator extends WgslGenerator {
46- public override typeAnnotation ( data : d . BaseData ) : string {
80+ #functionType: ShaderGenerator . TgpuShaderStage | 'normal' | undefined ;
81+ #entryFnState: EntryFnState | undefined ;
82+
83+ override typeAnnotation ( data : d . BaseData ) : string {
4784 // For WGSL identity types (scalars, vectors, common matrices), map to GLSL directly.
4885 if ( ! d . isLooseData ( data ) ) {
4986 const glslName = WGSL_TO_GLSL_TYPE [ data . type ] ;
@@ -52,20 +89,153 @@ export class GlslGenerator extends WgslGenerator {
5289 }
5390 }
5491
92+ if ( d . isWgslStruct ( data ) ) {
93+ return resolveStruct ( this . ctx , data ) ;
94+ }
95+
5596 // For all other types (structs, arrays, etc.) delegate to WGSL resolution.
5697 return super . typeAnnotation ( data ) ;
5798 }
5899
59- protected override emitVarDecl (
60- pre : string ,
100+ override _emitVarDecl (
61101 _keyword : 'var' | 'let' | 'const' ,
62102 name : string ,
63103 dataType : d . BaseData | ShaderGenerator . UnknownData ,
64104 rhsStr : string ,
65105 ) : string {
66- const glslTypeName =
67- dataType !== ShaderGenerator . UnknownData ? this . typeAnnotation ( dataType ) : 'auto' ;
68- return `${ pre } ${ glslTypeName } ${ name } = ${ rhsStr } ;` ;
106+ const glslTypeName = dataType !== UnknownData ? this . ctx . resolve ( dataType ) . value : 'auto' ;
107+ return `${ this . ctx . pre } ${ glslTypeName } ${ name } = ${ rhsStr } ;` ;
108+ }
109+
110+ override _return ( statement : Return ) : string {
111+ const exprNode = statement [ 1 ] ;
112+
113+ if ( exprNode === undefined ) {
114+ // Default behavior
115+ return super . _return ( statement ) ;
116+ }
117+
118+ if ( this . #functionType !== 'normal' ) {
119+ // oxlint-disable-next-line no-non-null-assertion
120+ const entryFnState = this . #entryFnState! ;
121+ const expectedReturnType = this . ctx . topFunctionReturnType ;
122+
123+ if ( typeof exprNode === 'object' && exprNode [ 0 ] === NODE . objectExpr ) {
124+ const transformed = Object . entries ( exprNode [ 1 ] ) . map ( ( [ prop , rhsNode ] ) => {
125+ let name : string | undefined = entryFnState . structPropToVarMap [ prop ] ;
126+ if ( name === undefined ) {
127+ if (
128+ prop === '$position' ||
129+ ( expectedReturnType &&
130+ d . isWgslStruct ( expectedReturnType ) &&
131+ expectedReturnType . propTypes [ prop ] === d . builtin . position )
132+ ) {
133+ name = 'gl_Position' ;
134+ } else {
135+ name = this . ctx . makeUniqueIdentifier ( prop , 'global' ) ;
136+ entryFnState . outVars . push ( { varName : name , propName : prop } ) ;
137+ }
138+ entryFnState . structPropToVarMap [ prop ] = name ;
139+ }
140+ const rhsExpr = this . _expression ( rhsNode ) ;
141+ const type = rhsExpr . dataType as d . BaseData ;
142+
143+ const snippet = tgpu [ '~unstable' ] . rawCodeSnippet ( name , type as d . AnyData , 'private' ) ;
144+
145+ return {
146+ name,
147+ snippet,
148+ assignment : [ NODE . assignmentExpr , name , '=' , rhsNode ] ,
149+ } as const ;
150+ } ) ;
151+
152+ const block = super . _block (
153+ [ NODE . block , [ ...transformed . map ( ( t ) => t . assignment ) , [ NODE . return ] ] ] ,
154+ Object . fromEntries (
155+ transformed . map ( ( { name, snippet } ) => {
156+ return [ name , snippet . $ ] as const ;
157+ } ) ,
158+ ) ,
159+ ) ;
160+
161+ return `${ this . ctx . pre } ${ block } ` ;
162+ } else {
163+ // Resolving the expression to inspect it's type
164+ // We will resolve it again as part of the modifed statement
165+ const expr = expectedReturnType
166+ ? this . _typedExpression ( exprNode , expectedReturnType )
167+ : this . _expression ( exprNode ) ;
168+
169+ if ( expr . dataType === UnknownData ) {
170+ // Unknown data type, don't know what to do
171+ return super . _return ( statement ) ;
172+ }
173+
174+ if ( expr . dataType . type . startsWith ( 'vec' ) ) {
175+ const block = super . _block (
176+ [ NODE . block , [ [ NODE . assignmentExpr , 'gl_Position' , '=' , exprNode ] , [ NODE . return ] ] ] ,
177+ { gl_Position : gl_PositionSnippet . $ } ,
178+ ) ;
179+
180+ return `${ this . ctx . pre } ${ block } ` ;
181+ }
182+ }
183+ }
184+
185+ return super . _return ( statement ) ;
186+ }
187+
188+ override functionDefinition ( options : ShaderGenerator . FunctionDefinitionOptions ) : string {
189+ if ( options . functionType !== 'normal' ) {
190+ this . ctx . reserveIdentifier ( 'gl_Position' , 'global' ) ;
191+ }
192+
193+ // Function body
194+ let lastFunctionType = this . #functionType;
195+ this . #functionType = options . functionType ;
196+ if ( options . functionType !== 'normal' ) {
197+ if ( this . #entryFnState) {
198+ throw new Error ( 'Cannot nest entry functions' ) ;
199+ }
200+ this . #entryFnState = { structPropToVarMap : { } , outVars : [ ] } ;
201+ }
202+
203+ try {
204+ const body = this . _block ( options . body ) ;
205+
206+ // Only after generating the body can we determine the return type
207+ const returnType = options . determineReturnType ( ) ;
208+
209+ if ( options . functionType !== 'normal' ) {
210+ // oxlint-disable-next-line no-non-null-assertion
211+ const entryFnState = this . #entryFnState! ;
212+ if ( d . isWgslStruct ( returnType ) ) {
213+ for ( const { varName, propName } of entryFnState . outVars ) {
214+ const dataType = returnType . propTypes [ propName ] ;
215+ if ( dataType && d . isDecorated ( dataType ) ) {
216+ const location = ( dataType . attribs as d . AnyAttribute [ ] ) . find (
217+ ( a ) => a . type === '@location' ,
218+ ) ?. params [ 0 ] ;
219+ this . ctx . addDeclaration ( `layout(location = ${ location } ) out ${ varName } ;` ) ;
220+ }
221+ }
222+ }
223+ return `void main() ${ body } ` ;
224+ }
225+
226+ const argList = options . args
227+ // Stripping out unused arguments in entry functions
228+ . filter ( ( arg ) => arg . used || options . functionType === 'normal' )
229+ . map ( ( arg ) => {
230+ return `${ this . ctx . resolve ( arg . decoratedType ) . value } ${ arg . name } ` ;
231+ } )
232+ . join ( ', ' ) ;
233+
234+ return `${ this . ctx . resolve ( returnType ) . value } ${ options . name } (${ argList } ) ${ body } ` ;
235+ } finally {
236+ this . #functionType = lastFunctionType ;
237+ this . #entryFnState = undefined ;
238+ }
69239 }
70240}
71241
0 commit comments