/*-------------------------------------------------------------------------
 * 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 query test case
 *//*--------------------------------------------------------------------*/

#include "es31fProgramInterfaceQueryTestCase.hpp"
#include "es31fProgramInterfaceDefinitionUtil.hpp"
#include "tcuTestLog.hpp"
#include "gluVarTypeUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deSTLUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

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

static glw::GLenum getProgramDefaultBlockInterfaceFromStorage(glu::Storage storage)
{
    switch (storage)
    {
    case glu::STORAGE_IN:
    case glu::STORAGE_PATCH_IN:
        return GL_PROGRAM_INPUT;

    case glu::STORAGE_OUT:
    case glu::STORAGE_PATCH_OUT:
        return GL_PROGRAM_OUTPUT;

    case glu::STORAGE_UNIFORM:
        return GL_UNIFORM;

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

static bool isBufferBackedInterfaceBlockStorage(glu::Storage storage)
{
    return storage == glu::STORAGE_BUFFER || storage == glu::STORAGE_UNIFORM;
}

const char *getRequiredExtensionForStage(glu::ShaderType stage)
{
    switch (stage)
    {
    case glu::SHADERTYPE_COMPUTE:
    case glu::SHADERTYPE_VERTEX:
    case glu::SHADERTYPE_FRAGMENT:
        return DE_NULL;

    case glu::SHADERTYPE_GEOMETRY:
        return "GL_EXT_geometry_shader";

    case glu::SHADERTYPE_TESSELLATION_CONTROL:
    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
        return "GL_EXT_tessellation_shader";

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

static int getTypeSize(glu::DataType type)
{
    if (type == glu::TYPE_FLOAT)
        return 4;
    else if (type == glu::TYPE_INT || type == glu::TYPE_UINT)
        return 4;
    else if (type == glu::TYPE_BOOL)
        return 4; // uint

    DE_ASSERT(false);
    return 0;
}

static int getVarTypeSize(const glu::VarType &type)
{
    if (type.isBasicType())
    {
        // return in basic machine units
        return glu::getDataTypeScalarSize(type.getBasicType()) *
               getTypeSize(glu::getDataTypeScalarType(type.getBasicType()));
    }
    else if (type.isStructType())
    {
        int size = 0;
        for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
            size += getVarTypeSize(type.getStructPtr()->getMember(ndx).getType());
        return size;
    }
    else if (type.isArrayType())
    {
        // unsized arrays are handled as if they had only one element
        if (type.getArraySize() == glu::VarType::UNSIZED_ARRAY)
            return getVarTypeSize(type.getElementType());
        else
            return type.getArraySize() * getVarTypeSize(type.getElementType());
    }
    else
    {
        DE_ASSERT(false);
        return 0;
    }
}

static glu::MatrixOrder getMatrixOrderFromPath(const std::vector<VariablePathComponent> &path)
{
    glu::MatrixOrder order = glu::MATRIXORDER_LAST;

    // inherit majority
    for (int pathNdx = 0; pathNdx < (int)path.size(); ++pathNdx)
    {
        glu::MatrixOrder matOrder;

        if (path[pathNdx].isInterfaceBlock())
            matOrder = path[pathNdx].getInterfaceBlock()->layout.matrixOrder;
        else if (path[pathNdx].isDeclaration())
            matOrder = path[pathNdx].getDeclaration()->layout.matrixOrder;
        else if (path[pathNdx].isVariableType())
            matOrder = glu::MATRIXORDER_LAST;
        else
        {
            DE_ASSERT(false);
            return glu::MATRIXORDER_LAST;
        }

        if (matOrder != glu::MATRIXORDER_LAST)
            order = matOrder;
    }

    return order;
}

class PropValidator
{
public:
    PropValidator(Context &context, ProgramResourcePropFlags validationProp, const char *requiredExtension);

    virtual std::string getHumanReadablePropertyString(glw::GLint propVal) const;
    virtual void validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource,
                          glw::GLint propValue, const std::string &implementationName) const = 0;

    bool isSupported(void) const;
    bool isSelected(uint32_t caseFlags) const;

protected:
    void setError(const std::string &err) const;

    tcu::TestContext &m_testCtx;
    const glu::RenderContext &m_renderContext;

private:
    const glu::ContextInfo &m_contextInfo;
    const char *m_extension;
    const ProgramResourcePropFlags m_validationProp;
};

PropValidator::PropValidator(Context &context, ProgramResourcePropFlags validationProp, const char *requiredExtension)
    : m_testCtx(context.getTestContext())
    , m_renderContext(context.getRenderContext())
    , m_contextInfo(context.getContextInfo())
    , m_extension(requiredExtension)
    , m_validationProp(validationProp)
{
}

std::string PropValidator::getHumanReadablePropertyString(glw::GLint propVal) const
{
    return de::toString(propVal);
}

bool PropValidator::isSupported(void) const
{
    if (glu::contextSupports(m_renderContext.getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_renderContext.getType(), glu::ApiType::core(4, 5)))
        return true;
    return m_extension == DE_NULL || m_contextInfo.isExtensionSupported(m_extension);
}

bool PropValidator::isSelected(uint32_t caseFlags) const
{
    return (caseFlags & (uint32_t)m_validationProp) != 0;
}

void PropValidator::setError(const std::string &err) const
{
    // don't overwrite earlier errors
    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, err.c_str());
}

class SingleVariableValidator : public PropValidator
{
public:
    SingleVariableValidator(Context &context, ProgramResourcePropFlags validationProp, glw::GLuint programID,
                            const VariableSearchFilter &filter, const char *requiredExtension);

    void validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource, glw::GLint propValue,
                  const std::string &implementationName) const;
    virtual void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                        glw::GLint propValue, const std::string &implementationName) const = 0;
    virtual void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                         const std::string &implementationName) const;

protected:
    const VariableSearchFilter m_filter;
    const glw::GLuint m_programID;
};

SingleVariableValidator::SingleVariableValidator(Context &context, ProgramResourcePropFlags validationProp,
                                                 glw::GLuint programID, const VariableSearchFilter &filter,
                                                 const char *requiredExtension)
    : PropValidator(context, validationProp, requiredExtension)
    , m_filter(filter)
    , m_programID(programID)
{
}

void SingleVariableValidator::validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource,
                                       glw::GLint propValue, const std::string &implementationName) const
{
    std::vector<VariablePathComponent> path;

    if (findProgramVariablePathByPathName(path, program, resource, m_filter))
    {
        const glu::VarType *variable = (path.back().isVariableType()) ? (path.back().getVariableType()) : (DE_NULL);

        if (!variable || !variable->isBasicType())
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, resource name \"" << resource
                               << "\" refers to a non-basic type." << tcu::TestLog::EndMessage;
            setError("resource not basic type");
        }
        else
            validateSingleVariable(path, resource, propValue, implementationName);

        // finding matching variable in any shader is sufficient
        return;
    }
    else if (deStringBeginsWith(resource.c_str(), "gl_"))
    {
        // special case for builtins
        validateBuiltinVariable(resource, propValue, implementationName);
        return;
    }

    // we are only supplied good names, generated by ourselves
    DE_ASSERT(false);
    throw tcu::InternalError("Resource name consistency error");
}

void SingleVariableValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                      const std::string &implementationName) const
{
    DE_UNREF(resource);
    DE_UNREF(propValue);
    DE_UNREF(implementationName);
    DE_ASSERT(false);
}

class SingleBlockValidator : public PropValidator
{
public:
    SingleBlockValidator(Context &context, ProgramResourcePropFlags validationProp, glw::GLuint programID,
                         const VariableSearchFilter &filter, const char *requiredExtension);

    void validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource, glw::GLint propValue,
                  const std::string &implementationName) const;
    virtual void validateSingleBlock(const glu::InterfaceBlock &block, const std::vector<int> &instanceIndex,
                                     const std::string &resource, glw::GLint propValue,
                                     const std::string &implementationName) const = 0;

protected:
    const VariableSearchFilter m_filter;
    const glw::GLuint m_programID;
};

SingleBlockValidator::SingleBlockValidator(Context &context, ProgramResourcePropFlags validationProp,
                                           glw::GLuint programID, const VariableSearchFilter &filter,
                                           const char *requiredExtension)
    : PropValidator(context, validationProp, requiredExtension)
    , m_filter(filter)
    , m_programID(programID)
{
}

void SingleBlockValidator::validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource,
                                    glw::GLint propValue, const std::string &implementationName) const
{
    glu::VarTokenizer tokenizer(resource.c_str());
    const std::string blockName = tokenizer.getIdentifier();
    std::vector<int> instanceIndex;

    tokenizer.advance();

    // array index
    while (tokenizer.getToken() == glu::VarTokenizer::TOKEN_LEFT_BRACKET)
    {
        tokenizer.advance();
        DE_ASSERT(tokenizer.getToken() == glu::VarTokenizer::TOKEN_NUMBER);

        instanceIndex.push_back(tokenizer.getNumber());

        tokenizer.advance();
        DE_ASSERT(tokenizer.getToken() == glu::VarTokenizer::TOKEN_RIGHT_BRACKET);

        tokenizer.advance();
    }

    // no trailing garbage
    DE_ASSERT(tokenizer.getToken() == glu::VarTokenizer::TOKEN_END);

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

        for (int blockNdx = 0; blockNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++blockNdx)
        {
            const glu::InterfaceBlock &block = shader->getDefaultBlock().interfaceBlocks[blockNdx];

            if (m_filter.matchesFilter(block) && block.interfaceName == blockName)
            {
                // dimensions match
                DE_ASSERT(instanceIndex.size() == block.dimensions.size());

                validateSingleBlock(block, instanceIndex, resource, propValue, implementationName);
                return;
            }
        }
    }

    // we are only supplied good names, generated by ourselves
    DE_ASSERT(false);
    throw tcu::InternalError("Resource name consistency error");
}

class TypeValidator : public SingleVariableValidator
{
public:
    TypeValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    std::string getHumanReadablePropertyString(glw::GLint propVal) const;
    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
};

TypeValidator::TypeValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_TYPE, programID, filter, DE_NULL)
{
}

std::string TypeValidator::getHumanReadablePropertyString(glw::GLint propVal) const
{
    return de::toString(glu::getShaderVarTypeStr(propVal));
}

void TypeValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                           glw::GLint propValue, const std::string &implementationName) const
{
    const glu::VarType *variable = path.back().getVariableType();

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying type, expecting "
                       << glu::getDataTypeName(variable->getBasicType()) << tcu::TestLog::EndMessage;

    if (variable->getBasicType() != glu::getDataTypeFromGLType(propValue))
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got "
                           << glu::getDataTypeName(glu::getDataTypeFromGLType(propValue)) << tcu::TestLog::EndMessage;
        setError("resource type invalid");
    }
}

void TypeValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                            const std::string &implementationName) const
{
    DE_UNREF(implementationName);

    static const struct
    {
        const char *name;
        glu::DataType type;
    } builtins[] = {
        {"gl_Position", glu::TYPE_FLOAT_VEC4},
        {"gl_FragCoord", glu::TYPE_FLOAT_VEC4},
        {"gl_PerVertex.gl_Position", glu::TYPE_FLOAT_VEC4},
        {"gl_VertexID", glu::TYPE_INT},
        {"gl_InvocationID", glu::TYPE_INT},
        {"gl_NumWorkGroups", glu::TYPE_UINT_VEC3},
        {"gl_FragDepth", glu::TYPE_FLOAT},
        {"gl_TessLevelOuter[0]", glu::TYPE_FLOAT},
        {"gl_TessLevelInner[0]", glu::TYPE_FLOAT},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(builtins); ++ndx)
    {
        if (resource == builtins[ndx].name)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying type, expecting "
                               << glu::getDataTypeName(builtins[ndx].type) << tcu::TestLog::EndMessage;

            if (glu::getDataTypeFromGLType(propValue) != builtins[ndx].type)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got "
                                   << glu::getDataTypeName(glu::getDataTypeFromGLType(propValue))
                                   << tcu::TestLog::EndMessage;
                setError("resource type invalid");
            }
            return;
        }
    }

    DE_ASSERT(false);
}

class ArraySizeValidator : public SingleVariableValidator
{
public:
    ArraySizeValidator(Context &context, glw::GLuint programID, int unsizedArraySize,
                       const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;

private:
    const int m_unsizedArraySize;
};

ArraySizeValidator::ArraySizeValidator(Context &context, glw::GLuint programID, int unsizedArraySize,
                                       const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_ARRAY_SIZE, programID, filter, DE_NULL)
    , m_unsizedArraySize(unsizedArraySize)
{
}

void ArraySizeValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                const std::string &resource, glw::GLint propValue,
                                                const std::string &implementationName) const
{
    const VariablePathComponent nullComponent;
    const VariablePathComponent &enclosingcomponent = (path.size() > 1) ? (path[path.size() - 2]) : (nullComponent);

    const bool isArray = enclosingcomponent.isVariableType() && enclosingcomponent.getVariableType()->isArrayType();
    const bool inUnsizedArray =
        isArray && (enclosingcomponent.getVariableType()->getArraySize() == glu::VarType::UNSIZED_ARRAY);
    const int arraySize = (!isArray)       ? (1) :
                          (inUnsizedArray) ? (m_unsizedArraySize) :
                                             (enclosingcomponent.getVariableType()->getArraySize());

    DE_ASSERT(arraySize >= 0);
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array size, expecting " << arraySize
                       << tcu::TestLog::EndMessage;

    if (arraySize != propValue)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
        setError("resource array size invalid");
    }
}

void ArraySizeValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                 const std::string &implementationName) const
{
    DE_UNREF(implementationName);

    static const struct
    {
        const char *name;
        int arraySize;
    } builtins[] = {
        {"gl_Position", 1},          {"gl_VertexID", 1},      {"gl_FragCoord", 1}, {"gl_PerVertex.gl_Position", 1},
        {"gl_InvocationID", 1},      {"gl_NumWorkGroups", 1}, {"gl_FragDepth", 1}, {"gl_TessLevelOuter[0]", 4},
        {"gl_TessLevelInner[0]", 2},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(builtins); ++ndx)
    {
        if (resource == builtins[ndx].name)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array size, expecting " << builtins[ndx].arraySize
                               << tcu::TestLog::EndMessage;

            if (propValue != builtins[ndx].arraySize)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
                setError("resource array size invalid");
            }
            return;
        }
    }

    DE_ASSERT(false);
}

class ArrayStrideValidator : public SingleVariableValidator
{
public:
    ArrayStrideValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

ArrayStrideValidator::ArrayStrideValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_ARRAY_STRIDE, programID, filter, DE_NULL)
{
}

void ArrayStrideValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                  const std::string &resource, glw::GLint propValue,
                                                  const std::string &implementationName) const
{
    const VariablePathComponent nullComponent;
    const VariablePathComponent &component          = path.back();
    const VariablePathComponent &enclosingcomponent = (path.size() > 1) ? (path[path.size() - 2]) : (nullComponent);
    const VariablePathComponent &firstComponent     = path.front();

    const bool isBufferBlock = firstComponent.isInterfaceBlock() &&
                               isBufferBackedInterfaceBlockStorage(firstComponent.getInterfaceBlock()->storage);
    const bool isArray = enclosingcomponent.isVariableType() && enclosingcomponent.getVariableType()->isArrayType();
    const bool isAtomicCounter = glu::isDataTypeAtomicCounter(
        component.getVariableType()
            ->getBasicType()); // atomic counters are buffer backed with a stride of 4 basic machine units

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    // Layout tests will verify layouts of buffer backed arrays properly. Here we just check values are greater or equal to the element size
    if (isBufferBlock && isArray)
    {
        const int elementSize = glu::getDataTypeScalarSize(component.getVariableType()->getBasicType()) *
                                getTypeSize(glu::getDataTypeScalarType(component.getVariableType()->getBasicType()));
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array stride, expecting greater or equal to "
                           << elementSize << tcu::TestLog::EndMessage;

        if (propValue < elementSize)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource array stride invalid");
        }
    }
    else
    {
        // Atomics are buffer backed with stride of 4 even though they are not in an interface block
        const int arrayStride = (isAtomicCounter && isArray) ? (4) : (!isBufferBlock && !isAtomicCounter) ? (-1) : (0);

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array stride, expecting " << arrayStride
                           << tcu::TestLog::EndMessage;

        if (arrayStride != propValue)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource array stride invalid");
        }
    }
}

class BlockIndexValidator : public SingleVariableValidator
{
public:
    BlockIndexValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

BlockIndexValidator::BlockIndexValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_BLOCK_INDEX, programID, filter, DE_NULL)
{
}

void BlockIndexValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                 const std::string &resource, glw::GLint propValue,
                                                 const std::string &implementationName) const
{
    const VariablePathComponent &firstComponent = path.front();

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    if (!firstComponent.isInterfaceBlock())
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying block index, expecting -1"
                           << tcu::TestLog::EndMessage;

        if (propValue != -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource block index invalid");
        }
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying block index, expecting a valid block index"
                           << tcu::TestLog::EndMessage;

        if (propValue == -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource block index invalid");
        }
        else
        {
            const glw::Functions &gl = m_renderContext.getFunctions();
            const glw::GLenum interface =
                (firstComponent.getInterfaceBlock()->storage == glu::STORAGE_UNIFORM) ? (GL_UNIFORM_BLOCK) :
                (firstComponent.getInterfaceBlock()->storage == glu::STORAGE_BUFFER)  ? (GL_SHADER_STORAGE_BLOCK) :
                                                                                        (0);
            glw::GLint written = 0;
            std::vector<char> nameBuffer(firstComponent.getInterfaceBlock()->interfaceName.size() +
                                             3 * firstComponent.getInterfaceBlock()->dimensions.size() + 2,
                                         '\0'); // +3 for appended "[N]", +1 for '\0' and +1 just for safety

            gl.getProgramResourceName(m_programID, interface, propValue, (int)nameBuffer.size() - 1, &written,
                                      &nameBuffer[0]);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query block name");
            TCU_CHECK(written < (int)nameBuffer.size());
            TCU_CHECK(nameBuffer.back() == '\0');

            {
                const std::string blockName(&nameBuffer[0], written);
                std::ostringstream expectedName;

                expectedName << firstComponent.getInterfaceBlock()->interfaceName;
                for (int dimensionNdx = 0; dimensionNdx < (int)firstComponent.getInterfaceBlock()->dimensions.size();
                     ++dimensionNdx)
                    expectedName << "[0]";

                m_testCtx.getLog() << tcu::TestLog::Message << "Block name with index " << propValue << " is \""
                                   << blockName << "\"" << tcu::TestLog::EndMessage;
                if (blockName != expectedName.str())
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "\tError, expected " << expectedName.str()
                                       << tcu::TestLog::EndMessage;
                    setError("resource block index invalid");
                }
            }
        }
    }
}

class IsRowMajorValidator : public SingleVariableValidator
{
public:
    IsRowMajorValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    std::string getHumanReadablePropertyString(glw::GLint propVal) const;
    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

IsRowMajorValidator::IsRowMajorValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR, programID, filter, DE_NULL)
{
}

std::string IsRowMajorValidator::getHumanReadablePropertyString(glw::GLint propVal) const
{
    return de::toString(glu::getBooleanStr(propVal));
}

void IsRowMajorValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                 const std::string &resource, glw::GLint propValue,
                                                 const std::string &implementationName) const
{
    const VariablePathComponent &component      = path.back();
    const VariablePathComponent &firstComponent = path.front();

    const bool isBufferBlock = firstComponent.isInterfaceBlock() &&
                               isBufferBackedInterfaceBlockStorage(firstComponent.getInterfaceBlock()->storage);
    const bool isMatrix = glu::isDataTypeMatrix(component.getVariableType()->getBasicType());
    const int expected =
        (isBufferBlock && isMatrix && getMatrixOrderFromPath(path) == glu::MATRIXORDER_ROW_MAJOR) ? (1) : (0);

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying matrix order, expecting IS_ROW_MAJOR = " << expected
                       << tcu::TestLog::EndMessage;

    if (propValue != expected)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
        setError("resource matrix order invalid");
    }
}

class MatrixStrideValidator : public SingleVariableValidator
{
public:
    MatrixStrideValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

MatrixStrideValidator::MatrixStrideValidator(Context &context, glw::GLuint programID,
                                             const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_MATRIX_STRIDE, programID, filter, DE_NULL)
{
}

void MatrixStrideValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                   const std::string &resource, glw::GLint propValue,
                                                   const std::string &implementationName) const
{
    const VariablePathComponent &component      = path.back();
    const VariablePathComponent &firstComponent = path.front();

    const bool isBufferBlock = firstComponent.isInterfaceBlock() &&
                               isBufferBackedInterfaceBlockStorage(firstComponent.getInterfaceBlock()->storage);
    const bool isMatrix = glu::isDataTypeMatrix(component.getVariableType()->getBasicType());

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    // Layout tests will verify layouts of buffer backed arrays properly. Here we just check the stride is is greater or equal to the row/column size
    if (isBufferBlock && isMatrix)
    {
        const bool columnMajor = getMatrixOrderFromPath(path) != glu::MATRIXORDER_ROW_MAJOR;
        const int numMajorElements =
            (columnMajor) ? (glu::getDataTypeMatrixNumRows(component.getVariableType()->getBasicType())) :
                            (glu::getDataTypeMatrixNumColumns(component.getVariableType()->getBasicType()));
        const int majorSize =
            numMajorElements * getTypeSize(glu::getDataTypeScalarType(component.getVariableType()->getBasicType()));

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying matrix stride, expecting greater or equal to "
                           << majorSize << tcu::TestLog::EndMessage;

        if (propValue < majorSize)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource matrix stride invalid");
        }
    }
    else
    {
        const int matrixStride =
            (!isBufferBlock && !glu::isDataTypeAtomicCounter(component.getVariableType()->getBasicType())) ? (-1) : (0);

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying matrix stride, expecting " << matrixStride
                           << tcu::TestLog::EndMessage;

        if (matrixStride != propValue)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource matrix stride invalid");
        }
    }
}

class AtomicCounterBufferIndexVerifier : public SingleVariableValidator
{
public:
    AtomicCounterBufferIndexVerifier(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

AtomicCounterBufferIndexVerifier::AtomicCounterBufferIndexVerifier(Context &context, glw::GLuint programID,
                                                                   const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_ATOMIC_COUNTER_BUFFER_INDEX, programID, filter, DE_NULL)
{
}

void AtomicCounterBufferIndexVerifier::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                              const std::string &resource, glw::GLint propValue,
                                                              const std::string &implementationName) const
{
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    if (!glu::isDataTypeAtomicCounter(path.back().getVariableType()->getBasicType()))
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying atomic counter buffer index, expecting -1"
                           << tcu::TestLog::EndMessage;

        if (propValue != -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource atomic counter buffer index invalid");
        }
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying atomic counter buffer index, expecting a valid index"
                           << tcu::TestLog::EndMessage;

        if (propValue == -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource atomic counter buffer index invalid");
        }
        else
        {
            const glw::Functions &gl      = m_renderContext.getFunctions();
            glw::GLint numActiveResources = 0;

            gl.getProgramInterfaceiv(m_programID, GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, &numActiveResources);
            GLU_EXPECT_NO_ERROR(gl.getError(),
                                "getProgramInterfaceiv(..., GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES, ...)");

            if (propValue >= numActiveResources)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue
                                   << ", GL_ACTIVE_RESOURCES = " << numActiveResources << tcu::TestLog::EndMessage;
                setError("resource atomic counter buffer index invalid");
            }
        }
    }
}

class LocationValidator : public SingleVariableValidator
{
public:
    LocationValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
};

LocationValidator::LocationValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_LOCATION, programID, filter, DE_NULL)
{
}

static int getVariableLocationLength(const glu::VarType &type)
{
    if (type.isBasicType())
    {
        if (glu::isDataTypeMatrix(type.getBasicType()))
            return glu::getDataTypeMatrixNumColumns(type.getBasicType());
        else
            return 1;
    }
    else if (type.isStructType())
    {
        int size = 0;
        for (int ndx = 0; ndx < type.getStructPtr()->getNumMembers(); ++ndx)
            size += getVariableLocationLength(type.getStructPtr()->getMember(ndx).getType());
        return size;
    }
    else if (type.isArrayType())
        return type.getArraySize() * getVariableLocationLength(type.getElementType());
    else
    {
        DE_ASSERT(false);
        return 0;
    }
}

static int getIOSubVariableLocation(const std::vector<VariablePathComponent> &path, int startNdx, int currentLocation)
{
    if (currentLocation == -1)
        return -1;

    if (path[startNdx].getVariableType()->isBasicType())
        return currentLocation;
    else if (path[startNdx].getVariableType()->isArrayType())
        return getIOSubVariableLocation(path, startNdx + 1, currentLocation);
    else if (path[startNdx].getVariableType()->isStructType())
    {
        for (int ndx = 0; ndx < path[startNdx].getVariableType()->getStructPtr()->getNumMembers(); ++ndx)
        {
            if (&path[startNdx].getVariableType()->getStructPtr()->getMember(ndx).getType() ==
                path[startNdx + 1].getVariableType())
                return getIOSubVariableLocation(path, startNdx + 1, currentLocation);

            if (currentLocation != -1)
                currentLocation += getVariableLocationLength(
                    path[startNdx].getVariableType()->getStructPtr()->getMember(ndx).getType());
        }

        // could not find member, never happens
        DE_ASSERT(false);
        return -1;
    }
    else
    {
        DE_ASSERT(false);
        return -1;
    }
}

static int getIOBlockVariableLocation(const std::vector<VariablePathComponent> &path)
{
    const glu::InterfaceBlock *block = path.front().getInterfaceBlock();
    int currentLocation              = block->layout.location;

    // Find the block member
    for (int memberNdx = 0; memberNdx < (int)block->variables.size(); ++memberNdx)
    {
        if (block->variables[memberNdx].layout.location != -1)
            currentLocation = block->variables[memberNdx].layout.location;

        if (&block->variables[memberNdx] == path[1].getDeclaration())
            break;

        // unspecified + unspecified = unspecified
        if (currentLocation != -1)
            currentLocation += getVariableLocationLength(block->variables[memberNdx].varType);
    }

    // Find subtype location in the complex type
    return getIOSubVariableLocation(path, 2, currentLocation);
}

static int getExplicitLocationFromPath(const std::vector<VariablePathComponent> &path)
{
    const glu::VariableDeclaration *varDecl =
        (path[0].isInterfaceBlock()) ? (path[1].getDeclaration()) : (path[0].getDeclaration());

    if (path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_UNIFORM)
    {
        // inside uniform block
        return -1;
    }
    else if (path.front().isInterfaceBlock() && (path.front().getInterfaceBlock()->storage == glu::STORAGE_IN ||
                                                 path.front().getInterfaceBlock()->storage == glu::STORAGE_OUT ||
                                                 path.front().getInterfaceBlock()->storage == glu::STORAGE_PATCH_IN ||
                                                 path.front().getInterfaceBlock()->storage == glu::STORAGE_PATCH_OUT))
    {
        // inside ioblock
        return getIOBlockVariableLocation(path);
    }
    else if (varDecl->storage == glu::STORAGE_UNIFORM)
    {
        // default block uniform
        return varDecl->layout.location;
    }
    else if (varDecl->storage == glu::STORAGE_IN || varDecl->storage == glu::STORAGE_OUT ||
             varDecl->storage == glu::STORAGE_PATCH_IN || varDecl->storage == glu::STORAGE_PATCH_OUT)
    {
        // default block input/output
        return getIOSubVariableLocation(path, 1, varDecl->layout.location);
    }
    else
    {
        DE_ASSERT(false);
        return -1;
    }
}

void LocationValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                               const std::string &resource, glw::GLint propValue,
                                               const std::string &implementationName) const
{
    const bool isAtomicCounterUniform = glu::isDataTypeAtomicCounter(path.back().getVariableType()->getBasicType());
    const bool isUniformBlockVariable =
        path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_UNIFORM;
    const bool isVertexShader        = m_filter.getShaderTypeBits() == (1u << glu::SHADERTYPE_VERTEX);
    const bool isFragmentShader      = m_filter.getShaderTypeBits() == (1u << glu::SHADERTYPE_FRAGMENT);
    const glu::Storage storage       = (path.front().isInterfaceBlock()) ? (path.front().getInterfaceBlock()->storage) :
                                                                           (path.front().getDeclaration()->storage);
    const bool isInputVariable       = (storage == glu::STORAGE_IN || storage == glu::STORAGE_PATCH_IN);
    const bool isOutputVariable      = (storage == glu::STORAGE_OUT || storage == glu::STORAGE_PATCH_OUT);
    const int explicitLayoutLocation = getExplicitLocationFromPath(path);

    bool expectLocation;
    std::string reasonStr;

    DE_UNREF(resource);

    if (isAtomicCounterUniform)
    {
        expectLocation = false;
        reasonStr      = "Atomic counter uniforms have effective location of -1";
    }
    else if (isUniformBlockVariable)
    {
        expectLocation = false;
        reasonStr      = "Uniform block variables have effective location of -1";
    }
    else if (isInputVariable && !isVertexShader && explicitLayoutLocation == -1)
    {
        expectLocation = false;
        reasonStr      = "Inputs (except for vertex shader inputs) not declared with a location layout qualifier have "
                         "effective location of -1";
    }
    else if (isOutputVariable && !isFragmentShader && explicitLayoutLocation == -1)
    {
        expectLocation = false;
        reasonStr = "Outputs (except for fragment shader outputs) not declared with a location layout qualifier have "
                    "effective location of -1";
    }
    else
    {
        expectLocation = true;
    }

    if (!expectLocation)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying uniform location, expecting -1. (" << reasonStr << ")"
                           << tcu::TestLog::EndMessage;

        if (propValue != -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource location invalid");
        }
    }
    else
    {
        bool locationOk;

        if (explicitLayoutLocation == -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying location, expecting a valid location"
                               << tcu::TestLog::EndMessage;
            locationOk = (propValue != -1);
        }
        else
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying location, expecting " << explicitLayoutLocation
                               << tcu::TestLog::EndMessage;
            locationOk = (propValue == explicitLayoutLocation);
        }

        if (!locationOk)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
            setError("resource location invalid");
        }
        else
        {
            const VariablePathComponent nullComponent;
            const VariablePathComponent &enclosingcomponent =
                (path.size() > 1) ? (path[path.size() - 2]) : (nullComponent);
            const bool isArray =
                enclosingcomponent.isVariableType() && enclosingcomponent.getVariableType()->isArrayType();

            const glw::Functions &gl    = m_renderContext.getFunctions();
            const glw::GLenum interface = getProgramDefaultBlockInterfaceFromStorage(storage);

            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Comparing location to the values returned by GetProgramResourceLocation"
                               << tcu::TestLog::EndMessage;

            // Test all bottom-level array elements
            if (isArray)
            {
                const std::string arrayResourceName =
                    (implementationName.size() > 3) ? (implementationName.substr(0, implementationName.size() - 3)) :
                                                      (""); // chop "[0]"

                for (int arrayElementNdx = 0; arrayElementNdx < enclosingcomponent.getVariableType()->getArraySize();
                     ++arrayElementNdx)
                {
                    const std::string elementResourceName =
                        arrayResourceName + "[" + de::toString(arrayElementNdx) + "]";
                    const glw::GLint location =
                        gl.getProgramResourceLocation(m_programID, interface, elementResourceName.c_str());

                    if (location != propValue + arrayElementNdx)
                    {
                        m_testCtx.getLog()
                            << tcu::TestLog::Message << "\tError, getProgramResourceLocation (resource=\""
                            << elementResourceName << "\") returned location " << location << ", expected "
                            << (propValue + arrayElementNdx) << tcu::TestLog::EndMessage;
                        setError("resource location invalid");
                    }
                    else
                        m_testCtx.getLog() << tcu::TestLog::Message << "\tLocation of \"" << elementResourceName
                                           << "\":\t" << location << tcu::TestLog::EndMessage;
                }
            }
            else
            {
                const glw::GLint location =
                    gl.getProgramResourceLocation(m_programID, interface, implementationName.c_str());

                if (location != propValue)
                {
                    m_testCtx.getLog() << tcu::TestLog::Message
                                       << "\tError, getProgramResourceLocation returned location " << location
                                       << ", expected " << propValue << tcu::TestLog::EndMessage;
                    setError("resource location invalid");
                }
            }
        }
    }
}

void LocationValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                const std::string &implementationName) const
{
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    // built-ins have no location

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying location, expecting -1" << tcu::TestLog::EndMessage;

    if (propValue != -1)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
        setError("resource location invalid");
    }
}

class VariableNameLengthValidator : public SingleVariableValidator
{
public:
    VariableNameLengthValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
    void validateNameLength(const std::string &implementationName, glw::GLint propValue) const;
};

VariableNameLengthValidator::VariableNameLengthValidator(Context &context, glw::GLuint programID,
                                                         const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_NAME_LENGTH, programID, filter, DE_NULL)
{
}

void VariableNameLengthValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                         const std::string &resource, glw::GLint propValue,
                                                         const std::string &implementationName) const
{
    DE_UNREF(path);
    DE_UNREF(resource);
    validateNameLength(implementationName, propValue);
}

void VariableNameLengthValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                          const std::string &implementationName) const
{
    DE_UNREF(resource);
    validateNameLength(implementationName, propValue);
}

void VariableNameLengthValidator::validateNameLength(const std::string &implementationName, glw::GLint propValue) const
{
    const int expected = (int)implementationName.length() + 1; // includes null byte
    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying name length, expecting " << expected << " ("
                       << (int)implementationName.length() << " for \"" << implementationName
                       << "\" + 1 byte for terminating null character)" << tcu::TestLog::EndMessage;

    if (propValue != expected)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid name length, got " << propValue
                           << tcu::TestLog::EndMessage;
        setError("name length invalid");
    }
}

class OffsetValidator : public SingleVariableValidator
{
public:
    OffsetValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

OffsetValidator::OffsetValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_OFFSET, programID, filter, DE_NULL)
{
}

void OffsetValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                             const std::string &resource, glw::GLint propValue,
                                             const std::string &implementationName) const
{
    const bool isAtomicCounterUniform = glu::isDataTypeAtomicCounter(path.back().getVariableType()->getBasicType());
    const bool isBufferBackedBlockStorage =
        path.front().isInterfaceBlock() &&
        isBufferBackedInterfaceBlockStorage(path.front().getInterfaceBlock()->storage);

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    if (!isAtomicCounterUniform && !isBufferBackedBlockStorage)
    {
        // Not buffer backed
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying offset, expecting -1" << tcu::TestLog::EndMessage;

        if (propValue != -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid offset, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("offset invalid");
        }
    }
    else
    {
        // Expect a valid offset
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying offset, expecting a valid offset"
                           << tcu::TestLog::EndMessage;

        if (propValue < 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid offset, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("offset invalid");
        }
    }
}

class VariableReferencedByShaderValidator : public PropValidator
{
public:
    VariableReferencedByShaderValidator(Context &context, glu::ShaderType shaderType,
                                        const VariableSearchFilter &searchFilter);

    std::string getHumanReadablePropertyString(glw::GLint propVal) const;
    void validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource, glw::GLint propValue,
                  const std::string &implementationName) const;

private:
    const VariableSearchFilter m_filter;
    const glu::ShaderType m_shaderType;
};

VariableReferencedByShaderValidator::VariableReferencedByShaderValidator(Context &context, glu::ShaderType shaderType,
                                                                         const VariableSearchFilter &searchFilter)
    : PropValidator(context, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER, getRequiredExtensionForStage(shaderType))
    , m_filter(VariableSearchFilter::logicalAnd(VariableSearchFilter::createShaderTypeFilter(shaderType), searchFilter))
    , m_shaderType(shaderType)
{
    DE_ASSERT(m_shaderType < glu::SHADERTYPE_LAST);
}

std::string VariableReferencedByShaderValidator::getHumanReadablePropertyString(glw::GLint propVal) const
{
    return de::toString(glu::getBooleanStr(propVal));
}

void VariableReferencedByShaderValidator::validate(const ProgramInterfaceDefinition::Program *program,
                                                   const std::string &resource, glw::GLint propValue,
                                                   const std::string &implementationName) const
{
    DE_UNREF(implementationName);

    std::vector<VariablePathComponent> unusedPath;
    const bool referencedByShader = findProgramVariablePathByPathName(unusedPath, program, resource, m_filter);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying referenced by " << glu::getShaderTypeName(m_shaderType)
                       << " shader, expecting " << ((referencedByShader) ? ("GL_TRUE") : ("GL_FALSE"))
                       << tcu::TestLog::EndMessage;

    if (propValue != ((referencedByShader) ? (1) : (0)))
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid referenced_by_"
                           << glu::getShaderTypeName(m_shaderType) << ", got " << propValue << tcu::TestLog::EndMessage;
        setError("referenced_by_" + std::string(glu::getShaderTypeName(m_shaderType)) + " invalid");
    }
}

class BlockNameLengthValidator : public SingleBlockValidator
{
public:
    BlockNameLengthValidator(Context &context, const glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleBlock(const glu::InterfaceBlock &block, const std::vector<int> &instanceIndex,
                             const std::string &resource, glw::GLint propValue,
                             const std::string &implementationName) const;
};

BlockNameLengthValidator::BlockNameLengthValidator(Context &context, const glw::GLuint programID,
                                                   const VariableSearchFilter &filter)
    : SingleBlockValidator(context, PROGRAMRESOURCEPROP_NAME_LENGTH, programID, filter, DE_NULL)
{
}

void BlockNameLengthValidator::validateSingleBlock(const glu::InterfaceBlock &block,
                                                   const std::vector<int> &instanceIndex, const std::string &resource,
                                                   glw::GLint propValue, const std::string &implementationName) const
{
    DE_UNREF(instanceIndex);
    DE_UNREF(block);
    DE_UNREF(resource);

    const int expected = (int)implementationName.length() + 1; // includes null byte
    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying name length, expecting " << expected << " ("
                       << (int)implementationName.length() << " for \"" << implementationName
                       << "\" + 1 byte for terminating null character)" << tcu::TestLog::EndMessage;

    if (propValue != expected)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid name length, got " << propValue
                           << tcu::TestLog::EndMessage;
        setError("name length invalid");
    }
}

class BufferBindingValidator : public SingleBlockValidator
{
public:
    BufferBindingValidator(Context &context, const glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleBlock(const glu::InterfaceBlock &block, const std::vector<int> &instanceIndex,
                             const std::string &resource, glw::GLint propValue,
                             const std::string &implementationName) const;
};

BufferBindingValidator::BufferBindingValidator(Context &context, const glw::GLuint programID,
                                               const VariableSearchFilter &filter)
    : SingleBlockValidator(context, PROGRAMRESOURCEPROP_BUFFER_BINDING, programID, filter, DE_NULL)
{
}

void BufferBindingValidator::validateSingleBlock(const glu::InterfaceBlock &block,
                                                 const std::vector<int> &instanceIndex, const std::string &resource,
                                                 glw::GLint propValue, const std::string &implementationName) const
{
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    if (block.layout.binding != -1)
    {
        int flatIndex     = 0;
        int dimensionSize = 1;

        for (int dimensionNdx = (int)(block.dimensions.size()) - 1; dimensionNdx >= 0; --dimensionNdx)
        {
            flatIndex += dimensionSize * instanceIndex[dimensionNdx];
            dimensionSize *= block.dimensions[dimensionNdx];
        }

        const int expected = (block.dimensions.empty()) ? (block.layout.binding) : (block.layout.binding + flatIndex);
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying block binding, expecting " << expected
                           << tcu::TestLog::EndMessage;

        if (propValue != expected)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid buffer binding, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("buffer binding invalid");
        }
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying buffer binding, expecting a valid binding"
                           << tcu::TestLog::EndMessage;

        if (propValue < 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid buffer binding, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("buffer binding invalid");
        }
    }
}

class BlockReferencedByShaderValidator : public PropValidator
{
public:
    BlockReferencedByShaderValidator(Context &context, glu::ShaderType shaderType,
                                     const VariableSearchFilter &searchFilter);

    std::string getHumanReadablePropertyString(glw::GLint propVal) const;
    void validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource, glw::GLint propValue,
                  const std::string &implementationName) const;

private:
    const VariableSearchFilter m_filter;
    const glu::ShaderType m_shaderType;
};

BlockReferencedByShaderValidator::BlockReferencedByShaderValidator(Context &context, glu::ShaderType shaderType,
                                                                   const VariableSearchFilter &searchFilter)
    : PropValidator(context, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER, getRequiredExtensionForStage(shaderType))
    , m_filter(VariableSearchFilter::logicalAnd(VariableSearchFilter::createShaderTypeFilter(shaderType), searchFilter))
    , m_shaderType(shaderType)
{
    DE_ASSERT(m_shaderType < glu::SHADERTYPE_LAST);
}

std::string BlockReferencedByShaderValidator::getHumanReadablePropertyString(glw::GLint propVal) const
{
    return de::toString(glu::getBooleanStr(propVal));
}

void BlockReferencedByShaderValidator::validate(const ProgramInterfaceDefinition::Program *program,
                                                const std::string &resource, glw::GLint propValue,
                                                const std::string &implementationName) const
{
    const std::string blockName = glu::parseVariableName(resource.c_str());
    bool referencedByShader     = false;

    DE_UNREF(implementationName);

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

        for (int blockNdx = 0; blockNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++blockNdx)
        {
            const glu::InterfaceBlock &block = shader->getDefaultBlock().interfaceBlocks[blockNdx];

            if (m_filter.matchesFilter(block) && block.interfaceName == blockName)
                referencedByShader = true;
        }
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying referenced by " << glu::getShaderTypeName(m_shaderType)
                       << " shader, expecting " << ((referencedByShader) ? ("GL_TRUE") : ("GL_FALSE"))
                       << tcu::TestLog::EndMessage;

    if (propValue != ((referencedByShader) ? (1) : (0)))
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid referenced_by_"
                           << glu::getShaderTypeName(m_shaderType) << ", got " << propValue << tcu::TestLog::EndMessage;
        setError("referenced_by_" + std::string(glu::getShaderTypeName(m_shaderType)) + " invalid");
    }
}

class TopLevelArraySizeValidator : public SingleVariableValidator
{
public:
    TopLevelArraySizeValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

TopLevelArraySizeValidator::TopLevelArraySizeValidator(Context &context, glw::GLuint programID,
                                                       const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_SIZE, programID, filter, DE_NULL)
{
}

void TopLevelArraySizeValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                        const std::string &resource, glw::GLint propValue,
                                                        const std::string &implementationName) const
{
    int expected;
    std::string reason;

    DE_ASSERT(path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_BUFFER);
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    if (!path[1].getDeclaration()->varType.isArrayType())
    {
        expected = 1;
        reason   = "Top-level block member is not an array";
    }
    else if (path[1].getDeclaration()->varType.getElementType().isBasicType())
    {
        expected = 1;
        reason   = "Top-level block member is not an array of an aggregate type";
    }
    else if (path[1].getDeclaration()->varType.getArraySize() == glu::VarType::UNSIZED_ARRAY)
    {
        expected = 0;
        reason   = "Top-level block member is an unsized top-level array";
    }
    else
    {
        expected = path[1].getDeclaration()->varType.getArraySize();
        reason   = "Top-level block member is a sized top-level array";
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying top level array size, expecting " << expected << ". ("
                       << reason << ")." << tcu::TestLog::EndMessage;

    if (propValue != expected)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid top level array size, got " << propValue
                           << tcu::TestLog::EndMessage;
        setError("top level array size invalid");
    }
}

class TopLevelArrayStrideValidator : public SingleVariableValidator
{
public:
    TopLevelArrayStrideValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

TopLevelArrayStrideValidator::TopLevelArrayStrideValidator(Context &context, glw::GLuint programID,
                                                           const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_STRIDE, programID, filter, DE_NULL)
{
}

void TopLevelArrayStrideValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                          const std::string &resource, glw::GLint propValue,
                                                          const std::string &implementationName) const
{
    DE_ASSERT(path.front().isInterfaceBlock() && path.front().getInterfaceBlock()->storage == glu::STORAGE_BUFFER);
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    if (!path[1].getDeclaration()->varType.isArrayType())
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Verifying top level array stride, expecting 0. (Top-level block member is not an array)."
                           << tcu::TestLog::EndMessage;

        if (propValue != 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, top level array stride, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("top level array stride invalid");
        }
    }
    else if (path[1].getDeclaration()->varType.getElementType().isBasicType())
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Verifying top level array stride, expecting 0. (Top-level block member is not an array "
                              "of an aggregate type)."
                           << tcu::TestLog::EndMessage;

        if (propValue != 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, top level array stride, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("top level array stride invalid");
        }
    }
    else
    {
        const int minimumStride = getVarTypeSize(path[1].getDeclaration()->varType.getElementType());

        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Verifying top level array stride, expecting greater or equal to " << minimumStride << "."
                           << tcu::TestLog::EndMessage;

        if (propValue < minimumStride)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid top level array stride, got " << propValue
                               << tcu::TestLog::EndMessage;
            setError("top level array stride invalid");
        }
    }
}

class TransformFeedbackResourceValidator : public PropValidator
{
public:
    TransformFeedbackResourceValidator(Context &context, ProgramResourcePropFlags validationProp);

    void validate(const ProgramInterfaceDefinition::Program *program, const std::string &resource, glw::GLint propValue,
                  const std::string &implementationName) const;

private:
    virtual void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                         const std::string &implementationName) const                      = 0;
    virtual void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                        glw::GLint propValue, const std::string &implementationName) const = 0;
};

TransformFeedbackResourceValidator::TransformFeedbackResourceValidator(Context &context,
                                                                       ProgramResourcePropFlags validationProp)
    : PropValidator(context, validationProp, DE_NULL)
{
}

void TransformFeedbackResourceValidator::validate(const ProgramInterfaceDefinition::Program *program,
                                                  const std::string &resource, glw::GLint propValue,
                                                  const std::string &implementationName) const
{
    if (deStringBeginsWith(resource.c_str(), "gl_"))
    {
        validateBuiltinVariable(resource, propValue, implementationName);
    }
    else
    {
        // Check resource name is a xfb output. (quick check)
#if defined(DE_DEBUG)
        bool generatorFound = false;

        // Check the resource name is a valid transform feedback resource and find the name generating resource
        for (int varyingNdx = 0; varyingNdx < (int)program->getTransformFeedbackVaryings().size(); ++varyingNdx)
        {
            const std::string varyingName = program->getTransformFeedbackVaryings()[varyingNdx];
            std::vector<VariablePathComponent> path;
            std::vector<std::string> resources;

            if (!findProgramVariablePathByPathName(path, program, varyingName,
                                                   VariableSearchFilter::createShaderTypeStorageFilter(
                                                       getProgramTransformFeedbackStage(program), glu::STORAGE_OUT)))
            {
                // program does not contain feedback varying, not valid program
                DE_ASSERT(false);
                return;
            }

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

            if (de::contains(resources.begin(), resources.end(), resource))
            {
                generatorFound = true;
                break;
            }
        }

        // resource name was not found, should never happen
        DE_ASSERT(generatorFound);
        DE_UNREF(generatorFound);
#endif

        // verify resource
        {
            std::vector<VariablePathComponent> path;

            if (!findProgramVariablePathByPathName(path, program, resource,
                                                   VariableSearchFilter::createShaderTypeStorageFilter(
                                                       getProgramTransformFeedbackStage(program), glu::STORAGE_OUT)))
                DE_ASSERT(false);

            validateSingleVariable(path, resource, propValue, implementationName);
        }
    }
}

class TransformFeedbackArraySizeValidator : public TransformFeedbackResourceValidator
{
public:
    TransformFeedbackArraySizeValidator(Context &context);

    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

TransformFeedbackArraySizeValidator::TransformFeedbackArraySizeValidator(Context &context)
    : TransformFeedbackResourceValidator(context, PROGRAMRESOURCEPROP_ARRAY_SIZE)
{
}

void TransformFeedbackArraySizeValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                                  const std::string &implementationName) const
{
    DE_UNREF(implementationName);

    int arraySize = 0;

    if (resource == "gl_Position")
        arraySize = 1;
    else
        DE_ASSERT(false);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array size, expecting " << arraySize
                       << tcu::TestLog::EndMessage;
    if (arraySize != propValue)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
        setError("resource array size invalid");
    }
}

void TransformFeedbackArraySizeValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                                 const std::string &resource, glw::GLint propValue,
                                                                 const std::string &implementationName) const
{
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    const int arraySize =
        (path.back().getVariableType()->isArrayType()) ? (path.back().getVariableType()->getArraySize()) : (1);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying array size, expecting " << arraySize
                       << tcu::TestLog::EndMessage;
    if (arraySize != propValue)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
        setError("resource array size invalid");
    }
}

class TransformFeedbackNameLengthValidator : public TransformFeedbackResourceValidator
{
public:
    TransformFeedbackNameLengthValidator(Context &context);

private:
    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
    void validateVariable(const std::string &implementationName, glw::GLint propValue) const;
};

TransformFeedbackNameLengthValidator::TransformFeedbackNameLengthValidator(Context &context)
    : TransformFeedbackResourceValidator(context, PROGRAMRESOURCEPROP_NAME_LENGTH)
{
}

void TransformFeedbackNameLengthValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                                   const std::string &implementationName) const
{
    DE_UNREF(resource);
    validateVariable(implementationName, propValue);
}

void TransformFeedbackNameLengthValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                                  const std::string &resource, glw::GLint propValue,
                                                                  const std::string &implementationName) const
{
    DE_UNREF(path);
    DE_UNREF(resource);
    validateVariable(implementationName, propValue);
}

void TransformFeedbackNameLengthValidator::validateVariable(const std::string &implementationName,
                                                            glw::GLint propValue) const
{
    const int expected = (int)implementationName.length() + 1; // includes null byte
    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying name length, expecting " << expected << " ("
                       << (int)implementationName.length() << " for \"" << implementationName
                       << "\" + 1 byte for terminating null character)" << tcu::TestLog::EndMessage;

    if (propValue != expected)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, invalid name length, got " << propValue
                           << tcu::TestLog::EndMessage;
        setError("name length invalid");
    }
}

class TransformFeedbackTypeValidator : public TransformFeedbackResourceValidator
{
public:
    TransformFeedbackTypeValidator(Context &context);

    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
};

TransformFeedbackTypeValidator::TransformFeedbackTypeValidator(Context &context)
    : TransformFeedbackResourceValidator(context, PROGRAMRESOURCEPROP_TYPE)
{
}

void TransformFeedbackTypeValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                             const std::string &implementationName) const
{
    DE_UNREF(implementationName);

    glu::DataType varType = glu::TYPE_INVALID;

    if (resource == "gl_Position")
        varType = glu::TYPE_FLOAT_VEC4;
    else
        DE_ASSERT(false);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying type, expecting " << glu::getDataTypeName(varType)
                       << tcu::TestLog::EndMessage;
    if (glu::getDataTypeFromGLType(propValue) != varType)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got "
                           << glu::getDataTypeName(glu::getDataTypeFromGLType(propValue)) << tcu::TestLog::EndMessage;
        setError("resource type invalid");
    }
    return;
}

void TransformFeedbackTypeValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                                            const std::string &resource, glw::GLint propValue,
                                                            const std::string &implementationName) const
{
    DE_UNREF(resource);
    DE_UNREF(implementationName);

    // Unlike other interfaces, xfb program interface uses just variable name to refer to arrays of basic types. (Others use "variable[0]")
    // Thus we might end up querying a type for an array. In this case, return the type of an array element.
    const glu::VarType &variable    = *path.back().getVariableType();
    const glu::VarType &elementType = (variable.isArrayType()) ? (variable.getElementType()) : (variable);

    DE_ASSERT(elementType.isBasicType());

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying type, expecting "
                       << glu::getDataTypeName(elementType.getBasicType()) << tcu::TestLog::EndMessage;
    if (elementType.getBasicType() != glu::getDataTypeFromGLType(propValue))
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got "
                           << glu::getDataTypeName(glu::getDataTypeFromGLType(propValue)) << tcu::TestLog::EndMessage;
        setError("resource type invalid");
    }
}

class PerPatchValidator : public SingleVariableValidator
{
public:
    PerPatchValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter);

    std::string getHumanReadablePropertyString(glw::GLint propVal) const;
    void validateSingleVariable(const std::vector<VariablePathComponent> &path, const std::string &resource,
                                glw::GLint propValue, const std::string &implementationName) const;
    void validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                 const std::string &implementationName) const;
};

PerPatchValidator::PerPatchValidator(Context &context, glw::GLuint programID, const VariableSearchFilter &filter)
    : SingleVariableValidator(context, PROGRAMRESOURCEPROP_IS_PER_PATCH, programID, filter,
                              "GL_EXT_tessellation_shader")
{
}

std::string PerPatchValidator::getHumanReadablePropertyString(glw::GLint propVal) const
{
    return de::toString(glu::getBooleanStr(propVal));
}

void PerPatchValidator::validateSingleVariable(const std::vector<VariablePathComponent> &path,
                                               const std::string &resource, glw::GLint propValue,
                                               const std::string &implementationName) const
{
    const glu::Storage storage = (path.front().isInterfaceBlock()) ? (path.front().getInterfaceBlock()->storage) :
                                                                     (path.front().getDeclaration()->storage);
    const int expected         = (storage == glu::STORAGE_PATCH_IN || storage == glu::STORAGE_PATCH_OUT) ? (1) : (0);

    DE_UNREF(resource);
    DE_UNREF(implementationName);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying if is per patch, expecting IS_PER_PATCH = " << expected
                       << tcu::TestLog::EndMessage;

    if (propValue != expected)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
        setError("resource is per patch invalid");
    }
}

void PerPatchValidator::validateBuiltinVariable(const std::string &resource, glw::GLint propValue,
                                                const std::string &implementationName) const
{
    DE_UNREF(implementationName);

    static const struct
    {
        const char *name;
        int isPerPatch;
    } builtins[] = {
        {"gl_Position", 0},          {"gl_PerVertex.gl_Position", 0}, {"gl_InvocationID", 0},
        {"gl_TessLevelOuter[0]", 1}, {"gl_TessLevelInner[0]", 1},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(builtins); ++ndx)
    {
        if (resource == builtins[ndx].name)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Verifying if is per patch, expecting IS_PER_PATCH = " << builtins[ndx].isPerPatch
                               << tcu::TestLog::EndMessage;

            if (propValue != builtins[ndx].isPerPatch)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "\tError, got " << propValue << tcu::TestLog::EndMessage;
                setError("resource is per patch invalid");
            }
            return;
        }
    }

    DE_ASSERT(false);
}

} // namespace

ProgramResourceQueryTestTarget::ProgramResourceQueryTestTarget(ProgramInterface interface_, uint32_t propFlags_)
    : interface(interface_)
    , propFlags(propFlags_)
{
    switch (interface)
    {
    case PROGRAMINTERFACE_UNIFORM:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_UNIFORM_INTERFACE_MASK) == propFlags);
        break;
    case PROGRAMINTERFACE_UNIFORM_BLOCK:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_UNIFORM_BLOCK_INTERFACE_MASK) == propFlags);
        break;
    case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_SHADER_STORAGE_BLOCK_MASK) == propFlags);
        break;
    case PROGRAMINTERFACE_PROGRAM_INPUT:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_PROGRAM_INPUT_MASK) == propFlags);
        break;
    case PROGRAMINTERFACE_PROGRAM_OUTPUT:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_PROGRAM_OUTPUT_MASK) == propFlags);
        break;
    case PROGRAMINTERFACE_BUFFER_VARIABLE:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_BUFFER_VARIABLE_MASK) == propFlags);
        break;
    case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
        DE_ASSERT((propFlags & PROGRAMRESOURCEPROP_TRANSFORM_FEEDBACK_VARYING_MASK) == propFlags);
        break;

    default:
        DE_ASSERT(false);
    }
}

ProgramInterfaceQueryTestCase::ProgramInterfaceQueryTestCase(Context &context, const char *name,
                                                             const char *description,
                                                             ProgramResourceQueryTestTarget queryTarget)
    : TestCase(context, name, description)
    , m_queryTarget(queryTarget)
{
}

ProgramInterfaceQueryTestCase::~ProgramInterfaceQueryTestCase(void)
{
}

ProgramInterface ProgramInterfaceQueryTestCase::getTargetInterface(void) const
{
    return m_queryTarget.interface;
}

static glw::GLenum getGLInterfaceEnumValue(ProgramInterface interface)
{
    switch (interface)
    {
    case PROGRAMINTERFACE_UNIFORM:
        return GL_UNIFORM;
    case PROGRAMINTERFACE_UNIFORM_BLOCK:
        return GL_UNIFORM_BLOCK;
    case PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER:
        return GL_ATOMIC_COUNTER_BUFFER;
    case PROGRAMINTERFACE_PROGRAM_INPUT:
        return GL_PROGRAM_INPUT;
    case PROGRAMINTERFACE_PROGRAM_OUTPUT:
        return GL_PROGRAM_OUTPUT;
    case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
        return GL_TRANSFORM_FEEDBACK_VARYING;
    case PROGRAMINTERFACE_BUFFER_VARIABLE:
        return GL_BUFFER_VARIABLE;
    case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
        return GL_SHADER_STORAGE_BLOCK;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

static bool isInterfaceBlockInterfaceName(const ProgramInterfaceDefinition::Program *program,
                                          ProgramInterface interface, const std::string &blockInterfaceName)
{
    uint32_t validStorageBits;
    uint32_t searchStageBits;

    DE_STATIC_ASSERT(glu::STORAGE_LAST < 32);
    DE_STATIC_ASSERT(glu::SHADERTYPE_LAST < 32);

    switch (interface)
    {
    case PROGRAMINTERFACE_UNIFORM_BLOCK:
    case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
    case PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER:
        return false;

    case PROGRAMINTERFACE_PROGRAM_INPUT:
        validStorageBits = (1u << glu::STORAGE_IN) | (1u << glu::STORAGE_PATCH_IN);
        searchStageBits  = (1u << program->getFirstStage());
        break;

    case PROGRAMINTERFACE_PROGRAM_OUTPUT:
        validStorageBits = (1u << glu::STORAGE_OUT) | (1u << glu::STORAGE_PATCH_OUT);
        searchStageBits  = (1u << program->getLastStage());
        break;

    case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
        validStorageBits = (1u << glu::STORAGE_OUT);
        searchStageBits  = (1u << getProgramTransformFeedbackStage(program));
        break;

    case PROGRAMINTERFACE_UNIFORM:
        validStorageBits = (1u << glu::STORAGE_UNIFORM);
        searchStageBits  = 0xFFFFFFFFu;
        break;

    case PROGRAMINTERFACE_BUFFER_VARIABLE:
        validStorageBits = (1u << glu::STORAGE_BUFFER);
        searchStageBits  = 0xFFFFFFFFu;
        break;

    default:
        DE_ASSERT(false);
        return false;
    }

    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
    {
        const ProgramInterfaceDefinition::Shader *const shader = program->getShaders()[shaderNdx];
        if (((1u << shader->getType()) & searchStageBits) == 0)
            continue;

        for (int blockNdx = 0; blockNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++blockNdx)
        {
            const glu::InterfaceBlock &block = shader->getDefaultBlock().interfaceBlocks[blockNdx];

            if (((1u << block.storage) & validStorageBits) == 0)
                continue;

            if (block.interfaceName == blockInterfaceName)
                return true;
        }
    }
    return false;
}

static std::string getInterfaceBlockInteraceNameByMember(const ProgramInterfaceDefinition::Program *program,
                                                         ProgramInterface interface, const std::string &memberName)
{
    uint32_t validStorageBits;
    uint32_t searchStageBits;

    DE_STATIC_ASSERT(glu::STORAGE_LAST < 32);
    DE_STATIC_ASSERT(glu::SHADERTYPE_LAST < 32);

    switch (interface)
    {
    case PROGRAMINTERFACE_UNIFORM_BLOCK:
    case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
    case PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER:
        return "";

    case PROGRAMINTERFACE_PROGRAM_INPUT:
        validStorageBits = (1u << glu::STORAGE_IN) | (1u << glu::STORAGE_PATCH_IN);
        searchStageBits  = (1u << program->getFirstStage());
        break;

    case PROGRAMINTERFACE_PROGRAM_OUTPUT:
        validStorageBits = (1u << glu::STORAGE_OUT) | (1u << glu::STORAGE_PATCH_OUT);
        searchStageBits  = (1u << program->getLastStage());
        break;

    case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
        validStorageBits = (1u << glu::STORAGE_OUT);
        searchStageBits  = (1u << getProgramTransformFeedbackStage(program));
        break;

    case PROGRAMINTERFACE_UNIFORM:
        validStorageBits = (1u << glu::STORAGE_UNIFORM);
        searchStageBits  = 0xFFFFFFFFu;
        break;

    case PROGRAMINTERFACE_BUFFER_VARIABLE:
        validStorageBits = (1u << glu::STORAGE_BUFFER);
        searchStageBits  = 0xFFFFFFFFu;
        break;

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

    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
    {
        const ProgramInterfaceDefinition::Shader *const shader = program->getShaders()[shaderNdx];
        if (((1u << shader->getType()) & searchStageBits) == 0)
            continue;

        for (int blockNdx = 0; blockNdx < (int)shader->getDefaultBlock().interfaceBlocks.size(); ++blockNdx)
        {
            const glu::InterfaceBlock &block = shader->getDefaultBlock().interfaceBlocks[blockNdx];

            if (((1u << block.storage) & validStorageBits) == 0)
                continue;

            for (int varNdx = 0; varNdx < (int)block.variables.size(); ++varNdx)
            {
                if (block.variables[varNdx].name == memberName)
                    return block.interfaceName;
            }
        }
    }
    return "";
}

static void queryAndValidateProps(tcu::TestContext &testCtx, const glw::Functions &gl, glw::GLuint programID,
                                  ProgramInterface interface, const char *targetResourceName,
                                  const ProgramInterfaceDefinition::Program *programDefinition,
                                  const std::vector<glw::GLenum> &props,
                                  const std::vector<const PropValidator *> &validators)
{
    const glw::GLenum glInterface          = getGLInterfaceEnumValue(interface);
    std::string implementationResourceName = targetResourceName;
    glw::GLuint resourceNdx;
    glw::GLint written = -1;

    // prefill result buffer with an invalid value. -1 might be valid sometimes, avoid it. Make buffer one larger
    // to allow detection of too many return values
    std::vector<glw::GLint> propValues(props.size() + 1, -2);

    DE_ASSERT(props.size() == validators.size());

    // query

    resourceNdx = gl.getProgramResourceIndex(programID, glInterface, targetResourceName);
    GLU_EXPECT_NO_ERROR(gl.getError(), "get resource index");

    if (resourceNdx == GL_INVALID_INDEX)
    {
        static const struct
        {
            bool removeTrailingArray;  // convert from "target[0]" -> "target"
            bool removeTrailingMember; // convert from "target.member" -> "target"
            bool removeIOBlock;        // convert from "InterfaceName.target" -> "target"
            bool addIOBlock;           // convert from "target" -> "InterfaceName.target"
            bool addIOBlockArray;      // convert from "target" -> "InterfaceName[0].target"
        } recoveryStrategies[] = {
            // try one patch
            {true, false, false, false, false},
            {false, true, false, false, false},
            {false, false, true, false, false},
            {false, false, false, true, false},
            {false, false, false, false, true},
            // patch both ends
            {true, false, true, false, false},
            {true, false, false, true, false},
            {true, false, false, false, true},
            {false, true, true, false, false},
            {false, true, false, true, false},
            {false, true, false, false, true},
        };

        // The resource name generation in the GL implementations is very commonly broken. Try to
        // keep the tests producing useful data even in these cases by attempting to recover from
        // common naming bugs. Set test result to failure even if recovery succeeded to signal
        // incorrect name generation.

        testCtx.getLog() << tcu::TestLog::Message << "getProgramResourceIndex returned GL_INVALID_INDEX for \""
                         << targetResourceName << "\"" << tcu::TestLog::EndMessage;
        testCtx.setTestResult(QP_TEST_RESULT_FAIL, "could not find target resource");

        for (int strategyNdx = 0; strategyNdx < DE_LENGTH_OF_ARRAY(recoveryStrategies); ++strategyNdx)
        {
            const std::string resourceName = std::string(targetResourceName);
            const size_t rootNameEnd       = resourceName.find_first_of(".[");
            const std::string rootName     = resourceName.substr(0, rootNameEnd);
            std::string simplifiedResourceName;

            if (recoveryStrategies[strategyNdx].removeTrailingArray)
            {
                if (de::endsWith(resourceName, "[0]"))
                    simplifiedResourceName = resourceName.substr(0, resourceName.length() - 3);
                else
                    continue;
            }

            if (recoveryStrategies[strategyNdx].removeTrailingMember)
            {
                const size_t lastMember = resourceName.find_last_of('.');
                if (lastMember != std::string::npos)
                    simplifiedResourceName = resourceName.substr(0, lastMember);
                else
                    continue;
            }

            if (recoveryStrategies[strategyNdx].removeIOBlock)
            {
                if (deStringBeginsWith(resourceName.c_str(), "gl_PerVertex."))
                {
                    // builtin interface bock, remove block name
                    simplifiedResourceName = resourceName.substr(13);
                }
                else if (isInterfaceBlockInterfaceName(programDefinition, interface, rootName))
                {
                    // user-defined inteface block, remove name
                    const size_t accessorEnd = resourceName.find('.'); // includes potential array accessor

                    if (accessorEnd != std::string::npos)
                        simplifiedResourceName = resourceName.substr(0, accessorEnd + 1);
                    else
                        continue;
                }
                else
                {
                    // recovery not applicable
                    continue;
                }
            }

            if (recoveryStrategies[strategyNdx].addIOBlock || recoveryStrategies[strategyNdx].addIOBlockArray)
            {
                const std::string arrayAccessor = (recoveryStrategies[strategyNdx].addIOBlockArray) ? ("[0]") : ("");

                if (deStringBeginsWith(resourceName.c_str(), "gl_") && resourceName.find('.') == std::string::npos)
                {
                    // free builtin variable, add block name
                    simplifiedResourceName = "gl_PerVertex" + arrayAccessor + "." + resourceName;
                }
                else
                {
                    const std::string interafaceName =
                        getInterfaceBlockInteraceNameByMember(programDefinition, interface, rootName);

                    if (!interafaceName.empty())
                    {
                        // free user variable, add block name
                        simplifiedResourceName = interafaceName + arrayAccessor + "." + resourceName;
                    }
                    else
                    {
                        // recovery not applicable
                        continue;
                    }
                }
            }

            if (simplifiedResourceName.empty())
                continue;

            resourceNdx = gl.getProgramResourceIndex(programID, glInterface, simplifiedResourceName.c_str());
            GLU_EXPECT_NO_ERROR(gl.getError(), "get resource index");

            // recovery succeeded
            if (resourceNdx != GL_INVALID_INDEX)
            {
                implementationResourceName = simplifiedResourceName;
                testCtx.getLog() << tcu::TestLog::Message
                                 << "\tResource not found, continuing anyway using index obtained for resource \""
                                 << simplifiedResourceName << "\"" << tcu::TestLog::EndMessage;
                break;
            }
        }

        if (resourceNdx == GL_INVALID_INDEX)
            return;
    }

    gl.getProgramResourceiv(programID, glInterface, resourceNdx, (int)props.size(), &props[0], (int)propValues.size(),
                            &written, &propValues[0]);
    GLU_EXPECT_NO_ERROR(gl.getError(), "get props");

    if (written != (int)props.size())
    {
        testCtx.getLog() << tcu::TestLog::Message
                         << "getProgramResourceiv returned unexpected number of values, expected " << (int)props.size()
                         << ", got " << written << tcu::TestLog::EndMessage;
        testCtx.setTestResult(QP_TEST_RESULT_FAIL, "getProgramResourceiv returned unexpected number of values");
        return;
    }

    if (propValues.back() != -2)
    {
        testCtx.getLog() << tcu::TestLog::Message
                         << "getProgramResourceiv post write buffer guard value was modified, too many return values"
                         << tcu::TestLog::EndMessage;
        testCtx.setTestResult(QP_TEST_RESULT_FAIL, "getProgramResourceiv returned unexpected number of values");
        return;
    }
    propValues.pop_back();
    DE_ASSERT(validators.size() == propValues.size());

    // log

    {
        tcu::MessageBuilder message(&testCtx.getLog());
        message << "For resource index " << resourceNdx << " (\"" << targetResourceName
                << "\") got following properties:\n";

        for (int propNdx = 0; propNdx < (int)propValues.size(); ++propNdx)
            message << "\t" << glu::getProgramResourcePropertyName(props[propNdx]) << ":\t"
                    << validators[propNdx]->getHumanReadablePropertyString(propValues[propNdx]) << "\n";

        message << tcu::TestLog::EndMessage;
    }

    // validate

    for (int propNdx = 0; propNdx < (int)propValues.size(); ++propNdx)
        validators[propNdx]->validate(programDefinition, targetResourceName, propValues[propNdx],
                                      implementationResourceName);
}

const ProgramInterfaceDefinition::Program *ProgramInterfaceQueryTestCase::getAndCheckProgramDefinition(void)
{
    const ProgramInterfaceDefinition::Program *programDefinition = getProgramDefinition();
    DE_ASSERT(programDefinition->isValid());

    auto type = m_context.getRenderContext().getType();
    if (glu::contextSupports(type, glu::ApiType::es(3, 2)) || glu::contextSupports(type, glu::ApiType::core(4, 5)))
    {
        return programDefinition;
    }

    if (programDefinition->hasStage(glu::SHADERTYPE_TESSELLATION_CONTROL) ||
        programDefinition->hasStage(glu::SHADERTYPE_TESSELLATION_EVALUATION))
    {
        if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
            throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    }

    // Testing IS_PER_PATCH as a part of a larger set is ok, since the extension is checked
    // before query. However, we don't want IS_PER_PATCH-specific tests to become noop and pass.
    if (m_queryTarget.propFlags == PROGRAMRESOURCEPROP_IS_PER_PATCH)
    {
        if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
            throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    }

    if (programDefinition->hasStage(glu::SHADERTYPE_GEOMETRY))
    {
        if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
            throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");
    }

    if (programContainsIOBlocks(programDefinition))
    {
        if (!m_context.getContextInfo().isExtensionSupported("GL_EXT_shader_io_blocks"))
            throw tcu::NotSupportedError("Test requires GL_EXT_shader_io_blocks extension");
    }

    return programDefinition;
}

int ProgramInterfaceQueryTestCase::getMaxPatchVertices(void)
{
    const glw::Functions &gl    = m_context.getRenderContext().getFunctions();
    glw::GLint maxPatchVertices = 0;

    gl.getIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
    GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(GL_MAX_PATCH_VERTICES)");
    return maxPatchVertices;
}

ProgramInterfaceQueryTestCase::IterateResult ProgramInterfaceQueryTestCase::iterate(void)
{
    struct TestProperty
    {
        glw::GLenum prop;
        const PropValidator *validator;
    };

    const ProgramInterfaceDefinition::Program *programDefinition = getAndCheckProgramDefinition();
    const std::vector<std::string> targetResources               = getQueryTargetResources();
    glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(programDefinition));

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    // Log program
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Program", "Program");

        // Feedback varyings
        if (!programDefinition->getTransformFeedbackVaryings().empty())
        {
            tcu::MessageBuilder builder(&m_testCtx.getLog());
            builder << "Transform feedback varyings: {";
            for (int ndx = 0; ndx < (int)programDefinition->getTransformFeedbackVaryings().size(); ++ndx)
            {
                if (ndx)
                    builder << ", ";
                builder << "\"" << programDefinition->getTransformFeedbackVaryings()[ndx] << "\"";
            }
            builder << "}" << tcu::TestLog::EndMessage;
        }

        m_testCtx.getLog() << program;
        if (!program.isOk())
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Program build failed, checking if program exceeded implementation limits"
                               << tcu::TestLog::EndMessage;
            checkProgramResourceUsage(programDefinition, m_context.getRenderContext().getFunctions(),
                                      m_testCtx.getLog());

            // within limits
            throw tcu::TestError("could not build program");
        }
    }

    // Check interface props

    switch (m_queryTarget.interface)
    {
    case PROGRAMINTERFACE_UNIFORM:
    {
        const VariableSearchFilter uniformFilter = VariableSearchFilter::createStorageFilter(glu::STORAGE_UNIFORM);

        const TypeValidator typeValidator(m_context, program.getProgram(), uniformFilter);
        const ArraySizeValidator arraySizeValidator(m_context, program.getProgram(), -1, uniformFilter);
        const ArrayStrideValidator arrayStrideValidator(m_context, program.getProgram(), uniformFilter);
        const BlockIndexValidator blockIndexValidator(m_context, program.getProgram(), uniformFilter);
        const IsRowMajorValidator isRowMajorValidator(m_context, program.getProgram(), uniformFilter);
        const MatrixStrideValidator matrixStrideValidator(m_context, program.getProgram(), uniformFilter);
        const AtomicCounterBufferIndexVerifier atomicCounterBufferIndexVerifier(m_context, program.getProgram(),
                                                                                uniformFilter);
        const LocationValidator locationValidator(m_context, program.getProgram(), uniformFilter);
        const VariableNameLengthValidator nameLengthValidator(m_context, program.getProgram(), uniformFilter);
        const OffsetValidator offsetVerifier(m_context, program.getProgram(), uniformFilter);
        const VariableReferencedByShaderValidator referencedByVertexVerifier(m_context, glu::SHADERTYPE_VERTEX,
                                                                             uniformFilter);
        const VariableReferencedByShaderValidator referencedByFragmentVerifier(m_context, glu::SHADERTYPE_FRAGMENT,
                                                                               uniformFilter);
        const VariableReferencedByShaderValidator referencedByComputeVerifier(m_context, glu::SHADERTYPE_COMPUTE,
                                                                              uniformFilter);
        const VariableReferencedByShaderValidator referencedByGeometryVerifier(m_context, glu::SHADERTYPE_GEOMETRY,
                                                                               uniformFilter);
        const VariableReferencedByShaderValidator referencedByTessControlVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_CONTROL, uniformFilter);
        const VariableReferencedByShaderValidator referencedByTessEvaluationVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_EVALUATION, uniformFilter);

        const TestProperty allProperties[] = {
            {GL_ARRAY_SIZE, &arraySizeValidator},
            {GL_ARRAY_STRIDE, &arrayStrideValidator},
            {GL_ATOMIC_COUNTER_BUFFER_INDEX, &atomicCounterBufferIndexVerifier},
            {GL_BLOCK_INDEX, &blockIndexValidator},
            {GL_IS_ROW_MAJOR, &isRowMajorValidator},
            {GL_LOCATION, &locationValidator},
            {GL_MATRIX_STRIDE, &matrixStrideValidator},
            {GL_NAME_LENGTH, &nameLengthValidator},
            {GL_OFFSET, &offsetVerifier},
            {GL_REFERENCED_BY_VERTEX_SHADER, &referencedByVertexVerifier},
            {GL_REFERENCED_BY_FRAGMENT_SHADER, &referencedByFragmentVerifier},
            {GL_REFERENCED_BY_COMPUTE_SHADER, &referencedByComputeVerifier},
            {GL_REFERENCED_BY_GEOMETRY_SHADER, &referencedByGeometryVerifier},
            {GL_REFERENCED_BY_TESS_CONTROL_SHADER, &referencedByTessControlVerifier},
            {GL_REFERENCED_BY_TESS_EVALUATION_SHADER, &referencedByTessEvaluationVerifier},
            {GL_TYPE, &typeValidator},
        };

        for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "UniformResource",
                                                "Uniform resource \"" + targetResources[targetResourceNdx] + "\"");
            const glw::Functions &gl = m_context.getRenderContext().getFunctions();
            std::vector<glw::GLenum> props;
            std::vector<const PropValidator *> validators;

            for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
            {
                if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
                    allProperties[propNdx].validator->isSupported())
                {
                    props.push_back(allProperties[propNdx].prop);
                    validators.push_back(allProperties[propNdx].validator);
                }
            }

            DE_ASSERT(!props.empty());

            queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface,
                                  targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
        }

        break;
    }

    case PROGRAMINTERFACE_UNIFORM_BLOCK:
    case PROGRAMINTERFACE_SHADER_STORAGE_BLOCK:
    {
        const glu::Storage storage             = (m_queryTarget.interface == PROGRAMINTERFACE_UNIFORM_BLOCK) ?
                                                     (glu::STORAGE_UNIFORM) :
                                                     (glu::STORAGE_BUFFER);
        const VariableSearchFilter blockFilter = VariableSearchFilter::createStorageFilter(storage);

        const BlockNameLengthValidator nameLengthValidator(m_context, program.getProgram(), blockFilter);
        const BlockReferencedByShaderValidator referencedByVertexVerifier(m_context, glu::SHADERTYPE_VERTEX,
                                                                          blockFilter);
        const BlockReferencedByShaderValidator referencedByFragmentVerifier(m_context, glu::SHADERTYPE_FRAGMENT,
                                                                            blockFilter);
        const BlockReferencedByShaderValidator referencedByComputeVerifier(m_context, glu::SHADERTYPE_COMPUTE,
                                                                           blockFilter);
        const BlockReferencedByShaderValidator referencedByGeometryVerifier(m_context, glu::SHADERTYPE_GEOMETRY,
                                                                            blockFilter);
        const BlockReferencedByShaderValidator referencedByTessControlVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_CONTROL, blockFilter);
        const BlockReferencedByShaderValidator referencedByTessEvaluationVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_EVALUATION, blockFilter);
        const BufferBindingValidator bufferBindingValidator(m_context, program.getProgram(), blockFilter);

        const TestProperty allProperties[] = {
            {GL_NAME_LENGTH, &nameLengthValidator},
            {GL_REFERENCED_BY_VERTEX_SHADER, &referencedByVertexVerifier},
            {GL_REFERENCED_BY_FRAGMENT_SHADER, &referencedByFragmentVerifier},
            {GL_REFERENCED_BY_COMPUTE_SHADER, &referencedByComputeVerifier},
            {GL_REFERENCED_BY_GEOMETRY_SHADER, &referencedByGeometryVerifier},
            {GL_REFERENCED_BY_TESS_CONTROL_SHADER, &referencedByTessControlVerifier},
            {GL_REFERENCED_BY_TESS_EVALUATION_SHADER, &referencedByTessEvaluationVerifier},
            {GL_BUFFER_BINDING, &bufferBindingValidator},
        };

        for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "BlockResource",
                                                "Interface block \"" + targetResources[targetResourceNdx] + "\"");
            const glw::Functions &gl = m_context.getRenderContext().getFunctions();
            std::vector<glw::GLenum> props;
            std::vector<const PropValidator *> validators;

            for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
            {
                if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
                    allProperties[propNdx].validator->isSupported())
                {
                    props.push_back(allProperties[propNdx].prop);
                    validators.push_back(allProperties[propNdx].validator);
                }
            }

            DE_ASSERT(!props.empty());

            queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface,
                                  targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
        }

        break;
    }

    case PROGRAMINTERFACE_PROGRAM_INPUT:
    case PROGRAMINTERFACE_PROGRAM_OUTPUT:
    {
        const bool isInputCase            = (m_queryTarget.interface == PROGRAMINTERFACE_PROGRAM_INPUT);
        const glu::Storage varyingStorage = (isInputCase) ? (glu::STORAGE_IN) : (glu::STORAGE_OUT);
        const glu::Storage patchStorage   = (isInputCase) ? (glu::STORAGE_PATCH_IN) : (glu::STORAGE_PATCH_OUT);
        const glu::ShaderType shaderType =
            (isInputCase) ? (programDefinition->getFirstStage()) : (programDefinition->getLastStage());
        const int unsizedArraySize =
            (isInputCase && shaderType == glu::SHADERTYPE_GEOMETRY) ?
                (1) // input points
            :
            (isInputCase && shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL) ?
                (getMaxPatchVertices()) // input batch size
            :
            (!isInputCase && shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL) ?
                (programDefinition->getTessellationNumOutputPatchVertices()) // output batch size
            :
            (isInputCase && shaderType == glu::SHADERTYPE_TESSELLATION_EVALUATION) ?
                (getMaxPatchVertices()) // input batch size
                :
                (-1);
        const VariableSearchFilter variableFilter = VariableSearchFilter::logicalAnd(
            VariableSearchFilter::createShaderTypeFilter(shaderType),
            VariableSearchFilter::logicalOr(VariableSearchFilter::createStorageFilter(varyingStorage),
                                            VariableSearchFilter::createStorageFilter(patchStorage)));

        const TypeValidator typeValidator(m_context, program.getProgram(), variableFilter);
        const ArraySizeValidator arraySizeValidator(m_context, program.getProgram(), unsizedArraySize, variableFilter);
        const LocationValidator locationValidator(m_context, program.getProgram(), variableFilter);
        const VariableNameLengthValidator nameLengthValidator(m_context, program.getProgram(), variableFilter);
        const VariableReferencedByShaderValidator referencedByVertexVerifier(m_context, glu::SHADERTYPE_VERTEX,
                                                                             variableFilter);
        const VariableReferencedByShaderValidator referencedByFragmentVerifier(m_context, glu::SHADERTYPE_FRAGMENT,
                                                                               variableFilter);
        const VariableReferencedByShaderValidator referencedByComputeVerifier(m_context, glu::SHADERTYPE_COMPUTE,
                                                                              variableFilter);
        const VariableReferencedByShaderValidator referencedByGeometryVerifier(m_context, glu::SHADERTYPE_GEOMETRY,
                                                                               variableFilter);
        const VariableReferencedByShaderValidator referencedByTessControlVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_CONTROL, variableFilter);
        const VariableReferencedByShaderValidator referencedByTessEvaluationVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_EVALUATION, variableFilter);
        const PerPatchValidator perPatchValidator(m_context, program.getProgram(), variableFilter);

        const TestProperty allProperties[] = {
            {GL_ARRAY_SIZE, &arraySizeValidator},
            {GL_LOCATION, &locationValidator},
            {GL_NAME_LENGTH, &nameLengthValidator},
            {GL_REFERENCED_BY_VERTEX_SHADER, &referencedByVertexVerifier},
            {GL_REFERENCED_BY_FRAGMENT_SHADER, &referencedByFragmentVerifier},
            {GL_REFERENCED_BY_COMPUTE_SHADER, &referencedByComputeVerifier},
            {GL_REFERENCED_BY_GEOMETRY_SHADER, &referencedByGeometryVerifier},
            {GL_REFERENCED_BY_TESS_CONTROL_SHADER, &referencedByTessControlVerifier},
            {GL_REFERENCED_BY_TESS_EVALUATION_SHADER, &referencedByTessEvaluationVerifier},
            {GL_TYPE, &typeValidator},
            {GL_IS_PER_PATCH, &perPatchValidator},
        };

        for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
        {
            const std::string resourceInterfaceName =
                (m_queryTarget.interface == PROGRAMINTERFACE_PROGRAM_INPUT) ? ("Input") : ("Output");
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "BlockResource",
                                                resourceInterfaceName + " resource \"" +
                                                    targetResources[targetResourceNdx] + "\"");
            const glw::Functions &gl = m_context.getRenderContext().getFunctions();
            std::vector<glw::GLenum> props;
            std::vector<const PropValidator *> validators;

            for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
            {
                if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
                    allProperties[propNdx].validator->isSupported())
                {
                    props.push_back(allProperties[propNdx].prop);
                    validators.push_back(allProperties[propNdx].validator);
                }
            }

            DE_ASSERT(!props.empty());

            queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface,
                                  targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
        }

        break;
    }

    case PROGRAMINTERFACE_BUFFER_VARIABLE:
    {
        const VariableSearchFilter variableFilter = VariableSearchFilter::createStorageFilter(glu::STORAGE_BUFFER);

        const TypeValidator typeValidator(m_context, program.getProgram(), variableFilter);
        const ArraySizeValidator arraySizeValidator(m_context, program.getProgram(), 0, variableFilter);
        const ArrayStrideValidator arrayStrideValidator(m_context, program.getProgram(), variableFilter);
        const BlockIndexValidator blockIndexValidator(m_context, program.getProgram(), variableFilter);
        const IsRowMajorValidator isRowMajorValidator(m_context, program.getProgram(), variableFilter);
        const MatrixStrideValidator matrixStrideValidator(m_context, program.getProgram(), variableFilter);
        const OffsetValidator offsetValidator(m_context, program.getProgram(), variableFilter);
        const VariableNameLengthValidator nameLengthValidator(m_context, program.getProgram(), variableFilter);
        const VariableReferencedByShaderValidator referencedByVertexVerifier(m_context, glu::SHADERTYPE_VERTEX,
                                                                             variableFilter);
        const VariableReferencedByShaderValidator referencedByFragmentVerifier(m_context, glu::SHADERTYPE_FRAGMENT,
                                                                               variableFilter);
        const VariableReferencedByShaderValidator referencedByComputeVerifier(m_context, glu::SHADERTYPE_COMPUTE,
                                                                              variableFilter);
        const VariableReferencedByShaderValidator referencedByGeometryVerifier(m_context, glu::SHADERTYPE_GEOMETRY,
                                                                               variableFilter);
        const VariableReferencedByShaderValidator referencedByTessControlVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_CONTROL, variableFilter);
        const VariableReferencedByShaderValidator referencedByTessEvaluationVerifier(
            m_context, glu::SHADERTYPE_TESSELLATION_EVALUATION, variableFilter);
        const TopLevelArraySizeValidator topLevelArraySizeValidator(m_context, program.getProgram(), variableFilter);
        const TopLevelArrayStrideValidator topLevelArrayStrideValidator(m_context, program.getProgram(),
                                                                        variableFilter);

        const TestProperty allProperties[] = {
            {GL_ARRAY_SIZE, &arraySizeValidator},
            {GL_ARRAY_STRIDE, &arrayStrideValidator},
            {GL_BLOCK_INDEX, &blockIndexValidator},
            {GL_IS_ROW_MAJOR, &isRowMajorValidator},
            {GL_MATRIX_STRIDE, &matrixStrideValidator},
            {GL_NAME_LENGTH, &nameLengthValidator},
            {GL_OFFSET, &offsetValidator},
            {GL_REFERENCED_BY_VERTEX_SHADER, &referencedByVertexVerifier},
            {GL_REFERENCED_BY_FRAGMENT_SHADER, &referencedByFragmentVerifier},
            {GL_REFERENCED_BY_COMPUTE_SHADER, &referencedByComputeVerifier},
            {GL_REFERENCED_BY_GEOMETRY_SHADER, &referencedByGeometryVerifier},
            {GL_REFERENCED_BY_TESS_CONTROL_SHADER, &referencedByTessControlVerifier},
            {GL_REFERENCED_BY_TESS_EVALUATION_SHADER, &referencedByTessEvaluationVerifier},
            {GL_TOP_LEVEL_ARRAY_SIZE, &topLevelArraySizeValidator},
            {GL_TOP_LEVEL_ARRAY_STRIDE, &topLevelArrayStrideValidator},
            {GL_TYPE, &typeValidator},
        };

        for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "BufferVariableResource",
                                                "Buffer variable \"" + targetResources[targetResourceNdx] + "\"");
            const glw::Functions &gl = m_context.getRenderContext().getFunctions();
            std::vector<glw::GLenum> props;
            std::vector<const PropValidator *> validators;

            for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
            {
                if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
                    allProperties[propNdx].validator->isSupported())
                {
                    props.push_back(allProperties[propNdx].prop);
                    validators.push_back(allProperties[propNdx].validator);
                }
            }

            DE_ASSERT(!props.empty());

            queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface,
                                  targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
        }

        break;
    }

    case PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING:
    {
        const TransformFeedbackTypeValidator typeValidator(m_context);
        const TransformFeedbackArraySizeValidator arraySizeValidator(m_context);
        const TransformFeedbackNameLengthValidator nameLengthValidator(m_context);

        const TestProperty allProperties[] = {
            {GL_ARRAY_SIZE, &arraySizeValidator},
            {GL_NAME_LENGTH, &nameLengthValidator},
            {GL_TYPE, &typeValidator},
        };

        for (int targetResourceNdx = 0; targetResourceNdx < (int)targetResources.size(); ++targetResourceNdx)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "XFBVariableResource",
                                                "Transform feedback varying \"" + targetResources[targetResourceNdx] +
                                                    "\"");
            const glw::Functions &gl = m_context.getRenderContext().getFunctions();
            std::vector<glw::GLenum> props;
            std::vector<const PropValidator *> validators;

            for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(allProperties); ++propNdx)
            {
                if (allProperties[propNdx].validator->isSelected(m_queryTarget.propFlags) &&
                    allProperties[propNdx].validator->isSupported())
                {
                    props.push_back(allProperties[propNdx].prop);
                    validators.push_back(allProperties[propNdx].validator);
                }
            }

            DE_ASSERT(!props.empty());

            queryAndValidateProps(m_testCtx, gl, program.getProgram(), m_queryTarget.interface,
                                  targetResources[targetResourceNdx].c_str(), programDefinition, props, validators);
        }

        break;
    }

    default:
        DE_ASSERT(false);
    }

    return STOP;
}

static bool checkLimit(glw::GLenum pname, int usage, const glw::Functions &gl, tcu::TestLog &log)
{
    if (usage > 0)
    {
        glw::GLint limit = 0;
        gl.getIntegerv(pname, &limit);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query limits");

        log << tcu::TestLog::Message << "\t" << glu::getGettableStateStr(pname) << " = " << limit << ", test requires "
            << usage << tcu::TestLog::EndMessage;

        if (limit < usage)
        {
            log << tcu::TestLog::Message << "\t\tLimit exceeded" << tcu::TestLog::EndMessage;
            return false;
        }
    }

    return true;
}

static bool checkShaderResourceUsage(const ProgramInterfaceDefinition::Program *program,
                                     const ProgramInterfaceDefinition::Shader *shader, const glw::Functions &gl,
                                     tcu::TestLog &log)
{
    const ProgramInterfaceDefinition::ShaderResourceUsage usage = getShaderResourceUsage(program, shader);

    switch (shader->getType())
    {
    case glu::SHADERTYPE_VERTEX:
    {
        const struct
        {
            glw::GLenum pname;
            int usage;
        } restrictions[] = {
            {GL_MAX_VERTEX_ATTRIBS, usage.numInputVectors},
            {GL_MAX_VERTEX_UNIFORM_COMPONENTS, usage.numDefaultBlockUniformComponents},
            {GL_MAX_VERTEX_UNIFORM_VECTORS, usage.numUniformVectors},
            {GL_MAX_VERTEX_UNIFORM_BLOCKS, usage.numUniformBlocks},
            {GL_MAX_VERTEX_OUTPUT_COMPONENTS, usage.numOutputComponents},
            {GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, usage.numSamplers},
            {GL_MAX_VERTEX_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
            {GL_MAX_VERTEX_ATOMIC_COUNTERS, usage.numAtomicCounters},
            {GL_MAX_VERTEX_IMAGE_UNIFORMS, usage.numImages},
            {GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, usage.numCombinedUniformComponents},
            {GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        };

        bool allOk = true;

        log << tcu::TestLog::Message << "Vertex shader:" << tcu::TestLog::EndMessage;
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
            allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

        return allOk;
    }

    case glu::SHADERTYPE_FRAGMENT:
    {
        const struct
        {
            glw::GLenum pname;
            int usage;
        } restrictions[] = {
            {GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, usage.numDefaultBlockUniformComponents},
            {GL_MAX_FRAGMENT_UNIFORM_VECTORS, usage.numUniformVectors},
            {GL_MAX_FRAGMENT_UNIFORM_BLOCKS, usage.numUniformBlocks},
            {GL_MAX_FRAGMENT_INPUT_COMPONENTS, usage.numInputComponents},
            {GL_MAX_TEXTURE_IMAGE_UNITS, usage.numSamplers},
            {GL_MAX_FRAGMENT_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
            {GL_MAX_FRAGMENT_ATOMIC_COUNTERS, usage.numAtomicCounters},
            {GL_MAX_FRAGMENT_IMAGE_UNIFORMS, usage.numImages},
            {GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, usage.numCombinedUniformComponents},
            {GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        };

        bool allOk = true;

        log << tcu::TestLog::Message << "Fragment shader:" << tcu::TestLog::EndMessage;
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
            allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

        return allOk;
    }

    case glu::SHADERTYPE_COMPUTE:
    {
        const struct
        {
            glw::GLenum pname;
            int usage;
        } restrictions[] = {
            {GL_MAX_COMPUTE_UNIFORM_BLOCKS, usage.numUniformBlocks},
            {GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, usage.numSamplers},
            {GL_MAX_COMPUTE_UNIFORM_COMPONENTS, usage.numDefaultBlockUniformComponents},
            {GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
            {GL_MAX_COMPUTE_ATOMIC_COUNTERS, usage.numAtomicCounters},
            {GL_MAX_COMPUTE_IMAGE_UNIFORMS, usage.numImages},
            {GL_MAX_COMBINED_COMPUTE_UNIFORM_COMPONENTS, usage.numCombinedUniformComponents},
            {GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        };

        bool allOk = true;

        log << tcu::TestLog::Message << "Compute shader:" << tcu::TestLog::EndMessage;
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
            allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

        return allOk;
    }

    case glu::SHADERTYPE_GEOMETRY:
    {
        const int totalOutputComponents = program->getGeometryNumOutputVertices() * usage.numOutputComponents;
        const struct
        {
            glw::GLenum pname;
            int usage;
        } restrictions[] = {
            {GL_MAX_GEOMETRY_UNIFORM_COMPONENTS, usage.numDefaultBlockUniformComponents},
            {GL_MAX_GEOMETRY_UNIFORM_BLOCKS, usage.numUniformBlocks},
            {GL_MAX_GEOMETRY_INPUT_COMPONENTS, usage.numInputComponents},
            {GL_MAX_GEOMETRY_OUTPUT_COMPONENTS, usage.numOutputComponents},
            {GL_MAX_GEOMETRY_OUTPUT_VERTICES, (int)program->getGeometryNumOutputVertices()},
            {GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS, totalOutputComponents},
            {GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS, usage.numSamplers},
            {GL_MAX_GEOMETRY_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
            {GL_MAX_GEOMETRY_ATOMIC_COUNTERS, usage.numAtomicCounters},
            {GL_MAX_GEOMETRY_IMAGE_UNIFORMS, usage.numImages},
            {GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        };

        bool allOk = true;

        log << tcu::TestLog::Message << "Geometry shader:" << tcu::TestLog::EndMessage;
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
            allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

        return allOk;
    }

    case glu::SHADERTYPE_TESSELLATION_CONTROL:
    {
        const int totalOutputComponents = program->getTessellationNumOutputPatchVertices() * usage.numOutputComponents +
                                          usage.numPatchOutputComponents;
        const struct
        {
            glw::GLenum pname;
            int usage;
        } restrictions[] = {
            {GL_MAX_PATCH_VERTICES, (int)program->getTessellationNumOutputPatchVertices()},
            {GL_MAX_TESS_PATCH_COMPONENTS, usage.numPatchOutputComponents},
            {GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS, usage.numDefaultBlockUniformComponents},
            {GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS, usage.numUniformBlocks},
            {GL_MAX_TESS_CONTROL_INPUT_COMPONENTS, usage.numInputComponents},
            {GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS, usage.numOutputComponents},
            {GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS, totalOutputComponents},
            {GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, usage.numSamplers},
            {GL_MAX_TESS_CONTROL_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
            {GL_MAX_TESS_CONTROL_ATOMIC_COUNTERS, usage.numAtomicCounters},
            {GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS, usage.numImages},
            {GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        };

        bool allOk = true;

        log << tcu::TestLog::Message << "Tessellation control shader:" << tcu::TestLog::EndMessage;
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
            allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

        return allOk;
    }

    case glu::SHADERTYPE_TESSELLATION_EVALUATION:
    {
        const struct
        {
            glw::GLenum pname;
            int usage;
        } restrictions[] = {
            {GL_MAX_PATCH_VERTICES, (int)program->getTessellationNumOutputPatchVertices()},
            {GL_MAX_TESS_PATCH_COMPONENTS, usage.numPatchInputComponents},
            {GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS, usage.numDefaultBlockUniformComponents},
            {GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, usage.numUniformBlocks},
            {GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS, usage.numInputComponents},
            {GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS, usage.numOutputComponents},
            {GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS, usage.numSamplers},
            {GL_MAX_TESS_EVALUATION_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
            {GL_MAX_TESS_EVALUATION_ATOMIC_COUNTERS, usage.numAtomicCounters},
            {GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, usage.numImages},
            {GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        };

        bool allOk = true;

        log << tcu::TestLog::Message << "Tessellation evaluation shader:" << tcu::TestLog::EndMessage;
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
            allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

        return allOk;
    }

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

static bool checkProgramCombinedResourceUsage(const ProgramInterfaceDefinition::Program *program,
                                              const glw::Functions &gl, tcu::TestLog &log)
{
    const ProgramInterfaceDefinition::ProgramResourceUsage usage = getCombinedProgramResourceUsage(program);

    const struct
    {
        glw::GLenum pname;
        int usage;
    } restrictions[] = {
        {GL_MAX_UNIFORM_BUFFER_BINDINGS, usage.uniformBufferMaxBinding + 1},
        {GL_MAX_UNIFORM_BLOCK_SIZE, usage.uniformBufferMaxSize},
        {GL_MAX_COMBINED_UNIFORM_BLOCKS, usage.numUniformBlocks},
        {GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS, usage.numCombinedVertexUniformComponents},
        {GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS, usage.numCombinedFragmentUniformComponents},
        {GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS, usage.numCombinedGeometryUniformComponents},
        {GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS, usage.numCombinedTessControlUniformComponents},
        {GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS, usage.numCombinedTessEvalUniformComponents},
        {GL_MAX_VARYING_COMPONENTS, usage.numVaryingComponents},
        {GL_MAX_VARYING_VECTORS, usage.numVaryingVectors},
        {GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, usage.numCombinedSamplers},
        {GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES, usage.numCombinedOutputResources},
        {GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, usage.atomicCounterBufferMaxBinding + 1},
        {GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE, usage.atomicCounterBufferMaxSize},
        {GL_MAX_COMBINED_ATOMIC_COUNTER_BUFFERS, usage.numAtomicCounterBuffers},
        {GL_MAX_COMBINED_ATOMIC_COUNTERS, usage.numAtomicCounters},
        {GL_MAX_IMAGE_UNITS, usage.maxImageBinding + 1},
        {GL_MAX_COMBINED_IMAGE_UNIFORMS, usage.numCombinedImages},
        {GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, usage.shaderStorageBufferMaxBinding + 1},
        {GL_MAX_SHADER_STORAGE_BLOCK_SIZE, usage.shaderStorageBufferMaxSize},
        {GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, usage.numShaderStorageBlocks},
        {GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, usage.numXFBInterleavedComponents},
        {GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, usage.numXFBSeparateAttribs},
        {GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS, usage.numXFBSeparateComponents},
        {GL_MAX_DRAW_BUFFERS, usage.fragmentOutputMaxBinding + 1},
    };

    bool allOk = true;

    log << tcu::TestLog::Message << "Program combined:" << tcu::TestLog::EndMessage;
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(restrictions); ++ndx)
        allOk &= checkLimit(restrictions[ndx].pname, restrictions[ndx].usage, gl, log);

    return allOk;
}

void checkProgramResourceUsage(const ProgramInterfaceDefinition::Program *program, const glw::Functions &gl,
                               tcu::TestLog &log)
{
    bool limitExceeded = false;

    for (int shaderNdx = 0; shaderNdx < (int)program->getShaders().size(); ++shaderNdx)
        limitExceeded |= !checkShaderResourceUsage(program, program->getShaders()[shaderNdx], gl, log);

    limitExceeded |= !checkProgramCombinedResourceUsage(program, gl, log);

    if (limitExceeded)
    {
        log << tcu::TestLog::Message << "One or more resource limits exceeded" << tcu::TestLog::EndMessage;
        throw tcu::NotSupportedError("one or more resource limits exceeded");
    }
}

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