@@ -45,7 +45,7 @@ export abstract class GLESVisitor extends CodeGenVisitor {
4545
4646 // Build combined _globalStructVarMap from both entry functions before per-stage processing.
4747 // This must happen here because vertex runs first and doesn't yet know fragment's variables.
48- this . _buildGlobalStructVarMap ( vertexEntry , fragmentEntry , shaderData , context ) ;
48+ this . _buildGlobalStructVarMap ( vertexEntry , fragmentEntry , shaderData , outerGlobalMacroDeclarations , context ) ;
4949
5050 return {
5151 vertex : this . _vertexMain ( vertexEntry , shaderData , outerGlobalMacroDeclarations ) ,
@@ -215,6 +215,7 @@ export abstract class GLESVisitor extends CodeGenVisitor {
215215 vertexEntry : string ,
216216 fragmentEntry : string ,
217217 data : ShaderData ,
218+ globalMacros : ASTNode . GlobalDeclaration [ ] ,
218219 context : VisitorContext
219220 ) : void {
220221 const map = context . _globalStructVarMap ;
@@ -266,6 +267,33 @@ export abstract class GLESVisitor extends CodeGenVisitor {
266267 }
267268 this . _collectStructVarsFromBody ( fnNode . statements , structTypeRoles , map ) ;
268269 }
270+
271+ // Also scan global macros for root variable names that might be global struct variables.
272+ // e.g. #define VSOutput_worldPos o.v_worldPos → root "o" → look up in symbol table
273+ let hasRoles = false ;
274+ for ( const _ in structTypeRoles ) {
275+ hasRoles = true ;
276+ break ;
277+ }
278+ if ( hasRoles ) {
279+ const checked = new Set < string > ( ) ;
280+ const symOut : SymbolInfo [ ] = [ ] ;
281+ this . _forEachMacroMemberAccess ( globalMacros , ( rootName ) => {
282+ if ( map [ rootName ] || checked . has ( rootName ) ) return ;
283+ checked . add ( rootName ) ;
284+ lookupSymbol . set ( rootName , ESymbolType . VAR ) ;
285+ symbolTable . getSymbols ( lookupSymbol , true , symOut ) ;
286+ for ( const sym of symOut ) {
287+ if ( sym . dataType ) {
288+ const role = structTypeRoles [ sym . dataType . typeLexeme ] ;
289+ if ( role ) {
290+ map [ rootName ] = role ;
291+ break ;
292+ }
293+ }
294+ }
295+ } ) ;
296+ }
269297 }
270298
271299 private _collectStructVarsFromBody (
@@ -296,18 +324,36 @@ export abstract class GLESVisitor extends CodeGenVisitor {
296324 */
297325 private _registerGlobalMacroReferences ( globalMacros : ASTNode . GlobalDeclaration [ ] , context : VisitorContext ) : void {
298326 const map = context . _globalStructVarMap ;
299- if ( ! Object . keys ( map ) . length ) return ;
327+ let hasEntries = false ;
328+ for ( const _ in map ) {
329+ hasEntries = true ;
330+ break ;
331+ }
332+ if ( ! hasEntries ) return ;
333+ this . _forEachMacroMemberAccess ( globalMacros , ( rootName , propName ) => {
334+ const role = map [ rootName ] ;
335+ if ( role ) context . referenceStructPropByName ( role , propName ) ;
336+ } ) ;
337+ }
338+
339+ /**
340+ * Traverse global macro declarations, extracting member access patterns (e.g. "o.v_uv")
341+ * and invoking the callback with (rootName, propName) for each match.
342+ */
343+ private _forEachMacroMemberAccess (
344+ macros : ASTNode . GlobalDeclaration [ ] ,
345+ callback : ( rootName : string , propName : string ) => void
346+ ) : void {
300347 const reg = CodeGenVisitor . _memberAccessReg ;
301- for ( const macro of globalMacros ) {
302- this . _scanMacroMemberAccess ( macro . children , reg , map , context ) ;
348+ for ( const macro of macros ) {
349+ this . _walkMacroChildren ( macro . children , reg , callback ) ;
303350 }
304351 }
305352
306- private _scanMacroMemberAccess (
353+ private _walkMacroChildren (
307354 children : NodeChild [ ] ,
308355 reg : RegExp ,
309- map : Record < string , "varying" | "attribute" | "mrt" > ,
310- context : VisitorContext
356+ callback : ( rootName : string , propName : string ) => void
311357 ) : void {
312358 for ( const child of children ) {
313359 if ( child instanceof BaseToken && child . type === Keyword . MACRO_DEFINE_EXPRESSION ) {
@@ -317,19 +363,20 @@ export abstract class GLESVisitor extends CodeGenVisitor {
317363 reg . lastIndex = 0 ;
318364 let match : RegExpExecArray | null ;
319365 while ( ( match = reg . exec ( value ) ) !== null ) {
320- const role = map [ match [ 1 ] ] ;
321- if ( role ) context . referenceStructPropByName ( role , match [ 2 ] ) ;
366+ callback ( match [ 1 ] , match [ 2 ] ) ;
322367 }
323368 } else if ( child instanceof TreeNode ) {
324- this . _scanMacroMemberAccess ( child . children , reg , map , context ) ;
369+ this . _walkMacroChildren ( child . children , reg , callback ) ;
325370 }
326371 }
327372 }
328373
329374 private _getGlobalSymbol ( out : ICodeSegment [ ] ) : void {
330- const { _referencedGlobals } = VisitorContext . context ;
375+ const context = VisitorContext . context ;
376+ const { _referencedGlobals } = context ;
331377
332- const lastLength = Object . keys ( _referencedGlobals ) . length ;
378+ let lastLength = 0 ;
379+ for ( const _ in _referencedGlobals ) lastLength ++ ;
333380 if ( lastLength === 0 ) return ;
334381
335382 for ( const ident in _referencedGlobals ) {
@@ -339,7 +386,9 @@ export abstract class GLESVisitor extends CodeGenVisitor {
339386 const symbols = _referencedGlobals [ ident ] ;
340387 for ( let i = 0 ; i < symbols . length ; i ++ ) {
341388 const sm = symbols [ i ] ;
342- const text = sm . astNode . codeGen ( this ) + ( sm . type === ESymbolType . VAR ? ";" : "" ) ;
389+ const codeGenResult = sm . astNode . codeGen ( this ) ;
390+ if ( ! codeGenResult ) continue ;
391+ const text = codeGenResult + ( sm . type === ESymbolType . VAR ? ";" : "" ) ;
343392 if ( ! sm . isInMacroBranch ) {
344393 out . push ( {
345394 text,
@@ -349,7 +398,9 @@ export abstract class GLESVisitor extends CodeGenVisitor {
349398 }
350399 }
351400
352- if ( Object . keys ( _referencedGlobals ) . length !== lastLength ) {
401+ let newLength = 0 ;
402+ for ( const _ in _referencedGlobals ) newLength ++ ;
403+ if ( newLength !== lastLength ) {
353404 this . _getGlobalSymbol ( out ) ;
354405 }
355406 }
0 commit comments