Edit

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

Branch :

  • Show log

    Commit

  • Author : Tobin Ehlis
    Date : 2019-11-15 14:40:31
    Hash : 240befe5
    Message : Add support for gl_HelperInvocation Added HelperInvocation to builtin_variables.json, regenerate the codegen portions of compiler, and plumb support for HelperInvocation through the rest of the compiler. Skipping some fails on Android and Swiftshader for this initial change and will debug/fix those issues in a follow-on. Bug: angleproject:4110 Change-Id: I781a2782ace84200bc615a2cc26b908a62e2aa26 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1922061 Commit-Queue: Tobin Ehlis <tobine@google.com> Reviewed-by: Jamie Madill <jmadill@chromium.org>

  • src/compiler/translator/OutputHLSL.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/OutputHLSL.h"
    
    #include <stdio.h>
    #include <algorithm>
    #include <cfloat>
    
    #include "common/angleutils.h"
    #include "common/debug.h"
    #include "common/utilities.h"
    #include "compiler/translator/AtomicCounterFunctionHLSL.h"
    #include "compiler/translator/BuiltInFunctionEmulator.h"
    #include "compiler/translator/BuiltInFunctionEmulatorHLSL.h"
    #include "compiler/translator/ImageFunctionHLSL.h"
    #include "compiler/translator/InfoSink.h"
    #include "compiler/translator/ResourcesHLSL.h"
    #include "compiler/translator/StructureHLSL.h"
    #include "compiler/translator/TextureFunctionHLSL.h"
    #include "compiler/translator/TranslatorHLSL.h"
    #include "compiler/translator/UtilsHLSL.h"
    #include "compiler/translator/blocklayout.h"
    #include "compiler/translator/tree_ops/RemoveSwitchFallThrough.h"
    #include "compiler/translator/tree_util/FindSymbolNode.h"
    #include "compiler/translator/tree_util/NodeSearch.h"
    #include "compiler/translator/util.h"
    
    namespace sh
    {
    
    namespace
    {
    
    constexpr const char kImage2DFunctionString[] = "// @@ IMAGE2D DECLARATION FUNCTION STRING @@";
    
    TString ArrayHelperFunctionName(const char *prefix, const TType &type)
    {
        TStringStream fnName = sh::InitializeStream<TStringStream>();
        fnName << prefix << "_";
        if (type.isArray())
        {
            for (unsigned int arraySize : *type.getArraySizes())
            {
                fnName << arraySize << "_";
            }
        }
        fnName << TypeString(type);
        return fnName.str();
    }
    
    bool IsDeclarationWrittenOut(TIntermDeclaration *node)
    {
        TIntermSequence *sequence = node->getSequence();
        TIntermTyped *variable    = (*sequence)[0]->getAsTyped();
        ASSERT(sequence->size() == 1);
        ASSERT(variable);
        return (variable->getQualifier() == EvqTemporary || variable->getQualifier() == EvqGlobal ||
                variable->getQualifier() == EvqConst || variable->getQualifier() == EvqShared);
    }
    
    bool IsInStd140UniformBlock(TIntermTyped *node)
    {
        TIntermBinary *binaryNode = node->getAsBinaryNode();
    
        if (binaryNode)
        {
            return IsInStd140UniformBlock(binaryNode->getLeft());
        }
    
        const TType &type = node->getType();
    
        if (type.getQualifier() == EvqUniform)
        {
            // determine if we are in the standard layout
            const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
            if (interfaceBlock)
            {
                return (interfaceBlock->blockStorage() == EbsStd140);
            }
        }
    
        return false;
    }
    
    const char *GetHLSLAtomicFunctionStringAndLeftParenthesis(TOperator op)
    {
        switch (op)
        {
            case EOpAtomicAdd:
                return "InterlockedAdd(";
            case EOpAtomicMin:
                return "InterlockedMin(";
            case EOpAtomicMax:
                return "InterlockedMax(";
            case EOpAtomicAnd:
                return "InterlockedAnd(";
            case EOpAtomicOr:
                return "InterlockedOr(";
            case EOpAtomicXor:
                return "InterlockedXor(";
            case EOpAtomicExchange:
                return "InterlockedExchange(";
            case EOpAtomicCompSwap:
                return "InterlockedCompareExchange(";
            default:
                UNREACHABLE();
                return "";
        }
    }
    
    bool IsAtomicFunctionForSharedVariableDirectAssign(const TIntermBinary &node)
    {
        TIntermAggregate *aggregateNode = node.getRight()->getAsAggregate();
        if (aggregateNode == nullptr)
        {
            return false;
        }
    
        if (node.getOp() == EOpAssign && IsAtomicFunction(aggregateNode->getOp()))
        {
            return !IsInShaderStorageBlock((*aggregateNode->getSequence())[0]->getAsTyped());
        }
    
        return false;
    }
    
    const char *kZeros       = "_ANGLE_ZEROS_";
    constexpr int kZeroCount = 256;
    std::string DefineZeroArray()
    {
        std::stringstream ss = sh::InitializeStream<std::stringstream>();
        // For 'static', if the declaration does not include an initializer, the value is set to zero.
        // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-variable-syntax
        ss << "static uint " << kZeros << "[" << kZeroCount << "];\n";
        return ss.str();
    }
    
    std::string GetZeroInitializer(size_t size)
    {
        std::stringstream ss = sh::InitializeStream<std::stringstream>();
        size_t quotient      = size / kZeroCount;
        size_t reminder      = size % kZeroCount;
    
        for (size_t i = 0; i < quotient; ++i)
        {
            if (i != 0)
            {
                ss << ", ";
            }
            ss << kZeros;
        }
    
        for (size_t i = 0; i < reminder; ++i)
        {
            if (quotient != 0 || i != 0)
            {
                ss << ", ";
            }
            ss << "0";
        }
    
        return ss.str();
    }
    
    }  // anonymous namespace
    
    TReferencedBlock::TReferencedBlock(const TInterfaceBlock *aBlock,
                                       const TVariable *aInstanceVariable)
        : block(aBlock), instanceVariable(aInstanceVariable)
    {}
    
    bool OutputHLSL::needStructMapping(TIntermTyped *node)
    {
        ASSERT(node->getBasicType() == EbtStruct);
        for (unsigned int n = 0u; getAncestorNode(n) != nullptr; ++n)
        {
            TIntermNode *ancestor               = getAncestorNode(n);
            const TIntermBinary *ancestorBinary = ancestor->getAsBinaryNode();
            if (ancestorBinary)
            {
                switch (ancestorBinary->getOp())
                {
                    case EOpIndexDirectStruct:
                    {
                        const TStructure *structure = ancestorBinary->getLeft()->getType().getStruct();
                        const TIntermConstantUnion *index =
                            ancestorBinary->getRight()->getAsConstantUnion();
                        const TField *field = structure->fields()[index->getIConst(0)];
                        if (field->type()->getStruct() == nullptr)
                        {
                            return false;
                        }
                        break;
                    }
                    case EOpIndexDirect:
                    case EOpIndexIndirect:
                        break;
                    default:
                        return true;
                }
            }
            else
            {
                const TIntermAggregate *ancestorAggregate = ancestor->getAsAggregate();
                if (ancestorAggregate)
                {
                    return true;
                }
                return false;
            }
        }
        return true;
    }
    
    void OutputHLSL::writeFloat(TInfoSinkBase &out, float f)
    {
        // This is known not to work for NaN on all drivers but make the best effort to output NaNs
        // regardless.
        if ((gl::isInf(f) || gl::isNaN(f)) && mShaderVersion >= 300 &&
            mOutputType == SH_HLSL_4_1_OUTPUT)
        {
            out << "asfloat(" << gl::bitCast<uint32_t>(f) << "u)";
        }
        else
        {
            out << std::min(FLT_MAX, std::max(-FLT_MAX, f));
        }
    }
    
    void OutputHLSL::writeSingleConstant(TInfoSinkBase &out, const TConstantUnion *const constUnion)
    {
        ASSERT(constUnion != nullptr);
        switch (constUnion->getType())
        {
            case EbtFloat:
                writeFloat(out, constUnion->getFConst());
                break;
            case EbtInt:
                out << constUnion->getIConst();
                break;
            case EbtUInt:
                out << constUnion->getUConst();
                break;
            case EbtBool:
                out << constUnion->getBConst();
                break;
            default:
                UNREACHABLE();
        }
    }
    
    const TConstantUnion *OutputHLSL::writeConstantUnionArray(TInfoSinkBase &out,
                                                              const TConstantUnion *const constUnion,
                                                              const size_t size)
    {
        const TConstantUnion *constUnionIterated = constUnion;
        for (size_t i = 0; i < size; i++, constUnionIterated++)
        {
            writeSingleConstant(out, constUnionIterated);
    
            if (i != size - 1)
            {
                out << ", ";
            }
        }
        return constUnionIterated;
    }
    
    OutputHLSL::OutputHLSL(sh::GLenum shaderType,
                           ShShaderSpec shaderSpec,
                           int shaderVersion,
                           const TExtensionBehavior &extensionBehavior,
                           const char *sourcePath,
                           ShShaderOutput outputType,
                           int numRenderTargets,
                           int maxDualSourceDrawBuffers,
                           const std::vector<ShaderVariable> &uniforms,
                           ShCompileOptions compileOptions,
                           sh::WorkGroupSize workGroupSize,
                           TSymbolTable *symbolTable,
                           PerformanceDiagnostics *perfDiagnostics,
                           const std::vector<InterfaceBlock> &shaderStorageBlocks)
        : TIntermTraverser(true, true, true, symbolTable),
          mShaderType(shaderType),
          mShaderSpec(shaderSpec),
          mShaderVersion(shaderVersion),
          mExtensionBehavior(extensionBehavior),
          mSourcePath(sourcePath),
          mOutputType(outputType),
          mCompileOptions(compileOptions),
          mInsideFunction(false),
          mInsideMain(false),
          mNumRenderTargets(numRenderTargets),
          mMaxDualSourceDrawBuffers(maxDualSourceDrawBuffers),
          mCurrentFunctionMetadata(nullptr),
          mWorkGroupSize(workGroupSize),
          mPerfDiagnostics(perfDiagnostics),
          mNeedStructMapping(false)
    {
        mUsesFragColor        = false;
        mUsesFragData         = false;
        mUsesDepthRange       = false;
        mUsesFragCoord        = false;
        mUsesPointCoord       = false;
        mUsesFrontFacing      = false;
        mUsesHelperInvocation = false;
        mUsesPointSize        = false;
        mUsesInstanceID       = false;
        mHasMultiviewExtensionEnabled =
            IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview) ||
            IsExtensionEnabled(mExtensionBehavior, TExtension::OVR_multiview2);
        mUsesViewID                  = false;
        mUsesVertexID                = false;
        mUsesFragDepth               = false;
        mUsesNumWorkGroups           = false;
        mUsesWorkGroupID             = false;
        mUsesLocalInvocationID       = false;
        mUsesGlobalInvocationID      = false;
        mUsesLocalInvocationIndex    = false;
        mUsesXor                     = false;
        mUsesDiscardRewriting        = false;
        mUsesNestedBreak             = false;
        mRequiresIEEEStrictCompiling = false;
        mUseZeroArray                = false;
        mUsesSecondaryColor          = false;
    
        mUniqueIndex = 0;
    
        mOutputLod0Function      = false;
        mInsideDiscontinuousLoop = false;
        mNestedLoopDepth         = 0;
    
        mExcessiveLoopIndex = nullptr;
    
        mStructureHLSL       = new StructureHLSL;
        mTextureFunctionHLSL = new TextureFunctionHLSL;
        mImageFunctionHLSL   = new ImageFunctionHLSL;
        mAtomicCounterFunctionHLSL =
            new AtomicCounterFunctionHLSL((compileOptions & SH_FORCE_ATOMIC_VALUE_RESOLUTION) != 0);
    
        unsigned int firstUniformRegister =
            ((compileOptions & SH_SKIP_D3D_CONSTANT_REGISTER_ZERO) != 0) ? 1u : 0u;
        mResourcesHLSL = new ResourcesHLSL(mStructureHLSL, outputType, uniforms, firstUniformRegister);
    
        if (mOutputType == SH_HLSL_3_0_OUTPUT)
        {
            // Fragment shaders need dx_DepthRange, dx_ViewCoords and dx_DepthFront.
            // Vertex shaders need a slightly different set: dx_DepthRange, dx_ViewCoords and
            // dx_ViewAdjust.
            // In both cases total 3 uniform registers need to be reserved.
            mResourcesHLSL->reserveUniformRegisters(3);
        }
    
        // Reserve registers for the default uniform block and driver constants
        mResourcesHLSL->reserveUniformBlockRegisters(2);
    
        mSSBOOutputHLSL =
            new ShaderStorageBlockOutputHLSL(this, symbolTable, mResourcesHLSL, shaderStorageBlocks);
    }
    
    OutputHLSL::~OutputHLSL()
    {
        SafeDelete(mSSBOOutputHLSL);
        SafeDelete(mStructureHLSL);
        SafeDelete(mResourcesHLSL);
        SafeDelete(mTextureFunctionHLSL);
        SafeDelete(mImageFunctionHLSL);
        SafeDelete(mAtomicCounterFunctionHLSL);
        for (auto &eqFunction : mStructEqualityFunctions)
        {
            SafeDelete(eqFunction);
        }
        for (auto &eqFunction : mArrayEqualityFunctions)
        {
            SafeDelete(eqFunction);
        }
    }
    
    void OutputHLSL::output(TIntermNode *treeRoot, TInfoSinkBase &objSink)
    {
        BuiltInFunctionEmulator builtInFunctionEmulator;
        InitBuiltInFunctionEmulatorForHLSL(&builtInFunctionEmulator);
        if ((mCompileOptions & SH_EMULATE_ISNAN_FLOAT_FUNCTION) != 0)
        {
            InitBuiltInIsnanFunctionEmulatorForHLSLWorkarounds(&builtInFunctionEmulator,
                                                               mShaderVersion);
        }
    
        builtInFunctionEmulator.markBuiltInFunctionsForEmulation(treeRoot);
    
        // Now that we are done changing the AST, do the analyses need for HLSL generation
        CallDAG::InitResult success = mCallDag.init(treeRoot, nullptr);
        ASSERT(success == CallDAG::INITDAG_SUCCESS);
        mASTMetadataList = CreateASTMetadataHLSL(treeRoot, mCallDag);
    
        const std::vector<MappedStruct> std140Structs = FlagStd140Structs(treeRoot);
        // TODO(oetuaho): The std140Structs could be filtered based on which ones actually get used in
        // the shader code. When we add shader storage blocks we might also consider an alternative
        // solution, since the struct mapping won't work very well for shader storage blocks.
    
        // Output the body and footer first to determine what has to go in the header
        mInfoSinkStack.push(&mBody);
        treeRoot->traverse(this);
        mInfoSinkStack.pop();
    
        mInfoSinkStack.push(&mFooter);
        mInfoSinkStack.pop();
    
        mInfoSinkStack.push(&mHeader);
        header(mHeader, std140Structs, &builtInFunctionEmulator);
        mInfoSinkStack.pop();
    
        objSink << mHeader.c_str();
        objSink << mBody.c_str();
        objSink << mFooter.c_str();
    
        builtInFunctionEmulator.cleanup();
    }
    
    const std::map<std::string, unsigned int> &OutputHLSL::getShaderStorageBlockRegisterMap() const
    {
        return mResourcesHLSL->getShaderStorageBlockRegisterMap();
    }
    
    const std::map<std::string, unsigned int> &OutputHLSL::getUniformBlockRegisterMap() const
    {
        return mResourcesHLSL->getUniformBlockRegisterMap();
    }
    
    const std::map<std::string, unsigned int> &OutputHLSL::getUniformRegisterMap() const
    {
        return mResourcesHLSL->getUniformRegisterMap();
    }
    
    unsigned int OutputHLSL::getReadonlyImage2DRegisterIndex() const
    {
        return mResourcesHLSL->getReadonlyImage2DRegisterIndex();
    }
    
    unsigned int OutputHLSL::getImage2DRegisterIndex() const
    {
        return mResourcesHLSL->getImage2DRegisterIndex();
    }
    
    const std::set<std::string> &OutputHLSL::getUsedImage2DFunctionNames() const
    {
        return mImageFunctionHLSL->getUsedImage2DFunctionNames();
    }
    
    TString OutputHLSL::structInitializerString(int indent,
                                                const TType &type,
                                                const TString &name) const
    {
        TString init;
    
        TString indentString;
        for (int spaces = 0; spaces < indent; spaces++)
        {
            indentString += "    ";
        }
    
        if (type.isArray())
        {
            init += indentString + "{\n";
            for (unsigned int arrayIndex = 0u; arrayIndex < type.getOutermostArraySize(); ++arrayIndex)
            {
                TStringStream indexedString = sh::InitializeStream<TStringStream>();
                indexedString << name << "[" << arrayIndex << "]";
                TType elementType = type;
                elementType.toArrayElementType();
                init += structInitializerString(indent + 1, elementType, indexedString.str());
                if (arrayIndex < type.getOutermostArraySize() - 1)
                {
                    init += ",";
                }
                init += "\n";
            }
            init += indentString + "}";
        }
        else if (type.getBasicType() == EbtStruct)
        {
            init += indentString + "{\n";
            const TStructure &structure = *type.getStruct();
            const TFieldList &fields    = structure.fields();
            for (unsigned int fieldIndex = 0; fieldIndex < fields.size(); fieldIndex++)
            {
                const TField &field      = *fields[fieldIndex];
                const TString &fieldName = name + "." + Decorate(field.name());
                const TType &fieldType   = *field.type();
    
                init += structInitializerString(indent + 1, fieldType, fieldName);
                if (fieldIndex < fields.size() - 1)
                {
                    init += ",";
                }
                init += "\n";
            }
            init += indentString + "}";
        }
        else
        {
            init += indentString + name;
        }
    
        return init;
    }
    
    TString OutputHLSL::generateStructMapping(const std::vector<MappedStruct> &std140Structs) const
    {
        TString mappedStructs;
    
        for (auto &mappedStruct : std140Structs)
        {
            const TInterfaceBlock *interfaceBlock =
                mappedStruct.blockDeclarator->getType().getInterfaceBlock();
            TQualifier qualifier = mappedStruct.blockDeclarator->getType().getQualifier();
            switch (qualifier)
            {
                case EvqUniform:
                    if (mReferencedUniformBlocks.count(interfaceBlock->uniqueId().get()) == 0)
                    {
                        continue;
                    }
                    break;
                case EvqBuffer:
                    continue;
                default:
                    UNREACHABLE();
                    return mappedStructs;
            }
    
            unsigned int instanceCount = 1u;
            bool isInstanceArray       = mappedStruct.blockDeclarator->isArray();
            if (isInstanceArray)
            {
                instanceCount = mappedStruct.blockDeclarator->getOutermostArraySize();
            }
    
            for (unsigned int instanceArrayIndex = 0; instanceArrayIndex < instanceCount;
                 ++instanceArrayIndex)
            {
                TString originalName;
                TString mappedName("map");
    
                if (mappedStruct.blockDeclarator->variable().symbolType() != SymbolType::Empty)
                {
                    const ImmutableString &instanceName =
                        mappedStruct.blockDeclarator->variable().name();
                    unsigned int instanceStringArrayIndex = GL_INVALID_INDEX;
                    if (isInstanceArray)
                        instanceStringArrayIndex = instanceArrayIndex;
                    TString instanceString = mResourcesHLSL->InterfaceBlockInstanceString(
                        instanceName, instanceStringArrayIndex);
                    originalName += instanceString;
                    mappedName += instanceString;
                    originalName += ".";
                    mappedName += "_";
                }
    
                TString fieldName = Decorate(mappedStruct.field->name());
                originalName += fieldName;
                mappedName += fieldName;
    
                TType *structType = mappedStruct.field->type();
                mappedStructs +=
                    "static " + Decorate(structType->getStruct()->name()) + " " + mappedName;
    
                if (structType->isArray())
                {
                    mappedStructs += ArrayString(*mappedStruct.field->type()).data();
                }
    
                mappedStructs += " =\n";
                mappedStructs += structInitializerString(0, *structType, originalName);
                mappedStructs += ";\n";
            }
        }
        return mappedStructs;
    }
    
    void OutputHLSL::writeReferencedAttributes(TInfoSinkBase &out) const
    {
        for (const auto &attribute : mReferencedAttributes)
        {
            const TType &type           = attribute.second->getType();
            const ImmutableString &name = attribute.second->name();
    
            out << "static " << TypeString(type) << " " << Decorate(name) << ArrayString(type) << " = "
                << zeroInitializer(type) << ";\n";
        }
    }
    
    void OutputHLSL::writeReferencedVaryings(TInfoSinkBase &out) const
    {
        for (const auto &varying : mReferencedVaryings)
        {
            const TType &type = varying.second->getType();
    
            // Program linking depends on this exact format
            out << "static " << InterpolationString(type.getQualifier()) << " " << TypeString(type)
                << " " << DecorateVariableIfNeeded(*varying.second) << ArrayString(type) << " = "
                << zeroInitializer(type) << ";\n";
        }
    }
    
    void OutputHLSL::header(TInfoSinkBase &out,
                            const std::vector<MappedStruct> &std140Structs,
                            const BuiltInFunctionEmulator *builtInFunctionEmulator) const
    {
        TString mappedStructs;
        if (mNeedStructMapping)
        {
            mappedStructs = generateStructMapping(std140Structs);
        }
    
        out << mStructureHLSL->structsHeader();
    
        mResourcesHLSL->uniformsHeader(out, mOutputType, mReferencedUniforms, mSymbolTable);
        out << mResourcesHLSL->uniformBlocksHeader(mReferencedUniformBlocks);
        mSSBOOutputHLSL->writeShaderStorageBlocksHeader(out);
    
        if (!mEqualityFunctions.empty())
        {
            out << "\n// Equality functions\n\n";
            for (const auto &eqFunction : mEqualityFunctions)
            {
                out << eqFunction->functionDefinition << "\n";
            }
        }
        if (!mArrayAssignmentFunctions.empty())
        {
            out << "\n// Assignment functions\n\n";
            for (const auto &assignmentFunction : mArrayAssignmentFunctions)
            {
                out << assignmentFunction.functionDefinition << "\n";
            }
        }
        if (!mArrayConstructIntoFunctions.empty())
        {
            out << "\n// Array constructor functions\n\n";
            for (const auto &constructIntoFunction : mArrayConstructIntoFunctions)
            {
                out << constructIntoFunction.functionDefinition << "\n";
            }
        }
    
        if (mUsesDiscardRewriting)
        {
            out << "#define ANGLE_USES_DISCARD_REWRITING\n";
        }
    
        if (mUsesNestedBreak)
        {
            out << "#define ANGLE_USES_NESTED_BREAK\n";
        }
    
        if (mRequiresIEEEStrictCompiling)
        {
            out << "#define ANGLE_REQUIRES_IEEE_STRICT_COMPILING\n";
        }
    
        out << "#ifdef ANGLE_ENABLE_LOOP_FLATTEN\n"
               "#define LOOP [loop]\n"
               "#define FLATTEN [flatten]\n"
               "#else\n"
               "#define LOOP\n"
               "#define FLATTEN\n"
               "#endif\n";
    
        // array stride for atomic counter buffers is always 4 per original extension
        // ARB_shader_atomic_counters and discussion on
        // https://github.com/KhronosGroup/OpenGL-API/issues/5
        out << "\n#define ATOMIC_COUNTER_ARRAY_STRIDE 4\n\n";
    
        if (mUseZeroArray)
        {
            out << DefineZeroArray() << "\n";
        }
    
        if (mShaderType == GL_FRAGMENT_SHADER)
        {
            const bool usingMRTExtension =
                IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers);
            const bool usingBFEExtension =
                IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_blend_func_extended);
    
            out << "// Varyings\n";
            writeReferencedVaryings(out);
            out << "\n";
    
            if ((IsDesktopGLSpec(mShaderSpec) && mShaderVersion >= 130) ||
                (!IsDesktopGLSpec(mShaderSpec) && mShaderVersion >= 300))
            {
                for (const auto &outputVariable : mReferencedOutputVariables)
                {
                    const ImmutableString &variableName = outputVariable.second->name();
                    const TType &variableType           = outputVariable.second->getType();
    
                    out << "static " << TypeString(variableType) << " out_" << variableName
                        << ArrayString(variableType) << " = " << zeroInitializer(variableType) << ";\n";
                }
            }
            else
            {
                const unsigned int numColorValues = usingMRTExtension ? mNumRenderTargets : 1;
    
                out << "static float4 gl_Color[" << numColorValues
                    << "] =\n"
                       "{\n";
                for (unsigned int i = 0; i < numColorValues; i++)
                {
                    out << "    float4(0, 0, 0, 0)";
                    if (i + 1 != numColorValues)
                    {
                        out << ",";
                    }
                    out << "\n";
                }
    
                out << "};\n";
    
                if (usingBFEExtension && mUsesSecondaryColor)
                {
                    out << "static float4 gl_SecondaryColor[" << mMaxDualSourceDrawBuffers
                        << "] = \n"
                           "{\n";
                    for (int i = 0; i < mMaxDualSourceDrawBuffers; i++)
                    {
                        out << "    float4(0, 0, 0, 0)";
                        if (i + 1 != mMaxDualSourceDrawBuffers)
                        {
                            out << ",";
                        }
                        out << "\n";
                    }
                    out << "};\n";
                }
            }
    
            if (mUsesFragDepth)
            {
                out << "static float gl_Depth = 0.0;\n";
            }
    
            if (mUsesFragCoord)
            {
                out << "static float4 gl_FragCoord = float4(0, 0, 0, 0);\n";
            }
    
            if (mUsesPointCoord)
            {
                out << "static float2 gl_PointCoord = float2(0.5, 0.5);\n";
            }
    
            if (mUsesFrontFacing)
            {
                out << "static bool gl_FrontFacing = false;\n";
            }
    
            if (mUsesHelperInvocation)
            {
                out << "static bool gl_HelperInvocation = false;\n";
            }
    
            out << "\n";
    
            if (mUsesDepthRange)
            {
                out << "struct gl_DepthRangeParameters\n"
                       "{\n"
                       "    float near;\n"
                       "    float far;\n"
                       "    float diff;\n"
                       "};\n"
                       "\n";
            }
    
            if (mOutputType == SH_HLSL_4_1_OUTPUT || mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
            {
                out << "cbuffer DriverConstants : register(b1)\n"
                       "{\n";
    
                if (mUsesDepthRange)
                {
                    out << "    float3 dx_DepthRange : packoffset(c0);\n";
                }
    
                if (mUsesFragCoord)
                {
                    out << "    float4 dx_ViewCoords : packoffset(c1);\n";
                }
    
                if (mUsesFragCoord || mUsesFrontFacing)
                {
                    out << "    float3 dx_DepthFront : packoffset(c2);\n";
                }
    
                if (mUsesFragCoord)
                {
                    // dx_ViewScale is only used in the fragment shader to correct
                    // the value for glFragCoord if necessary
                    out << "    float2 dx_ViewScale : packoffset(c3);\n";
                }
    
                if (mHasMultiviewExtensionEnabled)
                {
                    // We have to add a value which we can use to keep track of which multi-view code
                    // path is to be selected in the GS.
                    out << "    float multiviewSelectViewportIndex : packoffset(c3.z);\n";
                }
    
                if (mOutputType == SH_HLSL_4_1_OUTPUT)
                {
                    mResourcesHLSL->samplerMetadataUniforms(out, 4);
                }
    
                out << "};\n";
            }
            else
            {
                if (mUsesDepthRange)
                {
                    out << "uniform float3 dx_DepthRange : register(c0);";
                }
    
                if (mUsesFragCoord)
                {
                    out << "uniform float4 dx_ViewCoords : register(c1);\n";
                }
    
                if (mUsesFragCoord || mUsesFrontFacing)
                {
                    out << "uniform float3 dx_DepthFront : register(c2);\n";
                }
            }
    
            out << "\n";
    
            if (mUsesDepthRange)
            {
                out << "static gl_DepthRangeParameters gl_DepthRange = {dx_DepthRange.x, "
                       "dx_DepthRange.y, dx_DepthRange.z};\n"
                       "\n";
            }
    
            if (usingMRTExtension && mNumRenderTargets > 1)
            {
                out << "#define GL_USES_MRT\n";
            }
    
            if (mUsesFragColor)
            {
                out << "#define GL_USES_FRAG_COLOR\n";
            }
    
            if (mUsesFragData)
            {
                out << "#define GL_USES_FRAG_DATA\n";
            }
    
            if (mShaderVersion < 300 && usingBFEExtension && mUsesSecondaryColor)
            {
                out << "#define GL_USES_SECONDARY_COLOR\n";
            }
        }
        else if (mShaderType == GL_VERTEX_SHADER)
        {
            out << "// Attributes\n";
            writeReferencedAttributes(out);
            out << "\n"
                   "static float4 gl_Position = float4(0, 0, 0, 0);\n";
    
            if (mUsesPointSize)
            {
                out << "static float gl_PointSize = float(1);\n";
            }
    
            if (mUsesInstanceID)
            {
                out << "static int gl_InstanceID;";
            }
    
            if (mUsesVertexID)
            {
                out << "static int gl_VertexID;";
            }
    
            out << "\n"
                   "// Varyings\n";
            writeReferencedVaryings(out);
            out << "\n";
    
            if (mUsesDepthRange)
            {
                out << "struct gl_DepthRangeParameters\n"
                       "{\n"
                       "    float near;\n"
                       "    float far;\n"
                       "    float diff;\n"
                       "};\n"
                       "\n";
            }
    
            if (mOutputType == SH_HLSL_4_1_OUTPUT || mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
            {
                out << "cbuffer DriverConstants : register(b1)\n"
                       "{\n";
    
                if (mUsesDepthRange)
                {
                    out << "    float3 dx_DepthRange : packoffset(c0);\n";
                }
    
                // dx_ViewAdjust and dx_ViewCoords will only be used in Feature Level 9
                // shaders. However, we declare it for all shaders (including Feature Level 10+).
                // The bytecode is the same whether we declare it or not, since D3DCompiler removes it
                // if it's unused.
                out << "    float4 dx_ViewAdjust : packoffset(c1);\n";
                out << "    float2 dx_ViewCoords : packoffset(c2);\n";
                out << "    float2 dx_ViewScale  : packoffset(c3);\n";
    
                if (mHasMultiviewExtensionEnabled)
                {
                    // We have to add a value which we can use to keep track of which multi-view code
                    // path is to be selected in the GS.
                    out << "    float multiviewSelectViewportIndex : packoffset(c3.z);\n";
                }
    
                if (mOutputType == SH_HLSL_4_1_OUTPUT)
                {
                    mResourcesHLSL->samplerMetadataUniforms(out, 4);
                }
    
                if (mUsesVertexID)
                {
                    out << "    uint dx_VertexID : packoffset(c3.w);\n";
                }
    
                out << "};\n"
                       "\n";
            }
            else
            {
                if (mUsesDepthRange)
                {
                    out << "uniform float3 dx_DepthRange : register(c0);\n";
                }
    
                out << "uniform float4 dx_ViewAdjust : register(c1);\n";
                out << "uniform float2 dx_ViewCoords : register(c2);\n"
                       "\n";
            }
    
            if (mUsesDepthRange)
            {
                out << "static gl_DepthRangeParameters gl_DepthRange = {dx_DepthRange.x, "
                       "dx_DepthRange.y, dx_DepthRange.z};\n"
                       "\n";
            }
        }
        else  // Compute shader
        {
            ASSERT(mShaderType == GL_COMPUTE_SHADER);
    
            out << "cbuffer DriverConstants : register(b1)\n"
                   "{\n";
            if (mUsesNumWorkGroups)
            {
                out << "    uint3 gl_NumWorkGroups : packoffset(c0);\n";
            }
            ASSERT(mOutputType == SH_HLSL_4_1_OUTPUT);
            unsigned int registerIndex = 1;
            mResourcesHLSL->samplerMetadataUniforms(out, registerIndex);
            // Sampler metadata struct must be two 4-vec, 32 bytes.
            registerIndex += mResourcesHLSL->getSamplerCount() * 2;
            mResourcesHLSL->imageMetadataUniforms(out, registerIndex);
            out << "};\n";
    
            out << kImage2DFunctionString << "\n";
    
            std::ostringstream systemValueDeclaration  = sh::InitializeStream<std::ostringstream>();
            std::ostringstream glBuiltinInitialization = sh::InitializeStream<std::ostringstream>();
    
            systemValueDeclaration << "\nstruct CS_INPUT\n{\n";
            glBuiltinInitialization << "\nvoid initGLBuiltins(CS_INPUT input)\n"
                                    << "{\n";
    
            if (mUsesWorkGroupID)
            {
                out << "static uint3 gl_WorkGroupID = uint3(0, 0, 0);\n";
                systemValueDeclaration << "    uint3 dx_WorkGroupID : "
                                       << "SV_GroupID;\n";
                glBuiltinInitialization << "    gl_WorkGroupID = input.dx_WorkGroupID;\n";
            }
    
            if (mUsesLocalInvocationID)
            {
                out << "static uint3 gl_LocalInvocationID = uint3(0, 0, 0);\n";
                systemValueDeclaration << "    uint3 dx_LocalInvocationID : "
                                       << "SV_GroupThreadID;\n";
                glBuiltinInitialization << "    gl_LocalInvocationID = input.dx_LocalInvocationID;\n";
            }
    
            if (mUsesGlobalInvocationID)
            {
                out << "static uint3 gl_GlobalInvocationID = uint3(0, 0, 0);\n";
                systemValueDeclaration << "    uint3 dx_GlobalInvocationID : "
                                       << "SV_DispatchThreadID;\n";
                glBuiltinInitialization << "    gl_GlobalInvocationID = input.dx_GlobalInvocationID;\n";
            }
    
            if (mUsesLocalInvocationIndex)
            {
                out << "static uint gl_LocalInvocationIndex = uint(0);\n";
                systemValueDeclaration << "    uint dx_LocalInvocationIndex : "
                                       << "SV_GroupIndex;\n";
                glBuiltinInitialization
                    << "    gl_LocalInvocationIndex = input.dx_LocalInvocationIndex;\n";
            }
    
            systemValueDeclaration << "};\n\n";
            glBuiltinInitialization << "};\n\n";
    
            out << systemValueDeclaration.str();
            out << glBuiltinInitialization.str();
        }
    
        if (!mappedStructs.empty())
        {
            out << "// Structures from std140 blocks with padding removed\n";
            out << "\n";
            out << mappedStructs;
            out << "\n";
        }
    
        bool getDimensionsIgnoresBaseLevel =
            (mCompileOptions & SH_HLSL_GET_DIMENSIONS_IGNORES_BASE_LEVEL) != 0;
        mTextureFunctionHLSL->textureFunctionHeader(out, mOutputType, getDimensionsIgnoresBaseLevel);
        mImageFunctionHLSL->imageFunctionHeader(out);
        mAtomicCounterFunctionHLSL->atomicCounterFunctionHeader(out);
    
        if (mUsesFragCoord)
        {
            out << "#define GL_USES_FRAG_COORD\n";
        }
    
        if (mUsesPointCoord)
        {
            out << "#define GL_USES_POINT_COORD\n";
        }
    
        if (mUsesFrontFacing)
        {
            out << "#define GL_USES_FRONT_FACING\n";
        }
    
        if (mUsesHelperInvocation)
        {
            out << "#define GL_USES_HELPER_INVOCATION\n";
        }
    
        if (mUsesPointSize)
        {
            out << "#define GL_USES_POINT_SIZE\n";
        }
    
        if (mHasMultiviewExtensionEnabled)
        {
            out << "#define GL_ANGLE_MULTIVIEW_ENABLED\n";
        }
    
        if (mUsesVertexID)
        {
            out << "#define GL_USES_VERTEX_ID\n";
        }
    
        if (mUsesViewID)
        {
            out << "#define GL_USES_VIEW_ID\n";
        }
    
        if (mUsesFragDepth)
        {
            out << "#define GL_USES_FRAG_DEPTH\n";
        }
    
        if (mUsesDepthRange)
        {
            out << "#define GL_USES_DEPTH_RANGE\n";
        }
    
        if (mUsesXor)
        {
            out << "bool xor(bool p, bool q)\n"
                   "{\n"
                   "    return (p || q) && !(p && q);\n"
                   "}\n"
                   "\n";
        }
    
        builtInFunctionEmulator->outputEmulatedFunctions(out);
    }
    
    void OutputHLSL::visitSymbol(TIntermSymbol *node)
    {
        const TVariable &variable = node->variable();
    
        // Empty symbols can only appear in declarations and function arguments, and in either of those
        // cases the symbol nodes are not visited.
        ASSERT(variable.symbolType() != SymbolType::Empty);
    
        TInfoSinkBase &out = getInfoSink();
    
        // Handle accessing std140 structs by value
        if (IsInStd140UniformBlock(node) && node->getBasicType() == EbtStruct &&
            needStructMapping(node))
        {
            mNeedStructMapping = true;
            out << "map";
        }
    
        const ImmutableString &name     = variable.name();
        const TSymbolUniqueId &uniqueId = variable.uniqueId();
    
        if (name == "gl_DepthRange")
        {
            mUsesDepthRange = true;
            out << name;
        }
        else if (IsAtomicCounter(variable.getType().getBasicType()))
        {
            const TType &variableType = variable.getType();
            if (variableType.getQualifier() == EvqUniform)
            {
                TLayoutQualifier layout             = variableType.getLayoutQualifier();
                mReferencedUniforms[uniqueId.get()] = &variable;
                out << getAtomicCounterNameForBinding(layout.binding) << ", " << layout.offset;
            }
            else
            {
                TString varName = DecorateVariableIfNeeded(variable);
                out << varName << ", " << varName << "_offset";
            }
        }
        else
        {
            const TType &variableType = variable.getType();
            TQualifier qualifier      = variable.getType().getQualifier();
    
            ensureStructDefined(variableType);
    
            if (qualifier == EvqUniform)
            {
                const TInterfaceBlock *interfaceBlock = variableType.getInterfaceBlock();
    
                if (interfaceBlock)
                {
                    if (mReferencedUniformBlocks.count(interfaceBlock->uniqueId().get()) == 0)
                    {
                        const TVariable *instanceVariable = nullptr;
                        if (variableType.isInterfaceBlock())
                        {
                            instanceVariable = &variable;
                        }
                        mReferencedUniformBlocks[interfaceBlock->uniqueId().get()] =
                            new TReferencedBlock(interfaceBlock, instanceVariable);
                    }
                }
                else
                {
                    mReferencedUniforms[uniqueId.get()] = &variable;
                }
    
                out << DecorateVariableIfNeeded(variable);
            }
            else if (qualifier == EvqBuffer)
            {
                UNREACHABLE();
            }
            else if (qualifier == EvqAttribute || qualifier == EvqVertexIn)
            {
                mReferencedAttributes[uniqueId.get()] = &variable;
                out << Decorate(name);
            }
            else if (IsVarying(qualifier))
            {
                mReferencedVaryings[uniqueId.get()] = &variable;
                out << DecorateVariableIfNeeded(variable);
                if (variable.symbolType() == SymbolType::AngleInternal && name == "ViewID_OVR")
                {
                    mUsesViewID = true;
                }
            }
            else if (qualifier == EvqFragmentOut)
            {
                mReferencedOutputVariables[uniqueId.get()] = &variable;
                out << "out_" << name;
            }
            else if (qualifier == EvqFragColor)
            {
                out << "gl_Color[0]";
                mUsesFragColor = true;
            }
            else if (qualifier == EvqFragData)
            {
                out << "gl_Color";
                mUsesFragData = true;
            }
            else if (qualifier == EvqSecondaryFragColorEXT)
            {
                out << "gl_SecondaryColor[0]";
                mUsesSecondaryColor = true;
            }
            else if (qualifier == EvqSecondaryFragDataEXT)
            {
                out << "gl_SecondaryColor";
                mUsesSecondaryColor = true;
            }
            else if (qualifier == EvqFragCoord)
            {
                mUsesFragCoord = true;
                out << name;
            }
            else if (qualifier == EvqPointCoord)
            {
                mUsesPointCoord = true;
                out << name;
            }
            else if (qualifier == EvqFrontFacing)
            {
                mUsesFrontFacing = true;
                out << name;
            }
            else if (qualifier == EvqHelperInvocation)
            {
                mUsesHelperInvocation = true;
                out << name;
            }
            else if (qualifier == EvqPointSize)
            {
                mUsesPointSize = true;
                out << name;
            }
            else if (qualifier == EvqInstanceID)
            {
                mUsesInstanceID = true;
                out << name;
            }
            else if (qualifier == EvqVertexID)
            {
                mUsesVertexID = true;
                out << name;
            }
            else if (name == "gl_FragDepthEXT" || name == "gl_FragDepth")
            {
                mUsesFragDepth = true;
                out << "gl_Depth";
            }
            else if (qualifier == EvqNumWorkGroups)
            {
                mUsesNumWorkGroups = true;
                out << name;
            }
            else if (qualifier == EvqWorkGroupID)
            {
                mUsesWorkGroupID = true;
                out << name;
            }
            else if (qualifier == EvqLocalInvocationID)
            {
                mUsesLocalInvocationID = true;
                out << name;
            }
            else if (qualifier == EvqGlobalInvocationID)
            {
                mUsesGlobalInvocationID = true;
                out << name;
            }
            else if (qualifier == EvqLocalInvocationIndex)
            {
                mUsesLocalInvocationIndex = true;
                out << name;
            }
            else
            {
                out << DecorateVariableIfNeeded(variable);
            }
        }
    }
    
    void OutputHLSL::outputEqual(Visit visit, const TType &type, TOperator op, TInfoSinkBase &out)
    {
        if (type.isScalar() && !type.isArray())
        {
            if (op == EOpEqual)
            {
                outputTriplet(out, visit, "(", " == ", ")");
            }
            else
            {
                outputTriplet(out, visit, "(", " != ", ")");
            }
        }
        else
        {
            if (visit == PreVisit && op == EOpNotEqual)
            {
                out << "!";
            }
    
            if (type.isArray())
            {
                const TString &functionName = addArrayEqualityFunction(type);
                outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
            }
            else if (type.getBasicType() == EbtStruct)
            {
                const TStructure &structure = *type.getStruct();
                const TString &functionName = addStructEqualityFunction(structure);
                outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
            }
            else
            {
                ASSERT(type.isMatrix() || type.isVector());
                outputTriplet(out, visit, "all(", " == ", ")");
            }
        }
    }
    
    void OutputHLSL::outputAssign(Visit visit, const TType &type, TInfoSinkBase &out)
    {
        if (type.isArray())
        {
            const TString &functionName = addArrayAssignmentFunction(type);
            outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
        }
        else
        {
            outputTriplet(out, visit, "(", " = ", ")");
        }
    }
    
    bool OutputHLSL::ancestorEvaluatesToSamplerInStruct()
    {
        for (unsigned int n = 0u; getAncestorNode(n) != nullptr; ++n)
        {
            TIntermNode *ancestor               = getAncestorNode(n);
            const TIntermBinary *ancestorBinary = ancestor->getAsBinaryNode();
            if (ancestorBinary == nullptr)
            {
                return false;
            }
            switch (ancestorBinary->getOp())
            {
                case EOpIndexDirectStruct:
                {
                    const TStructure *structure = ancestorBinary->getLeft()->getType().getStruct();
                    const TIntermConstantUnion *index =
                        ancestorBinary->getRight()->getAsConstantUnion();
                    const TField *field = structure->fields()[index->getIConst(0)];
                    if (IsSampler(field->type()->getBasicType()))
                    {
                        return true;
                    }
                    break;
                }
                case EOpIndexDirect:
                    break;
                default:
                    // Returning a sampler from indirect indexing is not supported.
                    return false;
            }
        }
        return false;
    }
    
    bool OutputHLSL::visitSwizzle(Visit visit, TIntermSwizzle *node)
    {
        TInfoSinkBase &out = getInfoSink();
        if (visit == PostVisit)
        {
            out << ".";
            node->writeOffsetsAsXYZW(&out);
        }
        return true;
    }
    
    bool OutputHLSL::visitBinary(Visit visit, TIntermBinary *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        switch (node->getOp())
        {
            case EOpComma:
                outputTriplet(out, visit, "(", ", ", ")");
                break;
            case EOpAssign:
                if (node->isArray())
                {
                    TIntermAggregate *rightAgg = node->getRight()->getAsAggregate();
                    if (rightAgg != nullptr && rightAgg->isConstructor())
                    {
                        const TString &functionName = addArrayConstructIntoFunction(node->getType());
                        out << functionName << "(";
                        node->getLeft()->traverse(this);
                        TIntermSequence *seq = rightAgg->getSequence();
                        for (auto &arrayElement : *seq)
                        {
                            out << ", ";
                            arrayElement->traverse(this);
                        }
                        out << ")";
                        return false;
                    }
                    // ArrayReturnValueToOutParameter should have eliminated expressions where a
                    // function call is assigned.
                    ASSERT(rightAgg == nullptr);
                }
                // Assignment expressions with atomic functions should be transformed into atomic
                // function calls in HLSL.
                // e.g. original_value = atomicAdd(dest, value) should be translated into
                //      InterlockedAdd(dest, value, original_value);
                else if (IsAtomicFunctionForSharedVariableDirectAssign(*node))
                {
                    TIntermAggregate *atomicFunctionNode = node->getRight()->getAsAggregate();
                    TOperator atomicFunctionOp           = atomicFunctionNode->getOp();
                    out << GetHLSLAtomicFunctionStringAndLeftParenthesis(atomicFunctionOp);
                    TIntermSequence *argumentSeq = atomicFunctionNode->getSequence();
                    ASSERT(argumentSeq->size() >= 2u);
                    for (auto &argument : *argumentSeq)
                    {
                        argument->traverse(this);
                        out << ", ";
                    }
                    node->getLeft()->traverse(this);
                    out << ")";
                    return false;
                }
                else if (IsInShaderStorageBlock(node->getLeft()))
                {
                    mSSBOOutputHLSL->outputStoreFunctionCallPrefix(node->getLeft());
                    out << ", ";
                    if (IsInShaderStorageBlock(node->getRight()))
                    {
                        mSSBOOutputHLSL->outputLoadFunctionCall(node->getRight());
                    }
                    else
                    {
                        node->getRight()->traverse(this);
                    }
    
                    out << ")";
                    return false;
                }
                else if (IsInShaderStorageBlock(node->getRight()))
                {
                    node->getLeft()->traverse(this);
                    out << " = ";
                    mSSBOOutputHLSL->outputLoadFunctionCall(node->getRight());
                    return false;
                }
    
                outputAssign(visit, node->getType(), out);
                break;
            case EOpInitialize:
                if (visit == PreVisit)
                {
                    TIntermSymbol *symbolNode = node->getLeft()->getAsSymbolNode();
                    ASSERT(symbolNode);
                    TIntermTyped *initializer = node->getRight();
    
                    // Global initializers must be constant at this point.
                    ASSERT(symbolNode->getQualifier() != EvqGlobal || initializer->hasConstantValue());
    
                    // GLSL allows to write things like "float x = x;" where a new variable x is defined
                    // and the value of an existing variable x is assigned. HLSL uses C semantics (the
                    // new variable is created before the assignment is evaluated), so we need to
                    // convert
                    // this to "float t = x, x = t;".
                    if (writeSameSymbolInitializer(out, symbolNode, initializer))
                    {
                        // Skip initializing the rest of the expression
                        return false;
                    }
                    else if (writeConstantInitialization(out, symbolNode, initializer))
                    {
                        return false;
                    }
                }
                else if (visit == InVisit)
                {
                    out << " = ";
                    if (IsInShaderStorageBlock(node->getRight()))
                    {
                        mSSBOOutputHLSL->outputLoadFunctionCall(node->getRight());
                        return false;
                    }
                }
                break;
            case EOpAddAssign:
                outputTriplet(out, visit, "(", " += ", ")");
                break;
            case EOpSubAssign:
                outputTriplet(out, visit, "(", " -= ", ")");
                break;
            case EOpMulAssign:
                outputTriplet(out, visit, "(", " *= ", ")");
                break;
            case EOpVectorTimesScalarAssign:
                outputTriplet(out, visit, "(", " *= ", ")");
                break;
            case EOpMatrixTimesScalarAssign:
                outputTriplet(out, visit, "(", " *= ", ")");
                break;
            case EOpVectorTimesMatrixAssign:
                if (visit == PreVisit)
                {
                    out << "(";
                }
                else if (visit == InVisit)
                {
                    out << " = mul(";
                    node->getLeft()->traverse(this);
                    out << ", transpose(";
                }
                else
                {
                    out << ")))";
                }
                break;
            case EOpMatrixTimesMatrixAssign:
                if (visit == PreVisit)
                {
                    out << "(";
                }
                else if (visit == InVisit)
                {
                    out << " = transpose(mul(transpose(";
                    node->getLeft()->traverse(this);
                    out << "), transpose(";
                }
                else
                {
                    out << "))))";
                }
                break;
            case EOpDivAssign:
                outputTriplet(out, visit, "(", " /= ", ")");
                break;
            case EOpIModAssign:
                outputTriplet(out, visit, "(", " %= ", ")");
                break;
            case EOpBitShiftLeftAssign:
                outputTriplet(out, visit, "(", " <<= ", ")");
                break;
            case EOpBitShiftRightAssign:
                outputTriplet(out, visit, "(", " >>= ", ")");
                break;
            case EOpBitwiseAndAssign:
                outputTriplet(out, visit, "(", " &= ", ")");
                break;
            case EOpBitwiseXorAssign:
                outputTriplet(out, visit, "(", " ^= ", ")");
                break;
            case EOpBitwiseOrAssign:
                outputTriplet(out, visit, "(", " |= ", ")");
                break;
            case EOpIndexDirect:
            {
                const TType &leftType = node->getLeft()->getType();
                if (leftType.isInterfaceBlock())
                {
                    if (visit == PreVisit)
                    {
                        TIntermSymbol *instanceArraySymbol    = node->getLeft()->getAsSymbolNode();
                        const TInterfaceBlock *interfaceBlock = leftType.getInterfaceBlock();
    
                        ASSERT(leftType.getQualifier() == EvqUniform);
                        if (mReferencedUniformBlocks.count(interfaceBlock->uniqueId().get()) == 0)
                        {
                            mReferencedUniformBlocks[interfaceBlock->uniqueId().get()] =
                                new TReferencedBlock(interfaceBlock, &instanceArraySymbol->variable());
                        }
                        const int arrayIndex = node->getRight()->getAsConstantUnion()->getIConst(0);
                        out << mResourcesHLSL->InterfaceBlockInstanceString(
                            instanceArraySymbol->getName(), arrayIndex);
                        return false;
                    }
                }
                else if (ancestorEvaluatesToSamplerInStruct())
                {
                    // All parts of an expression that access a sampler in a struct need to use _ as
                    // separator to access the sampler variable that has been moved out of the struct.
                    outputTriplet(out, visit, "", "_", "");
                }
                else if (IsAtomicCounter(leftType.getBasicType()))
                {
                    outputTriplet(out, visit, "", " + (", ") * ATOMIC_COUNTER_ARRAY_STRIDE");
                }
                else
                {
                    outputTriplet(out, visit, "", "[", "]");
                }
            }
            break;
            case EOpIndexIndirect:
            {
                // We do not currently support indirect references to interface blocks
                ASSERT(node->getLeft()->getBasicType() != EbtInterfaceBlock);
    
                const TType &leftType = node->getLeft()->getType();
                if (IsAtomicCounter(leftType.getBasicType()))
                {
                    outputTriplet(out, visit, "", " + (", ") * ATOMIC_COUNTER_ARRAY_STRIDE");
                }
                else
                {
                    outputTriplet(out, visit, "", "[", "]");
                }
                break;
            }
            case EOpIndexDirectStruct:
            {
                const TStructure *structure       = node->getLeft()->getType().getStruct();
                const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
                const TField *field               = structure->fields()[index->getIConst(0)];
    
                // In cases where indexing returns a sampler, we need to access the sampler variable
                // that has been moved out of the struct.
                bool indexingReturnsSampler = IsSampler(field->type()->getBasicType());
                if (visit == PreVisit && indexingReturnsSampler)
                {
                    // Samplers extracted from structs have "angle" prefix to avoid name conflicts.
                    // This prefix is only output at the beginning of the indexing expression, which
                    // may have multiple parts.
                    out << "angle";
                }
                if (!indexingReturnsSampler)
                {
                    // All parts of an expression that access a sampler in a struct need to use _ as
                    // separator to access the sampler variable that has been moved out of the struct.
                    indexingReturnsSampler = ancestorEvaluatesToSamplerInStruct();
                }
                if (visit == InVisit)
                {
                    if (indexingReturnsSampler)
                    {
                        out << "_" << field->name();
                    }
                    else
                    {
                        out << "." << DecorateField(field->name(), *structure);
                    }
    
                    return false;
                }
            }
            break;
            case EOpIndexDirectInterfaceBlock:
            {
                ASSERT(!IsInShaderStorageBlock(node->getLeft()));
                bool structInStd140UniformBlock = node->getBasicType() == EbtStruct &&
                                                  IsInStd140UniformBlock(node->getLeft()) &&
                                                  needStructMapping(node);
                if (visit == PreVisit && structInStd140UniformBlock)
                {
                    mNeedStructMapping = true;
                    out << "map";
                }
                if (visit == InVisit)
                {
                    const TInterfaceBlock *interfaceBlock =
                        node->getLeft()->getType().getInterfaceBlock();
                    const TIntermConstantUnion *index = node->getRight()->getAsConstantUnion();
                    const TField *field               = interfaceBlock->fields()[index->getIConst(0)];
                    if (structInStd140UniformBlock)
                    {
                        out << "_";
                    }
                    else
                    {
                        out << ".";
                    }
                    out << Decorate(field->name());
    
                    return false;
                }
                break;
            }
            case EOpAdd:
                outputTriplet(out, visit, "(", " + ", ")");
                break;
            case EOpSub:
                outputTriplet(out, visit, "(", " - ", ")");
                break;
            case EOpMul:
                outputTriplet(out, visit, "(", " * ", ")");
                break;
            case EOpDiv:
                outputTriplet(out, visit, "(", " / ", ")");
                break;
            case EOpIMod:
                outputTriplet(out, visit, "(", " % ", ")");
                break;
            case EOpBitShiftLeft:
                outputTriplet(out, visit, "(", " << ", ")");
                break;
            case EOpBitShiftRight:
                outputTriplet(out, visit, "(", " >> ", ")");
                break;
            case EOpBitwiseAnd:
                outputTriplet(out, visit, "(", " & ", ")");
                break;
            case EOpBitwiseXor:
                outputTriplet(out, visit, "(", " ^ ", ")");
                break;
            case EOpBitwiseOr:
                outputTriplet(out, visit, "(", " | ", ")");
                break;
            case EOpEqual:
            case EOpNotEqual:
                outputEqual(visit, node->getLeft()->getType(), node->getOp(), out);
                break;
            case EOpLessThan:
                outputTriplet(out, visit, "(", " < ", ")");
                break;
            case EOpGreaterThan:
                outputTriplet(out, visit, "(", " > ", ")");
                break;
            case EOpLessThanEqual:
                outputTriplet(out, visit, "(", " <= ", ")");
                break;
            case EOpGreaterThanEqual:
                outputTriplet(out, visit, "(", " >= ", ")");
                break;
            case EOpVectorTimesScalar:
                outputTriplet(out, visit, "(", " * ", ")");
                break;
            case EOpMatrixTimesScalar:
                outputTriplet(out, visit, "(", " * ", ")");
                break;
            case EOpVectorTimesMatrix:
                outputTriplet(out, visit, "mul(", ", transpose(", "))");
                break;
            case EOpMatrixTimesVector:
                outputTriplet(out, visit, "mul(transpose(", "), ", ")");
                break;
            case EOpMatrixTimesMatrix:
                outputTriplet(out, visit, "transpose(mul(transpose(", "), transpose(", ")))");
                break;
            case EOpLogicalOr:
                // HLSL doesn't short-circuit ||, so we assume that || affected by short-circuiting have
                // been unfolded.
                ASSERT(!node->getRight()->hasSideEffects());
                outputTriplet(out, visit, "(", " || ", ")");
                return true;
            case EOpLogicalXor:
                mUsesXor = true;
                outputTriplet(out, visit, "xor(", ", ", ")");
                break;
            case EOpLogicalAnd:
                // HLSL doesn't short-circuit &&, so we assume that && affected by short-circuiting have
                // been unfolded.
                ASSERT(!node->getRight()->hasSideEffects());
                outputTriplet(out, visit, "(", " && ", ")");
                return true;
            default:
                UNREACHABLE();
        }
    
        return true;
    }
    
    bool OutputHLSL::visitUnary(Visit visit, TIntermUnary *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        switch (node->getOp())
        {
            case EOpNegative:
                outputTriplet(out, visit, "(-", "", ")");
                break;
            case EOpPositive:
                outputTriplet(out, visit, "(+", "", ")");
                break;
            case EOpLogicalNot:
                outputTriplet(out, visit, "(!", "", ")");
                break;
            case EOpBitwiseNot:
                outputTriplet(out, visit, "(~", "", ")");
                break;
            case EOpPostIncrement:
                outputTriplet(out, visit, "(", "", "++)");
                break;
            case EOpPostDecrement:
                outputTriplet(out, visit, "(", "", "--)");
                break;
            case EOpPreIncrement:
                outputTriplet(out, visit, "(++", "", ")");
                break;
            case EOpPreDecrement:
                outputTriplet(out, visit, "(--", "", ")");
                break;
            case EOpRadians:
                outputTriplet(out, visit, "radians(", "", ")");
                break;
            case EOpDegrees:
                outputTriplet(out, visit, "degrees(", "", ")");
                break;
            case EOpSin:
                outputTriplet(out, visit, "sin(", "", ")");
                break;
            case EOpCos:
                outputTriplet(out, visit, "cos(", "", ")");
                break;
            case EOpTan:
                outputTriplet(out, visit, "tan(", "", ")");
                break;
            case EOpAsin:
                outputTriplet(out, visit, "asin(", "", ")");
                break;
            case EOpAcos:
                outputTriplet(out, visit, "acos(", "", ")");
                break;
            case EOpAtan:
                outputTriplet(out, visit, "atan(", "", ")");
                break;
            case EOpSinh:
                outputTriplet(out, visit, "sinh(", "", ")");
                break;
            case EOpCosh:
                outputTriplet(out, visit, "cosh(", "", ")");
                break;
            case EOpTanh:
            case EOpAsinh:
            case EOpAcosh:
            case EOpAtanh:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpExp:
                outputTriplet(out, visit, "exp(", "", ")");
                break;
            case EOpLog:
                outputTriplet(out, visit, "log(", "", ")");
                break;
            case EOpExp2:
                outputTriplet(out, visit, "exp2(", "", ")");
                break;
            case EOpLog2:
                outputTriplet(out, visit, "log2(", "", ")");
                break;
            case EOpSqrt:
                outputTriplet(out, visit, "sqrt(", "", ")");
                break;
            case EOpInversesqrt:
                outputTriplet(out, visit, "rsqrt(", "", ")");
                break;
            case EOpAbs:
                outputTriplet(out, visit, "abs(", "", ")");
                break;
            case EOpSign:
                outputTriplet(out, visit, "sign(", "", ")");
                break;
            case EOpFloor:
                outputTriplet(out, visit, "floor(", "", ")");
                break;
            case EOpTrunc:
                outputTriplet(out, visit, "trunc(", "", ")");
                break;
            case EOpRound:
                outputTriplet(out, visit, "round(", "", ")");
                break;
            case EOpRoundEven:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpCeil:
                outputTriplet(out, visit, "ceil(", "", ")");
                break;
            case EOpFract:
                outputTriplet(out, visit, "frac(", "", ")");
                break;
            case EOpIsnan:
                if (node->getUseEmulatedFunction())
                    writeEmulatedFunctionTriplet(out, visit, node->getOp());
                else
                    outputTriplet(out, visit, "isnan(", "", ")");
                mRequiresIEEEStrictCompiling = true;
                break;
            case EOpIsinf:
                outputTriplet(out, visit, "isinf(", "", ")");
                break;
            case EOpFloatBitsToInt:
                outputTriplet(out, visit, "asint(", "", ")");
                break;
            case EOpFloatBitsToUint:
                outputTriplet(out, visit, "asuint(", "", ")");
                break;
            case EOpIntBitsToFloat:
                outputTriplet(out, visit, "asfloat(", "", ")");
                break;
            case EOpUintBitsToFloat:
                outputTriplet(out, visit, "asfloat(", "", ")");
                break;
            case EOpPackSnorm2x16:
            case EOpPackUnorm2x16:
            case EOpPackHalf2x16:
            case EOpUnpackSnorm2x16:
            case EOpUnpackUnorm2x16:
            case EOpUnpackHalf2x16:
            case EOpPackUnorm4x8:
            case EOpPackSnorm4x8:
            case EOpUnpackUnorm4x8:
            case EOpUnpackSnorm4x8:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpLength:
                outputTriplet(out, visit, "length(", "", ")");
                break;
            case EOpNormalize:
                outputTriplet(out, visit, "normalize(", "", ")");
                break;
            case EOpDFdx:
                if (mInsideDiscontinuousLoop || mOutputLod0Function)
                {
                    outputTriplet(out, visit, "(", "", ", 0.0)");
                }
                else
                {
                    outputTriplet(out, visit, "ddx(", "", ")");
                }
                break;
            case EOpDFdy:
                if (mInsideDiscontinuousLoop || mOutputLod0Function)
                {
                    outputTriplet(out, visit, "(", "", ", 0.0)");
                }
                else
                {
                    outputTriplet(out, visit, "ddy(", "", ")");
                }
                break;
            case EOpFwidth:
                if (mInsideDiscontinuousLoop || mOutputLod0Function)
                {
                    outputTriplet(out, visit, "(", "", ", 0.0)");
                }
                else
                {
                    outputTriplet(out, visit, "fwidth(", "", ")");
                }
                break;
            case EOpTranspose:
                outputTriplet(out, visit, "transpose(", "", ")");
                break;
            case EOpDeterminant:
                outputTriplet(out, visit, "determinant(transpose(", "", "))");
                break;
            case EOpInverse:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
    
            case EOpAny:
                outputTriplet(out, visit, "any(", "", ")");
                break;
            case EOpAll:
                outputTriplet(out, visit, "all(", "", ")");
                break;
            case EOpLogicalNotComponentWise:
                outputTriplet(out, visit, "(!", "", ")");
                break;
            case EOpBitfieldReverse:
                outputTriplet(out, visit, "reversebits(", "", ")");
                break;
            case EOpBitCount:
                outputTriplet(out, visit, "countbits(", "", ")");
                break;
            case EOpFindLSB:
                // Note that it's unclear from the HLSL docs what this returns for 0, but this is tested
                // in GLSLTest and results are consistent with GL.
                outputTriplet(out, visit, "firstbitlow(", "", ")");
                break;
            case EOpFindMSB:
                // Note that it's unclear from the HLSL docs what this returns for 0 or -1, but this is
                // tested in GLSLTest and results are consistent with GL.
                outputTriplet(out, visit, "firstbithigh(", "", ")");
                break;
            case EOpArrayLength:
            {
                TIntermTyped *operand = node->getOperand();
                ASSERT(IsInShaderStorageBlock(operand));
                mSSBOOutputHLSL->outputLengthFunctionCall(operand);
                return false;
            }
            default:
                UNREACHABLE();
        }
    
        return true;
    }
    
    ImmutableString OutputHLSL::samplerNamePrefixFromStruct(TIntermTyped *node)
    {
        if (node->getAsSymbolNode())
        {
            ASSERT(node->getAsSymbolNode()->variable().symbolType() != SymbolType::Empty);
            return node->getAsSymbolNode()->getName();
        }
        TIntermBinary *nodeBinary = node->getAsBinaryNode();
        switch (nodeBinary->getOp())
        {
            case EOpIndexDirect:
            {
                int index = nodeBinary->getRight()->getAsConstantUnion()->getIConst(0);
    
                std::stringstream prefixSink = sh::InitializeStream<std::stringstream>();
                prefixSink << samplerNamePrefixFromStruct(nodeBinary->getLeft()) << "_" << index;
                return ImmutableString(prefixSink.str());
            }
            case EOpIndexDirectStruct:
            {
                const TStructure *s = nodeBinary->getLeft()->getAsTyped()->getType().getStruct();
                int index           = nodeBinary->getRight()->getAsConstantUnion()->getIConst(0);
                const TField *field = s->fields()[index];
    
                std::stringstream prefixSink = sh::InitializeStream<std::stringstream>();
                prefixSink << samplerNamePrefixFromStruct(nodeBinary->getLeft()) << "_"
                           << field->name();
                return ImmutableString(prefixSink.str());
            }
            default:
                UNREACHABLE();
                return kEmptyImmutableString;
        }
    }
    
    bool OutputHLSL::visitBlock(Visit visit, TIntermBlock *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        bool isMainBlock = mInsideMain && getParentNode()->getAsFunctionDefinition();
    
        if (mInsideFunction)
        {
            outputLineDirective(out, node->getLine().first_line);
            out << "{\n";
            if (isMainBlock)
            {
                if (mShaderType == GL_COMPUTE_SHADER)
                {
                    out << "initGLBuiltins(input);\n";
                }
                else
                {
                    out << "@@ MAIN PROLOGUE @@\n";
                }
            }
        }
    
        for (TIntermNode *statement : *node->getSequence())
        {
            outputLineDirective(out, statement->getLine().first_line);
    
            statement->traverse(this);
    
            // Don't output ; after case labels, they're terminated by :
            // This is needed especially since outputting a ; after a case statement would turn empty
            // case statements into non-empty case statements, disallowing fall-through from them.
            // Also the output code is clearer if we don't output ; after statements where it is not
            // needed:
            //  * if statements
            //  * switch statements
            //  * blocks
            //  * function definitions
            //  * loops (do-while loops output the semicolon in VisitLoop)
            //  * declarations that don't generate output.
            if (statement->getAsCaseNode() == nullptr && statement->getAsIfElseNode() == nullptr &&
                statement->getAsBlock() == nullptr && statement->getAsLoopNode() == nullptr &&
                statement->getAsSwitchNode() == nullptr &&
                statement->getAsFunctionDefinition() == nullptr &&
                (statement->getAsDeclarationNode() == nullptr ||
                 IsDeclarationWrittenOut(statement->getAsDeclarationNode())) &&
                statement->getAsInvariantDeclarationNode() == nullptr)
            {
                out << ";\n";
            }
        }
    
        if (mInsideFunction)
        {
            outputLineDirective(out, node->getLine().last_line);
            if (isMainBlock && shaderNeedsGenerateOutput())
            {
                // We could have an empty main, a main function without a branch at the end, or a main
                // function with a discard statement at the end. In these cases we need to add a return
                // statement.
                bool needReturnStatement =
                    node->getSequence()->empty() || !node->getSequence()->back()->getAsBranchNode() ||
                    node->getSequence()->back()->getAsBranchNode()->getFlowOp() != EOpReturn;
                if (needReturnStatement)
                {
                    out << "return " << generateOutputCall() << ";\n";
                }
            }
            out << "}\n";
        }
    
        return false;
    }
    
    bool OutputHLSL::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        ASSERT(mCurrentFunctionMetadata == nullptr);
    
        size_t index = mCallDag.findIndex(node->getFunction()->uniqueId());
        ASSERT(index != CallDAG::InvalidIndex);
        mCurrentFunctionMetadata = &mASTMetadataList[index];
    
        const TFunction *func = node->getFunction();
    
        if (func->isMain())
        {
            // The stub strings below are replaced when shader is dynamically defined by its layout:
            switch (mShaderType)
            {
                case GL_VERTEX_SHADER:
                    out << "@@ VERTEX ATTRIBUTES @@\n\n"
                        << "@@ VERTEX OUTPUT @@\n\n"
                        << "VS_OUTPUT main(VS_INPUT input)";
                    break;
                case GL_FRAGMENT_SHADER:
                    out << "@@ PIXEL OUTPUT @@\n\n"
                        << "PS_OUTPUT main(@@ PIXEL MAIN PARAMETERS @@)";
                    break;
                case GL_COMPUTE_SHADER:
                    out << "[numthreads(" << mWorkGroupSize[0] << ", " << mWorkGroupSize[1] << ", "
                        << mWorkGroupSize[2] << ")]\n";
                    out << "void main(CS_INPUT input)";
                    break;
                default:
                    UNREACHABLE();
                    break;
            }
        }
        else
        {
            out << TypeString(node->getFunctionPrototype()->getType()) << " ";
            out << DecorateFunctionIfNeeded(func) << DisambiguateFunctionName(func)
                << (mOutputLod0Function ? "Lod0(" : "(");
    
            size_t paramCount = func->getParamCount();
            for (unsigned int i = 0; i < paramCount; i++)
            {
                const TVariable *param = func->getParam(i);
                ensureStructDefined(param->getType());
    
                writeParameter(param, out);
    
                if (i < paramCount - 1)
                {
                    out << ", ";
                }
            }
    
            out << ")\n";
        }
    
        mInsideFunction = true;
        if (func->isMain())
        {
            mInsideMain = true;
        }
        // The function body node will output braces.
        node->getBody()->traverse(this);
        mInsideFunction = false;
        mInsideMain     = false;
    
        mCurrentFunctionMetadata = nullptr;
    
        bool needsLod0 = mASTMetadataList[index].mNeedsLod0;
        if (needsLod0 && !mOutputLod0Function && mShaderType == GL_FRAGMENT_SHADER)
        {
            ASSERT(!node->getFunction()->isMain());
            mOutputLod0Function = true;
            node->traverse(this);
            mOutputLod0Function = false;
        }
    
        return false;
    }
    
    bool OutputHLSL::visitDeclaration(Visit visit, TIntermDeclaration *node)
    {
        if (visit == PreVisit)
        {
            TIntermSequence *sequence = node->getSequence();
            TIntermTyped *declarator  = (*sequence)[0]->getAsTyped();
            ASSERT(sequence->size() == 1);
            ASSERT(declarator);
    
            if (IsDeclarationWrittenOut(node))
            {
                TInfoSinkBase &out = getInfoSink();
                ensureStructDefined(declarator->getType());
    
                if (!declarator->getAsSymbolNode() ||
                    declarator->getAsSymbolNode()->variable().symbolType() !=
                        SymbolType::Empty)  // Variable declaration
                {
                    if (declarator->getQualifier() == EvqShared)
                    {
                        out << "groupshared ";
                    }
                    else if (!mInsideFunction)
                    {
                        out << "static ";
                    }
    
                    out << TypeString(declarator->getType()) + " ";
    
                    TIntermSymbol *symbol = declarator->getAsSymbolNode();
    
                    if (symbol)
                    {
                        symbol->traverse(this);
                        out << ArrayString(symbol->getType());
                        // Temporarily disable shadred memory initialization. It is very slow for D3D11
                        // drivers to compile a compute shader if we add code to initialize a
                        // groupshared array variable with a large array size. And maybe produce
                        // incorrect result. See http://anglebug.com/3226.
                        if (declarator->getQualifier() != EvqShared)
                        {
                            out << " = " + zeroInitializer(symbol->getType());
                        }
                    }
                    else
                    {
                        declarator->traverse(this);
                    }
                }
            }
            else if (IsVaryingOut(declarator->getQualifier()))
            {
                TIntermSymbol *symbol = declarator->getAsSymbolNode();
                ASSERT(symbol);  // Varying declarations can't have initializers.
    
                const TVariable &variable = symbol->variable();
    
                if (variable.symbolType() != SymbolType::Empty)
                {
                    // Vertex outputs which are declared but not written to should still be declared to
                    // allow successful linking.
                    mReferencedVaryings[symbol->uniqueId().get()] = &variable;
                }
            }
        }
        return false;
    }
    
    bool OutputHLSL::visitInvariantDeclaration(Visit visit, TIntermInvariantDeclaration *node)
    {
        // Do not do any translation
        return false;
    }
    
    void OutputHLSL::visitFunctionPrototype(TIntermFunctionPrototype *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        size_t index = mCallDag.findIndex(node->getFunction()->uniqueId());
        // Skip the prototype if it is not implemented (and thus not used)
        if (index == CallDAG::InvalidIndex)
        {
            return;
        }
    
        const TFunction *func = node->getFunction();
    
        TString name = DecorateFunctionIfNeeded(func);
        out << TypeString(node->getType()) << " " << name << DisambiguateFunctionName(func)
            << (mOutputLod0Function ? "Lod0(" : "(");
    
        size_t paramCount = func->getParamCount();
        for (unsigned int i = 0; i < paramCount; i++)
        {
            writeParameter(func->getParam(i), out);
    
            if (i < paramCount - 1)
            {
                out << ", ";
            }
        }
    
        out << ");\n";
    
        // Also prototype the Lod0 variant if needed
        bool needsLod0 = mASTMetadataList[index].mNeedsLod0;
        if (needsLod0 && !mOutputLod0Function && mShaderType == GL_FRAGMENT_SHADER)
        {
            mOutputLod0Function = true;
            node->traverse(this);
            mOutputLod0Function = false;
        }
    }
    
    bool OutputHLSL::visitAggregate(Visit visit, TIntermAggregate *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        switch (node->getOp())
        {
            case EOpCallBuiltInFunction:
            case EOpCallFunctionInAST:
            case EOpCallInternalRawFunction:
            {
                TIntermSequence *arguments = node->getSequence();
    
                bool lod0 = (mInsideDiscontinuousLoop || mOutputLod0Function) &&
                            mShaderType == GL_FRAGMENT_SHADER;
                if (node->getOp() == EOpCallFunctionInAST)
                {
                    if (node->isArray())
                    {
                        UNIMPLEMENTED();
                    }
                    size_t index = mCallDag.findIndex(node->getFunction()->uniqueId());
                    ASSERT(index != CallDAG::InvalidIndex);
                    lod0 &= mASTMetadataList[index].mNeedsLod0;
    
                    out << DecorateFunctionIfNeeded(node->getFunction());
                    out << DisambiguateFunctionName(node->getSequence());
                    out << (lod0 ? "Lod0(" : "(");
                }
                else if (node->getOp() == EOpCallInternalRawFunction)
                {
                    // This path is used for internal functions that don't have their definitions in the
                    // AST, such as precision emulation functions.
                    out << DecorateFunctionIfNeeded(node->getFunction()) << "(";
                }
                else if (node->getFunction()->isImageFunction())
                {
                    const ImmutableString &name              = node->getFunction()->name();
                    TType type                               = (*arguments)[0]->getAsTyped()->getType();
                    const ImmutableString &imageFunctionName = mImageFunctionHLSL->useImageFunction(
                        name, type.getBasicType(), type.getLayoutQualifier().imageInternalFormat,
                        type.getMemoryQualifier().readonly);
                    out << imageFunctionName << "(";
                }
                else if (node->getFunction()->isAtomicCounterFunction())
                {
                    const ImmutableString &name = node->getFunction()->name();
                    ImmutableString atomicFunctionName =
                        mAtomicCounterFunctionHLSL->useAtomicCounterFunction(name);
                    out << atomicFunctionName << "(";
                }
                else
                {
                    const ImmutableString &name = node->getFunction()->name();
                    TBasicType samplerType = (*arguments)[0]->getAsTyped()->getType().getBasicType();
                    int coords = 0;  // textureSize(gsampler2DMS) doesn't have a second argument.
                    if (arguments->size() > 1)
                    {
                        coords = (*arguments)[1]->getAsTyped()->getNominalSize();
                    }
                    const ImmutableString &textureFunctionName =
                        mTextureFunctionHLSL->useTextureFunction(name, samplerType, coords,
                                                                 arguments->size(), lod0, mShaderType);
                    out << textureFunctionName << "(";
                }
    
                for (TIntermSequence::iterator arg = arguments->begin(); arg != arguments->end(); arg++)
                {
                    TIntermTyped *typedArg = (*arg)->getAsTyped();
                    if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT && IsSampler(typedArg->getBasicType()))
                    {
                        out << "texture_";
                        (*arg)->traverse(this);
                        out << ", sampler_";
                    }
    
                    (*arg)->traverse(this);
    
                    if (typedArg->getType().isStructureContainingSamplers())
                    {
                        const TType &argType = typedArg->getType();
                        TVector<const TVariable *> samplerSymbols;
                        ImmutableString structName = samplerNamePrefixFromStruct(typedArg);
                        std::string namePrefix     = "angle_";
                        namePrefix += structName.data();
                        argType.createSamplerSymbols(ImmutableString(namePrefix), "", &samplerSymbols,
                                                     nullptr, mSymbolTable);
                        for (const TVariable *sampler : samplerSymbols)
                        {
                            if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
                            {
                                out << ", texture_" << sampler->name();
                                out << ", sampler_" << sampler->name();
                            }
                            else
                            {
                                // In case of HLSL 4.1+, this symbol is the sampler index, and in case
                                // of D3D9, it's the sampler variable.
                                out << ", " << sampler->name();
                            }
                        }
                    }
    
                    if (arg < arguments->end() - 1)
                    {
                        out << ", ";
                    }
                }
    
                out << ")";
    
                return false;
            }
            case EOpConstruct:
                outputConstructor(out, visit, node);
                break;
            case EOpEqualComponentWise:
                outputTriplet(out, visit, "(", " == ", ")");
                break;
            case EOpNotEqualComponentWise:
                outputTriplet(out, visit, "(", " != ", ")");
                break;
            case EOpLessThanComponentWise:
                outputTriplet(out, visit, "(", " < ", ")");
                break;
            case EOpGreaterThanComponentWise:
                outputTriplet(out, visit, "(", " > ", ")");
                break;
            case EOpLessThanEqualComponentWise:
                outputTriplet(out, visit, "(", " <= ", ")");
                break;
            case EOpGreaterThanEqualComponentWise:
                outputTriplet(out, visit, "(", " >= ", ")");
                break;
            case EOpMod:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpModf:
                outputTriplet(out, visit, "modf(", ", ", ")");
                break;
            case EOpPow:
                outputTriplet(out, visit, "pow(", ", ", ")");
                break;
            case EOpAtan:
                ASSERT(node->getSequence()->size() == 2);  // atan(x) is a unary operator
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpMin:
                outputTriplet(out, visit, "min(", ", ", ")");
                break;
            case EOpMax:
                outputTriplet(out, visit, "max(", ", ", ")");
                break;
            case EOpClamp:
                outputTriplet(out, visit, "clamp(", ", ", ")");
                break;
            case EOpMix:
            {
                TIntermTyped *lastParamNode = (*(node->getSequence()))[2]->getAsTyped();
                if (lastParamNode->getType().getBasicType() == EbtBool)
                {
                    // There is no HLSL equivalent for ESSL3 built-in "genType mix (genType x, genType
                    // y, genBType a)",
                    // so use emulated version.
                    ASSERT(node->getUseEmulatedFunction());
                    writeEmulatedFunctionTriplet(out, visit, node->getOp());
                }
                else
                {
                    outputTriplet(out, visit, "lerp(", ", ", ")");
                }
                break;
            }
            case EOpStep:
                outputTriplet(out, visit, "step(", ", ", ")");
                break;
            case EOpSmoothstep:
                outputTriplet(out, visit, "smoothstep(", ", ", ")");
                break;
            case EOpFrexp:
            case EOpLdexp:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpDistance:
                outputTriplet(out, visit, "distance(", ", ", ")");
                break;
            case EOpDot:
                outputTriplet(out, visit, "dot(", ", ", ")");
                break;
            case EOpCross:
                outputTriplet(out, visit, "cross(", ", ", ")");
                break;
            case EOpFaceforward:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpReflect:
                outputTriplet(out, visit, "reflect(", ", ", ")");
                break;
            case EOpRefract:
                outputTriplet(out, visit, "refract(", ", ", ")");
                break;
            case EOpOuterProduct:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpMulMatrixComponentWise:
                outputTriplet(out, visit, "(", " * ", ")");
                break;
            case EOpBitfieldExtract:
            case EOpBitfieldInsert:
            case EOpUaddCarry:
            case EOpUsubBorrow:
            case EOpUmulExtended:
            case EOpImulExtended:
                ASSERT(node->getUseEmulatedFunction());
                writeEmulatedFunctionTriplet(out, visit, node->getOp());
                break;
            case EOpBarrier:
                // barrier() is translated to GroupMemoryBarrierWithGroupSync(), which is the
                // cheapest *WithGroupSync() function, without any functionality loss, but
                // with the potential for severe performance loss.
                outputTriplet(out, visit, "GroupMemoryBarrierWithGroupSync(", "", ")");
                break;
            case EOpMemoryBarrierShared:
                outputTriplet(out, visit, "GroupMemoryBarrier(", "", ")");
                break;
            case EOpMemoryBarrierAtomicCounter:
            case EOpMemoryBarrierBuffer:
            case EOpMemoryBarrierImage:
                outputTriplet(out, visit, "DeviceMemoryBarrier(", "", ")");
                break;
            case EOpGroupMemoryBarrier:
            case EOpMemoryBarrier:
                outputTriplet(out, visit, "AllMemoryBarrier(", "", ")");
                break;
    
            // Single atomic function calls without return value.
            // e.g. atomicAdd(dest, value) should be translated into InterlockedAdd(dest, value).
            case EOpAtomicAdd:
            case EOpAtomicMin:
            case EOpAtomicMax:
            case EOpAtomicAnd:
            case EOpAtomicOr:
            case EOpAtomicXor:
            // The parameter 'original_value' of InterlockedExchange(dest, value, original_value)
            // and InterlockedCompareExchange(dest, compare_value, value, original_value) is not
            // optional.
            // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/interlockedexchange
            // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/interlockedcompareexchange
            // So all the call of atomicExchange(dest, value) and atomicCompSwap(dest,
            // compare_value, value) should all be modified into the form of "int temp; temp =
            // atomicExchange(dest, value);" and "int temp; temp = atomicCompSwap(dest,
            // compare_value, value);" in the intermediate tree before traversing outputHLSL.
            case EOpAtomicExchange:
            case EOpAtomicCompSwap:
            {
                ASSERT(node->getChildCount() > 1);
                TIntermTyped *memNode = (*node->getSequence())[0]->getAsTyped();
                if (IsInShaderStorageBlock(memNode))
                {
                    // Atomic memory functions for SSBO.
                    // "_ssbo_atomicXXX_TYPE(RWByteAddressBuffer buffer, uint loc" is written to |out|.
                    mSSBOOutputHLSL->outputAtomicMemoryFunctionCallPrefix(memNode, node->getOp());
                    // Write the rest argument list to |out|.
                    for (size_t i = 1; i < node->getChildCount(); i++)
                    {
                        out << ", ";
                        TIntermTyped *argument = (*node->getSequence())[i]->getAsTyped();
                        if (IsInShaderStorageBlock(argument))
                        {
                            mSSBOOutputHLSL->outputLoadFunctionCall(argument);
                        }
                        else
                        {
                            argument->traverse(this);
                        }
                    }
    
                    out << ")";
                    return false;
                }
                else
                {
                    // Atomic memory functions for shared variable.
                    if (node->getOp() != EOpAtomicExchange && node->getOp() != EOpAtomicCompSwap)
                    {
                        outputTriplet(out, visit,
                                      GetHLSLAtomicFunctionStringAndLeftParenthesis(node->getOp()), ",",
                                      ")");
                    }
                    else
                    {
                        UNREACHABLE();
                    }
                }
    
                break;
            }
            default:
                UNREACHABLE();
        }
    
        return true;
    }
    
    void OutputHLSL::writeIfElse(TInfoSinkBase &out, TIntermIfElse *node)
    {
        out << "if (";
    
        node->getCondition()->traverse(this);
    
        out << ")\n";
    
        outputLineDirective(out, node->getLine().first_line);
    
        bool discard = false;
    
        if (node->getTrueBlock())
        {
            // The trueBlock child node will output braces.
            node->getTrueBlock()->traverse(this);
    
            // Detect true discard
            discard = (discard || FindDiscard::search(node->getTrueBlock()));
        }
        else
        {
            // TODO(oetuaho): Check if the semicolon inside is necessary.
            // It's there as a result of conservative refactoring of the output.
            out << "{;}\n";
        }
    
        outputLineDirective(out, node->getLine().first_line);
    
        if (node->getFalseBlock())
        {
            out << "else\n";
    
            outputLineDirective(out, node->getFalseBlock()->getLine().first_line);
    
            // The falseBlock child node will output braces.
            node->getFalseBlock()->traverse(this);
    
            outputLineDirective(out, node->getFalseBlock()->getLine().first_line);
    
            // Detect false discard
            discard = (discard || FindDiscard::search(node->getFalseBlock()));
        }
    
        // ANGLE issue 486: Detect problematic conditional discard
        if (discard)
        {
            mUsesDiscardRewriting = true;
        }
    }
    
    bool OutputHLSL::visitTernary(Visit, TIntermTernary *)
    {
        // Ternary ops should have been already converted to something else in the AST. HLSL ternary
        // operator doesn't short-circuit, so it's not the same as the GLSL ternary operator.
        UNREACHABLE();
        return false;
    }
    
    bool OutputHLSL::visitIfElse(Visit visit, TIntermIfElse *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        ASSERT(mInsideFunction);
    
        // D3D errors when there is a gradient operation in a loop in an unflattened if.
        if (mShaderType == GL_FRAGMENT_SHADER && mCurrentFunctionMetadata->hasGradientLoop(node))
        {
            out << "FLATTEN ";
        }
    
        writeIfElse(out, node);
    
        return false;
    }
    
    bool OutputHLSL::visitSwitch(Visit visit, TIntermSwitch *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        ASSERT(node->getStatementList());
        if (visit == PreVisit)
        {
            node->setStatementList(RemoveSwitchFallThrough(node->getStatementList(), mPerfDiagnostics));
        }
        outputTriplet(out, visit, "switch (", ") ", "");
        // The curly braces get written when visiting the statementList block.
        return true;
    }
    
    bool OutputHLSL::visitCase(Visit visit, TIntermCase *node)
    {
        TInfoSinkBase &out = getInfoSink();
    
        if (node->hasCondition())
        {
            outputTriplet(out, visit, "case (", "", "):\n");
            return true;
        }
        else
        {
            out << "default:\n";
            return false;
        }
    }
    
    void OutputHLSL::visitConstantUnion(TIntermConstantUnion *node)
    {
        TInfoSinkBase &out = getInfoSink();
        writeConstantUnion(out, node->getType(), node->getConstantValue());
    }
    
    bool OutputHLSL::visitLoop(Visit visit, TIntermLoop *node)
    {
        mNestedLoopDepth++;
    
        bool wasDiscontinuous = mInsideDiscontinuousLoop;
        mInsideDiscontinuousLoop =
            mInsideDiscontinuousLoop || mCurrentFunctionMetadata->mDiscontinuousLoops.count(node) > 0;
    
        TInfoSinkBase &out = getInfoSink();
    
        if (mOutputType == SH_HLSL_3_0_OUTPUT)
        {
            if (handleExcessiveLoop(out, node))
            {
                mInsideDiscontinuousLoop = wasDiscontinuous;
                mNestedLoopDepth--;
    
                return false;
            }
        }
    
        const char *unroll = mCurrentFunctionMetadata->hasGradientInCallGraph(node) ? "LOOP" : "";
        if (node->getType() == ELoopDoWhile)
        {
            out << "{" << unroll << " do\n";
    
            outputLineDirective(out, node->getLine().first_line);
        }
        else
        {
            out << "{" << unroll << " 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";
    
            outputLineDirective(out, node->getLine().first_line);
        }
    
        if (node->getBody())
        {
            // The loop body node will output braces.
            node->getBody()->traverse(this);
        }
        else
        {
            // TODO(oetuaho): Check if the semicolon inside is necessary.
            // It's there as a result of conservative refactoring of the output.
            out << "{;}\n";
        }
    
        outputLineDirective(out, node->getLine().first_line);
    
        if (node->getType() == ELoopDoWhile)
        {
            outputLineDirective(out, node->getCondition()->getLine().first_line);
            out << "while (";
    
            node->getCondition()->traverse(this);
    
            out << ");\n";
        }
    
        out << "}\n";
    
        mInsideDiscontinuousLoop = wasDiscontinuous;
        mNestedLoopDepth--;
    
        return false;
    }
    
    bool OutputHLSL::visitBranch(Visit visit, TIntermBranch *node)
    {
        if (visit == PreVisit)
        {
            TInfoSinkBase &out = getInfoSink();
    
            switch (node->getFlowOp())
            {
                case EOpKill:
                    out << "discard";
                    break;
                case EOpBreak:
                    if (mNestedLoopDepth > 1)
                    {
                        mUsesNestedBreak = true;
                    }
    
                    if (mExcessiveLoopIndex)
                    {
                        out << "{Break";
                        mExcessiveLoopIndex->traverse(this);
                        out << " = true; break;}\n";
                    }
                    else
                    {
                        out << "break";
                    }
                    break;
                case EOpContinue:
                    out << "continue";
                    break;
                case EOpReturn:
                    if (node->getExpression())
                    {
                        ASSERT(!mInsideMain);
                        out << "return ";
                    }
                    else
                    {
                        if (mInsideMain && shaderNeedsGenerateOutput())
                        {
                            out << "return " << generateOutputCall();
                        }
                        else
                        {
                            out << "return";
                        }
                    }
                    break;
                default:
                    UNREACHABLE();
            }
        }
    
        return true;
    }
    
    // Handle loops with more than 254 iterations (unsupported by D3D9) by splitting them
    // (The D3D documentation says 255 iterations, but the compiler complains at anything more than
    // 254).
    bool OutputHLSL::handleExcessiveLoop(TInfoSinkBase &out, TIntermLoop *node)
    {
        const int MAX_LOOP_ITERATIONS = 254;
    
        // Parse loops of the form:
        // for(int index = initial; index [comparator] limit; index += increment)
        TIntermSymbol *index = nullptr;
        TOperator comparator = EOpNull;
        int initial          = 0;
        int limit            = 0;
        int increment        = 0;
    
        // Parse index name and intial value
        if (node->getInit())
        {
            TIntermDeclaration *init = node->getInit()->getAsDeclarationNode();
    
            if (init)
            {
                TIntermSequence *sequence = init->getSequence();
                TIntermTyped *variable    = (*sequence)[0]->getAsTyped();
    
                if (variable && variable->getQualifier() == EvqTemporary)
                {
                    TIntermBinary *assign = variable->getAsBinaryNode();
    
                    if (assign->getOp() == EOpInitialize)
                    {
                        TIntermSymbol *symbol          = assign->getLeft()->getAsSymbolNode();
                        TIntermConstantUnion *constant = assign->getRight()->getAsConstantUnion();
    
                        if (symbol && constant)
                        {
                            if (constant->getBasicType() == EbtInt && constant->isScalar())
                            {
                                index   = symbol;
                                initial = constant->getIConst(0);
                            }
                        }
                    }
                }
            }
        }
    
        // Parse comparator and limit value
        if (index != nullptr && node->getCondition())
        {
            TIntermBinary *test = node->getCondition()->getAsBinaryNode();
    
            if (test && test->getLeft()->getAsSymbolNode()->uniqueId() == index->uniqueId())
            {
                TIntermConstantUnion *constant = test->getRight()->getAsConstantUnion();
    
                if (constant)
                {
                    if (constant->getBasicType() == EbtInt && constant->isScalar())
                    {
                        comparator = test->getOp();
                        limit      = constant->getIConst(0);
                    }
                }
            }
        }
    
        // Parse increment
        if (index != nullptr && comparator != EOpNull && node->getExpression())
        {
            TIntermBinary *binaryTerminal = node->getExpression()->getAsBinaryNode();
            TIntermUnary *unaryTerminal   = node->getExpression()->getAsUnaryNode();
    
            if (binaryTerminal)
            {
                TOperator op                   = binaryTerminal->getOp();
                TIntermConstantUnion *constant = binaryTerminal->getRight()->getAsConstantUnion();
    
                if (constant)
                {
                    if (constant->getBasicType() == EbtInt && constant->isScalar())
                    {
                        int value = constant->getIConst(0);
    
                        switch (op)
                        {
                            case EOpAddAssign:
                                increment = value;
                                break;
                            case EOpSubAssign:
                                increment = -value;
                                break;
                            default:
                                UNIMPLEMENTED();
                        }
                    }
                }
            }
            else if (unaryTerminal)
            {
                TOperator op = unaryTerminal->getOp();
    
                switch (op)
                {
                    case EOpPostIncrement:
                        increment = 1;
                        break;
                    case EOpPostDecrement:
                        increment = -1;
                        break;
                    case EOpPreIncrement:
                        increment = 1;
                        break;
                    case EOpPreDecrement:
                        increment = -1;
                        break;
                    default:
                        UNIMPLEMENTED();
                }
            }
        }
    
        if (index != nullptr && comparator != EOpNull && increment != 0)
        {
            if (comparator == EOpLessThanEqual)
            {
                comparator = EOpLessThan;
                limit += 1;
            }
    
            if (comparator == EOpLessThan)
            {
                int iterations = (limit - initial) / increment;
    
                if (iterations <= MAX_LOOP_ITERATIONS)
                {
                    return false;  // Not an excessive loop
                }
    
                TIntermSymbol *restoreIndex = mExcessiveLoopIndex;
                mExcessiveLoopIndex         = index;
    
                out << "{int ";
                index->traverse(this);
                out << ";\n"
                       "bool Break";
                index->traverse(this);
                out << " = false;\n";
    
                bool firstLoopFragment = true;
    
                while (iterations > 0)
                {
                    int clampedLimit = initial + increment * std::min(MAX_LOOP_ITERATIONS, iterations);
    
                    if (!firstLoopFragment)
                    {
                        out << "if (!Break";
                        index->traverse(this);
                        out << ") {\n";
                    }
    
                    if (iterations <= MAX_LOOP_ITERATIONS)  // Last loop fragment
                    {
                        mExcessiveLoopIndex = nullptr;  // Stops setting the Break flag
                    }
    
                    // for(int index = initial; index < clampedLimit; index += increment)
                    const char *unroll =
                        mCurrentFunctionMetadata->hasGradientInCallGraph(node) ? "LOOP" : "";
    
                    out << unroll << " for(";
                    index->traverse(this);
                    out << " = ";
                    out << initial;
    
                    out << "; ";
                    index->traverse(this);
                    out << " < ";
                    out << clampedLimit;
    
                    out << "; ";
                    index->traverse(this);
                    out << " += ";
                    out << increment;
                    out << ")\n";
    
                    outputLineDirective(out, node->getLine().first_line);
                    out << "{\n";
    
                    if (node->getBody())
                    {
                        node->getBody()->traverse(this);
                    }
    
                    outputLineDirective(out, node->getLine().first_line);
                    out << ";}\n";
    
                    if (!firstLoopFragment)
                    {
                        out << "}\n";
                    }
    
                    firstLoopFragment = false;
    
                    initial += MAX_LOOP_ITERATIONS * increment;
                    iterations -= MAX_LOOP_ITERATIONS;
                }
    
                out << "}";
    
                mExcessiveLoopIndex = restoreIndex;
    
                return true;
            }
            else
                UNIMPLEMENTED();
        }
    
        return false;  // Not handled as an excessive loop
    }
    
    void OutputHLSL::outputTriplet(TInfoSinkBase &out,
                                   Visit visit,
                                   const char *preString,
                                   const char *inString,
                                   const char *postString)
    {
        if (visit == PreVisit)
        {
            out << preString;
        }
        else if (visit == InVisit)
        {
            out << inString;
        }
        else if (visit == PostVisit)
        {
            out << postString;
        }
    }
    
    void OutputHLSL::outputLineDirective(TInfoSinkBase &out, int line)
    {
        if ((mCompileOptions & SH_LINE_DIRECTIVES) && (line > 0))
        {
            out << "\n";
            out << "#line " << line;
    
            if (mSourcePath)
            {
                out << " \"" << mSourcePath << "\"";
            }
    
            out << "\n";
        }
    }
    
    void OutputHLSL::writeParameter(const TVariable *param, TInfoSinkBase &out)
    {
        const TType &type    = param->getType();
        TQualifier qualifier = type.getQualifier();
    
        TString nameStr = DecorateVariableIfNeeded(*param);
        ASSERT(nameStr != "");  // HLSL demands named arguments, also for prototypes
    
        if (IsSampler(type.getBasicType()))
        {
            if (mOutputType == SH_HLSL_4_1_OUTPUT)
            {
                // Samplers are passed as indices to the sampler array.
                ASSERT(qualifier != EvqOut && qualifier != EvqInOut);
                out << "const uint " << nameStr << ArrayString(type);
                return;
            }
            if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
            {
                out << QualifierString(qualifier) << " " << TextureString(type.getBasicType())
                    << " texture_" << nameStr << ArrayString(type) << ", " << QualifierString(qualifier)
                    << " " << SamplerString(type.getBasicType()) << " sampler_" << nameStr
                    << ArrayString(type);
                return;
            }
        }
    
        // If the parameter is an atomic counter, we need to add an extra parameter to keep track of the
        // buffer offset.
        if (IsAtomicCounter(type.getBasicType()))
        {
            out << QualifierString(qualifier) << " " << TypeString(type) << " " << nameStr << ", int "
                << nameStr << "_offset";
        }
        else
        {
            out << QualifierString(qualifier) << " " << TypeString(type) << " " << nameStr
                << ArrayString(type);
        }
    
        // If the structure parameter contains samplers, they need to be passed into the function as
        // separate parameters. HLSL doesn't natively support samplers in structs.
        if (type.isStructureContainingSamplers())
        {
            ASSERT(qualifier != EvqOut && qualifier != EvqInOut);
            TVector<const TVariable *> samplerSymbols;
            std::string namePrefix = "angle";
            namePrefix += nameStr.c_str();
            type.createSamplerSymbols(ImmutableString(namePrefix), "", &samplerSymbols, nullptr,
                                      mSymbolTable);
            for (const TVariable *sampler : samplerSymbols)
            {
                const TType &samplerType = sampler->getType();
                if (mOutputType == SH_HLSL_4_1_OUTPUT)
                {
                    out << ", const uint " << sampler->name() << ArrayString(samplerType);
                }
                else if (mOutputType == SH_HLSL_4_0_FL9_3_OUTPUT)
                {
                    ASSERT(IsSampler(samplerType.getBasicType()));
                    out << ", " << QualifierString(qualifier) << " "
                        << TextureString(samplerType.getBasicType()) << " texture_" << sampler->name()
                        << ArrayString(samplerType) << ", " << QualifierString(qualifier) << " "
                        << SamplerString(samplerType.getBasicType()) << " sampler_" << sampler->name()
                        << ArrayString(samplerType);
                }
                else
                {
                    ASSERT(IsSampler(samplerType.getBasicType()));
                    out << ", " << QualifierString(qualifier) << " " << TypeString(samplerType) << " "
                        << sampler->name() << ArrayString(samplerType);
                }
            }
        }
    }
    
    TString OutputHLSL::zeroInitializer(const TType &type) const
    {
        TString string;
    
        size_t size = type.getObjectSize();
        if (size >= kZeroCount)
        {
            mUseZeroArray = true;
        }
        string = GetZeroInitializer(size).c_str();
    
        return "{" + string + "}";
    }
    
    void OutputHLSL::outputConstructor(TInfoSinkBase &out, Visit visit, TIntermAggregate *node)
    {
        // Array constructors should have been already pruned from the code.
        ASSERT(!node->getType().isArray());
    
        if (visit == PreVisit)
        {
            TString constructorName;
            if (node->getBasicType() == EbtStruct)
            {
                constructorName = mStructureHLSL->addStructConstructor(*node->getType().getStruct());
            }
            else
            {
                constructorName =
                    mStructureHLSL->addBuiltInConstructor(node->getType(), node->getSequence());
            }
            out << constructorName << "(";
        }
        else if (visit == InVisit)
        {
            out << ", ";
        }
        else if (visit == PostVisit)
        {
            out << ")";
        }
    }
    
    const TConstantUnion *OutputHLSL::writeConstantUnion(TInfoSinkBase &out,
                                                         const TType &type,
                                                         const TConstantUnion *const constUnion)
    {
        ASSERT(!type.isArray());
    
        const TConstantUnion *constUnionIterated = constUnion;
    
        const TStructure *structure = type.getStruct();
        if (structure)
        {
            out << mStructureHLSL->addStructConstructor(*structure) << "(";
    
            const TFieldList &fields = structure->fields();
    
            for (size_t i = 0; i < fields.size(); i++)
            {
                const TType *fieldType = fields[i]->type();
                constUnionIterated     = writeConstantUnion(out, *fieldType, constUnionIterated);
    
                if (i != fields.size() - 1)
                {
                    out << ", ";
                }
            }
    
            out << ")";
        }
        else
        {
            size_t size    = type.getObjectSize();
            bool writeType = size > 1;
    
            if (writeType)
            {
                out << TypeString(type) << "(";
            }
            constUnionIterated = writeConstantUnionArray(out, constUnionIterated, size);
            if (writeType)
            {
                out << ")";
            }
        }
    
        return constUnionIterated;
    }
    
    void OutputHLSL::writeEmulatedFunctionTriplet(TInfoSinkBase &out, Visit visit, TOperator op)
    {
        if (visit == PreVisit)
        {
            const char *opStr = GetOperatorString(op);
            BuiltInFunctionEmulator::WriteEmulatedFunctionName(out, opStr);
            out << "(";
        }
        else
        {
            outputTriplet(out, visit, nullptr, ", ", ")");
        }
    }
    
    bool OutputHLSL::writeSameSymbolInitializer(TInfoSinkBase &out,
                                                TIntermSymbol *symbolNode,
                                                TIntermTyped *expression)
    {
        ASSERT(symbolNode->variable().symbolType() != SymbolType::Empty);
        const TIntermSymbol *symbolInInitializer = FindSymbolNode(expression, symbolNode->getName());
    
        if (symbolInInitializer)
        {
            // Type already printed
            out << "t" + str(mUniqueIndex) + " = ";
            expression->traverse(this);
            out << ", ";
            symbolNode->traverse(this);
            out << " = t" + str(mUniqueIndex);
    
            mUniqueIndex++;
            return true;
        }
    
        return false;
    }
    
    bool OutputHLSL::writeConstantInitialization(TInfoSinkBase &out,
                                                 TIntermSymbol *symbolNode,
                                                 TIntermTyped *initializer)
    {
        if (initializer->hasConstantValue())
        {
            symbolNode->traverse(this);
            out << ArrayString(symbolNode->getType());
            out << " = {";
            writeConstantUnionArray(out, initializer->getConstantValue(),
                                    initializer->getType().getObjectSize());
            out << "}";
            return true;
        }
        return false;
    }
    
    TString OutputHLSL::addStructEqualityFunction(const TStructure &structure)
    {
        const TFieldList &fields = structure.fields();
    
        for (const auto &eqFunction : mStructEqualityFunctions)
        {
            if (eqFunction->structure == &structure)
            {
                return eqFunction->functionName;
            }
        }
    
        const TString &structNameString = StructNameString(structure);
    
        StructEqualityFunction *function = new StructEqualityFunction();
        function->structure              = &structure;
        function->functionName           = "angle_eq_" + structNameString;
    
        TInfoSinkBase fnOut;
    
        fnOut << "bool " << function->functionName << "(" << structNameString << " a, "
              << structNameString + " b)\n"
              << "{\n"
                 "    return ";
    
        for (size_t i = 0; i < fields.size(); i++)
        {
            const TField *field    = fields[i];
            const TType *fieldType = field->type();
    
            const TString &fieldNameA = "a." + Decorate(field->name());
            const TString &fieldNameB = "b." + Decorate(field->name());
    
            if (i > 0)
            {
                fnOut << " && ";
            }
    
            fnOut << "(";
            outputEqual(PreVisit, *fieldType, EOpEqual, fnOut);
            fnOut << fieldNameA;
            outputEqual(InVisit, *fieldType, EOpEqual, fnOut);
            fnOut << fieldNameB;
            outputEqual(PostVisit, *fieldType, EOpEqual, fnOut);
            fnOut << ")";
        }
    
        fnOut << ";\n"
              << "}\n";
    
        function->functionDefinition = fnOut.c_str();
    
        mStructEqualityFunctions.push_back(function);
        mEqualityFunctions.push_back(function);
    
        return function->functionName;
    }
    
    TString OutputHLSL::addArrayEqualityFunction(const TType &type)
    {
        for (const auto &eqFunction : mArrayEqualityFunctions)
        {
            if (eqFunction->type == type)
            {
                return eqFunction->functionName;
            }
        }
    
        TType elementType(type);
        elementType.toArrayElementType();
    
        ArrayHelperFunction *function = new ArrayHelperFunction();
        function->type                = type;
    
        function->functionName = ArrayHelperFunctionName("angle_eq", type);
    
        TInfoSinkBase fnOut;
    
        const TString &typeName = TypeString(type);
        fnOut << "bool " << function->functionName << "(" << typeName << " a" << ArrayString(type)
              << ", " << typeName << " b" << ArrayString(type) << ")\n"
              << "{\n"
                 "    for (int i = 0; i < "
              << type.getOutermostArraySize()
              << "; ++i)\n"
                 "    {\n"
                 "        if (";
    
        outputEqual(PreVisit, elementType, EOpNotEqual, fnOut);
        fnOut << "a[i]";
        outputEqual(InVisit, elementType, EOpNotEqual, fnOut);
        fnOut << "b[i]";
        outputEqual(PostVisit, elementType, EOpNotEqual, fnOut);
    
        fnOut << ") { return false; }\n"
                 "    }\n"
                 "    return true;\n"
                 "}\n";
    
        function->functionDefinition = fnOut.c_str();
    
        mArrayEqualityFunctions.push_back(function);
        mEqualityFunctions.push_back(function);
    
        return function->functionName;
    }
    
    TString OutputHLSL::addArrayAssignmentFunction(const TType &type)
    {
        for (const auto &assignFunction : mArrayAssignmentFunctions)
        {
            if (assignFunction.type == type)
            {
                return assignFunction.functionName;
            }
        }
    
        TType elementType(type);
        elementType.toArrayElementType();
    
        ArrayHelperFunction function;
        function.type = type;
    
        function.functionName = ArrayHelperFunctionName("angle_assign", type);
    
        TInfoSinkBase fnOut;
    
        const TString &typeName = TypeString(type);
        fnOut << "void " << function.functionName << "(out " << typeName << " a" << ArrayString(type)
              << ", " << typeName << " b" << ArrayString(type) << ")\n"
              << "{\n"
                 "    for (int i = 0; i < "
              << type.getOutermostArraySize()
              << "; ++i)\n"
                 "    {\n"
                 "        ";
    
        outputAssign(PreVisit, elementType, fnOut);
        fnOut << "a[i]";
        outputAssign(InVisit, elementType, fnOut);
        fnOut << "b[i]";
        outputAssign(PostVisit, elementType, fnOut);
    
        fnOut << ";\n"
                 "    }\n"
                 "}\n";
    
        function.functionDefinition = fnOut.c_str();
    
        mArrayAssignmentFunctions.push_back(function);
    
        return function.functionName;
    }
    
    TString OutputHLSL::addArrayConstructIntoFunction(const TType &type)
    {
        for (const auto &constructIntoFunction : mArrayConstructIntoFunctions)
        {
            if (constructIntoFunction.type == type)
            {
                return constructIntoFunction.functionName;
            }
        }
    
        TType elementType(type);
        elementType.toArrayElementType();
    
        ArrayHelperFunction function;
        function.type = type;
    
        function.functionName = ArrayHelperFunctionName("angle_construct_into", type);
    
        TInfoSinkBase fnOut;
    
        const TString &typeName = TypeString(type);
        fnOut << "void " << function.functionName << "(out " << typeName << " a" << ArrayString(type);
        for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i)
        {
            fnOut << ", " << typeName << " b" << i << ArrayString(elementType);
        }
        fnOut << ")\n"
                 "{\n";
    
        for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i)
        {
            fnOut << "    ";
            outputAssign(PreVisit, elementType, fnOut);
            fnOut << "a[" << i << "]";
            outputAssign(InVisit, elementType, fnOut);
            fnOut << "b" << i;
            outputAssign(PostVisit, elementType, fnOut);
            fnOut << ";\n";
        }
        fnOut << "}\n";
    
        function.functionDefinition = fnOut.c_str();
    
        mArrayConstructIntoFunctions.push_back(function);
    
        return function.functionName;
    }
    
    void OutputHLSL::ensureStructDefined(const TType &type)
    {
        const TStructure *structure = type.getStruct();
        if (structure)
        {
            ASSERT(type.getBasicType() == EbtStruct);
            mStructureHLSL->ensureStructDefined(*structure);
        }
    }
    
    bool OutputHLSL::shaderNeedsGenerateOutput() const
    {
        return mShaderType == GL_VERTEX_SHADER || mShaderType == GL_FRAGMENT_SHADER;
    }
    
    const char *OutputHLSL::generateOutputCall() const
    {
        if (mShaderType == GL_VERTEX_SHADER)
        {
            return "generateOutput(input)";
        }
        else
        {
            return "generateOutput()";
        }
    }
    
    }  // namespace sh