mirror of
https://github.com/galacean/engine.git
synced 2026-05-31 15:51:33 +08:00
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)
This commit is contained in:
@@ -244,7 +244,19 @@ export abstract class CodeGenVisitor {
|
||||
const children = node.children;
|
||||
const fullType = children[0];
|
||||
if (fullType instanceof ASTNode.FullySpecifiedType && fullType.typeSpecifier.isCustom) {
|
||||
VisitorContext.context.referenceGlobal(<string>fullType.type, ESymbolType.STRUCT);
|
||||
const context = VisitorContext.context;
|
||||
const typeLexeme = fullType.typeSpecifier.lexeme;
|
||||
const role = context.getStructRole(typeLexeme);
|
||||
if (role) {
|
||||
// Global variable of a varying/attribute/mrt struct type (e.g. "Varyings o;").
|
||||
// Don't output as uniform; register the variable in struct var maps instead.
|
||||
const ident = children[1];
|
||||
if (ident instanceof BaseToken) {
|
||||
context.registerStructVar(ident.lexeme, role);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
context.referenceGlobal(<string>fullType.type, ESymbolType.STRUCT);
|
||||
}
|
||||
return `uniform ${this.defaultCodeGen(children)}`;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
|
||||
// Build combined _globalStructVarMap from both entry functions before per-stage processing.
|
||||
// This must happen here because vertex runs first and doesn't yet know fragment's variables.
|
||||
this._buildGlobalStructVarMap(vertexEntry, fragmentEntry, shaderData, context);
|
||||
this._buildGlobalStructVarMap(vertexEntry, fragmentEntry, shaderData, outerGlobalMacroDeclarations, context);
|
||||
|
||||
return {
|
||||
vertex: this._vertexMain(vertexEntry, shaderData, outerGlobalMacroDeclarations),
|
||||
@@ -215,6 +215,7 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
vertexEntry: string,
|
||||
fragmentEntry: string,
|
||||
data: ShaderData,
|
||||
globalMacros: ASTNode.GlobalDeclaration[],
|
||||
context: VisitorContext
|
||||
): void {
|
||||
const map = context._globalStructVarMap;
|
||||
@@ -266,6 +267,33 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
}
|
||||
this._collectStructVarsFromBody(fnNode.statements, structTypeRoles, map);
|
||||
}
|
||||
|
||||
// Also scan global macros for root variable names that might be global struct variables.
|
||||
// e.g. #define VSOutput_worldPos o.v_worldPos → root "o" → look up in symbol table
|
||||
let hasRoles = false;
|
||||
for (const _ in structTypeRoles) {
|
||||
hasRoles = true;
|
||||
break;
|
||||
}
|
||||
if (hasRoles) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _collectStructVarsFromBody(
|
||||
@@ -296,18 +324,36 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
*/
|
||||
private _registerGlobalMacroReferences(globalMacros: ASTNode.GlobalDeclaration[], context: VisitorContext): void {
|
||||
const map = context._globalStructVarMap;
|
||||
if (!Object.keys(map).length) return;
|
||||
let hasEntries = false;
|
||||
for (const _ in map) {
|
||||
hasEntries = true;
|
||||
break;
|
||||
}
|
||||
if (!hasEntries) return;
|
||||
this._forEachMacroMemberAccess(globalMacros, (rootName, propName) => {
|
||||
const role = map[rootName];
|
||||
if (role) context.referenceStructPropByName(role, propName);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse global macro declarations, extracting member access patterns (e.g. "o.v_uv")
|
||||
* and invoking the callback with (rootName, propName) for each match.
|
||||
*/
|
||||
private _forEachMacroMemberAccess(
|
||||
macros: ASTNode.GlobalDeclaration[],
|
||||
callback: (rootName: string, propName: string) => void
|
||||
): void {
|
||||
const reg = CodeGenVisitor._memberAccessReg;
|
||||
for (const macro of globalMacros) {
|
||||
this._scanMacroMemberAccess(macro.children, reg, map, context);
|
||||
for (const macro of macros) {
|
||||
this._walkMacroChildren(macro.children, reg, callback);
|
||||
}
|
||||
}
|
||||
|
||||
private _scanMacroMemberAccess(
|
||||
private _walkMacroChildren(
|
||||
children: NodeChild[],
|
||||
reg: RegExp,
|
||||
map: Record<string, "varying" | "attribute" | "mrt">,
|
||||
context: VisitorContext
|
||||
callback: (rootName: string, propName: string) => void
|
||||
): void {
|
||||
for (const child of children) {
|
||||
if (child instanceof BaseToken && child.type === Keyword.MACRO_DEFINE_EXPRESSION) {
|
||||
@@ -317,19 +363,20 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
reg.lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = reg.exec(value)) !== null) {
|
||||
const role = map[match[1]];
|
||||
if (role) context.referenceStructPropByName(role, match[2]);
|
||||
callback(match[1], match[2]);
|
||||
}
|
||||
} else if (child instanceof TreeNode) {
|
||||
this._scanMacroMemberAccess(child.children, reg, map, context);
|
||||
this._walkMacroChildren(child.children, reg, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _getGlobalSymbol(out: ICodeSegment[]): void {
|
||||
const { _referencedGlobals } = VisitorContext.context;
|
||||
const context = VisitorContext.context;
|
||||
const { _referencedGlobals } = context;
|
||||
|
||||
const lastLength = Object.keys(_referencedGlobals).length;
|
||||
let lastLength = 0;
|
||||
for (const _ in _referencedGlobals) lastLength++;
|
||||
if (lastLength === 0) return;
|
||||
|
||||
for (const ident in _referencedGlobals) {
|
||||
@@ -339,7 +386,9 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
const symbols = _referencedGlobals[ident];
|
||||
for (let i = 0; i < symbols.length; i++) {
|
||||
const sm = symbols[i];
|
||||
const text = sm.astNode.codeGen(this) + (sm.type === ESymbolType.VAR ? ";" : "");
|
||||
const codeGenResult = sm.astNode.codeGen(this);
|
||||
if (!codeGenResult) continue;
|
||||
const text = codeGenResult + (sm.type === ESymbolType.VAR ? ";" : "");
|
||||
if (!sm.isInMacroBranch) {
|
||||
out.push({
|
||||
text,
|
||||
@@ -349,7 +398,9 @@ export abstract class GLESVisitor extends CodeGenVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(_referencedGlobals).length !== lastLength) {
|
||||
let newLength = 0;
|
||||
for (const _ in _referencedGlobals) newLength++;
|
||||
if (newLength !== lastLength) {
|
||||
this._getGlobalSymbol(out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,11 @@ export class VisitorContext {
|
||||
if (this.isMRTStruct(typeLexeme)) return "mrt";
|
||||
}
|
||||
|
||||
registerStructVar(varName: string, role: "varying" | "attribute" | "mrt"): void {
|
||||
this._structVarMap[varName] = role;
|
||||
this._globalStructVarMap[varName] = role;
|
||||
}
|
||||
|
||||
referenceStructPropByName(role: "varying" | "attribute" | "mrt", propName: string): void {
|
||||
const list = role === "varying" ? this.varyingList : role === "attribute" ? this.attributeList : this.mrtList;
|
||||
const refList =
|
||||
|
||||
@@ -327,4 +327,36 @@ describe("ShaderLab", async () => {
|
||||
expect(fragment).to.contain("dot");
|
||||
expect(fragment).to.contain("texture2D");
|
||||
});
|
||||
|
||||
it("global-varying-var (Cocos VSOutput pattern: global Varyings var with #define macros)", async () => {
|
||||
const shaderSource = await readFile("./shaders/global-varying-var.shader");
|
||||
glslValidate(engine, shaderSource, shaderLabRelease);
|
||||
|
||||
// Verify verbose mode: global "Varyings o;" should not produce "uniform Varyings o;"
|
||||
// and should not duplicate varying declarations.
|
||||
const shader = shaderLabVerbose._parseShaderSource(shaderSource);
|
||||
const passSource = shader.subShaders[0].passes[0];
|
||||
const { vertex, fragment } = shaderLabVerbose._parseShaderPass(
|
||||
passSource.contents,
|
||||
passSource.vertexEntry,
|
||||
passSource.fragmentEntry,
|
||||
0,
|
||||
""
|
||||
)!;
|
||||
|
||||
expect(vertex).to.be.a("string").and.not.empty;
|
||||
expect(fragment).to.be.a("string").and.not.empty;
|
||||
|
||||
// No "uniform Varyings o;" in output
|
||||
expect(vertex).to.not.contain("uniform Varyings");
|
||||
expect(fragment).to.not.contain("uniform Varyings");
|
||||
|
||||
// Macros should be transformed: "o.v_worldPos" → "v_worldPos"
|
||||
expect(vertex).to.contain("#define VSOutput_worldPos v_worldPos");
|
||||
expect(vertex).to.contain("#define VSOutput_worldNormal v_normal.xyz");
|
||||
|
||||
// No duplicate varying declarations
|
||||
const varyingMatches = vertex.match(/varying vec3 v_worldPos/g);
|
||||
expect(varyingMatches).to.have.lengthOf(1);
|
||||
});
|
||||
});
|
||||
|
||||
42
tests/src/shader-lab/shaders/global-varying-var.shader
Normal file
42
tests/src/shader-lab/shaders/global-varying-var.shader
Normal file
@@ -0,0 +1,42 @@
|
||||
Shader "global-varying-var-test" {
|
||||
SubShader "Default" {
|
||||
Pass "Forward" {
|
||||
mat4 renderer_MVPMat;
|
||||
|
||||
struct Attributes { vec3 POSITION; vec3 NORMAL; vec2 TEXCOORD_0; };
|
||||
struct Varyings { vec3 v_worldPos; vec4 v_normal; vec2 v_uv; vec4 v_shadowBiasAndProbeId; };
|
||||
|
||||
VertexShader = vert;
|
||||
FragmentShader = frag;
|
||||
|
||||
sampler2D u_texture;
|
||||
vec3 u_lightDir;
|
||||
|
||||
#define VSOutput_worldPos o.v_worldPos
|
||||
#define VSOutput_worldNormal o.v_normal.xyz
|
||||
#define VSOutput_faceSideSign o.v_normal.w
|
||||
#define VSOutput_texcoord o.v_uv
|
||||
#define VSOutput_shadowBias o.v_shadowBiasAndProbeId.xy
|
||||
|
||||
Varyings o;
|
||||
|
||||
Varyings vert(Attributes input) {
|
||||
mat4 matWorld = renderer_MVPMat;
|
||||
vec4 pos = matWorld * vec4(input.POSITION, 1.0);
|
||||
VSOutput_worldPos = pos.xyz;
|
||||
VSOutput_worldNormal = input.NORMAL;
|
||||
VSOutput_faceSideSign = 1.0;
|
||||
VSOutput_texcoord = input.TEXCOORD_0;
|
||||
VSOutput_shadowBias = vec2(0.0, 0.0);
|
||||
gl_Position = pos;
|
||||
return o;
|
||||
}
|
||||
|
||||
void frag(Varyings v) {
|
||||
vec3 N = normalize(v.v_normal.xyz);
|
||||
float NdotL = dot(N, u_lightDir);
|
||||
gl_FragColor = texture2D(u_texture, v.v_uv) * NdotL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user