// // Copyright 2024 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // #include "compiler/translator/wgsl/RewritePipelineVariables.h" #include #include #include "GLES2/gl2.h" #include "GLSLANG/ShaderLang.h" #include "GLSLANG/ShaderVars.h" #include "anglebase/no_destructor.h" #include "common/angleutils.h" #include "common/log_utils.h" #include "compiler/translator/Common.h" #include "compiler/translator/ImmutableString.h" #include "compiler/translator/ImmutableStringBuilder.h" #include "compiler/translator/IntermNode.h" #include "compiler/translator/OutputTree.h" #include "compiler/translator/Symbol.h" #include "compiler/translator/SymbolUniqueId.h" #include "compiler/translator/Types.h" #include "compiler/translator/tree_util/BuiltIn_autogen.h" #include "compiler/translator/tree_util/FindMain.h" #include "compiler/translator/tree_util/ReplaceVariable.h" #include "compiler/translator/util.h" #include "compiler/translator/wgsl/Utils.h" namespace sh { namespace { const bool kOutputVariableUses = false; struct LocationAnnotation { // Most variables will not be assigned a location until link time, but some variables (like // gl_FragColor) imply an output location. int location = -1; }; struct BuiltinAnnotation { ImmutableString wgslBuiltinName; }; struct NoAnnotation {}; using PipelineAnnotation = std::variant; enum class IOType { Input, Output }; struct GlslToWgslBuiltinMapping { ImmutableString glslBuiltinName{nullptr}; PipelineAnnotation wgslPipelineAnnotation; IOType ioType; const TVariable *builtinVar; // The type from the WGSL spec that corresponds to `wgslPipelineAnnotation`. ImmutableString wgslBuiltinType{nullptr}; // The type that is expected by the shader in the AST, i.e. the type of `builtinVar`. If // nullptr, is the same as `wgslBuiltinType`. // TODO(anglebug.com/42267100): delete this and convert `builtinVar`'s type to a WGSL type. ImmutableString wgslTypeExpectedByShader{nullptr}; // A function to apply that does one of two thing: // 1. for an input builtin: converts the builtin, as supplied by WGPU, into the variable that // the GLSL shader expects. // 2. for an output builtin: converts the output variable from the GLSL shader into the // builtin supplied back to WGPU. // Can be nullptr for no conversion. ImmutableString conversionFunc{nullptr}; }; bool GetWgslBuiltinName(std::string glslBuiltinName, GLenum shaderType, GlslToWgslBuiltinMapping *outMapping) { static const angle::base::NoDestructor> kGlslBuiltinToWgslBuiltinVertex( {{"gl_VertexID", GlslToWgslBuiltinMapping{ImmutableString("gl_VertexID"), BuiltinAnnotation{ImmutableString("vertex_index")}, IOType::Input, BuiltInVariable::gl_VertexID(), ImmutableString("u32"), ImmutableString("i32"), ImmutableString("i32")}}, {"gl_InstanceID", GlslToWgslBuiltinMapping{ImmutableString("gl_InstanceID"), BuiltinAnnotation{ImmutableString("instance_index")}, IOType::Input, BuiltInVariable::gl_InstanceID(), ImmutableString("u32"), ImmutableString("i32"), ImmutableString("i32")}}, {"gl_Position", GlslToWgslBuiltinMapping{ ImmutableString("gl_Position"), BuiltinAnnotation{ImmutableString("position")}, IOType::Output, BuiltInVariable::gl_Position(), ImmutableString("vec4"), ImmutableString(nullptr), ImmutableString(nullptr)}}, {"gl_PointSize", GlslToWgslBuiltinMapping{ImmutableString("gl_PointSize"), NoAnnotation{}, IOType::Output, BuiltInVariable::gl_PointSize(), ImmutableString("f32"), ImmutableString(nullptr), ImmutableString(nullptr)}}, // TODO(anglebug.com/42267100): might have to emulate clip_distances, see // Metal's // https://source.chromium.org/chromium/chromium/src/+/main:third_party/angle/src/compiler/translator/msl/TranslatorMSL.cpp?q=symbol%3A%5Cbsh%3A%3AEmulateClipDistanceVaryings%5Cb%20case%3Ayes {"gl_ClipDistance", GlslToWgslBuiltinMapping{ImmutableString("gl_ClipDistance"), BuiltinAnnotation{ImmutableString("clip_distances")}, IOType::Output, nullptr, ImmutableString("TODO"), ImmutableString(nullptr), ImmutableString(nullptr)}}}); static const angle::base::NoDestructor> kGlslBuiltinToWgslBuiltinFragment({ {"gl_FragCoord", GlslToWgslBuiltinMapping{ImmutableString("gl_FragCoord"), BuiltinAnnotation{ImmutableString("position")}, IOType::Input, BuiltInVariable::gl_FragCoord(), ImmutableString("vec4"), ImmutableString(nullptr), ImmutableString(nullptr)}}, {"gl_FrontFacing", GlslToWgslBuiltinMapping{ImmutableString("gl_FrontFacing"), BuiltinAnnotation{ImmutableString("front_facing")}, IOType::Input, BuiltInVariable::gl_FrontFacing(), ImmutableString("bool"), ImmutableString(nullptr), ImmutableString(nullptr)}}, {"gl_SampleID", GlslToWgslBuiltinMapping{ ImmutableString("gl_SampleID"), BuiltinAnnotation{ImmutableString("sample_index")}, IOType::Input, BuiltInVariable::gl_SampleID(), ImmutableString("u32"), ImmutableString("i32"), ImmutableString("i32")}}, // TODO(anglebug.com/42267100): gl_SampleMask is GLSL 4.00 or ARB_sample_shading and // requires some special handling (see Metal). {"gl_SampleMaskIn", GlslToWgslBuiltinMapping{ImmutableString("gl_SampleMaskIn"), BuiltinAnnotation{ImmutableString("sample_mask")}, IOType::Input, nullptr, ImmutableString("u32"), ImmutableString("i32"), ImmutableString("i32")}}, // Just translate FragColor into a location = 0 out variable. // TODO(anglebug.com/42267100): maybe ASSERT that there are no user-defined output // variables? Is it possible for there to be other output variables when using // FragColor? {"gl_FragColor", GlslToWgslBuiltinMapping{ImmutableString("gl_FragColor"), LocationAnnotation{0}, IOType::Output, BuiltInVariable::gl_FragColor(), ImmutableString("vec4"), ImmutableString(nullptr), ImmutableString(nullptr)}}, {"gl_SampleMask", GlslToWgslBuiltinMapping{ImmutableString("gl_SampleMask"), BuiltinAnnotation{ImmutableString("sample_mask")}, IOType::Output, nullptr, ImmutableString("u32"), ImmutableString("i32"), ImmutableString("i32")}}, {"gl_FragDepth", GlslToWgslBuiltinMapping{ ImmutableString("gl_FragDepth"), BuiltinAnnotation{ImmutableString("frag_depth")}, IOType::Output, BuiltInVariable::gl_FragDepth(), ImmutableString("f32"), ImmutableString(nullptr), ImmutableString(nullptr)}}, }); // TODO(anglebug.com/42267100): gl_FragData needs to be emulated. Need something // like spir-v's // third_party/angle/src/compiler/translator/tree_ops/spirv/EmulateFragColorData.h. if (shaderType == GL_VERTEX_SHADER) { auto it = kGlslBuiltinToWgslBuiltinVertex->find(glslBuiltinName); if (it == kGlslBuiltinToWgslBuiltinVertex->end()) { return false; } *outMapping = it->second; return true; } else if (shaderType == GL_FRAGMENT_SHADER) { auto it = kGlslBuiltinToWgslBuiltinFragment->find(glslBuiltinName); if (it == kGlslBuiltinToWgslBuiltinFragment->end()) { return false; } *outMapping = it->second; return true; } else { UNREACHABLE(); return false; } } ImmutableString CreateNameToReplaceBuiltin(ImmutableString glslBuiltinName) { ImmutableStringBuilder newName(glslBuiltinName.length() + 1); newName << glslBuiltinName << '_'; return newName; } } // namespace // Friended by RewritePipelineVarOutput class RewritePipelineVarOutputBuilder { public: static bool GenerateMainFunctionAndIOStructs(TCompiler &compiler, TIntermBlock &root, RewritePipelineVarOutput &outVarReplacements); private: static bool GeneratePipelineStructStrings( RewritePipelineVarOutput::WgslIOBlock *ioblock, RewritePipelineVarOutput::RewrittenVarSet *varsToReplace, ImmutableString toStruct, ImmutableString fromStruct, const std::vector &shaderVars, const GlobalVars &globalVars, TCompiler &compiler, IOType ioType, const std::string &debugString); static bool GenerateForBuiltinVar(RewritePipelineVarOutput::WgslIOBlock *ioblock, RewritePipelineVarOutput::RewrittenVarSet *varsToReplace, ImmutableString toStruct, ImmutableString fromStruct, TCompiler &compiler, IOType ioType, const std::string &shaderVarName) { GlslToWgslBuiltinMapping wgslName; if (!GetWgslBuiltinName(shaderVarName, compiler.getShaderType(), &wgslName)) { return false; } const TVariable *varToReplace = wgslName.builtinVar; if (varToReplace == nullptr) { // Should be declared somewhere as a symbol. // TODO(anglebug.com/42267100): Not sure if this ever actually occurs. Will this // TVariable also have a declaration? Are there any gl_ variable that require or // even allow declaration? varToReplace = static_cast(compiler.getSymbolTable().findBuiltIn( ImmutableString(wgslName.glslBuiltinName), compiler.getShaderVersion())); if (kOutputVariableUses) { std::cout << "Var " << shaderVarName << " did not have a BuiltIn var but does have a builtin in the symbol " "table" << std::endl; } } ASSERT(ioType == wgslName.ioType); varsToReplace->insert(varToReplace->uniqueId().get()); ImmutableString builtinReplacement = CreateNameToReplaceBuiltin(wgslName.glslBuiltinName); // E.g. `gl_VertexID_ : i32`. ImmutableString globalType = wgslName.wgslTypeExpectedByShader.empty() ? wgslName.wgslBuiltinType : wgslName.wgslTypeExpectedByShader; ImmutableString globalStructVar = BuildConcatenatedImmutableString(builtinReplacement, " : ", globalType, ","); ioblock->angleGlobalMembers.push_back(globalStructVar); if (auto *builtinAnnotation = std::get_if(&wgslName.wgslPipelineAnnotation)) { // E.g. `@builtin(vertex_index) gl_VertexID_ : u32,`. const char *builtinAnnotationStart = "@builtin("; const char *builtinAnnotationEnd = ") "; ImmutableString annotatedStructVar = BuildConcatenatedImmutableString( builtinAnnotationStart, builtinAnnotation->wgslBuiltinName, builtinAnnotationEnd, builtinReplacement, " : ", wgslName.wgslBuiltinType, ","); ioblock->angleAnnotatedMembers.push_back(annotatedStructVar); } else if (auto *locationAnnotation = std::get_if(&wgslName.wgslPipelineAnnotation)) { ASSERT(locationAnnotation->location == 0); // E.g. `@location(0) gl_FragColor_ : vec4,`. const char *locationAnnotationStr = "@location(0) "; ImmutableString annotatedStructVar = BuildConcatenatedImmutableString( locationAnnotationStr, builtinReplacement, " : ", wgslName.wgslBuiltinType, ","); ioblock->angleAnnotatedMembers.push_back(annotatedStructVar); } else { ASSERT(std::get_if(&wgslName.wgslPipelineAnnotation)); } if (!std::get_if(&wgslName.wgslPipelineAnnotation)) { // E.g. `ANGLE_input_global.gl_VertexID_ = u32(ANGLE_input_annotated.gl_VertexID_);` ImmutableString conversion(nullptr); if (wgslName.conversionFunc.empty()) { conversion = BuildConcatenatedImmutableString(toStruct, ".", builtinReplacement, " = ", fromStruct, ".", builtinReplacement, ";"); } else { conversion = BuildConcatenatedImmutableString( toStruct, ".", builtinReplacement, " = ", wgslName.conversionFunc, "(", fromStruct, ".", builtinReplacement, ");"); } ioblock->angleConversionFuncs.push_back(conversion); } return true; } }; // Given a list of `shaderVars` (as well as `compiler` and a list of global variables in the GLSL // source, `globalVars`), computes the fields that should appear in the input/output pipeline // structs and the annotations that should appear in the WGSL source. // // `ioblock` will be filled with strings that make up the resulting structs, and with the strings // indicated by `fromStruct` and `toStruct`. `varsToReplace` will be filled with the symbols that // should be replaced in the final WGSL source wtih struct accesses. // // Finally, `debugString` should describe `shaderVars` (e.g. "input varyings"), and `ioType` // indicates whether `shaderVars` is meant to be an input or output variable, which is useful for // debugging asserts. [[nodiscard]] bool RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings( RewritePipelineVarOutput::WgslIOBlock *ioblock, RewritePipelineVarOutput::RewrittenVarSet *varsToReplace, ImmutableString toStruct, ImmutableString fromStruct, const std::vector &shaderVars, const GlobalVars &globalVars, TCompiler &compiler, IOType ioType, const std::string &debugString) { for (const ShaderVariable &shaderVar : shaderVars) { if (shaderVar.name == "gl_FragData" || shaderVar.name == "gl_SecondaryFragColorEXT" || shaderVar.name == "gl_SecondaryFragDataEXT") { // TODO(anglebug.com/42267100): declare gl_FragData as multiple variables. UNIMPLEMENTED(); return false; } if (kOutputVariableUses) { std::cout << "Use of " << (shaderVar.isBuiltIn() ? "builtin " : "") << debugString << ": " << shaderVar.name << std::endl; } if (shaderVar.isBuiltIn()) { if (!GenerateForBuiltinVar(ioblock, varsToReplace, toStruct, fromStruct, compiler, ioType, shaderVar.name)) { return false; } } else { if (!shaderVar.active) { // Skip any inactive attributes as they won't be assigned a location anyway. continue; } auto globalVarIt = globalVars.find(shaderVar.name); if (globalVarIt == globalVars.end()) { ANGLE_LOG(ERR) << "Should have found " << shaderVar.name << " in global vars"; return false; } TIntermDeclaration *declNode = globalVarIt->second; const TVariable *astVar = &ViewDeclaration(*declNode).symbol.variable(); TStringStream userVarNameStream; WriteNameOf(userVarNameStream, *astVar); TString userVarNameStr = userVarNameStream.str(); varsToReplace->insert(astVar->uniqueId().get()); // E.g. `_uuserVar : i32,`. TStringStream typeStream; WriteWgslType(typeStream, astVar->getType(), {}); TString type = typeStream.str(); ImmutableString globalStructVar = BuildConcatenatedImmutableString(userVarNameStr.c_str(), " : ", type.c_str(), ","); ioblock->angleGlobalMembers.push_back(globalStructVar); if (astVar->getType().isArray()) { // TODO(anglebug.com/42267100): need to support arrays (of scalars, vectors, and // matrices, maybe structs). ANGLE_LOG(ERR) << "Shader in/out variables of array type currently not supported."; return false; } else if (astVar->getType().isMatrix()) { // E.g. // @location(@@@@@@) outMatArr_col0 : vec3, // @location(@@@@@@) outMatArr_col1 : vec3, // @location(@@@@@@) outMatArr_col2 : vec3, TStringStream colVarList; // To the input/output struct, add one vector variable per matrix column. uint8_t cols = astVar->getType().getCols(); for (uint8_t i = 0; i < cols; i++) { const char *locationAnnotationStr = "@location(@@@@@@) "; TStringStream rowVecTypeStream; TType rowVecAstType = astVar->getType(); rowVecAstType.toMatrixColumnType(); WriteWgslType(rowVecTypeStream, rowVecAstType, {}); TString rowVecType = rowVecTypeStream.str(); ImmutableString colVarName = BuildConcatenatedImmutableString(userVarNameStr.c_str(), "_col", i); if (ioType == IOType::Input) { colVarList << fromStruct << "." << colVarName; if (i != cols - 1) { colVarList << ", "; } } // Add a column vec to the WGSL in/out block. ImmutableString annotatedStructVar = BuildConcatenatedImmutableString( locationAnnotationStr, colVarName, " : ", rowVecType.c_str(), ","); ioblock->angleAnnotatedMembers.push_back(annotatedStructVar); // When outputting matrices, they need to be split into column vectors which are // then placed in the WGSL in/out block. if (ioType == IOType::Output) { // e.g. // ANGLE_output_annotated.outMatArr_col0 = ANGLE_output_global.outMatArr[0]; // ANGLE_output_annotated.outMatArr_col1 = ANGLE_output_global.outMatArr[1]; // ANGLE_output_annotated.outMatArr_col2 = ANGLE_output_global.outMatArr[2]; ImmutableString extractColVec = BuildConcatenatedImmutableString( toStruct, '.', colVarName, " = ", fromStruct, '.', userVarNameStr.c_str(), '[', i, "];"); ioblock->angleConversionFuncs.push_back(extractColVec); } } // If input, construct the global matrix var from the column vectors in the WGSL // input block. if (ioType == IOType::Input) { // e.g. ANGLE_input_global.inMat = mat3x3(ANGLE_input_annotated.inMat_col0, // ANGLE_input_annotated.inMat_col1, ANGLE_input_annotated.inMat_col2); ImmutableString conversion = BuildConcatenatedImmutableString( toStruct, ".", userVarNameStr.c_str(), " = ", type.c_str(), '(', colVarList.str().c_str(), ");"); ioblock->angleConversionFuncs.push_back(conversion); } } else { // The only two types supported natively by WGSL are scalars and vectors. ASSERT((astVar->getType().isVector() || astVar->getType().isScalar()) && !astVar->getType().isArray()); // E.g. `@location(@@@@@@) _uuserVar : i32,`. const char *locationAnnotationStr = "@location(@@@@@@) "; ImmutableString annotatedStructVar = BuildConcatenatedImmutableString(locationAnnotationStr, globalStructVar); ioblock->angleAnnotatedMembers.push_back(annotatedStructVar); // E.g. `ANGLE_input_global._uuserVar = ANGLE_input_annotated._uuserVar;` ImmutableString conversion = BuildConcatenatedImmutableString(toStruct, ".", userVarNameStr.c_str(), " = ", fromStruct, ".", userVarNameStr.c_str(), ";"); ioblock->angleConversionFuncs.push_back(conversion); } } } return true; } bool RewritePipelineVarOutputBuilder::GenerateMainFunctionAndIOStructs( TCompiler &compiler, TIntermBlock &root, RewritePipelineVarOutput &outVarReplacements) { GlobalVars globalVars = FindGlobalVars(&root); // The Dawn WGSL compiler generates an error if there is no builtin(position) variable in a // vertex shader, though it doesn't look like the WGSL spec requires this. GLSL doesn't require // use of gl_Position (only that its value is undefined if not written to). So, generate a // @builtin(position) variable by pretending gl_Position is present even if it's not. if (compiler.getShaderType() == GL_VERTEX_SHADER) { bool hasPosition = false; for (const ShaderVariable &shaderVar : compiler.getOutputVaryings()) { if (shaderVar.name == std::string("gl_Position")) { hasPosition = true; } } if (!hasPosition) { if (!GenerateForBuiltinVar( &outVarReplacements.mOutputBlock, &outVarReplacements.mAngleOutputVars, /*toStruct=*/ImmutableString(kBuiltinOutputAnnotatedStructName), /*fromStruct=*/ImmutableString(kBuiltinOutputStructName), compiler, IOType::Output, "gl_Position")) { return false; } } } if (!RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings( &outVarReplacements.mInputBlock, &outVarReplacements.mAngleInputVars, /*toStruct=*/ImmutableString(kBuiltinInputStructName), /*fromStruct=*/ImmutableString(kBuiltinInputAnnotatedStructName), compiler.getInputVaryings(), globalVars, compiler, IOType::Input, "input varyings") || !RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings( &outVarReplacements.mInputBlock, &outVarReplacements.mAngleInputVars, /*toStruct=*/ImmutableString(kBuiltinInputStructName), /*fromStruct=*/ImmutableString(kBuiltinInputAnnotatedStructName), compiler.getAttributes(), globalVars, compiler, IOType::Input, "input attributes") || !RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings( &outVarReplacements.mOutputBlock, &outVarReplacements.mAngleOutputVars, /*toStruct=*/ImmutableString(kBuiltinOutputAnnotatedStructName), /*fromStruct=*/ImmutableString(kBuiltinOutputStructName), compiler.getOutputVaryings(), globalVars, compiler, IOType::Output, "output varyings") || !RewritePipelineVarOutputBuilder::GeneratePipelineStructStrings( &outVarReplacements.mOutputBlock, &outVarReplacements.mAngleOutputVars, /*toStruct=*/ImmutableString(kBuiltinOutputAnnotatedStructName), /*fromStruct=*/ImmutableString(kBuiltinOutputStructName), compiler.getOutputVariables(), globalVars, compiler, IOType::Output, "output variables")) { return false; } return true; } RewritePipelineVarOutput::RewritePipelineVarOutput(sh::GLenum shaderType) : mShaderType(shaderType) {} bool RewritePipelineVarOutput::IsInputVar(TSymbolUniqueId angleInputVar) const { return mAngleInputVars.count(angleInputVar.get()) > 0; } bool RewritePipelineVarOutput::IsOutputVar(TSymbolUniqueId angleOutputVar) const { return mAngleOutputVars.count(angleOutputVar.get()) > 0; } // static bool RewritePipelineVarOutput::OutputIOStruct(TInfoSinkBase &output, WgslIOBlock &block, ImmutableString builtinStructType, ImmutableString builtinStructName, ImmutableString builtinAnnotatedStructType) { if (!block.angleGlobalMembers.empty()) { // Output global struct definition. output << "struct " << builtinStructType << " {\n"; for (const ImmutableString &globalMember : block.angleGlobalMembers) { output << " " << globalMember << "\n"; } output << "};\n\n"; // Output decl of global struct. output << "var " << builtinStructName << " : " << builtinStructType << ";\n\n"; // Output annotated struct definition. output << "struct " << builtinAnnotatedStructType << " {\n"; for (const ImmutableString &annotatedMember : block.angleAnnotatedMembers) { output << " " << annotatedMember << "\n"; } output << "};\n\n"; } return true; } bool RewritePipelineVarOutput::OutputStructs(TInfoSinkBase &output) { if (!OutputIOStruct(output, mInputBlock, ImmutableString(kBuiltinInputStructType), ImmutableString(kBuiltinInputStructName), ImmutableString(kBuiltinInputAnnotatedStructType)) || !OutputIOStruct(output, mOutputBlock, ImmutableString(kBuiltinOutputStructType), ImmutableString(kBuiltinOutputStructName), ImmutableString(kBuiltinOutputAnnotatedStructType))) { return false; } return true; } // Could split OutputMainFunction() into the different parts of the main function. bool RewritePipelineVarOutput::OutputMainFunction(TInfoSinkBase &output) { if (mShaderType == GL_VERTEX_SHADER) { output << "@vertex\n"; } else { ASSERT(mShaderType == GL_FRAGMENT_SHADER); output << "@fragment\n"; } output << "fn wgslMain("; if (!mInputBlock.angleGlobalMembers.empty()) { output << kBuiltinInputAnnotatedStructName << " : " << kBuiltinInputAnnotatedStructType; } output << ")"; if (!mOutputBlock.angleGlobalMembers.empty()) { output << " -> " << kBuiltinOutputAnnotatedStructType; } output << "\n{\n"; for (const ImmutableString &conversionFunc : mInputBlock.angleConversionFuncs) { output << " " << conversionFunc << "\n"; } output << " " << '_' << kUserDefinedNamePrefix << "main()" << ";\n"; if (!mOutputBlock.angleGlobalMembers.empty()) { output << " var " << kBuiltinOutputAnnotatedStructName << " : " << kBuiltinOutputAnnotatedStructType << ";\n"; for (const ImmutableString &conversionFunc : mOutputBlock.angleConversionFuncs) { output << " " << conversionFunc << "\n"; } output << " return " << kBuiltinOutputAnnotatedStructName << ";\n"; } output << "}\n"; return true; } bool GenerateMainFunctionAndIOStructs(TCompiler &compiler, TIntermBlock &root, RewritePipelineVarOutput &outVarReplacements) { return RewritePipelineVarOutputBuilder::GenerateMainFunctionAndIOStructs(compiler, root, outVarReplacements); } } // namespace sh