/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Program interface utilities
 *//*--------------------------------------------------------------------*/

#include "es31fProgramInterfaceDefinitionUtil.hpp"
#include "es31fProgramInterfaceDefinition.hpp"
#include "gluVarType.hpp"
#include "gluVarTypeUtil.hpp"
#include "gluShaderUtil.hpp"
#include "deString.h"
#include "deStringUtil.hpp"
#include "glwEnums.hpp"

#include <set>
#include <map>
#include <sstream>
#include <vector>
#include <algorithm>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace ProgramInterfaceDefinition
{

VariableSearchFilter::VariableSearchFilter(void) : m_shaderTypeBits(0xFFFFFFFFul), m_storageBits(0xFFFFFFFFul)
{
}

VariableSearchFilter VariableSearchFilter::createShaderTypeFilter(glu::ShaderType type)
{
    DE_ASSERT(type < glu::SHADERTYPE_LAST);

    VariableSearchFilter filter;
    filter.m_shaderTypeBits = (1u << type);
    return filter;
}

VariableSearchFilter VariableSearchFilter::createStorageFilter(glu::Storage storage)
{
    DE_ASSERT(storage < glu::STORAGE_LAST);

    VariableSearchFilter filter;
    filter.m_storageBits = (1u << storage);
    return filter;
}

VariableSearchFilter VariableSearchFilter::createShaderTypeStorageFilter(glu::ShaderType type, glu::Storage storage)
{
    return logicalAnd(createShaderTypeFilter(type), createStorageFilter(storage));
}

VariableSearchFilter VariableSearchFilter::logicalOr(const VariableSearchFilter &a, const VariableSearchFilter &b)
{
    VariableSearchFilter filter;
    filter.m_shaderTypeBits = a.m_shaderTypeBits | b.m_shaderTypeBits;
    filter.m_storageBits    = a.m_storageBits | b.m_storageBits;
    return filter;
}

VariableSearchFilter VariableSearchFilter::logicalAnd(const VariableSearchFilter &a, const VariableSearchFilter &b)
{
    VariableSearchFilter filter;
    filter.m_shaderTypeBits = a.m_shaderTypeBits & b.m_shaderTypeBits;
    filter.m_storageBits    = a.m_storageBits & b.m_storageBits;
    return filter;
}

bool VariableSearchFilter::matchesFilter(const ProgramInterfaceDefinition::Shader *shader) const
{
    DE_ASSERT(shader->getType() < glu::SHADERTYPE_LAST);
    return (m_shaderTypeBits & (1u << shader->getType())) != 0;
}

bool VariableSearchFilter::matchesFilter(const glu::VariableDeclaration &variable) const
{
    DE_ASSERT(variable.storage < glu::STORAGE_LAST);
    return (m_storageBits & (1u << variable.storage)) != 0;
}

bool VariableSearchFilter::matchesFilter(const glu::InterfaceBlock &block) const
{
    DE_ASSERT(block.storage < glu::STORAGE_LAST);
    return (m_storageBits & (1u << block.storage)) != 0;
}

} // namespace ProgramInterfaceDefinition

static bool incrementMultiDimensionIndex(std::vector<int> &index, const std::vector<int> &dimensions)
{
    int incrementDimensionNdx = (int)(index.size() - 1);

    while (incrementDimensionNdx >= 0)
    {
        if (++index[incrementDimensionNdx] == dimensions[incrementDimensionNdx])
            index[incrementDimensionNdx--] = 0;
        else
            break;
    }

    return (incrementDimensionNdx != -1);
}

bool programContainsIOBlocks(const ProgramInterfaceDefinition::Program *program)
{
    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
    {
        if (shaderContainsIOBlocks(program->getShaders()[shaderNdx]))
            return true;
    }

    return false;
}

bool shaderContainsIOBlocks(const ProgramInterfaceDefinition::Shader *shader)
{
    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
    {
        const glu::Storage storage = shader->getDefaultBlock().interfaceBlocks[ndx].storage;
        if (storage == glu::STORAGE_IN || storage == glu::STORAGE_OUT || storage == glu::STORAGE_PATCH_IN ||
            storage == glu::STORAGE_PATCH_OUT)
        {
            return true;
        }
    }
    return false;
}

glu::ShaderType getProgramTransformFeedbackStage(const ProgramInterfaceDefinition::Program *program)
{
    if (program->hasStage(glu::SHADERTYPE_GEOMETRY))
        return glu::SHADERTYPE_GEOMETRY;

    if (program->hasStage(glu::SHADERTYPE_TESSELLATION_EVALUATION))
        return glu::SHADERTYPE_TESSELLATION_EVALUATION;

    if (program->hasStage(glu::SHADERTYPE_VERTEX))
        return glu::SHADERTYPE_VERTEX;

    DE_ASSERT(false);
    return glu::SHADERTYPE_LAST;
}

void generateVariableTypeResourceNames(std::vector<std::string> &resources, const std::string &name,
                                       const glu::VarType &type, uint32_t resourceNameGenerationFlags)
{
    DE_ASSERT((resourceNameGenerationFlags & (~RESOURCE_NAME_GENERATION_FLAG_MASK)) == 0);

    // remove top-level flag from children
    const uint32_t childFlags =
        resourceNameGenerationFlags & ~((uint32_t)RESOURCE_NAME_GENERATION_FLAG_TOP_LEVEL_BUFFER_VARIABLE);

    if (type.isBasicType())
        resources.push_back(name);
    else if (type.isStructType())
    {
        const glu::StructType *structType = type.getStructPtr();
        for (int ndx = 0; ndx < structType->getNumMembers(); ++ndx)
            generateVariableTypeResourceNames(resources, name + "." + structType->getMember(ndx).getName(),
                                              structType->getMember(ndx).getType(), childFlags);
    }
    else if (type.isArrayType())
    {
        // Bottom-level arrays of basic types of a transform feedback variable will produce only the first
        // element but without the trailing "[0]"
        if (type.getElementType().isBasicType() &&
            (resourceNameGenerationFlags & RESOURCE_NAME_GENERATION_FLAG_TRANSFORM_FEEDBACK_VARIABLE) != 0)
        {
            resources.push_back(name);
        }
        // Bottom-level arrays of basic types and SSBO top-level arrays of any type procude only first element
        else if (type.getElementType().isBasicType() ||
                 (resourceNameGenerationFlags & RESOURCE_NAME_GENERATION_FLAG_TOP_LEVEL_BUFFER_VARIABLE) != 0)
        {
            generateVariableTypeResourceNames(resources, name + "[0]", type.getElementType(), childFlags);
        }
        // Other arrays of aggregate types are expanded
        else
        {
            for (int ndx = 0; ndx < type.getArraySize(); ++ndx)
                generateVariableTypeResourceNames(resources, name + "[" + de::toString(ndx) + "]",
                                                  type.getElementType(), childFlags);
        }
    }
    else
        DE_ASSERT(false);
}

// Program source generation

namespace
{

using ProgramInterfaceDefinition::VariablePathComponent;
using ProgramInterfaceDefinition::VariableSearchFilter;

static std::string getShaderExtensionDeclarations(const ProgramInterfaceDefinition::Shader *shader)
{
    if (shader->getVersion() > glu::GLSL_VERSION_440)
        return "";

    std::vector<std::string> extensions;
    std::ostringstream buf;

    if (shader->getType() == glu::SHADERTYPE_GEOMETRY)
    {
        extensions.push_back("GL_EXT_geometry_shader");
    }
    else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_CONTROL ||
             shader->getType() == glu::SHADERTYPE_TESSELLATION_EVALUATION)
    {
        extensions.push_back("GL_EXT_tessellation_shader");
    }

    if (shaderContainsIOBlocks(shader))
        extensions.push_back("GL_EXT_shader_io_blocks");

    for (int ndx = 0; ndx < (int)extensions.size(); ++ndx)
        buf << "#extension " << extensions[ndx] << " : require\n";
    return buf.str();
}

static std::string getShaderTypeDeclarations(const ProgramInterfaceDefinition::Program *program,
                                             const ProgramInterfaceDefinition::Shader *shader)
{
    glu::ShaderType type = shader->getType();
    auto isCoreGL        = (shader->getVersion() > glu::GLSL_VERSION_440);

    switch (type)
    {
    case glu::SHADERTYPE_VERTEX:
        if (isCoreGL)
            return "out gl_PerVertex { vec4 gl_Position; };\n";
        return "";

    case glu::SHADERTYPE_FRAGMENT:
        return "";

    case glu::SHADERTYPE_GEOMETRY:
    {
        std::ostringstream buf;
        buf << "layout(points) in;\n"
               "layout(points, max_vertices="
            << program->getGeometryNumOutputVertices() << ") out;\n";
        if (isCoreGL)
        {
            buf << "in gl_PerVertex { vec4 gl_Position; } gl_in[];\n"
                   "out gl_PerVertex { vec4 gl_Position; };\n";
        }
        return buf.str();
    }

    case glu::SHADERTYPE_TESSELLATION_CONTROL:
    {
        std::ostringstream buf;
        buf << "layout(vertices=" << program->getTessellationNumOutputPatchVertices() << ") out;\n";
        if (isCoreGL)
        {
            buf << "in gl_PerVertex { vec4 gl_Position; } gl_in[];\n"
                   "out gl_PerVertex { vec4 gl_Position; } gl_out[];\n";
        }
        return buf.str();
    }

    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
    {
        std::ostringstream buf;
        if (isCoreGL)
        {
            buf << "in gl_PerVertex { vec4 gl_Position; } gl_in[];\n"
                   "out gl_PerVertex { vec4 gl_Position; };\n";
        }
        buf << "layout(triangles, point_mode) in;\n";
        return buf.str();
    }

    case glu::SHADERTYPE_COMPUTE:
        return "layout(local_size_x=1) in;\n";

    default:
        DE_ASSERT(false);
        return "";
    }
}

class StructNameEqualPredicate
{
public:
    StructNameEqualPredicate(const char *name) : m_name(name)
    {
    }
    bool operator()(const glu::StructType *type)
    {
        return type->hasTypeName() && (deStringEqual(m_name, type->getTypeName()) == true);
    }

private:
    const char *m_name;
};

static void collectNamedStructureDefinitions(std::vector<const glu::StructType *> &dst, const glu::VarType &type)
{
    if (type.isBasicType())
        return;
    else if (type.isArrayType())
        return collectNamedStructureDefinitions(dst, type.getElementType());
    else if (type.isStructType())
    {
        if (type.getStructPtr()->hasTypeName())
        {
            // must be unique (may share the the same struct)
            std::vector<const glu::StructType *>::iterator where =
                std::find_if(dst.begin(), dst.end(), StructNameEqualPredicate(type.getStructPtr()->getTypeName()));
            if (where != dst.end())
            {
                DE_ASSERT(**where == *type.getStructPtr());

                // identical type has been added already, types of members must be added too
                return;
            }
        }

        // Add types of members first
        for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
            collectNamedStructureDefinitions(dst, type.getStructPtr()->getMember(ndx).getType());

        dst.push_back(type.getStructPtr());
    }
    else
        DE_ASSERT(false);
}

static void writeStructureDefinitions(std::ostringstream &buf,
                                      const ProgramInterfaceDefinition::DefaultBlock &defaultBlock)
{
    std::vector<const glu::StructType *> namedStructs;

    // Collect all structs in post order

    for (int ndx = 0; ndx < (int)defaultBlock.variables.size(); ++ndx)
        collectNamedStructureDefinitions(namedStructs, defaultBlock.variables[ndx].varType);

    for (int blockNdx = 0; blockNdx < (int)defaultBlock.interfaceBlocks.size(); ++blockNdx)
        for (int ndx = 0; ndx < (int)defaultBlock.interfaceBlocks[blockNdx].variables.size(); ++ndx)
            collectNamedStructureDefinitions(namedStructs,
                                             defaultBlock.interfaceBlocks[blockNdx].variables[ndx].varType);

    // Write

    for (int structNdx = 0; structNdx < (int)namedStructs.size(); ++structNdx)
    {
        buf << "struct " << namedStructs[structNdx]->getTypeName()
            << "\n"
               "{\n";

        for (int memberNdx = 0; memberNdx < namedStructs[structNdx]->getNumMembers(); ++memberNdx)
            buf << glu::indent(1)
                << glu::declare(namedStructs[structNdx]->getMember(memberNdx).getType(),
                                namedStructs[structNdx]->getMember(memberNdx).getName(), 1)
                << ";\n";

        buf << "};\n";
    }

    if (!namedStructs.empty())
        buf << "\n";
}

static void writeInterfaceBlock(std::ostringstream &buf, const glu::InterfaceBlock &interfaceBlock)
{
    buf << interfaceBlock.layout;

    if (interfaceBlock.layout != glu::Layout())
        buf << " ";

    buf << glu::getStorageName(interfaceBlock.storage) << " " << interfaceBlock.interfaceName << "\n"
        << "{\n";

    for (int ndx = 0; ndx < (int)interfaceBlock.variables.size(); ++ndx)
        buf << glu::indent(1) << interfaceBlock.variables[ndx] << ";\n";

    buf << "}";

    if (!interfaceBlock.instanceName.empty())
        buf << " " << interfaceBlock.instanceName;

    for (int dimensionNdx = 0; dimensionNdx < (int)interfaceBlock.dimensions.size(); ++dimensionNdx)
        buf << "[" << interfaceBlock.dimensions[dimensionNdx] << "]";

    buf << ";\n\n";
}

static bool isReadableInterface(const glu::InterfaceBlock &interface)
{
    return interface.storage == glu::STORAGE_UNIFORM || interface.storage == glu::STORAGE_IN ||
           interface.storage == glu::STORAGE_PATCH_IN ||
           (interface.storage == glu::STORAGE_BUFFER &&
            (interface.memoryAccessQualifierFlags & glu::MEMORYACCESSQUALIFIER_WRITEONLY_BIT) == 0);
}

static bool isWritableInterface(const glu::InterfaceBlock &interface)
{
    return interface.storage == glu::STORAGE_OUT || interface.storage == glu::STORAGE_PATCH_OUT ||
           (interface.storage == glu::STORAGE_BUFFER &&
            (interface.memoryAccessQualifierFlags & glu::MEMORYACCESSQUALIFIER_READONLY_BIT) == 0);
}

static void writeVariableReadAccumulateExpression(std::ostringstream &buf, const std::string &accumulatorName,
                                                  const std::string &name, glu::ShaderType shaderType,
                                                  glu::Storage storage,
                                                  const ProgramInterfaceDefinition::Program *program,
                                                  const glu::VarType &varType)
{
    if (varType.isBasicType())
    {
        buf << "\t" << accumulatorName << " += ";

        if (glu::isDataTypeScalar(varType.getBasicType()))
            buf << "vec4(float(" << name << "))";
        else if (glu::isDataTypeVector(varType.getBasicType()))
            buf << "vec4(" << name << ".xyxy)";
        else if (glu::isDataTypeMatrix(varType.getBasicType()))
            buf << "vec4(float(" << name << "[0][0]))";
        else if (glu::isDataTypeSamplerMultisample(varType.getBasicType()))
            buf << "vec4(float(textureSize(" << name << ").x))";
        else if (glu::isDataTypeSampler(varType.getBasicType()))
            buf << "vec4(float(textureSize(" << name << ", 0).x))";
        else if (glu::isDataTypeImage(varType.getBasicType()))
            buf << "vec4(float(imageSize(" << name << ").x))";
        else if (varType.getBasicType() == glu::TYPE_UINT_ATOMIC_COUNTER)
            buf << "vec4(float(atomicCounterIncrement(" << name << ")))";
        else
            DE_ASSERT(false);

        buf << ";\n";
    }
    else if (varType.isStructType())
    {
        for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
            writeVariableReadAccumulateExpression(
                buf, accumulatorName, name + "." + varType.getStructPtr()->getMember(ndx).getName(), shaderType,
                storage, program, varType.getStructPtr()->getMember(ndx).getType());
    }
    else if (varType.isArrayType())
    {
        if (varType.getArraySize() != glu::VarType::UNSIZED_ARRAY)
        {
            for (int ndx = 0; ndx < varType.getArraySize(); ++ndx)
                writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[" + de::toString(ndx) + "]",
                                                      shaderType, storage, program, varType.getElementType());
        }
        else if (storage == glu::STORAGE_BUFFER)
        {
            // run-time sized array, read arbitrary
            writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[8]", shaderType, storage, program,
                                                  varType.getElementType());
        }
        else
        {
            DE_ASSERT(storage == glu::STORAGE_IN);

            if (shaderType == glu::SHADERTYPE_GEOMETRY)
            {
                // implicit sized geometry input array, size = primitive size. Just reading first is enough
                writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[0]", shaderType, storage, program,
                                                      varType.getElementType());
            }
            else if (shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL)
            {
                // implicit sized tessellation input array, size = input patch max size. Just reading current is enough
                writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[gl_InvocationID]", shaderType,
                                                      storage, program, varType.getElementType());
            }
            else if (shaderType == glu::SHADERTYPE_TESSELLATION_EVALUATION)
            {
                // implicit sized tessellation input array, size = output patch max size. Read all to prevent optimizations
                DE_ASSERT(program->getTessellationNumOutputPatchVertices() > 0);
                for (int ndx = 0; ndx < (int)program->getTessellationNumOutputPatchVertices(); ++ndx)
                {
                    writeVariableReadAccumulateExpression(buf, accumulatorName, name + "[" + de::toString(ndx) + "]",
                                                          shaderType, storage, program, varType.getElementType());
                }
            }
            else
                DE_ASSERT(false);
        }
    }
    else
        DE_ASSERT(false);
}

static void writeInterfaceReadAccumulateExpression(std::ostringstream &buf, const std::string &accumulatorName,
                                                   const glu::InterfaceBlock &block, glu::ShaderType shaderType,
                                                   const ProgramInterfaceDefinition::Program *program)
{
    if (block.dimensions.empty())
    {
        const std::string prefix = (block.instanceName.empty()) ? ("") : (block.instanceName + ".");

        for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
        {
            writeVariableReadAccumulateExpression(buf, accumulatorName, prefix + block.variables[ndx].name, shaderType,
                                                  block.storage, program, block.variables[ndx].varType);
        }
    }
    else
    {
        std::vector<int> index(block.dimensions.size(), 0);

        for (;;)
        {
            // access element
            {
                std::ostringstream name;
                name << block.instanceName;

                for (int dimensionNdx = 0; dimensionNdx < (int)block.dimensions.size(); ++dimensionNdx)
                    name << "[" << index[dimensionNdx] << "]";

                for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
                {
                    writeVariableReadAccumulateExpression(buf, accumulatorName,
                                                          name.str() + "." + block.variables[ndx].name, shaderType,
                                                          block.storage, program, block.variables[ndx].varType);
                }
            }

            // increment index
            if (!incrementMultiDimensionIndex(index, block.dimensions))
                break;
        }
    }
}

static void writeVariableWriteExpression(std::ostringstream &buf, const std::string &sourceVec4Name,
                                         const std::string &name, glu::ShaderType shaderType, glu::Storage storage,
                                         const ProgramInterfaceDefinition::Program *program,
                                         const glu::VarType &varType)
{
    if (varType.isBasicType())
    {
        buf << "\t" << name << " = ";

        if (glu::isDataTypeScalar(varType.getBasicType()))
            buf << glu::getDataTypeName(varType.getBasicType()) << "(" << sourceVec4Name << ".y)";
        else if (glu::isDataTypeVector(varType.getBasicType()) || glu::isDataTypeMatrix(varType.getBasicType()))
            buf << glu::getDataTypeName(varType.getBasicType()) << "("
                << glu::getDataTypeName(glu::getDataTypeScalarType(varType.getBasicType())) << "(" << sourceVec4Name
                << ".y))";
        else
            DE_ASSERT(false);

        buf << ";\n";
    }
    else if (varType.isStructType())
    {
        for (int ndx = 0; ndx < varType.getStructPtr()->getNumMembers(); ++ndx)
            writeVariableWriteExpression(buf, sourceVec4Name,
                                         name + "." + varType.getStructPtr()->getMember(ndx).getName(), shaderType,
                                         storage, program, varType.getStructPtr()->getMember(ndx).getType());
    }
    else if (varType.isArrayType())
    {
        if (varType.getArraySize() != glu::VarType::UNSIZED_ARRAY)
        {
            for (int ndx = 0; ndx < varType.getArraySize(); ++ndx)
                writeVariableWriteExpression(buf, sourceVec4Name, name + "[" + de::toString(ndx) + "]", shaderType,
                                             storage, program, varType.getElementType());
        }
        else if (storage == glu::STORAGE_BUFFER)
        {
            // run-time sized array, write arbitrary
            writeVariableWriteExpression(buf, sourceVec4Name, name + "[9]", shaderType, storage, program,
                                         varType.getElementType());
        }
        else
        {
            DE_ASSERT(storage == glu::STORAGE_OUT);

            if (shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL)
            {
                // implicit sized tessellation onput array, size = output patch max size. Can only write to gl_InvocationID
                writeVariableWriteExpression(buf, sourceVec4Name, name + "[gl_InvocationID]", shaderType, storage,
                                             program, varType.getElementType());
            }
            else
                DE_ASSERT(false);
        }
    }
    else
        DE_ASSERT(false);
}

static void writeInterfaceWriteExpression(std::ostringstream &buf, const std::string &sourceVec4Name,
                                          const glu::InterfaceBlock &block, glu::ShaderType shaderType,
                                          const ProgramInterfaceDefinition::Program *program)
{
    if (block.dimensions.empty())
    {
        const std::string prefix = (block.instanceName.empty()) ? ("") : (block.instanceName + ".");

        for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
        {
            writeVariableWriteExpression(buf, sourceVec4Name, prefix + block.variables[ndx].name, shaderType,
                                         block.storage, program, block.variables[ndx].varType);
        }
    }
    else
    {
        std::vector<int> index(block.dimensions.size(), 0);

        for (;;)
        {
            // access element
            {
                std::ostringstream name;
                name << block.instanceName;

                for (int dimensionNdx = 0; dimensionNdx < (int)block.dimensions.size(); ++dimensionNdx)
                    name << "[" << index[dimensionNdx] << "]";

                for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
                {
                    writeVariableWriteExpression(buf, sourceVec4Name, name.str() + "." + block.variables[ndx].name,
                                                 shaderType, block.storage, program, block.variables[ndx].varType);
                }
            }

            // increment index
            if (!incrementMultiDimensionIndex(index, block.dimensions))
                break;
        }
    }
}

static bool traverseVariablePath(std::vector<VariablePathComponent> &typePath, const char *subPath,
                                 const glu::VarType &type)
{
    glu::VarTokenizer tokenizer(subPath);

    typePath.push_back(VariablePathComponent(&type));

    if (tokenizer.getToken() == glu::VarTokenizer::TOKEN_END)
        return true;

    if (type.isStructType() && tokenizer.getToken() == glu::VarTokenizer::TOKEN_PERIOD)
    {
        tokenizer.advance();

        // malformed path
        if (tokenizer.getToken() != glu::VarTokenizer::TOKEN_IDENTIFIER)
            return false;

        for (int memberNdx = 0; memberNdx < type.getStructPtr()->getNumMembers(); ++memberNdx)
            if (type.getStructPtr()->getMember(memberNdx).getName() == tokenizer.getIdentifier())
                return traverseVariablePath(typePath, subPath + tokenizer.getCurrentTokenEndLocation(),
                                            type.getStructPtr()->getMember(memberNdx).getType());

        // malformed path, no such member
        return false;
    }
    else if (type.isArrayType() && tokenizer.getToken() == glu::VarTokenizer::TOKEN_LEFT_BRACKET)
    {
        tokenizer.advance();

        // malformed path
        if (tokenizer.getToken() != glu::VarTokenizer::TOKEN_NUMBER)
            return false;

        tokenizer.advance();
        if (tokenizer.getToken() != glu::VarTokenizer::TOKEN_RIGHT_BRACKET)
            return false;

        return traverseVariablePath(typePath, subPath + tokenizer.getCurrentTokenEndLocation(), type.getElementType());
    }

    return false;
}

static bool traverseVariablePath(std::vector<VariablePathComponent> &typePath, const std::string &path,
                                 const glu::VariableDeclaration &var)
{
    if (glu::parseVariableName(path.c_str()) != var.name)
        return false;

    typePath.push_back(VariablePathComponent(&var));
    return traverseVariablePath(typePath, path.c_str() + var.name.length(), var.varType);
}

static bool traverseShaderVariablePath(std::vector<VariablePathComponent> &typePath,
                                       const ProgramInterfaceDefinition::Shader *shader, const std::string &path,
                                       const VariableSearchFilter &filter)
{
    // Default block variable?
    for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
        if (filter.matchesFilter(shader->getDefaultBlock().variables[varNdx]))
            if (traverseVariablePath(typePath, path, shader->getDefaultBlock().variables[varNdx]))
                return true;

    // is variable an interface block variable?
    {
        const std::string blockName = glu::parseVariableName(path.c_str());

        for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
        {
            if (!filter.matchesFilter(shader->getDefaultBlock().interfaceBlocks[interfaceNdx]))
                continue;

            if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].interfaceName == blockName)
            {
                // resource is a member of a named interface block
                // \note there is no array index specifier even if the interface is declared as an array of instances
                const std::string blockMemberPath  = path.substr(blockName.size() + 1);
                const std::string blockMemeberName = glu::parseVariableName(blockMemberPath.c_str());

                for (int varNdx = 0;
                     varNdx < (int)shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables.size(); ++varNdx)
                {
                    if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx].name ==
                        blockMemeberName)
                    {
                        typePath.push_back(
                            VariablePathComponent(&shader->getDefaultBlock().interfaceBlocks[interfaceNdx]));
                        return traverseVariablePath(
                            typePath, blockMemberPath,
                            shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx]);
                    }
                }

                // terminate search
                return false;
            }
            else if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].instanceName.empty())
            {
                const std::string blockMemeberName = glu::parseVariableName(path.c_str());

                // unnamed block contains such variable?
                for (int varNdx = 0;
                     varNdx < (int)shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables.size(); ++varNdx)
                {
                    if (shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx].name ==
                        blockMemeberName)
                    {
                        typePath.push_back(
                            VariablePathComponent(&shader->getDefaultBlock().interfaceBlocks[interfaceNdx]));
                        return traverseVariablePath(
                            typePath, path, shader->getDefaultBlock().interfaceBlocks[interfaceNdx].variables[varNdx]);
                    }
                }

                // continue search
            }
        }
    }

    return false;
}

static bool traverseProgramVariablePath(std::vector<VariablePathComponent> &typePath,
                                        const ProgramInterfaceDefinition::Program *program, const std::string &path,
                                        const VariableSearchFilter &filter)
{
    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
    {
        const ProgramInterfaceDefinition::Shader *shader = program->getShaders()[shaderNdx];

        if (filter.matchesFilter(shader))
        {
            // \note modifying output variable even when returning false
            typePath.clear();
            if (traverseShaderVariablePath(typePath, shader, path, filter))
                return true;
        }
    }

    return false;
}

static bool containsSubType(const glu::VarType &complexType, glu::DataType basicType)
{
    if (complexType.isBasicType())
    {
        return complexType.getBasicType() == basicType;
    }
    else if (complexType.isArrayType())
    {
        return containsSubType(complexType.getElementType(), basicType);
    }
    else if (complexType.isStructType())
    {
        for (int ndx = 0; ndx < complexType.getStructPtr()->getNumMembers(); ++ndx)
            if (containsSubType(complexType.getStructPtr()->getMember(ndx).getType(), basicType))
                return true;
        return false;
    }
    else
    {
        DE_ASSERT(false);
        return false;
    }
}

static int getNumShaderBlocks(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    int retVal = 0;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
    {
        if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
        {
            int numInstances = 1;

            for (int dimensionNdx = 0;
                 dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
                numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];

            retVal += numInstances;
        }
    }

    return retVal;
}

static int getNumAtomicCounterBuffers(const ProgramInterfaceDefinition::Shader *shader)
{
    std::set<int> buffers;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
    {
        if (containsSubType(shader->getDefaultBlock().variables[ndx].varType, glu::TYPE_UINT_ATOMIC_COUNTER))
        {
            DE_ASSERT(shader->getDefaultBlock().variables[ndx].layout.binding != -1);
            buffers.insert(shader->getDefaultBlock().variables[ndx].layout.binding);
        }
    }

    return (int)buffers.size();
}

template <typename DataTypeMap>
static int accumulateComplexType(const glu::VarType &complexType, const DataTypeMap &dTypeMap)
{
    if (complexType.isBasicType())
        return dTypeMap(complexType.getBasicType());
    else if (complexType.isArrayType())
    {
        const int arraySize =
            (complexType.getArraySize() == glu::VarType::UNSIZED_ARRAY) ? (1) : (complexType.getArraySize());
        return arraySize * accumulateComplexType(complexType.getElementType(), dTypeMap);
    }
    else if (complexType.isStructType())
    {
        int sum = 0;
        for (int ndx = 0; ndx < complexType.getStructPtr()->getNumMembers(); ++ndx)
            sum += accumulateComplexType(complexType.getStructPtr()->getMember(ndx).getType(), dTypeMap);
        return sum;
    }
    else
    {
        DE_ASSERT(false);
        return false;
    }
}

template <typename InterfaceBlockFilter, typename VarDeclFilter, typename DataTypeMap>
static int accumulateShader(const ProgramInterfaceDefinition::Shader *shader, const InterfaceBlockFilter &ibFilter,
                            const VarDeclFilter &vdFilter, const DataTypeMap &dMap)
{
    int retVal = 0;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
    {
        if (ibFilter(shader->getDefaultBlock().interfaceBlocks[ndx]))
        {
            int numInstances = 1;

            for (int dimensionNdx = 0;
                 dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
                numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];

            for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].variables.size();
                 ++varNdx)
                retVal +=
                    numInstances * accumulateComplexType(
                                       shader->getDefaultBlock().interfaceBlocks[ndx].variables[varNdx].varType, dMap);
        }
    }

    for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
        if (vdFilter(shader->getDefaultBlock().variables[varNdx]))
            retVal += accumulateComplexType(shader->getDefaultBlock().variables[varNdx].varType, dMap);

    return retVal;
}

static bool unusedTrueConstantTypeFilter(glu::DataType d)
{
    DE_UNREF(d);
    return true;
}

class InstanceCounter
{
public:
    InstanceCounter(bool (*predicate)(glu::DataType)) : m_predicate(predicate)
    {
    }

    int operator()(glu::DataType t) const
    {
        return (m_predicate(t)) ? (1) : (0);
    }

private:
    bool (*const m_predicate)(glu::DataType);
};

class InterfaceBlockStorageFilter
{
public:
    InterfaceBlockStorageFilter(glu::Storage storage) : m_storage(storage)
    {
    }

    bool operator()(const glu::InterfaceBlock &b) const
    {
        return m_storage == b.storage;
    }

private:
    const glu::Storage m_storage;
};

class VariableDeclarationStorageFilter
{
public:
    VariableDeclarationStorageFilter(glu::Storage storage) : m_storage(storage)
    {
    }

    bool operator()(const glu::VariableDeclaration &d) const
    {
        return m_storage == d.storage;
    }

private:
    const glu::Storage m_storage;
};

static int getNumTypeInstances(const glu::VarType &complexType, bool (*predicate)(glu::DataType))
{
    return accumulateComplexType(complexType, InstanceCounter(predicate));
}

static int getNumTypeInstances(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage,
                               bool (*predicate)(glu::DataType))
{
    return accumulateShader(shader, InterfaceBlockStorageFilter(storage), VariableDeclarationStorageFilter(storage),
                            InstanceCounter(predicate));
}

static int getNumTypeInstances(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    return getNumTypeInstances(shader, storage, unusedTrueConstantTypeFilter);
}

static int accumulateShaderStorage(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage,
                                   int (*typeMap)(glu::DataType))
{
    return accumulateShader(shader, InterfaceBlockStorageFilter(storage), VariableDeclarationStorageFilter(storage),
                            typeMap);
}

static int getNumDataTypeComponents(glu::DataType type)
{
    if (glu::isDataTypeScalarOrVector(type) || glu::isDataTypeMatrix(type))
        return glu::getDataTypeScalarSize(type);
    else
        return 0;
}

static int getNumDataTypeVectors(glu::DataType type)
{
    if (glu::isDataTypeScalar(type))
        return 1;
    else if (glu::isDataTypeVector(type))
        return 1;
    else if (glu::isDataTypeMatrix(type))
        return glu::getDataTypeMatrixNumColumns(type);
    else
        return 0;
}

static int getNumComponents(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    return accumulateShaderStorage(shader, storage, getNumDataTypeComponents);
}

static int getNumVectors(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    return accumulateShaderStorage(shader, storage, getNumDataTypeVectors);
}

static int getNumDefaultBlockComponents(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    int retVal = 0;

    for (int varNdx = 0; varNdx < (int)shader->getDefaultBlock().variables.size(); ++varNdx)
        if (shader->getDefaultBlock().variables[varNdx].storage == storage)
            retVal +=
                accumulateComplexType(shader->getDefaultBlock().variables[varNdx].varType, getNumDataTypeComponents);

    return retVal;
}

static int getMaxBufferBinding(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    int maxBinding = -1;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
    {
        if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
        {
            const int binding = (shader->getDefaultBlock().interfaceBlocks[ndx].layout.binding == -1) ?
                                    (0) :
                                    (shader->getDefaultBlock().interfaceBlocks[ndx].layout.binding);
            int numInstances  = 1;

            for (int dimensionNdx = 0;
                 dimensionNdx < (int)shader->getDefaultBlock().interfaceBlocks[ndx].dimensions.size(); ++dimensionNdx)
                numInstances *= shader->getDefaultBlock().interfaceBlocks[ndx].dimensions[dimensionNdx];

            maxBinding = de::max(maxBinding, binding + numInstances - 1);
        }
    }

    return (int)maxBinding;
}

static int getBufferTypeSize(glu::DataType type, glu::MatrixOrder order)
{
    // assume vec4 alignments, should produce values greater than or equal to the actual resource usage
    int numVectors = 0;

    if (glu::isDataTypeScalarOrVector(type))
        numVectors = 1;
    else if (glu::isDataTypeMatrix(type) && order == glu::MATRIXORDER_ROW_MAJOR)
        numVectors = glu::getDataTypeMatrixNumRows(type);
    else if (glu::isDataTypeMatrix(type) && order != glu::MATRIXORDER_ROW_MAJOR)
        numVectors = glu::getDataTypeMatrixNumColumns(type);
    else
        DE_ASSERT(false);

    return 4 * numVectors;
}

static int getBufferVariableSize(const glu::VarType &type, glu::MatrixOrder order)
{
    if (type.isBasicType())
        return getBufferTypeSize(type.getBasicType(), order);
    else if (type.isArrayType())
    {
        const int arraySize = (type.getArraySize() == glu::VarType::UNSIZED_ARRAY) ? (1) : (type.getArraySize());
        return arraySize * getBufferVariableSize(type.getElementType(), order);
    }
    else if (type.isStructType())
    {
        int sum = 0;
        for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
            sum += getBufferVariableSize(type.getStructPtr()->getMember(ndx).getType(), order);
        return sum;
    }
    else
    {
        DE_ASSERT(false);
        return false;
    }
}

static int getBufferSize(const glu::InterfaceBlock &block, glu::MatrixOrder blockOrder)
{
    int size = 0;

    for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
        size += getBufferVariableSize(block.variables[ndx].varType,
                                      (block.variables[ndx].layout.matrixOrder == glu::MATRIXORDER_LAST) ?
                                          (blockOrder) :
                                          (block.variables[ndx].layout.matrixOrder));

    return size;
}

static int getBufferMaxSize(const ProgramInterfaceDefinition::Shader *shader, glu::Storage storage)
{
    int maxSize = 0;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
        if (shader->getDefaultBlock().interfaceBlocks[ndx].storage == storage)
            maxSize =
                de::max(maxSize, getBufferSize(shader->getDefaultBlock().interfaceBlocks[ndx],
                                               shader->getDefaultBlock().interfaceBlocks[ndx].layout.matrixOrder));

    return (int)maxSize;
}

static int getAtomicCounterMaxBinding(const ProgramInterfaceDefinition::Shader *shader)
{
    int maxBinding = -1;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
    {
        if (containsSubType(shader->getDefaultBlock().variables[ndx].varType, glu::TYPE_UINT_ATOMIC_COUNTER))
        {
            DE_ASSERT(shader->getDefaultBlock().variables[ndx].layout.binding != -1);
            maxBinding = de::max(maxBinding, shader->getDefaultBlock().variables[ndx].layout.binding);
        }
    }

    return (int)maxBinding;
}

static int getUniformMaxBinding(const ProgramInterfaceDefinition::Shader *shader, bool (*predicate)(glu::DataType))
{
    int maxBinding = -1;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
    {
        const int binding      = (shader->getDefaultBlock().variables[ndx].layout.binding == -1) ?
                                     (0) :
                                     (shader->getDefaultBlock().variables[ndx].layout.binding);
        const int numInstances = getNumTypeInstances(shader->getDefaultBlock().variables[ndx].varType, predicate);

        maxBinding = de::max(maxBinding, binding + numInstances - 1);
    }

    return maxBinding;
}

static int getAtomicCounterMaxBufferSize(const ProgramInterfaceDefinition::Shader *shader)
{
    std::map<int, int> bufferSizes;
    int maxSize = 0;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
    {
        if (containsSubType(shader->getDefaultBlock().variables[ndx].varType, glu::TYPE_UINT_ATOMIC_COUNTER))
        {
            const int bufferBinding = shader->getDefaultBlock().variables[ndx].layout.binding;
            const int offset        = (shader->getDefaultBlock().variables[ndx].layout.offset == -1) ?
                                          (0) :
                                          (shader->getDefaultBlock().variables[ndx].layout.offset);
            const int size          = offset + 4 * getNumTypeInstances(shader->getDefaultBlock().variables[ndx].varType,
                                                                       glu::isDataTypeAtomicCounter);

            DE_ASSERT(shader->getDefaultBlock().variables[ndx].layout.binding != -1);

            if (bufferSizes.find(bufferBinding) == bufferSizes.end())
                bufferSizes[bufferBinding] = size;
            else
                bufferSizes[bufferBinding] = de::max<int>(bufferSizes[bufferBinding], size);
        }
    }

    for (std::map<int, int>::iterator it = bufferSizes.begin(); it != bufferSizes.end(); ++it)
        maxSize = de::max<int>(maxSize, it->second);

    return maxSize;
}

static int getNumFeedbackVaryingComponents(const ProgramInterfaceDefinition::Program *program, const std::string &name)
{
    std::vector<VariablePathComponent> path;

    if (name == "gl_Position")
        return 4;

    DE_ASSERT(deStringBeginsWith(name.c_str(), "gl_") == false);

    if (!traverseProgramVariablePath(path, program, name,
                                     VariableSearchFilter::createShaderTypeStorageFilter(
                                         getProgramTransformFeedbackStage(program), glu::STORAGE_OUT)))
        DE_ASSERT(false); // Program failed validate, invalid operation

    return accumulateComplexType(*path.back().getVariableType(), getNumDataTypeComponents);
}

static int getNumXFBComponents(const ProgramInterfaceDefinition::Program *program)
{
    int numComponents = 0;

    for (int ndx = 0; ndx < (int)program->getTransformFeedbackVaryings().size(); ++ndx)
        numComponents += getNumFeedbackVaryingComponents(program, program->getTransformFeedbackVaryings()[ndx]);

    return numComponents;
}

static int getNumMaxXFBOutputComponents(const ProgramInterfaceDefinition::Program *program)
{
    int numComponents = 0;

    for (int ndx = 0; ndx < (int)program->getTransformFeedbackVaryings().size(); ++ndx)
        numComponents = de::max(numComponents,
                                getNumFeedbackVaryingComponents(program, program->getTransformFeedbackVaryings()[ndx]));

    return numComponents;
}

static int getFragmentOutputMaxLocation(const ProgramInterfaceDefinition::Shader *shader)
{
    DE_ASSERT(shader->getType() == glu::SHADERTYPE_FRAGMENT);

    int maxOutputLocation = -1;

    for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
    {
        if (shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_OUT)
        {
            // missing location qualifier means location == 0
            const int outputLocation = (shader->getDefaultBlock().variables[ndx].layout.location == -1) ?
                                           (0) :
                                           (shader->getDefaultBlock().variables[ndx].layout.location);

            // only basic types or arrays of basic types possible
            DE_ASSERT(!shader->getDefaultBlock().variables[ndx].varType.isStructType());

            const int locationSlotsTaken = (shader->getDefaultBlock().variables[ndx].varType.isArrayType()) ?
                                               (shader->getDefaultBlock().variables[ndx].varType.getArraySize()) :
                                               (1);

            maxOutputLocation = de::max(maxOutputLocation, outputLocation + locationSlotsTaken - 1);
        }
    }

    return maxOutputLocation;
}

} // namespace

std::vector<std::string> getProgramInterfaceBlockMemberResourceList(const glu::InterfaceBlock &interfaceBlock)
{
    const std::string namePrefix = (!interfaceBlock.instanceName.empty()) ? (interfaceBlock.interfaceName + ".") : ("");
    const bool isTopLevelBufferVariable = (interfaceBlock.storage == glu::STORAGE_BUFFER);
    std::vector<std::string> resources;

    // \note this is defined in the GLSL spec, not in the GL spec
    for (int variableNdx = 0; variableNdx < (int)interfaceBlock.variables.size(); ++variableNdx)
        generateVariableTypeResourceNames(resources, namePrefix + interfaceBlock.variables[variableNdx].name,
                                          interfaceBlock.variables[variableNdx].varType,
                                          (isTopLevelBufferVariable) ?
                                              (RESOURCE_NAME_GENERATION_FLAG_TOP_LEVEL_BUFFER_VARIABLE) :
                                              (RESOURCE_NAME_GENERATION_FLAG_DEFAULT));

    return resources;
}

std::vector<std::string> getProgramInterfaceResourceList(const ProgramInterfaceDefinition::Program *program,
                                                         ProgramInterface interface)
{
    // The same {uniform (block), buffer (variable)} can exist in multiple shaders, remove duplicates but keep order
    const bool removeDuplicated =
        (interface == PROGRAMINTERFACE_UNIFORM) || (interface == PROGRAMINTERFACE_UNIFORM_BLOCK) ||
        (interface == PROGRAMINTERFACE_BUFFER_VARIABLE) || (interface == PROGRAMINTERFACE_SHADER_STORAGE_BLOCK);
    std::vector<std::string> resources;

    switch (interface)
    {
    case PROGRAMINTERFACE_UNIFORM:
    case PROGRAMINTERFACE_BUFFER_VARIABLE:
    {
        const glu::Storage storage =
            (interface == PROGRAMINTERFACE_UNIFORM) ? (glu::STORAGE_UNIFORM) : (glu::STORAGE_BUFFER);

        for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
        {
            const ProgramInterfaceDefinition::Shader *shader = program->getShaders()[shaderNdx];

            for (int variableNdx = 0; variableNdx < (int)shader->getDefaultBlock().variables.size(); ++variableNdx)
                if (shader->getDefaultBlock().variables[variableNdx].storage == storage)
                    generateVariableTypeResourceNames(resources, shader->getDefaultBlock().variables[variableNdx].name,
                                                      shader->getDefaultBlock().variables[variableNdx].varType,
                                                      RESOURCE_NAME_GENERATION_FLAG_DEFAULT);

            for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size();
                 ++interfaceNdx)
            {
                const glu::InterfaceBlock &interfaceBlock = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
                if (interfaceBlock.storage == storage)
                {
                    const std::vector<std::string> blockResources =
                        getProgramInterfaceBlockMemberResourceList(interfaceBlock);
                    resources.insert(resources.end(), blockResources.begin(), blockResources.end());
                }
            }
        }
        break;
    }

    case PROGRAMINTERFACE_UNIFORM_BLOCK:
    case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
    {
        const glu::Storage storage =
            (interface == PROGRAMINTERFACE_UNIFORM_BLOCK) ? (glu::STORAGE_UNIFORM) : (glu::STORAGE_BUFFER);

        for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
        {
            const ProgramInterfaceDefinition::Shader *shader = program->getShaders()[shaderNdx];
            for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size();
                 ++interfaceNdx)
            {
                const glu::InterfaceBlock &interfaceBlock = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
                if (interfaceBlock.storage == storage)
                {
                    std::vector<int> index(interfaceBlock.dimensions.size(), 0);

                    for (;;)
                    {
                        // add resource string for each element
                        {
                            std::ostringstream name;
                            name << interfaceBlock.interfaceName;

                            for (int dimensionNdx = 0; dimensionNdx < (int)interfaceBlock.dimensions.size();
                                 ++dimensionNdx)
                                name << "[" << index[dimensionNdx] << "]";

                            resources.push_back(name.str());
                        }

                        // increment index
                        if (!incrementMultiDimensionIndex(index, interfaceBlock.dimensions))
                            break;
                    }
                }
            }
        }
        break;
    }

    case PROGRAMINTERFACE_PROGRAM_INPUT:
    case PROGRAMINTERFACE_PROGRAM_OUTPUT:
    {
        const glu::Storage queryStorage =
            (interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (glu::STORAGE_IN) : (glu::STORAGE_OUT);
        const glu::Storage queryPatchStorage =
            (interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (glu::STORAGE_PATCH_IN) : (glu::STORAGE_PATCH_OUT);
        const glu::ShaderType shaderType =
            (interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? (program->getFirstStage()) : (program->getLastStage());

        for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
        {
            const ProgramInterfaceDefinition::Shader *shader = program->getShaders()[shaderNdx];

            if (shader->getType() != shaderType)
                continue;

            for (int variableNdx = 0; variableNdx < (int)shader->getDefaultBlock().variables.size(); ++variableNdx)
            {
                const glu::Storage variableStorage = shader->getDefaultBlock().variables[variableNdx].storage;
                if (variableStorage == queryStorage || variableStorage == queryPatchStorage)
                    generateVariableTypeResourceNames(resources, shader->getDefaultBlock().variables[variableNdx].name,
                                                      shader->getDefaultBlock().variables[variableNdx].varType,
                                                      RESOURCE_NAME_GENERATION_FLAG_DEFAULT);
            }

            for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size();
                 ++interfaceNdx)
            {
                const glu::InterfaceBlock &interfaceBlock = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
                if (interfaceBlock.storage == queryStorage || interfaceBlock.storage == queryPatchStorage)
                {
                    const std::vector<std::string> blockResources =
                        getProgramInterfaceBlockMemberResourceList(interfaceBlock);
                    resources.insert(resources.end(), blockResources.begin(), blockResources.end());
                }
            }
        }

        // built-ins
        if (interface == PROGRAMINTERFACE_PROGRAM_INPUT)
        {
            if (shaderType == glu::SHADERTYPE_VERTEX && resources.empty())
                resources.push_back("gl_VertexID"); // only read from when there are no other inputs
            else if (shaderType == glu::SHADERTYPE_FRAGMENT && resources.empty())
                resources.push_back("gl_FragCoord"); // only read from when there are no other inputs
            else if (shaderType == glu::SHADERTYPE_GEOMETRY)
                resources.push_back("gl_PerVertex.gl_Position");
            else if (shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL)
            {
                resources.push_back("gl_InvocationID");
                resources.push_back("gl_PerVertex.gl_Position");
            }
            else if (shaderType == glu::SHADERTYPE_TESSELLATION_EVALUATION)
                resources.push_back("gl_PerVertex.gl_Position");
            else if (shaderType == glu::SHADERTYPE_COMPUTE && resources.empty())
                resources.push_back("gl_NumWorkGroups"); // only read from when there are no other inputs
        }
        else if (interface == PROGRAMINTERFACE_PROGRAM_OUTPUT)
        {
            if (shaderType == glu::SHADERTYPE_VERTEX)
                resources.push_back("gl_Position");
            else if (shaderType == glu::SHADERTYPE_FRAGMENT && resources.empty())
                resources.push_back("gl_FragDepth"); // only written to when there are no other outputs
            else if (shaderType == glu::SHADERTYPE_GEOMETRY)
                resources.push_back("gl_Position");
            else if (shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL)
            {
                resources.push_back("gl_PerVertex.gl_Position");
                resources.push_back("gl_TessLevelOuter[0]");
                resources.push_back("gl_TessLevelInner[0]");
            }
            else if (shaderType == glu::SHADERTYPE_TESSELLATION_EVALUATION)
                resources.push_back("gl_Position");
        }

        break;
    }

    case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
    {
        const glu::ShaderType xfbStage = getProgramTransformFeedbackStage(program);

        for (int varyingNdx = 0; varyingNdx < (int)program->getTransformFeedbackVaryings().size(); ++varyingNdx)
        {
            const std::string &varyingName = program->getTransformFeedbackVaryings()[varyingNdx];

            if (deStringBeginsWith(varyingName.c_str(), "gl_"))
                resources.push_back(varyingName); // builtin
            else
            {
                std::vector<VariablePathComponent> path;

                if (!traverseProgramVariablePath(
                        path, program, varyingName,
                        VariableSearchFilter::createShaderTypeStorageFilter(xfbStage, glu::STORAGE_OUT)))
                    DE_ASSERT(false); // Program failed validate, invalid operation

                generateVariableTypeResourceNames(resources, varyingName, *path.back().getVariableType(),
                                                  RESOURCE_NAME_GENERATION_FLAG_TRANSFORM_FEEDBACK_VARIABLE);
            }
        }

        break;
    }

    default:
        DE_ASSERT(false);
    }

    if (removeDuplicated)
    {
        std::set<std::string> addedVariables;
        std::vector<std::string> uniqueResouces;

        for (int ndx = 0; ndx < (int)resources.size(); ++ndx)
        {
            if (addedVariables.find(resources[ndx]) == addedVariables.end())
            {
                addedVariables.insert(resources[ndx]);
                uniqueResouces.push_back(resources[ndx]);
            }
        }

        uniqueResouces.swap(resources);
    }

    return resources;
}

/**
 * Name of the unused uniform added by generateProgramInterfaceProgramSources
 *
 * A uniform named "unusedZero" is added by
 * generateProgramInterfaceProgramSources.  It is used in expressions to
 * prevent various program resources from being eliminated by the GLSL
 * compiler's optimizer.
 *
 * \sa deqp::gles31::Functional::ProgramInterfaceDefinition::generateProgramInterfaceProgramSources
 */
const char *getUnusedZeroUniformName()
{
    return "unusedZero";
}

glu::ProgramSources generateProgramInterfaceProgramSources(const ProgramInterfaceDefinition::Program *program)
{
    glu::ProgramSources sources;

    DE_ASSERT(program->isValid());

    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
    {
        const ProgramInterfaceDefinition::Shader *shader = program->getShaders()[shaderNdx];
        bool containsUserDefinedOutputs                  = false;
        bool containsUserDefinedInputs                   = false;
        std::ostringstream sourceBuf;
        std::ostringstream usageBuf;

        sourceBuf << glu::getGLSLVersionDeclaration(shader->getVersion()) << "\n"
                  << getShaderExtensionDeclarations(shader) << getShaderTypeDeclarations(program, shader) << "\n";

        // Struct definitions

        writeStructureDefinitions(sourceBuf, shader->getDefaultBlock());

        // variables in the default scope

        for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
            sourceBuf << shader->getDefaultBlock().variables[ndx] << ";\n";

        if (!shader->getDefaultBlock().variables.empty())
            sourceBuf << "\n";

        // Interface blocks

        for (int ndx = 0; ndx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++ndx)
            writeInterfaceBlock(sourceBuf, shader->getDefaultBlock().interfaceBlocks[ndx]);

        // Use inputs and outputs so that they won't be removed by the optimizer

        usageBuf << "highp uniform vec4 " << getUnusedZeroUniformName()
                 << "; // Default value is vec4(0.0).\n"
                    "highp vec4 readInputs()\n"
                    "{\n"
                    "    highp vec4 retValue = "
                 << getUnusedZeroUniformName() << ";\n";

        // User-defined inputs

        for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
        {
            if (shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_IN ||
                shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_PATCH_IN ||
                shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_UNIFORM)
            {
                writeVariableReadAccumulateExpression(usageBuf, "retValue",
                                                      shader->getDefaultBlock().variables[ndx].name, shader->getType(),
                                                      shader->getDefaultBlock().variables[ndx].storage, program,
                                                      shader->getDefaultBlock().variables[ndx].varType);
                containsUserDefinedInputs = true;
            }
        }

        for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
        {
            const glu::InterfaceBlock &interface = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
            if (isReadableInterface(interface))
            {
                writeInterfaceReadAccumulateExpression(usageBuf, "retValue", interface, shader->getType(), program);
                containsUserDefinedInputs = true;
            }
        }

        // Built-in-inputs

        switch (shader->getType())
        {
        case glu::SHADERTYPE_VERTEX:
            // make readInputs to never be compile time constant
            if (!containsUserDefinedInputs)
                usageBuf << "    retValue += vec4(float(gl_VertexID));\n";
            break;

        case glu::SHADERTYPE_FRAGMENT:
            // make readInputs to never be compile time constant
            if (!containsUserDefinedInputs)
                usageBuf << "    retValue += gl_FragCoord;\n";
            break;
        case glu::SHADERTYPE_GEOMETRY:
            // always use previous stage's output values so that previous stage won't be optimized out
            usageBuf << "    retValue += gl_in[0].gl_Position;\n";
            break;
        case glu::SHADERTYPE_TESSELLATION_CONTROL:
            // always use previous stage's output values so that previous stage won't be optimized out
            usageBuf << "    retValue += gl_in[0].gl_Position;\n";
            break;
        case glu::SHADERTYPE_TESSELLATION_EVALUATION:
            // always use previous stage's output values so that previous stage won't be optimized out
            usageBuf << "    retValue += gl_in[0].gl_Position;\n";
            break;

        case glu::SHADERTYPE_COMPUTE:
            // make readInputs to never be compile time constant
            if (!containsUserDefinedInputs)
                usageBuf << "    retValue += vec4(float(gl_NumWorkGroups.x));\n";
            break;
        default:
            DE_ASSERT(false);
        }

        usageBuf << "    return retValue;\n"
                    "}\n\n";

        usageBuf << "void writeOutputs(in highp vec4 unusedValue)\n"
                    "{\n";

        // User-defined outputs

        for (int ndx = 0; ndx < (int)shader->getDefaultBlock().variables.size(); ++ndx)
        {
            if (shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_OUT ||
                shader->getDefaultBlock().variables[ndx].storage == glu::STORAGE_PATCH_OUT)
            {
                writeVariableWriteExpression(usageBuf, "unusedValue", shader->getDefaultBlock().variables[ndx].name,
                                             shader->getType(), shader->getDefaultBlock().variables[ndx].storage,
                                             program, shader->getDefaultBlock().variables[ndx].varType);
                containsUserDefinedOutputs = true;
            }
        }

        for (int interfaceNdx = 0; interfaceNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++interfaceNdx)
        {
            const glu::InterfaceBlock &interface = shader->getDefaultBlock().interfaceBlocks[interfaceNdx];
            if (isWritableInterface(interface))
            {
                writeInterfaceWriteExpression(usageBuf, "unusedValue", interface, shader->getType(), program);
                containsUserDefinedOutputs = true;
            }
        }

        // Builtin-outputs that must be written to

        if (shader->getType() == glu::SHADERTYPE_VERTEX)
            usageBuf << "    gl_Position = unusedValue;\n";
        else if (shader->getType() == glu::SHADERTYPE_GEOMETRY)
            usageBuf << "    gl_Position = unusedValue;\n"
                        "    EmitVertex();\n";
        else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_CONTROL)
            usageBuf << "    gl_out[gl_InvocationID].gl_Position = unusedValue;\n"
                        "    gl_TessLevelOuter[0] = 2.8;\n"
                        "    gl_TessLevelOuter[1] = 2.8;\n"
                        "    gl_TessLevelOuter[2] = 2.8;\n"
                        "    gl_TessLevelOuter[3] = 2.8;\n"
                        "    gl_TessLevelInner[0] = 2.8;\n"
                        "    gl_TessLevelInner[1] = 2.8;\n";
        else if (shader->getType() == glu::SHADERTYPE_TESSELLATION_EVALUATION)
            usageBuf << "    gl_Position = unusedValue;\n";

        // Output to sink input data to

        if (!containsUserDefinedOutputs)
        {
            if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
                usageBuf << "    gl_FragDepth = dot(unusedValue.xy, unusedValue.xw);\n";
            else if (shader->getType() == glu::SHADERTYPE_COMPUTE)
                usageBuf << "    unusedOutputBlock.unusedValue = unusedValue;\n";
        }

        usageBuf << "}\n\n"
                    "void main()\n"
                    "{\n"
                    "    writeOutputs(readInputs());\n"
                    "}\n";

        // Interface for unused output

        if (shader->getType() == glu::SHADERTYPE_COMPUTE && !containsUserDefinedOutputs)
        {
            sourceBuf << "writeonly buffer UnusedOutputInterface\n"
                      << "{\n"
                      << "    highp vec4 unusedValue;\n"
                      << "} unusedOutputBlock;\n\n";
        }

        sources << glu::ShaderSource(shader->getType(), sourceBuf.str() + usageBuf.str());
    }

    if (program->isSeparable())
        sources << glu::ProgramSeparable(true);

    for (int ndx = 0; ndx < (int)program->getTransformFeedbackVaryings().size(); ++ndx)
        sources << glu::TransformFeedbackVarying(program->getTransformFeedbackVaryings()[ndx]);

    if (program->getTransformFeedbackMode())
        sources << glu::TransformFeedbackMode(program->getTransformFeedbackMode());

    return sources;
}

bool findProgramVariablePathByPathName(std::vector<VariablePathComponent> &typePath,
                                       const ProgramInterfaceDefinition::Program *program, const std::string &pathName,
                                       const VariableSearchFilter &filter)
{
    std::vector<VariablePathComponent> modifiedPath;

    if (!traverseProgramVariablePath(modifiedPath, program, pathName, filter))
        return false;

    // modify param only on success
    typePath.swap(modifiedPath);
    return true;
}

ProgramInterfaceDefinition::ShaderResourceUsage getShaderResourceUsage(
    const ProgramInterfaceDefinition::Program *program, const ProgramInterfaceDefinition::Shader *shader)
{
    ProgramInterfaceDefinition::ShaderResourceUsage retVal;

    retVal.numInputs          = getNumTypeInstances(shader, glu::STORAGE_IN);
    retVal.numInputVectors    = getNumVectors(shader, glu::STORAGE_IN);
    retVal.numInputComponents = getNumComponents(shader, glu::STORAGE_IN);

    retVal.numOutputs          = getNumTypeInstances(shader, glu::STORAGE_OUT);
    retVal.numOutputVectors    = getNumVectors(shader, glu::STORAGE_OUT);
    retVal.numOutputComponents = getNumComponents(shader, glu::STORAGE_OUT);

    retVal.numPatchInputComponents  = getNumComponents(shader, glu::STORAGE_PATCH_IN);
    retVal.numPatchOutputComponents = getNumComponents(shader, glu::STORAGE_PATCH_OUT);

    retVal.numDefaultBlockUniformComponents = getNumDefaultBlockComponents(shader, glu::STORAGE_UNIFORM);
    retVal.numCombinedUniformComponents     = getNumComponents(shader, glu::STORAGE_UNIFORM);
    retVal.numUniformVectors                = getNumVectors(shader, glu::STORAGE_UNIFORM);

    retVal.numSamplers = getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeSampler);
    retVal.numImages   = getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeImage);

    retVal.numAtomicCounterBuffers = getNumAtomicCounterBuffers(shader);
    retVal.numAtomicCounters       = getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeAtomicCounter);

    retVal.numUniformBlocks       = getNumShaderBlocks(shader, glu::STORAGE_UNIFORM);
    retVal.numShaderStorageBlocks = getNumShaderBlocks(shader, glu::STORAGE_BUFFER);

    // add builtins
    switch (shader->getType())
    {
    case glu::SHADERTYPE_VERTEX:
        // gl_Position is not counted
        break;

    case glu::SHADERTYPE_FRAGMENT:
        // nada
        break;

    case glu::SHADERTYPE_GEOMETRY:
        // gl_Position in (point mode => size 1)
        retVal.numInputs += 1;
        retVal.numInputVectors += 1;
        retVal.numInputComponents += 4;

        // gl_Position out
        retVal.numOutputs += 1;
        retVal.numOutputVectors += 1;
        retVal.numOutputComponents += 4;
        break;

    case glu::SHADERTYPE_TESSELLATION_CONTROL:
        // gl_Position in is read up to gl_InstanceID
        retVal.numInputs += 1 * program->getTessellationNumOutputPatchVertices();
        retVal.numInputVectors += 1 * program->getTessellationNumOutputPatchVertices();
        retVal.numInputComponents += 4 * program->getTessellationNumOutputPatchVertices();

        // gl_Position out, size = num patch out vertices
        retVal.numOutputs += 1 * program->getTessellationNumOutputPatchVertices();
        retVal.numOutputVectors += 1 * program->getTessellationNumOutputPatchVertices();
        retVal.numOutputComponents += 4 * program->getTessellationNumOutputPatchVertices();
        break;

    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
        // gl_Position in is read up to gl_InstanceID
        retVal.numInputs += 1 * program->getTessellationNumOutputPatchVertices();
        retVal.numInputVectors += 1 * program->getTessellationNumOutputPatchVertices();
        retVal.numInputComponents += 4 * program->getTessellationNumOutputPatchVertices();

        // gl_Position out
        retVal.numOutputs += 1;
        retVal.numOutputVectors += 1;
        retVal.numOutputComponents += 4;
        break;

    case glu::SHADERTYPE_COMPUTE:
        // nada
        break;

    default:
        DE_ASSERT(false);
        break;
    }
    return retVal;
}

ProgramInterfaceDefinition::ProgramResourceUsage getCombinedProgramResourceUsage(
    const ProgramInterfaceDefinition::Program *program)
{
    ProgramInterfaceDefinition::ProgramResourceUsage retVal;
    int numVertexOutputComponents  = 0;
    int numFragmentInputComponents = 0;
    int numVertexOutputVectors     = 0;
    int numFragmentInputVectors    = 0;

    retVal.uniformBufferMaxBinding =
        -1; // max binding is inclusive upper bound. Allow 0 bindings by using negative value
    retVal.uniformBufferMaxSize                    = 0;
    retVal.numUniformBlocks                        = 0;
    retVal.numCombinedVertexUniformComponents      = 0;
    retVal.numCombinedFragmentUniformComponents    = 0;
    retVal.numCombinedGeometryUniformComponents    = 0;
    retVal.numCombinedTessControlUniformComponents = 0;
    retVal.numCombinedTessEvalUniformComponents    = 0;
    retVal.shaderStorageBufferMaxBinding           = -1; // see above
    retVal.shaderStorageBufferMaxSize              = 0;
    retVal.numShaderStorageBlocks                  = 0;
    retVal.numVaryingComponents                    = 0;
    retVal.numVaryingVectors                       = 0;
    retVal.numCombinedSamplers                     = 0;
    retVal.atomicCounterBufferMaxBinding           = -1; // see above
    retVal.atomicCounterBufferMaxSize              = 0;
    retVal.numAtomicCounterBuffers                 = 0;
    retVal.numAtomicCounters                       = 0;
    retVal.maxImageBinding                         = -1; // see above
    retVal.numCombinedImages                       = 0;
    retVal.numCombinedOutputResources              = 0;
    retVal.numXFBInterleavedComponents             = 0;
    retVal.numXFBSeparateAttribs                   = 0;
    retVal.numXFBSeparateComponents                = 0;
    retVal.fragmentOutputMaxBinding                = -1; // see above

    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
    {
        const ProgramInterfaceDefinition::Shader *const shader = program->getShaders()[shaderNdx];

        retVal.uniformBufferMaxBinding =
            de::max(retVal.uniformBufferMaxBinding, getMaxBufferBinding(shader, glu::STORAGE_UNIFORM));
        retVal.uniformBufferMaxSize =
            de::max(retVal.uniformBufferMaxSize, getBufferMaxSize(shader, glu::STORAGE_UNIFORM));
        retVal.numUniformBlocks += getNumShaderBlocks(shader, glu::STORAGE_UNIFORM);

        switch (shader->getType())
        {
        case glu::SHADERTYPE_VERTEX:
            retVal.numCombinedVertexUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
            break;
        case glu::SHADERTYPE_FRAGMENT:
            retVal.numCombinedFragmentUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
            break;
        case glu::SHADERTYPE_GEOMETRY:
            retVal.numCombinedGeometryUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
            break;
        case glu::SHADERTYPE_TESSELLATION_CONTROL:
            retVal.numCombinedTessControlUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
            break;
        case glu::SHADERTYPE_TESSELLATION_EVALUATION:
            retVal.numCombinedTessEvalUniformComponents += getNumComponents(shader, glu::STORAGE_UNIFORM);
            break;
        default:
            break;
        }

        retVal.shaderStorageBufferMaxBinding =
            de::max(retVal.shaderStorageBufferMaxBinding, getMaxBufferBinding(shader, glu::STORAGE_BUFFER));
        retVal.shaderStorageBufferMaxSize =
            de::max(retVal.shaderStorageBufferMaxSize, getBufferMaxSize(shader, glu::STORAGE_BUFFER));
        retVal.numShaderStorageBlocks += getNumShaderBlocks(shader, glu::STORAGE_BUFFER);

        if (shader->getType() == glu::SHADERTYPE_VERTEX)
        {
            numVertexOutputComponents += getNumComponents(shader, glu::STORAGE_OUT);
            numVertexOutputVectors += getNumVectors(shader, glu::STORAGE_OUT);
        }
        else if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
        {
            numFragmentInputComponents += getNumComponents(shader, glu::STORAGE_IN);
            numFragmentInputVectors += getNumVectors(shader, glu::STORAGE_IN);
        }

        retVal.numCombinedSamplers += getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeSampler);

        retVal.atomicCounterBufferMaxBinding =
            de::max(retVal.atomicCounterBufferMaxBinding, getAtomicCounterMaxBinding(shader));
        retVal.atomicCounterBufferMaxSize =
            de::max(retVal.atomicCounterBufferMaxSize, getAtomicCounterMaxBufferSize(shader));
        retVal.numAtomicCounterBuffers += getNumAtomicCounterBuffers(shader);
        retVal.numAtomicCounters += getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeAtomicCounter);
        retVal.maxImageBinding = de::max(retVal.maxImageBinding, getUniformMaxBinding(shader, glu::isDataTypeImage));
        retVal.numCombinedImages += getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeImage);

        retVal.numCombinedOutputResources += getNumTypeInstances(shader, glu::STORAGE_UNIFORM, glu::isDataTypeImage);
        retVal.numCombinedOutputResources += getNumShaderBlocks(shader, glu::STORAGE_BUFFER);

        if (shader->getType() == glu::SHADERTYPE_FRAGMENT)
        {
            retVal.numCombinedOutputResources += getNumVectors(shader, glu::STORAGE_OUT);
            retVal.fragmentOutputMaxBinding =
                de::max(retVal.fragmentOutputMaxBinding, getFragmentOutputMaxLocation(shader));
        }
    }

    if (program->getTransformFeedbackMode() == GL_INTERLEAVED_ATTRIBS)
        retVal.numXFBInterleavedComponents = getNumXFBComponents(program);
    else if (program->getTransformFeedbackMode() == GL_SEPARATE_ATTRIBS)
    {
        retVal.numXFBSeparateAttribs    = (int)program->getTransformFeedbackVaryings().size();
        retVal.numXFBSeparateComponents = getNumMaxXFBOutputComponents(program);
    }

    // legacy limits
    retVal.numVaryingComponents = de::max(numVertexOutputComponents, numFragmentInputComponents);
    retVal.numVaryingVectors    = de::max(numVertexOutputVectors, numFragmentInputVectors);

    return retVal;
}

} // namespace Functional
} // namespace gles31
} // namespace deqp
