// // Copyright 2016 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. // // TranslatorSPIRV: // A set of transformations that prepare the AST to be compatible with GL_KHR_vulkan_glsl followed // by a pass that generates SPIR-V. // See: https://www.khronos.org/registry/vulkan/specs/misc/GL_KHR_vulkan_glsl.txt // #include "compiler/translator/spirv/TranslatorSPIRV.h" #include "angle_gl.h" #include "common/PackedEnums.h" #include "common/utilities.h" #include "compiler/translator/ImmutableStringBuilder.h" #include "compiler/translator/IntermNode.h" #include "compiler/translator/StaticType.h" #include "compiler/translator/spirv/BuiltinsWorkaround.h" #include "compiler/translator/spirv/OutputSPIRV.h" #include "compiler/translator/tree_ops/DeclarePerVertexBlocks.h" #include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h" #include "compiler/translator/tree_ops/RecordConstantPrecision.h" #include "compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h" #include "compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h" #include "compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h" #include "compiler/translator/tree_ops/RewriteAtomicCounters.h" #include "compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h" #include "compiler/translator/tree_ops/RewriteDfdy.h" #include "compiler/translator/tree_ops/RewriteStructSamplers.h" #include "compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h" #include "compiler/translator/tree_ops/spirv/EmulateAdvancedBlendEquations.h" #include "compiler/translator/tree_ops/spirv/EmulateDithering.h" #include "compiler/translator/tree_ops/spirv/EmulateFragColorData.h" #include "compiler/translator/tree_ops/spirv/EmulateFramebufferFetch.h" #include "compiler/translator/tree_ops/spirv/EmulateYUVBuiltIns.h" #include "compiler/translator/tree_ops/spirv/FlagSamplersWithTexelFetch.h" #include "compiler/translator/tree_ops/spirv/RewriteInterpolateAtOffset.h" #include "compiler/translator/tree_ops/spirv/RewriteR32fImages.h" #include "compiler/translator/tree_util/BuiltIn.h" #include "compiler/translator/tree_util/DriverUniform.h" #include "compiler/translator/tree_util/FindFunction.h" #include "compiler/translator/tree_util/FindMain.h" #include "compiler/translator/tree_util/IntermNode_util.h" #include "compiler/translator/tree_util/ReplaceClipCullDistanceVariable.h" #include "compiler/translator/tree_util/ReplaceVariable.h" #include "compiler/translator/tree_util/RewriteSampleMaskVariable.h" #include "compiler/translator/tree_util/RunAtTheBeginningOfShader.h" #include "compiler/translator/tree_util/RunAtTheEndOfShader.h" #include "compiler/translator/tree_util/SpecializationConstant.h" #include "compiler/translator/util.h" namespace sh { namespace { constexpr ImmutableString kFlippedPointCoordName = ImmutableString("flippedPointCoord"); constexpr ImmutableString kFlippedFragCoordName = ImmutableString("flippedFragCoord"); constexpr ImmutableString kDefaultUniformsBlockName = ImmutableString("defaultUniforms"); bool IsDefaultUniform(const TType &type) { return type.getQualifier() == EvqUniform && type.getInterfaceBlock() == nullptr && !IsOpaqueType(type.getBasicType()); } class ReplaceDefaultUniformsTraverser : public TIntermTraverser { public: ReplaceDefaultUniformsTraverser(const VariableReplacementMap &variableMap) : TIntermTraverser(true, false, false), mVariableMap(variableMap) {} bool visitDeclaration(Visit visit, TIntermDeclaration *node) override { const TIntermSequence &sequence = *(node->getSequence()); TIntermTyped *variable = sequence.front()->getAsTyped(); const TType &type = variable->getType(); if (IsDefaultUniform(type)) { // Remove the uniform declaration. TIntermSequence emptyReplacement; mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, std::move(emptyReplacement)); return false; } return true; } void visitSymbol(TIntermSymbol *symbol) override { const TVariable &variable = symbol->variable(); const TType &type = variable.getType(); if (!IsDefaultUniform(type) || gl::IsBuiltInName(variable.name().data())) { return; } ASSERT(mVariableMap.count(&variable) > 0); queueReplacement(mVariableMap.at(&variable)->deepCopy(), OriginalNode::IS_DROPPED); } private: const VariableReplacementMap &mVariableMap; }; bool DeclareDefaultUniforms(TranslatorSPIRV *compiler, TIntermBlock *root, TSymbolTable *symbolTable, gl::ShaderType shaderType) { // First, collect all default uniforms and declare a uniform block. TFieldList *uniformList = new TFieldList; TVector uniformVars; for (TIntermNode *node : *root->getSequence()) { TIntermDeclaration *decl = node->getAsDeclarationNode(); if (decl == nullptr) { continue; } const TIntermSequence &sequence = *(decl->getSequence()); TIntermSymbol *symbol = sequence.front()->getAsSymbolNode(); if (symbol == nullptr) { continue; } const TType &type = symbol->getType(); if (IsDefaultUniform(type)) { TType *fieldType = new TType(type); uniformList->push_back(new TField(fieldType, symbol->getName(), symbol->getLine(), symbol->variable().symbolType())); uniformVars.push_back(&symbol->variable()); } } TLayoutQualifier layoutQualifier = TLayoutQualifier::Create(); layoutQualifier.blockStorage = EbsStd140; const TVariable *uniformBlock = DeclareInterfaceBlock( root, symbolTable, uniformList, EvqUniform, layoutQualifier, TMemoryQualifier::Create(), 0, kDefaultUniformsBlockName, ImmutableString("")); compiler->assignSpirvId(uniformBlock->getType().getInterfaceBlock()->uniqueId(), vk::spirv::kIdDefaultUniformsBlock); // Create a map from the uniform variables to new variables that reference the fields of the // block. VariableReplacementMap variableMap; for (size_t fieldIndex = 0; fieldIndex < uniformVars.size(); ++fieldIndex) { const TVariable *variable = uniformVars[fieldIndex]; TType *replacementType = new TType(variable->getType()); replacementType->setInterfaceBlockField(uniformBlock->getType().getInterfaceBlock(), fieldIndex); TVariable *replacementVariable = new TVariable(symbolTable, variable->name(), replacementType, variable->symbolType()); variableMap[variable] = new TIntermSymbol(replacementVariable); } // Finally transform the AST and make sure references to the uniforms are replaced with the new // variables. ReplaceDefaultUniformsTraverser defaultTraverser(variableMap); root->traverse(&defaultTraverser); return defaultTraverser.updateTree(compiler, root); } // Replaces a builtin variable with a version that is rotated and corrects the X and Y coordinates. [[nodiscard]] bool RotateAndFlipBuiltinVariable(TCompiler *compiler, TIntermBlock *root, TIntermSequence *insertSequence, TIntermTyped *swapXY, TIntermTyped *flipXY, TSymbolTable *symbolTable, const TVariable *builtin, const ImmutableString &flippedVariableName, TIntermTyped *pivot) { // Create a symbol reference to 'builtin'. TIntermSymbol *builtinRef = new TIntermSymbol(builtin); // Create a symbol reference to our new variable that will hold the modified builtin. TType *type = new TType(builtin->getType()); type->setQualifier(EvqGlobal); type->setPrimarySize(builtin->getType().getNominalSize()); TVariable *replacementVar = new TVariable(symbolTable, flippedVariableName, type, SymbolType::AngleInternal); DeclareGlobalVariable(root, replacementVar); TIntermSymbol *flippedBuiltinRef = new TIntermSymbol(replacementVar); // Use this new variable instead of 'builtin' everywhere. if (!ReplaceVariable(compiler, root, builtin, replacementVar)) { return false; } // Create the expression "(swapXY ? builtin.yx : builtin.xy)" TIntermTyped *builtinXY = new TIntermSwizzle(builtinRef, {0, 1}); TIntermTyped *builtinYX = new TIntermSwizzle(builtinRef->deepCopy(), {1, 0}); builtinXY = new TIntermTernary(swapXY, builtinYX, builtinXY); // Create the expression "(builtin.xy - pivot) * flipXY + pivot TIntermBinary *removePivot = new TIntermBinary(EOpSub, builtinXY, pivot); TIntermBinary *inverseXY = new TIntermBinary(EOpMul, removePivot, flipXY); TIntermBinary *plusPivot = new TIntermBinary(EOpAdd, inverseXY, pivot->deepCopy()); // Create the corrected variable and copy the value of the original builtin. TIntermBinary *assignment = new TIntermBinary(EOpAssign, flippedBuiltinRef, builtinRef->deepCopy()); // Create an assignment to the replaced variable's .xy. TIntermSwizzle *correctedXY = new TIntermSwizzle(flippedBuiltinRef->deepCopy(), {0, 1}); TIntermBinary *assignToXY = new TIntermBinary(EOpAssign, correctedXY, plusPivot); // Add this assigment at the beginning of the main function insertSequence->insert(insertSequence->begin(), assignToXY); insertSequence->insert(insertSequence->begin(), assignment); return compiler->validateAST(root); } TIntermSequence *GetMainSequence(TIntermBlock *root) { TIntermFunctionDefinition *main = FindMain(root); return main->getBody()->getSequence(); } // Declares a new variable to replace gl_DepthRange, its values are fed from a driver uniform. [[nodiscard]] bool ReplaceGLDepthRangeWithDriverUniform(TCompiler *compiler, TIntermBlock *root, const DriverUniform *driverUniforms, TSymbolTable *symbolTable) { // Create a symbol reference to "gl_DepthRange" const TVariable *depthRangeVar = static_cast( symbolTable->findBuiltIn(ImmutableString("gl_DepthRange"), 0)); // ANGLEUniforms.depthRange TIntermTyped *angleEmulatedDepthRangeRef = driverUniforms->getDepthRange(); // Use this variable instead of gl_DepthRange everywhere. return ReplaceVariableWithTyped(compiler, root, depthRangeVar, angleEmulatedDepthRangeRef); } // Declares a new variable to replace gl_BoundingBoxEXT, its values are fed from a global temporary // variable. [[nodiscard]] bool ReplaceGLBoundingBoxWithGlobal(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable, int shaderVersion) { // Declare the replacement bounding box variable type TType *emulatedBoundingBoxDeclType = new TType(EbtFloat, EbpHigh, EvqGlobal, 4); emulatedBoundingBoxDeclType->makeArray(2u); TVariable *ANGLEBoundingBoxVar = new TVariable( symbolTable->nextUniqueId(), ImmutableString("ANGLEBoundingBox"), SymbolType::AngleInternal, TExtension::EXT_primitive_bounding_box, emulatedBoundingBoxDeclType); DeclareGlobalVariable(root, ANGLEBoundingBoxVar); const TVariable *builtinBoundingBoxVar; bool replacementResult = true; // Create a symbol reference to "gl_BoundingBoxEXT" builtinBoundingBoxVar = static_cast( symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxEXT"), shaderVersion)); if (builtinBoundingBoxVar != nullptr) { // Use the replacement variable instead of builtin gl_BoundingBoxEXT everywhere. replacementResult &= ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar); } // Create a symbol reference to "gl_BoundingBoxOES" builtinBoundingBoxVar = static_cast( symbolTable->findBuiltIn(ImmutableString("gl_BoundingBoxOES"), shaderVersion)); if (builtinBoundingBoxVar != nullptr) { // Use the replacement variable instead of builtin gl_BoundingBoxOES everywhere. replacementResult &= ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar); } if (shaderVersion >= 320) { // Create a symbol reference to "gl_BoundingBox" builtinBoundingBoxVar = static_cast( symbolTable->findBuiltIn(ImmutableString("gl_BoundingBox"), shaderVersion)); if (builtinBoundingBoxVar != nullptr) { // Use the replacement variable instead of builtin gl_BoundingBox everywhere. replacementResult &= ReplaceVariable(compiler, root, builtinBoundingBoxVar, ANGLEBoundingBoxVar); } } return replacementResult; } [[nodiscard]] bool AddXfbEmulationSupport(TranslatorSPIRV *compiler, TIntermBlock *root, TSymbolTable *symbolTable, const DriverUniform *driverUniforms) { // Generate the following function and place it before main(). This function takes a "strides" // parameter that is determined at link time, and calculates for each transform feedback buffer // (of which there are a maximum of four) what the starting index is to write to the output // buffer. // // ivec4 ANGLEGetXfbOffsets(ivec4 strides) // { // int xfbIndex = gl_VertexIndex // + gl_InstanceIndex * ANGLEUniforms.xfbVerticesPerInstance; // return ANGLEUniforms.xfbBufferOffsets + xfbIndex * strides; // } constexpr uint32_t kMaxXfbBuffers = 4; const TType *ivec4Type = StaticType::GetBasic(); TType *stridesType = new TType(*ivec4Type); stridesType->setQualifier(EvqParamConst); // Create the parameter variable. TVariable *stridesVar = new TVariable(symbolTable, ImmutableString("strides"), stridesType, SymbolType::AngleInternal); TIntermSymbol *stridesSymbol = new TIntermSymbol(stridesVar); // Create references to gl_VertexIndex, gl_InstanceIndex, ANGLEUniforms.xfbVerticesPerInstance // and ANGLEUniforms.xfbBufferOffsets. TIntermSymbol *vertexIndex = new TIntermSymbol(BuiltInVariable::gl_VertexIndex()); TIntermSymbol *instanceIndex = new TIntermSymbol(BuiltInVariable::gl_InstanceIndex()); TIntermTyped *xfbVerticesPerInstance = driverUniforms->getXfbVerticesPerInstance(); TIntermTyped *xfbBufferOffsets = driverUniforms->getXfbBufferOffsets(); // gl_InstanceIndex * ANGLEUniforms.xfbVerticesPerInstance TIntermBinary *xfbInstanceIndex = new TIntermBinary(EOpMul, instanceIndex, xfbVerticesPerInstance); // gl_VertexIndex + |xfbInstanceIndex| TIntermBinary *xfbIndex = new TIntermBinary(EOpAdd, vertexIndex, xfbInstanceIndex); // |xfbIndex| * |strides| TIntermBinary *xfbStrides = new TIntermBinary(EOpVectorTimesScalar, xfbIndex, stridesSymbol); // ANGLEUniforms.xfbBufferOffsets + |xfbStrides| TIntermBinary *xfbOffsets = new TIntermBinary(EOpAdd, xfbBufferOffsets, xfbStrides); // Create the function body, which has a single return statement. Note that the `xfbIndex` // variable declared in the comment at the beginning of this function is simply replaced in the // return statement for brevity. TIntermBlock *body = new TIntermBlock; body->appendStatement(new TIntermBranch(EOpReturn, xfbOffsets)); // Declare the function TFunction *getOffsetsFunction = new TFunction(symbolTable, ImmutableString("ANGLEGetXfbOffsets"), SymbolType::AngleInternal, ivec4Type, true); getOffsetsFunction->addParameter(stridesVar); compiler->assignSpirvId(getOffsetsFunction->uniqueId(), vk::spirv::kIdXfbEmulationGetOffsetsFunction); TIntermFunctionDefinition *functionDef = CreateInternalFunctionDefinitionNode(*getOffsetsFunction, body); // Insert the function declaration before main(). const size_t mainIndex = FindMainIndex(root); root->insertChildNodes(mainIndex, {functionDef}); // Generate the following function and place it before main(). This function will be filled // with transform feedback capture code at link time. // // void ANGLECaptureXfb() // { // } const TType *voidType = StaticType::GetBasic(); // Create the function body, which is empty. body = new TIntermBlock; // Declare the function TFunction *xfbCaptureFunction = new TFunction(symbolTable, ImmutableString("ANGLECaptureXfb"), SymbolType::AngleInternal, voidType, false); compiler->assignSpirvId(xfbCaptureFunction->uniqueId(), vk::spirv::kIdXfbEmulationCaptureFunction); // Insert the function declaration before main(). root->insertChildNodes(mainIndex, {CreateInternalFunctionDefinitionNode(*xfbCaptureFunction, body)}); // Create the following logic and add it at the end of main(): // // ANGLECaptureXfb(); // // Create the function call TIntermAggregate *captureXfbCall = TIntermAggregate::CreateFunctionCall(*xfbCaptureFunction, {}); // Run it at the end of the shader. if (!RunAtTheEndOfShader(compiler, root, captureXfbCall, symbolTable)) { return false; } // Additionally, generate the following storage buffer declarations used to capture transform // feedback output. Again, there's a maximum of four buffers. // // buffer ANGLEXfbBuffer0 // { // float xfbOut[]; // } ANGLEXfb0; // buffer ANGLEXfbBuffer1 // { // float xfbOut[]; // } ANGLEXfb1; // ... for (uint32_t bufferIndex = 0; bufferIndex < kMaxXfbBuffers; ++bufferIndex) { TFieldList *fieldList = new TFieldList; TType *xfbOutType = new TType(EbtFloat, EbpHigh, EvqGlobal); xfbOutType->makeArray(0); TField *field = new TField(xfbOutType, ImmutableString("xfbOut"), TSourceLoc(), SymbolType::AngleInternal); fieldList->push_back(field); static_assert( kMaxXfbBuffers < 10, "ImmutableStringBuilder memory size below needs to accomodate the number of buffers"); ImmutableStringBuilder blockName(strlen("ANGLEXfbBuffer") + 2); blockName << "ANGLEXfbBuffer"; blockName.appendDecimal(bufferIndex); ImmutableStringBuilder varName(strlen("ANGLEXfb") + 2); varName << "ANGLEXfb"; varName.appendDecimal(bufferIndex); TLayoutQualifier layoutQualifier = TLayoutQualifier::Create(); layoutQualifier.blockStorage = EbsStd430; const TVariable *xfbBuffer = DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, layoutQualifier, TMemoryQualifier::Create(), 0, blockName, varName); static_assert(vk::spirv::kIdXfbEmulationBufferBlockOne == vk::spirv::kIdXfbEmulationBufferBlockZero + 1); static_assert(vk::spirv::kIdXfbEmulationBufferBlockTwo == vk::spirv::kIdXfbEmulationBufferBlockZero + 2); static_assert(vk::spirv::kIdXfbEmulationBufferBlockThree == vk::spirv::kIdXfbEmulationBufferBlockZero + 3); static_assert(vk::spirv::kIdXfbEmulationBufferVarOne == vk::spirv::kIdXfbEmulationBufferVarZero + 1); static_assert(vk::spirv::kIdXfbEmulationBufferVarTwo == vk::spirv::kIdXfbEmulationBufferVarZero + 2); static_assert(vk::spirv::kIdXfbEmulationBufferVarThree == vk::spirv::kIdXfbEmulationBufferVarZero + 3); compiler->assignSpirvId(xfbBuffer->getType().getInterfaceBlock()->uniqueId(), vk::spirv::kIdXfbEmulationBufferBlockZero + bufferIndex); compiler->assignSpirvId(xfbBuffer->uniqueId(), vk::spirv::kIdXfbEmulationBufferVarZero + bufferIndex); } return compiler->validateAST(root); } [[nodiscard]] bool AddXfbExtensionSupport(TranslatorSPIRV *compiler, TIntermBlock *root, TSymbolTable *symbolTable, const DriverUniform *driverUniforms) { // Generate the following output varying declaration used to capture transform feedback output // from gl_Position, as it can't be captured directly due to changes that are applied to it for // clip-space correction and pre-rotation. // // out vec4 ANGLEXfbPosition; const TType *vec4Type = nullptr; switch (compiler->getShaderType()) { case GL_VERTEX_SHADER: vec4Type = StaticType::Get(); break; case GL_TESS_EVALUATION_SHADER_EXT: vec4Type = StaticType::Get(); break; case GL_GEOMETRY_SHADER_EXT: vec4Type = StaticType::Get(); break; default: UNREACHABLE(); } TVariable *varyingVar = new TVariable(symbolTable, ImmutableString("ANGLEXfbPosition"), vec4Type, SymbolType::AngleInternal); compiler->assignSpirvId(varyingVar->uniqueId(), vk::spirv::kIdXfbExtensionPosition); TIntermDeclaration *varyingDecl = new TIntermDeclaration(); varyingDecl->appendDeclarator(new TIntermSymbol(varyingVar)); // Insert the varying declaration before the first function. const size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root); root->insertChildNodes(firstFunctionIndex, {varyingDecl}); return compiler->validateAST(root); } [[nodiscard]] bool AddVertexTransformationSupport(TranslatorSPIRV *compiler, const ShCompileOptions &compileOptions, TIntermBlock *root, TSymbolTable *symbolTable, SpecConst *specConst, const DriverUniform *driverUniforms) { // In GL the viewport transformation is slightly different - see the GL 2.0 spec section "2.12.1 // Controlling the Viewport". In Vulkan the corresponding spec section is currently "23.4. // Coordinate Transformations". The following transformation needs to be done: // // z_vk = 0.5 * (w_gl + z_gl) // // where z_vk is the depth output of a Vulkan geometry-stage shader and z_gl is the same for GL. // // Generate the following function and place it before main(). This function takes // gl_Position and rotates xy, and adjusts z (if necessary). // // vec4 ANGLETransformPosition(vec4 position) // { // return vec4((swapXY ? position.yx : position.xy) * flipXY, // transformDepth ? (gl_Position.z + gl_Position.w) / 2 : gl_Position.z, // gl_Postion.w); // } const TType *vec4Type = StaticType::GetBasic(); TType *positionType = new TType(*vec4Type); positionType->setQualifier(EvqParamConst); // Create the parameter variable. TVariable *positionVar = new TVariable(symbolTable, ImmutableString("position"), positionType, SymbolType::AngleInternal); TIntermSymbol *positionSymbol = new TIntermSymbol(positionVar); // swapXY ? position.yx : position.xy TIntermTyped *swapXY = specConst->getSwapXY(); if (swapXY == nullptr) { swapXY = driverUniforms->getSwapXY(); } TIntermTyped *xy = new TIntermSwizzle(positionSymbol, {0, 1}); TIntermTyped *swappedXY = new TIntermSwizzle(positionSymbol->deepCopy(), {1, 0}); TIntermTyped *rotatedXY = new TIntermTernary(swapXY, swappedXY, xy); // (swapXY ? position.yx : position.xy) * flipXY TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::PreFragment); TIntermTyped *rotatedFlippedXY = new TIntermBinary(EOpMul, rotatedXY, flipXY); // (gl_Position.z + gl_Position.w) / 2 TIntermTyped *z = new TIntermSwizzle(positionSymbol->deepCopy(), {2}); TIntermTyped *w = new TIntermSwizzle(positionSymbol->deepCopy(), {3}); TIntermTyped *transformedDepth = z; if (compileOptions.addVulkanDepthCorrection) { TIntermBinary *zPlusW = new TIntermBinary(EOpAdd, z, w->deepCopy()); TIntermBinary *halfZPlusW = new TIntermBinary(EOpMul, zPlusW, CreateFloatNode(0.5, EbpMedium)); // transformDepth ? (gl_Position.z + gl_Position.w) / 2 : gl_Position.z, TIntermTyped *transformDepth = driverUniforms->getTransformDepth(); transformedDepth = new TIntermTernary(transformDepth, halfZPlusW, z->deepCopy()); } // vec4(...); TIntermSequence args = { rotatedFlippedXY, transformedDepth, w, }; TIntermTyped *transformedPosition = TIntermAggregate::CreateConstructor(*vec4Type, &args); // Create the function body, which has a single return statement. TIntermBlock *body = new TIntermBlock; body->appendStatement(new TIntermBranch(EOpReturn, transformedPosition)); // Declare the function TFunction *transformPositionFunction = new TFunction(symbolTable, ImmutableString("ANGLETransformPosition"), SymbolType::AngleInternal, vec4Type, true); transformPositionFunction->addParameter(positionVar); compiler->assignSpirvId(transformPositionFunction->uniqueId(), vk::spirv::kIdTransformPositionFunction); TIntermFunctionDefinition *functionDef = CreateInternalFunctionDefinitionNode(*transformPositionFunction, body); // Insert the function declaration before main(). const size_t mainIndex = FindMainIndex(root); root->insertChildNodes(mainIndex, {functionDef}); return compiler->validateAST(root); } [[nodiscard]] bool InsertFragCoordCorrection(TCompiler *compiler, const ShCompileOptions &compileOptions, TIntermBlock *root, TIntermSequence *insertSequence, TSymbolTable *symbolTable, SpecConst *specConst, const DriverUniform *driverUniforms) { TIntermTyped *flipXY = driverUniforms->getFlipXY(symbolTable, DriverUniformFlip::Fragment); TIntermTyped *pivot = driverUniforms->getHalfRenderArea(); TIntermTyped *swapXY = specConst->getSwapXY(); if (swapXY == nullptr) { swapXY = driverUniforms->getSwapXY(); } const TVariable *fragCoord = static_cast( symbolTable->findBuiltIn(ImmutableString("gl_FragCoord"), compiler->getShaderVersion())); return RotateAndFlipBuiltinVariable(compiler, root, insertSequence, swapXY, flipXY, symbolTable, fragCoord, kFlippedFragCoordName, pivot); } bool HasFramebufferFetch(const TExtensionBehavior &extBehavior, const ShCompileOptions &compileOptions) { return IsExtensionEnabled(extBehavior, TExtension::EXT_shader_framebuffer_fetch) || IsExtensionEnabled(extBehavior, TExtension::EXT_shader_framebuffer_fetch_non_coherent) || IsExtensionEnabled(extBehavior, TExtension::ARM_shader_framebuffer_fetch) || IsExtensionEnabled(extBehavior, TExtension::NV_shader_framebuffer_fetch) || (compileOptions.pls.type == ShPixelLocalStorageType::FramebufferFetch && IsExtensionEnabled(extBehavior, TExtension::ANGLE_shader_pixel_local_storage)); } template Variable *FindShaderVariable(std::vector *vars, const ImmutableString &name) { for (Variable &var : *vars) { if (name == var.name) { return &var; } } UNREACHABLE(); return nullptr; } ShaderVariable *FindIOBlockShaderVariable(std::vector *vars, const ImmutableString &name) { for (ShaderVariable &var : *vars) { if (name == var.structOrBlockName) { return &var; } } UNREACHABLE(); return nullptr; } ShaderVariable *FindUniformFieldShaderVariable(std::vector *vars, const ImmutableString &name, const char *prefix) { for (ShaderVariable &var : *vars) { // The name of the sampler is derived from the uniform name + fields // that reach the uniform, concatenated with '_' per RewriteStructSamplers. std::string varName = prefix; varName += '_'; varName += var.name; if (name == varName) { return &var; } ShaderVariable *field = FindUniformFieldShaderVariable(&var.fields, name, varName.c_str()); if (field != nullptr) { return field; } } return nullptr; } ShaderVariable *FindUniformShaderVariable(std::vector *vars, const ImmutableString &name) { for (ShaderVariable &var : *vars) { if (name == var.name) { return &var; } // Note: samplers in structs are moved out. Such samplers will be found in the fields of // the struct uniform. ShaderVariable *field = FindUniformFieldShaderVariable(&var.fields, name, var.name.c_str()); if (field != nullptr) { return field; } } UNREACHABLE(); return nullptr; } void SetSpirvIdInFields(uint32_t id, std::vector *fields) { for (ShaderVariable &field : *fields) { field.id = id; SetSpirvIdInFields(id, &field.fields); } } } // anonymous namespace TranslatorSPIRV::TranslatorSPIRV(sh::GLenum type, ShShaderSpec spec) : TCompiler(type, spec, SH_SPIRV_VULKAN_OUTPUT), mFirstUnusedSpirvId(0) {} bool TranslatorSPIRV::translateImpl(TIntermBlock *root, const ShCompileOptions &compileOptions, PerformanceDiagnostics * /*perfDiagnostics*/, SpecConst *specConst, DriverUniform *driverUniforms) { if (getShaderType() == GL_VERTEX_SHADER) { if (!ShaderBuiltinsWorkaround(this, root, &getSymbolTable(), compileOptions)) { return false; } } // Write out default uniforms into a uniform block assigned to a specific set/binding. int defaultUniformCount = 0; int aggregateTypesUsedForUniforms = 0; int r32fImageCount = 0; int atomicCounterCount = 0; for (const auto &uniform : getUniforms()) { if (!uniform.isBuiltIn() && uniform.active && !gl::IsOpaqueType(uniform.type)) { ++defaultUniformCount; } if (uniform.isStruct() || uniform.isArrayOfArrays()) { ++aggregateTypesUsedForUniforms; } if (uniform.active && gl::IsImageType(uniform.type) && uniform.imageUnitFormat == GL_R32F) { ++r32fImageCount; } if (uniform.active && gl::IsAtomicCounterType(uniform.type)) { ++atomicCounterCount; } } // Remove declarations of inactive shader interface variables so SPIR-V transformer doesn't need // to replace them. Note that currently, CollectVariables marks every field of an active // uniform that's of struct type as active, i.e. no extracted sampler is inactive, so this can // be done before extracting samplers from structs. if (!RemoveInactiveInterfaceVariables(this, root, &getSymbolTable(), getAttributes(), getInputVaryings(), getOutputVariables(), getUniforms(), getInterfaceBlocks(), true)) { return false; } // If there are any function calls that take array-of-array of opaque uniform parameters, or // other opaque uniforms that need special handling in Vulkan, such as atomic counters, // monomorphize the functions by removing said parameters and replacing them in the function // body with the call arguments. // // This has a few benefits: // // - It dramatically simplifies future transformations w.r.t to samplers in structs, array of // arrays of opaque types, atomic counters etc. // - Avoids the need for shader*ArrayDynamicIndexing Vulkan features. UnsupportedFunctionArgsBitSet args{UnsupportedFunctionArgs::StructContainingSamplers, UnsupportedFunctionArgs::ArrayOfArrayOfSamplerOrImage, UnsupportedFunctionArgs::AtomicCounter, UnsupportedFunctionArgs::SamplerCubeEmulation, UnsupportedFunctionArgs::Image}; if (!MonomorphizeUnsupportedFunctions(this, root, &getSymbolTable(), compileOptions, args)) { return false; } if (aggregateTypesUsedForUniforms > 0) { if (!SeparateStructFromUniformDeclarations(this, root, &getSymbolTable())) { return false; } int removedUniformsCount; if (!RewriteStructSamplers(this, root, &getSymbolTable(), &removedUniformsCount)) { return false; } defaultUniformCount -= removedUniformsCount; } // Replace array of array of opaque uniforms with a flattened array. This is run after // MonomorphizeUnsupportedFunctions and RewriteStructSamplers so that it's not possible for an // array of array of opaque type to be partially subscripted and passed to a function. if (!RewriteArrayOfArrayOfOpaqueUniforms(this, root, &getSymbolTable())) { return false; } // Rewrite samplerCubes as sampler2DArrays. This must be done after rewriting struct samplers // as it doesn't expect that. if (compileOptions.emulateSeamfulCubeMapSampling) { if (!RewriteCubeMapSamplersAs2DArray(this, root, &getSymbolTable(), getShaderType() == GL_FRAGMENT_SHADER)) { return false; } } if (!FlagSamplersForTexelFetch(this, root, &getSymbolTable(), &mUniforms)) { return false; } gl::ShaderType packedShaderType = gl::FromGLenum(getShaderType()); if (defaultUniformCount > 0) { if (!DeclareDefaultUniforms(this, root, &getSymbolTable(), packedShaderType)) { return false; } } if (getShaderType() == GL_COMPUTE_SHADER) { driverUniforms->addComputeDriverUniformsToShader(root, &getSymbolTable()); } else { driverUniforms->addGraphicsDriverUniformsToShader(root, &getSymbolTable()); } assignSpirvId( driverUniforms->getDriverUniformsVariable()->getType().getInterfaceBlock()->uniqueId(), vk::spirv::kIdDriverUniformsBlock); if (r32fImageCount > 0) { if (!RewriteR32fImages(this, root, &getSymbolTable())) { return false; } } if (atomicCounterCount > 0) { // ANGLEUniforms.acbBufferOffsets const TIntermTyped *acbBufferOffsets = driverUniforms->getAcbBufferOffsets(); const TVariable *atomicCounters = nullptr; if (!RewriteAtomicCounters(this, root, &getSymbolTable(), acbBufferOffsets, &atomicCounters)) { return false; } assignSpirvId(atomicCounters->getType().getInterfaceBlock()->uniqueId(), vk::spirv::kIdAtomicCounterBlock); } else if (getShaderVersion() >= 310) { // Vulkan doesn't support Atomic Storage as a Storage Class, but we've seen // cases where builtins are using it even with no active atomic counters. // This pass simply removes those builtins in that scenario. if (!RemoveAtomicCounterBuiltins(this, root)) { return false; } } if (packedShaderType != gl::ShaderType::Compute) { if (!ReplaceGLDepthRangeWithDriverUniform(this, root, driverUniforms, &getSymbolTable())) { return false; } // Search for the gl_ClipDistance/gl_CullDistance usage, if its used, we need to do some // replacements. bool useClipDistance = false; bool useCullDistance = false; for (const ShaderVariable &outputVarying : mOutputVaryings) { if (outputVarying.name == "gl_ClipDistance") { useClipDistance = true; } else if (outputVarying.name == "gl_CullDistance") { useCullDistance = true; } } for (const ShaderVariable &inputVarying : mInputVaryings) { if (inputVarying.name == "gl_ClipDistance") { useClipDistance = true; } else if (inputVarying.name == "gl_CullDistance") { useCullDistance = true; } } if (useClipDistance && !ReplaceClipDistanceAssignments(this, root, &getSymbolTable(), getShaderType(), driverUniforms->getClipDistancesEnabled())) { return false; } if (useCullDistance && !ReplaceCullDistanceAssignments(this, root, &getSymbolTable(), getShaderType())) { return false; } } if (gl::ShaderTypeSupportsTransformFeedback(packedShaderType)) { if (compileOptions.addVulkanXfbExtensionSupportCode) { // Add support code for transform feedback extension. if (!AddXfbExtensionSupport(this, root, &getSymbolTable(), driverUniforms)) { return false; } } // Add support code for pre-rotation and depth correction in the vertex processing stages. if (!AddVertexTransformationSupport(this, compileOptions, root, &getSymbolTable(), specConst, driverUniforms)) { return false; } } switch (packedShaderType) { case gl::ShaderType::Fragment: { bool usesPointCoord = false; bool usesFragCoord = false; bool usesSampleMaskIn = false; bool useSamplePosition = false; // Search for the gl_PointCoord usage, if its used, we need to flip the y coordinate. for (const ShaderVariable &inputVarying : mInputVaryings) { if (!inputVarying.isBuiltIn()) { continue; } if (inputVarying.name == "gl_SampleMaskIn") { usesSampleMaskIn = true; continue; } if (inputVarying.name == "gl_SamplePosition") { useSamplePosition = true; continue; } if (inputVarying.name == "gl_PointCoord") { usesPointCoord = true; break; } if (inputVarying.name == "gl_FragCoord") { usesFragCoord = true; break; } } bool hasGLSampleMask = false; bool hasGLSecondaryFragData = false; for (const ShaderVariable &outputVar : mOutputVariables) { if (outputVar.name == "gl_SampleMask") { ASSERT(!hasGLSampleMask); hasGLSampleMask = true; continue; } if (outputVar.name == "gl_SecondaryFragDataEXT") { ASSERT(!hasGLSecondaryFragData); hasGLSecondaryFragData = true; continue; } } if (usesPointCoord) { TIntermTyped *flipNegXY = driverUniforms->getNegFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment); TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium); TIntermTyped *swapXY = specConst->getSwapXY(); if (swapXY == nullptr) { swapXY = driverUniforms->getSwapXY(); } if (!RotateAndFlipBuiltinVariable( this, root, GetMainSequence(root), swapXY, flipNegXY, &getSymbolTable(), BuiltInVariable::gl_PointCoord(), kFlippedPointCoordName, pivot)) { return false; } } if (useSamplePosition) { TIntermTyped *flipXY = driverUniforms->getFlipXY(&getSymbolTable(), DriverUniformFlip::Fragment); TIntermConstantUnion *pivot = CreateFloatNode(0.5f, EbpMedium); TIntermTyped *swapXY = specConst->getSwapXY(); if (swapXY == nullptr) { swapXY = driverUniforms->getSwapXY(); } const TVariable *samplePositionBuiltin = static_cast(getSymbolTable().findBuiltIn( ImmutableString("gl_SamplePosition"), getShaderVersion())); if (!RotateAndFlipBuiltinVariable(this, root, GetMainSequence(root), swapXY, flipXY, &getSymbolTable(), samplePositionBuiltin, kFlippedPointCoordName, pivot)) { return false; } } if (usesFragCoord) { if (!InsertFragCoordCorrection(this, compileOptions, root, GetMainSequence(root), &getSymbolTable(), specConst, driverUniforms)) { return false; } } // Emulate gl_FragColor and gl_FragData with normal output variables. if (!EmulateFragColorData(this, root, &getSymbolTable(), hasGLSecondaryFragData)) { return false; } // Emulate framebuffer fetch if used. if (HasFramebufferFetch(getExtensionBehavior(), compileOptions)) { if (!EmulateFramebufferFetch(this, root, &mUniforms)) { return false; } } // This should be operated after doing ReplaceLastFragData and ReplaceInOutVariables, // because they will create the input attachment variables. AddBlendMainCaller will // check the existing input attachment variables and if there is no existing input // attachment variable then create a new one. if (getAdvancedBlendEquations().any() && compileOptions.addAdvancedBlendEquationsEmulation && !EmulateAdvancedBlendEquations(this, root, &getSymbolTable(), driverUniforms, &mUniforms, getAdvancedBlendEquations())) { return false; } if (!RewriteDfdy(this, root, &getSymbolTable(), getShaderVersion(), specConst, driverUniforms)) { return false; } if (!RewriteInterpolateAtOffset(this, root, &getSymbolTable(), getShaderVersion(), specConst, driverUniforms)) { return false; } if (usesSampleMaskIn && !RewriteSampleMaskIn(this, root, &getSymbolTable())) { return false; } if (hasGLSampleMask) { TIntermTyped *numSamples = driverUniforms->getNumSamples(); if (!RewriteSampleMask(this, root, &getSymbolTable(), numSamples)) { return false; } } { const TVariable *numSamplesVar = static_cast(getSymbolTable().findBuiltIn( ImmutableString("gl_NumSamples"), getShaderVersion())); TIntermTyped *numSamples = driverUniforms->getNumSamples(); if (!ReplaceVariableWithTyped(this, root, numSamplesVar, numSamples)) { return false; } } if (IsExtensionEnabled(getExtensionBehavior(), TExtension::EXT_YUV_target)) { if (!EmulateYUVBuiltIns(this, root, &getSymbolTable())) { return false; } } if (!EmulateDithering(this, compileOptions, root, &getSymbolTable(), specConst, driverUniforms)) { return false; } break; } case gl::ShaderType::Vertex: { if (compileOptions.addVulkanXfbEmulationSupportCode) { // Add support code for transform feedback emulation. Only applies to vertex shader // as tessellation and geometry shader transform feedback capture require // VK_EXT_transform_feedback. if (!AddXfbEmulationSupport(this, root, &getSymbolTable(), driverUniforms)) { return false; } } break; } case gl::ShaderType::Geometry: break; case gl::ShaderType::TessControl: { if (!ReplaceGLBoundingBoxWithGlobal(this, root, &getSymbolTable(), getShaderVersion())) { return false; } break; } case gl::ShaderType::TessEvaluation: break; case gl::ShaderType::Compute: break; default: UNREACHABLE(); break; } specConst->declareSpecConsts(root); mValidateASTOptions.validateSpecConstReferences = true; // Gather specialization constant usage bits so that we can feedback to context. mSpecConstUsageBits = specConst->getSpecConstUsageBits(); if (!validateAST(root)) { return false; } // Make sure function call validation is not accidentally left off anywhere. ASSERT(mValidateASTOptions.validateFunctionCall); ASSERT(mValidateASTOptions.validateNoRawFunctionCalls); // Declare the implicitly defined gl_PerVertex I/O blocks if not already. This will help SPIR-V // generation treat them mostly like usual I/O blocks. const TVariable *inputPerVertex = nullptr; const TVariable *outputPerVertex = nullptr; if (!DeclarePerVertexBlocks(this, root, &getSymbolTable(), &inputPerVertex, &outputPerVertex)) { return false; } if (inputPerVertex) { assignSpirvId(inputPerVertex->getType().getInterfaceBlock()->uniqueId(), vk::spirv::kIdInputPerVertexBlock); } if (outputPerVertex) { assignSpirvId(outputPerVertex->getType().getInterfaceBlock()->uniqueId(), vk::spirv::kIdOutputPerVertexBlock); assignSpirvId(outputPerVertex->uniqueId(), vk::spirv::kIdOutputPerVertexVar); } // Now that all transformations are done, assign SPIR-V ids to whatever shader variable is still // present in the shader in some form. This should be the last thing done in this function. assignSpirvIds(root); return true; } bool TranslatorSPIRV::translate(TIntermBlock *root, const ShCompileOptions &compileOptions, PerformanceDiagnostics *perfDiagnostics) { mUniqueToSpirvIdMap.clear(); mFirstUnusedSpirvId = 0; SpecConst specConst(&getSymbolTable(), compileOptions, getShaderType()); DriverUniform driverUniforms(DriverUniformMode::InterfaceBlock); DriverUniformExtended driverUniformsExt(DriverUniformMode::InterfaceBlock); const bool useExtendedDriverUniforms = compileOptions.addVulkanXfbEmulationSupportCode; DriverUniform *uniforms = useExtendedDriverUniforms ? &driverUniformsExt : &driverUniforms; if (!translateImpl(root, compileOptions, perfDiagnostics, &specConst, uniforms)) { return false; } return OutputSPIRV(this, root, compileOptions, mUniqueToSpirvIdMap, mFirstUnusedSpirvId); } bool TranslatorSPIRV::shouldFlattenPragmaStdglInvariantAll() { // Not necessary. return false; } void TranslatorSPIRV::assignSpirvId(TSymbolUniqueId uniqueId, uint32_t spirvId) { ASSERT(mUniqueToSpirvIdMap.find(uniqueId.get()) == mUniqueToSpirvIdMap.end()); mUniqueToSpirvIdMap[uniqueId.get()] = spirvId; } void TranslatorSPIRV::assignSpirvIds(TIntermBlock *root) { // Match the declarations with collected variables and assign a new id to each, starting from // the first unreserved id. This makes sure that the reserved ids for internal variables and // ids for shader variables form a minimal contiguous range. The Vulkan backend takes advantage // of this fact for optimal hashing. mFirstUnusedSpirvId = vk::spirv::kIdFirstUnreserved; for (TIntermNode *node : *root->getSequence()) { TIntermDeclaration *decl = node->getAsDeclarationNode(); if (decl == nullptr) { continue; } TIntermSymbol *symbol = decl->getSequence()->front()->getAsSymbolNode(); if (symbol == nullptr) { continue; } const TType &type = symbol->getType(); const TQualifier qualifier = type.getQualifier(); // Skip internal symbols, which already have a reserved id. const TSymbolUniqueId uniqueId = type.isInterfaceBlock() ? type.getInterfaceBlock()->uniqueId() : symbol->uniqueId(); if (mUniqueToSpirvIdMap.find(uniqueId.get()) != mUniqueToSpirvIdMap.end()) { continue; } uint32_t *variableId = nullptr; std::vector *fields = nullptr; if (type.isInterfaceBlock()) { if (IsVaryingIn(qualifier)) { ShaderVariable *varying = FindIOBlockShaderVariable(&mInputVaryings, type.getInterfaceBlock()->name()); variableId = &varying->id; fields = &varying->fields; } else if (IsVaryingOut(qualifier)) { ShaderVariable *varying = FindIOBlockShaderVariable(&mOutputVaryings, type.getInterfaceBlock()->name()); variableId = &varying->id; fields = &varying->fields; } else if (IsStorageBuffer(qualifier)) { InterfaceBlock *block = FindShaderVariable(&mShaderStorageBlocks, type.getInterfaceBlock()->name()); variableId = &block->id; } else { InterfaceBlock *block = FindShaderVariable(&mUniformBlocks, type.getInterfaceBlock()->name()); variableId = &block->id; } } else if (qualifier == EvqUniform) { ShaderVariable *uniform = FindUniformShaderVariable(&mUniforms, symbol->getName()); variableId = &uniform->id; } else if (qualifier == EvqAttribute || qualifier == EvqVertexIn) { ShaderVariable *attribute = FindShaderVariable(&mAttributes, symbol->getName()); variableId = &attribute->id; } else if (IsShaderIn(qualifier)) { ShaderVariable *varying = FindShaderVariable(&mInputVaryings, symbol->getName()); variableId = &varying->id; fields = &varying->fields; } else if (qualifier == EvqFragmentOut) { // webgl_FragColor, webgl_FragData, webgl_SecondaryFragColor and webgl_SecondaryFragData // are recorded with their original names (starting with gl_) ImmutableString name(symbol->getName()); if (angle::BeginsWith(name.data(), "webgl_")) { name = ImmutableString(name.data() + 3, name.length() - 3); } ShaderVariable *output = FindShaderVariable(&mOutputVariables, name); variableId = &output->id; } else if (IsShaderOut(qualifier)) { ShaderVariable *varying = FindShaderVariable(&mOutputVaryings, symbol->getName()); variableId = &varying->id; fields = &varying->fields; } if (variableId == nullptr) { continue; } ASSERT(variableId != nullptr); assignSpirvId(uniqueId, mFirstUnusedSpirvId); *variableId = mFirstUnusedSpirvId; // Propagate the id to the first field of structs/blocks too. The front-end gathers // varyings as fields, and the transformer needs to infer the variable id (of struct type) // just by looking at the fields. if (fields != nullptr) { SetSpirvIdInFields(mFirstUnusedSpirvId, fields); } ++mFirstUnusedSpirvId; } } } // namespace sh