//
// 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"
#include "libANGLE/queryconversions.h"
#include "libANGLE/renderer/GLImplFactory.h"
#include "libANGLE/renderer/ProgramExecutableImpl.h"

namespace gl
{
namespace
{
ANGLE_ENABLE_STRUCT_PADDING_WARNINGS
// A placeholder struct just to ensure sh::BlockMemberInfo is tightly packed since vulkan backend
// uses it and memcpy the entire vector which requires it tightly packed to make msan happy.
struct BlockMemberInfoPaddingTest
{
    sh::BlockMemberInfo blockMemberInfo;
};
ANGLE_DISABLE_STRUCT_PADDING_WARNINGS

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,
                           ProgramOutput &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.pod.location = baseLocation;
            const unsigned int location = baseLocation + elementIndex;
            outputLocations[location]   = locationInfo;
        }
    }
}

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

bool IsOutputSecondaryForLink(const ProgramAliasedBindings &fragmentOutputIndexes,
                              const ProgramOutput &outputVariable)
{
    if (outputVariable.pod.index != -1)
    {
        ASSERT(outputVariable.pod.index == 0 || outputVariable.pod.index == 1);
        return (outputVariable.pod.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;
}

RangeUI AddUniforms(const ShaderMap<SharedProgramExecutable> &executables,
                    ShaderBitSet activeShaders,
                    std::vector<LinkedUniform> *outputUniforms,
                    std::vector<std::string> *outputUniformNames,
                    std::vector<std::string> *outputUniformMappedNames,
                    const std::function<RangeUI(const ProgramExecutable &)> &getRange)
{
    unsigned int startRange = static_cast<unsigned int>(outputUniforms->size());
    for (ShaderType shaderType : activeShaders)
    {
        const ProgramExecutable &executable = *executables[shaderType];
        const RangeUI uniformRange          = getRange(executable);

        const std::vector<LinkedUniform> &programUniforms = executable.getUniforms();
        outputUniforms->insert(outputUniforms->end(), programUniforms.begin() + uniformRange.low(),
                               programUniforms.begin() + uniformRange.high());

        const std::vector<std::string> &uniformNames = executable.getUniformNames();
        outputUniformNames->insert(outputUniformNames->end(),
                                   uniformNames.begin() + uniformRange.low(),
                                   uniformNames.begin() + uniformRange.high());

        const std::vector<std::string> &uniformMappedNames = executable.getUniformMappedNames();
        outputUniformMappedNames->insert(outputUniformMappedNames->end(),
                                         uniformMappedNames.begin() + uniformRange.low(),
                                         uniformMappedNames.begin() + uniformRange.high());
    }
    return RangeUI(startRange, static_cast<unsigned int>(outputUniforms->size()));
}

template <typename BlockT>
void AppendActiveBlocks(ShaderType shaderType,
                        const std::vector<BlockT> &blocksIn,
                        std::vector<BlockT> &blocksOut,
                        ProgramPipelineUniformBlockIndexMap *ppoBlockMap)
{
    for (size_t index = 0; index < blocksIn.size(); ++index)
    {
        const BlockT &block = blocksIn[index];
        if (block.isActive(shaderType))
        {
            // Have a way for the PPO to know how to map the program's UBO index into its own UBO
            // array.  This is used to propagate changes to the program's UBOs to the PPO's UBO
            // list.
            if (ppoBlockMap != nullptr)
            {
                (*ppoBlockMap)[static_cast<uint32_t>(index)] =
                    static_cast<uint32_t>(blocksOut.size());
            }

            blocksOut.push_back(block);
        }
    }
}

void SaveProgramInputs(BinaryOutputStream *stream, const std::vector<ProgramInput> &programInputs)
{
    stream->writeInt(programInputs.size());
    for (const ProgramInput &attrib : programInputs)
    {
        stream->writeString(attrib.name);
        stream->writeString(attrib.mappedName);
        stream->writeStruct(attrib.pod);
    }
}
void LoadProgramInputs(BinaryInputStream *stream, std::vector<ProgramInput> *programInputs)
{
    size_t attribCount = stream->readInt<size_t>();
    ASSERT(programInputs->empty());
    if (attribCount > 0)
    {
        programInputs->resize(attribCount);
        for (size_t attribIndex = 0; attribIndex < attribCount; ++attribIndex)
        {
            ProgramInput &attrib = (*programInputs)[attribIndex];
            stream->readString(&attrib.name);
            stream->readString(&attrib.mappedName);
            stream->readStruct(&attrib.pod);
        }
    }
}

void SaveUniforms(BinaryOutputStream *stream,
                  const std::vector<LinkedUniform> &uniforms,
                  const std::vector<std::string> &uniformNames,
                  const std::vector<std::string> &uniformMappedNames,
                  const std::vector<VariableLocation> &uniformLocations)
{
    stream->writeVector(uniforms);
    ASSERT(uniforms.size() == uniformNames.size());
    ASSERT(uniforms.size() == uniformMappedNames.size());
    for (const std::string &name : uniformNames)
    {
        stream->writeString(name);
    }
    for (const std::string &name : uniformMappedNames)
    {
        stream->writeString(name);
    }
    stream->writeVector(uniformLocations);
}
void LoadUniforms(BinaryInputStream *stream,
                  std::vector<LinkedUniform> *uniforms,
                  std::vector<std::string> *uniformNames,
                  std::vector<std::string> *uniformMappedNames,
                  std::vector<VariableLocation> *uniformLocations)
{
    stream->readVector(uniforms);
    if (!uniforms->empty())
    {
        uniformNames->resize(uniforms->size());
        for (size_t uniformIndex = 0; uniformIndex < uniforms->size(); ++uniformIndex)
        {
            stream->readString(&(*uniformNames)[uniformIndex]);
        }
        uniformMappedNames->resize(uniforms->size());
        for (size_t uniformIndex = 0; uniformIndex < uniforms->size(); ++uniformIndex)
        {
            stream->readString(&(*uniformMappedNames)[uniformIndex]);
        }
    }
    stream->readVector(uniformLocations);
}

void SaveSamplerBindings(BinaryOutputStream *stream,
                         const std::vector<SamplerBinding> &samplerBindings,
                         const std::vector<GLuint> &samplerBoundTextureUnits)
{
    stream->writeVector(samplerBindings);
    stream->writeInt(samplerBoundTextureUnits.size());
}
void LoadSamplerBindings(BinaryInputStream *stream,
                         std::vector<SamplerBinding> *samplerBindings,
                         std::vector<GLuint> *samplerBoundTextureUnits)
{
    stream->readVector(samplerBindings);
    ASSERT(samplerBoundTextureUnits->empty());
    size_t boundTextureUnitsCount = stream->readInt<size_t>();
    samplerBoundTextureUnits->resize(boundTextureUnitsCount, 0);
}

void WriteBufferVariable(BinaryOutputStream *stream, const BufferVariable &var)
{
    stream->writeString(var.name);
    stream->writeString(var.mappedName);
    stream->writeStruct(var.pod);
}

void LoadBufferVariable(BinaryInputStream *stream, BufferVariable *var)
{
    var->name       = stream->readString();
    var->mappedName = stream->readString();
    stream->readStruct(&var->pod);
}

void WriteShaderVariableBuffer(BinaryOutputStream *stream, const ShaderVariableBuffer &var)
{
    stream->writeVector(var.memberIndexes);
    stream->writeStruct(var.pod);
}

void LoadShaderVariableBuffer(BinaryInputStream *stream, ShaderVariableBuffer *var)
{
    stream->readVector(&var->memberIndexes);
    stream->readStruct(&var->pod);
}

void WriteInterfaceBlock(BinaryOutputStream *stream, const InterfaceBlock &block)
{
    stream->writeString(block.name);
    stream->writeString(block.mappedName);
    stream->writeVector(block.memberIndexes);
    stream->writeStruct(block.pod);
}

void LoadInterfaceBlock(BinaryInputStream *stream, InterfaceBlock *block)
{
    block->name       = stream->readString();
    block->mappedName = stream->readString();
    stream->readVector(&block->memberIndexes);
    stream->readStruct(&block->pod);
}

void CopyStringToBuffer(GLchar *buffer,
                        const std::string &string,
                        GLsizei bufSize,
                        GLsizei *lengthOut)
{
    ASSERT(bufSize > 0);
    size_t length = std::min<size_t>(bufSize - 1, string.length());
    memcpy(buffer, string.c_str(), length);
    buffer[length] = '\0';

    if (lengthOut)
    {
        *lengthOut = static_cast<GLsizei>(length);
    }
}

template <typename T>
GLuint GetResourceMaxNameSize(const T &resource, GLint max)
{
    if (resource.isArray())
    {
        return std::max(max, clampCast<GLint>((resource.name + "[0]").size()));
    }
    else
    {
        return std::max(max, clampCast<GLint>((resource.name).size()));
    }
}

template <typename T>
GLuint GetResourceLocation(const GLchar *name, const T &variable, GLint location)
{
    if (variable.isBuiltIn())
    {
        return GL_INVALID_INDEX;
    }

    if (variable.isArray())
    {
        size_t nameLengthWithoutArrayIndexOut;
        size_t arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndexOut);
        // The 'name' string may not contain the array notation "[0]"
        if (arrayIndex != GL_INVALID_INDEX)
        {
            location += arrayIndex;
        }
    }

    return location;
}

template <typename T>
const std::string GetResourceName(const T &resource)
{
    std::string resourceName = resource.name;

    if (resource.isArray())
    {
        resourceName += "[0]";
    }

    return resourceName;
}

GLint GetVariableLocation(const std::vector<gl::ProgramOutput> &list,
                          const std::vector<VariableLocation> &locationList,
                          const std::string &name)
{
    size_t nameLengthWithoutArrayIndex;
    unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex);

    for (size_t location = 0u; location < locationList.size(); ++location)
    {
        const VariableLocation &variableLocation = locationList[location];
        if (!variableLocation.used())
        {
            continue;
        }

        const gl::ProgramOutput &variable = list[variableLocation.index];

        // Array output variables may be bound out of order, so we need to ensure we only pick the
        // first element if given the base name.
        if ((variable.name == name) && (variableLocation.arrayIndex == 0))
        {
            return static_cast<GLint>(location);
        }
        if (variable.isArray() && variableLocation.arrayIndex == arrayIndex &&
            angle::BeginsWith(variable.name, name, nameLengthWithoutArrayIndex))
        {
            return static_cast<GLint>(location);
        }
    }

    return -1;
}

template <typename VarT>
GLuint GetResourceIndexFromName(const std::vector<VarT> &list, const std::string &name)
{
    std::string nameAsArrayName = name + "[0]";
    for (size_t index = 0; index < list.size(); index++)
    {
        const VarT &resource = list[index];
        if (resource.name == name || (resource.isArray() && resource.name == nameAsArrayName))
        {
            return static_cast<GLuint>(index);
        }
    }

    return GL_INVALID_INDEX;
}

GLuint GetUniformIndexFromName(const std::vector<LinkedUniform> &uniformList,
                               const std::vector<std::string> &nameList,
                               const std::string &name)
{
    std::string nameAsArrayName = name + "[0]";
    for (size_t index = 0; index < nameList.size(); index++)
    {
        const std::string &uniformName = nameList[index];
        if (uniformName == name || (uniformList[index].isArray() && uniformName == nameAsArrayName))
        {
            return static_cast<GLuint>(index);
        }
    }

    return GL_INVALID_INDEX;
}

GLint GetUniformLocation(const std::vector<LinkedUniform> &uniformList,
                         const std::vector<std::string> &nameList,
                         const std::vector<VariableLocation> &locationList,
                         const std::string &name)
{
    size_t nameLengthWithoutArrayIndex;
    unsigned int arrayIndex = ParseArrayIndex(name, &nameLengthWithoutArrayIndex);

    for (size_t location = 0u; location < locationList.size(); ++location)
    {
        const VariableLocation &variableLocation = locationList[location];
        if (!variableLocation.used())
        {
            continue;
        }

        const LinkedUniform &variable  = uniformList[variableLocation.index];
        const std::string &uniformName = nameList[variableLocation.index];

        // Array output variables may be bound out of order, so we need to ensure we only pick the
        // first element if given the base name. Uniforms don't allow this behavior and some code
        // seemingly depends on the opposite behavior, so only enable it for output variables.
        if (angle::BeginsWith(uniformName, name) && (variableLocation.arrayIndex == 0))
        {
            if (name.length() == uniformName.length())
            {
                ASSERT(name == uniformName);
                // GLES 3.1 November 2016 page 87.
                // The string exactly matches the name of the active variable.
                return static_cast<GLint>(location);
            }
            if (name.length() + 3u == uniformName.length() && variable.isArray())
            {
                ASSERT(name + "[0]" == uniformName);
                // The string identifies the base name of an active array, where the string would
                // exactly match the name of the variable if the suffix "[0]" were appended to the
                // string.
                return static_cast<GLint>(location);
            }
        }
        if (variable.isArray() && variableLocation.arrayIndex == arrayIndex &&
            nameLengthWithoutArrayIndex + 3u == uniformName.length() &&
            angle::BeginsWith(uniformName, name, nameLengthWithoutArrayIndex))
        {
            ASSERT(name.substr(0u, nameLengthWithoutArrayIndex) + "[0]" == uniformName);
            // The string identifies an active element of the array, where the string ends with the
            // concatenation of the "[" character, an integer (with no "+" sign, extra leading
            // zeroes, or whitespace) identifying an array element, and the "]" character, the
            // integer is less than the number of active elements of the array variable, and where
            // the string would exactly match the enumerated name of the array if the decimal
            // integer were replaced with zero.
            return static_cast<GLint>(location);
        }
    }

    return -1;
}

GLuint GetInterfaceBlockIndex(const std::vector<InterfaceBlock> &list, const std::string &name)
{
    std::vector<unsigned int> subscripts;
    std::string baseName = ParseResourceName(name, &subscripts);

    unsigned int numBlocks = static_cast<unsigned int>(list.size());
    for (unsigned int blockIndex = 0; blockIndex < numBlocks; blockIndex++)
    {
        const auto &block = list[blockIndex];
        if (block.name == baseName)
        {
            const bool arrayElementZero =
                (subscripts.empty() && (!block.pod.isArray || block.pod.arrayElement == 0));
            const bool arrayElementMatches =
                (subscripts.size() == 1 && subscripts[0] == block.pod.arrayElement);
            if (arrayElementMatches || arrayElementZero)
            {
                return blockIndex;
            }
        }
    }

    return GL_INVALID_INDEX;
}

void GetInterfaceBlockName(const UniformBlockIndex index,
                           const std::vector<InterfaceBlock> &list,
                           GLsizei bufSize,
                           GLsizei *length,
                           GLchar *name)
{
    ASSERT(index.value < list.size());

    const auto &block = list[index.value];

    if (bufSize > 0)
    {
        std::string blockName = block.name;

        if (block.pod.isArray)
        {
            blockName += ArrayString(block.pod.arrayElement);
        }
        CopyStringToBuffer(name, blockName, bufSize, length);
    }
}

template <typename T>
GLint GetActiveInterfaceBlockMaxNameLength(const std::vector<T> &resources)
{
    int maxLength = 0;

    for (const T &resource : resources)
    {
        if (!resource.name.empty())
        {
            int length = static_cast<int>(resource.nameWithArrayIndex().length());
            maxLength  = std::max(length + 1, maxLength);
        }
    }

    return maxLength;
}

// This simplified cast function doesn't need to worry about advanced concepts like
// depth range values, or casting to bool.
template <typename DestT, typename SrcT>
DestT UniformStateQueryCast(SrcT value);

// From-Float-To-Integer Casts
template <>
GLint UniformStateQueryCast(GLfloat value)
{
    return clampCast<GLint>(roundf(value));
}

template <>
GLuint UniformStateQueryCast(GLfloat value)
{
    return clampCast<GLuint>(roundf(value));
}

// From-Integer-to-Integer Casts
template <>
GLint UniformStateQueryCast(GLuint value)
{
    return clampCast<GLint>(value);
}

template <>
GLuint UniformStateQueryCast(GLint value)
{
    return clampCast<GLuint>(value);
}

// From-Boolean-to-Anything Casts
template <>
GLfloat UniformStateQueryCast(GLboolean value)
{
    return (ConvertToBool(value) ? 1.0f : 0.0f);
}

template <>
GLint UniformStateQueryCast(GLboolean value)
{
    return (ConvertToBool(value) ? 1 : 0);
}

template <>
GLuint UniformStateQueryCast(GLboolean value)
{
    return (ConvertToBool(value) ? 1u : 0u);
}

// Default to static_cast
template <typename DestT, typename SrcT>
DestT UniformStateQueryCast(SrcT value)
{
    return static_cast<DestT>(value);
}

template <typename SrcT, typename DestT>
void UniformStateQueryCastLoop(DestT *dataOut, const uint8_t *srcPointer, int components)
{
    for (int comp = 0; comp < components; ++comp)
    {
        // We only work with strides of 4 bytes for uniform components. (GLfloat/GLint)
        // Don't use SrcT stride directly since GLboolean has a stride of 1 byte.
        size_t offset               = comp * 4;
        const SrcT *typedSrcPointer = reinterpret_cast<const SrcT *>(&srcPointer[offset]);
        dataOut[comp]               = UniformStateQueryCast<DestT>(*typedSrcPointer);
    }
}
}  // anonymous namespace

// ImageBinding implementation.
ImageBinding::ImageBinding(GLuint imageUnit, size_t count, TextureType textureTypeIn)
    : textureType(textureTypeIn)
{
    for (size_t index = 0; index < count; ++index)
    {
        boundImageUnits.push_back(imageUnit + static_cast<GLuint>(index));
    }
}

// ProgramInput implementation.
ProgramInput::ProgramInput(const sh::ShaderVariable &var)
{
    ASSERT(!var.isStruct());

    name       = var.name;
    mappedName = var.mappedName;

    SetBitField(pod.type, var.type);
    pod.location = var.hasImplicitLocation ? -1 : var.location;
    SetBitField(pod.interpolation, var.interpolation);
    pod.flagBitsAsUByte              = 0;
    pod.flagBits.active              = var.active;
    pod.flagBits.isPatch             = var.isPatch;
    pod.flagBits.hasImplicitLocation = var.hasImplicitLocation;
    pod.flagBits.isArray             = var.isArray();
    pod.flagBits.isBuiltIn           = IsBuiltInName(var.name);
    SetBitField(pod.basicTypeElementCount, var.getBasicTypeElementCount());
    pod.id = var.id;
    SetBitField(pod.arraySizeProduct, var.getArraySizeProduct());
}

// ProgramOutput implementation.
ProgramOutput::ProgramOutput(const sh::ShaderVariable &var)
{
    name       = var.name;
    mappedName = var.mappedName;

    pod.type     = var.type;
    pod.location = var.location;
    pod.index    = var.index;
    pod.id       = var.id;

    SetBitField(pod.outermostArraySize, var.getOutermostArraySize());
    SetBitField(pod.basicTypeElementCount, var.getBasicTypeElementCount());

    SetBitField(pod.isPatch, var.isPatch);
    SetBitField(pod.yuv, var.yuv);
    SetBitField(pod.isBuiltIn, IsBuiltInName(var.name));
    SetBitField(pod.isArray, var.isArray());
    SetBitField(pod.hasImplicitLocation, var.hasImplicitLocation);
    SetBitField(pod.pad, 0);
}

// ProgramExecutable implementation.
ProgramExecutable::ProgramExecutable(rx::GLImplFactory *factory, InfoLog *infoLog)
    : mImplementation(factory->createProgramExecutable(this)),
      mInfoLog(infoLog),
      mActiveSamplerRefCounts{},
      mCachedBaseVertex(0),
      mCachedBaseInstance(0)
{
    memset(&mPod, 0, sizeof(mPod));
    mPod.geometryShaderInputPrimitiveType  = PrimitiveMode::Triangles;
    mPod.geometryShaderOutputPrimitiveType = PrimitiveMode::TriangleStrip;
    mPod.geometryShaderInvocations         = 1;
    mPod.transformFeedbackBufferMode       = GL_INTERLEAVED_ATTRIBS;
    mPod.computeShaderLocalSize.fill(1);

    reset();
}

ProgramExecutable::~ProgramExecutable()
{
    ASSERT(mImplementation == nullptr);
}

void ProgramExecutable::destroy(const Context *context)
{
    ASSERT(mImplementation != nullptr);

    mImplementation->destroy(context);
    SafeDelete(mImplementation);
}

void ProgramExecutable::reset()
{
    mPod.activeAttribLocationsMask.reset();
    mPod.attributesTypeMask.reset();
    mPod.attributesMask.reset();
    mPod.maxActiveAttribLocation = 0;
    mPod.activeOutputVariablesMask.reset();
    mPod.activeSecondaryOutputVariablesMask.reset();

    mPod.defaultUniformRange       = RangeUI(0, 0);
    mPod.samplerUniformRange       = RangeUI(0, 0);
    mPod.imageUniformRange         = RangeUI(0, 0);
    mPod.atomicCounterUniformRange = RangeUI(0, 0);
    mPod.fragmentInoutRange        = RangeUI(0, 0);

    mPod.hasClipDistance         = false;
    mPod.hasDiscard              = false;
    mPod.enablesPerSampleShading = false;
    mPod.hasYUVOutput            = false;

    mPod.advancedBlendEquations.reset();

    mPod.geometryShaderInputPrimitiveType  = PrimitiveMode::Triangles;
    mPod.geometryShaderOutputPrimitiveType = PrimitiveMode::TriangleStrip;
    mPod.geometryShaderInvocations         = 1;
    mPod.geometryShaderMaxVertices         = 0;

    mPod.numViews = -1;

    mPod.drawIDLocation = -1;

    mPod.baseVertexLocation   = -1;
    mPod.baseInstanceLocation = -1;

    mPod.tessControlShaderVertices = 0;
    mPod.tessGenMode               = GL_NONE;
    mPod.tessGenSpacing            = GL_NONE;
    mPod.tessGenVertexOrder        = GL_NONE;
    mPod.tessGenPointMode          = GL_NONE;
    mPod.drawBufferTypeMask.reset();
    mPod.computeShaderLocalSize.fill(1);

    mPod.specConstUsageBits.reset();

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

    mActiveImagesMask.reset();

    mProgramInputs.clear();
    mLinkedTransformFeedbackVaryings.clear();
    mTransformFeedbackStrides.clear();
    mUniforms.clear();
    mUniformNames.clear();
    mUniformMappedNames.clear();
    mUniformBlocks.clear();
    mUniformLocations.clear();
    mShaderStorageBlocks.clear();
    mAtomicCounterBuffers.clear();
    mBufferVariables.clear();
    mOutputVariables.clear();
    mOutputLocations.clear();
    mSecondaryOutputLocations.clear();
    mSamplerBindings.clear();
    mSamplerBoundTextureUnits.clear();
    mImageBindings.clear();
}

void ProgramExecutable::load(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");
    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");

    stream->readStruct(&mPod);

    LoadProgramInputs(stream, &mProgramInputs);
    LoadUniforms(stream, &mUniforms, &mUniformNames, &mUniformMappedNames, &mUniformLocations);

    size_t uniformBlockCount = stream->readInt<size_t>();
    ASSERT(getUniformBlocks().empty());
    mUniformBlocks.resize(uniformBlockCount);
    for (size_t uniformBlockIndex = 0; uniformBlockIndex < uniformBlockCount; ++uniformBlockIndex)
    {
        InterfaceBlock &uniformBlock = mUniformBlocks[uniformBlockIndex];
        LoadInterfaceBlock(stream, &uniformBlock);
        ASSERT(mPod.activeUniformBlockBindings.test(uniformBlockIndex) ==
               (uniformBlock.pod.binding != 0));
    }

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

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

    size_t bufferVariableCount = stream->readInt<size_t>();
    ASSERT(getBufferVariables().empty());
    mBufferVariables.resize(bufferVariableCount);
    for (size_t bufferVarIndex = 0; bufferVarIndex < bufferVariableCount; ++bufferVarIndex)
    {
        LoadBufferVariable(stream, &mBufferVariables[bufferVarIndex]);
    }

    size_t transformFeedbackVaryingCount = stream->readInt<size_t>();
    ASSERT(mLinkedTransformFeedbackVaryings.empty());
    mLinkedTransformFeedbackVaryings.resize(transformFeedbackVaryingCount);
    for (size_t transformFeedbackVaryingIndex = 0;
         transformFeedbackVaryingIndex < transformFeedbackVaryingCount;
         ++transformFeedbackVaryingIndex)
    {
        TransformFeedbackVarying &varying =
            mLinkedTransformFeedbackVaryings[transformFeedbackVaryingIndex];
        stream->readVector(&varying.arraySizes);
        stream->readInt(&varying.type);
        stream->readString(&varying.name);
        varying.arrayIndex = stream->readInt<GLuint>();
    }

    size_t outputCount = stream->readInt<size_t>();
    ASSERT(getOutputVariables().empty());
    mOutputVariables.resize(outputCount);
    for (size_t outputIndex = 0; outputIndex < outputCount; ++outputIndex)
    {
        ProgramOutput &output = mOutputVariables[outputIndex];
        stream->readString(&output.name);
        stream->readString(&output.mappedName);
        stream->readStruct(&output.pod);
    }

    stream->readVector(&mOutputLocations);
    stream->readVector(&mSecondaryOutputLocations);
    LoadSamplerBindings(stream, &mSamplerBindings, &mSamplerBoundTextureUnits);

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

    // These values are currently only used by PPOs, so only load them when the program is marked
    // separable to save memory.
    if (mPod.isSeparable)
    {
        for (ShaderType shaderType : getLinkedShaderStages())
        {
            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);
            }
        }
    }
}

void ProgramExecutable::save(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");
    static_assert(
        IMPLEMENTATION_MAX_DRAW_BUFFERS * 2 <= 8 * sizeof(uint32_t),
        "All bits of mDrawBufferTypeMask and mActiveOutputVariables can be contained in 32 bits");

    ASSERT(mPod.geometryShaderInvocations >= 1 && mPod.geometryShaderMaxVertices >= 0);
    stream->writeStruct(mPod);

    SaveProgramInputs(stream, mProgramInputs);
    SaveUniforms(stream, mUniforms, mUniformNames, mUniformMappedNames, mUniformLocations);

    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(getBufferVariables().size());
    for (const BufferVariable &bufferVariable : getBufferVariables())
    {
        WriteBufferVariable(stream, bufferVariable);
    }

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

        stream->writeIntOrNegOne(var.arrayIndex);
    }

    stream->writeInt(getOutputVariables().size());
    for (const ProgramOutput &output : getOutputVariables())
    {
        stream->writeString(output.name);
        stream->writeString(output.mappedName);
        stream->writeStruct(output.pod);
    }

    stream->writeVector(mOutputLocations);
    stream->writeVector(mSecondaryOutputLocations);
    SaveSamplerBindings(stream, mSamplerBindings, mSamplerBoundTextureUnits);

    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 (mPod.isSeparable)
    {
        for (ShaderType shaderType : getLinkedShaderStages())
        {
            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);
            }
        }
    }
}

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

ShaderType ProgramExecutable::getFirstLinkedShaderStageType() const
{
    const ShaderBitSet linkedStages = mPod.linkedShaderStages;
    if (linkedStages.none())
    {
        return ShaderType::InvalidEnum;
    }

    return linkedStages.first();
}

ShaderType ProgramExecutable::getLastLinkedShaderStageType() const
{
    const ShaderBitSet linkedStages = mPod.linkedShaderStages;
    if (linkedStages.none())
    {
        return ShaderType::InvalidEnum;
    }

    return linkedStages.last();
}

void ProgramExecutable::setActive(size_t textureUnit,
                                  const SamplerBinding &samplerBinding,
                                  const gl::LinkedUniform &samplerUniform)
{
    mActiveSamplersMask.set(textureUnit);
    mActiveSamplerTypes[textureUnit]      = samplerBinding.textureType;
    mActiveSamplerYUV[textureUnit]        = IsSamplerYUVType(samplerBinding.samplerType);
    mActiveSamplerFormats[textureUnit]    = samplerBinding.format;
    mActiveSamplerShaderBits[textureUnit] = samplerUniform.activeShaders();
}

void ProgramExecutable::setInactive(size_t textureUnit)
{
    mActiveSamplersMask.reset(textureUnit);
    mActiveSamplerTypes[textureUnit] = TextureType::InvalidEnum;
    mActiveSamplerYUV.reset(textureUnit);
    mActiveSamplerFormats[textureUnit] = SamplerFormat::InvalidEnum;
    mActiveSamplerShaderBits[textureUnit].reset();
}

void ProgramExecutable::hasSamplerTypeConflict(size_t textureUnit)
{
    // Conflicts are marked with InvalidEnum
    mActiveSamplerYUV.reset(textureUnit);
    mActiveSamplerTypes[textureUnit] = TextureType::InvalidEnum;
}

void ProgramExecutable::hasSamplerFormatConflict(size_t textureUnit)
{
    // Conflicts are marked with InvalidEnum
    mActiveSamplerFormats[textureUnit] = SamplerFormat::InvalidEnum;
}

void ProgramExecutable::updateActiveSamplers(const ProgramExecutable &executable)
{
    const std::vector<SamplerBinding> &samplerBindings = executable.getSamplerBindings();
    const std::vector<GLuint> &boundTextureUnits       = executable.getSamplerBoundTextureUnits();

    for (uint32_t samplerIndex = 0; samplerIndex < samplerBindings.size(); ++samplerIndex)
    {
        const SamplerBinding &samplerBinding = samplerBindings[samplerIndex];

        for (uint16_t index = 0; index < samplerBinding.textureUnitsCount; index++)
        {
            GLint textureUnit = samplerBinding.getTextureUnit(boundTextureUnits, index);
            if (++mActiveSamplerRefCounts[textureUnit] == 1)
            {
                uint32_t uniformIndex = executable.getUniformIndexFromSamplerIndex(samplerIndex);
                setActive(textureUnit, samplerBinding, executable.getUniforms()[uniformIndex]);
            }
            else
            {
                if (mActiveSamplerTypes[textureUnit] != samplerBinding.textureType ||
                    mActiveSamplerYUV.test(textureUnit) !=
                        IsSamplerYUVType(samplerBinding.samplerType))
                {
                    hasSamplerTypeConflict(textureUnit);
                }

                if (mActiveSamplerFormats[textureUnit] != samplerBinding.format)
                {
                    hasSamplerFormatConflict(textureUnit);
                }
            }
            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)
{
    bool foundBinding         = false;
    TextureType foundType     = TextureType::InvalidEnum;
    bool foundYUV             = false;
    SamplerFormat foundFormat = SamplerFormat::InvalidEnum;

    for (uint32_t samplerIndex = 0; samplerIndex < mSamplerBindings.size(); ++samplerIndex)
    {
        const SamplerBinding &binding = mSamplerBindings[samplerIndex];

        // 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 (uint16_t index = 0; index < binding.textureUnitsCount; index++)
        {
            GLuint textureUnit = binding.getTextureUnit(mSamplerBoundTextureUnits, index);
            if (textureUnit != textureUnitIndex)
            {
                continue;
            }

            if (!foundBinding)
            {
                foundBinding          = true;
                foundType             = binding.textureType;
                foundYUV              = IsSamplerYUVType(binding.samplerType);
                foundFormat           = binding.format;
                uint32_t uniformIndex = getUniformIndexFromSamplerIndex(samplerIndex);
                setActive(textureUnit, binding, mUniforms[uniformIndex]);
            }
            else
            {
                if (foundType != binding.textureType ||
                    foundYUV != IsSamplerYUVType(binding.samplerType))
                {
                    hasSamplerTypeConflict(textureUnit);
                }

                if (foundFormat != binding.format)
                {
                    hasSamplerFormatConflict(textureUnit);
                }
            }
        }
    }
}

void ProgramExecutable::saveLinkedStateInfo(const ProgramState &state)
{
    for (ShaderType shaderType : getLinkedShaderStages())
    {
        const SharedCompiledShaderState &shader = state.getAttachedShader(shaderType);
        ASSERT(shader);
        mPod.linkedShaderVersions[shaderType] = shader->shaderVersion;
        mLinkedOutputVaryings[shaderType]     = shader->outputVaryings;
        mLinkedInputVaryings[shaderType]      = shader->inputVaryings;
        mLinkedUniforms[shaderType]           = shader->uniforms;
        mLinkedUniformBlocks[shaderType]      = shader->uniformBlocks;
    }
}

bool ProgramExecutable::linkMergedVaryings(const Caps &caps,
                                           const Limitations &limitations,
                                           const Version &clientVersion,
                                           bool webglCompatibility,
                                           const ProgramMergedVaryings &mergedVaryings,
                                           const LinkingVariables &linkingVariables,
                                           ProgramVaryingPacking *varyingPacking)
{
    ShaderType tfStage = GetLastPreFragmentStage(linkingVariables.isShaderStageUsedBitset);

    if (!linkValidateTransformFeedback(caps, clientVersion, mergedVaryings, tfStage))
    {
        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 (limitations.noFlexibleVaryingPacking)
    {
        // D3D9 pack mode is strictly more strict than WebGL, so takes priority.
        packMode = PackMode::ANGLE_NON_CONFORMANT_D3D9;
    }
    else if (webglCompatibility)
    {
        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, caps, packMode, activeShadersMask,
                                                    mergedVaryings, mTransformFeedbackVaryingNames,
                                                    mPod.isSeparable))
    {
        return false;
    }

    gatherTransformFeedbackVaryings(mergedVaryings, tfStage);
    updateTransformFeedbackStrides();

    return true;
}

bool ProgramExecutable::linkValidateTransformFeedback(const Caps &caps,
                                                      const Version &clientVersion,
                                                      const ProgramMergedVaryings &varyings,
                                                      ShaderType stage)
{
    // Validate the tf names regardless of the actual program varyings.
    std::set<std::string> uniqueNames;
    for (const std::string &tfVaryingName : mTransformFeedbackVaryingNames)
    {
        if (clientVersion < Version(3, 1) && tfVaryingName.find('[') != std::string::npos)
        {
            *mInfoLog << "Capture of array elements is undefined and not supported.";
            return false;
        }
        if (clientVersion >= 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);
    }

    // From OpneGLES spec. 11.1.2.1: A program will fail to link if:
    // the count specified by TransformFeedbackVaryings is non-zero, but the
    // program object has no vertex, tessellation evaluation, or geometry shader
    if (mTransformFeedbackVaryingNames.size() > 0 &&
        !gl::ShaderTypeSupportsTransformFeedback(getLinkedTransformFeedbackStage()))
    {
        *mInfoLog << "Linked transform feedback stage " << getLinkedTransformFeedbackStage()
                  << " does not support transform feedback varying.";
        return false;
    }

    // Validate against program varyings.
    size_t totalComponents = 0;
    for (const std::string &tfVaryingName : mTransformFeedbackVaryingNames)
    {
        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 (clientVersion < 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;
        }

        componentCount = VariableComponentCount(var->type) * elementCount;
        if (mPod.transformFeedbackBufferMode == 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 (mPod.transformFeedbackBufferMode == 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)
{
    // Gather the linked varyings that are used for transform feedback, they should all exist.
    mLinkedTransformFeedbackVaryings.clear();
    for (const std::string &tfVaryingName : mTransformFeedbackVaryingNames)
    {
        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 (mLinkedTransformFeedbackVaryings.empty())
    {
        return;
    }

    if (mPod.transformFeedbackBufferMode == 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(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)
        {
            mCachedValidateSamplersResult = false;
            return false;
        }

        if (mActiveSamplerFormats[textureUnit] == SamplerFormat::InvalidEnum)
        {
            mCachedValidateSamplersResult = false;
            return false;
        }
    }

    mCachedValidateSamplersResult = true;
    return true;
}

bool ProgramExecutable::linkValidateOutputVariables(
    const Caps &caps,
    const Version &version,
    GLuint combinedImageUniformsCount,
    GLuint combinedShaderStorageBlocksCount,
    int fragmentShaderVersion,
    const ProgramAliasedBindings &fragmentOutputLocations,
    const ProgramAliasedBindings &fragmentOutputIndices)
{
    ASSERT(mPod.activeOutputVariablesMask.none());
    ASSERT(mPod.activeSecondaryOutputVariablesMask.none());
    ASSERT(mPod.drawBufferTypeMask.none());
    ASSERT(!mPod.hasYUVOutput);

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

    // 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 ProgramOutput &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 ProgramOutput &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.pod.basicTypeElementCount;
        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 ProgramOutput &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.pod.basicTypeElementCount;
        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;
        }
    }

    if (!gatherOutputTypes())
    {
        return false;
    }

    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 +
                mPod.activeOutputVariablesMask.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;
        }
    }

    return true;
}

bool ProgramExecutable::gatherOutputTypes()
{
    for (const ProgramOutput &outputVariable : mOutputVariables)
    {
        if (outputVariable.isBuiltIn() && outputVariable.name != "gl_FragColor" &&
            outputVariable.name != "gl_FragData" &&
            outputVariable.name != "gl_SecondaryFragColorEXT" &&
            outputVariable.name != "gl_SecondaryFragDataEXT")
        {
            continue;
        }

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

        const bool secondary =
            outputVariable.pod.index == 1 || (outputVariable.name == "gl_SecondaryFragColorEXT" ||
                                              outputVariable.name == "gl_SecondaryFragDataEXT");

        const ComponentType componentType =
            GLenumToComponentType(VariableComponentType(outputVariable.pod.type));

        // 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.pod.basicTypeElementCount;
        for (unsigned int elementIndex = 0; elementIndex < elementCount; elementIndex++)
        {
            const unsigned int location = baseLocation + elementIndex;
            ASSERT(location < mPod.activeOutputVariablesMask.size());
            ASSERT(location < mPod.activeSecondaryOutputVariablesMask.size());
            if (secondary)
            {
                mPod.activeSecondaryOutputVariablesMask.set(location);
            }
            else
            {
                mPod.activeOutputVariablesMask.set(location);
            }
            const ComponentType storedComponentType =
                gl::GetComponentTypeMask(mPod.drawBufferTypeMask, location);
            if (storedComponentType == ComponentType::InvalidEnum)
            {
                SetComponentTypeMask(componentType, location, &mPod.drawBufferTypeMask);
            }
            else if (storedComponentType != componentType)
            {
                *mInfoLog << "Inconsistent component types for fragment outputs at location "
                          << location;
                return false;
            }
        }

        if (outputVariable.pod.yuv)
        {
            ASSERT(mOutputVariables.size() == 1);
            mPod.hasYUVOutput = true;
        }
    }

    return true;
}

bool ProgramExecutable::linkUniforms(
    const Caps &caps,
    const ShaderMap<std::vector<sh::ShaderVariable>> &shaderUniforms,
    const ProgramAliasedBindings &uniformLocationBindings,
    GLuint *combinedImageUniformsCountOut,
    std::vector<UnusedUniform> *unusedUniformsOutOrNull)
{
    UniformLinker linker(mPod.linkedShaderStages, shaderUniforms);
    if (!linker.link(caps, *mInfoLog, uniformLocationBindings))
    {
        return false;
    }

    linker.getResults(&mUniforms, &mUniformNames, &mUniformMappedNames, unusedUniformsOutOrNull,
                      &mUniformLocations);

    linkSamplerAndImageBindings(combinedImageUniformsCountOut);

    if (!linkAtomicCounterBuffers(caps))
    {
        return false;
    }

    return true;
}

void ProgramExecutable::linkSamplerAndImageBindings(GLuint *combinedImageUniforms)
{
    ASSERT(combinedImageUniforms);

    // Iterate over mExecutable->mUniforms from the back, and find the range of subpass inputs,
    // atomic counters, images and samplers in that order.
    auto highIter = mUniforms.rbegin();
    auto lowIter  = highIter;

    unsigned int high = static_cast<unsigned int>(mUniforms.size());
    unsigned int low  = high;

    // Note that uniform block uniforms are not yet appended to this list.
    ASSERT(mUniforms.empty() || highIter->isAtomicCounter() || highIter->isImage() ||
           highIter->isSampler() || highIter->isInDefaultBlock() || highIter->isFragmentInOut());

    for (; lowIter != mUniforms.rend() && lowIter->isFragmentInOut(); ++lowIter)
    {
        --low;
    }

    mPod.fragmentInoutRange = RangeUI(low, high);

    highIter = lowIter;
    high     = low;

    for (; lowIter != mUniforms.rend() && lowIter->isAtomicCounter(); ++lowIter)
    {
        --low;
    }

    mPod.atomicCounterUniformRange = RangeUI(low, high);

    highIter = lowIter;
    high     = low;

    for (; lowIter != mUniforms.rend() && lowIter->isImage(); ++lowIter)
    {
        --low;
    }

    mPod.imageUniformRange = RangeUI(low, high);
    *combinedImageUniforms = 0u;
    // If uniform is a image type, insert it into the mImageBindings array.
    for (unsigned int imageIndex : mPod.imageUniformRange)
    {
        // ES3.1 (section 7.6.1) and GLSL ES3.1 (section 4.4.5), Uniform*i{v} commands
        // cannot load values into a uniform defined as an image. if declare without a
        // binding qualifier, any uniform image variable (include all elements of
        // unbound image array) should be bound to unit zero.
        auto &imageUniform      = mUniforms[imageIndex];
        TextureType textureType = ImageTypeToTextureType(imageUniform.getType());
        const GLuint arraySize  = imageUniform.getBasicTypeElementCount();

        if (imageUniform.getBinding() == -1)
        {
            mImageBindings.emplace_back(
                ImageBinding(imageUniform.getBasicTypeElementCount(), textureType));
        }
        else
        {
            // The arrays of arrays are flattened to arrays, it needs to record the array offset for
            // the correct binding image unit.
            mImageBindings.emplace_back(ImageBinding(
                imageUniform.getBinding() + imageUniform.pod.parentArrayIndex * arraySize,
                imageUniform.getBasicTypeElementCount(), textureType));
        }

        *combinedImageUniforms += imageUniform.activeShaderCount() * arraySize;
    }

    highIter = lowIter;
    high     = low;

    for (; lowIter != mUniforms.rend() && lowIter->isSampler(); ++lowIter)
    {
        --low;
    }

    mPod.samplerUniformRange = RangeUI(low, high);

    // If uniform is a sampler type, insert it into the mSamplerBindings array.
    uint16_t totalCount = 0;
    for (unsigned int samplerIndex : mPod.samplerUniformRange)
    {
        const auto &samplerUniform = mUniforms[samplerIndex];
        TextureType textureType    = SamplerTypeToTextureType(samplerUniform.getType());
        GLenum samplerType         = samplerUniform.getType();
        uint16_t elementCount      = samplerUniform.getBasicTypeElementCount();
        SamplerFormat format       = GetUniformTypeInfo(samplerType).samplerFormat;
        mSamplerBindings.emplace_back(textureType, samplerType, format, totalCount, elementCount);
        totalCount += elementCount;
    }
    mSamplerBoundTextureUnits.resize(totalCount, 0);

    // Whatever is left constitutes the default uniforms.
    mPod.defaultUniformRange = RangeUI(0, low);
}

bool ProgramExecutable::linkAtomicCounterBuffers(const Caps &caps)
{
    for (unsigned int index : mPod.atomicCounterUniformRange)
    {
        auto &uniform = mUniforms[index];

        uniform.pod.blockOffset                    = uniform.getOffset();
        uniform.pod.blockArrayStride               = uniform.isArray() ? 4 : 0;
        uniform.pod.blockMatrixStride              = 0;
        uniform.pod.flagBits.blockIsRowMajorMatrix = false;
        uniform.pod.flagBits.isBlock               = true;

        bool found = false;
        for (size_t bufferIndex = 0; bufferIndex < mAtomicCounterBuffers.size(); ++bufferIndex)
        {
            AtomicCounterBuffer &buffer = mAtomicCounterBuffers[bufferIndex];
            if (buffer.pod.binding == uniform.getBinding())
            {
                buffer.memberIndexes.push_back(index);
                SetBitField(uniform.pod.bufferIndex, bufferIndex);
                found = true;
                buffer.unionReferencesWith(uniform);
                break;
            }
        }
        if (!found)
        {
            AtomicCounterBuffer atomicCounterBuffer;
            atomicCounterBuffer.pod.binding = uniform.getBinding();
            atomicCounterBuffer.memberIndexes.push_back(index);
            atomicCounterBuffer.unionReferencesWith(uniform);
            mAtomicCounterBuffers.push_back(atomicCounterBuffer);
            SetBitField(uniform.pod.bufferIndex, mAtomicCounterBuffers.size() - 1);
        }
    }

    // Count each atomic counter buffer to validate against
    // per-stage and combined gl_Max*AtomicCounterBuffers.
    GLint combinedShaderACBCount           = 0;
    gl::ShaderMap<GLint> perShaderACBCount = {};
    for (size_t bufferIndex = 0; bufferIndex < mAtomicCounterBuffers.size(); ++bufferIndex)
    {
        AtomicCounterBuffer &acb        = mAtomicCounterBuffers[bufferIndex];
        const ShaderBitSet shaderStages = acb.activeShaders();
        for (gl::ShaderType shaderType : shaderStages)
        {
            ++perShaderACBCount[shaderType];
        }
        ++combinedShaderACBCount;
    }
    if (combinedShaderACBCount > caps.maxCombinedAtomicCounterBuffers)
    {
        *mInfoLog << " combined AtomicCounterBuffers count exceeds limit";
        return false;
    }
    for (gl::ShaderType stage : gl::AllShaderTypes())
    {
        if (perShaderACBCount[stage] > caps.maxShaderAtomicCounterBuffers[stage])
        {
            *mInfoLog << GetShaderTypeString(stage)
                      << " shader AtomicCounterBuffers count exceeds limit";
            return false;
        }
    }
    return true;
}

void ProgramExecutable::copyInputsFromProgram(const ProgramExecutable &executable)
{
    mProgramInputs = executable.getProgramInputs();
}

void ProgramExecutable::copyUniformBuffersFromProgram(
    const ProgramExecutable &executable,
    ShaderType shaderType,
    ProgramPipelineUniformBlockIndexMap *ppoUniformBlockMap)
{
    AppendActiveBlocks(shaderType, executable.getUniformBlocks(), mUniformBlocks,
                       ppoUniformBlockMap);
}

void ProgramExecutable::copyStorageBuffersFromProgram(const ProgramExecutable &executable,
                                                      ShaderType shaderType)
{
    AppendActiveBlocks(shaderType, executable.getShaderStorageBlocks(), mShaderStorageBlocks,
                       nullptr);
    AppendActiveBlocks(shaderType, executable.getAtomicCounterBuffers(), mAtomicCounterBuffers,
                       nullptr);

    // Buffer variable info is queried through the program, and program pipelines don't access it.
    ASSERT(mBufferVariables.empty());
}

void ProgramExecutable::clearSamplerBindings()
{
    mSamplerBindings.clear();
    mSamplerBoundTextureUnits.clear();
}

void ProgramExecutable::copySamplerBindingsFromProgram(const ProgramExecutable &executable)
{
    const std::vector<SamplerBinding> &bindings = executable.getSamplerBindings();
    const std::vector<GLuint> &textureUnits     = executable.getSamplerBoundTextureUnits();
    uint16_t adjustedStartIndex                 = mSamplerBoundTextureUnits.size();
    mSamplerBoundTextureUnits.insert(mSamplerBoundTextureUnits.end(), textureUnits.begin(),
                                     textureUnits.end());
    for (const SamplerBinding &binding : bindings)
    {
        mSamplerBindings.push_back(binding);
        mSamplerBindings.back().textureUnitsStartIndex += adjustedStartIndex;
    }
}

void ProgramExecutable::copyImageBindingsFromProgram(const ProgramExecutable &executable)
{
    const std::vector<ImageBinding> &bindings = executable.getImageBindings();
    mImageBindings.insert(mImageBindings.end(), bindings.begin(), bindings.end());
}

void ProgramExecutable::copyOutputsFromProgram(const ProgramExecutable &executable)
{
    mOutputVariables          = executable.getOutputVariables();
    mOutputLocations          = executable.getOutputLocations();
    mSecondaryOutputLocations = executable.getSecondaryOutputLocations();
}

void ProgramExecutable::copyUniformsFromProgramMap(
    const ShaderMap<SharedProgramExecutable> &executables)
{
    // Merge default uniforms.
    auto getDefaultRange = [](const ProgramExecutable &state) {
        return state.getDefaultUniformRange();
    };
    mPod.defaultUniformRange = AddUniforms(executables, mPod.linkedShaderStages, &mUniforms,
                                           &mUniformNames, &mUniformMappedNames, getDefaultRange);

    // Merge sampler uniforms.
    auto getSamplerRange = [](const ProgramExecutable &state) {
        return state.getSamplerUniformRange();
    };
    mPod.samplerUniformRange = AddUniforms(executables, mPod.linkedShaderStages, &mUniforms,
                                           &mUniformNames, &mUniformMappedNames, getSamplerRange);

    // Merge image uniforms.
    auto getImageRange = [](const ProgramExecutable &state) {
        return state.getImageUniformRange();
    };
    mPod.imageUniformRange = AddUniforms(executables, mPod.linkedShaderStages, &mUniforms,
                                         &mUniformNames, &mUniformMappedNames, getImageRange);

    // Merge atomic counter uniforms.
    auto getAtomicRange = [](const ProgramExecutable &state) {
        return state.getAtomicCounterUniformRange();
    };
    mPod.atomicCounterUniformRange =
        AddUniforms(executables, mPod.linkedShaderStages, &mUniforms, &mUniformNames,
                    &mUniformMappedNames, getAtomicRange);

    // Merge fragment in/out uniforms.
    auto getInoutRange = [](const ProgramExecutable &state) {
        return state.getFragmentInoutRange();
    };
    mPod.fragmentInoutRange = AddUniforms(executables, mPod.linkedShaderStages, &mUniforms,
                                          &mUniformNames, &mUniformMappedNames, getInoutRange);

    // Note: uniforms are set through the program, and the program pipeline never needs it.
    ASSERT(mUniformLocations.empty());
}

void ProgramExecutable::getResourceName(const std::string name,
                                        GLsizei bufSize,
                                        GLsizei *length,
                                        GLchar *dest) const
{
    if (length)
    {
        *length = 0;
    }

    if (bufSize > 0)
    {
        CopyStringToBuffer(dest, name, bufSize, length);
    }
}

GLuint ProgramExecutable::getInputResourceIndex(const GLchar *name) const
{
    const std::string nameString = StripLastArrayIndex(name);

    for (size_t index = 0; index < mProgramInputs.size(); index++)
    {
        if (mProgramInputs[index].name == nameString)
        {
            return static_cast<GLuint>(index);
        }
    }

    return GL_INVALID_INDEX;
}

GLuint ProgramExecutable::getInputResourceMaxNameSize() const
{
    GLint max = 0;

    for (const ProgramInput &resource : mProgramInputs)
    {
        max = GetResourceMaxNameSize(resource, max);
    }

    return max;
}

GLuint ProgramExecutable::getOutputResourceMaxNameSize() const
{
    GLint max = 0;

    for (const gl::ProgramOutput &resource : mOutputVariables)
    {
        max = GetResourceMaxNameSize(resource, max);
    }

    return max;
}

GLuint ProgramExecutable::getInputResourceLocation(const GLchar *name) const
{
    const GLuint index = getInputResourceIndex(name);
    if (index == GL_INVALID_INDEX)
    {
        return index;
    }

    const ProgramInput &variable = getInputResource(index);

    return GetResourceLocation(name, variable, variable.getLocation());
}

GLuint ProgramExecutable::getOutputResourceLocation(const GLchar *name) const
{
    const GLuint index = getOutputResourceIndex(name);
    if (index == GL_INVALID_INDEX)
    {
        return index;
    }

    const gl::ProgramOutput &variable = getOutputResource(index);

    return GetResourceLocation(name, variable, variable.pod.location);
}

GLuint ProgramExecutable::getOutputResourceIndex(const GLchar *name) const
{
    const std::string nameString = StripLastArrayIndex(name);

    for (size_t index = 0; index < mOutputVariables.size(); index++)
    {
        if (mOutputVariables[index].name == nameString)
        {
            return static_cast<GLuint>(index);
        }
    }

    return GL_INVALID_INDEX;
}

void ProgramExecutable::getInputResourceName(GLuint index,
                                             GLsizei bufSize,
                                             GLsizei *length,
                                             GLchar *name) const
{
    getResourceName(getInputResourceName(index), bufSize, length, name);
}

void ProgramExecutable::getOutputResourceName(GLuint index,
                                              GLsizei bufSize,
                                              GLsizei *length,
                                              GLchar *name) const
{
    getResourceName(getOutputResourceName(index), bufSize, length, name);
}

void ProgramExecutable::getUniformResourceName(GLuint index,
                                               GLsizei bufSize,
                                               GLsizei *length,
                                               GLchar *name) const
{
    getResourceName(getUniformNameByIndex(index), bufSize, length, name);
}

void ProgramExecutable::getBufferVariableResourceName(GLuint index,
                                                      GLsizei bufSize,
                                                      GLsizei *length,
                                                      GLchar *name) const
{
    ASSERT(index < mBufferVariables.size());
    getResourceName(mBufferVariables[index].name, bufSize, length, name);
}

const std::string ProgramExecutable::getInputResourceName(GLuint index) const
{
    return GetResourceName(getInputResource(index));
}

const std::string ProgramExecutable::getOutputResourceName(GLuint index) const
{
    return GetResourceName(getOutputResource(index));
}

GLint ProgramExecutable::getFragDataLocation(const std::string &name) const
{
    const GLint primaryLocation = GetVariableLocation(mOutputVariables, mOutputLocations, name);
    if (primaryLocation != -1)
    {
        return primaryLocation;
    }
    return GetVariableLocation(mOutputVariables, mSecondaryOutputLocations, name);
}

GLint ProgramExecutable::getFragDataIndex(const std::string &name) const
{
    if (GetVariableLocation(mOutputVariables, mOutputLocations, name) != -1)
    {
        return 0;
    }
    if (GetVariableLocation(mOutputVariables, mSecondaryOutputLocations, name) != -1)
    {
        return 1;
    }
    return -1;
}

GLsizei ProgramExecutable::getTransformFeedbackVaryingMaxLength() const
{
    GLsizei maxSize = 0;
    for (const TransformFeedbackVarying &var : mLinkedTransformFeedbackVaryings)
    {
        maxSize = std::max(maxSize, static_cast<GLsizei>(var.nameWithArrayIndex().length() + 1));
    }

    return maxSize;
}

GLuint ProgramExecutable::getTransformFeedbackVaryingResourceIndex(const GLchar *name) const
{
    for (GLuint tfIndex = 0; tfIndex < mLinkedTransformFeedbackVaryings.size(); ++tfIndex)
    {
        if (mLinkedTransformFeedbackVaryings[tfIndex].nameWithArrayIndex() == name)
        {
            return tfIndex;
        }
    }
    return GL_INVALID_INDEX;
}

const TransformFeedbackVarying &ProgramExecutable::getTransformFeedbackVaryingResource(
    GLuint index) const
{
    ASSERT(index < mLinkedTransformFeedbackVaryings.size());
    return mLinkedTransformFeedbackVaryings[index];
}

void ProgramExecutable::getTransformFeedbackVarying(GLuint index,
                                                    GLsizei bufSize,
                                                    GLsizei *length,
                                                    GLsizei *size,
                                                    GLenum *type,
                                                    GLchar *name) const
{
    if (mLinkedTransformFeedbackVaryings.empty())
    {
        // Program is not successfully linked
        return;
    }

    ASSERT(index < mLinkedTransformFeedbackVaryings.size());
    const auto &var     = mLinkedTransformFeedbackVaryings[index];
    std::string varName = var.nameWithArrayIndex();
    GLsizei lastNameIdx = std::min(bufSize - 1, static_cast<GLsizei>(varName.length()));
    if (length)
    {
        *length = lastNameIdx;
    }
    if (size)
    {
        *size = var.size();
    }
    if (type)
    {
        *type = var.type;
    }
    if (name)
    {
        memcpy(name, varName.c_str(), lastNameIdx);
        name[lastNameIdx] = '\0';
    }
}

void ProgramExecutable::getActiveAttribute(GLuint index,
                                           GLsizei bufsize,
                                           GLsizei *length,
                                           GLint *size,
                                           GLenum *type,
                                           GLchar *name) const
{
    if (mProgramInputs.empty())
    {
        // Program is not successfully linked
        if (bufsize > 0)
        {
            name[0] = '\0';
        }

        if (length)
        {
            *length = 0;
        }

        *type = GL_NONE;
        *size = 1;
        return;
    }

    ASSERT(index < mProgramInputs.size());
    const ProgramInput &attrib = mProgramInputs[index];

    if (bufsize > 0)
    {
        CopyStringToBuffer(name, attrib.name, bufsize, length);
    }

    // Always a single 'type' instance
    *size = 1;
    *type = attrib.getType();
}

GLint ProgramExecutable::getActiveAttributeMaxLength() const
{
    size_t maxLength = 0;

    for (const ProgramInput &attrib : mProgramInputs)
    {
        maxLength = std::max(attrib.name.length() + 1, maxLength);
    }

    return static_cast<GLint>(maxLength);
}

GLuint ProgramExecutable::getAttributeLocation(const std::string &name) const
{
    for (const ProgramInput &attribute : mProgramInputs)
    {
        if (attribute.name == name)
        {
            return attribute.getLocation();
        }
    }

    return static_cast<GLuint>(-1);
}

void ProgramExecutable::getActiveUniform(GLuint index,
                                         GLsizei bufsize,
                                         GLsizei *length,
                                         GLint *size,
                                         GLenum *type,
                                         GLchar *name) const
{
    if (mUniforms.empty())
    {
        // Program is not successfully linked
        if (bufsize > 0)
        {
            name[0] = '\0';
        }

        if (length)
        {
            *length = 0;
        }

        *size = 0;
        *type = GL_NONE;
    }

    ASSERT(index < mUniforms.size());
    const LinkedUniform &uniform = mUniforms[index];

    if (bufsize > 0)
    {
        const std::string &string = getUniformNameByIndex(index);
        CopyStringToBuffer(name, string, bufsize, length);
    }

    *size = clampCast<GLint>(uniform.getBasicTypeElementCount());
    *type = uniform.getType();
}

GLint ProgramExecutable::getActiveUniformMaxLength() const
{
    size_t maxLength = 0;

    for (GLuint index = 0; index < static_cast<size_t>(mUniformNames.size()); index++)
    {
        const std::string &uniformName = getUniformNameByIndex(index);
        if (!uniformName.empty())
        {
            size_t length = uniformName.length() + 1u;
            if (getUniformByIndex(index).isArray())
            {
                length += 3;  // Counting in "[0]".
            }
            maxLength = std::max(length, maxLength);
        }
    }

    return static_cast<GLint>(maxLength);
}

bool ProgramExecutable::isValidUniformLocation(UniformLocation location) const
{
    ASSERT(angle::IsValueInRangeForNumericType<GLint>(mUniformLocations.size()));
    return location.value >= 0 && static_cast<size_t>(location.value) < mUniformLocations.size() &&
           mUniformLocations[location.value].used();
}

const LinkedUniform &ProgramExecutable::getUniformByLocation(UniformLocation location) const
{
    ASSERT(location.value >= 0 && static_cast<size_t>(location.value) < mUniformLocations.size());
    return mUniforms[getUniformIndexFromLocation(location)];
}

const VariableLocation &ProgramExecutable::getUniformLocation(UniformLocation location) const
{
    ASSERT(location.value >= 0 && static_cast<size_t>(location.value) < mUniformLocations.size());
    return mUniformLocations[location.value];
}

UniformLocation ProgramExecutable::getUniformLocation(const std::string &name) const
{
    return {GetUniformLocation(mUniforms, mUniformNames, mUniformLocations, name)};
}

GLuint ProgramExecutable::getUniformIndex(const std::string &name) const
{
    return getUniformIndexFromName(name);
}

bool ProgramExecutable::shouldIgnoreUniform(UniformLocation location) const
{
    if (location.value < 0)
    {
        return true;
    }

    if (static_cast<size_t>(location.value) >= mUniformLocations.size())
    {
        ERR() << "Invalid uniform location " << location.value << ", expected [0, "
              << mUniformLocations.size() << ")";
        return true;
    }

    return mUniformLocations[location.value].ignored;
}

GLuint ProgramExecutable::getUniformIndexFromName(const std::string &name) const
{
    return GetUniformIndexFromName(mUniforms, mUniformNames, name);
}

GLuint ProgramExecutable::getBufferVariableIndexFromName(const std::string &name) const
{
    return GetResourceIndexFromName(mBufferVariables, name);
}

GLuint ProgramExecutable::getUniformIndexFromLocation(UniformLocation location) const
{
    ASSERT(location.value >= 0 && static_cast<size_t>(location.value) < mUniformLocations.size());
    return mUniformLocations[location.value].index;
}

Optional<GLuint> ProgramExecutable::getSamplerIndex(UniformLocation location) const
{
    GLuint index = getUniformIndexFromLocation(location);
    if (!isSamplerUniformIndex(index))
    {
        return Optional<GLuint>::Invalid();
    }

    return getSamplerIndexFromUniformIndex(index);
}

bool ProgramExecutable::isSamplerUniformIndex(GLuint index) const
{
    return mPod.samplerUniformRange.contains(index);
}

GLuint ProgramExecutable::getSamplerIndexFromUniformIndex(GLuint uniformIndex) const
{
    ASSERT(isSamplerUniformIndex(uniformIndex));
    return uniformIndex - mPod.samplerUniformRange.low();
}

bool ProgramExecutable::isImageUniformIndex(GLuint index) const
{
    return mPod.imageUniformRange.contains(index);
}

GLuint ProgramExecutable::getImageIndexFromUniformIndex(GLuint uniformIndex) const
{
    ASSERT(isImageUniformIndex(uniformIndex));
    return uniformIndex - mPod.imageUniformRange.low();
}

void ProgramExecutable::getActiveUniformBlockName(const Context *context,
                                                  const UniformBlockIndex blockIndex,
                                                  GLsizei bufSize,
                                                  GLsizei *length,
                                                  GLchar *blockName) const
{
    GetInterfaceBlockName(blockIndex, mUniformBlocks, bufSize, length, blockName);
}

void ProgramExecutable::getActiveShaderStorageBlockName(const GLuint blockIndex,
                                                        GLsizei bufSize,
                                                        GLsizei *length,
                                                        GLchar *blockName) const
{
    GetInterfaceBlockName({blockIndex}, mShaderStorageBlocks, bufSize, length, blockName);
}

GLint ProgramExecutable::getActiveUniformBlockMaxNameLength() const
{
    return GetActiveInterfaceBlockMaxNameLength(mUniformBlocks);
}

GLint ProgramExecutable::getActiveShaderStorageBlockMaxNameLength() const
{
    return GetActiveInterfaceBlockMaxNameLength(mShaderStorageBlocks);
}

GLuint ProgramExecutable::getUniformBlockIndex(const std::string &name) const
{
    return GetInterfaceBlockIndex(mUniformBlocks, name);
}

GLuint ProgramExecutable::getShaderStorageBlockIndex(const std::string &name) const
{
    return GetInterfaceBlockIndex(mShaderStorageBlocks, name);
}

GLuint ProgramExecutable::getSamplerUniformBinding(const VariableLocation &uniformLocation) const
{
    GLuint samplerIndex                  = getSamplerIndexFromUniformIndex(uniformLocation.index);
    const SamplerBinding &samplerBinding = mSamplerBindings[samplerIndex];
    if (uniformLocation.arrayIndex >= samplerBinding.textureUnitsCount)
    {
        return 0;
    }

    const std::vector<GLuint> &boundTextureUnits = mSamplerBoundTextureUnits;
    return samplerBinding.getTextureUnit(boundTextureUnits, uniformLocation.arrayIndex);
}

GLuint ProgramExecutable::getImageUniformBinding(const VariableLocation &uniformLocation) const
{
    GLuint imageIndex = getImageIndexFromUniformIndex(uniformLocation.index);

    const std::vector<GLuint> &boundImageUnits = mImageBindings[imageIndex].boundImageUnits;
    return boundImageUnits[uniformLocation.arrayIndex];
}

template <typename UniformT,
          GLint UniformSize,
          void (rx::ProgramExecutableImpl::*SetUniformFunc)(GLint, GLsizei, const UniformT *)>
void ProgramExecutable::setUniformGeneric(UniformLocation location,
                                          GLsizei count,
                                          const UniformT *v)
{
    if (shouldIgnoreUniform(location))
    {
        return;
    }

    const VariableLocation &locationInfo = mUniformLocations[location.value];
    GLsizei clampedCount                 = clampUniformCount(locationInfo, count, UniformSize, v);
    (mImplementation->*SetUniformFunc)(location.value, clampedCount, v);
    onStateChange(angle::SubjectMessage::ProgramUniformUpdated);
}

void ProgramExecutable::setUniform1fv(UniformLocation location, GLsizei count, const GLfloat *v)
{
    setUniformGeneric<GLfloat, 1, &rx::ProgramExecutableImpl::setUniform1fv>(location, count, v);
}

void ProgramExecutable::setUniform2fv(UniformLocation location, GLsizei count, const GLfloat *v)
{
    setUniformGeneric<GLfloat, 2, &rx::ProgramExecutableImpl::setUniform2fv>(location, count, v);
}

void ProgramExecutable::setUniform3fv(UniformLocation location, GLsizei count, const GLfloat *v)
{
    setUniformGeneric<GLfloat, 3, &rx::ProgramExecutableImpl::setUniform3fv>(location, count, v);
}

void ProgramExecutable::setUniform4fv(UniformLocation location, GLsizei count, const GLfloat *v)
{
    setUniformGeneric<GLfloat, 4, &rx::ProgramExecutableImpl::setUniform4fv>(location, count, v);
}

void ProgramExecutable::setUniform1iv(Context *context,
                                      UniformLocation location,
                                      GLsizei count,
                                      const GLint *v)
{
    if (shouldIgnoreUniform(location))
    {
        return;
    }

    const VariableLocation &locationInfo = mUniformLocations[location.value];
    GLsizei clampedCount                 = clampUniformCount(locationInfo, count, 1, v);

    mImplementation->setUniform1iv(location.value, clampedCount, v);

    if (isSamplerUniformIndex(locationInfo.index))
    {
        updateSamplerUniform(context, locationInfo, clampedCount, v);
    }
    else
    {
        onStateChange(angle::SubjectMessage::ProgramUniformUpdated);
    }
}

void ProgramExecutable::setUniform2iv(UniformLocation location, GLsizei count, const GLint *v)
{
    setUniformGeneric<GLint, 2, &rx::ProgramExecutableImpl::setUniform2iv>(location, count, v);
}

void ProgramExecutable::setUniform3iv(UniformLocation location, GLsizei count, const GLint *v)
{
    setUniformGeneric<GLint, 3, &rx::ProgramExecutableImpl::setUniform3iv>(location, count, v);
}

void ProgramExecutable::setUniform4iv(UniformLocation location, GLsizei count, const GLint *v)
{
    setUniformGeneric<GLint, 4, &rx::ProgramExecutableImpl::setUniform4iv>(location, count, v);
}

void ProgramExecutable::setUniform1uiv(UniformLocation location, GLsizei count, const GLuint *v)
{
    setUniformGeneric<GLuint, 1, &rx::ProgramExecutableImpl::setUniform1uiv>(location, count, v);
}

void ProgramExecutable::setUniform2uiv(UniformLocation location, GLsizei count, const GLuint *v)
{
    setUniformGeneric<GLuint, 2, &rx::ProgramExecutableImpl::setUniform2uiv>(location, count, v);
}

void ProgramExecutable::setUniform3uiv(UniformLocation location, GLsizei count, const GLuint *v)
{
    setUniformGeneric<GLuint, 3, &rx::ProgramExecutableImpl::setUniform3uiv>(location, count, v);
}

void ProgramExecutable::setUniform4uiv(UniformLocation location, GLsizei count, const GLuint *v)
{
    setUniformGeneric<GLuint, 4, &rx::ProgramExecutableImpl::setUniform4uiv>(location, count, v);
}

template <typename UniformT,
          GLint MatrixC,
          GLint MatrixR,
          void (rx::ProgramExecutableImpl::*
                    SetUniformMatrixFunc)(GLint, GLsizei, GLboolean, const UniformT *)>
void ProgramExecutable::setUniformMatrixGeneric(UniformLocation location,
                                                GLsizei count,
                                                GLboolean transpose,
                                                const UniformT *v)
{
    if (shouldIgnoreUniform(location))
    {
        return;
    }

    GLsizei clampedCount = clampMatrixUniformCount<MatrixC, MatrixR>(location, count, transpose, v);
    (mImplementation->*SetUniformMatrixFunc)(location.value, clampedCount, transpose, v);
    onStateChange(angle::SubjectMessage::ProgramUniformUpdated);
}

void ProgramExecutable::setUniformMatrix2fv(UniformLocation location,
                                            GLsizei count,
                                            GLboolean transpose,
                                            const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 2, 2, &rx::ProgramExecutableImpl::setUniformMatrix2fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix3fv(UniformLocation location,
                                            GLsizei count,
                                            GLboolean transpose,
                                            const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 3, 3, &rx::ProgramExecutableImpl::setUniformMatrix3fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix4fv(UniformLocation location,
                                            GLsizei count,
                                            GLboolean transpose,
                                            const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 4, 4, &rx::ProgramExecutableImpl::setUniformMatrix4fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix2x3fv(UniformLocation location,
                                              GLsizei count,
                                              GLboolean transpose,
                                              const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 2, 3, &rx::ProgramExecutableImpl::setUniformMatrix2x3fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix2x4fv(UniformLocation location,
                                              GLsizei count,
                                              GLboolean transpose,
                                              const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 2, 4, &rx::ProgramExecutableImpl::setUniformMatrix2x4fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix3x2fv(UniformLocation location,
                                              GLsizei count,
                                              GLboolean transpose,
                                              const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 3, 2, &rx::ProgramExecutableImpl::setUniformMatrix3x2fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix3x4fv(UniformLocation location,
                                              GLsizei count,
                                              GLboolean transpose,
                                              const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 3, 4, &rx::ProgramExecutableImpl::setUniformMatrix3x4fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix4x2fv(UniformLocation location,
                                              GLsizei count,
                                              GLboolean transpose,
                                              const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 4, 2, &rx::ProgramExecutableImpl::setUniformMatrix4x2fv>(
        location, count, transpose, v);
}

void ProgramExecutable::setUniformMatrix4x3fv(UniformLocation location,
                                              GLsizei count,
                                              GLboolean transpose,
                                              const GLfloat *v)
{
    setUniformMatrixGeneric<GLfloat, 4, 3, &rx::ProgramExecutableImpl::setUniformMatrix4x3fv>(
        location, count, transpose, v);
}

void ProgramExecutable::getUniformfv(const Context *context,
                                     UniformLocation location,
                                     GLfloat *v) const
{
    const VariableLocation &uniformLocation = mUniformLocations[location.value];
    const LinkedUniform &uniform            = mUniforms[uniformLocation.index];

    if (uniform.isSampler())
    {
        *v = static_cast<GLfloat>(getSamplerUniformBinding(uniformLocation));
        return;
    }
    else if (uniform.isImage())
    {
        *v = static_cast<GLfloat>(getImageUniformBinding(uniformLocation));
        return;
    }

    const GLenum nativeType = VariableComponentType(uniform.getType());
    if (nativeType == GL_FLOAT)
    {
        mImplementation->getUniformfv(context, location.value, v);
    }
    else
    {
        getUniformInternal(context, v, location, nativeType,
                           VariableComponentCount(uniform.getType()));
    }
}

void ProgramExecutable::getUniformiv(const Context *context,
                                     UniformLocation location,
                                     GLint *v) const
{
    const VariableLocation &uniformLocation = mUniformLocations[location.value];
    const LinkedUniform &uniform            = mUniforms[uniformLocation.index];

    if (uniform.isSampler())
    {
        *v = static_cast<GLint>(getSamplerUniformBinding(uniformLocation));
        return;
    }
    else if (uniform.isImage())
    {
        *v = static_cast<GLint>(getImageUniformBinding(uniformLocation));
        return;
    }

    const GLenum nativeType = VariableComponentType(uniform.getType());
    if (nativeType == GL_INT || nativeType == GL_BOOL)
    {
        mImplementation->getUniformiv(context, location.value, v);
    }
    else
    {
        getUniformInternal(context, v, location, nativeType,
                           VariableComponentCount(uniform.getType()));
    }
}

void ProgramExecutable::getUniformuiv(const Context *context,
                                      UniformLocation location,
                                      GLuint *v) const
{
    const VariableLocation &uniformLocation = mUniformLocations[location.value];
    const LinkedUniform &uniform            = mUniforms[uniformLocation.index];

    if (uniform.isSampler())
    {
        *v = getSamplerUniformBinding(uniformLocation);
        return;
    }
    else if (uniform.isImage())
    {
        *v = getImageUniformBinding(uniformLocation);
        return;
    }

    const GLenum nativeType = VariableComponentType(uniform.getType());
    if (nativeType == GL_UNSIGNED_INT)
    {
        mImplementation->getUniformuiv(context, location.value, v);
    }
    else
    {
        getUniformInternal(context, v, location, nativeType,
                           VariableComponentCount(uniform.getType()));
    }
}

void ProgramExecutable::setUniformValuesFromBindingQualifiers()
{
    for (unsigned int samplerIndex : mPod.samplerUniformRange)
    {
        const auto &samplerUniform = mUniforms[samplerIndex];
        if (samplerUniform.getBinding() != -1)
        {
            const std::string &uniformName = getUniformNameByIndex(samplerIndex);
            UniformLocation location       = getUniformLocation(uniformName);
            ASSERT(location.value != -1);
            std::vector<GLint> boundTextureUnits;
            for (unsigned int elementIndex = 0;
                 elementIndex < samplerUniform.getBasicTypeElementCount(); ++elementIndex)
            {
                boundTextureUnits.push_back(samplerUniform.getBinding() + elementIndex);
            }

            // Here we pass nullptr to avoid a large chain of calls that need a non-const Context.
            // We know it's safe not to notify the Context because this is only called after link.
            setUniform1iv(nullptr, location, static_cast<GLsizei>(boundTextureUnits.size()),
                          boundTextureUnits.data());
        }
    }
}

template <typename T>
GLsizei ProgramExecutable::clampUniformCount(const VariableLocation &locationInfo,
                                             GLsizei count,
                                             int vectorSize,
                                             const T *v)
{
    if (count == 1)
        return 1;

    const LinkedUniform &linkedUniform = mUniforms[locationInfo.index];

    // OpenGL ES 3.0.4 spec pg 67: "Values for any array element that exceeds the highest array
    // element index used, as reported by GetActiveUniform, will be ignored by the GL."
    unsigned int remainingElements =
        linkedUniform.getBasicTypeElementCount() - locationInfo.arrayIndex;
    GLsizei maxElementCount =
        static_cast<GLsizei>(remainingElements * linkedUniform.getElementComponents());

    if (count * vectorSize > maxElementCount)
    {
        return maxElementCount / vectorSize;
    }

    return count;
}

template <size_t cols, size_t rows, typename T>
GLsizei ProgramExecutable::clampMatrixUniformCount(UniformLocation location,
                                                   GLsizei count,
                                                   GLboolean transpose,
                                                   const T *v)
{
    const VariableLocation &locationInfo = mUniformLocations[location.value];

    if (!transpose)
    {
        return clampUniformCount(locationInfo, count, cols * rows, v);
    }

    const LinkedUniform &linkedUniform = mUniforms[locationInfo.index];

    // OpenGL ES 3.0.4 spec pg 67: "Values for any array element that exceeds the highest array
    // element index used, as reported by GetActiveUniform, will be ignored by the GL."
    unsigned int remainingElements =
        linkedUniform.getBasicTypeElementCount() - locationInfo.arrayIndex;
    return std::min(count, static_cast<GLsizei>(remainingElements));
}

void ProgramExecutable::updateSamplerUniform(Context *context,
                                             const VariableLocation &locationInfo,
                                             GLsizei clampedCount,
                                             const GLint *v)
{
    ASSERT(isSamplerUniformIndex(locationInfo.index));
    GLuint samplerIndex                    = getSamplerIndexFromUniformIndex(locationInfo.index);
    SamplerBinding &samplerBinding         = mSamplerBindings[samplerIndex];
    std::vector<GLuint> &boundTextureUnits = mSamplerBoundTextureUnits;

    if (locationInfo.arrayIndex >= samplerBinding.textureUnitsCount)
    {
        return;
    }
    GLsizei safeUniformCount =
        std::min(clampedCount,
                 static_cast<GLsizei>(samplerBinding.textureUnitsCount - locationInfo.arrayIndex));

    // Update the sampler uniforms.
    for (uint16_t arrayIndex = 0; arrayIndex < safeUniformCount; ++arrayIndex)
    {
        GLint oldTextureUnit =
            samplerBinding.getTextureUnit(boundTextureUnits, arrayIndex + locationInfo.arrayIndex);
        GLint newTextureUnit = v[arrayIndex];

        if (oldTextureUnit == newTextureUnit)
        {
            continue;
        }

        // Update sampler's bound textureUnit
        boundTextureUnits[samplerBinding.textureUnitsStartIndex + arrayIndex +
                          locationInfo.arrayIndex] = newTextureUnit;

        // Update the reference counts.
        uint32_t &oldRefCount = mActiveSamplerRefCounts[oldTextureUnit];
        uint32_t &newRefCount = mActiveSamplerRefCounts[newTextureUnit];
        ASSERT(oldRefCount > 0);
        ASSERT(newRefCount < std::numeric_limits<uint32_t>::max());
        oldRefCount--;
        newRefCount++;

        // Check for binding type change.
        TextureType newSamplerType     = mActiveSamplerTypes[newTextureUnit];
        TextureType oldSamplerType     = mActiveSamplerTypes[oldTextureUnit];
        SamplerFormat newSamplerFormat = mActiveSamplerFormats[newTextureUnit];
        SamplerFormat oldSamplerFormat = mActiveSamplerFormats[oldTextureUnit];
        bool newSamplerYUV             = mActiveSamplerYUV.test(newTextureUnit);

        if (newRefCount == 1)
        {
            setActive(newTextureUnit, samplerBinding, mUniforms[locationInfo.index]);
        }
        else
        {
            if (newSamplerType != samplerBinding.textureType ||
                newSamplerYUV != IsSamplerYUVType(samplerBinding.samplerType))
            {
                hasSamplerTypeConflict(newTextureUnit);
            }

            if (newSamplerFormat != samplerBinding.format)
            {
                hasSamplerFormatConflict(newTextureUnit);
            }
        }

        // Unset previously active sampler.
        if (oldRefCount == 0)
        {
            setInactive(oldTextureUnit);
        }
        else
        {
            if (oldSamplerType == TextureType::InvalidEnum ||
                oldSamplerFormat == SamplerFormat::InvalidEnum)
            {
                // Previous conflict. Check if this new change fixed the conflict.
                setSamplerUniformTextureTypeAndFormat(oldTextureUnit);
            }
        }

        // Update the observing PPO's executable, if any.
        // Do this before any of the Context work, since that uses the current ProgramExecutable,
        // which will be the PPO's if this Program is bound to it, rather than this Program's.
        if (mPod.isSeparable)
        {
            onStateChange(angle::SubjectMessage::ProgramTextureOrImageBindingChanged);
        }

        // Notify context.
        if (context)
        {
            context->onSamplerUniformChange(newTextureUnit);
            context->onSamplerUniformChange(oldTextureUnit);
        }
    }

    // Invalidate the validation cache.
    resetCachedValidateSamplersResult();
    // Inform any PPOs this Program may be bound to.
    onStateChange(angle::SubjectMessage::SamplerUniformsUpdated);
}

// Driver differences mean that doing the uniform value cast ourselves gives consistent results.
// EG: on NVIDIA drivers, it was observed that getUniformi for MAX_INT+1 returned MIN_INT.
template <typename DestT>
void ProgramExecutable::getUniformInternal(const Context *context,
                                           DestT *dataOut,
                                           UniformLocation location,
                                           GLenum nativeType,
                                           int components) const
{
    switch (nativeType)
    {
        case GL_BOOL:
        {
            GLint tempValue[16] = {0};
            mImplementation->getUniformiv(context, location.value, tempValue);
            UniformStateQueryCastLoop<GLboolean>(
                dataOut, reinterpret_cast<const uint8_t *>(tempValue), components);
            break;
        }
        case GL_INT:
        {
            GLint tempValue[16] = {0};
            mImplementation->getUniformiv(context, location.value, tempValue);
            UniformStateQueryCastLoop<GLint>(dataOut, reinterpret_cast<const uint8_t *>(tempValue),
                                             components);
            break;
        }
        case GL_UNSIGNED_INT:
        {
            GLuint tempValue[16] = {0};
            mImplementation->getUniformuiv(context, location.value, tempValue);
            UniformStateQueryCastLoop<GLuint>(dataOut, reinterpret_cast<const uint8_t *>(tempValue),
                                              components);
            break;
        }
        case GL_FLOAT:
        {
            GLfloat tempValue[16] = {0};
            mImplementation->getUniformfv(context, location.value, tempValue);
            UniformStateQueryCastLoop<GLfloat>(
                dataOut, reinterpret_cast<const uint8_t *>(tempValue), components);
            break;
        }
        default:
            UNREACHABLE();
            break;
    }
}

void ProgramExecutable::setDrawIDUniform(GLint drawid)
{
    ASSERT(hasDrawIDUniform());
    mImplementation->setUniform1iv(mPod.drawIDLocation, 1, &drawid);
}

void ProgramExecutable::setBaseVertexUniform(GLint baseVertex)
{
    ASSERT(hasBaseVertexUniform());
    if (baseVertex == mCachedBaseVertex)
    {
        return;
    }
    mCachedBaseVertex = baseVertex;
    mImplementation->setUniform1iv(mPod.baseVertexLocation, 1, &baseVertex);
}

void ProgramExecutable::setBaseInstanceUniform(GLuint baseInstance)
{
    ASSERT(hasBaseInstanceUniform());
    if (baseInstance == mCachedBaseInstance)
    {
        return;
    }
    mCachedBaseInstance   = baseInstance;
    GLint baseInstanceInt = baseInstance;
    mImplementation->setUniform1iv(mPod.baseInstanceLocation, 1, &baseInstanceInt);
}

gl::ProgramExecutable::DirtyBits ProgramExecutable::getAndResetDirtyBits() const
{
    DirtyBits dirtyBits = mDirtyBits;
    mDirtyBits.reset();
    return dirtyBits;
}

void InstallExecutable(const Context *context,
                       const SharedProgramExecutable &toInstall,
                       SharedProgramExecutable *executable)
{
    // There should never be a need to re-install the same executable.
    ASSERT(toInstall.get() != executable->get());

    // Destroy the old executable before it gets deleted.
    UninstallExecutable(context, executable);

    // Install the new executable.
    *executable = toInstall;
}

void UninstallExecutable(const Context *context, SharedProgramExecutable *executable)
{
    if (executable->use_count() == 1)
    {
        (*executable)->destroy(context);
    }

    executable->reset();
}

}  // namespace gl
