Skip to content

fix(shader-lab): transform struct member access in #define values#2967

Open
zhuxudong wants to merge 7 commits intogalacean:dev/2.0from
zhuxudong:fix/shaderlab-define-struct-access
Open

fix(shader-lab): transform struct member access in #define values#2967
zhuxudong wants to merge 7 commits intogalacean:dev/2.0from
zhuxudong:fix/shaderlab-define-struct-access

Conversation

@zhuxudong
Copy link
Copy Markdown
Member

@zhuxudong zhuxudong commented Apr 15, 2026

Summary

  • Support #define VAR o.v_uv style macros: CodeGen now strips struct variable prefix during transformation
  • Handle global #define with cross-stage struct variable transformation
  • Skip type inference for member access macros in semantic analysis (prevents wrong type resolution)
  • Handle global struct-typed variable declarations (suppress incorrect uniform output)
  • Add MacroValueType.MemberAccess classification in Preprocessor

Test plan

  • Added define-struct-access.shader with expected GLSL output snapshot comparison
  • Added define-struct-access-global.shader for global define cross-stage test
  • Added macro-member-access-builtin-arg.shader for Cocos FSInput pattern
  • Added global-varying-var.shader for Cocos VSOutput pattern
  • Verified no duplicate varying declarations and no uniform Varyings in output

Summary by CodeRabbit

Release Notes

  • New Features

    • Added support for shader macro definitions with member-access patterns (e.g., input.v_uv, v.rgb).
  • Bug Fixes

    • Improved type inference accuracy for member-access expressions in macro expansions.
  • Tests

    • Added test cases validating struct member-access patterns in shader definitions and macro expansions.

…ing CodeGen

When #define values contain struct member access like `o.v_uv` (where `o` is
a Varyings/Attributes/MRT struct variable), the CodeGen now correctly transforms
them to just the property name (e.g. `v_uv`), matching the behavior of direct
struct member access in regular code. Closes galacean#2944.
…uct-access

Verify the actual CodeGen output instead of just checking GLSL compilation:
- #define values with struct member access are correctly transformed
- varying/attribute declarations are emitted for referenced properties
…ions

Add assertions for macro usage in expressions (not just #define transformation):
- Macro as RHS in multiplication, as LHS in assignment
- Macro as function argument in dot(), texture2D()
- Multiple varying properties (v_uv, v_normal) referenced via #define
…ransform

Build a combined _globalStructVarMap in visitShaderProgram by scanning
both vertex and fragment entry functions, so global #define values like
`attr.POSITION` or `o.v_uv` are correctly transformed in all stages.

Rewrite define-struct-access tests to use snapshot file comparison
against expected/ GLSL outputs for clearer verification.
…cro scanning

- Suppress `uniform` output for global struct-typed variables (e.g. `Varyings o;`)
- Register global struct vars in both per-function and cross-stage maps
- Unify macro member access scanning into callback-based _forEachMacroMemberAccess
- Add registerStructVar() encapsulation in VisitorContext
- Add Cocos VSOutput pattern test (global-varying-var)
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

Walkthrough

The shader compiler now supports member-access patterns in macro definitions (e.g., input.v_uv, v.rgb). A new MacroValueType.MemberAccess enum variant detects these patterns, and code generation tracks struct-to-role mappings (varying, attribute, mrt), registers struct property references, and omits struct uniforms for typed globals matching these roles.

Changes

Cohort / File(s) Summary
Macro Detection
packages/shader-lab/src/Preprocessor.ts
Added MacroValueType.MemberAccess enum variant with regex to identify member-access patterns; updated _parseMacroDefines to classify matching values and getReferenceSymbolNames to extract root symbols from member-access expressions.
Type Inference
packages/shader-lab/src/parser/AST.ts
Updated VariableIdentifier.semanticAnalyze to skip type assignment when identifier comes from a member-access macro, leaving type as TypeAny; added _isMemberAccessMacro helper to detect these patterns.
Struct-Role Tracking
packages/shader-lab/src/codeGen/VisitorContext.ts
Added _structVarMap and _globalStructVarMap to track variable-to-role mappings; introduced getStructRole, registerStructVar, and referenceStructPropByName public methods to manage struct property references during codegen.
Code Generation Logic
packages/shader-lab/src/codeGen/CodeGenVisitor.ts
Added _transformMacroDefineValue() helper to rewrite macro expressions by detecting <var>.<prop> patterns and registering references; extended defaultCodeGen() to transform MACRO_DEFINE_EXPRESSION tokens; modified visitGlobalVariableDeclaration() to register struct vars and skip emission for varying/attribute/mrt types; added _collectStructVars() to map function parameters and local declarations to roles.
Global Struct Resolution
packages/shader-lab/src/codeGen/GLESVisitor.ts
Computes _globalStructVarMap from entry function parameters, local declarations, and global macro patterns before stage codegen; added per-stage pre-registration to call referenceStructPropByName for all member-access macro properties; extended macro codegen to transform expressions using the global map.
Tests
tests/src/shader-lab/ShaderLab.test.ts
Added four test cases: "define-struct-access-global" and "define-struct-access" validate parsed GLSL output for struct member access in macros; "macro-member-access-builtin-arg" and "global-varying-var" validate macro expansion, builtin call presence, and absence of uniform struct declarations.

Sequence Diagram(s)

sequenceDiagram
    actor Parser
    participant Preprocessor
    participant AST
    participant VisitorContext
    participant CodeGenVisitor
    participant GLESVisitor

    Parser->>Preprocessor: Parse macro with member access<br/>(e.g., `#define` MACRO input.v_uv)
    activate Preprocessor
    Preprocessor->>Preprocessor: Detect pattern with<br/>_memberAccessReg
    Preprocessor->>Preprocessor: Set valueType =<br/>MacroValueType.MemberAccess
    Preprocessor-->>Parser: Return macro definition
    deactivate Preprocessor

    Parser->>AST: Analyze variable identifier<br/>referencing macro
    activate AST
    AST->>AST: Check _isMemberAccessMacro
    AST->>AST: Skip type assignment<br/>→ TypeAny
    AST-->>Parser: Return with TypeAny
    deactivate AST

    Parser->>GLESVisitor: Begin code generation
    activate GLESVisitor
    GLESVisitor->>CodeGenVisitor: _collectStructVars from<br/>entry functions
    activate CodeGenVisitor
    CodeGenVisitor->>VisitorContext: registerStructVar(varName, role)
    activate VisitorContext
    VisitorContext->>VisitorContext: Store in _structVarMap<br/>and _globalStructVarMap
    VisitorContext-->>CodeGenVisitor: Registered
    deactivate VisitorContext
    CodeGenVisitor-->>GLESVisitor: Struct map populated
    deactivate CodeGenVisitor

    GLESVisitor->>CodeGenVisitor: _transformMacroDefineValue<br/>(using _globalStructVarMap)
    activate CodeGenVisitor
    CodeGenVisitor->>CodeGenVisitor: Extract root var<br/>from member access
    CodeGenVisitor->>VisitorContext: referenceStructPropByName<br/>(role, propName)
    activate VisitorContext
    VisitorContext->>VisitorContext: Register property<br/>in role's reference list
    VisitorContext-->>CodeGenVisitor: Referenced
    deactivate VisitorContext
    CodeGenVisitor-->>GLESVisitor: Return transformed<br/>expression
    deactivate CodeGenVisitor

    GLESVisitor->>GLESVisitor: Emit GLSL with<br/>transformed expressions
    GLESVisitor->>GLESVisitor: Skip uniform emit<br/>for struct vars
    GLESVisitor-->>Parser: Return compiled GLSL
    deactivate GLESVisitor
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit's joy for struct-access lines,
Where macros now detect member designs,
Roles tracked and mapped with careful care,
Transform expressions floating in the air!
Varyings bloom, uniforms take flight—
Shader compilation done just right!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: transforming struct member access patterns in #define macro values during code generation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/shader-lab/src/codeGen/CodeGenVisitor.ts`:
- Around line 408-426: _collectStructVars currently wipes context._structVarMap
losing globals registered by visitGlobalVariableDeclaration; fix this by first
cloning the existing map (save preserved = { ...context._structVarMap }), then
clear context._structVarMap as before, collect params and locals via
fnNode.protoType.parameterList and
this._collectStructVarsFromNode(fnNode.statements, context, map), and finally
restore preserved entries only for keys not already set (for each key in
preserved if (!(key in map)) map[key] = preserved[key]) so global struct vars
remain available to function-body macros.

In `@packages/shader-lab/src/codeGen/GLESVisitor.ts`:
- Around line 279-295: Inside the _forEachMacroMemberAccess loop, symOut is
reused across rootName lookups causing stale symbols to persist; before calling
symbolTable.getSymbols(lookupSymbol, true, symOut) reset symOut (e.g.
symOut.length = 0 or reassign a new array) so each rootName lookup only examines
its own symbols when populating map using structTypeRoles, while keeping the
existing checked, lookupSymbol and map logic unchanged.

In `@packages/shader-lab/src/codeGen/VisitorContext.ts`:
- Around line 80-92: The function referenceStructPropByName currently does
nothing when a struct member is missing, which lets callers strip the root
prefix and produce a later undeclared-identifier error; update
referenceStructPropByName to record a placeholder for missing properties so
macro rewriting knows the name was referenced but unresolved. Specifically, in
referenceStructPropByName (and when choosing list via
varyingList/attributeList/mrtList and the corresponding
_referencedVaryingList/_referencedAttributeList/_referencedMRTList), if
props.length === 0 set refList[propName] to an explicit placeholder (e.g. an
empty array or null) instead of leaving it undefined/returning, so downstream
code can detect the unresolved member and preserve the original root.prop (or
emit the correct compile-time error).

In `@packages/shader-lab/src/Preprocessor.ts`:
- Around line 66-69: The MemberAccess branch in Preprocessor.ts is incorrectly
treating macro parameter placeholders (e.g., `input` in `#define GET_UV(input)
input.v_uv`) as external references; update the logic in the
MacroValueType.MemberAccess handling so that after extracting rootName from
info.value you check whether rootName is a parameter of the current macro and
skip pushing it into out when it is (i.e., only push rootName if it is not
listed among the macro's parameter names). Reference symbols:
MacroValueType.MemberAccess, info.value, rootName, out, and
VariableIdentifier.semanticAnalyze to ensure macro-local placeholders are not
treated as external identifiers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b35f62a1-31c4-4cab-8d3f-9a561bf80d82

📥 Commits

Reviewing files that changed from the base of the PR and between 708e95b and 7dd2c07.

⛔ Files ignored due to path filters (8)
  • tests/src/shader-lab/expected/define-struct-access-global.frag.glsl is excluded by !**/*.glsl
  • tests/src/shader-lab/expected/define-struct-access-global.vert.glsl is excluded by !**/*.glsl
  • tests/src/shader-lab/expected/define-struct-access.frag.glsl is excluded by !**/*.glsl
  • tests/src/shader-lab/expected/define-struct-access.vert.glsl is excluded by !**/*.glsl
  • tests/src/shader-lab/shaders/define-struct-access-global.shader is excluded by !**/*.shader
  • tests/src/shader-lab/shaders/define-struct-access.shader is excluded by !**/*.shader
  • tests/src/shader-lab/shaders/global-varying-var.shader is excluded by !**/*.shader
  • tests/src/shader-lab/shaders/macro-member-access-builtin-arg.shader is excluded by !**/*.shader
📒 Files selected for processing (6)
  • packages/shader-lab/src/Preprocessor.ts
  • packages/shader-lab/src/codeGen/CodeGenVisitor.ts
  • packages/shader-lab/src/codeGen/GLESVisitor.ts
  • packages/shader-lab/src/codeGen/VisitorContext.ts
  • packages/shader-lab/src/parser/AST.ts
  • tests/src/shader-lab/ShaderLab.test.ts

Comment on lines +408 to +426
private _collectStructVars(fnNode: ASTNode.FunctionDefinition, context: VisitorContext): void {
const map = context._structVarMap;
// Clear previous function's mappings
for (const key in map) delete map[key];

// Collect from function parameters
const paramList = fnNode.protoType.parameterList;
if (paramList) {
for (const param of paramList) {
if (param.ident && param.typeInfo && typeof param.typeInfo.type === "string") {
const role = context.getStructRole(param.typeInfo.typeLexeme);
if (role) map[param.ident.lexeme] = role;
}
}
}

// Collect from local variable declarations in function body
this._collectStructVarsFromNode(fnNode.statements, context, map);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve global struct vars when rebuilding the function map.

_collectStructVars() clears _structVarMap and only re-adds params and locals. That drops global struct variables registered earlier by visitGlobalVariableDeclaration(), so a function-body macro like #define UV o.v_uv stops transforming when o is global.

Suggested fix
   private _collectStructVars(fnNode: ASTNode.FunctionDefinition, context: VisitorContext): void {
     const map = context._structVarMap;
     // Clear previous function's mappings
     for (const key in map) delete map[key];
+    for (const key in context._globalStructVarMap) {
+      map[key] = context._globalStructVarMap[key];
+    }

     // Collect from function parameters
     const paramList = fnNode.protoType.parameterList;
     if (paramList) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shader-lab/src/codeGen/CodeGenVisitor.ts` around lines 408 - 426,
_collectStructVars currently wipes context._structVarMap losing globals
registered by visitGlobalVariableDeclaration; fix this by first cloning the
existing map (save preserved = { ...context._structVarMap }), then clear
context._structVarMap as before, collect params and locals via
fnNode.protoType.parameterList and
this._collectStructVarsFromNode(fnNode.statements, context, map), and finally
restore preserved entries only for keys not already set (for each key in
preserved if (!(key in map)) map[key] = preserved[key]) so global struct vars
remain available to function-body macros.

Comment on lines +279 to +295
const checked = new Set<string>();
const symOut: SymbolInfo[] = [];
this._forEachMacroMemberAccess(globalMacros, (rootName) => {
if (map[rootName] || checked.has(rootName)) return;
checked.add(rootName);
lookupSymbol.set(rootName, ESymbolType.VAR);
symbolTable.getSymbols(lookupSymbol, true, symOut);
for (const sym of symOut) {
if (sym.dataType) {
const role = structTypeRoles[sym.dataType.typeLexeme];
if (role) {
map[rootName] = role;
break;
}
}
}
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reset symOut before each root lookup.

symOut is reused across different rootNames, but symbolTable.getSymbols(..., symOut) appends into the provided array. If one root resolves and the next one doesn't, the stale symbols are still iterated and the later root can inherit the earlier root's role.

Suggested fix
       this._forEachMacroMemberAccess(globalMacros, (rootName) => {
         if (map[rootName] || checked.has(rootName)) return;
         checked.add(rootName);
         lookupSymbol.set(rootName, ESymbolType.VAR);
+        symOut.length = 0;
         symbolTable.getSymbols(lookupSymbol, true, symOut);
         for (const sym of symOut) {
           if (sym.dataType) {
             const role = structTypeRoles[sym.dataType.typeLexeme];
             if (role) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const checked = new Set<string>();
const symOut: SymbolInfo[] = [];
this._forEachMacroMemberAccess(globalMacros, (rootName) => {
if (map[rootName] || checked.has(rootName)) return;
checked.add(rootName);
lookupSymbol.set(rootName, ESymbolType.VAR);
symbolTable.getSymbols(lookupSymbol, true, symOut);
for (const sym of symOut) {
if (sym.dataType) {
const role = structTypeRoles[sym.dataType.typeLexeme];
if (role) {
map[rootName] = role;
break;
}
}
}
});
const checked = new Set<string>();
const symOut: SymbolInfo[] = [];
this._forEachMacroMemberAccess(globalMacros, (rootName) => {
if (map[rootName] || checked.has(rootName)) return;
checked.add(rootName);
lookupSymbol.set(rootName, ESymbolType.VAR);
symOut.length = 0;
symbolTable.getSymbols(lookupSymbol, true, symOut);
for (const sym of symOut) {
if (sym.dataType) {
const role = structTypeRoles[sym.dataType.typeLexeme];
if (role) {
map[rootName] = role;
break;
}
}
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shader-lab/src/codeGen/GLESVisitor.ts` around lines 279 - 295,
Inside the _forEachMacroMemberAccess loop, symOut is reused across rootName
lookups causing stale symbols to persist; before calling
symbolTable.getSymbols(lookupSymbol, true, symOut) reset symOut (e.g.
symOut.length = 0 or reassign a new array) so each rootName lookup only examines
its own symbols when populating map using structTypeRoles, while keeping the
existing checked, lookupSymbol and map logic unchanged.

Comment on lines +80 to +92
referenceStructPropByName(role: "varying" | "attribute" | "mrt", propName: string): void {
const list = role === "varying" ? this.varyingList : role === "attribute" ? this.attributeList : this.mrtList;
const refList =
role === "varying"
? this._referencedVaryingList
: role === "attribute"
? this._referencedAttributeList
: this._referencedMRTList;
if (refList[propName]) return;
const props = list.filter((item) => item.ident.lexeme === propName);
if (props.length) {
refList[propName] = props;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't silently swallow unknown struct members in macro rewrites.

referenceStructPropByName() no-ops when propName is missing, but the callers still strip the root. prefix. That means #define UV o.v_uu turns into v_uu and fails much later as an undeclared identifier instead of producing the same compilation error that normal member access gets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shader-lab/src/codeGen/VisitorContext.ts` around lines 80 - 92, The
function referenceStructPropByName currently does nothing when a struct member
is missing, which lets callers strip the root prefix and produce a later
undeclared-identifier error; update referenceStructPropByName to record a
placeholder for missing properties so macro rewriting knows the name was
referenced but unresolved. Specifically, in referenceStructPropByName (and when
choosing list via varyingList/attributeList/mrtList and the corresponding
_referencedVaryingList/_referencedAttributeList/_referencedMRTList), if
props.length === 0 set refList[propName] to an explicit placeholder (e.g. an
empty array or null) instead of leaving it undefined/returning, so downstream
code can detect the unresolved member and preserve the original root.prop (or
emit the correct compile-time error).

Comment on lines +66 to +69
} else if (valueType === MacroValueType.MemberAccess) {
// Extract root symbol: "input.v_uv" → "input"
const rootName = info.value.substring(0, info.value.indexOf("."));
if (out.indexOf(rootName) === -1) out.push(rootName);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Skip member-access roots that are macro parameters.

For function-like macros such as #define GET_UV(input) input.v_uv, this branch currently records input as an external reference. That makes VariableIdentifier.semanticAnalyze() look up and potentially mark an unrelated symbol named input, which is wrong for macro-local placeholders.

Suggested fix
       } else if (valueType === MacroValueType.MemberAccess) {
         // Extract root symbol: "input.v_uv" → "input"
         const rootName = info.value.substring(0, info.value.indexOf("."));
+        if (info.params.indexOf(rootName) !== -1) continue;
         if (out.indexOf(rootName) === -1) out.push(rootName);
       } else if (valueType === MacroValueType.Other) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shader-lab/src/Preprocessor.ts` around lines 66 - 69, The
MemberAccess branch in Preprocessor.ts is incorrectly treating macro parameter
placeholders (e.g., `input` in `#define GET_UV(input) input.v_uv`) as external
references; update the logic in the MacroValueType.MemberAccess handling so that
after extracting rootName from info.value you check whether rootName is a
parameter of the current macro and skip pushing it into out when it is (i.e.,
only push rootName if it is not listed among the macro's parameter names).
Reference symbols: MacroValueType.MemberAccess, info.value, rootName, out, and
VariableIdentifier.semanticAnalyze to ensure macro-local placeholders are not
treated as external identifiers.

GuoLei1990

This comment was marked as outdated.

GuoLei1990

This comment was marked as outdated.

GuoLei1990

This comment was marked as outdated.

@cptbtptpbcptdtptp cptbtptpbcptdtptp added the shader Shader related functions label Apr 17, 2026
GuoLei1990

This comment was marked as outdated.

Copy link
Copy Markdown
Member

@GuoLei1990 GuoLei1990 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

总结

支持 #define VAR o.v_uv 风格的宏——CodeGen 在变换时自动剥离 struct variable 前缀。Preprocessor 新增 MacroValueType.MemberAccess 类型,AST 跳过成员访问宏的类型推断,CodeGen 执行 varName.propName → propName 替换并抑制全局 struct 变量的 uniform 输出。三层设计清晰,测试覆盖 4 种模式。

自上次 CHANGES_REQUESTED (2026-04-17) 以来无新 commit。之前提出的 P1 问题仍未修复。


已关闭问题清单

# 问题 状态
1 Preprocessor vs CodeGenVisitor 正则分类不一致 P2,不阻塞
2 静态共享 /g flag regex 重入风险 P3,不阻塞
3 referenceStructPropByName 对不存在的成员静默跳过 P2,不阻塞
4 _collectStructVarsFromBody / _collectStructVarsFromNode 重复逻辑 简化建议,不阻塞
5 symOut 数组复用问题 误报——SymbolTable.getSymbols() 内部已 out.length = 0

问题

  • [P1] Preprocessor.ts:63-69MemberAccess 分支缺少 macro param 守卫 — 对于函数式宏如 #define GET_UV(input) input.v_uvrootName ("input") 是宏参数而非外部引用。SymbolFunctionCall 分支已有 if (info.params.indexOf(referencedName) !== -1) continue 守卫,MemberAccess 分支遗漏。修复:
} else if (valueType === MacroValueType.MemberAccess) {
  const rootName = info.value.substring(0, info.value.indexOf("."));
  if (info.params.indexOf(rootName) !== -1) continue;  // ← 加这行
  if (out.indexOf(rootName) === -1) out.push(rootName);
}
  • [P1] CodeGenVisitor.ts:405-426_collectStructVars 清空 _structVarMap 时丢失全局结构体变量 — 函数体宏(如 #define UV o.v_uv)中 o 是全局变量时(通过 visitGlobalVariableDeclarationregisterStructVar 注册),清空后变换停止工作。当前全局宏路径通过 overrideMap 参数绕过了此问题,但函数体内引用全局 struct 变量的宏会失败。修复:清空后从 _globalStructVarMap 恢复全局注册:
private _collectStructVars(fnNode, context): void {
  const map = context._structVarMap;
  for (const key in map) delete map[key];
  // Restore global struct variable registrations
  const globalMap = context._globalStructVarMap;
  for (const key in globalMap) map[key] = globalMap[key];
  // ... rest of collection
}

以上两个 P1 问题自首次 review (2026-04-15) 以来已连续 4 轮提出,请修复后再合入。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

shader Shader related functions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants