Edit

kc3-lang/angle/src/compiler/translator/OutputGLSLBase.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2021-08-25 10:23:48
    Hash : f9d261f5
    Message : Translator: Don't promote precision from initializer Take the following GLSL code: precision mediump float; uniform highp float u; float x = u; When `x` is declared, its precision is mediump due to `precision mediump float`. Its initializer (`u`) however is highp. Previously, ANGLE generated the following code to ensure the precision of the initializer is applied to the variable: highp float x = u; Compiling the same code with glslang and looking at the SPIR-V (both SPIR-V for OpenGL and SPIR-V for Vulkan), such a promotion is not done. The generated SPIR-V is equivalent to: mediump float x = u; This change aligns ANGLE's output with glslang's by outputting the precision of the variable itself instead of the precision of the assignment (which is highp due to promotion from the two operands). Bug: angleproject:4889 Bug: angleproject:6132 Change-Id: I6f338a865c0a7710329fc13bc171fd245dd30b31 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3118965 Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Tim Van Patten <timvp@google.com> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • src/compiler/translator/OutputGLSLBase.cpp
  • //
    // Copyright 2002 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/OutputGLSLBase.h"
    
    #include "angle_gl.h"
    #include "common/debug.h"
    #include "common/mathutil.h"
    #include "compiler/translator/Compiler.h"
    #include "compiler/translator/util.h"
    
    #include <cfloat>
    
    namespace sh
    {
    
    namespace
    {
    
    bool isSingleStatement(TIntermNode *node)
    {
        if (node->getAsFunctionDefinition())
        {
            return false;
        }
        else if (node->getAsBlock())
        {
            return false;
        }
        else if (node->getAsIfElseNode())
        {
            return false;
        }
        else if (node->getAsLoopNode())
        {
            return false;
        }
        else if (node->getAsSwitchNode())
        {
            return false;
        }
        else if (node->getAsCaseNode())
        {
            return false;
        }
        else if (node->getAsPreprocessorDirective())
        {
            return false;
        }
        return true;
    }
    
    class CommaSeparatedListItemPrefixGenerator
    {
      public:
        CommaSeparatedListItemPrefixGenerator() : mFirst(true) {}
    
      private:
        bool mFirst;
    
        template <typename Stream>
        friend Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen);
    };
    
    template <typename Stream>
    Stream &operator<<(Stream &out, CommaSeparatedListItemPrefixGenerator &gen)
    {
        if (gen.mFirst)
        {
            gen.mFirst = false;
        }
        else
        {
            out << ", ";
        }
        return out;
    }
    
    }  // namespace
    
    TOutputGLSLBase::TOutputGLSLBase(TCompiler *compiler,
                                     TInfoSinkBase &objSink,
                                     ShCompileOptions compileOptions)
        : TIntermTraverser(true, true, true, &compiler->getSymbolTable()),
          mObjSink(objSink),
          mDeclaringVariable(false),
          mHashFunction(compiler->getHashFunction()),
          mNameMap(compiler->getNameMap()),
          mShaderType(compiler->getShaderType()),
          mShaderVersion(compiler->getShaderVersion()),
          mOutput(compiler->getOutputType()),
          mHighPrecisionSupported(compiler->isHighPrecisionSupported()),
          mCompileOptions(compileOptions)
    {}
    
    void TOutputGLSLBase::writeInvariantQualifier(const TType &type)
    {
        if (!sh::RemoveInvariant(mShaderType, mShaderVersion, mOutput, mCompileOptions))
        {
            TInfoSinkBase &out = objSink();
            out << "invariant ";
        }
    }
    
    void TOutputGLSLBase::writePreciseQualifier(const TType &type)
    {
        TInfoSinkBase &out = objSink();
        out << "precise ";
    }
    
    void TOutputGLSLBase::writeFloat(TInfoSinkBase &out, float f)
    {
        if ((gl::isInf(f) || gl::isNaN(f)) && mShaderVersion >= 300)
        {
            out << "uintBitsToFloat(" << gl::bitCast<uint32_t>(f) << "u)";
        }
        else
        {
            out << std::min(FLT_MAX, std::max(-FLT_MAX, f));
        }
    }
    
    void TOutputGLSLBase::writeTriplet(Visit visit,
                                       const char *preStr,
                                       const char *inStr,
                                       const char *postStr)
    {
        TInfoSinkBase &out = objSink();
        if (visit == PreVisit && preStr)
            out << preStr;
        else if (visit == InVisit && inStr)
            out << inStr;
        else if (visit == PostVisit && postStr)
            out << postStr;
    }
    
    void TOutputGLSLBase::writeFunctionTriplet(Visit visit,
                                               const ImmutableString &functionName,
                                               bool useEmulatedFunction)
    {
        TInfoSinkBase &out = objSink();
        if (visit == PreVisit)
        {
            if (useEmulatedFunction)
            {
                BuiltInFunctionEmulator::WriteEmulatedFunctionName(out, functionName.data());
            }
            else
            {
                out << functionName;
            }
            out << "(";
        }
        else
        {
            writeTriplet(visit, nullptr, ", ", ")");
        }
    }
    
    // Outputs what goes inside layout(), except for location and binding qualifiers, as they are
    // handled differently between GL GLSL and Vulkan GLSL.
    std::string TOutputGLSLBase::getCommonLayoutQualifiers(TIntermSymbol *variable)
    {
        std::ostringstream out;
        CommaSeparatedListItemPrefixGenerator listItemPrefix;
    
        const TType &type                       = variable->getType();
        const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
    
        if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
            IsVarying(type.getQualifier()))
        {
            if (type.getQualifier() == EvqFragmentOut && layoutQualifier.index >= 0)
            {
                out << listItemPrefix << "index = " << layoutQualifier.index;
            }
        }
    
        if (type.getQualifier() == EvqFragmentOut)
        {
            if (layoutQualifier.yuv == true)
            {
                out << listItemPrefix << "yuv";
            }
        }
    
        if (IsImage(type.getBasicType()))
        {
            if (layoutQualifier.imageInternalFormat != EiifUnspecified)
            {
                ASSERT(type.getQualifier() == EvqTemporary || type.getQualifier() == EvqUniform);
                out << listItemPrefix
                    << getImageInternalFormatString(layoutQualifier.imageInternalFormat);
            }
        }
    
        if (IsAtomicCounter(type.getBasicType()))
        {
            out << listItemPrefix << "offset = " << layoutQualifier.offset;
        }
    
        return out.str();
    }
    
    // Outputs memory qualifiers applied to images, buffers and its fields, as well as image function
    // arguments.
    std::string TOutputGLSLBase::getMemoryQualifiers(const TType &type)
    {
        std::ostringstream out;
    
        const TMemoryQualifier &memoryQualifier = type.getMemoryQualifier();
        if (memoryQualifier.readonly)
        {
            out << "readonly ";
        }
    
        if (memoryQualifier.writeonly)
        {
            out << "writeonly ";
        }
    
        if (memoryQualifier.coherent)
        {
            out << "coherent ";
        }
    
        if (memoryQualifier.restrictQualifier)
        {
            out << "restrict ";
        }
    
        if (memoryQualifier.volatileQualifier)
        {
            out << "volatile ";
        }
    
        return out.str();
    }
    
    void TOutputGLSLBase::writeLayoutQualifier(TIntermSymbol *variable)
    {
        const TType &type = variable->getType();
    
        if (!NeedsToWriteLayoutQualifier(type))
        {
            return;
        }
    
        if (type.getBasicType() == EbtInterfaceBlock)
        {
            declareInterfaceBlockLayout(type);
            return;
        }
    
        TInfoSinkBase &out                      = objSink();
        const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
        out << "layout(";
    
        CommaSeparatedListItemPrefixGenerator listItemPrefix;
    
        if (type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
            IsVarying(type.getQualifier()))
        {
            if (layoutQualifier.location >= 0)
            {
                out << listItemPrefix << "location = " << layoutQualifier.location;
            }
        }
    
        if (IsOpaqueType(type.getBasicType()))
        {
            if (layoutQualifier.binding >= 0)
            {
                out << listItemPrefix << "binding = " << layoutQualifier.binding;
            }
        }
    
        std::string otherQualifiers = getCommonLayoutQualifiers(variable);
        if (!otherQualifiers.empty())
        {
            out << listItemPrefix << otherQualifiers;
        }
    
        out << ") ";
    }
    
    void TOutputGLSLBase::writeFieldLayoutQualifier(const TField *field)
    {
        if (!field->type()->isMatrix() && !field->type()->isStructureContainingMatrices())
        {
            return;
        }
    
        TInfoSinkBase &out = objSink();
    
        out << "layout(";
        switch (field->type()->getLayoutQualifier().matrixPacking)
        {
            case EmpUnspecified:
            case EmpColumnMajor:
                // Default matrix packing is column major.
                out << "column_major";
                break;
    
            case EmpRowMajor:
                out << "row_major";
                break;
    
            default:
                UNREACHABLE();
                break;
        }
        out << ") ";
    }
    
    void TOutputGLSLBase::writeQualifier(TQualifier qualifier, const TType &type, const TSymbol *symbol)
    {
        const char *result = mapQualifierToString(qualifier);
        if (result && result[0] != '\0')
        {
            objSink() << result << " ";
        }
    
        objSink() << getMemoryQualifiers(type);
    }
    
    const char *TOutputGLSLBase::mapQualifierToString(TQualifier qualifier)
    {
        if (sh::IsGLSL410OrOlder(mOutput) && mShaderVersion >= 300 &&
            (mCompileOptions & SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3) != 0)
        {
            switch (qualifier)
            {
                // The return string is consistent with sh::getQualifierString() from
                // BaseTypes.h minus the "centroid" keyword.
                case EvqCentroid:
                    return "";
                case EvqCentroidIn:
                    return "smooth in";
                case EvqCentroidOut:
                    return "smooth out";
                default:
                    break;
            }
        }
        if (sh::IsGLSL130OrNewer(mOutput))
        {
            switch (qualifier)
            {
                case EvqAttribute:
                    return "in";
                case EvqVaryingIn:
                    return "in";
                case EvqVaryingOut:
                    return "out";
                default:
                    break;
            }
        }
    
        // Handle qualifiers that produce different output based on shader type.
        switch (qualifier)
        {
            case EvqClipDistance:
            case EvqCullDistance:
                return mShaderType == GL_FRAGMENT_SHADER ? "in" : "out";
            default:
                break;
        }
    
        return sh::getQualifierString(qualifier);
    }
    
    void TOutputGLSLBase::writeVariableType(const TType &type,
                                            const TSymbol *symbol,
                                            bool isFunctionArgument)
    {
        TQualifier qualifier = type.getQualifier();
        TInfoSinkBase &out   = objSink();
        if (type.isInvariant())
        {
            writeInvariantQualifier(type);
        }
        if (type.isPrecise())
        {
            writePreciseQualifier(type);
        }
        if (qualifier != EvqTemporary && qualifier != EvqGlobal)
        {
            writeQualifier(qualifier, type, symbol);
        }
        if (isFunctionArgument)
        {
            // Function arguments are the only place (other than image/SSBO/field declaration) where
            // memory qualifiers can appear.
            out << getMemoryQualifiers(type);
        }
    
        // Declare the struct.
        if (type.isStructSpecifier())
        {
            const TStructure *structure = type.getStruct();
    
            declareStruct(structure);
        }
        else if (type.getBasicType() == EbtInterfaceBlock)
        {
            declareInterfaceBlock(type);
        }
        else
        {
            if (writeVariablePrecision(type.getPrecision()))
                out << " ";
            out << getTypeName(type);
        }
    }
    
    void TOutputGLSLBase::writeFunctionParameters(const TFunction *func)
    {
        TInfoSinkBase &out = objSink();
        size_t paramCount  = func->getParamCount();
        for (size_t i = 0; i < paramCount; ++i)
        {
            const TVariable *param = func->getParam(i);
            const TType &type      = param->getType();
            writeVariableType(type, param, true);
    
            if (param->symbolType() != SymbolType::Empty)
            {
                out << " " << hashName(param);
            }
            if (type.isArray())
            {
                out << ArrayString(type);
            }
    
            // Put a comma if this is not the last argument.
            if (i != paramCount - 1)
                out << ", ";
        }
    }
    
    const TConstantUnion *TOutputGLSLBase::writeConstantUnion(const TType &type,
                                                              const TConstantUnion *pConstUnion)
    {
        TInfoSinkBase &out = objSink();
    
        if (type.getBasicType() == EbtStruct)
        {
            const TStructure *structure = type.getStruct();
            out << hashName(structure) << "(";
    
            const TFieldList &fields = structure->fields();
            for (size_t i = 0; i < fields.size(); ++i)
            {
                const TType *fieldType = fields[i]->type();
                ASSERT(fieldType != nullptr);
                pConstUnion = writeConstantUnion(*fieldType, pConstUnion);
                if (i != fields.size() - 1)
                    out << ", ";
            }
            out << ")";
        }
        else
        {
            size_t size    = type.getObjectSize();
            bool writeType = size > 1;
            if (writeType)
                out << getTypeName(type) << "(";
            for (size_t i = 0; i < size; ++i, ++pConstUnion)
            {
                switch (pConstUnion->getType())
                {
                    case EbtFloat:
                        writeFloat(out, pConstUnion->getFConst());
                        break;
                    case EbtInt:
                        out << pConstUnion->getIConst();
                        break;
                    case EbtUInt:
                        out << pConstUnion->getUConst() << "u";
                        break;
                    case EbtBool:
                        out << pConstUnion->getBConst();
                        break;
                    case EbtYuvCscStandardEXT:
                        out << getYuvCscStandardEXTString(pConstUnion->getYuvCscStandardEXTConst());
                        break;
                    default:
                        UNREACHABLE();
                }
                if (i != size - 1)
                    out << ", ";
            }
            if (writeType)
                out << ")";
        }
        return pConstUnion;
    }
    
    void TOutputGLSLBase::writeConstructorTriplet(Visit visit, const TType &type)
    {
        TInfoSinkBase &out = objSink();
        if (visit == PreVisit)
        {
            if (type.isArray())
            {
                out << getTypeName(type);
                out << ArrayString(type);
                out << "(";
            }
            else
            {
                out << getTypeName(type) << "(";
            }
        }
        else
        {
            writeTriplet(visit, nullptr, ", ", ")");
        }
    }
    
    void TOutputGLSLBase::visitSymbol(TIntermSymbol *node)
    {
        TInfoSinkBase &out = objSink();
        out << hashName(&node->variable());
    
        if (mDeclaringVariable && node->getType().isArray())
            out << ArrayString(node->getType());
    }
    
    void TOutputGLSLBase::visitConstantUnion(TIntermConstantUnion *node)
    {
        writeConstantUnion(node->getType(), node->getConstantValue());
    }
    
    bool TOutputGLSLBase::visitSwizzle(Visit visit, TIntermSwizzle *node)
    {
        TInfoSinkBase &out = objSink();
        if (visit == PostVisit)
        {
            out << ".";
            node->writeOffsetsAsXYZW(&out);
        }
        return true;
    }
    
    bool TOutputGLSLBase::visitBinary(Visit visit, TIntermBinary *node)
    {
        bool visitChildren = true;
        TInfoSinkBase &out = objSink();
        switch (node->getOp())
        {
            case EOpComma:
                writeTriplet(visit, "(", ", ", ")");
                break;
            case EOpInitialize:
                if (visit == InVisit)
                {
                    out << " = ";
                    // RHS of initialize is not being declared.
                    mDeclaringVariable = false;
                }
                break;
            case EOpAssign:
                writeTriplet(visit, "(", " = ", ")");
                break;
            case EOpAddAssign:
                writeTriplet(visit, "(", " += ", ")");
                break;
            case EOpSubAssign:
                writeTriplet(visit, "(", " -= ", ")");
                break;
            case EOpDivAssign:
                writeTriplet(visit, "(", " /= ", ")");
                break;
            case EOpIModAssign:
                writeTriplet(visit, "(", " %= ", ")");
                break;
            // Notice the fall-through.
            case EOpMulAssign:
            case EOpVectorTimesMatrixAssign:
            case EOpVectorTimesScalarAssign:
            case EOpMatrixTimesScalarAssign:
            case EOpMatrixTimesMatrixAssign:
                writeTriplet(visit, "(", " *= ", ")");
                break;
            case EOpBitShiftLeftAssign:
                writeTriplet(visit, "(", " <<= ", ")");
                break;
            case EOpBitShiftRightAssign:
                writeTriplet(visit, "(", " >>= ", ")");
                break;
            case EOpBitwiseAndAssign:
                writeTriplet(visit, "(", " &= ", ")");
                break;
            case EOpBitwiseXorAssign:
                writeTriplet(visit, "(", " ^= ", ")");
                break;
            case EOpBitwiseOrAssign:
                writeTriplet(visit, "(", " |= ", ")");
                break;
    
            case EOpIndexDirect:
            case EOpIndexIndirect:
                writeTriplet(visit, nullptr, "[", "]");
                break;
            case EOpIndexDirectStruct:
                if (visit == InVisit)
                {
                    // Here we are writing out "foo.bar", where "foo" is struct
                    // and "bar" is field. In AST, it is represented as a binary
                    // node, where left child represents "foo" and right child "bar".
                    // The node itself represents ".". The struct field "bar" is
                    // actually stored as an index into TStructure::fields.
                    out << ".";
                    const TStructure *structure       = node->getLeft()->getType().getStruct();
                    const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
                    const TField *field               = structure->fields()[index->getIConst(0)];
    
                    out << hashFieldName(field);
                    visitChildren = false;
                }
                break;
            case EOpIndexDirectInterfaceBlock:
                if (visit == InVisit)
                {
                    out << ".";
                    const TInterfaceBlock *interfaceBlock =
                        node->getLeft()->getType().getInterfaceBlock();
                    const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
                    const TField *field               = interfaceBlock->fields()[index->getIConst(0)];
                    out << hashFieldName(field);
                    visitChildren = false;
                }
                break;
    
            case EOpAdd:
                writeTriplet(visit, "(", " + ", ")");
                break;
            case EOpSub:
                writeTriplet(visit, "(", " - ", ")");
                break;
            case EOpMul:
                writeTriplet(visit, "(", " * ", ")");
                break;
            case EOpDiv:
                writeTriplet(visit, "(", " / ", ")");
                break;
            case EOpIMod:
                writeTriplet(visit, "(", " % ", ")");
                break;
            case EOpBitShiftLeft:
                writeTriplet(visit, "(", " << ", ")");
                break;
            case EOpBitShiftRight:
                writeTriplet(visit, "(", " >> ", ")");
                break;
            case EOpBitwiseAnd:
                writeTriplet(visit, "(", " & ", ")");
                break;
            case EOpBitwiseXor:
                writeTriplet(visit, "(", " ^ ", ")");
                break;
            case EOpBitwiseOr:
                writeTriplet(visit, "(", " | ", ")");
                break;
    
            case EOpEqual:
                writeTriplet(visit, "(", " == ", ")");
                break;
            case EOpNotEqual:
                writeTriplet(visit, "(", " != ", ")");
                break;
            case EOpLessThan:
                writeTriplet(visit, "(", " < ", ")");
                break;
            case EOpGreaterThan:
                writeTriplet(visit, "(", " > ", ")");
                break;
            case EOpLessThanEqual:
                writeTriplet(visit, "(", " <= ", ")");
                break;
            case EOpGreaterThanEqual:
                writeTriplet(visit, "(", " >= ", ")");
                break;
    
            // Notice the fall-through.
            case EOpVectorTimesScalar:
            case EOpVectorTimesMatrix:
            case EOpMatrixTimesVector:
            case EOpMatrixTimesScalar:
            case EOpMatrixTimesMatrix:
                writeTriplet(visit, "(", " * ", ")");
                break;
    
            case EOpLogicalOr:
                writeTriplet(visit, "(", " || ", ")");
                break;
            case EOpLogicalXor:
                writeTriplet(visit, "(", " ^^ ", ")");
                break;
            case EOpLogicalAnd:
                writeTriplet(visit, "(", " && ", ")");
                break;
            default:
                UNREACHABLE();
        }
    
        return visitChildren;
    }
    
    bool TOutputGLSLBase::visitUnary(Visit visit, TIntermUnary *node)
    {
        const char *preString  = "";
        const char *postString = ")";
    
        switch (node->getOp())
        {
            case EOpNegative:
                preString = "(-";
                break;
            case EOpPositive:
                preString = "(+";
                break;
            case EOpLogicalNot:
                preString = "(!";
                break;
            case EOpBitwiseNot:
                preString = "(~";
                break;
    
            case EOpPostIncrement:
                preString  = "(";
                postString = "++)";
                break;
            case EOpPostDecrement:
                preString  = "(";
                postString = "--)";
                break;
            case EOpPreIncrement:
                preString = "(++";
                break;
            case EOpPreDecrement:
                preString = "(--";
                break;
            case EOpArrayLength:
                preString  = "((";
                postString = ").length())";
                break;
    
            default:
                writeFunctionTriplet(visit, node->getFunction()->name(),
                                     node->getUseEmulatedFunction());
                return true;
        }
    
        writeTriplet(visit, preString, nullptr, postString);
    
        return true;
    }
    
    bool TOutputGLSLBase::visitTernary(Visit visit, TIntermTernary *node)
    {
        TInfoSinkBase &out = objSink();
        // Notice two brackets at the beginning and end. The outer ones
        // encapsulate the whole ternary expression. This preserves the
        // order of precedence when ternary expressions are used in a
        // compound expression, i.e., c = 2 * (a < b ? 1 : 2).
        out << "((";
        node->getCondition()->traverse(this);
        out << ") ? (";
        node->getTrueExpression()->traverse(this);
        out << ") : (";
        node->getFalseExpression()->traverse(this);
        out << "))";
        return false;
    }
    
    bool TOutputGLSLBase::visitIfElse(Visit visit, TIntermIfElse *node)
    {
        TInfoSinkBase &out = objSink();
    
        out << "if (";
        node->getCondition()->traverse(this);
        out << ")\n";
    
        visitCodeBlock(node->getTrueBlock());
    
        if (node->getFalseBlock())
        {
            out << "else\n";
            visitCodeBlock(node->getFalseBlock());
        }
        return false;
    }
    
    bool TOutputGLSLBase::visitSwitch(Visit visit, TIntermSwitch *node)
    {
        ASSERT(node->getStatementList());
        writeTriplet(visit, "switch (", ") ", nullptr);
        // The curly braces get written when visiting the statementList aggregate
        return true;
    }
    
    bool TOutputGLSLBase::visitCase(Visit visit, TIntermCase *node)
    {
        if (node->hasCondition())
        {
            writeTriplet(visit, "case (", nullptr, "):\n");
            return true;
        }
        else
        {
            TInfoSinkBase &out = objSink();
            out << "default:\n";
            return false;
        }
    }
    
    bool TOutputGLSLBase::visitBlock(Visit visit, TIntermBlock *node)
    {
        TInfoSinkBase &out = objSink();
        // Scope the blocks except when at the global scope.
        if (getCurrentTraversalDepth() > 0)
        {
            out << "{\n";
        }
    
        for (TIntermSequence::const_iterator iter = node->getSequence()->begin();
             iter != node->getSequence()->end(); ++iter)
        {
            TIntermNode *curNode = *iter;
            ASSERT(curNode != nullptr);
            curNode->traverse(this);
    
            if (isSingleStatement(curNode))
                out << ";\n";
        }
    
        // Scope the blocks except when at the global scope.
        if (getCurrentTraversalDepth() > 0)
        {
            out << "}\n";
        }
        return false;
    }
    
    bool TOutputGLSLBase::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
    {
        TIntermFunctionPrototype *prototype = node->getFunctionPrototype();
        prototype->traverse(this);
        visitCodeBlock(node->getBody());
    
        // Fully processed; no need to visit children.
        return false;
    }
    
    bool TOutputGLSLBase::visitGlobalQualifierDeclaration(Visit visit,
                                                          TIntermGlobalQualifierDeclaration *node)
    {
        TInfoSinkBase &out = objSink();
        ASSERT(visit == PreVisit);
        const TIntermSymbol *symbol = node->getSymbol();
        out << (node->isPrecise() ? "precise " : "invariant ") << hashName(&symbol->variable());
        return false;
    }
    
    void TOutputGLSLBase::visitFunctionPrototype(TIntermFunctionPrototype *node)
    {
        TInfoSinkBase &out = objSink();
    
        const TType &type = node->getType();
        writeVariableType(type, node->getFunction(), false);
        if (type.isArray())
            out << ArrayString(type);
    
        out << " " << hashFunctionNameIfNeeded(node->getFunction());
    
        out << "(";
        writeFunctionParameters(node->getFunction());
        out << ")";
    }
    
    bool TOutputGLSLBase::visitAggregate(Visit visit, TIntermAggregate *node)
    {
        bool visitChildren = true;
        if (node->getOp() == EOpConstruct)
        {
            writeConstructorTriplet(visit, node->getType());
        }
        else
        {
            // Function call.
            ImmutableString functionName = node->getFunction()->name();
            if (visit == PreVisit)
            {
                // No raw function is expected.
                ASSERT(node->getOp() != EOpCallInternalRawFunction);
    
                if (node->getOp() == EOpCallFunctionInAST)
                {
                    functionName = hashFunctionNameIfNeeded(node->getFunction());
                }
                else
                {
                    functionName =
                        translateTextureFunction(node->getFunction()->name(), mCompileOptions);
                }
            }
            writeFunctionTriplet(visit, functionName, node->getUseEmulatedFunction());
        }
        return visitChildren;
    }
    
    bool TOutputGLSLBase::visitDeclaration(Visit visit, TIntermDeclaration *node)
    {
        TInfoSinkBase &out = objSink();
    
        // Variable declaration.
        if (visit == PreVisit)
        {
            const TIntermSequence &sequence = *(node->getSequence());
            TIntermTyped *decl              = sequence.front()->getAsTyped();
            TIntermSymbol *symbolNode       = decl->getAsSymbolNode();
            if (symbolNode == nullptr)
            {
                ASSERT(decl->getAsBinaryNode() && decl->getAsBinaryNode()->getOp() == EOpInitialize);
                symbolNode = decl->getAsBinaryNode()->getLeft()->getAsSymbolNode();
            }
            ASSERT(symbolNode);
    
            if (symbolNode->getName() != "gl_ClipDistance" &&
                symbolNode->getName() != "gl_CullDistance")
            {
                // gl_Clip/CullDistance re-declaration doesn't need layout.
                writeLayoutQualifier(symbolNode);
            }
    
            writeVariableType(symbolNode->getType(), &symbolNode->variable(), false);
            if (symbolNode->variable().symbolType() != SymbolType::Empty)
            {
                out << " ";
            }
            mDeclaringVariable = true;
        }
        else if (visit == InVisit)
        {
            UNREACHABLE();
        }
        else
        {
            mDeclaringVariable = false;
        }
        return true;
    }
    
    bool TOutputGLSLBase::visitLoop(Visit visit, TIntermLoop *node)
    {
        TInfoSinkBase &out = objSink();
    
        TLoopType loopType = node->getType();
    
        if (loopType == ELoopFor)  // for loop
        {
            out << "for (";
            if (node->getInit())
                node->getInit()->traverse(this);
            out << "; ";
    
            if (node->getCondition())
                node->getCondition()->traverse(this);
            out << "; ";
    
            if (node->getExpression())
                node->getExpression()->traverse(this);
            out << ")\n";
    
            visitCodeBlock(node->getBody());
        }
        else if (loopType == ELoopWhile)  // while loop
        {
            out << "while (";
            ASSERT(node->getCondition() != nullptr);
            node->getCondition()->traverse(this);
            out << ")\n";
    
            visitCodeBlock(node->getBody());
        }
        else  // do-while loop
        {
            ASSERT(loopType == ELoopDoWhile);
            out << "do\n";
    
            visitCodeBlock(node->getBody());
    
            out << "while (";
            ASSERT(node->getCondition() != nullptr);
            node->getCondition()->traverse(this);
            out << ");\n";
        }
    
        // No need to visit children. They have been already processed in
        // this function.
        return false;
    }
    
    bool TOutputGLSLBase::visitBranch(Visit visit, TIntermBranch *node)
    {
        switch (node->getFlowOp())
        {
            case EOpKill:
                writeTriplet(visit, "discard", nullptr, nullptr);
                break;
            case EOpBreak:
                writeTriplet(visit, "break", nullptr, nullptr);
                break;
            case EOpContinue:
                writeTriplet(visit, "continue", nullptr, nullptr);
                break;
            case EOpReturn:
                writeTriplet(visit, "return ", nullptr, nullptr);
                break;
            default:
                UNREACHABLE();
        }
    
        return true;
    }
    
    void TOutputGLSLBase::visitCodeBlock(TIntermBlock *node)
    {
        TInfoSinkBase &out = objSink();
        if (node != nullptr)
        {
            node->traverse(this);
            // Single statements not part of a sequence need to be terminated
            // with semi-colon.
            if (isSingleStatement(node))
                out << ";\n";
        }
        else
        {
            out << "{\n}\n";  // Empty code block.
        }
    }
    
    void TOutputGLSLBase::visitPreprocessorDirective(TIntermPreprocessorDirective *node)
    {
        TInfoSinkBase &out = objSink();
    
        out << "\n";
    
        switch (node->getDirective())
        {
            case PreprocessorDirective::Define:
                out << "#define";
                break;
            case PreprocessorDirective::Endif:
                out << "#endif";
                break;
            case PreprocessorDirective::If:
                out << "#if";
                break;
            case PreprocessorDirective::Ifdef:
                out << "#ifdef";
                break;
    
            default:
                UNREACHABLE();
                break;
        }
    
        if (!node->getCommand().empty())
        {
            out << " " << node->getCommand();
        }
    
        out << "\n";
    }
    
    ImmutableString TOutputGLSLBase::getTypeName(const TType &type)
    {
        if (type.getBasicType() == EbtSamplerVideoWEBGL)
        {
            // TODO(http://anglebug.com/3889): translate SamplerVideoWEBGL into different token
            // when necessary (e.g. on Android devices)
            return ImmutableString("sampler2D");
        }
    
        return GetTypeName(type, mHashFunction, &mNameMap);
    }
    
    ImmutableString TOutputGLSLBase::hashName(const TSymbol *symbol)
    {
        return HashName(symbol, mHashFunction, &mNameMap);
    }
    
    ImmutableString TOutputGLSLBase::hashFieldName(const TField *field)
    {
        ASSERT(field->symbolType() != SymbolType::Empty);
        if (field->symbolType() == SymbolType::UserDefined)
        {
            return HashName(field->name(), mHashFunction, &mNameMap);
        }
    
        return field->name();
    }
    
    ImmutableString TOutputGLSLBase::hashFunctionNameIfNeeded(const TFunction *func)
    {
        if (func->isMain())
        {
            return func->name();
        }
        else
        {
            return hashName(func);
        }
    }
    
    void TOutputGLSLBase::declareStruct(const TStructure *structure)
    {
        TInfoSinkBase &out = objSink();
    
        out << "struct ";
    
        if (structure->symbolType() != SymbolType::Empty)
        {
            out << hashName(structure) << " ";
        }
        out << "{\n";
        const TFieldList &fields = structure->fields();
        for (size_t i = 0; i < fields.size(); ++i)
        {
            const TField *field    = fields[i];
            const TType &fieldType = *field->type();
            if (writeVariablePrecision(fieldType.getPrecision()))
            {
                out << " ";
            }
            if (fieldType.isPrecise())
            {
                writePreciseQualifier(fieldType);
            }
            out << getTypeName(fieldType) << " " << hashFieldName(field);
            if (fieldType.isArray())
            {
                out << ArrayString(fieldType);
            }
            out << ";\n";
        }
        out << "}";
    }
    
    void TOutputGLSLBase::declareInterfaceBlockLayout(const TType &type)
    {
        // 4.4.5 Uniform and Shader Storage Block Layout Qualifiers in GLSL 4.5 spec.
        // Layout qualifiers can be used for uniform and shader storage blocks,
        // but not for non-block uniform declarations.
        if (IsShaderIoBlock(type.getQualifier()))
        {
            return;
        }
    
        const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
        TInfoSinkBase &out                    = objSink();
    
        out << "layout(";
    
        switch (interfaceBlock->blockStorage())
        {
            case EbsUnspecified:
            case EbsShared:
                // Default block storage is shared.
                out << "shared";
                break;
    
            case EbsPacked:
                out << "packed";
                break;
    
            case EbsStd140:
                out << "std140";
                break;
    
            case EbsStd430:
                out << "std430";
                break;
    
            default:
                UNREACHABLE();
                break;
        }
    
        if (interfaceBlock->blockBinding() >= 0)
        {
            out << ", ";
            out << "binding = " << interfaceBlock->blockBinding();
        }
    
        out << ") ";
    }
    
    const char *getVariableInterpolation(TQualifier qualifier)
    {
        switch (qualifier)
        {
            case EvqSmoothOut:
                return "smooth out ";
            case EvqFlatOut:
                return "flat out ";
            case EvqNoPerspectiveOut:
                return "noperspective out ";
            case EvqCentroidOut:
                return "centroid out ";
            case EvqSmoothIn:
                return "smooth in ";
            case EvqFlatIn:
                return "flat in ";
            case EvqNoPerspectiveIn:
                return "noperspective in ";
            case EvqCentroidIn:
                return "centroid in ";
            default:
                break;
        }
        return nullptr;
    }
    
    void TOutputGLSLBase::declareInterfaceBlock(const TType &type)
    {
        const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
        TInfoSinkBase &out                    = objSink();
    
        out << hashName(interfaceBlock) << "{\n";
        const TFieldList &fields = interfaceBlock->fields();
        for (const TField *field : fields)
        {
            if (!IsShaderIoBlock(type.getQualifier()) && type.getQualifier() != EvqPatchIn &&
                type.getQualifier() != EvqPatchOut)
            {
                writeFieldLayoutQualifier(field);
            }
    
            const TType &fieldType = *field->type();
    
            out << getMemoryQualifiers(fieldType);
            if (writeVariablePrecision(fieldType.getPrecision()))
                out << " ";
            if (fieldType.isInvariant())
            {
                writeInvariantQualifier(fieldType);
            }
            if (fieldType.isPrecise())
            {
                writePreciseQualifier(fieldType);
            }
    
            const char *qualifier = getVariableInterpolation(fieldType.getQualifier());
            if (qualifier != nullptr)
                out << qualifier;
    
            out << getTypeName(fieldType) << " " << hashFieldName(field);
    
            if (fieldType.isArray())
                out << ArrayString(fieldType);
            out << ";\n";
        }
        out << "}";
    }
    
    void WritePragma(TInfoSinkBase &out, ShCompileOptions compileOptions, const TPragma &pragma)
    {
        if ((compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL) == 0)
        {
            if (pragma.stdgl.invariantAll)
                out << "#pragma STDGL invariant(all)\n";
        }
    }
    
    void WriteGeometryShaderLayoutQualifiers(TInfoSinkBase &out,
                                             sh::TLayoutPrimitiveType inputPrimitive,
                                             int invocations,
                                             sh::TLayoutPrimitiveType outputPrimitive,
                                             int maxVertices)
    {
        // Omit 'invocations = 1'
        if (inputPrimitive != EptUndefined || invocations > 1)
        {
            out << "layout (";
    
            if (inputPrimitive != EptUndefined)
            {
                out << getGeometryShaderPrimitiveTypeString(inputPrimitive);
            }
    
            if (invocations > 1)
            {
                if (inputPrimitive != EptUndefined)
                {
                    out << ", ";
                }
                out << "invocations = " << invocations;
            }
            out << ") in;\n";
        }
    
        if (outputPrimitive != EptUndefined || maxVertices != -1)
        {
            out << "layout (";
    
            if (outputPrimitive != EptUndefined)
            {
                out << getGeometryShaderPrimitiveTypeString(outputPrimitive);
            }
    
            if (maxVertices != -1)
            {
                if (outputPrimitive != EptUndefined)
                {
                    out << ", ";
                }
                out << "max_vertices = " << maxVertices;
            }
            out << ") out;\n";
        }
    }
    
    void WriteTessControlShaderLayoutQualifiers(TInfoSinkBase &out, int inputVertices)
    {
        if (inputVertices != 0)
        {
            out << "layout (vertices = " << inputVertices << ") out;\n";
        }
    }
    
    void WriteTessEvaluationShaderLayoutQualifiers(TInfoSinkBase &out,
                                                   sh::TLayoutTessEvaluationType inputPrimitive,
                                                   sh::TLayoutTessEvaluationType inputVertexSpacing,
                                                   sh::TLayoutTessEvaluationType inputOrdering,
                                                   sh::TLayoutTessEvaluationType inputPoint)
    {
        if (inputPrimitive != EtetUndefined)
        {
            out << "layout (";
            out << getTessEvaluationShaderTypeString(inputPrimitive);
            if (inputVertexSpacing != EtetUndefined)
            {
                out << ", " << getTessEvaluationShaderTypeString(inputVertexSpacing);
            }
            if (inputOrdering != EtetUndefined)
            {
                out << ", " << getTessEvaluationShaderTypeString(inputOrdering);
            }
            if (inputPoint != EtetUndefined)
            {
                out << ", " << getTessEvaluationShaderTypeString(inputPoint);
            }
            out << ") in;\n";
        }
    }
    
    // If SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS is enabled, layout qualifiers are spilled whenever
    // variables with specified layout qualifiers are copied. Additional checks are needed against the
    // type and storage qualifier of the variable to verify that layout qualifiers have to be outputted.
    // TODO (mradev): Fix layout qualifier spilling in ScalarizeVecAndMatConstructorArgs and remove
    // NeedsToWriteLayoutQualifier.
    bool NeedsToWriteLayoutQualifier(const TType &type)
    {
        if (type.getBasicType() == EbtInterfaceBlock)
        {
            return true;
        }
    
        const TLayoutQualifier &layoutQualifier = type.getLayoutQualifier();
    
        if ((type.getQualifier() == EvqFragmentOut || type.getQualifier() == EvqVertexIn ||
             IsVarying(type.getQualifier())) &&
            layoutQualifier.location >= 0)
        {
            return true;
        }
    
        if (type.getQualifier() == EvqFragmentOut && layoutQualifier.yuv == true)
        {
            return true;
        }
    
        if (IsOpaqueType(type.getBasicType()) && layoutQualifier.binding != -1)
        {
            return true;
        }
    
        if (IsImage(type.getBasicType()) && layoutQualifier.imageInternalFormat != EiifUnspecified)
        {
            return true;
        }
        return false;
    }
    
    void EmitEarlyFragmentTestsGLSL(const TCompiler &compiler, TInfoSinkBase &sink)
    {
        if (compiler.isEarlyFragmentTestsSpecified() || compiler.isEarlyFragmentTestsOptimized())
        {
            sink << "layout (early_fragment_tests) in;\n";
        }
    }
    
    void EmitWorkGroupSizeGLSL(const TCompiler &compiler, TInfoSinkBase &sink)
    {
        if (compiler.isComputeShaderLocalSizeDeclared())
        {
            const sh::WorkGroupSize &localSize = compiler.getComputeShaderLocalSize();
            sink << "layout (local_size_x=" << localSize[0] << ", local_size_y=" << localSize[1]
                 << ", local_size_z=" << localSize[2] << ") in;\n";
        }
    }
    
    void EmitMultiviewGLSL(const TCompiler &compiler,
                           const ShCompileOptions &compileOptions,
                           const TExtension extension,
                           const TBehavior behavior,
                           TInfoSinkBase &sink)
    {
        ASSERT(behavior != EBhUndefined);
        if (behavior == EBhDisable)
            return;
    
        const bool isVertexShader = (compiler.getShaderType() == GL_VERTEX_SHADER);
        if ((compileOptions & SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW) != 0)
        {
            // Emit ARB_shader_viewport_layer_array/NV_viewport_array2 in a vertex shader if the
            // SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER option is set and the
            // OVR_multiview(2) extension is requested.
            if (isVertexShader && (compileOptions & SH_SELECT_VIEW_IN_NV_GLSL_VERTEX_SHADER) != 0)
            {
                sink << "#if defined(GL_ARB_shader_viewport_layer_array)\n"
                     << "#extension GL_ARB_shader_viewport_layer_array : require\n"
                     << "#elif defined(GL_NV_viewport_array2)\n"
                     << "#extension GL_NV_viewport_array2 : require\n"
                     << "#endif\n";
            }
        }
        else
        {
            sink << "#extension GL_OVR_multiview";
            if (extension == TExtension::OVR_multiview2)
            {
                sink << "2";
            }
            sink << " : " << GetBehaviorString(behavior) << "\n";
    
            const auto &numViews = compiler.getNumViews();
            if (isVertexShader && numViews != -1)
            {
                sink << "layout(num_views=" << numViews << ") in;\n";
            }
        }
    }
    
    }  // namespace sh