//
// Copyright 2017 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.
//

// ProgramLinkedResources.cpp: implements link-time checks for default block uniforms, and generates
// uniform locations. Populates data structures related to uniforms so that they can be stored in
// program state.

#include "libANGLE/ProgramLinkedResources.h"

#include "common/string_utils.h"
#include "common/utilities.h"
#include "libANGLE/Caps.h"
#include "libANGLE/Context.h"
#include "libANGLE/Shader.h"
#include "libANGLE/features.h"

namespace gl
{
namespace
{
LinkedUniform *FindUniform(std::vector<LinkedUniform> &list, const std::string &name)
{
    for (LinkedUniform &uniform : list)
    {
        if (uniform.name == name)
            return &uniform;
    }

    return nullptr;
}

template <typename VarT>
void SetActive(std::vector<VarT> *list,
               const std::string &name,
               ShaderType shaderType,
               bool active,
               uint32_t id)
{
    for (auto &variable : *list)
    {
        if (variable.name == name)
        {
            variable.setActive(shaderType, active, id);
            return;
        }
    }
}

// GLSL ES Spec 3.00.3, section 4.3.5.
LinkMismatchError LinkValidateUniforms(const sh::ShaderVariable &uniform1,
                                       const sh::ShaderVariable &uniform2,
                                       std::string *mismatchedStructFieldName)
{
#if ANGLE_PROGRAM_LINK_VALIDATE_UNIFORM_PRECISION == ANGLE_ENABLED
    const bool validatePrecisionFeature = true;
#else
    const bool validatePrecisionFeature = false;
#endif

    // Validate precision match of uniforms iff they are statically used
    bool validatePrecision = uniform1.staticUse && uniform2.staticUse && validatePrecisionFeature;
    LinkMismatchError linkError = LinkValidateProgramVariables(
        uniform1, uniform2, validatePrecision, false, false, mismatchedStructFieldName);
    if (linkError != LinkMismatchError::NO_MISMATCH)
    {
        return linkError;
    }

    // GLSL ES Spec 3.10.4, section 4.4.5.
    if (uniform1.binding != -1 && uniform2.binding != -1 && uniform1.binding != uniform2.binding)
    {
        return LinkMismatchError::BINDING_MISMATCH;
    }

    // GLSL ES Spec 3.10.4, section 9.2.1.
    if (uniform1.location != -1 && uniform2.location != -1 &&
        uniform1.location != uniform2.location)
    {
        return LinkMismatchError::LOCATION_MISMATCH;
    }
    if (uniform1.offset != uniform2.offset)
    {
        return LinkMismatchError::OFFSET_MISMATCH;
    }

    return LinkMismatchError::NO_MISMATCH;
}

GLuint GetMaximumShaderUniformVectors(ShaderType shaderType, const Caps &caps)
{
    switch (shaderType)
    {
        case ShaderType::Vertex:
            return static_cast<GLuint>(caps.maxVertexUniformVectors);
        case ShaderType::Fragment:
            return static_cast<GLuint>(caps.maxFragmentUniformVectors);

        case ShaderType::Compute:
        case ShaderType::Geometry:
        case ShaderType::TessControl:
        case ShaderType::TessEvaluation:
            return static_cast<GLuint>(caps.maxShaderUniformComponents[shaderType]) / 4;

        default:
            UNREACHABLE();
            return 0u;
    }
}

enum class UniformType : uint8_t
{
    Variable      = 0,
    Sampler       = 1,
    Image         = 2,
    AtomicCounter = 3,

    InvalidEnum = 4,
    EnumCount   = 4,
};

const char *GetUniformResourceNameString(UniformType uniformType)
{
    switch (uniformType)
    {
        case UniformType::Variable:
            return "uniform";
        case UniformType::Sampler:
            return "texture image unit";
        case UniformType::Image:
            return "image uniform";
        case UniformType::AtomicCounter:
            return "atomic counter";
        default:
            UNREACHABLE();
            return "";
    }
}

std::string GetUniformResourceLimitName(ShaderType shaderType, UniformType uniformType)
{
    // Special case: MAX_TEXTURE_IMAGE_UNITS (no "MAX_FRAGMENT_TEXTURE_IMAGE_UNITS")
    if (shaderType == ShaderType::Fragment && uniformType == UniformType::Sampler)
    {
        return "MAX_TEXTURE_IMAGE_UNITS";
    }

    std::ostringstream ostream;
    ostream << "MAX_" << GetShaderTypeString(shaderType) << "_";

    switch (uniformType)
    {
        case UniformType::Variable:
            // For vertex and fragment shaders, ES 2.0 only defines MAX_VERTEX_UNIFORM_VECTORS and
            // MAX_FRAGMENT_UNIFORM_VECTORS ([OpenGL ES 2.0] Table 6.20).
            if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment)
            {
                ostream << "UNIFORM_VECTORS";
                break;
            }
            // For compute and geometry shaders, there are no definitions on
            // "MAX_COMPUTE_UNIFORM_VECTORS" or "MAX_GEOMETRY_UNIFORM_VECTORS_EXT"
            // ([OpenGL ES 3.1] Table 20.45, [EXT_geometry_shader] Table 20.43gs).
            else
            {
                ostream << "UNIFORM_COMPONENTS";
            }
            break;
        case UniformType::Sampler:
            ostream << "TEXTURE_IMAGE_UNITS";
            break;
        case UniformType::Image:
            ostream << "IMAGE_UNIFORMS";
            break;
        case UniformType::AtomicCounter:
            ostream << "ATOMIC_COUNTERS";
            break;
        default:
            UNREACHABLE();
            return "";
    }

    if (shaderType == ShaderType::Geometry)
    {
        ostream << "_EXT";
    }

    return ostream.str();
}

void LogUniformsExceedLimit(ShaderType shaderType,
                            UniformType uniformType,
                            GLuint limit,
                            InfoLog &infoLog)
{
    infoLog << GetShaderTypeString(shaderType) << " shader "
            << GetUniformResourceNameString(uniformType) << "s count exceeds "
            << GetUniformResourceLimitName(shaderType, uniformType) << "(" << limit << ")";
}

// The purpose of this visitor is to capture the uniforms in a uniform block. Each new uniform is
// added to "uniformsOut".
class UniformBlockEncodingVisitor : public sh::VariableNameVisitor
{
  public:
    UniformBlockEncodingVisitor(const GetBlockMemberInfoFunc &getMemberInfo,
                                const std::string &namePrefix,
                                const std::string &mappedNamePrefix,
                                std::vector<LinkedUniform> *uniformsOut,
                                ShaderType shaderType,
                                int blockIndex)
        : sh::VariableNameVisitor(namePrefix, mappedNamePrefix),
          mGetMemberInfo(getMemberInfo),
          mUniformsOut(uniformsOut),
          mShaderType(shaderType),
          mBlockIndex(blockIndex)
    {}

    void visitNamedVariable(const sh::ShaderVariable &variable,
                            bool isRowMajor,
                            const std::string &name,
                            const std::string &mappedName,
                            const std::vector<unsigned int> &arraySizes) override
    {
        // If getBlockMemberInfo returns false, the variable is optimized out.
        sh::BlockMemberInfo variableInfo;
        if (!mGetMemberInfo(name, mappedName, &variableInfo))
            return;

        std::string nameWithArrayIndex       = name;
        std::string mappedNameWithArrayIndex = mappedName;

        if (variable.isArray())
        {
            nameWithArrayIndex += "[0]";
            mappedNameWithArrayIndex += "[0]";
        }

        if (mBlockIndex == -1)
        {
            SetActive(mUniformsOut, nameWithArrayIndex, mShaderType, variable.active, variable.id);
            return;
        }

        LinkedUniform newUniform(variable.type, variable.precision, nameWithArrayIndex,
                                 variable.arraySizes, -1, -1, -1, mBlockIndex, variableInfo);
        newUniform.mappedName = mappedNameWithArrayIndex;
        newUniform.setActive(mShaderType, variable.active, variable.id);

        // Since block uniforms have no location, we don't need to store them in the uniform
        // locations list.
        mUniformsOut->push_back(newUniform);
    }

  private:
    const GetBlockMemberInfoFunc &mGetMemberInfo;
    std::vector<LinkedUniform> *mUniformsOut;
    const ShaderType mShaderType;
    const int mBlockIndex;
};

// The purpose of this visitor is to capture the buffer variables in a shader storage block. Each
// new buffer variable is stored in "bufferVariablesOut".
class ShaderStorageBlockVisitor : public sh::BlockEncoderVisitor
{
  public:
    ShaderStorageBlockVisitor(const GetBlockMemberInfoFunc &getMemberInfo,
                              const std::string &namePrefix,
                              const std::string &mappedNamePrefix,
                              std::vector<BufferVariable> *bufferVariablesOut,
                              ShaderType shaderType,
                              int blockIndex)
        : sh::BlockEncoderVisitor(namePrefix, mappedNamePrefix, &mStubEncoder),
          mGetMemberInfo(getMemberInfo),
          mBufferVariablesOut(bufferVariablesOut),
          mShaderType(shaderType),
          mBlockIndex(blockIndex)
    {}

    void visitNamedVariable(const sh::ShaderVariable &variable,
                            bool isRowMajor,
                            const std::string &name,
                            const std::string &mappedName,
                            const std::vector<unsigned int> &arraySizes) override
    {
        if (mSkipEnabled)
            return;

        // If getBlockMemberInfo returns false, the variable is optimized out.
        sh::BlockMemberInfo variableInfo;
        if (!mGetMemberInfo(name, mappedName, &variableInfo))
            return;

        std::string nameWithArrayIndex       = name;
        std::string mappedNameWithArrayIndex = mappedName;

        if (variable.isArray())
        {
            nameWithArrayIndex += "[0]";
            mappedNameWithArrayIndex += "[0]";
        }

        if (mBlockIndex == -1)
        {
            SetActive(mBufferVariablesOut, nameWithArrayIndex, mShaderType, variable.active,
                      variable.id);
            return;
        }

        BufferVariable newBufferVariable(variable.type, variable.precision, nameWithArrayIndex,
                                         variable.arraySizes, mBlockIndex, variableInfo);
        newBufferVariable.mappedName = mappedNameWithArrayIndex;
        newBufferVariable.setActive(mShaderType, variable.active, variable.id);

        newBufferVariable.topLevelArraySize = mTopLevelArraySize;

        mBufferVariablesOut->push_back(newBufferVariable);
    }

  private:
    const GetBlockMemberInfoFunc &mGetMemberInfo;
    std::vector<BufferVariable> *mBufferVariablesOut;
    const ShaderType mShaderType;
    const int mBlockIndex;
    sh::StubBlockEncoder mStubEncoder;
};

struct ShaderUniformCount
{
    unsigned int vectorCount        = 0;
    unsigned int samplerCount       = 0;
    unsigned int imageCount         = 0;
    unsigned int atomicCounterCount = 0;
    unsigned int fragmentInOutCount = 0;
};

ShaderUniformCount &operator+=(ShaderUniformCount &lhs, const ShaderUniformCount &rhs)
{
    lhs.vectorCount += rhs.vectorCount;
    lhs.samplerCount += rhs.samplerCount;
    lhs.imageCount += rhs.imageCount;
    lhs.atomicCounterCount += rhs.atomicCounterCount;
    lhs.fragmentInOutCount += rhs.fragmentInOutCount;
    return lhs;
}

// The purpose of this visitor is to flatten struct and array uniforms into a list of singleton
// uniforms. They are stored in separate lists by uniform type so they can be sorted in order.
// Counts for each uniform category are stored and can be queried with "getCounts".
class FlattenUniformVisitor : public sh::VariableNameVisitor
{
  public:
    FlattenUniformVisitor(ShaderType shaderType,
                          const sh::ShaderVariable &uniform,
                          std::vector<LinkedUniform> *uniforms,
                          std::vector<LinkedUniform> *samplerUniforms,
                          std::vector<LinkedUniform> *imageUniforms,
                          std::vector<LinkedUniform> *atomicCounterUniforms,
                          std::vector<LinkedUniform> *inputAttachmentUniforms,
                          std::vector<UnusedUniform> *unusedUniforms)
        : sh::VariableNameVisitor("", ""),
          mShaderType(shaderType),
          mMarkActive(uniform.active),
          mMarkStaticUse(uniform.staticUse),
          mBinding(uniform.binding),
          mOffset(uniform.offset),
          mLocation(uniform.location),
          mUniforms(uniforms),
          mSamplerUniforms(samplerUniforms),
          mImageUniforms(imageUniforms),
          mAtomicCounterUniforms(atomicCounterUniforms),
          mInputAttachmentUniforms(inputAttachmentUniforms),
          mUnusedUniforms(unusedUniforms)
    {}

    void visitNamedOpaqueObject(const sh::ShaderVariable &variable,
                                const std::string &name,
                                const std::string &mappedName,
                                const std::vector<unsigned int> &arraySizes) override
    {
        visitNamedVariable(variable, false, name, mappedName, arraySizes);
    }

    void visitNamedVariable(const sh::ShaderVariable &variable,
                            bool isRowMajor,
                            const std::string &name,
                            const std::string &mappedName,
                            const std::vector<unsigned int> &arraySizes) override
    {
        bool isSampler                          = IsSamplerType(variable.type);
        bool isImage                            = IsImageType(variable.type);
        bool isAtomicCounter                    = IsAtomicCounterType(variable.type);
        bool isFragmentInOut                    = variable.isFragmentInOut;
        std::vector<LinkedUniform> *uniformList = mUniforms;
        if (isSampler)
        {
            uniformList = mSamplerUniforms;
        }
        else if (isImage)
        {
            uniformList = mImageUniforms;
        }
        else if (isAtomicCounter)
        {
            uniformList = mAtomicCounterUniforms;
        }
        else if (isFragmentInOut)
        {
            uniformList = mInputAttachmentUniforms;
        }

        std::string fullNameWithArrayIndex(name);
        std::string fullMappedNameWithArrayIndex(mappedName);

        if (variable.isArray())
        {
            // We're following the GLES 3.1 November 2016 spec section 7.3.1.1 Naming Active
            // Resources and including [0] at the end of array variable names.
            fullNameWithArrayIndex += "[0]";
            fullMappedNameWithArrayIndex += "[0]";
        }

        LinkedUniform *existingUniform = FindUniform(*uniformList, fullNameWithArrayIndex);
        if (existingUniform)
        {
            if (getBinding() != -1)
            {
                existingUniform->binding = getBinding();
            }
            if (getOffset() != -1)
            {
                existingUniform->offset = getOffset();
            }
            if (mLocation != -1)
            {
                existingUniform->location = mLocation;
            }
            if (mMarkActive)
            {
                existingUniform->active = true;
                existingUniform->setActive(mShaderType, true, variable.id);
            }
            if (mMarkStaticUse)
            {
                existingUniform->staticUse = true;
            }
        }
        else
        {
            LinkedUniform linkedUniform(variable.type, variable.precision, fullNameWithArrayIndex,
                                        variable.arraySizes, getBinding(), getOffset(), mLocation,
                                        -1, sh::kDefaultBlockMemberInfo);
            linkedUniform.mappedName          = fullMappedNameWithArrayIndex;
            linkedUniform.active              = mMarkActive;
            linkedUniform.staticUse           = mMarkStaticUse;
            linkedUniform.outerArraySizes     = arraySizes;
            linkedUniform.texelFetchStaticUse = variable.texelFetchStaticUse;
            linkedUniform.id                  = variable.id;
            linkedUniform.imageUnitFormat     = variable.imageUnitFormat;
            linkedUniform.isFragmentInOut     = variable.isFragmentInOut;
            if (variable.hasParentArrayIndex())
            {
                linkedUniform.setParentArrayIndex(variable.parentArrayIndex());
            }

            std::vector<unsigned int> arrayDims = arraySizes;
            ASSERT(variable.arraySizes.size() == 1 || variable.arraySizes.size() == 0);
            arrayDims.push_back(variable.arraySizes.empty() ? 1 : variable.arraySizes[0]);

            size_t numDimensions = arraySizes.size();
            uint32_t arrayStride = 1;
            for (size_t dimension = numDimensions; dimension > 0;)
            {
                --dimension;
                arrayStride *= arrayDims[dimension + 1];
                linkedUniform.outerArrayOffset += arrayStride * mArrayElementStack[dimension];
            }

            if (mMarkActive)
            {
                linkedUniform.setActive(mShaderType, true, variable.id);
            }
            else
            {
                mUnusedUniforms->emplace_back(
                    linkedUniform.name, linkedUniform.isSampler(), linkedUniform.isImage(),
                    linkedUniform.isAtomicCounter(), linkedUniform.isFragmentInOut);
            }

            uniformList->push_back(linkedUniform);
        }

        unsigned int elementCount = variable.getBasicTypeElementCount();

        // Samplers and images aren't "real" uniforms, so they don't count towards register usage.
        // Likewise, don't count "real" uniforms towards opaque count.

        if (!IsOpaqueType(variable.type) && !isFragmentInOut)
        {
            mUniformCount.vectorCount += VariableRegisterCount(variable.type) * elementCount;
        }

        mUniformCount.samplerCount += (isSampler ? elementCount : 0);
        mUniformCount.imageCount += (isImage ? elementCount : 0);
        mUniformCount.atomicCounterCount += (isAtomicCounter ? elementCount : 0);
        mUniformCount.fragmentInOutCount += (isFragmentInOut ? elementCount : 0);

        if (mLocation != -1)
        {
            mLocation += elementCount;
        }
    }

    void enterStructAccess(const sh::ShaderVariable &structVar, bool isRowMajor) override
    {
        mStructStackSize++;
        sh::VariableNameVisitor::enterStructAccess(structVar, isRowMajor);
    }

    void exitStructAccess(const sh::ShaderVariable &structVar, bool isRowMajor) override
    {
        mStructStackSize--;
        sh::VariableNameVisitor::exitStructAccess(structVar, isRowMajor);
    }

    void enterArrayElement(const sh::ShaderVariable &arrayVar, unsigned int arrayElement) override
    {
        mArrayElementStack.push_back(arrayElement);
        sh::VariableNameVisitor::enterArrayElement(arrayVar, arrayElement);
    }

    void exitArrayElement(const sh::ShaderVariable &arrayVar, unsigned int arrayElement) override
    {
        mArrayElementStack.pop_back();
        sh::VariableNameVisitor::exitArrayElement(arrayVar, arrayElement);
    }

    ShaderUniformCount getCounts() const { return mUniformCount; }

  private:
    int getBinding() const { return mStructStackSize == 0 ? mBinding : -1; }
    int getOffset() const { return mStructStackSize == 0 ? mOffset : -1; }

    ShaderType mShaderType;

    // Active and StaticUse are given separately because they are tracked at struct granularity.
    bool mMarkActive;
    bool mMarkStaticUse;
    int mBinding;
    int mOffset;
    int mLocation;
    std::vector<LinkedUniform> *mUniforms;
    std::vector<LinkedUniform> *mSamplerUniforms;
    std::vector<LinkedUniform> *mImageUniforms;
    std::vector<LinkedUniform> *mAtomicCounterUniforms;
    std::vector<LinkedUniform> *mInputAttachmentUniforms;
    std::vector<UnusedUniform> *mUnusedUniforms;
    std::vector<unsigned int> mArrayElementStack;
    ShaderUniformCount mUniformCount;
    unsigned int mStructStackSize = 0;
};

class InterfaceBlockInfo final : angle::NonCopyable
{
  public:
    InterfaceBlockInfo(CustomBlockLayoutEncoderFactory *customEncoderFactory)
        : mCustomEncoderFactory(customEncoderFactory)
    {}

    void getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks);

    bool getBlockSize(const std::string &name, const std::string &mappedName, size_t *sizeOut);
    bool getBlockMemberInfo(const std::string &name,
                            const std::string &mappedName,
                            sh::BlockMemberInfo *infoOut);

  private:
    size_t getBlockInfo(const sh::InterfaceBlock &interfaceBlock);

    std::map<std::string, size_t> mBlockSizes;
    sh::BlockLayoutMap mBlockLayout;
    // Based on the interface block layout, the std140 or std430 encoders are used.  On some
    // platforms (currently only D3D), there could be another non-standard encoder used.
    CustomBlockLayoutEncoderFactory *mCustomEncoderFactory;
};

void InterfaceBlockInfo::getShaderBlockInfo(const std::vector<sh::InterfaceBlock> &interfaceBlocks)
{
    for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks)
    {
        if (!IsActiveInterfaceBlock(interfaceBlock))
            continue;

        if (mBlockSizes.count(interfaceBlock.name) > 0)
            continue;

        size_t dataSize                  = getBlockInfo(interfaceBlock);
        mBlockSizes[interfaceBlock.name] = dataSize;
    }
}

size_t InterfaceBlockInfo::getBlockInfo(const sh::InterfaceBlock &interfaceBlock)
{
    ASSERT(IsActiveInterfaceBlock(interfaceBlock));

    // define member uniforms
    sh::Std140BlockEncoder std140Encoder;
    sh::Std430BlockEncoder std430Encoder;
    sh::BlockLayoutEncoder *customEncoder = nullptr;
    sh::BlockLayoutEncoder *encoder       = nullptr;

    if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD140)
    {
        encoder = &std140Encoder;
    }
    else if (interfaceBlock.layout == sh::BLOCKLAYOUT_STD430)
    {
        encoder = &std430Encoder;
    }
    else if (mCustomEncoderFactory)
    {
        encoder = customEncoder = mCustomEncoderFactory->makeEncoder();
    }
    else
    {
        UNREACHABLE();
        return 0;
    }

    sh::GetInterfaceBlockInfo(interfaceBlock.fields, interfaceBlock.fieldPrefix(), encoder,
                              &mBlockLayout);

    size_t offset = encoder->getCurrentOffset();

    SafeDelete(customEncoder);

    return offset;
}

bool InterfaceBlockInfo::getBlockSize(const std::string &name,
                                      const std::string &mappedName,
                                      size_t *sizeOut)
{
    size_t nameLengthWithoutArrayIndex;
    ParseArrayIndex(name, &nameLengthWithoutArrayIndex);
    std::string baseName = name.substr(0u, nameLengthWithoutArrayIndex);
    auto sizeIter        = mBlockSizes.find(baseName);
    if (sizeIter == mBlockSizes.end())
    {
        *sizeOut = 0;
        return false;
    }

    *sizeOut = sizeIter->second;
    return true;
}

bool InterfaceBlockInfo::getBlockMemberInfo(const std::string &name,
                                            const std::string &mappedName,
                                            sh::BlockMemberInfo *infoOut)
{
    auto infoIter = mBlockLayout.find(name);
    if (infoIter == mBlockLayout.end())
    {
        *infoOut = sh::kDefaultBlockMemberInfo;
        return false;
    }

    *infoOut = infoIter->second;
    return true;
}

void GetFilteredVaryings(const std::vector<sh::ShaderVariable> &varyings,
                         std::vector<const sh::ShaderVariable *> *filteredVaryingsOut)
{
    for (const sh::ShaderVariable &varying : varyings)
    {
        // Built-in varyings obey special rules
        if (varying.isBuiltIn())
        {
            continue;
        }

        filteredVaryingsOut->push_back(&varying);
    }
}

LinkMismatchError LinkValidateVaryings(const sh::ShaderVariable &outputVarying,
                                       const sh::ShaderVariable &inputVarying,
                                       int shaderVersion,
                                       ShaderType frontShaderType,
                                       ShaderType backShaderType,
                                       bool isSeparable,
                                       std::string *mismatchedStructFieldName)
{
    // [ES 3.2 spec] 7.4.1 Shader Interface Matching:
    // Tessellation control shader per-vertex output variables and blocks and tessellation control,
    // tessellation evaluation, and geometry shader per-vertex input variables and blocks are
    // required to be declared as arrays, with each element representing input or output values for
    // a single vertex of a multi-vertex primitive. For the purposes of interface matching, such
    // variables and blocks are treated as though they were not declared as arrays.
    bool treatOutputAsNonArray =
        (frontShaderType == ShaderType::TessControl && !outputVarying.isPatch);
    bool treatInputAsNonArray =
        ((backShaderType == ShaderType::TessControl ||
          backShaderType == ShaderType::TessEvaluation || backShaderType == ShaderType::Geometry) &&
         !inputVarying.isPatch);

    // Skip the validation on the array sizes between a vertex output varying and a geometry input
    // varying as it has been done before.
    bool validatePrecision      = isSeparable && (shaderVersion > 100);
    LinkMismatchError linkError = LinkValidateProgramVariables(
        outputVarying, inputVarying, validatePrecision, treatOutputAsNonArray, treatInputAsNonArray,
        mismatchedStructFieldName);
    if (linkError != LinkMismatchError::NO_MISMATCH)
    {
        return linkError;
    }

    // Explicit locations must match if the names match.
    if (outputVarying.isSameNameAtLinkTime(inputVarying) &&
        outputVarying.location != inputVarying.location)
    {
        return LinkMismatchError::LOCATION_MISMATCH;
    }

    if (!sh::InterpolationTypesMatch(outputVarying.interpolation, inputVarying.interpolation))
    {
        return LinkMismatchError::INTERPOLATION_TYPE_MISMATCH;
    }

    if (shaderVersion == 100 && outputVarying.isInvariant != inputVarying.isInvariant)
    {
        return LinkMismatchError::INVARIANCE_MISMATCH;
    }

    return LinkMismatchError::NO_MISMATCH;
}

bool DoShaderVariablesMatch(int frontShaderVersion,
                            ShaderType frontShaderType,
                            ShaderType backShaderType,
                            const sh::ShaderVariable &input,
                            const sh::ShaderVariable &output,
                            bool isSeparable,
                            gl::InfoLog &infoLog)
{
    bool namesMatch     = input.isSameNameAtLinkTime(output);
    bool locationsMatch = input.location != -1 && input.location == output.location;

    // An output block is considered to match an input block in the subsequent
    // shader if the two blocks have the same block name, and the members of the
    // block match exactly in name, type, qualification, and declaration order.
    //
    // - For the purposes of shader interface matching, the gl_PointSize
    //   member of the intrinsically declared gl_PerVertex shader interface
    //   block is ignored.
    // - Output blocks that do not match in name, but have a location and match
    //   in every other way listed above may be considered to match by some
    //   implementations, but not all - so this behaviour should not be relied
    //   upon.

    // An output variable is considered to match an input variable in the subsequent
    // shader if:
    //
    // - the two variables match in name, type, and qualification; or
    // - the two variables are declared with the same location qualifier and
    //   match in type and qualification.

    if (namesMatch || locationsMatch)
    {
        std::string mismatchedStructFieldName;
        LinkMismatchError linkError =
            LinkValidateVaryings(output, input, frontShaderVersion, frontShaderType, backShaderType,
                                 isSeparable, &mismatchedStructFieldName);
        if (linkError != LinkMismatchError::NO_MISMATCH)
        {
            LogLinkMismatch(infoLog, input.name, "varying", linkError, mismatchedStructFieldName,
                            frontShaderType, backShaderType);
            return false;
        }

        return true;
    }

    return false;
}

const char *GetInterfaceBlockTypeString(sh::BlockType blockType)
{
    switch (blockType)
    {
        case sh::BlockType::BLOCK_UNIFORM:
            return "uniform block";
        case sh::BlockType::BLOCK_BUFFER:
            return "shader storage block";
        default:
            UNREACHABLE();
            return "";
    }
}

std::string GetInterfaceBlockLimitName(ShaderType shaderType, sh::BlockType blockType)
{
    std::ostringstream stream;
    stream << "GL_MAX_" << GetShaderTypeString(shaderType) << "_";

    switch (blockType)
    {
        case sh::BlockType::BLOCK_UNIFORM:
            stream << "UNIFORM_BUFFERS";
            break;
        case sh::BlockType::BLOCK_BUFFER:
            stream << "SHADER_STORAGE_BLOCKS";
            break;
        default:
            UNREACHABLE();
            return "";
    }

    if (shaderType == ShaderType::Geometry)
    {
        stream << "_EXT";
    }

    return stream.str();
}

void LogInterfaceBlocksExceedLimit(InfoLog &infoLog,
                                   ShaderType shaderType,
                                   sh::BlockType blockType,
                                   GLuint limit)
{
    infoLog << GetShaderTypeString(shaderType) << " shader "
            << GetInterfaceBlockTypeString(blockType) << " count exceeds "
            << GetInterfaceBlockLimitName(shaderType, blockType) << " (" << limit << ")";
}

bool ValidateInterfaceBlocksCount(GLuint maxInterfaceBlocks,
                                  const std::vector<sh::InterfaceBlock> &interfaceBlocks,
                                  ShaderType shaderType,
                                  sh::BlockType blockType,
                                  GLuint *combinedInterfaceBlocksCount,
                                  InfoLog &infoLog)
{
    GLuint blockCount = 0;
    for (const sh::InterfaceBlock &block : interfaceBlocks)
    {
        if (IsActiveInterfaceBlock(block))
        {
            blockCount += std::max(block.arraySize, 1u);
            if (blockCount > maxInterfaceBlocks)
            {
                LogInterfaceBlocksExceedLimit(infoLog, shaderType, blockType, maxInterfaceBlocks);
                return false;
            }
        }
    }

    // [OpenGL ES 3.1] Chapter 7.6.2 Page 105:
    // If a uniform block is used by multiple shader stages, each such use counts separately
    // against this combined limit.
    // [OpenGL ES 3.1] Chapter 7.8 Page 111:
    // If a shader storage block in a program is referenced by multiple shaders, each such
    // reference counts separately against this combined limit.
    if (combinedInterfaceBlocksCount)
    {
        *combinedInterfaceBlocksCount += blockCount;
    }

    return true;
}
}  // anonymous namespace

UniformLinker::UniformLinker(const ShaderBitSet &activeShaderStages,
                             const ShaderMap<std::vector<sh::ShaderVariable>> &shaderUniforms)
    : mActiveShaderStages(activeShaderStages), mShaderUniforms(shaderUniforms)
{}

UniformLinker::~UniformLinker() = default;

void UniformLinker::getResults(std::vector<LinkedUniform> *uniforms,
                               std::vector<UnusedUniform> *unusedUniformsOutOrNull,
                               std::vector<VariableLocation> *uniformLocationsOutOrNull)
{
    uniforms->swap(mUniforms);

    if (unusedUniformsOutOrNull)
    {
        unusedUniformsOutOrNull->swap(mUnusedUniforms);
    }

    if (uniformLocationsOutOrNull)
    {
        uniformLocationsOutOrNull->swap(mUniformLocations);
    }
}

bool UniformLinker::link(const Caps &caps,
                         InfoLog &infoLog,
                         const ProgramAliasedBindings &uniformLocationBindings)
{
    if (mActiveShaderStages[ShaderType::Vertex] && mActiveShaderStages[ShaderType::Fragment])
    {
        if (!validateGraphicsUniforms(infoLog))
        {
            return false;
        }
    }

    // Flatten the uniforms list (nested fields) into a simple list (no nesting).
    // Also check the maximum uniform vector and sampler counts.
    if (!flattenUniformsAndCheckCaps(caps, infoLog))
    {
        return false;
    }

    if (!checkMaxCombinedAtomicCounters(caps, infoLog))
    {
        return false;
    }

    if (!indexUniforms(infoLog, uniformLocationBindings))
    {
        return false;
    }

    return true;
}

bool UniformLinker::validateGraphicsUniforms(InfoLog &infoLog) const
{
    // Check that uniforms defined in the graphics shaders are identical
    std::map<std::string, ShaderUniform> linkedUniforms;

    for (const ShaderType shaderType : mActiveShaderStages)
    {
        if (shaderType == ShaderType::Vertex)
        {
            for (const sh::ShaderVariable &vertexUniform : mShaderUniforms[ShaderType::Vertex])
            {
                linkedUniforms[vertexUniform.name] =
                    std::make_pair(ShaderType::Vertex, &vertexUniform);
            }
        }
        else
        {
            bool isLastShader = (shaderType == ShaderType::Fragment);
            if (!validateGraphicsUniformsPerShader(shaderType, !isLastShader, &linkedUniforms,
                                                   infoLog))
            {
                return false;
            }
        }
    }

    return true;
}

bool UniformLinker::validateGraphicsUniformsPerShader(
    ShaderType shaderToLink,
    bool extendLinkedUniforms,
    std::map<std::string, ShaderUniform> *linkedUniforms,
    InfoLog &infoLog) const
{
    ASSERT(mActiveShaderStages[shaderToLink] && linkedUniforms);

    for (const sh::ShaderVariable &uniform : mShaderUniforms[shaderToLink])
    {
        const auto &entry = linkedUniforms->find(uniform.name);
        if (entry != linkedUniforms->end())
        {
            const sh::ShaderVariable &linkedUniform = *(entry->second.second);
            std::string mismatchedStructFieldName;
            LinkMismatchError linkError =
                LinkValidateUniforms(uniform, linkedUniform, &mismatchedStructFieldName);
            if (linkError != LinkMismatchError::NO_MISMATCH)
            {
                LogLinkMismatch(infoLog, uniform.name, "uniform", linkError,
                                mismatchedStructFieldName, entry->second.first, shaderToLink);
                return false;
            }
        }
        else if (extendLinkedUniforms)
        {
            (*linkedUniforms)[uniform.name] = std::make_pair(shaderToLink, &uniform);
        }
    }

    return true;
}

bool UniformLinker::indexUniforms(InfoLog &infoLog,
                                  const ProgramAliasedBindings &uniformLocationBindings)
{
    // Locations which have been allocated for an unused uniform.
    std::set<GLuint> ignoredLocations;

    int maxUniformLocation = -1;

    // Gather uniform locations that have been set either using the bindUniformLocationCHROMIUM API
    // or by using a location layout qualifier and check conflicts between them.
    if (!gatherUniformLocationsAndCheckConflicts(infoLog, uniformLocationBindings,
                                                 &ignoredLocations, &maxUniformLocation))
    {
        return false;
    }

    // Conflicts have been checked, now we can prune non-statically used uniforms. Code further down
    // the line relies on only having statically used uniforms in mUniforms.
    pruneUnusedUniforms();

    // Gather uniforms that have their location pre-set and uniforms that don't yet have a location.
    std::vector<VariableLocation> unlocatedUniforms;
    std::map<GLuint, VariableLocation> preLocatedUniforms;

    for (size_t uniformIndex = 0; uniformIndex < mUniforms.size(); uniformIndex++)
    {
        const LinkedUniform &uniform = mUniforms[uniformIndex];

        if ((uniform.isBuiltIn() && !uniform.isEmulatedBuiltIn()) ||
            IsAtomicCounterType(uniform.type) || uniform.isFragmentInOut)
        {
            continue;
        }

        int preSetLocation = uniformLocationBindings.getBinding(uniform);
        int shaderLocation = uniform.location;

        if (shaderLocation != -1)
        {
            preSetLocation = shaderLocation;
        }

        unsigned int elementCount = uniform.getBasicTypeElementCount();
        for (unsigned int arrayIndex = 0; arrayIndex < elementCount; arrayIndex++)
        {
            VariableLocation location(arrayIndex, static_cast<unsigned int>(uniformIndex));

            if ((arrayIndex == 0 && preSetLocation != -1) || shaderLocation != -1)
            {
                int elementLocation                 = preSetLocation + arrayIndex;
                preLocatedUniforms[elementLocation] = location;
            }
            else
            {
                unlocatedUniforms.push_back(location);
            }
        }
    }

    // Make enough space for all uniforms, with pre-set locations or not.
    mUniformLocations.resize(
        std::max(unlocatedUniforms.size() + preLocatedUniforms.size() + ignoredLocations.size(),
                 static_cast<size_t>(maxUniformLocation + 1)));

    // Assign uniforms with pre-set locations
    for (const auto &uniform : preLocatedUniforms)
    {
        mUniformLocations[uniform.first] = uniform.second;
    }

    // Assign ignored uniforms
    for (const auto &ignoredLocation : ignoredLocations)
    {
        mUniformLocations[ignoredLocation].markIgnored();
    }

    // Automatically assign locations for the rest of the uniforms
    size_t nextUniformLocation = 0;
    for (const auto &unlocatedUniform : unlocatedUniforms)
    {
        while (mUniformLocations[nextUniformLocation].used() ||
               mUniformLocations[nextUniformLocation].ignored)
        {
            nextUniformLocation++;
        }

        ASSERT(nextUniformLocation < mUniformLocations.size());
        mUniformLocations[nextUniformLocation] = unlocatedUniform;
        nextUniformLocation++;
    }

    return true;
}

bool UniformLinker::gatherUniformLocationsAndCheckConflicts(
    InfoLog &infoLog,
    const ProgramAliasedBindings &uniformLocationBindings,
    std::set<GLuint> *ignoredLocations,
    int *maxUniformLocation)
{
    // All the locations where another uniform can't be located.
    std::set<GLuint> reservedLocations;

    for (const LinkedUniform &uniform : mUniforms)
    {
        if ((uniform.isBuiltIn() && !uniform.isEmulatedBuiltIn()) || uniform.isFragmentInOut)
        {
            // The uniform of the fragment inout is not a normal uniform type. So, in the case of
            // the fragment inout, this routine should be skipped.
            continue;
        }

        int apiBoundLocation = uniformLocationBindings.getBinding(uniform);
        int shaderLocation   = uniform.location;

        if (shaderLocation != -1)
        {
            unsigned int elementCount = uniform.getBasicTypeElementCount();

            for (unsigned int arrayIndex = 0; arrayIndex < elementCount; arrayIndex++)
            {
                // GLSL ES 3.10 section 4.4.3
                int elementLocation = shaderLocation + arrayIndex;
                *maxUniformLocation = std::max(*maxUniformLocation, elementLocation);
                if (reservedLocations.find(elementLocation) != reservedLocations.end())
                {
                    infoLog << "Multiple uniforms bound to location " << elementLocation << ".";
                    return false;
                }
                reservedLocations.insert(elementLocation);
                if (!uniform.active)
                {
                    ignoredLocations->insert(elementLocation);
                }
            }
        }
        else if (apiBoundLocation != -1 && uniform.staticUse)
        {
            // Only the first location is reserved even if the uniform is an array.
            *maxUniformLocation = std::max(*maxUniformLocation, apiBoundLocation);
            if (reservedLocations.find(apiBoundLocation) != reservedLocations.end())
            {
                infoLog << "Multiple uniforms bound to location " << apiBoundLocation << ".";
                return false;
            }
            reservedLocations.insert(apiBoundLocation);
            if (!uniform.active)
            {
                ignoredLocations->insert(apiBoundLocation);
            }
        }
    }

    // Record the uniform locations that were bound using the API for uniforms that were not found
    // from the shader. Other uniforms should not be assigned to those locations.
    for (const auto &locationBinding : uniformLocationBindings)
    {
        GLuint location = locationBinding.second.location;
        if (reservedLocations.find(location) == reservedLocations.end())
        {
            ignoredLocations->insert(location);
            *maxUniformLocation = std::max(*maxUniformLocation, static_cast<int>(location));
        }
    }

    return true;
}

void UniformLinker::pruneUnusedUniforms()
{
    auto uniformIter = mUniforms.begin();
    while (uniformIter != mUniforms.end())
    {
        if (uniformIter->active)
        {
            ++uniformIter;
        }
        else
        {
            mUnusedUniforms.emplace_back(uniformIter->name, uniformIter->isSampler(),
                                         uniformIter->isImage(), uniformIter->isAtomicCounter(),
                                         uniformIter->isFragmentInOut);
            uniformIter = mUniforms.erase(uniformIter);
        }
    }
}

bool UniformLinker::flattenUniformsAndCheckCapsForShader(
    ShaderType shaderType,
    const Caps &caps,
    std::vector<LinkedUniform> &samplerUniforms,
    std::vector<LinkedUniform> &imageUniforms,
    std::vector<LinkedUniform> &atomicCounterUniforms,
    std::vector<LinkedUniform> &inputAttachmentUniforms,
    std::vector<UnusedUniform> &unusedUniforms,
    InfoLog &infoLog)
{
    ShaderUniformCount shaderUniformCount;
    for (const sh::ShaderVariable &uniform : mShaderUniforms[shaderType])
    {
        FlattenUniformVisitor flattener(shaderType, uniform, &mUniforms, &samplerUniforms,
                                        &imageUniforms, &atomicCounterUniforms,
                                        &inputAttachmentUniforms, &unusedUniforms);
        sh::TraverseShaderVariable(uniform, false, &flattener);

        if (uniform.active)
        {
            shaderUniformCount += flattener.getCounts();
        }
        else
        {
            unusedUniforms.emplace_back(uniform.name, IsSamplerType(uniform.type),
                                        IsImageType(uniform.type),
                                        IsAtomicCounterType(uniform.type), uniform.isFragmentInOut);
        }
    }

    // This code does not do fine-grained component counting.
    GLuint maxUniformVectorsCount = GetMaximumShaderUniformVectors(shaderType, caps);
    if (shaderUniformCount.vectorCount > maxUniformVectorsCount)
    {
        GLuint maxUniforms = 0u;

        // See comments in GetUniformResourceLimitName()
        if (shaderType == ShaderType::Vertex || shaderType == ShaderType::Fragment)
        {
            maxUniforms = maxUniformVectorsCount;
        }
        else
        {
            maxUniforms = maxUniformVectorsCount * 4;
        }

        LogUniformsExceedLimit(shaderType, UniformType::Variable, maxUniforms, infoLog);
        return false;
    }

    if (shaderUniformCount.samplerCount >
        static_cast<GLuint>(caps.maxShaderTextureImageUnits[shaderType]))
    {
        LogUniformsExceedLimit(shaderType, UniformType::Sampler,
                               caps.maxShaderTextureImageUnits[shaderType], infoLog);
        return false;
    }

    if (shaderUniformCount.imageCount >
        static_cast<GLuint>(caps.maxShaderImageUniforms[shaderType]))
    {
        LogUniformsExceedLimit(shaderType, UniformType::Image,
                               caps.maxShaderImageUniforms[shaderType], infoLog);
        return false;
    }

    if (shaderUniformCount.atomicCounterCount >
        static_cast<GLuint>(caps.maxShaderAtomicCounters[shaderType]))
    {
        LogUniformsExceedLimit(shaderType, UniformType::AtomicCounter,
                               caps.maxShaderAtomicCounters[shaderType], infoLog);
        return false;
    }

    return true;
}

bool UniformLinker::flattenUniformsAndCheckCaps(const Caps &caps, InfoLog &infoLog)
{
    std::vector<LinkedUniform> samplerUniforms;
    std::vector<LinkedUniform> imageUniforms;
    std::vector<LinkedUniform> atomicCounterUniforms;
    std::vector<LinkedUniform> inputAttachmentUniforms;
    std::vector<UnusedUniform> unusedUniforms;

    for (const ShaderType shaderType : mActiveShaderStages)
    {
        if (!flattenUniformsAndCheckCapsForShader(shaderType, caps, samplerUniforms, imageUniforms,
                                                  atomicCounterUniforms, inputAttachmentUniforms,
                                                  unusedUniforms, infoLog))
        {
            return false;
        }
    }

    mUniforms.insert(mUniforms.end(), samplerUniforms.begin(), samplerUniforms.end());
    mUniforms.insert(mUniforms.end(), imageUniforms.begin(), imageUniforms.end());
    mUniforms.insert(mUniforms.end(), atomicCounterUniforms.begin(), atomicCounterUniforms.end());
    mUniforms.insert(mUniforms.end(), inputAttachmentUniforms.begin(),
                     inputAttachmentUniforms.end());
    mUnusedUniforms.insert(mUnusedUniforms.end(), unusedUniforms.begin(), unusedUniforms.end());
    return true;
}

bool UniformLinker::checkMaxCombinedAtomicCounters(const Caps &caps, InfoLog &infoLog)
{
    unsigned int atomicCounterCount = 0;
    for (const auto &uniform : mUniforms)
    {
        if (IsAtomicCounterType(uniform.type) && uniform.active)
        {
            atomicCounterCount += uniform.getBasicTypeElementCount();
            if (atomicCounterCount > static_cast<GLuint>(caps.maxCombinedAtomicCounters))
            {
                infoLog << "atomic counter count exceeds MAX_COMBINED_ATOMIC_COUNTERS"
                        << caps.maxCombinedAtomicCounters << ").";
                return false;
            }
        }
    }
    return true;
}

// InterfaceBlockLinker implementation.
InterfaceBlockLinker::InterfaceBlockLinker() = default;

InterfaceBlockLinker::~InterfaceBlockLinker() = default;

void InterfaceBlockLinker::init(std::vector<InterfaceBlock> *blocksOut,
                                std::vector<std::string> *unusedInterfaceBlocksOut)
{
    mBlocksOut                = blocksOut;
    mUnusedInterfaceBlocksOut = unusedInterfaceBlocksOut;
}

void InterfaceBlockLinker::addShaderBlocks(ShaderType shaderType,
                                           const std::vector<sh::InterfaceBlock> *blocks)
{
    mShaderBlocks[shaderType] = blocks;
}

void InterfaceBlockLinker::linkBlocks(const GetBlockSizeFunc &getBlockSize,
                                      const GetBlockMemberInfoFunc &getMemberInfo) const
{
    ASSERT(mBlocksOut->empty());

    std::set<std::string> visitedList;

    for (const ShaderType shaderType : AllShaderTypes())
    {
        if (!mShaderBlocks[shaderType])
        {
            continue;
        }

        for (const sh::InterfaceBlock &block : *mShaderBlocks[shaderType])
        {
            if (!IsActiveInterfaceBlock(block))
            {
                mUnusedInterfaceBlocksOut->push_back(block.name);
                continue;
            }

            if (visitedList.count(block.name) == 0)
            {
                defineInterfaceBlock(getBlockSize, getMemberInfo, block, shaderType);
                visitedList.insert(block.name);
                continue;
            }

            if (!block.active)
            {
                mUnusedInterfaceBlocksOut->push_back(block.name);
                continue;
            }

            for (InterfaceBlock &priorBlock : *mBlocksOut)
            {
                if (block.name == priorBlock.name)
                {
                    priorBlock.setActive(shaderType, true, block.id);

                    std::unique_ptr<sh::ShaderVariableVisitor> visitor(
                        getVisitor(getMemberInfo, block.fieldPrefix(), block.fieldMappedPrefix(),
                                   shaderType, -1));

                    sh::TraverseShaderVariables(block.fields, false, visitor.get());
                }
            }
        }
    }
}

void InterfaceBlockLinker::defineInterfaceBlock(const GetBlockSizeFunc &getBlockSize,
                                                const GetBlockMemberInfoFunc &getMemberInfo,
                                                const sh::InterfaceBlock &interfaceBlock,
                                                ShaderType shaderType) const
{
    size_t blockSize = 0;
    std::vector<unsigned int> blockIndexes;

    const int blockIndex = static_cast<int>(mBlocksOut->size());
    // Track the first and last block member index to determine the range of active block members in
    // the block.
    const size_t firstBlockMemberIndex = getCurrentBlockMemberIndex();

    std::unique_ptr<sh::ShaderVariableVisitor> visitor(
        getVisitor(getMemberInfo, interfaceBlock.fieldPrefix(), interfaceBlock.fieldMappedPrefix(),
                   shaderType, blockIndex));
    sh::TraverseShaderVariables(interfaceBlock.fields, false, visitor.get());

    const size_t lastBlockMemberIndex = getCurrentBlockMemberIndex();

    for (size_t blockMemberIndex = firstBlockMemberIndex; blockMemberIndex < lastBlockMemberIndex;
         ++blockMemberIndex)
    {
        blockIndexes.push_back(static_cast<unsigned int>(blockMemberIndex));
    }

    const unsigned int firstFieldArraySize = interfaceBlock.fields[0].getArraySizeProduct();

    for (unsigned int arrayElement = 0; arrayElement < interfaceBlock.elementCount();
         ++arrayElement)
    {
        std::string blockArrayName       = interfaceBlock.name;
        std::string blockMappedArrayName = interfaceBlock.mappedName;
        if (interfaceBlock.isArray())
        {
            blockArrayName += ArrayString(arrayElement);
            blockMappedArrayName += ArrayString(arrayElement);
        }

        // Don't define this block at all if it's not active in the implementation.
        if (!getBlockSize(blockArrayName, blockMappedArrayName, &blockSize))
        {
            continue;
        }

        // ESSL 3.10 section 4.4.4 page 58:
        // Any uniform or shader storage block declared without a binding qualifier is initially
        // assigned to block binding point zero.
        const int blockBinding =
            (interfaceBlock.binding == -1 ? 0 : interfaceBlock.binding + arrayElement);
        InterfaceBlock block(interfaceBlock.name, interfaceBlock.mappedName,
                             interfaceBlock.isArray(), interfaceBlock.isReadOnly, arrayElement,
                             firstFieldArraySize, blockBinding);
        block.memberIndexes = blockIndexes;
        block.setActive(shaderType, interfaceBlock.active, interfaceBlock.id);

        // Since all block elements in an array share the same active interface blocks, they
        // will all be active once any block member is used. So, since interfaceBlock.name[0]
        // was active, here we will add every block element in the array.
        block.dataSize = static_cast<unsigned int>(blockSize);
        mBlocksOut->push_back(block);
    }
}

// UniformBlockLinker implementation.
UniformBlockLinker::UniformBlockLinker() = default;

UniformBlockLinker::~UniformBlockLinker() {}

void UniformBlockLinker::init(std::vector<InterfaceBlock> *blocksOut,
                              std::vector<LinkedUniform> *uniformsOut,
                              std::vector<std::string> *unusedInterfaceBlocksOut)
{
    InterfaceBlockLinker::init(blocksOut, unusedInterfaceBlocksOut);
    mUniformsOut = uniformsOut;
}

size_t UniformBlockLinker::getCurrentBlockMemberIndex() const
{
    return mUniformsOut->size();
}

sh::ShaderVariableVisitor *UniformBlockLinker::getVisitor(
    const GetBlockMemberInfoFunc &getMemberInfo,
    const std::string &namePrefix,
    const std::string &mappedNamePrefix,
    ShaderType shaderType,
    int blockIndex) const
{
    return new UniformBlockEncodingVisitor(getMemberInfo, namePrefix, mappedNamePrefix,
                                           mUniformsOut, shaderType, blockIndex);
}

// ShaderStorageBlockLinker implementation.
ShaderStorageBlockLinker::ShaderStorageBlockLinker() = default;

ShaderStorageBlockLinker::~ShaderStorageBlockLinker() = default;

void ShaderStorageBlockLinker::init(std::vector<InterfaceBlock> *blocksOut,
                                    std::vector<BufferVariable> *bufferVariablesOut,
                                    std::vector<std::string> *unusedInterfaceBlocksOut)
{
    InterfaceBlockLinker::init(blocksOut, unusedInterfaceBlocksOut);
    mBufferVariablesOut = bufferVariablesOut;
}

size_t ShaderStorageBlockLinker::getCurrentBlockMemberIndex() const
{
    return mBufferVariablesOut->size();
}

sh::ShaderVariableVisitor *ShaderStorageBlockLinker::getVisitor(
    const GetBlockMemberInfoFunc &getMemberInfo,
    const std::string &namePrefix,
    const std::string &mappedNamePrefix,
    ShaderType shaderType,
    int blockIndex) const
{
    return new ShaderStorageBlockVisitor(getMemberInfo, namePrefix, mappedNamePrefix,
                                         mBufferVariablesOut, shaderType, blockIndex);
}

// AtomicCounterBufferLinker implementation.
AtomicCounterBufferLinker::AtomicCounterBufferLinker() = default;

AtomicCounterBufferLinker::~AtomicCounterBufferLinker() = default;

void AtomicCounterBufferLinker::init(std::vector<AtomicCounterBuffer> *atomicCounterBuffersOut)
{
    mAtomicCounterBuffersOut = atomicCounterBuffersOut;
}

void AtomicCounterBufferLinker::link(const std::map<int, unsigned int> &sizeMap) const
{
    for (auto &atomicCounterBuffer : *mAtomicCounterBuffersOut)
    {
        auto bufferSize = sizeMap.find(atomicCounterBuffer.binding);
        ASSERT(bufferSize != sizeMap.end());
        atomicCounterBuffer.dataSize = bufferSize->second;
    }
}

ProgramLinkedResources::ProgramLinkedResources() = default;

ProgramLinkedResources::~ProgramLinkedResources() = default;

LinkingVariables::LinkingVariables(const Context *context, const ProgramState &state)
{
    for (ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        Shader *shader = state.getAttachedShader(shaderType);
        if (shader)
        {
            outputVaryings[shaderType] = shader->getOutputVaryings(context);
            inputVaryings[shaderType]  = shader->getInputVaryings(context);
            uniforms[shaderType]       = shader->getUniforms(context);
            uniformBlocks[shaderType]  = shader->getUniformBlocks(context);
            isShaderStageUsedBitset.set(shaderType);
        }
    }
}

LinkingVariables::LinkingVariables(const ProgramPipelineState &state)
{
    for (ShaderType shaderType : state.getExecutable().getLinkedShaderStages())
    {
        const Program *program = state.getShaderProgram(shaderType);
        ASSERT(program);
        outputVaryings[shaderType] = program->getExecutable().getLinkedOutputVaryings(shaderType);
        inputVaryings[shaderType]  = program->getExecutable().getLinkedInputVaryings(shaderType);
        uniforms[shaderType] = program->getState().getExecutable().getLinkedUniforms(shaderType);
        uniformBlocks[shaderType] =
            program->getState().getExecutable().getLinkedUniformBlocks(shaderType);
        isShaderStageUsedBitset.set(shaderType);
    }
}

LinkingVariables::~LinkingVariables() = default;

void ProgramLinkedResources::init(std::vector<InterfaceBlock> *uniformBlocksOut,
                                  std::vector<LinkedUniform> *uniformsOut,
                                  std::vector<InterfaceBlock> *shaderStorageBlocksOut,
                                  std::vector<BufferVariable> *bufferVariablesOut,
                                  std::vector<AtomicCounterBuffer> *atomicCounterBuffersOut)
{
    uniformBlockLinker.init(uniformBlocksOut, uniformsOut, &unusedInterfaceBlocks);
    shaderStorageBlockLinker.init(shaderStorageBlocksOut, bufferVariablesOut,
                                  &unusedInterfaceBlocks);
    atomicCounterBufferLinker.init(atomicCounterBuffersOut);
}

void ProgramLinkedResourcesLinker::linkResources(const Context *context,
                                                 const ProgramState &programState,
                                                 const ProgramLinkedResources &resources) const
{
    // Gather uniform interface block info.
    InterfaceBlockInfo uniformBlockInfo(mCustomEncoderFactory);
    for (const ShaderType shaderType : AllShaderTypes())
    {
        Shader *shader = programState.getAttachedShader(shaderType);
        if (shader)
        {
            uniformBlockInfo.getShaderBlockInfo(shader->getUniformBlocks(context));
        }
    }

    auto getUniformBlockSize = [&uniformBlockInfo](const std::string &name,
                                                   const std::string &mappedName, size_t *sizeOut) {
        return uniformBlockInfo.getBlockSize(name, mappedName, sizeOut);
    };

    auto getUniformBlockMemberInfo = [&uniformBlockInfo](const std::string &name,
                                                         const std::string &mappedName,
                                                         sh::BlockMemberInfo *infoOut) {
        return uniformBlockInfo.getBlockMemberInfo(name, mappedName, infoOut);
    };

    // Link uniform interface blocks.
    resources.uniformBlockLinker.linkBlocks(getUniformBlockSize, getUniformBlockMemberInfo);

    // Gather storage buffer interface block info.
    InterfaceBlockInfo shaderStorageBlockInfo(mCustomEncoderFactory);
    for (const ShaderType shaderType : AllShaderTypes())
    {
        Shader *shader = programState.getAttachedShader(shaderType);
        if (shader)
        {
            shaderStorageBlockInfo.getShaderBlockInfo(shader->getShaderStorageBlocks(context));
        }
    }
    auto getShaderStorageBlockSize = [&shaderStorageBlockInfo](const std::string &name,
                                                               const std::string &mappedName,
                                                               size_t *sizeOut) {
        return shaderStorageBlockInfo.getBlockSize(name, mappedName, sizeOut);
    };

    auto getShaderStorageBlockMemberInfo = [&shaderStorageBlockInfo](const std::string &name,
                                                                     const std::string &mappedName,
                                                                     sh::BlockMemberInfo *infoOut) {
        return shaderStorageBlockInfo.getBlockMemberInfo(name, mappedName, infoOut);
    };

    // Link storage buffer interface blocks.
    resources.shaderStorageBlockLinker.linkBlocks(getShaderStorageBlockSize,
                                                  getShaderStorageBlockMemberInfo);

    // Gather and link atomic counter buffer interface blocks.
    std::map<int, unsigned int> sizeMap;
    getAtomicCounterBufferSizeMap(programState, sizeMap);
    resources.atomicCounterBufferLinker.link(sizeMap);
}

void ProgramLinkedResourcesLinker::getAtomicCounterBufferSizeMap(
    const ProgramState &programState,
    std::map<int, unsigned int> &sizeMapOut) const
{
    for (unsigned int index : programState.getAtomicCounterUniformRange())
    {
        const LinkedUniform &glUniform = programState.getUniforms()[index];

        auto &bufferDataSize = sizeMapOut[glUniform.binding];

        // Calculate the size of the buffer by finding the end of the last uniform with the same
        // binding. The end of the uniform is calculated by finding the initial offset of the
        // uniform and adding size of the uniform. For arrays, the size is the number of elements
        // times the element size (should always by 4 for atomic_units).
        unsigned dataOffset =
            glUniform.offset + static_cast<unsigned int>(glUniform.getBasicTypeElementCount() *
                                                         glUniform.getElementSize());
        if (dataOffset > bufferDataSize)
        {
            bufferDataSize = dataOffset;
        }
    }
}

bool LinkValidateProgramGlobalNames(InfoLog &infoLog,
                                    const ProgramExecutable &executable,
                                    const LinkingVariables &linkingVariables)
{
    angle::HashMap<std::string, const sh::ShaderVariable *> uniformMap;
    using BlockAndFieldPair = std::pair<const sh::InterfaceBlock *, const sh::ShaderVariable *>;
    angle::HashMap<std::string, std::vector<BlockAndFieldPair>> uniformBlockFieldMap;

    for (ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        if (!linkingVariables.isShaderStageUsedBitset[shaderType])
        {
            continue;
        }

        // Build a map of Uniforms
        const std::vector<sh::ShaderVariable> &uniforms = linkingVariables.uniforms[shaderType];
        for (const auto &uniform : uniforms)
        {
            uniformMap[uniform.name] = &uniform;
        }

        // Build a map of Uniform Blocks
        // This will also detect any field name conflicts between Uniform Blocks without instance
        // names
        const std::vector<sh::InterfaceBlock> &uniformBlocks =
            linkingVariables.uniformBlocks[shaderType];

        for (const auto &uniformBlock : uniformBlocks)
        {
            // Only uniform blocks without an instance name can create a conflict with their field
            // names
            if (!uniformBlock.instanceName.empty())
            {
                continue;
            }

            for (const auto &field : uniformBlock.fields)
            {
                if (!uniformBlockFieldMap.count(field.name))
                {
                    // First time we've seen this uniform block field name, so add the
                    // (Uniform Block, Field) pair immediately since there can't be a conflict yet
                    BlockAndFieldPair blockAndFieldPair(&uniformBlock, &field);
                    std::vector<BlockAndFieldPair> newUniformBlockList;
                    newUniformBlockList.push_back(blockAndFieldPair);
                    uniformBlockFieldMap[field.name] = newUniformBlockList;
                    continue;
                }

                // We've seen this name before.
                // We need to check each of the uniform blocks that contain a field with this name
                // to see if there's a conflict or not.
                std::vector<BlockAndFieldPair> prevBlockFieldPairs =
                    uniformBlockFieldMap[field.name];
                for (const auto &prevBlockFieldPair : prevBlockFieldPairs)
                {
                    const sh::InterfaceBlock *prevUniformBlock      = prevBlockFieldPair.first;
                    const sh::ShaderVariable *prevUniformBlockField = prevBlockFieldPair.second;

                    if (uniformBlock.isSameInterfaceBlockAtLinkTime(*prevUniformBlock))
                    {
                        // The same uniform block should, by definition, contain the same field name
                        continue;
                    }

                    // The uniform blocks don't match, so check if the necessary field properties
                    // also match
                    if ((field.name == prevUniformBlockField->name) &&
                        (field.type == prevUniformBlockField->type) &&
                        (field.precision == prevUniformBlockField->precision))
                    {
                        infoLog << "Name conflicts between uniform block field names: "
                                << field.name;
                        return false;
                    }
                }

                // No conflict, so record this pair
                BlockAndFieldPair blockAndFieldPair(&uniformBlock, &field);
                uniformBlockFieldMap[field.name].push_back(blockAndFieldPair);
            }
        }
    }

    // Validate no uniform names conflict with attribute names
    if (linkingVariables.isShaderStageUsedBitset[ShaderType::Vertex])
    {
        // ESSL 3.00.6 section 4.3.5:
        // If a uniform variable name is declared in one stage (e.g., a vertex shader)
        // but not in another (e.g., a fragment shader), then that name is still
        // available in the other stage for a different use.
        std::unordered_set<std::string> uniforms;
        for (const sh::ShaderVariable &uniform : linkingVariables.uniforms[ShaderType::Vertex])
        {
            uniforms.insert(uniform.name);
        }
        for (const auto &attrib : executable.getProgramInputs())
        {
            if (uniforms.count(attrib.name))
            {
                infoLog << "Name conflicts between a uniform and an attribute: " << attrib.name;
                return false;
            }
        }
    }

    // Validate no Uniform Block fields conflict with other Uniforms
    for (const auto &uniformBlockField : uniformBlockFieldMap)
    {
        const std::string &fieldName = uniformBlockField.first;
        if (uniformMap.count(fieldName))
        {
            infoLog << "Name conflicts between a uniform and a uniform block field: " << fieldName;
            return false;
        }
    }

    return true;
}

// [OpenGL ES 3.2] Chapter 7.4.1 "Shader Interface Matching"
bool LinkValidateShaderInterfaceMatching(const std::vector<sh::ShaderVariable> &outputVaryings,
                                         const std::vector<sh::ShaderVariable> &inputVaryings,
                                         ShaderType frontShaderType,
                                         ShaderType backShaderType,
                                         int frontShaderVersion,
                                         int backShaderVersion,
                                         bool isSeparable,
                                         gl::InfoLog &infoLog)
{
    ASSERT(frontShaderVersion == backShaderVersion);

    std::vector<const sh::ShaderVariable *> filteredInputVaryings;
    std::vector<const sh::ShaderVariable *> filteredOutputVaryings;

    GetFilteredVaryings(inputVaryings, &filteredInputVaryings);
    GetFilteredVaryings(outputVaryings, &filteredOutputVaryings);

    // Separable programs require the number of inputs and outputs match
    if (isSeparable && filteredInputVaryings.size() < filteredOutputVaryings.size())
    {
        infoLog << GetShaderTypeString(backShaderType)
                << " does not consume all varyings generated by "
                << GetShaderTypeString(frontShaderType);
        return false;
    }
    if (isSeparable && filteredInputVaryings.size() > filteredOutputVaryings.size())
    {
        infoLog << GetShaderTypeString(frontShaderType)
                << " does not generate all varyings consumed by "
                << GetShaderTypeString(backShaderType);
        return false;
    }

    // All inputs must match all outputs
    for (const sh::ShaderVariable *input : filteredInputVaryings)
    {
        bool match = false;
        for (const sh::ShaderVariable *output : filteredOutputVaryings)
        {
            if (DoShaderVariablesMatch(frontShaderVersion, frontShaderType, backShaderType, *input,
                                       *output, isSeparable, infoLog))
            {
                match = true;
                break;
            }
        }

        // We permit unmatched, unreferenced varyings. Note that this specifically depends on
        // whether the input is statically used - a statically used input should fail this test even
        // if it is not active. GLSL ES 3.00.6 section 4.3.10.
        if (!match && input->staticUse)
        {
            const std::string &name =
                input->isShaderIOBlock ? input->structOrBlockName : input->name;
            infoLog << GetShaderTypeString(backShaderType) << " varying " << name
                    << " does not match any " << GetShaderTypeString(frontShaderType) << " varying";
            return false;
        }
    }

    return true;
}

LinkMismatchError LinkValidateProgramVariables(const sh::ShaderVariable &variable1,
                                               const sh::ShaderVariable &variable2,
                                               bool validatePrecision,
                                               bool treatVariable1AsNonArray,
                                               bool treatVariable2AsNonArray,
                                               std::string *mismatchedStructOrBlockMemberName)
{
    if (variable1.type != variable2.type)
    {
        return LinkMismatchError::TYPE_MISMATCH;
    }

    bool variable1IsArray = variable1.isArray();
    bool variable2IsArray = variable2.isArray();
    if (treatVariable1AsNonArray)
    {
        ASSERT(variable1IsArray);
        variable1IsArray = false;
    }
    if (treatVariable2AsNonArray)
    {
        ASSERT(variable2IsArray);
        variable2IsArray = false;
    }
    // TODO(anglebug.com/5557): Investigate interactions with arrays-of-arrays.
    if (variable1IsArray != variable2IsArray)
    {
        return LinkMismatchError::ARRAYNESS_MISMATCH;
    }
    if (!treatVariable1AsNonArray && !treatVariable2AsNonArray &&
        variable1.arraySizes != variable2.arraySizes)
    {
        return LinkMismatchError::ARRAY_SIZE_MISMATCH;
    }
    if (validatePrecision && variable1.precision != variable2.precision)
    {
        return LinkMismatchError::PRECISION_MISMATCH;
    }
    if (!variable1.isShaderIOBlock && !variable2.isShaderIOBlock &&
        variable1.structOrBlockName != variable2.structOrBlockName)
    {
        return LinkMismatchError::STRUCT_NAME_MISMATCH;
    }
    if (variable1.imageUnitFormat != variable2.imageUnitFormat)
    {
        return LinkMismatchError::FORMAT_MISMATCH;
    }

    if (variable1.fields.size() != variable2.fields.size())
    {
        return LinkMismatchError::FIELD_NUMBER_MISMATCH;
    }
    const unsigned int numMembers = static_cast<unsigned int>(variable1.fields.size());
    for (unsigned int memberIndex = 0; memberIndex < numMembers; memberIndex++)
    {
        const sh::ShaderVariable &member1 = variable1.fields[memberIndex];
        const sh::ShaderVariable &member2 = variable2.fields[memberIndex];

        if (member1.name != member2.name)
        {
            return LinkMismatchError::FIELD_NAME_MISMATCH;
        }

        if (member1.interpolation != member2.interpolation)
        {
            return LinkMismatchError::INTERPOLATION_TYPE_MISMATCH;
        }

        if (variable1.isShaderIOBlock && variable2.isShaderIOBlock)
        {
            if (member1.location != member2.location)
            {
                return LinkMismatchError::FIELD_LOCATION_MISMATCH;
            }

            if (member1.structOrBlockName != member2.structOrBlockName)
            {
                return LinkMismatchError::FIELD_STRUCT_NAME_MISMATCH;
            }
        }

        LinkMismatchError linkErrorOnField = LinkValidateProgramVariables(
            member1, member2, validatePrecision, false, false, mismatchedStructOrBlockMemberName);
        if (linkErrorOnField != LinkMismatchError::NO_MISMATCH)
        {
            AddProgramVariableParentPrefix(member1.name, mismatchedStructOrBlockMemberName);
            return linkErrorOnField;
        }
    }

    return LinkMismatchError::NO_MISMATCH;
}

void AddProgramVariableParentPrefix(const std::string &parentName, std::string *mismatchedFieldName)
{
    ASSERT(mismatchedFieldName);
    if (mismatchedFieldName->empty())
    {
        *mismatchedFieldName = parentName;
    }
    else
    {
        std::ostringstream stream;
        stream << parentName << "." << *mismatchedFieldName;
        *mismatchedFieldName = stream.str();
    }
}

bool LinkValidateBuiltInVaryingsInvariant(const std::vector<sh::ShaderVariable> &vertexVaryings,
                                          const std::vector<sh::ShaderVariable> &fragmentVaryings,
                                          int vertexShaderVersion,
                                          InfoLog &infoLog)
{
    bool glPositionIsInvariant   = false;
    bool glPointSizeIsInvariant  = false;
    bool glFragCoordIsInvariant  = false;
    bool glPointCoordIsInvariant = false;

    for (const sh::ShaderVariable &varying : vertexVaryings)
    {
        if (!varying.isBuiltIn())
        {
            continue;
        }
        if (varying.name.compare("gl_Position") == 0)
        {
            glPositionIsInvariant = varying.isInvariant;
        }
        else if (varying.name.compare("gl_PointSize") == 0)
        {
            glPointSizeIsInvariant = varying.isInvariant;
        }
    }

    for (const sh::ShaderVariable &varying : fragmentVaryings)
    {
        if (!varying.isBuiltIn())
        {
            continue;
        }
        if (varying.name.compare("gl_FragCoord") == 0)
        {
            glFragCoordIsInvariant = varying.isInvariant;
        }
        else if (varying.name.compare("gl_PointCoord") == 0)
        {
            glPointCoordIsInvariant = varying.isInvariant;
        }
    }

    // There is some ambiguity in ESSL 1.00.17 paragraph 4.6.4 interpretation,
    // for example, https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13842.
    // Not requiring invariance to match is supported by:
    // dEQP, WebGL CTS, Nexus 5X GLES
    if (glFragCoordIsInvariant && !glPositionIsInvariant)
    {
        infoLog << "gl_FragCoord can only be declared invariant if and only if gl_Position is "
                   "declared invariant.";
        return false;
    }
    if (glPointCoordIsInvariant && !glPointSizeIsInvariant)
    {
        infoLog << "gl_PointCoord can only be declared invariant if and only if gl_PointSize is "
                   "declared invariant.";
        return false;
    }

    return true;
}

bool LinkValidateBuiltInVaryings(const std::vector<sh::ShaderVariable> &outputVaryings,
                                 const std::vector<sh::ShaderVariable> &inputVaryings,
                                 ShaderType outputShaderType,
                                 ShaderType inputShaderType,
                                 int outputShaderVersion,
                                 int inputShaderVersion,
                                 InfoLog &infoLog)
{
    ASSERT(outputShaderVersion == inputShaderVersion);

    // Only ESSL 1.0 has restrictions on matching input and output invariance
    if (inputShaderVersion == 100 && outputShaderType == ShaderType::Vertex &&
        inputShaderType == ShaderType::Fragment)
    {
        return LinkValidateBuiltInVaryingsInvariant(outputVaryings, inputVaryings,
                                                    outputShaderVersion, infoLog);
    }

    uint32_t sizeClipDistance = 0;
    uint32_t sizeCullDistance = 0;

    for (const sh::ShaderVariable &varying : outputVaryings)
    {
        if (!varying.isBuiltIn())
        {
            continue;
        }
        if (varying.name.compare("gl_ClipDistance") == 0)
        {
            sizeClipDistance = varying.getOutermostArraySize();
        }
        else if (varying.name.compare("gl_CullDistance") == 0)
        {
            sizeCullDistance = varying.getOutermostArraySize();
        }
    }

    for (const sh::ShaderVariable &varying : inputVaryings)
    {
        if (!varying.isBuiltIn())
        {
            continue;
        }
        if (varying.name.compare("gl_ClipDistance") == 0)
        {
            if (sizeClipDistance != varying.getOutermostArraySize())
            {
                infoLog << "If either shader redeclares the built-in arrays gl_ClipDistance[] the "
                           "array must have the same size in both shaders. "
                        << "Output size " << sizeClipDistance << ", input size "
                        << varying.getOutermostArraySize() << ".";
                return false;
            }
        }
        else if (varying.name.compare("gl_CullDistance") == 0)
        {
            if (sizeCullDistance != varying.getOutermostArraySize())
            {
                infoLog << "If either shader redeclares the built-in arrays gl_CullDistance[] the "
                           "array must have the same size in both shaders. "
                        << "Output size " << sizeCullDistance << ", input size "
                        << varying.getOutermostArraySize() << ".";

                return false;
            }
        }
    }
    return true;
}

void LogAmbiguousFieldLinkMismatch(InfoLog &infoLog,
                                   const std::string &blockName1,
                                   const std::string &blockName2,
                                   const std::string &fieldName,
                                   ShaderType shaderType1,
                                   ShaderType shaderType2)
{
    infoLog << "Ambiguous field '" << fieldName << "' in blocks '" << blockName1 << "' ("
            << GetShaderTypeString(shaderType1) << " shader) and '" << blockName2 << "' ("
            << GetShaderTypeString(shaderType2) << " shader) which don't have instance names.";
}

bool ValidateInstancelessGraphicsInterfaceBlocksPerShader(
    const std::vector<sh::InterfaceBlock> &interfaceBlocks,
    ShaderType shaderType,
    InterfaceBlockMap *instancelessBlocksFields,
    InfoLog &infoLog)
{
    ASSERT(instancelessBlocksFields);

    for (const sh::InterfaceBlock &block : interfaceBlocks)
    {
        if (!block.instanceName.empty())
        {
            continue;
        }

        for (const sh::ShaderVariable &field : block.fields)
        {
            const auto &entry = instancelessBlocksFields->find(field.name);
            if (entry != instancelessBlocksFields->end())
            {
                const sh::InterfaceBlock &linkedBlock = *(entry->second.second);
                if (block.name != linkedBlock.name)
                {
                    LogAmbiguousFieldLinkMismatch(infoLog, block.name, linkedBlock.name, field.name,
                                                  entry->second.first, shaderType);
                    return false;
                }
            }
            else
            {
                (*instancelessBlocksFields)[field.name] = std::make_pair(shaderType, &block);
            }
        }
    }

    return true;
}

LinkMismatchError LinkValidateInterfaceBlockFields(const sh::ShaderVariable &blockField1,
                                                   const sh::ShaderVariable &blockField2,
                                                   bool webglCompatibility,
                                                   std::string *mismatchedBlockFieldName)
{
    if (blockField1.name != blockField2.name)
    {
        return LinkMismatchError::FIELD_NAME_MISMATCH;
    }

    // If webgl, validate precision of UBO fields, otherwise don't.  See Khronos bug 10287.
    LinkMismatchError linkError = LinkValidateProgramVariables(
        blockField1, blockField2, webglCompatibility, false, false, mismatchedBlockFieldName);
    if (linkError != LinkMismatchError::NO_MISMATCH)
    {
        AddProgramVariableParentPrefix(blockField1.name, mismatchedBlockFieldName);
        return linkError;
    }

    if (blockField1.isRowMajorLayout != blockField2.isRowMajorLayout)
    {
        AddProgramVariableParentPrefix(blockField1.name, mismatchedBlockFieldName);
        return LinkMismatchError::MATRIX_PACKING_MISMATCH;
    }

    return LinkMismatchError::NO_MISMATCH;
}

LinkMismatchError AreMatchingInterfaceBlocks(const sh::InterfaceBlock &interfaceBlock1,
                                             const sh::InterfaceBlock &interfaceBlock2,
                                             bool webglCompatibility,
                                             std::string *mismatchedBlockFieldName)
{
    // validate blocks for the same member types
    if (interfaceBlock1.fields.size() != interfaceBlock2.fields.size())
    {
        return LinkMismatchError::FIELD_NUMBER_MISMATCH;
    }
    if (interfaceBlock1.arraySize != interfaceBlock2.arraySize)
    {
        return LinkMismatchError::ARRAY_SIZE_MISMATCH;
    }
    if (interfaceBlock1.layout != interfaceBlock2.layout ||
        interfaceBlock1.binding != interfaceBlock2.binding)
    {
        return LinkMismatchError::LAYOUT_QUALIFIER_MISMATCH;
    }
    if (interfaceBlock1.instanceName.empty() != interfaceBlock2.instanceName.empty())
    {
        return LinkMismatchError::INSTANCE_NAME_MISMATCH;
    }
    const unsigned int numBlockMembers = static_cast<unsigned int>(interfaceBlock1.fields.size());
    for (unsigned int blockMemberIndex = 0; blockMemberIndex < numBlockMembers; blockMemberIndex++)
    {
        const sh::ShaderVariable &member1 = interfaceBlock1.fields[blockMemberIndex];
        const sh::ShaderVariable &member2 = interfaceBlock2.fields[blockMemberIndex];

        LinkMismatchError linkError = LinkValidateInterfaceBlockFields(
            member1, member2, webglCompatibility, mismatchedBlockFieldName);
        if (linkError != LinkMismatchError::NO_MISMATCH)
        {
            return linkError;
        }
    }
    return LinkMismatchError::NO_MISMATCH;
}

void InitializeInterfaceBlockMap(const std::vector<sh::InterfaceBlock> &interfaceBlocks,
                                 ShaderType shaderType,
                                 InterfaceBlockMap *linkedInterfaceBlocks)
{
    ASSERT(linkedInterfaceBlocks);

    for (const sh::InterfaceBlock &interfaceBlock : interfaceBlocks)
    {
        (*linkedInterfaceBlocks)[interfaceBlock.name] = std::make_pair(shaderType, &interfaceBlock);
    }
}

bool ValidateGraphicsInterfaceBlocksPerShader(
    const std::vector<sh::InterfaceBlock> &interfaceBlocksToLink,
    ShaderType shaderType,
    bool webglCompatibility,
    InterfaceBlockMap *linkedBlocks,
    InfoLog &infoLog)
{
    ASSERT(linkedBlocks);

    for (const sh::InterfaceBlock &block : interfaceBlocksToLink)
    {
        const auto &entry = linkedBlocks->find(block.name);
        if (entry != linkedBlocks->end())
        {
            const sh::InterfaceBlock &linkedBlock = *(entry->second.second);
            std::string mismatchedStructFieldName;
            LinkMismatchError linkError = AreMatchingInterfaceBlocks(
                block, linkedBlock, webglCompatibility, &mismatchedStructFieldName);
            if (linkError != LinkMismatchError::NO_MISMATCH)
            {
                LogLinkMismatch(infoLog, block.name, GetInterfaceBlockTypeString(block.blockType),
                                linkError, mismatchedStructFieldName, entry->second.first,
                                shaderType);
                return false;
            }
        }
        else
        {
            (*linkedBlocks)[block.name] = std::make_pair(shaderType, &block);
        }
    }

    return true;
}

bool ValidateInterfaceBlocksMatch(
    GLuint numShadersHasInterfaceBlocks,
    const ShaderMap<const std::vector<sh::InterfaceBlock> *> &shaderInterfaceBlocks,
    InfoLog &infoLog,
    bool webglCompatibility,
    InterfaceBlockMap *instancelessInterfaceBlocksFields)
{
    for (ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        // Validate that instanceless blocks of different names don't have fields of the same name.
        if (shaderInterfaceBlocks[shaderType] &&
            !ValidateInstancelessGraphicsInterfaceBlocksPerShader(
                *shaderInterfaceBlocks[shaderType], shaderType, instancelessInterfaceBlocksFields,
                infoLog))
        {
            return false;
        }
    }

    if (numShadersHasInterfaceBlocks < 2u)
    {
        return true;
    }

    ASSERT(!shaderInterfaceBlocks[ShaderType::Compute]);

    // Check that interface blocks defined in the graphics shaders are identical

    InterfaceBlockMap linkedInterfaceBlocks;

    bool interfaceBlockMapInitialized = false;
    for (ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        if (!shaderInterfaceBlocks[shaderType])
        {
            continue;
        }

        if (!interfaceBlockMapInitialized)
        {
            InitializeInterfaceBlockMap(*shaderInterfaceBlocks[shaderType], shaderType,
                                        &linkedInterfaceBlocks);
            interfaceBlockMapInitialized = true;
        }
        else if (!ValidateGraphicsInterfaceBlocksPerShader(*shaderInterfaceBlocks[shaderType],
                                                           shaderType, webglCompatibility,
                                                           &linkedInterfaceBlocks, infoLog))
        {
            return false;
        }
    }

    return true;
}

bool LinkValidateProgramInterfaceBlocks(const Context *context,
                                        ShaderBitSet activeProgramStages,
                                        const ProgramLinkedResources &resources,
                                        InfoLog &infoLog,
                                        GLuint *combinedShaderStorageBlocksCountOut)
{
    ASSERT(combinedShaderStorageBlocksCountOut);

    const Caps &caps              = context->getCaps();
    const bool webglCompatibility = context->isWebGL();
    const Version &version        = context->getClientVersion();

    GLuint combinedUniformBlocksCount                                         = 0u;
    GLuint numShadersHasUniformBlocks                                         = 0u;
    ShaderMap<const std::vector<sh::InterfaceBlock> *> allShaderUniformBlocks = {};
    InterfaceBlockMap instancelessInterfaceBlocksFields;

    for (ShaderType shaderType : activeProgramStages)
    {
        const std::vector<sh::InterfaceBlock> &uniformBlocks =
            resources.uniformBlockLinker.getShaderBlocks(shaderType);
        if (!uniformBlocks.empty())
        {
            if (!ValidateInterfaceBlocksCount(
                    static_cast<GLuint>(caps.maxShaderUniformBlocks[shaderType]), uniformBlocks,
                    shaderType, sh::BlockType::BLOCK_UNIFORM, &combinedUniformBlocksCount, infoLog))
            {
                return false;
            }

            allShaderUniformBlocks[shaderType] = &uniformBlocks;
            ++numShadersHasUniformBlocks;
        }
    }

    if (combinedUniformBlocksCount > static_cast<GLuint>(caps.maxCombinedUniformBlocks))
    {
        infoLog << "The sum of the number of active uniform blocks exceeds "
                   "MAX_COMBINED_UNIFORM_BLOCKS ("
                << caps.maxCombinedUniformBlocks << ").";
        return false;
    }

    if (!ValidateInterfaceBlocksMatch(numShadersHasUniformBlocks, allShaderUniformBlocks, infoLog,
                                      webglCompatibility, &instancelessInterfaceBlocksFields))
    {
        return false;
    }

    if (version >= Version(3, 1))
    {
        *combinedShaderStorageBlocksCountOut                                      = 0u;
        GLuint numShadersHasShaderStorageBlocks                                   = 0u;
        ShaderMap<const std::vector<sh::InterfaceBlock> *> allShaderStorageBlocks = {};
        for (ShaderType shaderType : activeProgramStages)
        {
            const std::vector<sh::InterfaceBlock> &shaderStorageBlocks =
                resources.shaderStorageBlockLinker.getShaderBlocks(shaderType);
            if (!shaderStorageBlocks.empty())
            {
                if (!ValidateInterfaceBlocksCount(
                        static_cast<GLuint>(caps.maxShaderStorageBlocks[shaderType]),
                        shaderStorageBlocks, shaderType, sh::BlockType::BLOCK_BUFFER,
                        combinedShaderStorageBlocksCountOut, infoLog))
                {
                    return false;
                }

                allShaderStorageBlocks[shaderType] = &shaderStorageBlocks;
                ++numShadersHasShaderStorageBlocks;
            }
        }

        if (*combinedShaderStorageBlocksCountOut >
            static_cast<GLuint>(caps.maxCombinedShaderStorageBlocks))
        {
            infoLog << "The sum of the number of active shader storage blocks exceeds "
                       "MAX_COMBINED_SHADER_STORAGE_BLOCKS ("
                    << caps.maxCombinedShaderStorageBlocks << ").";
            return false;
        }

        if (!ValidateInterfaceBlocksMatch(numShadersHasShaderStorageBlocks, allShaderStorageBlocks,
                                          infoLog, webglCompatibility,
                                          &instancelessInterfaceBlocksFields))
        {
            return false;
        }
    }

    return true;
}

}  // namespace gl
