Skip to content

Commit 80e6e3e

Browse files
committed
fix(shader-lab): handle global struct-typed variables and simplify macro 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)
1 parent 7b18aef commit 80e6e3e

File tree

5 files changed

+157
-15
lines changed

5 files changed

+157
-15
lines changed

packages/shader-lab/src/codeGen/CodeGenVisitor.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,19 @@ export abstract class CodeGenVisitor {
244244
const children = node.children;
245245
const fullType = children[0];
246246
if (fullType instanceof ASTNode.FullySpecifiedType && fullType.typeSpecifier.isCustom) {
247-
VisitorContext.context.referenceGlobal(<string>fullType.type, ESymbolType.STRUCT);
247+
const context = VisitorContext.context;
248+
const typeLexeme = fullType.typeSpecifier.lexeme;
249+
const role = context.getStructRole(typeLexeme);
250+
if (role) {
251+
// Global variable of a varying/attribute/mrt struct type (e.g. "Varyings o;").
252+
// Don't output as uniform; register the variable in struct var maps instead.
253+
const ident = children[1];
254+
if (ident instanceof BaseToken) {
255+
context.registerStructVar(ident.lexeme, role);
256+
}
257+
return "";
258+
}
259+
context.referenceGlobal(<string>fullType.type, ESymbolType.STRUCT);
248260
}
249261
return `uniform ${this.defaultCodeGen(children)}`;
250262
}

packages/shader-lab/src/codeGen/GLESVisitor.ts

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

packages/shader-lab/src/codeGen/VisitorContext.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ export class VisitorContext {
7272
if (this.isMRTStruct(typeLexeme)) return "mrt";
7373
}
7474

75+
registerStructVar(varName: string, role: "varying" | "attribute" | "mrt"): void {
76+
this._structVarMap[varName] = role;
77+
this._globalStructVarMap[varName] = role;
78+
}
79+
7580
referenceStructPropByName(role: "varying" | "attribute" | "mrt", propName: string): void {
7681
const list = role === "varying" ? this.varyingList : role === "attribute" ? this.attributeList : this.mrtList;
7782
const refList =

tests/src/shader-lab/ShaderLab.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,4 +327,36 @@ describe("ShaderLab", async () => {
327327
expect(fragment).to.contain("dot");
328328
expect(fragment).to.contain("texture2D");
329329
});
330+
331+
it("global-varying-var (Cocos VSOutput pattern: global Varyings var with #define macros)", async () => {
332+
const shaderSource = await readFile("./shaders/global-varying-var.shader");
333+
glslValidate(engine, shaderSource, shaderLabRelease);
334+
335+
// Verify verbose mode: global "Varyings o;" should not produce "uniform Varyings o;"
336+
// and should not duplicate varying declarations.
337+
const shader = shaderLabVerbose._parseShaderSource(shaderSource);
338+
const passSource = shader.subShaders[0].passes[0];
339+
const { vertex, fragment } = shaderLabVerbose._parseShaderPass(
340+
passSource.contents,
341+
passSource.vertexEntry,
342+
passSource.fragmentEntry,
343+
0,
344+
""
345+
)!;
346+
347+
expect(vertex).to.be.a("string").and.not.empty;
348+
expect(fragment).to.be.a("string").and.not.empty;
349+
350+
// No "uniform Varyings o;" in output
351+
expect(vertex).to.not.contain("uniform Varyings");
352+
expect(fragment).to.not.contain("uniform Varyings");
353+
354+
// Macros should be transformed: "o.v_worldPos" → "v_worldPos"
355+
expect(vertex).to.contain("#define VSOutput_worldPos v_worldPos");
356+
expect(vertex).to.contain("#define VSOutput_worldNormal v_normal.xyz");
357+
358+
// No duplicate varying declarations
359+
const varyingMatches = vertex.match(/varying vec3 v_worldPos/g);
360+
expect(varyingMatches).to.have.lengthOf(1);
361+
});
330362
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
Shader "global-varying-var-test" {
2+
SubShader "Default" {
3+
Pass "Forward" {
4+
mat4 renderer_MVPMat;
5+
6+
struct Attributes { vec3 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; };
7+
struct Varyings { vec3 v_worldPos; vec4 v_normal; vec2 v_uv; vec4 v_shadowBiasAndProbeId; };
8+
9+
VertexShader = vert;
10+
FragmentShader = frag;
11+
12+
sampler2D u_texture;
13+
vec3 u_lightDir;
14+
15+
#define VSOutput_worldPos o.v_worldPos
16+
#define VSOutput_worldNormal o.v_normal.xyz
17+
#define VSOutput_faceSideSign o.v_normal.w
18+
#define VSOutput_texcoord o.v_uv
19+
#define VSOutput_shadowBias o.v_shadowBiasAndProbeId.xy
20+
21+
Varyings o;
22+
23+
Varyings vert(Attributes input) {
24+
mat4 matWorld = renderer_MVPMat;
25+
vec4 pos = matWorld * vec4(input.POSITION, 1.0);
26+
VSOutput_worldPos = pos.xyz;
27+
VSOutput_worldNormal = input.NORMAL;
28+
VSOutput_faceSideSign = 1.0;
29+
VSOutput_texcoord = input.TEXCOORD_0;
30+
VSOutput_shadowBias = vec2(0.0, 0.0);
31+
gl_Position = pos;
32+
return o;
33+
}
34+
35+
void frag(Varyings v) {
36+
vec3 N = normalize(v.v_normal.xyz);
37+
float NdotL = dot(N, u_lightDir);
38+
gl_FragColor = texture2D(u_texture, v.v_uv) * NdotL;
39+
}
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)