//
// Copyright 2020 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.
//
// ProgramExecutable.cpp: Collects the interfaces common to both Programs and
// ProgramPipelines in order to execute/draw with either.

#include "libANGLE/ProgramExecutable.h"

#include "common/string_utils.h"
#include "libANGLE/Context.h"
#include "libANGLE/Program.h"
#include "libANGLE/Shader.h"

namespace gl
{
namespace
{
bool IncludeSameArrayElement(const std::set<std::string> &nameSet, const std::string &name)
{
    std::vector<unsigned int> subscripts;
    std::string baseName = ParseResourceName(name, &subscripts);
    for (const std::string &nameInSet : nameSet)
    {
        std::vector<unsigned int> arrayIndices;
        std::string arrayName = ParseResourceName(nameInSet, &arrayIndices);
        if (baseName == arrayName &&
            (subscripts.empty() || arrayIndices.empty() || subscripts == arrayIndices))
        {
            return true;
        }
    }
    return false;
}

// Find the matching varying or field by name.
const sh::ShaderVariable *FindOutputVaryingOrField(const ProgramMergedVaryings &varyings,
                                                   ShaderType stage,
                                                   const std::string &name)
{
    const sh::ShaderVariable *var = nullptr;
    for (const ProgramVaryingRef &ref : varyings)
    {
        if (ref.frontShaderStage != stage)
        {
            continue;
        }

        const sh::ShaderVariable *varying = ref.get(stage);
        if (varying->name == name)
        {
            var = varying;
            break;
        }
        GLuint fieldIndex = 0;
        var               = varying->findField(name, &fieldIndex);
        if (var != nullptr)
        {
            break;
        }
    }
    return var;
}

bool FindUsedOutputLocation(std::vector<VariableLocation> &outputLocations,
                            unsigned int baseLocation,
                            unsigned int elementCount,
                            const std::vector<VariableLocation> &reservedLocations,
                            unsigned int variableIndex)
{
    if (baseLocation + elementCount > outputLocations.size())
    {
        elementCount = baseLocation < outputLocations.size()
                           ? static_cast<unsigned int>(outputLocations.size() - baseLocation)
                           : 0;
    }
    for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
    {
        const unsigned int location = baseLocation + elementIndex;
        if (outputLocations[location].used())
        {
            VariableLocation locationInfo(elementIndex, variableIndex);
            if (std::find(reservedLocations.begin(), reservedLocations.end(), locationInfo) ==
                reservedLocations.end())
            {
                return true;
            }
        }
    }
    return false;
}

void AssignOutputLocations(std::vector<VariableLocation> &outputLocations,
                           unsigned int baseLocation,
                           unsigned int elementCount,
                           const std::vector<VariableLocation> &reservedLocations,
                           unsigned int variableIndex,
                           sh::ShaderVariable &outputVariable)
{
    if (baseLocation + elementCount > outputLocations.size())
    {
        outputLocations.resize(baseLocation + elementCount);
    }
    for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
    {
        VariableLocation locationInfo(elementIndex, variableIndex);
        if (std::find(reservedLocations.begin(), reservedLocations.end(), locationInfo) ==
            reservedLocations.end())
        {
            outputVariable.location     = baseLocation;
            const unsigned int location = baseLocation + elementIndex;
            outputLocations[location]   = locationInfo;
        }
    }
}

int GetOutputLocationForLink(const ProgramAliasedBindings &fragmentOutputLocations,
                             const sh::ShaderVariable &outputVariable)
{
    if (outputVariable.location != -1)
    {
        return outputVariable.location;
    }
    int apiLocation = fragmentOutputLocations.getBinding(outputVariable);
    if (apiLocation != -1)
    {
        return apiLocation;
    }
    return -1;
}

bool IsOutputSecondaryForLink(const ProgramAliasedBindings &fragmentOutputIndexes,
                              const sh::ShaderVariable &outputVariable)
{
    if (outputVariable.index != -1)
    {
        ASSERT(outputVariable.index == 0 || outputVariable.index == 1);
        return (outputVariable.index == 1);
    }
    int apiIndex = fragmentOutputIndexes.getBinding(outputVariable);
    if (apiIndex != -1)
    {
        // Index layout qualifier from the shader takes precedence, so the index from the API is
        // checked only if the index was not set in the shader. This is not specified in the EXT
        // spec, but is specified in desktop OpenGL specs.
        return (apiIndex == 1);
    }
    // EXT_blend_func_extended: Outputs get index 0 by default.
    return false;
}

}  // anonymous namespace

ProgramExecutable::ProgramExecutable()
    : mMaxActiveAttribLocation(0),
      mAttributesTypeMask(0),
      mAttributesMask(0),
      mActiveSamplerRefCounts{},
      mCanDrawWith(false),
      mYUVOutput(false),
      mTransformFeedbackBufferMode(GL_INTERLEAVED_ATTRIBS),
      mDefaultUniformRange(0, 0),
      mSamplerUniformRange(0, 0),
      mImageUniformRange(0, 0),
      mFragmentInoutRange(0, 0),
      mPipelineHasUniformBuffers(false),
      mPipelineHasStorageBuffers(false),
      mPipelineHasAtomicCounterBuffers(false),
      mPipelineHasDefaultUniforms(false),
      mPipelineHasTextures(false),
      mPipelineHasImages(false),
      // [GL_EXT_geometry_shader] Table 20.22
      mGeometryShaderInputPrimitiveType(PrimitiveMode::Triangles),
      mGeometryShaderOutputPrimitiveType(PrimitiveMode::TriangleStrip),
      mGeometryShaderInvocations(1),
      mGeometryShaderMaxVertices(0),
      mTessControlShaderVertices(0),
      mTessGenMode(GL_NONE),
      mTessGenSpacing(GL_NONE),
      mTessGenVertexOrder(GL_NONE),
      mTessGenPointMode(GL_NONE)
{
    reset();
}

ProgramExecutable::ProgramExecutable(const ProgramExecutable &other)
    : mLinkedShaderStages(other.mLinkedShaderStages),
      mActiveAttribLocationsMask(other.mActiveAttribLocationsMask),
      mMaxActiveAttribLocation(other.mMaxActiveAttribLocation),
      mAttributesTypeMask(other.mAttributesTypeMask),
      mAttributesMask(other.mAttributesMask),
      mActiveSamplersMask(other.mActiveSamplersMask),
      mActiveSamplerRefCounts(other.mActiveSamplerRefCounts),
      mActiveSamplerTypes(other.mActiveSamplerTypes),
      mActiveSamplerYUV(other.mActiveSamplerYUV),
      mActiveSamplerFormats(other.mActiveSamplerFormats),
      mActiveSamplerShaderBits(other.mActiveSamplerShaderBits),
      mActiveImagesMask(other.mActiveImagesMask),
      mActiveImageShaderBits(other.mActiveImageShaderBits),
      mCanDrawWith(other.mCanDrawWith),
      mOutputVariables(other.mOutputVariables),
      mOutputLocations(other.mOutputLocations),
      mSecondaryOutputLocations(other.mSecondaryOutputLocations),
      mYUVOutput(other.mYUVOutput),
      mProgramInputs(other.mProgramInputs),
      mLinkedTransformFeedbackVaryings(other.mLinkedTransformFeedbackVaryings),
      mTransformFeedbackStrides(other.mTransformFeedbackStrides),
      mTransformFeedbackBufferMode(other.mTransformFeedbackBufferMode),
      mUniforms(other.mUniforms),
      mDefaultUniformRange(other.mDefaultUniformRange),
      mSamplerUniformRange(other.mSamplerUniformRange),
      mUniformBlocks(other.mUniformBlocks),
      mActiveUniformBlockBindings(other.mActiveUniformBlockBindings),
      mAtomicCounterBuffers(other.mAtomicCounterBuffers),
      mImageUniformRange(other.mImageUniformRange),
      mShaderStorageBlocks(other.mShaderStorageBlocks),
      mFragmentInoutRange(other.mFragmentInoutRange),
      mPipelineHasUniformBuffers(other.mPipelineHasUniformBuffers),
      mPipelineHasStorageBuffers(other.mPipelineHasStorageBuffers),
      mPipelineHasAtomicCounterBuffers(other.mPipelineHasAtomicCounterBuffers),
      mPipelineHasDefaultUniforms(other.mPipelineHasDefaultUniforms),
      mPipelineHasTextures(other.mPipelineHasTextures),
      mPipelineHasImages(other.mPipelineHasImages)
{
    reset();
}

ProgramExecutable::~ProgramExecutable() = default;

void ProgramExecutable::reset()
{
    resetInfoLog();
    mActiveAttribLocationsMask.reset();
    mAttributesTypeMask.reset();
    mAttributesMask.reset();
    mMaxActiveAttribLocation = 0;

    mActiveSamplersMask.reset();
    mActiveSamplerRefCounts = {};
    mActiveSamplerTypes.fill(TextureType::InvalidEnum);
    mActiveSamplerYUV.reset();
    mActiveSamplerFormats.fill(SamplerFormat::InvalidEnum);

    mActiveImagesMask.reset();

    mProgramInputs.clear();
    mLinkedTransformFeedbackVaryings.clear();
    mTransformFeedbackStrides.clear();
    mUniforms.clear();
    mUniformBlocks.clear();
    mActiveUniformBlockBindings.reset();
    mShaderStorageBlocks.clear();
    mAtomicCounterBuffers.clear();
    mOutputVariables.clear();
    mOutputLocations.clear();
    mActiveOutputVariablesMask.reset();
    mSecondaryOutputLocations.clear();
    mYUVOutput = false;
    mSamplerBindings.clear();
    mImageBindings.clear();

    mPipelineHasUniformBuffers       = false;
    mPipelineHasStorageBuffers       = false;
    mPipelineHasAtomicCounterBuffers = false;
    mPipelineHasDefaultUniforms      = false;
    mPipelineHasTextures             = false;

    mGeometryShaderInputPrimitiveType  = PrimitiveMode::Triangles;
    mGeometryShaderOutputPrimitiveType = PrimitiveMode::TriangleStrip;
    mGeometryShaderInvocations         = 1;
    mGeometryShaderMaxVertices         = 0;

    mTessControlShaderVertices = 0;
    mTessGenMode               = GL_NONE;
    mTessGenSpacing            = GL_NONE;
    mTessGenVertexOrder        = GL_NONE;
    mTessGenPointMode          = GL_NONE;

    mOutputVariableTypes.clear();
    mDrawBufferTypeMask.reset();
}

void ProgramExecutable::load(bool isSeparable, gl::BinaryInputStream *stream)
{
    static_assert(MAX_VERTEX_ATTRIBS * 2 <= sizeof(uint32_t) * 8,
                  "Too many vertex attribs for mask: All bits of mAttributesTypeMask types and "
                  "mask fit into 32 bits each");
    mAttributesTypeMask        = gl::ComponentTypeMask(stream->readInt<uint32_t>());
    mAttributesMask            = gl::AttributesMask(stream->readInt<uint32_t>());
    mActiveAttribLocationsMask = gl::AttributesMask(stream->readInt<uint32_t>());
    mMaxActiveAttribLocation   = stream->readInt<unsigned int>();

    unsigned int fragmentInoutRangeLow  = stream->readInt<uint32_t>();
    unsigned int fragmentInoutRangeHigh = stream->readInt<uint32_t>();
    mFragmentInoutRange                 = RangeUI(fragmentInoutRangeLow, fragmentInoutRangeHigh);

    mLinkedShaderStages = ShaderBitSet(stream->readInt<uint8_t>());

    mPipelineHasUniformBuffers       = stream->readBool();
    mPipelineHasStorageBuffers       = stream->readBool();
    mPipelineHasAtomicCounterBuffers = stream->readBool();
    mPipelineHasDefaultUniforms      = stream->readBool();
    mPipelineHasTextures             = stream->readBool();

    mGeometryShaderInputPrimitiveType  = stream->readEnum<PrimitiveMode>();
    mGeometryShaderOutputPrimitiveType = stream->readEnum<PrimitiveMode>();
    mGeometryShaderInvocations         = stream->readInt<int>();
    mGeometryShaderMaxVertices         = stream->readInt<int>();

    mTessControlShaderVertices = stream->readInt<int>();
    mTessGenMode               = stream->readInt<GLenum>();
    mTessGenSpacing            = stream->readInt<GLenum>();
    mTessGenVertexOrder        = stream->readInt<GLenum>();
    mTessGenPointMode          = stream->readInt<GLenum>();

    size_t attribCount = stream->readInt<size_t>();
    ASSERT(getProgramInputs().empty());
    for (size_t attribIndex = 0; attribIndex < attribCount; ++attribIndex)
    {
        sh::ShaderVariable attrib;
        LoadShaderVar(stream, &attrib);
        attrib.location = stream->readInt<int>();
        mProgramInputs.push_back(attrib);
    }

    size_t uniformCount = stream->readInt<size_t>();
    ASSERT(getUniforms().empty());
    for (size_t uniformIndex = 0; uniformIndex < uniformCount; ++uniformIndex)
    {
        LinkedUniform uniform;
        LoadShaderVar(stream, &uniform);

        uniform.bufferIndex = stream->readInt<int>();
        LoadBlockMemberInfo(stream, &uniform.blockInfo);

        stream->readIntVector<unsigned int>(&uniform.outerArraySizes);

        uniform.typeInfo = &GetUniformTypeInfo(uniform.type);

        // Active shader info
        for (ShaderType shaderType : gl::AllShaderTypes())
        {
            uniform.setActive(shaderType, stream->readBool());
        }

        mUniforms.push_back(uniform);
    }

    size_t uniformBlockCount = stream->readInt<size_t>();
    ASSERT(getUniformBlocks().empty());
    for (size_t uniformBlockIndex = 0; uniformBlockIndex < uniformBlockCount; ++uniformBlockIndex)
    {
        InterfaceBlock uniformBlock;
        LoadInterfaceBlock(stream, &uniformBlock);
        mUniformBlocks.push_back(uniformBlock);

        mActiveUniformBlockBindings.set(uniformBlockIndex, uniformBlock.binding != 0);
    }

    size_t shaderStorageBlockCount = stream->readInt<size_t>();
    ASSERT(getShaderStorageBlocks().empty());
    for (size_t shaderStorageBlockIndex = 0; shaderStorageBlockIndex < shaderStorageBlockCount;
         ++shaderStorageBlockIndex)
    {
        InterfaceBlock shaderStorageBlock;
        LoadInterfaceBlock(stream, &shaderStorageBlock);
        mShaderStorageBlocks.push_back(shaderStorageBlock);
    }

    size_t atomicCounterBufferCount = stream->readInt<size_t>();
    ASSERT(getAtomicCounterBuffers().empty());
    for (size_t bufferIndex = 0; bufferIndex < atomicCounterBufferCount; ++bufferIndex)
    {
        AtomicCounterBuffer atomicCounterBuffer;
        LoadShaderVariableBuffer(stream, &atomicCounterBuffer);

        mAtomicCounterBuffers.push_back(atomicCounterBuffer);
    }

    size_t transformFeedbackVaryingCount = stream->readInt<size_t>();
    ASSERT(mLinkedTransformFeedbackVaryings.empty());
    for (size_t transformFeedbackVaryingIndex = 0;
         transformFeedbackVaryingIndex < transformFeedbackVaryingCount;
         ++transformFeedbackVaryingIndex)
    {
        sh::ShaderVariable varying;
        stream->readIntVector<unsigned int>(&varying.arraySizes);
        stream->readInt(&varying.type);
        stream->readString(&varying.name);

        GLuint arrayIndex = stream->readInt<GLuint>();

        mLinkedTransformFeedbackVaryings.emplace_back(varying, arrayIndex);
    }

    mTransformFeedbackBufferMode = stream->readInt<GLint>();

    size_t outputCount = stream->readInt<size_t>();
    ASSERT(getOutputVariables().empty());
    for (size_t outputIndex = 0; outputIndex < outputCount; ++outputIndex)
    {
        sh::ShaderVariable output;
        LoadShaderVar(stream, &output);
        output.location = stream->readInt<int>();
        output.index    = stream->readInt<int>();
        mOutputVariables.push_back(output);
    }

    size_t outputVarCount = stream->readInt<size_t>();
    ASSERT(getOutputLocations().empty());
    for (size_t outputIndex = 0; outputIndex < outputVarCount; ++outputIndex)
    {
        VariableLocation locationData;
        stream->readInt(&locationData.arrayIndex);
        stream->readInt(&locationData.index);
        stream->readBool(&locationData.ignored);
        mOutputLocations.push_back(locationData);
    }

    mActiveOutputVariablesMask =
        gl::DrawBufferMask(stream->readInt<gl::DrawBufferMask::value_type>());

    size_t outputTypeCount = stream->readInt<size_t>();
    for (size_t outputIndex = 0; outputIndex < outputTypeCount; ++outputIndex)
    {
        mOutputVariableTypes.push_back(stream->readInt<GLenum>());
    }

    static_assert(IMPLEMENTATION_MAX_DRAW_BUFFERS * 2 <= 8 * sizeof(uint32_t),
                  "All bits of mDrawBufferTypeMask and mActiveOutputVariables types and mask fit "
                  "into 32 bits each");
    mDrawBufferTypeMask = gl::ComponentTypeMask(stream->readInt<uint32_t>());

    stream->readBool(&mYUVOutput);

    size_t secondaryOutputVarCount = stream->readInt<size_t>();
    ASSERT(getSecondaryOutputLocations().empty());
    for (size_t outputIndex = 0; outputIndex < secondaryOutputVarCount; ++outputIndex)
    {
        VariableLocation locationData;
        stream->readInt(&locationData.arrayIndex);
        stream->readInt(&locationData.index);
        stream->readBool(&locationData.ignored);
        mSecondaryOutputLocations.push_back(locationData);
    }

    unsigned int defaultUniformRangeLow  = stream->readInt<unsigned int>();
    unsigned int defaultUniformRangeHigh = stream->readInt<unsigned int>();
    mDefaultUniformRange                 = RangeUI(defaultUniformRangeLow, defaultUniformRangeHigh);

    unsigned int samplerRangeLow  = stream->readInt<unsigned int>();
    unsigned int samplerRangeHigh = stream->readInt<unsigned int>();
    mSamplerUniformRange          = RangeUI(samplerRangeLow, samplerRangeHigh);

    size_t samplerCount = stream->readInt<size_t>();
    for (size_t samplerIndex = 0; samplerIndex < samplerCount; ++samplerIndex)
    {
        TextureType textureType = stream->readEnum<TextureType>();
        GLenum samplerType      = stream->readInt<GLenum>();
        SamplerFormat format    = stream->readEnum<SamplerFormat>();
        size_t bindingCount     = stream->readInt<size_t>();
        mSamplerBindings.emplace_back(textureType, samplerType, format, bindingCount);
    }

    unsigned int imageRangeLow  = stream->readInt<unsigned int>();
    unsigned int imageRangeHigh = stream->readInt<unsigned int>();
    mImageUniformRange          = RangeUI(imageRangeLow, imageRangeHigh);

    size_t imageBindingCount = stream->readInt<size_t>();
    for (size_t imageIndex = 0; imageIndex < imageBindingCount; ++imageIndex)
    {
        size_t elementCount     = stream->readInt<size_t>();
        TextureType textureType = static_cast<TextureType>(stream->readInt<unsigned int>());
        ImageBinding imageBinding(elementCount, textureType);
        for (size_t elementIndex = 0; elementIndex < elementCount; ++elementIndex)
        {
            imageBinding.boundImageUnits[elementIndex] = stream->readInt<unsigned int>();
        }
        mImageBindings.emplace_back(imageBinding);
    }

    // These values are currently only used by PPOs, so only load them when the program is marked
    // separable to save memory.
    if (isSeparable)
    {
        for (ShaderType shaderType : mLinkedShaderStages)
        {
            mLinkedOutputVaryings[shaderType].resize(stream->readInt<size_t>());
            for (sh::ShaderVariable &variable : mLinkedOutputVaryings[shaderType])
            {
                LoadShaderVar(stream, &variable);
            }
            mLinkedInputVaryings[shaderType].resize(stream->readInt<size_t>());
            for (sh::ShaderVariable &variable : mLinkedInputVaryings[shaderType])
            {
                LoadShaderVar(stream, &variable);
            }
            mLinkedUniforms[shaderType].resize(stream->readInt<size_t>());
            for (sh::ShaderVariable &variable : mLinkedUniforms[shaderType])
            {
                LoadShaderVar(stream, &variable);
            }
            mLinkedUniformBlocks[shaderType].resize(stream->readInt<size_t>());
            for (sh::InterfaceBlock &shaderStorageBlock : mLinkedUniformBlocks[shaderType])
            {
                LoadShInterfaceBlock(stream, &shaderStorageBlock);
            }
            mLinkedShaderVersions[shaderType] = stream->readInt<int>();
        }
    }
}

void ProgramExecutable::save(bool isSeparable, gl::BinaryOutputStream *stream) const
{
    static_assert(MAX_VERTEX_ATTRIBS * 2 <= sizeof(uint32_t) * 8,
                  "All bits of mAttributesTypeMask types and mask fit into 32 bits each");
    stream->writeInt(static_cast<uint32_t>(mAttributesTypeMask.to_ulong()));
    stream->writeInt(static_cast<uint32_t>(mAttributesMask.to_ulong()));
    stream->writeInt(static_cast<uint32_t>(mActiveAttribLocationsMask.to_ulong()));
    stream->writeInt(mMaxActiveAttribLocation);

    stream->writeInt(mFragmentInoutRange.low());
    stream->writeInt(mFragmentInoutRange.high());

    stream->writeInt(mLinkedShaderStages.bits());

    stream->writeBool(mPipelineHasUniformBuffers);
    stream->writeBool(mPipelineHasStorageBuffers);
    stream->writeBool(mPipelineHasAtomicCounterBuffers);
    stream->writeBool(mPipelineHasDefaultUniforms);
    stream->writeBool(mPipelineHasTextures);

    ASSERT(mGeometryShaderInvocations >= 1 && mGeometryShaderMaxVertices >= 0);
    stream->writeEnum(mGeometryShaderInputPrimitiveType);
    stream->writeEnum(mGeometryShaderOutputPrimitiveType);
    stream->writeInt(mGeometryShaderInvocations);
    stream->writeInt(mGeometryShaderMaxVertices);

    stream->writeInt(mTessControlShaderVertices);
    stream->writeInt(mTessGenMode);
    stream->writeInt(mTessGenSpacing);
    stream->writeInt(mTessGenVertexOrder);
    stream->writeInt(mTessGenPointMode);

    stream->writeInt(getProgramInputs().size());
    for (const sh::ShaderVariable &attrib : getProgramInputs())
    {
        WriteShaderVar(stream, attrib);
        stream->writeInt(attrib.location);
    }

    stream->writeInt(getUniforms().size());
    for (const LinkedUniform &uniform : getUniforms())
    {
        WriteShaderVar(stream, uniform);

        stream->writeInt(uniform.bufferIndex);
        WriteBlockMemberInfo(stream, uniform.blockInfo);

        stream->writeIntVector(uniform.outerArraySizes);

        // Active shader info
        for (ShaderType shaderType : gl::AllShaderTypes())
        {
            stream->writeBool(uniform.isActive(shaderType));
        }
    }

    stream->writeInt(getUniformBlocks().size());
    for (const InterfaceBlock &uniformBlock : getUniformBlocks())
    {
        WriteInterfaceBlock(stream, uniformBlock);
    }

    stream->writeInt(getShaderStorageBlocks().size());
    for (const InterfaceBlock &shaderStorageBlock : getShaderStorageBlocks())
    {
        WriteInterfaceBlock(stream, shaderStorageBlock);
    }

    stream->writeInt(mAtomicCounterBuffers.size());
    for (const AtomicCounterBuffer &atomicCounterBuffer : getAtomicCounterBuffers())
    {
        WriteShaderVariableBuffer(stream, atomicCounterBuffer);
    }

    stream->writeInt(getLinkedTransformFeedbackVaryings().size());
    for (const auto &var : getLinkedTransformFeedbackVaryings())
    {
        stream->writeIntVector(var.arraySizes);
        stream->writeInt(var.type);
        stream->writeString(var.name);

        stream->writeIntOrNegOne(var.arrayIndex);
    }

    stream->writeInt(getTransformFeedbackBufferMode());

    stream->writeInt(getOutputVariables().size());
    for (const sh::ShaderVariable &output : getOutputVariables())
    {
        WriteShaderVar(stream, output);
        stream->writeInt(output.location);
        stream->writeInt(output.index);
    }

    stream->writeInt(getOutputLocations().size());
    for (const auto &outputVar : getOutputLocations())
    {
        stream->writeInt(outputVar.arrayIndex);
        stream->writeIntOrNegOne(outputVar.index);
        stream->writeBool(outputVar.ignored);
    }

    stream->writeInt(static_cast<int>(mActiveOutputVariablesMask.to_ulong()));

    stream->writeInt(mOutputVariableTypes.size());
    for (const auto &outputVariableType : mOutputVariableTypes)
    {
        stream->writeInt(outputVariableType);
    }

    static_assert(
        IMPLEMENTATION_MAX_DRAW_BUFFERS * 2 <= 8 * sizeof(uint32_t),
        "All bits of mDrawBufferTypeMask and mActiveOutputVariables can be contained in 32 bits");
    stream->writeInt(static_cast<int>(mDrawBufferTypeMask.to_ulong()));

    stream->writeBool(mYUVOutput);

    stream->writeInt(getSecondaryOutputLocations().size());
    for (const auto &outputVar : getSecondaryOutputLocations())
    {
        stream->writeInt(outputVar.arrayIndex);
        stream->writeIntOrNegOne(outputVar.index);
        stream->writeBool(outputVar.ignored);
    }

    stream->writeInt(getDefaultUniformRange().low());
    stream->writeInt(getDefaultUniformRange().high());

    stream->writeInt(getSamplerUniformRange().low());
    stream->writeInt(getSamplerUniformRange().high());

    stream->writeInt(getSamplerBindings().size());
    for (const auto &samplerBinding : getSamplerBindings())
    {
        stream->writeEnum(samplerBinding.textureType);
        stream->writeInt(samplerBinding.samplerType);
        stream->writeEnum(samplerBinding.format);
        stream->writeInt(samplerBinding.boundTextureUnits.size());
    }

    stream->writeInt(getImageUniformRange().low());
    stream->writeInt(getImageUniformRange().high());

    stream->writeInt(getImageBindings().size());
    for (const auto &imageBinding : getImageBindings())
    {
        stream->writeInt(imageBinding.boundImageUnits.size());
        stream->writeInt(static_cast<unsigned int>(imageBinding.textureType));
        for (size_t i = 0; i < imageBinding.boundImageUnits.size(); ++i)
        {
            stream->writeInt(imageBinding.boundImageUnits[i]);
        }
    }

    // These values are currently only used by PPOs, so only save them when the program is marked
    // separable to save memory.
    if (isSeparable)
    {
        for (ShaderType shaderType : mLinkedShaderStages)
        {
            stream->writeInt(mLinkedOutputVaryings[shaderType].size());
            for (const sh::ShaderVariable &shaderVariable : mLinkedOutputVaryings[shaderType])
            {
                WriteShaderVar(stream, shaderVariable);
            }
            stream->writeInt(mLinkedInputVaryings[shaderType].size());
            for (const sh::ShaderVariable &shaderVariable : mLinkedInputVaryings[shaderType])
            {
                WriteShaderVar(stream, shaderVariable);
            }
            stream->writeInt(mLinkedUniforms[shaderType].size());
            for (const sh::ShaderVariable &shaderVariable : mLinkedUniforms[shaderType])
            {
                WriteShaderVar(stream, shaderVariable);
            }
            stream->writeInt(mLinkedUniformBlocks[shaderType].size());
            for (const sh::InterfaceBlock &shaderStorageBlock : mLinkedUniformBlocks[shaderType])
            {
                WriteShInterfaceBlock(stream, shaderStorageBlock);
            }
            stream->writeInt(mLinkedShaderVersions[shaderType]);
        }
    }
}

int ProgramExecutable::getInfoLogLength() const
{
    return static_cast<int>(mInfoLog.getLength());
}

void ProgramExecutable::getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog) const
{
    return mInfoLog.getLog(bufSize, length, infoLog);
}

std::string ProgramExecutable::getInfoLogString() const
{
    return mInfoLog.str();
}

bool ProgramExecutable::isAttribLocationActive(size_t attribLocation) const
{
    // TODO(timvp): http://anglebug.com/3570: Enable this assert here somehow.
    //    ASSERT(!mLinkingState);
    ASSERT(attribLocation < mActiveAttribLocationsMask.size());
    return mActiveAttribLocationsMask[attribLocation];
}

AttributesMask ProgramExecutable::getAttributesMask() const
{
    // TODO(timvp): http://anglebug.com/3570: Enable this assert here somehow.
    //    ASSERT(!mLinkingState);
    return mAttributesMask;
}

bool ProgramExecutable::hasDefaultUniforms() const
{
    return !getDefaultUniformRange().empty() || mPipelineHasDefaultUniforms;
}

bool ProgramExecutable::hasTextures() const
{
    return !getSamplerBindings().empty() || mPipelineHasTextures;
}

// TODO: http://anglebug.com/3570: Remove mHas*UniformBuffers once PPO's have valid data in
// mUniformBlocks
bool ProgramExecutable::hasUniformBuffers() const
{
    return !mUniformBlocks.empty() || mPipelineHasUniformBuffers;
}

bool ProgramExecutable::hasStorageBuffers() const
{
    return !mShaderStorageBlocks.empty() || mPipelineHasStorageBuffers;
}

bool ProgramExecutable::hasAtomicCounterBuffers() const
{
    return !mAtomicCounterBuffers.empty() || mPipelineHasAtomicCounterBuffers;
}

bool ProgramExecutable::hasImages() const
{
    return !mImageBindings.empty() || mPipelineHasImages;
}

bool ProgramExecutable::usesFramebufferFetch() const
{
    return (mFragmentInoutRange.length() > 0);
}

GLuint ProgramExecutable::getUniformIndexFromImageIndex(GLuint imageIndex) const
{
    ASSERT(imageIndex < mImageUniformRange.length());
    return imageIndex + mImageUniformRange.low();
}

void ProgramExecutable::updateActiveSamplers(const ProgramState &programState)
{
    const std::vector<SamplerBinding> &samplerBindings = programState.getSamplerBindings();

    for (uint32_t samplerIndex = 0; samplerIndex < samplerBindings.size(); ++samplerIndex)
    {
        const SamplerBinding &samplerBinding = samplerBindings[samplerIndex];
        uint32_t uniformIndex = programState.getUniformIndexFromSamplerIndex(samplerIndex);
        const gl::LinkedUniform &samplerUniform = programState.getUniforms()[uniformIndex];

        for (GLint textureUnit : samplerBinding.boundTextureUnits)
        {
            if (++mActiveSamplerRefCounts[textureUnit] == 1)
            {
                mActiveSamplerTypes[textureUnit]   = samplerBinding.textureType;
                mActiveSamplerYUV[textureUnit]     = IsSamplerYUVType(samplerBinding.samplerType);
                mActiveSamplerFormats[textureUnit] = samplerBinding.format;
                mActiveSamplerShaderBits[textureUnit] = samplerUniform.activeShaders();
            }
            else
            {
                if (mActiveSamplerTypes[textureUnit] != samplerBinding.textureType)
                {
                    // Conflicts are marked with InvalidEnum
                    mActiveSamplerTypes[textureUnit] = TextureType::InvalidEnum;
                }
                if (mActiveSamplerYUV.test(textureUnit) !=
                    IsSamplerYUVType(samplerBinding.samplerType))
                {
                    mActiveSamplerYUV[textureUnit] = false;
                }
                if (mActiveSamplerFormats[textureUnit] != samplerBinding.format)
                {
                    mActiveSamplerFormats[textureUnit] = SamplerFormat::InvalidEnum;
                }
            }
            mActiveSamplersMask.set(textureUnit);
        }
    }

    // Invalidate the validation cache.
    resetCachedValidateSamplersResult();
}

void ProgramExecutable::updateActiveImages(const ProgramExecutable &executable)
{
    const std::vector<ImageBinding> &imageBindings = executable.getImageBindings();
    for (uint32_t imageIndex = 0; imageIndex < imageBindings.size(); ++imageIndex)
    {
        const gl::ImageBinding &imageBinding = imageBindings.at(imageIndex);

        uint32_t uniformIndex = executable.getUniformIndexFromImageIndex(imageIndex);
        const gl::LinkedUniform &imageUniform = executable.getUniforms()[uniformIndex];
        const ShaderBitSet shaderBits         = imageUniform.activeShaders();
        for (GLint imageUnit : imageBinding.boundImageUnits)
        {
            mActiveImagesMask.set(imageUnit);
            mActiveImageShaderBits[imageUnit] |= shaderBits;
        }
    }
}

void ProgramExecutable::setSamplerUniformTextureTypeAndFormat(
    size_t textureUnitIndex,
    std::vector<SamplerBinding> &samplerBindings)
{
    bool foundBinding         = false;
    TextureType foundType     = TextureType::InvalidEnum;
    bool foundYUV             = false;
    SamplerFormat foundFormat = SamplerFormat::InvalidEnum;

    for (const SamplerBinding &binding : samplerBindings)
    {
        // A conflict exists if samplers of different types are sourced by the same texture unit.
        // We need to check all bound textures to detect this error case.
        for (GLuint textureUnit : binding.boundTextureUnits)
        {
            if (textureUnit == textureUnitIndex)
            {
                if (!foundBinding)
                {
                    foundBinding = true;
                    foundType    = binding.textureType;
                    foundYUV     = IsSamplerYUVType(binding.samplerType);
                    foundFormat  = binding.format;
                }
                else
                {
                    if (foundType != binding.textureType)
                    {
                        foundType = TextureType::InvalidEnum;
                    }
                    if (foundYUV != IsSamplerYUVType(binding.samplerType))
                    {
                        foundYUV = false;
                    }
                    if (foundFormat != binding.format)
                    {
                        foundFormat = SamplerFormat::InvalidEnum;
                    }
                }
            }
        }
    }

    mActiveSamplerTypes[textureUnitIndex]   = foundType;
    mActiveSamplerYUV[textureUnitIndex]     = foundYUV;
    mActiveSamplerFormats[textureUnitIndex] = foundFormat;
}

void ProgramExecutable::updateCanDrawWith()
{
    mCanDrawWith = hasLinkedShaderStage(ShaderType::Vertex);
}

void ProgramExecutable::saveLinkedStateInfo(const ProgramState &state)
{
    for (ShaderType shaderType : getLinkedShaderStages())
    {
        Shader *shader = state.getAttachedShader(shaderType);
        ASSERT(shader);
        mLinkedOutputVaryings[shaderType] = shader->getOutputVaryings();
        mLinkedInputVaryings[shaderType]  = shader->getInputVaryings();
        mLinkedShaderVersions[shaderType] = shader->getShaderVersion();
        mLinkedUniforms[shaderType]       = shader->getUniforms();
        mLinkedUniformBlocks[shaderType]  = shader->getUniformBlocks();
    }
}

bool ProgramExecutable::isYUVOutput() const
{
    return mYUVOutput;
}

ShaderType ProgramExecutable::getLinkedTransformFeedbackStage() const
{
    return GetLastPreFragmentStage(mLinkedShaderStages);
}

bool ProgramExecutable::linkMergedVaryings(
    const Context *context,
    const ProgramMergedVaryings &mergedVaryings,
    const std::vector<std::string> &transformFeedbackVaryingNames,
    const LinkingVariables &linkingVariables,
    bool isSeparable,
    ProgramVaryingPacking *varyingPacking)
{
    ShaderType tfStage = GetLastPreFragmentStage(linkingVariables.isShaderStageUsedBitset);

    if (!linkValidateTransformFeedback(context, mergedVaryings, tfStage,
                                       transformFeedbackVaryingNames))
    {
        return false;
    }

    // Map the varyings to the register file
    // In WebGL, we use a slightly different handling for packing variables.
    gl::PackMode packMode = PackMode::ANGLE_RELAXED;
    if (context->getLimitations().noFlexibleVaryingPacking)
    {
        // D3D9 pack mode is strictly more strict than WebGL, so takes priority.
        packMode = PackMode::ANGLE_NON_CONFORMANT_D3D9;
    }
    else if (context->isWebGL())
    {
        packMode = PackMode::WEBGL_STRICT;
    }

    // Build active shader stage map.
    ShaderBitSet activeShadersMask;
    for (ShaderType shaderType : kAllGraphicsShaderTypes)
    {
        // - Check for attached shaders to handle the case of a Program linking the currently
        // attached shaders.
        // - Check for linked shaders to handle the case of a PPO linking separable programs before
        // drawing.
        if (linkingVariables.isShaderStageUsedBitset[shaderType] ||
            getLinkedShaderStages().test(shaderType))
        {
            activeShadersMask[shaderType] = true;
        }
    }

    if (!varyingPacking->collectAndPackUserVaryings(mInfoLog, context->getCaps(), packMode,
                                                    activeShadersMask, mergedVaryings,
                                                    transformFeedbackVaryingNames, isSeparable))
    {
        return false;
    }

    gatherTransformFeedbackVaryings(mergedVaryings, tfStage, transformFeedbackVaryingNames);
    updateTransformFeedbackStrides();

    return true;
}

bool ProgramExecutable::linkValidateTransformFeedback(
    const Context *context,
    const ProgramMergedVaryings &varyings,
    ShaderType stage,
    const std::vector<std::string> &transformFeedbackVaryingNames)
{
    const Version &version = context->getClientVersion();

    // Validate the tf names regardless of the actual program varyings.
    std::set<std::string> uniqueNames;
    for (const std::string &tfVaryingName : transformFeedbackVaryingNames)
    {
        if (version < Version(3, 1) && tfVaryingName.find('[') != std::string::npos)
        {
            mInfoLog << "Capture of array elements is undefined and not supported.";
            return false;
        }
        if (version >= Version(3, 1))
        {
            if (IncludeSameArrayElement(uniqueNames, tfVaryingName))
            {
                mInfoLog << "Two transform feedback varyings include the same array element ("
                         << tfVaryingName << ").";
                return false;
            }
        }
        else
        {
            if (uniqueNames.count(tfVaryingName) > 0)
            {
                mInfoLog << "Two transform feedback varyings specify the same output variable ("
                         << tfVaryingName << ").";
                return false;
            }
        }
        uniqueNames.insert(tfVaryingName);
    }

    // Validate against program varyings.
    size_t totalComponents = 0;
    for (const std::string &tfVaryingName : transformFeedbackVaryingNames)
    {
        std::vector<unsigned int> subscripts;
        std::string baseName = ParseResourceName(tfVaryingName, &subscripts);

        const sh::ShaderVariable *var = FindOutputVaryingOrField(varyings, stage, baseName);
        if (var == nullptr)
        {
            mInfoLog << "Transform feedback varying " << tfVaryingName
                     << " does not exist in the vertex shader.";
            return false;
        }

        // Validate the matching variable.
        if (var->isStruct())
        {
            mInfoLog << "Struct cannot be captured directly (" << baseName << ").";
            return false;
        }

        size_t elementCount   = 0;
        size_t componentCount = 0;

        if (var->isArray())
        {
            if (version < Version(3, 1))
            {
                mInfoLog << "Capture of arrays is undefined and not supported.";
                return false;
            }

            // GLSL ES 3.10 section 4.3.6: A vertex output can't be an array of arrays.
            ASSERT(!var->isArrayOfArrays());

            if (!subscripts.empty() && subscripts[0] >= var->getOutermostArraySize())
            {
                mInfoLog << "Cannot capture outbound array element '" << tfVaryingName << "'.";
                return false;
            }
            elementCount = (subscripts.empty() ? var->getOutermostArraySize() : 1);
        }
        else
        {
            if (!subscripts.empty())
            {
                mInfoLog << "Varying '" << baseName
                         << "' is not an array to be captured by element.";
                return false;
            }
            elementCount = 1;
        }

        const Caps &caps = context->getCaps();

        // TODO(jmadill): Investigate implementation limits on D3D11
        componentCount = VariableComponentCount(var->type) * elementCount;
        if (mTransformFeedbackBufferMode == GL_SEPARATE_ATTRIBS &&
            componentCount > static_cast<GLuint>(caps.maxTransformFeedbackSeparateComponents))
        {
            mInfoLog << "Transform feedback varying " << tfVaryingName << " components ("
                     << componentCount << ") exceed the maximum separate components ("
                     << caps.maxTransformFeedbackSeparateComponents << ").";
            return false;
        }

        totalComponents += componentCount;
        if (mTransformFeedbackBufferMode == GL_INTERLEAVED_ATTRIBS &&
            totalComponents > static_cast<GLuint>(caps.maxTransformFeedbackInterleavedComponents))
        {
            mInfoLog << "Transform feedback varying total components (" << totalComponents
                     << ") exceed the maximum interleaved components ("
                     << caps.maxTransformFeedbackInterleavedComponents << ").";
            return false;
        }
    }
    return true;
}

void ProgramExecutable::gatherTransformFeedbackVaryings(
    const ProgramMergedVaryings &varyings,
    ShaderType stage,
    const std::vector<std::string> &transformFeedbackVaryingNames)
{
    // Gather the linked varyings that are used for transform feedback, they should all exist.
    mLinkedTransformFeedbackVaryings.clear();
    for (const std::string &tfVaryingName : transformFeedbackVaryingNames)
    {
        std::vector<unsigned int> subscripts;
        std::string baseName = ParseResourceName(tfVaryingName, &subscripts);
        size_t subscript     = GL_INVALID_INDEX;
        if (!subscripts.empty())
        {
            subscript = subscripts.back();
        }
        for (const ProgramVaryingRef &ref : varyings)
        {
            if (ref.frontShaderStage != stage)
            {
                continue;
            }

            const sh::ShaderVariable *varying = ref.get(stage);
            if (baseName == varying->name)
            {
                mLinkedTransformFeedbackVaryings.emplace_back(*varying,
                                                              static_cast<GLuint>(subscript));
                break;
            }
            else if (varying->isStruct())
            {
                GLuint fieldIndex = 0;
                const auto *field = varying->findField(tfVaryingName, &fieldIndex);
                if (field != nullptr)
                {
                    mLinkedTransformFeedbackVaryings.emplace_back(*field, *varying);
                    break;
                }
            }
        }
    }
}

void ProgramExecutable::updateTransformFeedbackStrides()
{
    if (mTransformFeedbackBufferMode == GL_INTERLEAVED_ATTRIBS)
    {
        mTransformFeedbackStrides.resize(1);
        size_t totalSize = 0;
        for (const TransformFeedbackVarying &varying : mLinkedTransformFeedbackVaryings)
        {
            totalSize += varying.size() * VariableExternalSize(varying.type);
        }
        mTransformFeedbackStrides[0] = static_cast<GLsizei>(totalSize);
    }
    else
    {
        mTransformFeedbackStrides.resize(mLinkedTransformFeedbackVaryings.size());
        for (size_t i = 0; i < mLinkedTransformFeedbackVaryings.size(); i++)
        {
            TransformFeedbackVarying &varying = mLinkedTransformFeedbackVaryings[i];
            mTransformFeedbackStrides[i] =
                static_cast<GLsizei>(varying.size() * VariableExternalSize(varying.type));
        }
    }
}

bool ProgramExecutable::validateSamplersImpl(InfoLog *infoLog, const Caps &caps) const
{
    // if any two active samplers in a program are of different types, but refer to the same
    // texture image unit, and this is the current program, then ValidateProgram will fail, and
    // DrawArrays and DrawElements will issue the INVALID_OPERATION error.
    for (size_t textureUnit : mActiveSamplersMask)
    {
        if (mActiveSamplerTypes[textureUnit] == TextureType::InvalidEnum)
        {
            if (infoLog)
            {
                (*infoLog) << "Samplers of conflicting types refer to the same texture "
                              "image unit ("
                           << textureUnit << ").";
            }

            mCachedValidateSamplersResult = false;
            return false;
        }

        if (mActiveSamplerFormats[textureUnit] == SamplerFormat::InvalidEnum)
        {
            if (infoLog)
            {
                (*infoLog) << "Samplers of conflicting formats refer to the same texture "
                              "image unit ("
                           << textureUnit << ").";
            }

            mCachedValidateSamplersResult = false;
            return false;
        }
    }

    mCachedValidateSamplersResult = true;
    return true;
}

bool ProgramExecutable::linkValidateOutputVariables(
    const Caps &caps,
    const Extensions &extensions,
    const Version &version,
    GLuint combinedImageUniformsCount,
    GLuint combinedShaderStorageBlocksCount,
    const std::vector<sh::ShaderVariable> &outputVariables,
    int fragmentShaderVersion,
    const ProgramAliasedBindings &fragmentOutputLocations,
    const ProgramAliasedBindings &fragmentOutputIndices)
{
    ASSERT(mOutputVariableTypes.empty());
    ASSERT(mActiveOutputVariablesMask.none());
    ASSERT(mDrawBufferTypeMask.none());
    ASSERT(!mYUVOutput);

    // Gather output variable types
    for (const sh::ShaderVariable &outputVariable : outputVariables)
    {
        if (outputVariable.isBuiltIn() && outputVariable.name != "gl_FragColor" &&
            outputVariable.name != "gl_FragData")
        {
            continue;
        }

        unsigned int baseLocation =
            (outputVariable.location == -1 ? 0u
                                           : static_cast<unsigned int>(outputVariable.location));

        // GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of
        // structures, so we may use getBasicTypeElementCount().
        unsigned int elementCount = outputVariable.getBasicTypeElementCount();
        for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
        {
            const unsigned int location = baseLocation + elementIndex;
            if (location >= mOutputVariableTypes.size())
            {
                mOutputVariableTypes.resize(location + 1, GL_NONE);
            }
            ASSERT(location < mActiveOutputVariablesMask.size());
            mActiveOutputVariablesMask.set(location);
            mOutputVariableTypes[location] = VariableComponentType(outputVariable.type);
            ComponentType componentType    = GLenumToComponentType(mOutputVariableTypes[location]);
            SetComponentTypeMask(componentType, location, &mDrawBufferTypeMask);
        }

        if (outputVariable.yuv)
        {
            ASSERT(outputVariables.size() == 1);
            mYUVOutput = true;
        }
    }

    if (version >= ES_3_1)
    {
        // [OpenGL ES 3.1] Chapter 8.22 Page 203:
        // A link error will be generated if the sum of the number of active image uniforms used in
        // all shaders, the number of active shader storage blocks, and the number of active
        // fragment shader outputs exceeds the implementation-dependent value of
        // MAX_COMBINED_SHADER_OUTPUT_RESOURCES.
        if (combinedImageUniformsCount + combinedShaderStorageBlocksCount +
                mActiveOutputVariablesMask.count() >
            static_cast<GLuint>(caps.maxCombinedShaderOutputResources))
        {
            mInfoLog
                << "The sum of the number of active image uniforms, active shader storage blocks "
                   "and active fragment shader outputs exceeds "
                   "MAX_COMBINED_SHADER_OUTPUT_RESOURCES ("
                << caps.maxCombinedShaderOutputResources << ")";
            return false;
        }
    }

    mOutputVariables = outputVariables;

    if (fragmentShaderVersion == 100)
    {
        return true;
    }

    // EXT_blend_func_extended doesn't specify anything related to binding specific elements of an
    // output array in explicit terms.
    //
    // Assuming fragData is an output array, you can defend the position that:
    // P1) you must support binding "fragData" because it's specified
    // P2) you must support querying "fragData[x]" because it's specified
    // P3) you must support binding "fragData[0]" because it's a frequently used pattern
    //
    // Then you can make the leap of faith:
    // P4) you must support binding "fragData[x]" because you support "fragData[0]"
    // P5) you must support binding "fragData[x]" because you support querying "fragData[x]"
    //
    // The spec brings in the "world of arrays" when it mentions binding the arrays and the
    // automatic binding. Thus it must be interpreted that the thing is not undefined, rather you
    // must infer the only possible interpretation (?). Note again: this need of interpretation
    // might be completely off of what GL spec logic is.
    //
    // The other complexity is that unless you implement this feature, it's hard to understand what
    // should happen when the client invokes the feature. You cannot add an additional error as it
    // is not specified. One can ignore it, but obviously it creates the discrepancies...

    std::vector<VariableLocation> reservedLocations;

    // Process any output API bindings for arrays that don't alias to the first element.
    for (const auto &bindingPair : fragmentOutputLocations)
    {
        const std::string &name       = bindingPair.first;
        const ProgramBinding &binding = bindingPair.second;

        size_t nameLengthWithoutArrayIndex;
        unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex);
        if (arrayIndex == 0 || arrayIndex == GL_INVALID_INDEX)
        {
            continue;
        }
        for (unsigned int outputVariableIndex = 0; outputVariableIndex < mOutputVariables.size();
             outputVariableIndex++)
        {
            const sh::ShaderVariable &outputVariable = mOutputVariables[outputVariableIndex];
            // Check that the binding corresponds to an output array and its array index fits.
            if (outputVariable.isBuiltIn() || !outputVariable.isArray() ||
                !angle::BeginsWith(outputVariable.name, name, nameLengthWithoutArrayIndex) ||
                arrayIndex >= outputVariable.getOutermostArraySize())
            {
                continue;
            }

            // Get the API index that corresponds to this exact binding.
            // This index may differ from the index used for the array's base.
            std::vector<VariableLocation> &outputLocations =
                fragmentOutputIndices.getBindingByName(name) == 1 ? mSecondaryOutputLocations
                                                                  : mOutputLocations;
            unsigned int location = binding.location;
            VariableLocation locationInfo(arrayIndex, outputVariableIndex);
            if (location >= outputLocations.size())
            {
                outputLocations.resize(location + 1);
            }
            if (outputLocations[location].used())
            {
                mInfoLog << "Location of variable " << outputVariable.name
                         << " conflicts with another variable.";
                return false;
            }
            outputLocations[location] = locationInfo;

            // Note the array binding location so that it can be skipped later.
            reservedLocations.push_back(locationInfo);
        }
    }

    // Reserve locations for output variables whose location is fixed in the shader or through the
    // API. Otherwise, the remaining unallocated outputs will be processed later.
    for (unsigned int outputVariableIndex = 0; outputVariableIndex < mOutputVariables.size();
         outputVariableIndex++)
    {
        const sh::ShaderVariable &outputVariable = mOutputVariables[outputVariableIndex];

        // Don't store outputs for gl_FragDepth, gl_FragColor, etc.
        if (outputVariable.isBuiltIn())
            continue;

        int fixedLocation = GetOutputLocationForLink(fragmentOutputLocations, outputVariable);
        if (fixedLocation == -1)
        {
            // Here we're only reserving locations for variables whose location is fixed.
            continue;
        }
        unsigned int baseLocation = static_cast<unsigned int>(fixedLocation);

        std::vector<VariableLocation> &outputLocations =
            IsOutputSecondaryForLink(fragmentOutputIndices, outputVariable)
                ? mSecondaryOutputLocations
                : mOutputLocations;

        // GLSL ES 3.10 section 4.3.6: Output variables cannot be arrays of arrays or arrays of
        // structures, so we may use getBasicTypeElementCount().
        unsigned int elementCount = outputVariable.getBasicTypeElementCount();
        if (FindUsedOutputLocation(outputLocations, baseLocation, elementCount, reservedLocations,
                                   outputVariableIndex))
        {
            mInfoLog << "Location of variable " << outputVariable.name
                     << " conflicts with another variable.";
            return false;
        }
        AssignOutputLocations(outputLocations, baseLocation, elementCount, reservedLocations,
                              outputVariableIndex, mOutputVariables[outputVariableIndex]);
    }

    // Here we assign locations for the output variables that don't yet have them. Note that we're
    // not necessarily able to fit the variables optimally, since then we might have to try
    // different arrangements of output arrays. Now we just assign the locations in the order that
    // we got the output variables. The spec isn't clear on what kind of algorithm is required for
    // finding locations for the output variables, so this should be acceptable at least for now.
    GLuint maxLocation = static_cast<GLuint>(caps.maxDrawBuffers);
    if (!mSecondaryOutputLocations.empty())
    {
        // EXT_blend_func_extended: Program outputs will be validated against
        // MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT if there's even one output with index one.
        maxLocation = caps.maxDualSourceDrawBuffers;
    }

    for (unsigned int outputVariableIndex = 0; outputVariableIndex < mOutputVariables.size();
         outputVariableIndex++)
    {
        const sh::ShaderVariable &outputVariable = mOutputVariables[outputVariableIndex];

        // Don't store outputs for gl_FragDepth, gl_FragColor, etc.
        if (outputVariable.isBuiltIn())
            continue;

        int fixedLocation = GetOutputLocationForLink(fragmentOutputLocations, outputVariable);
        std::vector<VariableLocation> &outputLocations =
            IsOutputSecondaryForLink(fragmentOutputIndices, outputVariable)
                ? mSecondaryOutputLocations
                : mOutputLocations;
        unsigned int baseLocation = 0;
        unsigned int elementCount = outputVariable.getBasicTypeElementCount();
        if (fixedLocation != -1)
        {
            // Secondary inputs might have caused the max location to drop below what has already
            // been explicitly assigned locations. Check for any fixed locations above the max
            // that should cause linking to fail.
            baseLocation = static_cast<unsigned int>(fixedLocation);
        }
        else
        {
            // No fixed location, so try to fit the output in unassigned locations.
            // Try baseLocations starting from 0 one at a time and see if the variable fits.
            while (FindUsedOutputLocation(outputLocations, baseLocation, elementCount,
                                          reservedLocations, outputVariableIndex))
            {
                baseLocation++;
            }
            AssignOutputLocations(outputLocations, baseLocation, elementCount, reservedLocations,
                                  outputVariableIndex, mOutputVariables[outputVariableIndex]);
        }

        // Check for any elements assigned above the max location that are actually used.
        if (baseLocation + elementCount > maxLocation &&
            (baseLocation >= maxLocation ||
             FindUsedOutputLocation(outputLocations, maxLocation,
                                    baseLocation + elementCount - maxLocation, reservedLocations,
                                    outputVariableIndex)))
        {
            // EXT_blend_func_extended: Linking can fail:
            // "if the explicit binding assignments do not leave enough space for the linker to
            // automatically assign a location for a varying out array, which requires multiple
            // contiguous locations."
            mInfoLog << "Could not fit output variable into available locations: "
                     << outputVariable.name;
            return false;
        }
    }

    return true;
}

}  // namespace gl
