Skip to content

Commit

Permalink
ShaderLab support MRT (#2452)
Browse files Browse the repository at this point in the history
* feat: add mrt syntax
  • Loading branch information
Sway007 authored Dec 24, 2024
1 parent afddc21 commit 07186da
Show file tree
Hide file tree
Showing 43 changed files with 1,224 additions and 1,009 deletions.
113 changes: 113 additions & 0 deletions e2e/case/shaderLab-mrt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* @title ShaderLab MRT
* @category Material
*/

import { Camera, Color, Logger, Material, MeshRenderer, PrimitiveMesh, Shader, WebGLEngine } from "@galacean/engine";
import { ShaderLab } from "@galacean/engine-shader-lab";
import { initScreenshot, updateForE2E } from "./.mockForE2E";

const shaderLab = new ShaderLab();

const shaderSource = `Shader "/custom.gs" {
SubShader "Default" {
UsePass "pbr/Default/ShadowCaster"
Pass "Pass0" {
struct Attributes {
vec3 POSITION;
vec2 TEXCOORD_0;
vec4 JOINTS_0;
vec4 WEIGHTS_0;
};
struct Varyings {
vec2 uv;
};
mat4 renderer_MVPMat;
vec4 material_BaseColor;
VertexShader = vert;
FragmentShader = frag;
Varyings vert(Attributes attr) {
Varyings v;
vec4 position = vec4(attr.POSITION, 1.0);
// Skin
#ifdef RENDERER_HAS_SKIN
mat4 skinMatrix = getSkinMatrix(attr);
position = skinMatrix * position;
#endif
gl_Position = renderer_MVPMat * position;
v.uv = attr.TEXCOORD_0;
return v;
}
struct mrt {
layout(location = 0) vec4 fragColor0;
layout(location = 1) vec4 fragColor1;
};
mrt frag(Varyings v) {
mrt o;
vec4 baseColor = material_BaseColor;
#ifdef MATERIAL_HAS_BASETEXTURE
vec4 textureColor = texture2D(material_BaseTexture, v.uv);
#ifndef ENGINE_IS_COLORSPACE_GAMMA
textureColor = gammaToLinear(textureColor);
#endif
baseColor *= textureColor;
#endif
#ifdef MATERIAL_IS_ALPHA_CUTOFF
if( baseColor.a < material_AlphaCutoff ) {
discard;
}
#endif
o.fragColor0 = baseColor;
o.fragColor1 = baseColor;
return o;
}
}
}
}`;

Logger.enable();
WebGLEngine.create({ canvas: "canvas", shaderLab }).then((engine) => {
engine.canvas.resizeByClientSize();

const shader = Shader.create(shaderSource);
const scene = engine.sceneManager.activeScene;
const rootEntity = scene.createRootEntity();

// camera
const cameraEntity = rootEntity.createChild("cameraNode");
cameraEntity.transform.setPosition(0, 0, 5);
const camera = cameraEntity.addComponent(Camera);

// sphere
{
const sphere = rootEntity.createChild("sphere");
sphere.transform.position.x = -1;
const renderer = sphere.addComponent(MeshRenderer);
renderer.mesh = PrimitiveMesh.createSphere(engine);
const material = new Material(engine, shader);
material.shaderData.setColor("material_BaseColor", new Color(1, 0, 0, 0.2));
renderer.setMaterial(material);
}

updateForE2E(engine);

initScreenshot(engine, camera);
});
5 changes: 5 additions & 0 deletions e2e/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ export const E2E_CONFIG = {
caseFileName: "material-shaderLab",
threshold: 0.2
},
shaderLabMRT: {
category: "Material",
caseFileName: "shaderLab-mrt",
threshold: 0.2
},
shaderReplacement: {
category: "Material",
caseFileName: "material-shaderReplacement",
Expand Down
3 changes: 3 additions & 0 deletions e2e/fixtures/originImage/Material_shaderLab-mrt.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion packages/shader-lab/src/GSError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export class GSError extends Error {
if (i === start.line) {
remarkStart = start.column;
paddingLength += start.column;
} else if (i === end.line) {
}
if (i === end.line) {
remarkEnd = end.column;
}
const remarkLength = Math.max(remarkEnd - remarkStart, 1);
Expand Down
8 changes: 4 additions & 4 deletions packages/shader-lab/src/ParserUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ENonTerminal, GrammarSymbol } from "./parser/GrammarSymbol";
import { NoneTerminal, GrammarSymbol } from "./parser/GrammarSymbol";
import { BaseToken as Token } from "./common/BaseToken";
import { EKeyword, ETokenType, GalaceanDataType } from "./common";
import { TreeNode } from "./parser/AST";
Expand All @@ -7,7 +7,7 @@ import State from "./lalr/State";
// #endif

export class ParserUtils {
static unwrapNodeByType<T = TreeNode>(node: TreeNode, type: ENonTerminal): T | undefined {
static unwrapNodeByType<T = TreeNode>(node: TreeNode, type: NoneTerminal): T | undefined {
const child = node.children[0];
if (child instanceof Token) return;
if (child.nt === type) return child as T;
Expand All @@ -30,12 +30,12 @@ export class ParserUtils {
if (this.isTerminal(sm)) {
return ETokenType[sm] ?? EKeyword[sm];
}
return ENonTerminal[sm];
return NoneTerminal[sm];
}
// #endif

static isTerminal(sm: GrammarSymbol) {
return sm < ENonTerminal.START;
return sm < NoneTerminal.START;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions packages/shader-lab/src/ShaderLabUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ClearableObjectPool, IPoolElement } from "@galacean/engine";
import { ClearableObjectPool, IPoolElement, Logger } from "@galacean/engine";
import { GSErrorName } from "./GSError";
import { ShaderRange } from "./common/ShaderRange";
import { ShaderPosition } from "./common/ShaderPosition";
Expand Down Expand Up @@ -27,11 +27,11 @@ export class ShaderLabUtils {
source: string,
location: ShaderRange | ShaderPosition,
file?: string
): Error {
): Error | undefined {
// #if _VERBOSE
return new GSError(errorName, message, location, source, file);
// #else
return new Error(`[${errorName}]: ${message}`);
Logger.error(message);
// #endif
}
}
78 changes: 56 additions & 22 deletions packages/shader-lab/src/codeGen/CodeGenVisitor.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ENonTerminal } from "../parser/GrammarSymbol";
import { NoneTerminal } from "../parser/GrammarSymbol";
import { BaseToken as Token } from "../common/BaseToken";
import { EKeyword, ShaderPosition, ShaderRange } from "../common";
import { ASTNode, TreeNode } from "../parser/AST";
Expand All @@ -11,10 +11,12 @@ import { GSErrorName } from "../GSError";
// #if _VERBOSE
import { GSError } from "../GSError";
// #endif
import { ShaderLabUtils } from "../ShaderLabUtils";
import { Logger, ReturnableObjectPool } from "@galacean/engine";
import { TempArray } from "../TempArray";

export const V3_GL_FragColor = "GS_glFragColor";
export const V3_GL_FragData = "GS_glFragData";

/**
* @internal
* The code generator
Expand All @@ -23,6 +25,10 @@ export abstract class CodeGenVisitor {
// #if _VERBOSE
readonly errors: Error[] = [];
// #endif

abstract getFragDataCodeGen(index: string | number): string;
abstract getReferencedMRTPropText(index: string | number, ident: string): string;

protected static _tmpArrayPool = new ReturnableObjectPool(TempArray<string>, 10);

defaultCodeGen(children: NodeChild[]) {
Expand All @@ -41,12 +47,13 @@ export abstract class CodeGenVisitor {
}

visitPostfixExpression(node: ASTNode.PostfixExpression) {
if (node.children.length === 3) {
const context = VisitorContext.context;

const postExpr = node.children[0] as ASTNode.PostfixExpression;
const children = node.children;
const derivationLength = children.length;
const context = VisitorContext.context;

const prop = node.children[2];
if (derivationLength === 3) {
const postExpr = children[0] as ASTNode.PostfixExpression;
const prop = children[2];

if (prop instanceof Token) {
if (context.isAttributeStruct(<string>postExpr.type)) {
Expand All @@ -65,12 +72,36 @@ export abstract class CodeGenVisitor {
}
// #endif
return prop.lexeme;
} else if (context.isMRTStruct(<string>postExpr.type)) {
const error = context.referenceMRTProp(prop);
// #if _VERBOSE
if (error) {
this.errors.push(<GSError>error);
}
// #endif
return prop.lexeme;
}

return `${postExpr.codeGen(this)}.${prop.lexeme}`;
} else {
return `${postExpr.codeGen(this)}.${prop.codeGen(this)}`;
}
} else if (derivationLength === 4) {
const identNode = children[0] as ASTNode.PostfixExpression;
const indexNode = children[2] as ASTNode.Expression;
const identLexeme = identNode.codeGen(this);
const indexLexeme = indexNode.codeGen(this);
if (identLexeme === "gl_FragData") {
// #if _VERBOSE
if (context._referencedVaryingList[V3_GL_FragColor]) {
this._reportError(identNode.location, "cannot use both gl_FragData and gl_FragColor");
}
// #endif
const mrtLexeme = this.getFragDataCodeGen(indexLexeme);
context._referencedMRTList[mrtLexeme] = this.getReferencedMRTPropText(indexLexeme, mrtLexeme);
return mrtLexeme;
}
return `${identLexeme}[${indexLexeme}]`;
}
return this.defaultCodeGen(node.children);
}
Expand Down Expand Up @@ -110,7 +141,7 @@ export abstract class CodeGenVisitor {

visitStatementList(node: ASTNode.StatementList): string {
const children = node.children as TreeNode[];
if (node.children.length === 1) {
if (children.length === 1) {
return children[0].codeGen(this);
} else {
return `${children[0].codeGen(this)}\n${children[1].codeGen(this)}`;
Expand All @@ -126,22 +157,24 @@ export abstract class CodeGenVisitor {
}

visitGlobalVariableDeclaration(node: ASTNode.VariableDeclaration): string {
const fullType = node.children[0];
const children = node.children;
const fullType = children[0];
if (fullType instanceof ASTNode.FullySpecifiedType && fullType.typeSpecifier.isCustom) {
VisitorContext.context.referenceGlobal(<string>fullType.type, ESymbolType.STRUCT);
}
return this.defaultCodeGen(node.children);
return this.defaultCodeGen(children);
}

visitDeclaration(node: ASTNode.Declaration): string {
const child = node.children[0];
if (
child instanceof ASTNode.InitDeclaratorList &&
child.typeInfo.typeLexeme === VisitorContext.context.varyingStruct?.ident?.lexeme
) {
return "";
const { context } = VisitorContext;
const children = node.children;
const child = children[0];

if (child instanceof ASTNode.InitDeclaratorList) {
const typeLexeme = child.typeInfo.typeLexeme;
if (context.isVaryingStruct(typeLexeme) || context.isMRTStruct(typeLexeme)) return "";
}
return this.defaultCodeGen(node.children);
return this.defaultCodeGen(children);
}

visitFunctionProtoType(node: ASTNode.FunctionProtoType): string {
Expand Down Expand Up @@ -174,24 +207,25 @@ export abstract class CodeGenVisitor {
}

visitJumpStatement(node: ASTNode.JumpStatement): string {
const cmd = node.children[0] as Token;
const children = node.children;
const cmd = children[0] as Token;
if (cmd.type === EKeyword.RETURN) {
const expr = node.children[1];
const expr = children[1];
if (expr instanceof ASTNode.Expression) {
const returnVar = ParserUtils.unwrapNodeByType<ASTNode.VariableIdentifier>(
expr,
ENonTerminal.variable_identifier
NoneTerminal.variable_identifier
);
if (returnVar?.typeInfo === VisitorContext.context.varyingStruct?.ident?.lexeme) {
return "";
}
const returnFnCall = ParserUtils.unwrapNodeByType<ASTNode.FunctionCall>(expr, ENonTerminal.function_call);
const returnFnCall = ParserUtils.unwrapNodeByType<ASTNode.FunctionCall>(expr, NoneTerminal.function_call);
if (returnFnCall?.type === VisitorContext.context.varyingStruct?.ident?.lexeme) {
return `${expr.codeGen(this)};`;
}
}
}
return this.defaultCodeGen(node.children);
return this.defaultCodeGen(children);
}

visitFunctionIdentifier(node: ASTNode.FunctionIdentifier): string {
Expand Down
Loading

0 comments on commit 07186da

Please sign in to comment.