/*-------------------------------------------------------------------------
 * 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 tests.
 *//*--------------------------------------------------------------------*/

#include "es31fProgramInterfaceQueryTests.hpp"
#include "es31fProgramInterfaceQueryTestCase.hpp"
#include "es31fProgramInterfaceDefinition.hpp"
#include "es31fProgramInterfaceDefinitionUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"
#include "gluShaderProgram.hpp"
#include "gluVarTypeUtil.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deRandom.hpp"
#include "deString.h"
#include "deStringUtil.hpp"
#include "deSharedPtr.hpp"
#include "deUniquePtr.hpp"
#include "deSTLUtil.hpp"
#include "deArrayUtil.hpp"

#include <set>
#include <map>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

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 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())
    {
        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 std::string convertGLTypeNameToTestName(const char *glName)
{
    // vectors and matrices are fine as is
    {
        if (deStringBeginsWith(glName, "vec") == true || deStringBeginsWith(glName, "ivec") == true ||
            deStringBeginsWith(glName, "uvec") == true || deStringBeginsWith(glName, "bvec") == true ||
            deStringBeginsWith(glName, "mat") == true)
            return std::string(glName);
    }

    // convert camel case to use underscore
    {
        std::ostringstream buf;
        std::istringstream name(glName);
        bool mergeNextToken        = false;
        bool previousTokenWasDigit = false;

        while (!name.eof())
        {
            std::ostringstream token;

            while (name.peek() != EOF)
            {
                if ((de::isDigit((char)name.peek()) || de::isUpper((char)name.peek())) && token.tellp())
                    break;

                token << de::toLower((char)name.get());
            }

            if (buf.str().empty() || mergeNextToken)
                buf << token.str();
            else
                buf << '_' << token.str();

            // Single char causes next char to be merged (don't split initialisms or acronyms) unless it is 'D' after a number (split to ..._2d_acronym_aa
            mergeNextToken = false;
            if (token.tellp() == (std::streamoff)1)
            {
                if (!previousTokenWasDigit || token.str()[0] != 'd')
                    mergeNextToken = true;

                previousTokenWasDigit = de::isDigit(token.str()[0]);
            }
            else
                previousTokenWasDigit = false;
        }

        return buf.str();
    }
}

static glw::GLenum getProgramInterfaceGLEnum(ProgramInterface interface)
{
    static const glw::GLenum s_enums[] = {
        GL_UNIFORM,                    // PROGRAMINTERFACE_UNIFORM
        GL_UNIFORM_BLOCK,              // PROGRAMINTERFACE_UNIFORM_BLOCK
        GL_ATOMIC_COUNTER_BUFFER,      // PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER
        GL_PROGRAM_INPUT,              // PROGRAMINTERFACE_PROGRAM_INPUT
        GL_PROGRAM_OUTPUT,             // PROGRAMINTERFACE_PROGRAM_OUTPUT
        GL_TRANSFORM_FEEDBACK_VARYING, // PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING
        GL_BUFFER_VARIABLE,            // PROGRAMINTERFACE_BUFFER_VARIABLE
        GL_SHADER_STORAGE_BLOCK,       // PROGRAMINTERFACE_SHADER_STORAGE_BLOCK
    };

    return de::getSizedArrayElement<PROGRAMINTERFACE_LAST>(s_enums, interface);
}

static glu::ShaderType getShaderMaskFirstStage(uint32_t mask)
{
    if (mask & (1u << glu::SHADERTYPE_COMPUTE))
        return glu::SHADERTYPE_COMPUTE;

    if (mask & (1u << glu::SHADERTYPE_VERTEX))
        return glu::SHADERTYPE_VERTEX;

    if (mask & (1u << glu::SHADERTYPE_TESSELLATION_CONTROL))
        return glu::SHADERTYPE_TESSELLATION_CONTROL;

    if (mask & (1u << glu::SHADERTYPE_TESSELLATION_EVALUATION))
        return glu::SHADERTYPE_TESSELLATION_EVALUATION;

    if (mask & (1u << glu::SHADERTYPE_GEOMETRY))
        return glu::SHADERTYPE_GEOMETRY;

    if (mask & (1u << glu::SHADERTYPE_FRAGMENT))
        return glu::SHADERTYPE_FRAGMENT;

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

static glu::ShaderType getShaderMaskLastStage(uint32_t mask)
{
    if (mask & (1u << glu::SHADERTYPE_FRAGMENT))
        return glu::SHADERTYPE_FRAGMENT;

    if (mask & (1u << glu::SHADERTYPE_GEOMETRY))
        return glu::SHADERTYPE_GEOMETRY;

    if (mask & (1u << glu::SHADERTYPE_TESSELLATION_EVALUATION))
        return glu::SHADERTYPE_TESSELLATION_EVALUATION;

    if (mask & (1u << glu::SHADERTYPE_TESSELLATION_CONTROL))
        return glu::SHADERTYPE_TESSELLATION_CONTROL;

    if (mask & (1u << glu::SHADERTYPE_VERTEX))
        return glu::SHADERTYPE_VERTEX;

    if (mask & (1u << glu::SHADERTYPE_COMPUTE))
        return glu::SHADERTYPE_COMPUTE;

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

static bool checkSupport(Context &ctx)
{
    auto ctxType = ctx.getRenderContext().getType();
    return contextSupports(ctxType, glu::ApiType::es(3, 2)) || contextSupports(ctxType, glu::ApiType::core(4, 5));
}

static std::string specializeShader(Context &context, const char *code)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(context.getRenderContext().getType());
    std::map<std::string, std::string> specializationMap;

    specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

    return tcu::StringTemplate(code).specialize(specializationMap);
}

namespace ResourceDefinition
{

class Node
{
public:
    enum NodeType
    {
        TYPE_PROGRAM = 0,
        TYPE_SHADER,
        TYPE_DEFAULT_BLOCK,
        TYPE_VARIABLE,
        TYPE_INTERFACE_BLOCK,
        TYPE_ARRAY_ELEMENT,
        TYPE_STRUCT_MEMBER,
        TYPE_STORAGE_QUALIFIER,
        TYPE_LAYOUT_QUALIFIER,
        TYPE_SHADER_SET,
        TYPE_INTERPOLATION_QUALIFIER,
        TYPE_TRANSFORM_FEEDBACK_TARGET,

        TYPE_LAST
    };

    typedef de::SharedPtr<const Node> SharedPtr;

    Node(NodeType type, const SharedPtr &enclosingNode) : m_type(type), m_enclosingNode(enclosingNode)
    {
        DE_ASSERT(type < TYPE_LAST);
    }
    virtual ~Node(void)
    {
    }

    inline const Node *getEnclosingNode(void) const
    {
        return m_enclosingNode.get();
    }
    inline NodeType getType(void) const
    {
        return m_type;
    }

private:
    const NodeType m_type;
    const SharedPtr m_enclosingNode;
};

class Program : public Node
{
public:
    Program(bool separable = false) : Node(TYPE_PROGRAM, SharedPtr()), m_separable(separable)
    {
    }

    const bool m_separable;
};

class Shader : public Node
{
public:
    Shader(const SharedPtr &enclosingNode, glu::ShaderType type, glu::GLSLVersion version)
        : Node(TYPE_SHADER, enclosingNode)
        , m_type(type)
        , m_version(version)
    {
        DE_ASSERT(enclosingNode->getType() == TYPE_PROGRAM);
        DE_ASSERT(type < glu::SHADERTYPE_LAST);
    }

    const glu::ShaderType m_type;
    const glu::GLSLVersion m_version;
};

class DefaultBlock : public Node
{
public:
    DefaultBlock(const SharedPtr &enclosing) : Node(TYPE_DEFAULT_BLOCK, enclosing)
    {
        // enclosed by the shader
        DE_ASSERT(enclosing->getType() == TYPE_SHADER || enclosing->getType() == TYPE_SHADER_SET);
    }
};

class StorageQualifier : public Node
{
public:
    StorageQualifier(const SharedPtr &enclosing, glu::Storage storage)
        : Node(TYPE_STORAGE_QUALIFIER, enclosing)
        , m_storage(storage)
    {
        // not a part of any block
        DE_ASSERT(enclosing->getType() == TYPE_DEFAULT_BLOCK);
    }

    const glu::Storage m_storage;
};

class Variable : public Node
{
public:
    Variable(const SharedPtr &enclosing, glu::DataType dataType) : Node(TYPE_VARIABLE, enclosing), m_dataType(dataType)
    {
        DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER || enclosing->getType() == TYPE_LAYOUT_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERFACE_BLOCK || enclosing->getType() == TYPE_ARRAY_ELEMENT ||
                  enclosing->getType() == TYPE_STRUCT_MEMBER || enclosing->getType() == TYPE_TRANSFORM_FEEDBACK_TARGET);
    }

    const glu::DataType m_dataType;
};

class InterfaceBlock : public Node
{
public:
    InterfaceBlock(const SharedPtr &enclosing, bool named) : Node(TYPE_INTERFACE_BLOCK, enclosing), m_named(named)
    {
        // Must be storage qualified
        const Node *storageNode = enclosing.get();
        while (storageNode->getType() == TYPE_ARRAY_ELEMENT || storageNode->getType() == TYPE_LAYOUT_QUALIFIER)
        {
            storageNode = storageNode->getEnclosingNode();
        }

        DE_ASSERT(storageNode->getType() == TYPE_STORAGE_QUALIFIER);
        DE_UNREF(storageNode);
    }

    const bool m_named;
};

class ArrayElement : public Node
{
public:
    ArrayElement(const SharedPtr &enclosing, int arraySize = DEFAULT_SIZE)
        : Node(TYPE_ARRAY_ELEMENT, enclosing)
        , m_arraySize(arraySize)
    {
        DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER || enclosing->getType() == TYPE_LAYOUT_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERFACE_BLOCK || enclosing->getType() == TYPE_ARRAY_ELEMENT ||
                  enclosing->getType() == TYPE_STRUCT_MEMBER || enclosing->getType() == TYPE_TRANSFORM_FEEDBACK_TARGET);
    }

    const int m_arraySize;

    enum
    {
        DEFAULT_SIZE  = -1,
        UNSIZED_ARRAY = -2,
    };
};

class StructMember : public Node
{
public:
    StructMember(const SharedPtr &enclosing) : Node(TYPE_STRUCT_MEMBER, enclosing)
    {
        DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER || enclosing->getType() == TYPE_LAYOUT_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERFACE_BLOCK || enclosing->getType() == TYPE_ARRAY_ELEMENT ||
                  enclosing->getType() == TYPE_STRUCT_MEMBER || enclosing->getType() == TYPE_TRANSFORM_FEEDBACK_TARGET);
    }
};

class LayoutQualifier : public Node
{
public:
    LayoutQualifier(const SharedPtr &enclosing, const glu::Layout &layout)
        : Node(TYPE_LAYOUT_QUALIFIER, enclosing)
        , m_layout(layout)
    {
        DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER || enclosing->getType() == TYPE_LAYOUT_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER || enclosing->getType() == TYPE_DEFAULT_BLOCK ||
                  enclosing->getType() == TYPE_INTERFACE_BLOCK);
    }

    const glu::Layout m_layout;
};

class InterpolationQualifier : public Node
{
public:
    InterpolationQualifier(const SharedPtr &enclosing, const glu::Interpolation &interpolation)
        : Node(TYPE_INTERPOLATION_QUALIFIER, enclosing)
        , m_interpolation(interpolation)
    {
        DE_ASSERT(enclosing->getType() == TYPE_STORAGE_QUALIFIER || enclosing->getType() == TYPE_LAYOUT_QUALIFIER ||
                  enclosing->getType() == TYPE_INTERPOLATION_QUALIFIER || enclosing->getType() == TYPE_DEFAULT_BLOCK ||
                  enclosing->getType() == TYPE_INTERFACE_BLOCK);
    }

    const glu::Interpolation m_interpolation;
};

class ShaderSet : public Node
{
public:
    ShaderSet(const SharedPtr &enclosing, glu::GLSLVersion version);
    ShaderSet(const SharedPtr &enclosing, glu::GLSLVersion version, uint32_t stagesPresentBits,
              uint32_t stagesReferencingBits);

    void setStage(glu::ShaderType type, bool referencing);
    bool isStagePresent(glu::ShaderType stage) const;
    bool isStageReferencing(glu::ShaderType stage) const;

    uint32_t getReferencingMask(void) const;

    const glu::GLSLVersion m_version;

private:
    bool m_stagePresent[glu::SHADERTYPE_LAST];
    bool m_stageReferencing[glu::SHADERTYPE_LAST];
};

ShaderSet::ShaderSet(const SharedPtr &enclosing, glu::GLSLVersion version)
    : Node(TYPE_SHADER_SET, enclosing)
    , m_version(version)
{
    DE_ASSERT(enclosing->getType() == TYPE_PROGRAM);

    deMemset(m_stagePresent, 0, sizeof(m_stagePresent));
    deMemset(m_stageReferencing, 0, sizeof(m_stageReferencing));
}

ShaderSet::ShaderSet(const SharedPtr &enclosing, glu::GLSLVersion version, uint32_t stagesPresentBits,
                     uint32_t stagesReferencingBits)
    : Node(TYPE_SHADER_SET, enclosing)
    , m_version(version)
{
    for (uint32_t stageNdx = 0; stageNdx < glu::SHADERTYPE_LAST; ++stageNdx)
    {
        const uint32_t stageMask    = (1u << stageNdx);
        const bool stagePresent     = (stagesPresentBits & stageMask) != 0;
        const bool stageReferencing = (stagesReferencingBits & stageMask) != 0;

        DE_ASSERT(stagePresent || !stageReferencing);

        m_stagePresent[stageNdx]     = stagePresent;
        m_stageReferencing[stageNdx] = stageReferencing;
    }
}

void ShaderSet::setStage(glu::ShaderType type, bool referencing)
{
    DE_ASSERT(type < glu::SHADERTYPE_LAST);
    m_stagePresent[type]     = true;
    m_stageReferencing[type] = referencing;
}

bool ShaderSet::isStagePresent(glu::ShaderType stage) const
{
    DE_ASSERT(stage < glu::SHADERTYPE_LAST);
    return m_stagePresent[stage];
}

bool ShaderSet::isStageReferencing(glu::ShaderType stage) const
{
    DE_ASSERT(stage < glu::SHADERTYPE_LAST);
    return m_stageReferencing[stage];
}

uint32_t ShaderSet::getReferencingMask(void) const
{
    uint32_t mask = 0;
    for (uint32_t stage = 0; stage < glu::SHADERTYPE_LAST; ++stage)
    {
        if (m_stageReferencing[stage])
            mask |= (1u << stage);
    }
    return mask;
}

class TransformFeedbackTarget : public Node
{
public:
    TransformFeedbackTarget(const SharedPtr &enclosing, const char *builtinVarName = DE_NULL)
        : Node(TYPE_TRANSFORM_FEEDBACK_TARGET, enclosing)
        , m_builtinVarName(builtinVarName)
    {
    }

    const char *const m_builtinVarName;
};

} // namespace ResourceDefinition

static glu::Precision getDataTypeDefaultPrecision(const glu::DataType &type)
{
    if (glu::isDataTypeBoolOrBVec(type))
        return glu::PRECISION_LAST;
    else if (glu::isDataTypeScalarOrVector(type) || glu::isDataTypeMatrix(type))
        return glu::PRECISION_HIGHP;
    else if (glu::isDataTypeSampler(type))
        return glu::PRECISION_HIGHP;
    else if (glu::isDataTypeImage(type))
        return glu::PRECISION_HIGHP;
    else if (type == glu::TYPE_UINT_ATOMIC_COUNTER)
        return glu::PRECISION_HIGHP;

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

static de::MovePtr<ProgramInterfaceDefinition::Program> generateProgramDefinitionFromResource(
    const ResourceDefinition::Node *resource)
{
    de::MovePtr<ProgramInterfaceDefinition::Program> program(new ProgramInterfaceDefinition::Program());
    const ResourceDefinition::Node *head = resource;

    if (head->getType() == ResourceDefinition::Node::TYPE_VARIABLE)
    {
        DE_ASSERT(dynamic_cast<const ResourceDefinition::Variable *>(resource));

        enum BindingType
        {
            BINDING_VARIABLE,
            BINDING_INTERFACE_BLOCK,
            BINDING_DEFAULT_BLOCK
        };

        int structNdx                 = 0;
        int autoAssignArraySize       = 0;
        const glu::DataType basicType = static_cast<const ResourceDefinition::Variable *>(resource)->m_dataType;
        BindingType boundObject       = BINDING_VARIABLE;
        glu::VariableDeclaration variable(glu::VarType(basicType, getDataTypeDefaultPrecision(basicType)), "target");
        glu::InterfaceBlock interfaceBlock;
        ProgramInterfaceDefinition::DefaultBlock defaultBlock;
        std::vector<std::string> feedbackTargetVaryingPath;
        bool feedbackTargetSet = false;

        // image specific
        if (glu::isDataTypeImage(basicType))
        {
            variable.memoryAccessQualifierBits |= glu::MEMORYACCESSQUALIFIER_READONLY_BIT;
            variable.layout.binding = 1;

            if (basicType >= glu::TYPE_IMAGE_2D && basicType <= glu::TYPE_IMAGE_3D)
                variable.layout.format = glu::FORMATLAYOUT_RGBA8;
            else if (basicType >= glu::TYPE_INT_IMAGE_2D && basicType <= glu::TYPE_INT_IMAGE_3D)
                variable.layout.format = glu::FORMATLAYOUT_RGBA8I;
            else if (basicType >= glu::TYPE_UINT_IMAGE_2D && basicType <= glu::TYPE_UINT_IMAGE_3D)
                variable.layout.format = glu::FORMATLAYOUT_RGBA8UI;
            else
                DE_ASSERT(false);
        }

        // atomic counter specific
        if (basicType == glu::TYPE_UINT_ATOMIC_COUNTER)
            variable.layout.binding = 1;

        for (head = head->getEnclosingNode(); head; head = head->getEnclosingNode())
        {
            if (head->getType() == ResourceDefinition::Node::TYPE_STORAGE_QUALIFIER)
            {
                const ResourceDefinition::StorageQualifier *qualifier =
                    static_cast<const ResourceDefinition::StorageQualifier *>(head);

                DE_ASSERT(dynamic_cast<const ResourceDefinition::StorageQualifier *>(head));

                if (boundObject == BINDING_VARIABLE)
                {
                    DE_ASSERT(variable.storage == glu::STORAGE_LAST);
                    variable.storage = qualifier->m_storage;
                }
                else if (boundObject == BINDING_INTERFACE_BLOCK)
                {
                    DE_ASSERT(interfaceBlock.storage == glu::STORAGE_LAST);
                    interfaceBlock.storage = qualifier->m_storage;
                }
                else
                    DE_ASSERT(false);
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_LAYOUT_QUALIFIER)
            {
                const ResourceDefinition::LayoutQualifier *qualifier =
                    static_cast<const ResourceDefinition::LayoutQualifier *>(head);
                glu::Layout *targetLayout = DE_NULL;

                DE_ASSERT(dynamic_cast<const ResourceDefinition::LayoutQualifier *>(head));

                if (boundObject == BINDING_VARIABLE)
                    targetLayout = &variable.layout;
                else if (boundObject == BINDING_INTERFACE_BLOCK)
                    targetLayout = &interfaceBlock.layout;
                else
                    DE_ASSERT(false);

                if (qualifier->m_layout.location != -1)
                    targetLayout->location = qualifier->m_layout.location;

                if (qualifier->m_layout.binding != -1)
                    targetLayout->binding = qualifier->m_layout.binding;

                if (qualifier->m_layout.offset != -1)
                    targetLayout->offset = qualifier->m_layout.offset;

                if (qualifier->m_layout.format != glu::FORMATLAYOUT_LAST)
                    targetLayout->format = qualifier->m_layout.format;

                if (qualifier->m_layout.matrixOrder != glu::MATRIXORDER_LAST)
                    targetLayout->matrixOrder = qualifier->m_layout.matrixOrder;
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_INTERPOLATION_QUALIFIER)
            {
                const ResourceDefinition::InterpolationQualifier *qualifier =
                    static_cast<const ResourceDefinition::InterpolationQualifier *>(head);

                DE_ASSERT(dynamic_cast<const ResourceDefinition::InterpolationQualifier *>(head));

                if (boundObject == BINDING_VARIABLE)
                    variable.interpolation = qualifier->m_interpolation;
                else
                    DE_ASSERT(false);
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_ARRAY_ELEMENT)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::ArrayElement *>(head));

                const ResourceDefinition::ArrayElement *arrayElement =
                    static_cast<const ResourceDefinition::ArrayElement *>(head);
                int arraySize;

                // Vary array size per level
                if (arrayElement->m_arraySize == ResourceDefinition::ArrayElement::DEFAULT_SIZE)
                {
                    if (--autoAssignArraySize <= 1)
                        autoAssignArraySize = 3;

                    arraySize = autoAssignArraySize;
                }
                else if (arrayElement->m_arraySize == ResourceDefinition::ArrayElement::UNSIZED_ARRAY)
                    arraySize = glu::VarType::UNSIZED_ARRAY;
                else
                    arraySize = arrayElement->m_arraySize;

                if (boundObject == BINDING_VARIABLE)
                    variable.varType = glu::VarType(variable.varType, arraySize);
                else if (boundObject == BINDING_INTERFACE_BLOCK)
                    interfaceBlock.dimensions.push_back(arraySize);
                else
                    DE_ASSERT(false);

                if (feedbackTargetSet)
                    feedbackTargetVaryingPath.back().append("[0]");
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::StructMember *>(head));
                DE_ASSERT(boundObject == BINDING_VARIABLE);

                // Struct members cannot contain any qualifiers except precision
                DE_ASSERT(variable.interpolation == glu::INTERPOLATION_LAST);
                DE_ASSERT(variable.layout == glu::Layout());
                DE_ASSERT(variable.memoryAccessQualifierBits == 0);
                DE_ASSERT(variable.storage == glu::STORAGE_LAST);

                {
                    glu::StructType *structPtr =
                        new glu::StructType(("StructType" + de::toString(structNdx++)).c_str());
                    structPtr->addMember(variable.name.c_str(), variable.varType);

                    variable = glu::VariableDeclaration(glu::VarType(structPtr), "target");
                }

                if (feedbackTargetSet)
                    feedbackTargetVaryingPath.push_back("target");
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::InterfaceBlock *>(head));
                DE_ASSERT(boundObject == BINDING_VARIABLE);

                const bool named = static_cast<const ResourceDefinition::InterfaceBlock *>(head)->m_named;

                boundObject = BINDING_INTERFACE_BLOCK;

                interfaceBlock.interfaceName = "TargetInterface";
                interfaceBlock.instanceName  = (named) ? ("targetInstance") : ("");
                interfaceBlock.variables.push_back(variable);

                if (feedbackTargetSet && !interfaceBlock.instanceName.empty())
                    feedbackTargetVaryingPath.push_back(interfaceBlock.interfaceName);
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::DefaultBlock *>(head));
                DE_ASSERT(boundObject == BINDING_VARIABLE || boundObject == BINDING_INTERFACE_BLOCK);

                if (boundObject == BINDING_VARIABLE)
                    defaultBlock.variables.push_back(variable);
                else if (boundObject == BINDING_INTERFACE_BLOCK)
                    defaultBlock.interfaceBlocks.push_back(interfaceBlock);
                else
                    DE_ASSERT(false);

                boundObject = BINDING_DEFAULT_BLOCK;
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_SHADER)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::Shader *>(head));

                const ResourceDefinition::Shader *shaderDef = static_cast<const ResourceDefinition::Shader *>(head);
                ProgramInterfaceDefinition::Shader *shader =
                    program->addShader(shaderDef->m_type, shaderDef->m_version);

                shader->getDefaultBlock() = defaultBlock;
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_SHADER_SET)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::ShaderSet *>(head));

                const ResourceDefinition::ShaderSet *shaderDef =
                    static_cast<const ResourceDefinition::ShaderSet *>(head);

                for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; ++shaderType)
                {
                    if (shaderDef->isStagePresent((glu::ShaderType)shaderType))
                    {
                        ProgramInterfaceDefinition::Shader *shader =
                            program->addShader((glu::ShaderType)shaderType, shaderDef->m_version);

                        if (shaderDef->isStageReferencing((glu::ShaderType)shaderType))
                            shader->getDefaultBlock() = defaultBlock;
                    }
                }
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_PROGRAM)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::Program *>(head));

                const ResourceDefinition::Program *programDef = static_cast<const ResourceDefinition::Program *>(head);

                program->setSeparable(programDef->m_separable);

                DE_ASSERT(feedbackTargetSet == !feedbackTargetVaryingPath.empty());
                if (!feedbackTargetVaryingPath.empty())
                {
                    std::ostringstream buf;

                    for (std::vector<std::string>::reverse_iterator it = feedbackTargetVaryingPath.rbegin();
                         it != feedbackTargetVaryingPath.rend(); ++it)
                    {
                        if (it != feedbackTargetVaryingPath.rbegin())
                            buf << ".";
                        buf << *it;
                    }

                    program->addTransformFeedbackVarying(buf.str());
                    program->setTransformFeedbackMode(GL_INTERLEAVED_ATTRIBS);
                }
                break;
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::TransformFeedbackTarget *>(head));

                const ResourceDefinition::TransformFeedbackTarget *feedbackTarget =
                    static_cast<const ResourceDefinition::TransformFeedbackTarget *>(head);

                DE_ASSERT(feedbackTarget->m_builtinVarName == DE_NULL);
                DE_UNREF(feedbackTarget);

                feedbackTargetSet = true;
                feedbackTargetVaryingPath.push_back(variable.name);
            }
            else
            {
                DE_ASSERT(false);
                break;
            }
        }
    }
    else if (head->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK ||
             head->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
    {
        const char *feedbackTargetVaryingName = DE_NULL;

        // empty default block

        for (; head; head = head->getEnclosingNode())
        {
            if (head->getType() == ResourceDefinition::Node::TYPE_SHADER)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::Shader *>(head));

                const ResourceDefinition::Shader *shaderDef = static_cast<const ResourceDefinition::Shader *>(head);

                program->addShader(shaderDef->m_type, shaderDef->m_version);
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_SHADER_SET)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::ShaderSet *>(head));

                const ResourceDefinition::ShaderSet *shaderDef =
                    static_cast<const ResourceDefinition::ShaderSet *>(head);

                for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; ++shaderType)
                    if (shaderDef->isStagePresent((glu::ShaderType)shaderType))
                        program->addShader((glu::ShaderType)shaderType, shaderDef->m_version);
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_PROGRAM)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::Program *>(head));

                const ResourceDefinition::Program *programDef = static_cast<const ResourceDefinition::Program *>(head);

                program->setSeparable(programDef->m_separable);
                if (feedbackTargetVaryingName)
                {
                    program->addTransformFeedbackVarying(std::string(feedbackTargetVaryingName));
                    program->setTransformFeedbackMode(GL_INTERLEAVED_ATTRIBS);
                }
                break;
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::TransformFeedbackTarget *>(head));

                const ResourceDefinition::TransformFeedbackTarget *feedbackTarget =
                    static_cast<const ResourceDefinition::TransformFeedbackTarget *>(head);

                DE_ASSERT(feedbackTarget->m_builtinVarName != DE_NULL);

                feedbackTargetVaryingName = feedbackTarget->m_builtinVarName;
            }
            else if (head->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK)
            {
            }
            else
            {
                DE_ASSERT(false);
                break;
            }
        }
    }

    if (program->hasStage(glu::SHADERTYPE_GEOMETRY))
        program->setGeometryNumOutputVertices(1);
    if (program->hasStage(glu::SHADERTYPE_TESSELLATION_CONTROL) ||
        program->hasStage(glu::SHADERTYPE_TESSELLATION_EVALUATION))
        program->setTessellationNumOutputPatchVertices(1);

    return program;
}

static void checkAndLogProgram(const glu::ShaderProgram &program,
                               const ProgramInterfaceDefinition::Program *programDefinition, const glw::Functions &gl,
                               tcu::TestLog &log)
{
    const tcu::ScopedLogSection section(log, "Program", "Program");

    log << program;
    if (!program.isOk())
    {
        log << tcu::TestLog::Message << "Program build failed, checking if program exceeded implementation limits"
            << tcu::TestLog::EndMessage;
        checkProgramResourceUsage(programDefinition, gl, log);

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

// Resource list query case

class ResourceListTestCase : public TestCase
{
public:
    ResourceListTestCase(Context &context, const ResourceDefinition::Node::SharedPtr &targetResource,
                         ProgramInterface interface, const char *name = DE_NULL);
    ~ResourceListTestCase(void);

protected:
    void init(void);
    void deinit(void);
    IterateResult iterate(void);

    void queryResourceList(std::vector<std::string> &dst, glw::GLuint program);
    bool verifyResourceList(const std::vector<std::string> &resourceList,
                            const std::vector<std::string> &expectedResources);
    bool verifyResourceIndexQuery(const std::vector<std::string> &resourceList,
                                  const std::vector<std::string> &referenceResources, glw::GLuint program);
    bool verifyMaxNameLength(const std::vector<std::string> &referenceResourceList, glw::GLuint program);

    static std::string genTestCaseName(ProgramInterface interface, const ResourceDefinition::Node *);
    static bool isArrayedInterface(ProgramInterface interface, uint32_t stageBits);

    const ProgramInterface m_programInterface;
    ResourceDefinition::Node::SharedPtr m_targetResource;
    ProgramInterfaceDefinition::Program *m_programDefinition;
};

ResourceListTestCase::ResourceListTestCase(Context &context, const ResourceDefinition::Node::SharedPtr &targetResource,
                                           ProgramInterface interface, const char *name)
    : TestCase(context, (name == DE_NULL) ? (genTestCaseName(interface, targetResource.get()).c_str()) : (name), "")
    , m_programInterface(interface)
    , m_targetResource(targetResource)
    , m_programDefinition(DE_NULL)
{
    // GL_ATOMIC_COUNTER_BUFFER: no resource names
    DE_ASSERT(m_programInterface != PROGRAMINTERFACE_ATOMIC_COUNTER_BUFFER);
}

ResourceListTestCase::~ResourceListTestCase(void)
{
    deinit();
}

void ResourceListTestCase::init(void)
{
    m_programDefinition           = generateProgramDefinitionFromResource(m_targetResource.get()).release();
    const bool supportsES32orGL45 = checkSupport(m_context);

    if ((m_programDefinition->hasStage(glu::SHADERTYPE_TESSELLATION_CONTROL) ||
         m_programDefinition->hasStage(glu::SHADERTYPE_TESSELLATION_EVALUATION)) &&
        !supportsES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
    {
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    }
    if (m_programDefinition->hasStage(glu::SHADERTYPE_GEOMETRY) && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
    {
        throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");
    }
    if (programContainsIOBlocks(m_programDefinition) && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_shader_io_blocks"))
    {
        throw tcu::NotSupportedError("Test requires GL_EXT_shader_io_blocks extension");
    }
}

void ResourceListTestCase::deinit(void)
{
    m_targetResource.clear();

    delete m_programDefinition;
    m_programDefinition = DE_NULL;
}

ResourceListTestCase::IterateResult ResourceListTestCase::iterate(void)
{
    const glu::ShaderProgram program(m_context.getRenderContext(),
                                     generateProgramInterfaceProgramSources(m_programDefinition));

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_programDefinition, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // Check resource list
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "ResourceList", "Resource list");
        std::vector<std::string> resourceList;
        std::vector<std::string> expectedResources;

        queryResourceList(resourceList, program.getProgram());
        expectedResources = getProgramInterfaceResourceList(m_programDefinition, m_programInterface);

        // verify the list and the expected list match

        if (!verifyResourceList(resourceList, expectedResources))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid resource list");

        // verify GetProgramResourceIndex() matches the indices of the list

        if (!verifyResourceIndexQuery(resourceList, expectedResources, program.getProgram()))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GetProgramResourceIndex returned unexpected values");

        // Verify MAX_NAME_LENGTH
        if (!verifyMaxNameLength(resourceList, program.getProgram()))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "MAX_NAME_LENGTH invalid");
    }

    return STOP;
}

void ResourceListTestCase::queryResourceList(std::vector<std::string> &dst, glw::GLuint program)
{
    const glw::Functions &gl           = m_context.getRenderContext().getFunctions();
    const glw::GLenum programInterface = getProgramInterfaceGLEnum(m_programInterface);
    glw::GLint numActiveResources      = 0;
    glw::GLint maxNameLength           = 0;
    std::vector<char> buffer;

    m_testCtx.getLog() << tcu::TestLog::Message << "Querying " << glu::getProgramInterfaceName(programInterface)
                       << " interface:" << tcu::TestLog::EndMessage;

    gl.getProgramInterfaceiv(program, programInterface, GL_ACTIVE_RESOURCES, &numActiveResources);
    gl.getProgramInterfaceiv(program, programInterface, GL_MAX_NAME_LENGTH, &maxNameLength);
    GLU_EXPECT_NO_ERROR(gl.getError(), "query interface");

    m_testCtx.getLog() << tcu::TestLog::Message << "\tGL_ACTIVE_RESOURCES = " << numActiveResources << "\n"
                       << "\tGL_MAX_NAME_LENGTH = " << maxNameLength << tcu::TestLog::EndMessage;

    m_testCtx.getLog() << tcu::TestLog::Message << "Querying all active resources" << tcu::TestLog::EndMessage;

    buffer.resize(maxNameLength + 1, '\0');

    for (int resourceNdx = 0; resourceNdx < numActiveResources; ++resourceNdx)
    {
        glw::GLint written = 0;

        gl.getProgramResourceName(program, programInterface, resourceNdx, maxNameLength, &written, &buffer[0]);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query resource name");

        dst.push_back(std::string(&buffer[0], written));
    }
}

bool ResourceListTestCase::verifyResourceList(const std::vector<std::string> &resourceList,
                                              const std::vector<std::string> &expectedResources)
{
    bool error = false;

    // Log and compare resource lists

    m_testCtx.getLog() << tcu::TestLog::Message << "GL returned resources:" << tcu::TestLog::EndMessage;

    for (int ndx = 0; ndx < (int)resourceList.size(); ++ndx)
    {
        // unusedZero is a uniform that may be added by
        // generateProgramInterfaceProgramSources.  Omit it here to avoid
        // confusion about the output.
        if (resourceList[ndx] != getUnusedZeroUniformName())
            m_testCtx.getLog() << tcu::TestLog::Message << "\t" << ndx << ": " << resourceList[ndx]
                               << tcu::TestLog::EndMessage;
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Expected list of resources:" << tcu::TestLog::EndMessage;

    for (int ndx = 0; ndx < (int)expectedResources.size(); ++ndx)
        m_testCtx.getLog() << tcu::TestLog::Message << "\t" << ndx << ": " << expectedResources[ndx]
                           << tcu::TestLog::EndMessage;

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying resource list contents." << tcu::TestLog::EndMessage;

    for (int ndx = 0; ndx < (int)expectedResources.size(); ++ndx)
    {
        if (!de::contains(resourceList.begin(), resourceList.end(), expectedResources[ndx]))
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, resource list did not contain active resource "
                               << expectedResources[ndx] << tcu::TestLog::EndMessage;
            error = true;
        }
    }

    for (int ndx = 0; ndx < (int)resourceList.size(); ++ndx)
    {
        if (!de::contains(expectedResources.begin(), expectedResources.end(), resourceList[ndx]))
        {
            // Ignore all builtin variables or the variable unusedZero,
            // mismatch causes errors otherwise.  unusedZero is a uniform that
            // may be added by generateProgramInterfaceProgramSources.
            if (deStringBeginsWith(resourceList[ndx].c_str(), "gl_") == false &&
                resourceList[ndx] != getUnusedZeroUniformName())
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, resource list contains unexpected resource name "
                                   << resourceList[ndx] << tcu::TestLog::EndMessage;
                error = true;
            }
            else
                m_testCtx.getLog() << tcu::TestLog::Message << "Note, resource list contains unknown built-in "
                                   << resourceList[ndx] << ". This variable is ignored." << tcu::TestLog::EndMessage;
        }
    }

    return !error;
}

bool ResourceListTestCase::verifyResourceIndexQuery(const std::vector<std::string> &resourceList,
                                                    const std::vector<std::string> &referenceResources,
                                                    glw::GLuint program)
{
    const glw::Functions &gl           = m_context.getRenderContext().getFunctions();
    const glw::GLenum programInterface = getProgramInterfaceGLEnum(m_programInterface);
    bool error                         = false;

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Verifying GetProgramResourceIndex returns correct indices for resource names."
                       << tcu::TestLog::EndMessage;

    for (int ndx = 0; ndx < (int)referenceResources.size(); ++ndx)
    {
        const glw::GLuint index =
            gl.getProgramResourceIndex(program, programInterface, referenceResources[ndx].c_str());
        GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

        if (index == GL_INVALID_INDEX)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, for active resource \"" << referenceResources[ndx]
                               << "\" got index GL_INVALID_INDEX." << tcu::TestLog::EndMessage;
            error = true;
        }
        else if ((int)index >= (int)resourceList.size())
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, for active resource \"" << referenceResources[ndx]
                               << "\" got index " << index << " (larger or equal to GL_ACTIVE_RESOURCES)."
                               << tcu::TestLog::EndMessage;
            error = true;
        }
        else if (resourceList[index] != referenceResources[ndx])
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, for active resource \"" << referenceResources[ndx]
                               << "\" got index (index = " << index << ") of another resource (" << resourceList[index]
                               << ")." << tcu::TestLog::EndMessage;
            error = true;
        }
    }

    // Query for "name" should match "name[0]" except for XFB

    if (m_programInterface != PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING)
    {
        for (int ndx = 0; ndx < (int)referenceResources.size(); ++ndx)
        {
            if (de::endsWith(referenceResources[ndx], "[0]"))
            {
                const std::string queryString = referenceResources[ndx].substr(0, referenceResources[ndx].length() - 3);
                const glw::GLuint index = gl.getProgramResourceIndex(program, programInterface, queryString.c_str());
                GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

                if (index == GL_INVALID_INDEX)
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for \"" << queryString
                                       << "\" resulted in index GL_INVALID_INDEX." << tcu::TestLog::EndMessage;
                    error = true;
                }
                else if ((int)index >= (int)resourceList.size())
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for \"" << queryString
                                       << "\" resulted in index " << index
                                       << " (larger or equal to GL_ACTIVE_RESOURCES)." << tcu::TestLog::EndMessage;
                    error = true;
                }
                else if (resourceList[index] != queryString + "[0]")
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for \"" << queryString
                                       << "\" got index (index = " << index << ") of another resource (\""
                                       << resourceList[index] << "\")." << tcu::TestLog::EndMessage;
                    error = true;
                }
            }
        }
    }

    return !error;
}

bool ResourceListTestCase::verifyMaxNameLength(const std::vector<std::string> &resourceList, glw::GLuint program)
{
    const glw::Functions &gl           = m_context.getRenderContext().getFunctions();
    const glw::GLenum programInterface = getProgramInterfaceGLEnum(m_programInterface);
    glw::GLint maxNameLength           = 0;
    glw::GLint expectedMaxNameLength   = 0;

    gl.getProgramInterfaceiv(program, programInterface, GL_MAX_NAME_LENGTH, &maxNameLength);
    GLU_EXPECT_NO_ERROR(gl.getError(), "query interface");

    for (int ndx = 0; ndx < (int)resourceList.size(); ++ndx)
        expectedMaxNameLength = de::max(expectedMaxNameLength, (int)resourceList[ndx].size() + 1);

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying MAX_NAME_LENGTH, expecting " << expectedMaxNameLength
                       << " (i.e. consistent with the queried resource list)" << tcu::TestLog::EndMessage;

    if (expectedMaxNameLength != maxNameLength)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Error, got " << maxNameLength << tcu::TestLog::EndMessage;
        return false;
    }

    return true;
}

std::string ResourceListTestCase::genTestCaseName(ProgramInterface interface, const ResourceDefinition::Node *root)
{
    bool isImplicitlySizedArray = false;
    bool hasVariable            = false;
    bool accumulateName         = true;
    std::string buf             = "var";
    std::string prefix;

    for (const ResourceDefinition::Node *node = root; node; node = node->getEnclosingNode())
    {
        switch (node->getType())
        {
        case ResourceDefinition::Node::TYPE_VARIABLE:
        {
            hasVariable = true;
            break;
        }

        case ResourceDefinition::Node::TYPE_STRUCT_MEMBER:
        {
            if (accumulateName)
                buf += "_struct";
            break;
        }

        case ResourceDefinition::Node::TYPE_ARRAY_ELEMENT:
        {
            DE_ASSERT(dynamic_cast<const ResourceDefinition::ArrayElement *>(node));
            const ResourceDefinition::ArrayElement *arrayElement =
                static_cast<const ResourceDefinition::ArrayElement *>(node);

            isImplicitlySizedArray = (arrayElement->m_arraySize == ResourceDefinition::ArrayElement::UNSIZED_ARRAY);

            if (accumulateName)
                buf += "_array";
            break;
        }

        case ResourceDefinition::Node::TYPE_STORAGE_QUALIFIER:
        {
            DE_ASSERT(dynamic_cast<const ResourceDefinition::StorageQualifier *>(node));
            const ResourceDefinition::StorageQualifier *storageDef =
                static_cast<const ResourceDefinition::StorageQualifier *>(node);

            if (storageDef->m_storage == glu::STORAGE_PATCH_IN || storageDef->m_storage == glu::STORAGE_PATCH_OUT)
            {
                if (accumulateName)
                    prefix += "patch_";
            }
            break;
        }

        case ResourceDefinition::Node::TYPE_SHADER:
        case ResourceDefinition::Node::TYPE_SHADER_SET:
        {
            bool arrayedInterface;

            if (node->getType() == ResourceDefinition::Node::TYPE_SHADER)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::Shader *>(node));
                const ResourceDefinition::Shader *shaderDef = static_cast<const ResourceDefinition::Shader *>(node);

                arrayedInterface = isArrayedInterface(interface, (1u << shaderDef->m_type));
            }
            else
            {
                DE_ASSERT(node->getType() == ResourceDefinition::Node::TYPE_SHADER_SET);
                DE_ASSERT(dynamic_cast<const ResourceDefinition::ShaderSet *>(node));
                const ResourceDefinition::ShaderSet *shaderDef =
                    static_cast<const ResourceDefinition::ShaderSet *>(node);

                arrayedInterface = isArrayedInterface(interface, shaderDef->getReferencingMask());
            }

            if (arrayedInterface && isImplicitlySizedArray)
            {
                // omit implicit arrayness from name, i.e. remove trailing "_array"
                DE_ASSERT(de::endsWith(buf, "_array"));
                buf = buf.substr(0, buf.length() - 6);
            }

            break;
        }

        case ResourceDefinition::Node::TYPE_INTERFACE_BLOCK:
        {
            accumulateName = false;
            break;
        }

        default:
            break;
        }
    }

    if (!hasVariable)
        return prefix + "empty";
    else
        return prefix + buf;
}

bool ResourceListTestCase::isArrayedInterface(ProgramInterface interface, uint32_t stageBits)
{
    if (interface == PROGRAMINTERFACE_PROGRAM_INPUT)
    {
        const glu::ShaderType firstStage = getShaderMaskFirstStage(stageBits);
        return firstStage == glu::SHADERTYPE_TESSELLATION_CONTROL ||
               firstStage == glu::SHADERTYPE_TESSELLATION_EVALUATION || firstStage == glu::SHADERTYPE_GEOMETRY;
    }
    else if (interface == PROGRAMINTERFACE_PROGRAM_OUTPUT)
    {
        const glu::ShaderType lastStage = getShaderMaskLastStage(stageBits);
        return lastStage == glu::SHADERTYPE_TESSELLATION_CONTROL;
    }
    return false;
}

// Resouce property query case

class ResourceTestCase : public ProgramInterfaceQueryTestCase
{
public:
    ResourceTestCase(Context &context, const ResourceDefinition::Node::SharedPtr &targetResource,
                     const ProgramResourceQueryTestTarget &queryTarget, const char *name = DE_NULL);
    ~ResourceTestCase(void);

private:
    void init(void);
    void deinit(void);
    const ProgramInterfaceDefinition::Program *getProgramDefinition(void) const;
    std::vector<std::string> getQueryTargetResources(void) const;

    static std::string genTestCaseName(const ResourceDefinition::Node *);
    static std::string genMultilineDescription(const ResourceDefinition::Node *);

    ResourceDefinition::Node::SharedPtr m_targetResource;
    ProgramInterfaceDefinition::Program *m_program;
    std::vector<std::string> m_targetResources;
};

ResourceTestCase::ResourceTestCase(Context &context, const ResourceDefinition::Node::SharedPtr &targetResource,
                                   const ProgramResourceQueryTestTarget &queryTarget, const char *name)
    : ProgramInterfaceQueryTestCase(
          context, (name == DE_NULL) ? (genTestCaseName(targetResource.get()).c_str()) : (name), "", queryTarget)
    , m_targetResource(targetResource)
    , m_program(DE_NULL)
{
}

ResourceTestCase::~ResourceTestCase(void)
{
    deinit();
}

void ResourceTestCase::init(void)
{
    m_testCtx.getLog() << tcu::TestLog::Message << genMultilineDescription(m_targetResource.get())
                       << tcu::TestLog::EndMessage;

    // Program
    {
        // Generate interface with target resource
        m_program         = generateProgramDefinitionFromResource(m_targetResource.get()).release();
        m_targetResources = getProgramInterfaceResourceList(m_program, getTargetInterface());
    }
}

void ResourceTestCase::deinit(void)
{
    m_targetResource.clear();

    delete m_program;
    m_program = DE_NULL;

    m_targetResources = std::vector<std::string>();
}

const ProgramInterfaceDefinition::Program *ResourceTestCase::getProgramDefinition(void) const
{
    return m_program;
}

std::vector<std::string> ResourceTestCase::getQueryTargetResources(void) const
{
    return m_targetResources;
}

std::string ResourceTestCase::genTestCaseName(const ResourceDefinition::Node *resource)
{
    if (resource->getType() == ResourceDefinition::Node::TYPE_VARIABLE)
    {
        DE_ASSERT(dynamic_cast<const ResourceDefinition::Variable *>(resource));

        const ResourceDefinition::Variable *variable = static_cast<const ResourceDefinition::Variable *>(resource);

        return convertGLTypeNameToTestName(glu::getDataTypeName(variable->m_dataType));
    }

    DE_ASSERT(false);
    return "";
}

std::string ResourceTestCase::genMultilineDescription(const ResourceDefinition::Node *resource)
{
    if (resource->getType() == ResourceDefinition::Node::TYPE_VARIABLE)
    {
        DE_ASSERT(dynamic_cast<const ResourceDefinition::Variable *>(resource));

        const ResourceDefinition::Variable *varDef = static_cast<const ResourceDefinition::Variable *>(resource);
        std::ostringstream buf;
        std::ostringstream structureDescriptor;
        std::string uniformType;

        for (const ResourceDefinition::Node *node = resource; node; node = node->getEnclosingNode())
        {
            if (node->getType() == ResourceDefinition::Node::TYPE_STORAGE_QUALIFIER)
            {
                DE_ASSERT(dynamic_cast<const ResourceDefinition::StorageQualifier *>(node));

                const ResourceDefinition::StorageQualifier *storageDef =
                    static_cast<const ResourceDefinition::StorageQualifier *>(node);

                uniformType = std::string(" ") + glu::getStorageName(storageDef->m_storage);
                structureDescriptor << "\n\tdeclared as \"" << glu::getStorageName(storageDef->m_storage) << "\"";
            }

            if (node->getType() == ResourceDefinition::Node::TYPE_ARRAY_ELEMENT)
                structureDescriptor << "\n\tarray";

            if (node->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER)
                structureDescriptor << "\n\tin a struct";

            if (node->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK)
                structureDescriptor << "\n\tin the default block";

            if (node->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
                structureDescriptor << "\n\tin an interface block";
        }

        buf << "Querying properties of " << glu::getDataTypeName(varDef->m_dataType) << uniformType << " variable.\n"
            << "Variable is:\n"
            << "\t" << glu::getDataTypeName(varDef->m_dataType) << structureDescriptor.str();

        return buf.str();
    }
    else if (resource->getType() == ResourceDefinition::Node::TYPE_TRANSFORM_FEEDBACK_TARGET)
    {
        DE_ASSERT(dynamic_cast<const ResourceDefinition::TransformFeedbackTarget *>(resource));

        const ResourceDefinition::TransformFeedbackTarget *xfbDef =
            static_cast<const ResourceDefinition::TransformFeedbackTarget *>(resource);

        DE_ASSERT(xfbDef->m_builtinVarName);

        return std::string("Querying properties of a builtin variable ") + xfbDef->m_builtinVarName;
    }

    DE_ASSERT(false);
    return "";
}

class ResourceNameBufferLimitCase : public TestCase
{
public:
    ResourceNameBufferLimitCase(Context &context, const char *name, const char *description);
    ~ResourceNameBufferLimitCase(void);

private:
    IterateResult iterate(void);
};

ResourceNameBufferLimitCase::ResourceNameBufferLimitCase(Context &context, const char *name, const char *description)
    : TestCase(context, name, description)
{
}

ResourceNameBufferLimitCase::~ResourceNameBufferLimitCase(void)
{
}

ResourceNameBufferLimitCase::IterateResult ResourceNameBufferLimitCase::iterate(void)
{
    static const char *const computeSource = "${GLSL_VERSION_DECL}\n"
                                             "layout(local_size_x = 1) in;\n"
                                             "uniform highp int u_uniformWithALongName;\n"
                                             "writeonly buffer OutputBufferBlock { highp int b_output_int; };\n"
                                             "void main ()\n"
                                             "{\n"
                                             "    b_output_int = u_uniformWithALongName;\n"
                                             "}\n";

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
                                                                       specializeShader(m_context, computeSource)));
    glw::GLuint uniformIndex;

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

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

        m_testCtx.getLog() << program;
        if (!program.isOk())
            throw tcu::TestError("could not build program");
    }

    uniformIndex = gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_uniformWithALongName");
    GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

    if (uniformIndex == GL_INVALID_INDEX)
        throw tcu::TestError("Uniform u_uniformWithALongName resource index was GL_INVALID_INDEX");

    // Query with different sized buffers, len("u_uniformWithALongName") == 22

    {
        static const struct
        {
            const char *description;
            int querySize;
            bool returnLength;
        } querySizes[] = {
            {"Query to larger buffer", 24, true},
            {"Query to buffer the same size", 23, true},
            {"Query to one byte too small buffer", 22, true},
            {"Query to one byte buffer", 1, true},
            {"Query to zero sized buffer", 0, true},
            {"Query to one byte too small buffer, null length argument", 22, false},
            {"Query to one byte buffer, null length argument", 1, false},
            {"Query to zero sized buffer, null length argument", 0, false},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(querySizes); ++ndx)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Query", querySizes[ndx].description);
            const int uniformNameLen = 22;
            const int expectedWriteLen =
                (querySizes[ndx].querySize != 0) ? (de::min(uniformNameLen, (querySizes[ndx].querySize - 1))) : (0);
            char buffer[26];
            glw::GLsizei written = -1;

            // One byte for guard
            DE_ASSERT((int)sizeof(buffer) > querySizes[ndx].querySize);

            deMemset(buffer, 'x', sizeof(buffer));

            if (querySizes[ndx].querySize)
                m_testCtx.getLog() << tcu::TestLog::Message << "Querying uniform name to a buffer of size "
                                   << querySizes[ndx].querySize << ", expecting query to write " << expectedWriteLen
                                   << " bytes followed by a null terminator" << tcu::TestLog::EndMessage;
            else
                m_testCtx.getLog() << tcu::TestLog::Message << "Querying uniform name to a buffer of size "
                                   << querySizes[ndx].querySize << ", expecting query to write 0 bytes"
                                   << tcu::TestLog::EndMessage;

            gl.getProgramResourceName(program.getProgram(), GL_UNIFORM, uniformIndex, querySizes[ndx].querySize,
                                      (querySizes[ndx].returnLength) ? (&written) : (DE_NULL), buffer);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query resource name");

            if (querySizes[ndx].returnLength && written != expectedWriteLen)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected write length of " << expectedWriteLen
                                   << ", got " << written << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected write lenght");
            }
            else if (querySizes[ndx].querySize != 0 && buffer[expectedWriteLen] != 0)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected null terminator at " << expectedWriteLen
                                   << ", got dec=" << (int)buffer[expectedWriteLen] << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Missing null terminator");
            }
            else if (querySizes[ndx].querySize != 0 && buffer[expectedWriteLen + 1] != 'x')
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, guard at index " << (expectedWriteLen + 1)
                                   << " was modified, got dec=" << (int)buffer[expectedWriteLen + 1]
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrote over buffer size");
            }
            else if (querySizes[ndx].querySize == 0 && buffer[0] != 'x')
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, buffer size was 0 but buffer contents were modified. At index 0 got dec="
                                   << (int)buffer[0] << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer contents were modified");
            }
        }
    }

    return STOP;
}

class ResourceQueryBufferLimitCase : public TestCase
{
public:
    ResourceQueryBufferLimitCase(Context &context, const char *name, const char *description);
    ~ResourceQueryBufferLimitCase(void);

private:
    IterateResult iterate(void);
};

ResourceQueryBufferLimitCase::ResourceQueryBufferLimitCase(Context &context, const char *name, const char *description)
    : TestCase(context, name, description)
{
}

ResourceQueryBufferLimitCase::~ResourceQueryBufferLimitCase(void)
{
}

ResourceQueryBufferLimitCase::IterateResult ResourceQueryBufferLimitCase::iterate(void)
{
    static const char *const computeSource = "${GLSL_VERSION_DECL}\n"
                                             "layout(local_size_x = 1) in;\n"
                                             "uniform highp int u_uniform;\n"
                                             "writeonly buffer OutputBufferBlock { highp int b_output_int; };\n"
                                             "void main ()\n"
                                             "{\n"
                                             "    b_output_int = u_uniform;\n"
                                             "}\n";

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(), glu::ProgramSources() << glu::ComputeSource(
                                                                       specializeShader(m_context, computeSource)));
    glw::GLuint uniformIndex;

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

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

        m_testCtx.getLog() << program;
        if (!program.isOk())
            throw tcu::TestError("could not build program");
    }

    uniformIndex = gl.getProgramResourceIndex(program.getProgram(), GL_UNIFORM, "u_uniform");
    GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

    if (uniformIndex == GL_INVALID_INDEX)
        throw tcu::TestError("Uniform u_uniform resource index was GL_INVALID_INDEX");

    // Query uniform properties

    {
        static const struct
        {
            const char *description;
            int numProps;
            int bufferSize;
            bool returnLength;
        } querySizes[] = {
            {"Query to a larger buffer", 2, 3, true},
            {"Query to too small a buffer", 3, 2, true},
            {"Query to zero sized buffer", 3, 0, true},
            {"Query to a larger buffer, null length argument", 2, 3, false},
            {"Query to too small a buffer, null length argument", 3, 2, false},
            {"Query to zero sized buffer, null length argument", 3, 0, false},
        };

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(querySizes); ++ndx)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryToLarger", querySizes[ndx].description);
            const glw::GLenum props[]  = {GL_LOCATION, GL_LOCATION, GL_LOCATION};
            const int expectedWriteLen = de::min(querySizes[ndx].bufferSize, querySizes[ndx].numProps);
            int params[]               = {255, 255, 255, 255};
            glw::GLsizei written       = -1;

            DE_ASSERT(querySizes[ndx].numProps <= DE_LENGTH_OF_ARRAY(props));
            DE_ASSERT(querySizes[ndx].bufferSize <
                      DE_LENGTH_OF_ARRAY(params)); // leave at least one element for overflow detection

            m_testCtx.getLog() << tcu::TestLog::Message << "Querying " << querySizes[ndx].numProps
                               << " uniform prop(s) to a buffer with size " << querySizes[ndx].bufferSize
                               << ". Expecting query to return " << expectedWriteLen << " prop(s)"
                               << tcu::TestLog::EndMessage;

            gl.getProgramResourceiv(program.getProgram(), GL_UNIFORM, uniformIndex, querySizes[ndx].numProps, props,
                                    querySizes[ndx].bufferSize, (querySizes[ndx].returnLength) ? (&written) : (DE_NULL),
                                    params);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query program resources");

            if (querySizes[ndx].returnLength && written != expectedWriteLen)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, expected write length of " << expectedWriteLen
                                   << ", got " << written << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unexpected write lenght");
            }
            else if (params[expectedWriteLen] != 255)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, guard at index " << (expectedWriteLen)
                                   << " was modified. Was 255 before call, got dec=" << params[expectedWriteLen]
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Wrote over buffer size");
            }
        }
    }

    return STOP;
}

class InterfaceBlockBaseCase : public TestCase
{
public:
    enum CaseType
    {
        CASE_NAMED_BLOCK = 0,
        CASE_UNNAMED_BLOCK,
        CASE_BLOCK_ARRAY,

        CASE_LAST
    };

    InterfaceBlockBaseCase(Context &context, const char *name, const char *description, glu::Storage storage,
                           CaseType caseType);
    ~InterfaceBlockBaseCase(void);

private:
    void init(void);
    void deinit(void);

protected:
    const glu::Storage m_storage;
    const CaseType m_caseType;
    ProgramInterfaceDefinition::Program *m_program;
};

InterfaceBlockBaseCase::InterfaceBlockBaseCase(Context &context, const char *name, const char *description,
                                               glu::Storage storage, CaseType caseType)
    : TestCase(context, name, description)
    , m_storage(storage)
    , m_caseType(caseType)
    , m_program(DE_NULL)
{
    DE_ASSERT(storage == glu::STORAGE_UNIFORM || storage == glu::STORAGE_BUFFER);
}

InterfaceBlockBaseCase::~InterfaceBlockBaseCase(void)
{
    deinit();
}

void InterfaceBlockBaseCase::init(void)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
    ProgramInterfaceDefinition::Shader *shader;

    m_program = new ProgramInterfaceDefinition::Program();
    shader    = m_program->addShader(glu::SHADERTYPE_COMPUTE, glslVersion);

    // PrecedingInterface
    {
        glu::InterfaceBlock precedingInterfaceBlock;

        precedingInterfaceBlock.interfaceName  = "PrecedingInterface";
        precedingInterfaceBlock.layout.binding = 0;
        precedingInterfaceBlock.storage        = m_storage;
        precedingInterfaceBlock.instanceName   = "precedingInstance";

        precedingInterfaceBlock.variables.push_back(
            glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "precedingMember"));

        // Unsized array type
        if (m_storage == glu::STORAGE_BUFFER)
            precedingInterfaceBlock.variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY),
                "precedingMemberUnsizedArray"));
        else
            precedingInterfaceBlock.variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), 2), "precedingMemberArray"));

        shader->getDefaultBlock().interfaceBlocks.push_back(precedingInterfaceBlock);
    }

    // TargetInterface
    {
        glu::InterfaceBlock targetInterfaceBlock;

        targetInterfaceBlock.interfaceName  = "TargetInterface";
        targetInterfaceBlock.layout.binding = 1;
        targetInterfaceBlock.storage        = m_storage;

        if (m_caseType == CASE_UNNAMED_BLOCK)
            targetInterfaceBlock.instanceName = "";
        else
            targetInterfaceBlock.instanceName = "targetInstance";

        if (m_caseType == CASE_BLOCK_ARRAY)
            targetInterfaceBlock.dimensions.push_back(2);

        // Basic type
        {
            targetInterfaceBlock.variables.push_back(
                glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "blockMemberBasic"));
        }

        // Array type
        {
            targetInterfaceBlock.variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), 3), "blockMemberArray"));
        }

        // Struct type
        {
            glu::StructType *structPtr = new glu::StructType("StructType");
            structPtr->addMember("structMemberBasic", glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP));
            structPtr->addMember("structMemberArray",
                                 glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), 2));

            targetInterfaceBlock.variables.push_back(
                glu::VariableDeclaration(glu::VarType(glu::VarType(structPtr), 2), "blockMemberStruct"));
        }

        // Unsized array type
        if (m_storage == glu::STORAGE_BUFFER)
            targetInterfaceBlock.variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY),
                "blockMemberUnsizedArray"));

        shader->getDefaultBlock().interfaceBlocks.push_back(targetInterfaceBlock);
    }

    // TrailingInterface
    {
        glu::InterfaceBlock trailingInterfaceBlock;

        trailingInterfaceBlock.interfaceName  = "TrailingInterface";
        trailingInterfaceBlock.layout.binding = 3;
        trailingInterfaceBlock.storage        = m_storage;
        trailingInterfaceBlock.instanceName   = "trailingInstance";
        trailingInterfaceBlock.variables.push_back(
            glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "trailingMember"));

        shader->getDefaultBlock().interfaceBlocks.push_back(trailingInterfaceBlock);
    }

    DE_ASSERT(m_program->isValid());
}

void InterfaceBlockBaseCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;
}

class InterfaceBlockActiveVariablesTestCase : public InterfaceBlockBaseCase
{
public:
    InterfaceBlockActiveVariablesTestCase(Context &context, const char *name, const char *description,
                                          glu::Storage storage, CaseType caseType);

private:
    IterateResult iterate(void);
};

InterfaceBlockActiveVariablesTestCase::InterfaceBlockActiveVariablesTestCase(Context &context, const char *name,
                                                                             const char *description,
                                                                             glu::Storage storage, CaseType caseType)
    : InterfaceBlockBaseCase(context, name, description, storage, caseType)
{
}

InterfaceBlockActiveVariablesTestCase::IterateResult InterfaceBlockActiveVariablesTestCase::iterate(void)
{
    const ProgramInterface programInterface =
        (m_storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) :
        (m_storage == glu::STORAGE_BUFFER)  ? (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK) :
                                              (PROGRAMINTERFACE_LAST);
    const glw::GLenum programGLInterfaceValue     = getProgramInterfaceGLEnum(programInterface);
    const glw::GLenum programMemberInterfaceValue = (m_storage == glu::STORAGE_UNIFORM) ? (GL_UNIFORM) :
                                                    (m_storage == glu::STORAGE_BUFFER)  ? (GL_BUFFER_VARIABLE) :
                                                                                          (0);
    const std::vector<std::string> blockNames     = getProgramInterfaceResourceList(m_program, programInterface);
    glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
    int expectedMaxNumActiveVariables = 0;

    DE_ASSERT(programInterface != PROGRAMINTERFACE_LAST);

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // Verify all blocks

    for (int blockNdx = 0; blockNdx < (int)blockNames.size(); ++blockNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Block", "Block \"" + blockNames[blockNdx] + "\"");
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();
        const glw::GLuint resourceNdx =
            gl.getProgramResourceIndex(program.getProgram(), programGLInterfaceValue, blockNames[blockNdx].c_str());
        glw::GLint numActiveResources;
        std::vector<std::string> activeResourceNames;

        GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

        if (resourceNdx == GL_INVALID_INDEX)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Error, getProgramResourceIndex returned GL_INVALID_INDEX for \""
                               << blockNames[blockNdx] << "\"" << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Resource not found");
            continue;
        }

        // query block information

        {
            const glw::GLenum props[] = {GL_NUM_ACTIVE_VARIABLES};
            glw::GLint retBuffer[2]   = {-1, -1};
            glw::GLint written        = -1;

            gl.getProgramResourceiv(program.getProgram(), programGLInterfaceValue, resourceNdx,
                                    DE_LENGTH_OF_ARRAY(props), props, 1, &written, retBuffer);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_NUM_ACTIVE_VARIABLES");

            numActiveResources            = retBuffer[0];
            expectedMaxNumActiveVariables = de::max(expectedMaxNumActiveVariables, numActiveResources);
            m_testCtx.getLog() << tcu::TestLog::Message << "NUM_ACTIVE_VARIABLES = " << numActiveResources
                               << tcu::TestLog::EndMessage;

            if (written == -1 || retBuffer[0] == -1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, Query for NUM_ACTIVE_VARIABLES did not return a value"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for NUM_ACTIVE_VARIABLES failed");
                continue;
            }
            else if (retBuffer[1] != -1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, Query for NUM_ACTIVE_VARIABLES returned too many values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for NUM_ACTIVE_VARIABLES returned too many values");
                continue;
            }
            else if (retBuffer[0] < 0)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, NUM_ACTIVE_VARIABLES < 0"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "NUM_ACTIVE_VARIABLES < 0");
                continue;
            }
        }

        // query block variable information

        {
            const glw::GLenum props[] = {GL_ACTIVE_VARIABLES};
            std::vector<glw::GLint> activeVariableIndices(
                numActiveResources + 1, -1); // Allocate one extra trailing to detect wrong write lengths
            glw::GLint written = -1;

            gl.getProgramResourceiv(program.getProgram(), programGLInterfaceValue, resourceNdx,
                                    DE_LENGTH_OF_ARRAY(props), props, (glw::GLsizei)activeVariableIndices.size(),
                                    &written, &activeVariableIndices[0]);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_ACTIVE_VARIABLES");

            if (written == -1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, Query for GL_ACTIVE_VARIABLES did not return any values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for GL_ACTIVE_VARIABLES failed");
                continue;
            }
            else if (written != numActiveResources)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, Query for GL_ACTIVE_VARIABLES did not return NUM_ACTIVE_VARIABLES values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL,
                                        "Query for GL_ACTIVE_VARIABLES returned invalid number of values");
                continue;
            }
            else if (activeVariableIndices.back() != -1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, GL_ACTIVE_VARIABLES query return buffer trailing guard value was "
                                      "modified, getProgramResourceiv returned more than NUM_ACTIVE_VARIABLES values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Query for GL_ACTIVE_VARIABLES returned too many values");
                continue;
            }

            // log indices
            {
                tcu::MessageBuilder builder(&m_testCtx.getLog());

                builder << "Active variable indices: {";
                for (int varNdx = 0; varNdx < numActiveResources; ++varNdx)
                {
                    if (varNdx)
                        builder << ", ";
                    builder << activeVariableIndices[varNdx];
                }
                builder << "}" << tcu::TestLog::EndMessage;
            }

            // collect names

            activeResourceNames.resize(numActiveResources);

            for (int varNdx = 0; varNdx < numActiveResources; ++varNdx)
            {
                const glw::GLenum nameProp = GL_NAME_LENGTH;
                glw::GLint nameLength      = -1;
                std::vector<char> nameBuffer;

                written = -1;
                gl.getProgramResourceiv(program.getProgram(), programMemberInterfaceValue,
                                        activeVariableIndices[varNdx], 1, &nameProp, 1, &written, &nameLength);
                GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_NAME_LENGTH");

                if (nameLength <= 0 || written <= 0)
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, GL_NAME_LENGTH query failed"
                                       << tcu::TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GL_NAME_LENGTH query failed");
                    continue;
                }

                nameBuffer.resize(nameLength + 2, 'X'); // allocate more than required
                written = -1;
                gl.getProgramResourceName(program.getProgram(), programMemberInterfaceValue,
                                          activeVariableIndices[varNdx], nameLength + 1, &written, &nameBuffer[0]);
                GLU_EXPECT_NO_ERROR(gl.getError(), "getProgramResourceName");

                if (written <= 0)
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, name query failed, no data written"
                                       << tcu::TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "name query failed");
                    continue;
                }
                else if (written > nameLength)
                {
                    m_testCtx.getLog() << tcu::TestLog::Message
                                       << "Error, name query failed, query returned too much data"
                                       << tcu::TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "name query failed");
                    continue;
                }

                activeResourceNames[varNdx] = std::string(&nameBuffer[0], written);
            }

            // log collected names
            {
                tcu::MessageBuilder builder(&m_testCtx.getLog());

                builder << "Active variables:\n";
                for (int varNdx = 0; varNdx < numActiveResources; ++varNdx)
                    builder << "\t" << activeResourceNames[varNdx] << "\n";
                builder << tcu::TestLog::EndMessage;
            }
        }

        // verify names
        {
            glu::InterfaceBlock *block  = DE_NULL;
            const std::string blockName = glu::parseVariableName(blockNames[blockNdx].c_str());
            std::vector<std::string> referenceList;

            for (int interfaceNdx = 0;
                 interfaceNdx < (int)m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks.size();
                 ++interfaceNdx)
            {
                if (m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx].interfaceName ==
                    blockName)
                {
                    block = &m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx];
                    break;
                }
            }

            if (!block)
                throw tcu::InternalError("could not find block referenced in the reference resource list");

            // generate reference list

            referenceList = getProgramInterfaceBlockMemberResourceList(*block);
            {
                tcu::MessageBuilder builder(&m_testCtx.getLog());

                builder << "Expected variable names:\n";
                for (int varNdx = 0; varNdx < (int)referenceList.size(); ++varNdx)
                    builder << "\t" << referenceList[varNdx] << "\n";
                builder << tcu::TestLog::EndMessage;
            }

            // compare lists
            {
                bool listsIdentical = true;

                for (int ndx = 0; ndx < (int)referenceList.size(); ++ndx)
                {
                    if (!de::contains(activeResourceNames.begin(), activeResourceNames.end(), referenceList[ndx]))
                    {
                        m_testCtx.getLog()
                            << tcu::TestLog::Message << "Error, variable name list did not contain active variable "
                            << referenceList[ndx] << tcu::TestLog::EndMessage;
                        listsIdentical = false;
                    }
                }

                for (int ndx = 0; ndx < (int)activeResourceNames.size(); ++ndx)
                {
                    if (!de::contains(referenceList.begin(), referenceList.end(), activeResourceNames[ndx]))
                    {
                        m_testCtx.getLog()
                            << tcu::TestLog::Message << "Error, variable name list contains unexpected resource \""
                            << activeResourceNames[ndx] << "\"" << tcu::TestLog::EndMessage;
                        listsIdentical = false;
                    }
                }

                if (listsIdentical)
                    m_testCtx.getLog() << tcu::TestLog::Message << "Lists identical" << tcu::TestLog::EndMessage;
                else
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, invalid active variable list"
                                       << tcu::TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid active variable list");
                    continue;
                }
            }
        }
    }

    // Max num active variables
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "MaxNumActiveVariables", "MAX_NUM_ACTIVE_VARIABLES");
        const glw::Functions &gl         = m_context.getRenderContext().getFunctions();
        glw::GLint maxNumActiveVariables = -1;

        gl.getProgramInterfaceiv(program.getProgram(), programGLInterfaceValue, GL_MAX_NUM_ACTIVE_VARIABLES,
                                 &maxNumActiveVariables);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query MAX_NUM_ACTIVE_VARIABLES");

        m_testCtx.getLog() << tcu::TestLog::Message << "MAX_NUM_ACTIVE_VARIABLES = " << maxNumActiveVariables
                           << tcu::TestLog::EndMessage;

        if (expectedMaxNumActiveVariables != maxNumActiveVariables)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected MAX_NUM_ACTIVE_VARIABLES"
                               << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "unexpected MAX_NUM_ACTIVE_VARIABLES");
        }
        else
            m_testCtx.getLog() << tcu::TestLog::Message << "MAX_NUM_ACTIVE_VARIABLES valid" << tcu::TestLog::EndMessage;
    }

    return STOP;
}

class InterfaceBlockDataSizeTestCase : public InterfaceBlockBaseCase
{
public:
    InterfaceBlockDataSizeTestCase(Context &context, const char *name, const char *description, glu::Storage storage,
                                   CaseType caseType);

private:
    IterateResult iterate(void);
    int getBlockMinDataSize(const std::string &blockName) const;
    int getBlockMinDataSize(const glu::InterfaceBlock &block) const;
};

InterfaceBlockDataSizeTestCase::InterfaceBlockDataSizeTestCase(Context &context, const char *name,
                                                               const char *description, glu::Storage storage,
                                                               CaseType caseType)
    : InterfaceBlockBaseCase(context, name, description, storage, caseType)
{
}

InterfaceBlockDataSizeTestCase::IterateResult InterfaceBlockDataSizeTestCase::iterate(void)
{
    const ProgramInterface programInterface =
        (m_storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) :
        (m_storage == glu::STORAGE_BUFFER)  ? (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK) :
                                              (PROGRAMINTERFACE_LAST);
    const glw::GLenum programGLInterfaceValue = getProgramInterfaceGLEnum(programInterface);
    const std::vector<std::string> blockNames = getProgramInterfaceResourceList(m_program, programInterface);
    glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));

    DE_ASSERT(programInterface != PROGRAMINTERFACE_LAST);

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // Verify all blocks
    for (int blockNdx = 0; blockNdx < (int)blockNames.size(); ++blockNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Block", "Block \"" + blockNames[blockNdx] + "\"");
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();
        const glw::GLuint resourceNdx =
            gl.getProgramResourceIndex(program.getProgram(), programGLInterfaceValue, blockNames[blockNdx].c_str());
        const int expectedMinDataSize = getBlockMinDataSize(blockNames[blockNdx]);
        glw::GLint queryDataSize      = -1;

        GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

        if (resourceNdx == GL_INVALID_INDEX)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Error, getProgramResourceIndex returned GL_INVALID_INDEX for \""
                               << blockNames[blockNdx] << "\"" << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Resource not found");
            continue;
        }

        // query
        {
            const glw::GLenum prop = GL_BUFFER_DATA_SIZE;

            gl.getProgramResourceiv(program.getProgram(), programGLInterfaceValue, resourceNdx, 1, &prop, 1, DE_NULL,
                                    &queryDataSize);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query resource BUFFER_DATA_SIZE");
        }

        m_testCtx.getLog() << tcu::TestLog::Message << "BUFFER_DATA_SIZE = " << queryDataSize << "\n"
                           << "Buffer data size with tight packing: " << expectedMinDataSize
                           << tcu::TestLog::EndMessage;

        if (queryDataSize < expectedMinDataSize)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, buffer size was less than minimum buffer data size"
                               << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Buffer data size invalid");
            continue;
        }
        else
            m_testCtx.getLog() << tcu::TestLog::Message << "Buffer size valid" << tcu::TestLog::EndMessage;
    }

    return STOP;
}

int InterfaceBlockDataSizeTestCase::getBlockMinDataSize(const std::string &blockFullName) const
{
    const std::string blockName = glu::parseVariableName(blockFullName.c_str());

    for (int interfaceNdx = 0; interfaceNdx < (int)m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks.size();
         ++interfaceNdx)
    {
        if (m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx].interfaceName == blockName &&
            m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx].storage == m_storage)
            return getBlockMinDataSize(m_program->getShaders()[0]->getDefaultBlock().interfaceBlocks[interfaceNdx]);
    }

    DE_ASSERT(false);
    return -1;
}

class AtomicCounterCase : public TestCase
{
public:
    AtomicCounterCase(Context &context, const char *name, const char *description);
    ~AtomicCounterCase(void);

private:
    void init(void);
    void deinit(void);

protected:
    int getNumAtomicCounterBuffers(void) const;
    int getMaxNumActiveVariables(void) const;
    int getBufferVariableCount(int binding) const;
    int getBufferMinimumDataSize(int binding) const;

    ProgramInterfaceDefinition::Program *m_program;
};

AtomicCounterCase::AtomicCounterCase(Context &context, const char *name, const char *description)
    : TestCase(context, name, description)
    , m_program(DE_NULL)
{
}

AtomicCounterCase::~AtomicCounterCase(void)
{
    deinit();
}

void AtomicCounterCase::init(void)
{
    ProgramInterfaceDefinition::Shader *shader;
    glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    m_program = new ProgramInterfaceDefinition::Program();
    shader    = m_program->addShader(glu::SHADERTYPE_COMPUTE, glslVersion);

    {
        glu::VariableDeclaration decl(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST),
                                      "binding1_counter1", glu::STORAGE_UNIFORM);
        decl.layout.binding = 1;
        shader->getDefaultBlock().variables.push_back(decl);
    }
    {
        glu::VariableDeclaration decl(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST),
                                      "binding1_counter2", glu::STORAGE_UNIFORM);
        decl.layout.binding = 1;
        decl.layout.offset  = 8;

        shader->getDefaultBlock().variables.push_back(decl);
    }
    {
        glu::VariableDeclaration decl(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST),
                                      "binding2_counter1", glu::STORAGE_UNIFORM);
        decl.layout.binding = 2;
        shader->getDefaultBlock().variables.push_back(decl);
    }

    DE_ASSERT(m_program->isValid());
}

void AtomicCounterCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;
}

int AtomicCounterCase::getNumAtomicCounterBuffers(void) const
{
    std::set<int> buffers;

    for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
    {
        if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
            glu::isDataTypeAtomicCounter(
                m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()))
        {
            buffers.insert(m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding);
        }
    }

    return (int)buffers.size();
}

int AtomicCounterCase::getMaxNumActiveVariables(void) const
{
    int maxVars = 0;
    std::map<int, int> numBufferVars;

    for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
    {
        if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
            glu::isDataTypeAtomicCounter(
                m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()))
        {
            const int binding = m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding;

            if (numBufferVars.find(binding) == numBufferVars.end())
                numBufferVars[binding] = 1;
            else
                ++numBufferVars[binding];
        }
    }

    for (std::map<int, int>::const_iterator it = numBufferVars.begin(); it != numBufferVars.end(); ++it)
        maxVars = de::max(maxVars, it->second);

    return maxVars;
}

int AtomicCounterCase::getBufferVariableCount(int binding) const
{
    int numVars = 0;

    for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
    {
        if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
            glu::isDataTypeAtomicCounter(
                m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()) &&
            m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding == binding)
            ++numVars;
    }

    return numVars;
}

int AtomicCounterCase::getBufferMinimumDataSize(int binding) const
{
    int minSize       = -1;
    int currentOffset = 0;

    for (int ndx = 0; ndx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size(); ++ndx)
    {
        if (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.isBasicType() &&
            glu::isDataTypeAtomicCounter(
                m_program->getShaders()[0]->getDefaultBlock().variables[ndx].varType.getBasicType()) &&
            m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.binding == binding)
        {
            const int thisOffset = (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.offset != -1) ?
                                       (m_program->getShaders()[0]->getDefaultBlock().variables[ndx].layout.offset) :
                                       (currentOffset);
            currentOffset        = thisOffset + 4;

            minSize = de::max(minSize, thisOffset + 4);
        }
    }

    return minSize;
}

class AtomicCounterResourceListCase : public AtomicCounterCase
{
public:
    AtomicCounterResourceListCase(Context &context, const char *name, const char *description);

private:
    IterateResult iterate(void);
};

AtomicCounterResourceListCase::AtomicCounterResourceListCase(Context &context, const char *name,
                                                             const char *description)
    : AtomicCounterCase(context, name, description)
{
}

AtomicCounterResourceListCase::IterateResult AtomicCounterResourceListCase::iterate(void)
{
    const glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "ActiveResources", "ACTIVE_RESOURCES");
        const glw::Functions &gl             = m_context.getRenderContext().getFunctions();
        glw::GLint numActiveResources        = -1;
        const int numExpectedActiveResources = 2; // 2 buffer bindings

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying ACTIVE_RESOURCES, expecting "
                           << numExpectedActiveResources << tcu::TestLog::EndMessage;

        gl.getProgramInterfaceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES,
                                 &numActiveResources);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_ACTIVE_RESOURCES");

        m_testCtx.getLog() << tcu::TestLog::Message << "ACTIVE_RESOURCES = " << numActiveResources
                           << tcu::TestLog::EndMessage;

        if (numActiveResources != numExpectedActiveResources)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected ACTIVE_RESOURCES"
                               << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected ACTIVE_RESOURCES");
        }
        else
            m_testCtx.getLog() << tcu::TestLog::Message << "ACTIVE_RESOURCES valid" << tcu::TestLog::EndMessage;
    }

    return STOP;
}

class AtomicCounterActiveVariablesCase : public AtomicCounterCase
{
public:
    AtomicCounterActiveVariablesCase(Context &context, const char *name, const char *description);

private:
    IterateResult iterate(void);
};

AtomicCounterActiveVariablesCase::AtomicCounterActiveVariablesCase(Context &context, const char *name,
                                                                   const char *description)
    : AtomicCounterCase(context, name, description)
{
}

AtomicCounterActiveVariablesCase::IterateResult AtomicCounterActiveVariablesCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
    const int numAtomicBuffers              = getNumAtomicCounterBuffers();
    const int expectedMaxNumActiveVariables = getMaxNumActiveVariables();

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // check active variables
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Interface", "ATOMIC_COUNTER_BUFFER interface");
        glw::GLint queryActiveResources       = -1;
        glw::GLint queryMaxNumActiveVariables = -1;

        gl.getProgramInterfaceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, GL_ACTIVE_RESOURCES,
                                 &queryActiveResources);
        gl.getProgramInterfaceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, GL_MAX_NUM_ACTIVE_VARIABLES,
                                 &queryMaxNumActiveVariables);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query interface");

        m_testCtx.getLog() << tcu::TestLog::Message << "GL_ACTIVE_RESOURCES = " << queryActiveResources << "\n"
                           << "GL_MAX_NUM_ACTIVE_VARIABLES = " << queryMaxNumActiveVariables << "\n"
                           << tcu::TestLog::EndMessage;

        if (queryActiveResources != numAtomicBuffers)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected GL_ACTIVE_RESOURCES, expected "
                               << numAtomicBuffers << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected GL_ACTIVE_RESOURCES");
        }

        if (queryMaxNumActiveVariables != expectedMaxNumActiveVariables)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Error, got unexpected GL_MAX_NUM_ACTIVE_VARIABLES, expected "
                               << expectedMaxNumActiveVariables << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected GL_MAX_NUM_ACTIVE_VARIABLES");
        }
    }

    // Check each buffer
    for (int bufferNdx = 0; bufferNdx < numAtomicBuffers; ++bufferNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Resource",
                                            "Resource index " + de::toString(bufferNdx));
        std::vector<glw::GLint> activeVariables;
        std::vector<std::string> memberNames;

        // Find active variables
        {
            const glw::GLenum numActiveVariablesProp = GL_NUM_ACTIVE_VARIABLES;
            const glw::GLenum activeVariablesProp    = GL_ACTIVE_VARIABLES;
            glw::GLint numActiveVariables            = -2;
            glw::GLint written                       = -1;

            gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, 1,
                                    &numActiveVariablesProp, 1, &written, &numActiveVariables);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query num active variables");

            if (numActiveVariables <= 0)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, got unexpected NUM_ACTIVE_VARIABLES: " << numActiveVariables
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected NUM_ACTIVE_VARIABLES");
                continue;
            }

            if (written <= 0)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, query for NUM_ACTIVE_VARIABLES returned no values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "NUM_ACTIVE_VARIABLES query failed");
                continue;
            }

            m_testCtx.getLog() << tcu::TestLog::Message << "GL_NUM_ACTIVE_VARIABLES = " << numActiveVariables
                               << tcu::TestLog::EndMessage;

            written = -1;
            activeVariables.resize(numActiveVariables + 1, -2);

            gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, 1, &activeVariablesProp,
                                    numActiveVariables, &written, &activeVariables[0]);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query active variables");

            if (written != numActiveVariables)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, unexpected number of ACTIVE_VARIABLES, NUM_ACTIVE_VARIABLES = "
                                   << numActiveVariables << ", query returned " << written << " values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected ACTIVE_VARIABLES");
                continue;
            }

            if (activeVariables.back() != -2)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, query for ACTIVE_VARIABLES wrote over target buffer bounds"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "ACTIVE_VARIABLES query failed");
                continue;
            }

            activeVariables.pop_back();
        }

        // log indices
        {
            tcu::MessageBuilder builder(&m_testCtx.getLog());

            builder << "Active variable indices: {";
            for (int varNdx = 0; varNdx < (int)activeVariables.size(); ++varNdx)
            {
                if (varNdx)
                    builder << ", ";
                builder << activeVariables[varNdx];
            }
            builder << "}" << tcu::TestLog::EndMessage;
        }

        // collect member names
        for (int ndx = 0; ndx < (int)activeVariables.size(); ++ndx)
        {
            const glw::GLenum nameLengthProp = GL_NAME_LENGTH;
            glw::GLint nameLength            = -1;
            glw::GLint written               = -1;
            std::vector<char> nameBuf;

            gl.getProgramResourceiv(program.getProgram(), GL_UNIFORM, activeVariables[ndx], 1, &nameLengthProp, 1,
                                    &written, &nameLength);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer variable name length");

            if (written <= 0 || nameLength == -1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for GL_NAME_LENGTH returned no values"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GL_NAME_LENGTH query failed");
                continue;
            }

            nameBuf.resize(
                nameLength + 2,
                'X'); // +2 to tolerate potential off-by-ones in some implementations, name queries will check these cases better
            written = -1;

            gl.getProgramResourceName(program.getProgram(), GL_UNIFORM, activeVariables[ndx], (int)nameBuf.size(),
                                      &written, &nameBuf[0]);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer variable name");

            if (written <= 0)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for resource name returned no name"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Name query failed");
                continue;
            }

            memberNames.push_back(std::string(&nameBuf[0], written));
        }

        // log names
        {
            tcu::MessageBuilder builder(&m_testCtx.getLog());

            builder << "Active variables:\n";
            for (int varNdx = 0; varNdx < (int)memberNames.size(); ++varNdx)
            {
                builder << "\t" << memberNames[varNdx] << "\n";
            }
            builder << tcu::TestLog::EndMessage;
        }

        // check names are all in the same buffer
        {
            bool bindingsValid = true;

            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying names" << tcu::TestLog::EndMessage;

            for (int nameNdx = 0; nameNdx < (int)memberNames.size(); ++nameNdx)
            {
                int prevBinding = -1;

                for (int varNdx = 0; varNdx < (int)m_program->getShaders()[0]->getDefaultBlock().variables.size();
                     ++varNdx)
                {
                    if (m_program->getShaders()[0]->getDefaultBlock().variables[varNdx].name == memberNames[nameNdx])
                    {
                        const int varBinding =
                            m_program->getShaders()[0]->getDefaultBlock().variables[varNdx].layout.binding;

                        if (prevBinding == -1 || prevBinding == varBinding)
                            prevBinding = varBinding;
                        else
                            bindingsValid = false;
                    }
                }

                if (prevBinding == -1)
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, could not find variable with name \""
                                       << memberNames[nameNdx] << "\"" << tcu::TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Variable name invalid");
                }
                else if (getBufferVariableCount(prevBinding) != (int)memberNames.size())
                {
                    m_testCtx.getLog() << tcu::TestLog::Message << "Error, unexpected variable count for binding "
                                       << prevBinding << ". Expected " << getBufferVariableCount(prevBinding)
                                       << ", got " << (int)memberNames.size() << tcu::TestLog::EndMessage;
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Variable names invalid");
                }
            }

            if (!bindingsValid)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, all resource do not share the same buffer"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Active variables invalid");
                continue;
            }
        }
    }

    return STOP;
}

class AtomicCounterBufferBindingCase : public AtomicCounterCase
{
public:
    AtomicCounterBufferBindingCase(Context &context, const char *name, const char *description);

private:
    IterateResult iterate(void);
};

AtomicCounterBufferBindingCase::AtomicCounterBufferBindingCase(Context &context, const char *name,
                                                               const char *description)
    : AtomicCounterCase(context, name, description)
{
}

AtomicCounterBufferBindingCase::IterateResult AtomicCounterBufferBindingCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
    const int numAtomicBuffers = getNumAtomicCounterBuffers();

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // check every buffer
    for (int bufferNdx = 0; bufferNdx < numAtomicBuffers; ++bufferNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Resource",
                                            "Resource index " + de::toString(bufferNdx));
        const glw::GLenum bufferBindingProp = GL_BUFFER_BINDING;
        glw::GLint bufferBinding            = -1;
        glw::GLint written                  = -1;

        gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, 1, &bufferBindingProp, 1,
                                &written, &bufferBinding);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");

        if (written <= 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for BUFFER_BINDING returned no values."
                               << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "BUFFER_BINDING query failed");
        }

        m_testCtx.getLog() << tcu::TestLog::Message << "GL_BUFFER_BINDING = " << bufferBinding
                           << tcu::TestLog::EndMessage;

        // no such buffer binding?
        if (getBufferVariableCount(bufferBinding) == 0)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, got buffer with BUFFER_BINDING = " << bufferBinding
                               << ", but such buffer does not exist." << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected BUFFER_BINDING");
        }
    }

    return STOP;
}

class AtomicCounterBufferDataSizeCase : public AtomicCounterCase
{
public:
    AtomicCounterBufferDataSizeCase(Context &context, const char *name, const char *description);

private:
    IterateResult iterate(void);
};

AtomicCounterBufferDataSizeCase::AtomicCounterBufferDataSizeCase(Context &context, const char *name,
                                                                 const char *description)
    : AtomicCounterCase(context, name, description)
{
}

AtomicCounterBufferDataSizeCase::IterateResult AtomicCounterBufferDataSizeCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
    const int numAtomicBuffers = getNumAtomicCounterBuffers();

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // check every buffer
    for (int bufferNdx = 0; bufferNdx < numAtomicBuffers; ++bufferNdx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Resource",
                                            "Resource index " + de::toString(bufferNdx));
        const glw::GLenum props[] = {GL_BUFFER_BINDING, GL_BUFFER_DATA_SIZE};
        glw::GLint values[]       = {-1, -1};
        glw::GLint written        = -1;
        int bufferMinDataSize;

        gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, bufferNdx, DE_LENGTH_OF_ARRAY(props),
                                props, DE_LENGTH_OF_ARRAY(values), &written, values);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");

        if (written != 2)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Error, query for (BUFFER_BINDING, BUFFER_DATA_SIZE) returned " << written
                               << " value(s)." << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "property query failed");
            continue;
        }

        m_testCtx.getLog() << tcu::TestLog::Message << "GL_BUFFER_BINDING = " << values[0] << "\n"
                           << "GL_BUFFER_DATA_SIZE = " << values[1] << tcu::TestLog::EndMessage;

        bufferMinDataSize = getBufferMinimumDataSize(values[0]);
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying data size, expected greater than or equal to "
                           << bufferMinDataSize << tcu::TestLog::EndMessage;

        // no such buffer binding?
        if (bufferMinDataSize == -1)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, got buffer with BUFFER_BINDING = " << values[0]
                               << ", but such buffer does not exist." << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected BUFFER_BINDING");
        }
        else if (values[1] < bufferMinDataSize)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Error, got buffer with BUFFER_DATA_SIZE = " << values[1]
                               << ", expected greater than or equal to " << bufferMinDataSize
                               << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected BUFFER_BINDING");
        }
        else
            m_testCtx.getLog() << tcu::TestLog::Message << "Data size valid" << tcu::TestLog::EndMessage;
    }

    return STOP;
}

class AtomicCounterReferencedByCase : public TestCase
{
public:
    AtomicCounterReferencedByCase(Context &context, const char *name, const char *description, bool separable,
                                  uint32_t presentStagesMask, uint32_t activeStagesMask);
    ~AtomicCounterReferencedByCase(void);

private:
    void init(void);
    void deinit(void);
    IterateResult iterate(void);

    const bool m_separable;
    const uint32_t m_presentStagesMask;
    const uint32_t m_activeStagesMask;
    ProgramInterfaceDefinition::Program *m_program;
};

AtomicCounterReferencedByCase::AtomicCounterReferencedByCase(Context &context, const char *name,
                                                             const char *description, bool separable,
                                                             uint32_t presentStagesMask, uint32_t activeStagesMask)
    : TestCase(context, name, description)
    , m_separable(separable)
    , m_presentStagesMask(presentStagesMask)
    , m_activeStagesMask(activeStagesMask)
    , m_program(DE_NULL)
{
    DE_ASSERT((activeStagesMask & presentStagesMask) == activeStagesMask);
}

AtomicCounterReferencedByCase::~AtomicCounterReferencedByCase(void)
{
    deinit();
}

void AtomicCounterReferencedByCase::init(void)
{
    const uint32_t geometryMask = (1 << glu::SHADERTYPE_GEOMETRY);
    const uint32_t tessellationMask =
        (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) | (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION);
    glu::VariableDeclaration atomicVar(glu::VarType(glu::TYPE_UINT_ATOMIC_COUNTER, glu::PRECISION_LAST),
                                       "targetCounter", glu::STORAGE_UNIFORM);
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
    const bool supportsES32orGL45      = checkSupport(m_context);

    if ((m_presentStagesMask & tessellationMask) != 0 && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    if ((m_presentStagesMask & geometryMask) != 0 && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");

    atomicVar.layout.binding = 1;

    m_program = new ProgramInterfaceDefinition::Program();
    m_program->setSeparable(m_separable);

    for (int shaderType = 0; shaderType < glu::SHADERTYPE_LAST; ++shaderType)
    {
        if (m_activeStagesMask & (1 << shaderType))
            m_program->addShader((glu::ShaderType)shaderType, glslVersion)
                ->getDefaultBlock()
                .variables.push_back(atomicVar);
        else if (m_presentStagesMask & (1 << shaderType))
            m_program->addShader((glu::ShaderType)shaderType, glslVersion);
    }

    if (m_program->hasStage(glu::SHADERTYPE_GEOMETRY))
        m_program->setGeometryNumOutputVertices(1);
    if (m_program->hasStage(glu::SHADERTYPE_TESSELLATION_CONTROL) ||
        m_program->hasStage(glu::SHADERTYPE_TESSELLATION_EVALUATION))
        m_program->setTessellationNumOutputPatchVertices(1);

    DE_ASSERT(m_program->isValid());
}

void AtomicCounterReferencedByCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;
}

AtomicCounterReferencedByCase::IterateResult AtomicCounterReferencedByCase::iterate(void)
{
    const glu::RenderContext &rc = m_context.getRenderContext();
    const bool isES32orGL45      = glu::contextSupports(rc.getType(), glu::ApiType::es(3, 2)) ||
                              glu::contextSupports(rc.getType(), glu::ApiType::core(4, 5));

    const struct
    {
        glw::GLenum propName;
        glu::ShaderType shaderType;
        const char *extension;
    } targetProps[] = {
        {GL_REFERENCED_BY_VERTEX_SHADER, glu::SHADERTYPE_VERTEX, DE_NULL},
        {GL_REFERENCED_BY_FRAGMENT_SHADER, glu::SHADERTYPE_FRAGMENT, DE_NULL},
        {GL_REFERENCED_BY_COMPUTE_SHADER, glu::SHADERTYPE_COMPUTE, DE_NULL},
        {GL_REFERENCED_BY_TESS_CONTROL_SHADER, glu::SHADERTYPE_TESSELLATION_CONTROL,
         (isES32orGL45 ? DE_NULL : "GL_EXT_tessellation_shader")},
        {GL_REFERENCED_BY_TESS_EVALUATION_SHADER, glu::SHADERTYPE_TESSELLATION_EVALUATION,
         (isES32orGL45 ? DE_NULL : "GL_EXT_tessellation_shader")},
        {GL_REFERENCED_BY_GEOMETRY_SHADER, glu::SHADERTYPE_GEOMETRY,
         (isES32orGL45 ? DE_NULL : "GL_EXT_geometry_shader")},
    };

    const glw::Functions &gl = rc.getFunctions();
    const glu::ShaderProgram program(rc, generateProgramInterfaceProgramSources(m_program));

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, rc.getFunctions(), m_testCtx.getLog());

    // check props
    for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(targetProps); ++propNdx)
    {
        if (targetProps[propNdx].extension == DE_NULL ||
            m_context.getContextInfo().isExtensionSupported(targetProps[propNdx].extension))
        {
            const glw::GLenum prop = targetProps[propNdx].propName;
            const glw::GLint expected =
                ((m_activeStagesMask & (1 << targetProps[propNdx].shaderType)) != 0) ? (GL_TRUE) : (GL_FALSE);
            glw::GLint value   = -1;
            glw::GLint written = -1;

            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying " << glu::getProgramResourcePropertyName(prop)
                               << ", expecting " << glu::getBooleanName(expected) << tcu::TestLog::EndMessage;

            gl.getProgramResourceiv(program.getProgram(), GL_ATOMIC_COUNTER_BUFFER, 0, 1, &prop, 1, &written, &value);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");

            if (written != 1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, query for referenced_by_* returned invalid number of values."
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "property query failed");
                continue;
            }

            m_testCtx.getLog() << tcu::TestLog::Message << glu::getProgramResourcePropertyName(prop) << " = "
                               << glu::getBooleanStr(value) << tcu::TestLog::EndMessage;

            if (value != expected)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected value"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "unexpected property value");
                continue;
            }
        }
    }

    return STOP;
}

class ProgramInputOutputReferencedByCase : public TestCase
{
public:
    enum CaseType
    {
        CASE_VERTEX_FRAGMENT = 0,
        CASE_VERTEX_GEO_FRAGMENT,
        CASE_VERTEX_TESS_FRAGMENT,
        CASE_VERTEX_TESS_GEO_FRAGMENT,

        CASE_SEPARABLE_VERTEX,
        CASE_SEPARABLE_FRAGMENT,
        CASE_SEPARABLE_GEOMETRY,
        CASE_SEPARABLE_TESS_CTRL,
        CASE_SEPARABLE_TESS_EVAL,

        CASE_LAST
    };
    ProgramInputOutputReferencedByCase(Context &context, const char *name, const char *description,
                                       glu::Storage targetStorage, CaseType caseType);
    ~ProgramInputOutputReferencedByCase(void);

private:
    void init(void);
    void deinit(void);
    IterateResult iterate(void);

    const CaseType m_caseType;
    const glu::Storage m_targetStorage;
    ProgramInterfaceDefinition::Program *m_program;
};

ProgramInputOutputReferencedByCase::ProgramInputOutputReferencedByCase(Context &context, const char *name,
                                                                       const char *description,
                                                                       glu::Storage targetStorage, CaseType caseType)
    : TestCase(context, name, description)
    , m_caseType(caseType)
    , m_targetStorage(targetStorage)
    , m_program(DE_NULL)
{
    DE_ASSERT(caseType < CASE_LAST);
}

ProgramInputOutputReferencedByCase::~ProgramInputOutputReferencedByCase(void)
{
    deinit();
}

void ProgramInputOutputReferencedByCase::init(void)
{
    const bool hasTessellationShader =
        (m_caseType == CASE_VERTEX_TESS_FRAGMENT) || (m_caseType == CASE_VERTEX_TESS_GEO_FRAGMENT) ||
        (m_caseType == CASE_SEPARABLE_TESS_CTRL) || (m_caseType == CASE_SEPARABLE_TESS_EVAL);
    const bool hasGeometryShader = (m_caseType == CASE_VERTEX_GEO_FRAGMENT) ||
                                   (m_caseType == CASE_VERTEX_TESS_GEO_FRAGMENT) ||
                                   (m_caseType == CASE_SEPARABLE_GEOMETRY);
    const bool supportsES32orGL45 = checkSupport(m_context);

    if (hasTessellationShader && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_tessellation_shader extension");
    if (hasGeometryShader && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_EXT_geometry_shader"))
        throw tcu::NotSupportedError("Test requires GL_EXT_geometry_shader extension");

    glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
    m_program                    = new ProgramInterfaceDefinition::Program();

    if (m_caseType == CASE_SEPARABLE_VERTEX || m_caseType == CASE_SEPARABLE_FRAGMENT ||
        m_caseType == CASE_SEPARABLE_GEOMETRY || m_caseType == CASE_SEPARABLE_TESS_CTRL ||
        m_caseType == CASE_SEPARABLE_TESS_EVAL)
    {
        const bool isInputCase = (m_targetStorage == glu::STORAGE_IN || m_targetStorage == glu::STORAGE_PATCH_IN);
        const bool perPatchStorage =
            (m_targetStorage == glu::STORAGE_PATCH_IN || m_targetStorage == glu::STORAGE_PATCH_OUT);
        const char *varName = (isInputCase) ? ("shaderInput") : ("shaderOutput");
        const glu::VariableDeclaration targetDecl(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), varName,
                                                  m_targetStorage);
        const glu::ShaderType shaderType =
            (m_caseType == CASE_SEPARABLE_VERTEX)    ? (glu::SHADERTYPE_VERTEX) :
            (m_caseType == CASE_SEPARABLE_FRAGMENT)  ? (glu::SHADERTYPE_FRAGMENT) :
            (m_caseType == CASE_SEPARABLE_GEOMETRY)  ? (glu::SHADERTYPE_GEOMETRY) :
            (m_caseType == CASE_SEPARABLE_TESS_CTRL) ? (glu::SHADERTYPE_TESSELLATION_CONTROL) :
            (m_caseType == CASE_SEPARABLE_TESS_EVAL) ? (glu::SHADERTYPE_TESSELLATION_EVALUATION) :
                                                       (glu::SHADERTYPE_LAST);
        const bool arrayedInterface = (isInputCase) ? ((shaderType == glu::SHADERTYPE_GEOMETRY) ||
                                                       (shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL) ||
                                                       (shaderType == glu::SHADERTYPE_TESSELLATION_EVALUATION)) :
                                                      (shaderType == glu::SHADERTYPE_TESSELLATION_CONTROL);

        m_program->setSeparable(true);

        if (arrayedInterface && !perPatchStorage)
        {
            const glu::VariableDeclaration targetDeclArr(glu::VarType(targetDecl.varType, glu::VarType::UNSIZED_ARRAY),
                                                         varName, m_targetStorage);
            m_program->addShader(shaderType, glslVersion)->getDefaultBlock().variables.push_back(targetDeclArr);
        }
        else
        {
            m_program->addShader(shaderType, glslVersion)->getDefaultBlock().variables.push_back(targetDecl);
        }
    }
    else if (m_caseType == CASE_VERTEX_FRAGMENT || m_caseType == CASE_VERTEX_GEO_FRAGMENT ||
             m_caseType == CASE_VERTEX_TESS_FRAGMENT || m_caseType == CASE_VERTEX_TESS_GEO_FRAGMENT)
    {
        ProgramInterfaceDefinition::Shader *vertex   = m_program->addShader(glu::SHADERTYPE_VERTEX, glslVersion);
        ProgramInterfaceDefinition::Shader *fragment = m_program->addShader(glu::SHADERTYPE_FRAGMENT, glslVersion);

        m_program->setSeparable(false);

        vertex->getDefaultBlock().variables.push_back(glu::VariableDeclaration(
            glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderInput", glu::STORAGE_IN));
        vertex->getDefaultBlock().variables.push_back(
            glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderOutput",
                                     glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(1)));

        fragment->getDefaultBlock().variables.push_back(
            glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderOutput",
                                     glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(0)));
        fragment->getDefaultBlock().variables.push_back(
            glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderInput",
                                     glu::STORAGE_IN, glu::INTERPOLATION_LAST, glu::Layout(1)));

        if (m_caseType == CASE_VERTEX_TESS_FRAGMENT || m_caseType == CASE_VERTEX_TESS_GEO_FRAGMENT)
        {
            ProgramInterfaceDefinition::Shader *tessCtrl =
                m_program->addShader(glu::SHADERTYPE_TESSELLATION_CONTROL, glslVersion);
            ProgramInterfaceDefinition::Shader *tessEval =
                m_program->addShader(glu::SHADERTYPE_TESSELLATION_EVALUATION, glslVersion);

            tessCtrl->getDefaultBlock().variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY),
                "shaderInput", glu::STORAGE_IN, glu::INTERPOLATION_LAST, glu::Layout(1)));
            tessCtrl->getDefaultBlock().variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY),
                "shaderOutput", glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(1)));

            tessEval->getDefaultBlock().variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY),
                "shaderInput", glu::STORAGE_IN, glu::INTERPOLATION_LAST, glu::Layout(1)));
            tessEval->getDefaultBlock().variables.push_back(
                glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderOutput",
                                         glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(1)));
        }

        if (m_caseType == CASE_VERTEX_GEO_FRAGMENT || m_caseType == CASE_VERTEX_TESS_GEO_FRAGMENT)
        {
            ProgramInterfaceDefinition::Shader *geometry = m_program->addShader(glu::SHADERTYPE_GEOMETRY, glslVersion);

            geometry->getDefaultBlock().variables.push_back(glu::VariableDeclaration(
                glu::VarType(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), glu::VarType::UNSIZED_ARRAY),
                "shaderInput", glu::STORAGE_IN, glu::INTERPOLATION_LAST, glu::Layout(1)));
            geometry->getDefaultBlock().variables.push_back(
                glu::VariableDeclaration(glu::VarType(glu::TYPE_FLOAT_VEC4, glu::PRECISION_HIGHP), "shaderOutput",
                                         glu::STORAGE_OUT, glu::INTERPOLATION_LAST, glu::Layout(1)));
        }
    }
    else
        DE_ASSERT(false);

    if (m_program->hasStage(glu::SHADERTYPE_GEOMETRY))
        m_program->setGeometryNumOutputVertices(1);
    if (m_program->hasStage(glu::SHADERTYPE_TESSELLATION_CONTROL) ||
        m_program->hasStage(glu::SHADERTYPE_TESSELLATION_EVALUATION))
        m_program->setTessellationNumOutputPatchVertices(1);

    DE_ASSERT(m_program->isValid());
}

void ProgramInputOutputReferencedByCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;
}

ProgramInputOutputReferencedByCase::IterateResult ProgramInputOutputReferencedByCase::iterate(void)
{
    static const struct
    {
        glw::GLenum propName;
        glu::ShaderType shaderType;
        const char *extension;
    } targetProps[] = {
        {GL_REFERENCED_BY_VERTEX_SHADER, glu::SHADERTYPE_VERTEX, DE_NULL},
        {GL_REFERENCED_BY_FRAGMENT_SHADER, glu::SHADERTYPE_FRAGMENT, DE_NULL},
        {GL_REFERENCED_BY_COMPUTE_SHADER, glu::SHADERTYPE_COMPUTE, DE_NULL},
        {GL_REFERENCED_BY_TESS_CONTROL_SHADER, glu::SHADERTYPE_TESSELLATION_CONTROL, "GL_EXT_tessellation_shader"},
        {GL_REFERENCED_BY_TESS_EVALUATION_SHADER, glu::SHADERTYPE_TESSELLATION_EVALUATION,
         "GL_EXT_tessellation_shader"},
        {GL_REFERENCED_BY_GEOMETRY_SHADER, glu::SHADERTYPE_GEOMETRY, "GL_EXT_geometry_shader"},
    };

    const bool isInputCase   = (m_targetStorage == glu::STORAGE_IN || m_targetStorage == glu::STORAGE_PATCH_IN);
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(), generateProgramInterfaceProgramSources(m_program));
    const std::string targetResourceName = (isInputCase) ? ("shaderInput") : ("shaderOutput");
    const glw::GLenum programGLInterface = (isInputCase) ? (GL_PROGRAM_INPUT) : (GL_PROGRAM_OUTPUT);
    glw::GLuint resourceIndex;

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    checkAndLogProgram(program, m_program, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // find target resource index

    resourceIndex = gl.getProgramResourceIndex(program.getProgram(), programGLInterface, targetResourceName.c_str());
    GLU_EXPECT_NO_ERROR(gl.getError(), "query resource index");

    if (resourceIndex == GL_INVALID_INDEX)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Error, query for resource \"" << targetResourceName
                           << "\" index returned invalid index." << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "could not find target resource");
        return STOP;
    }

    // check props
    for (int propNdx = 0; propNdx < DE_LENGTH_OF_ARRAY(targetProps); ++propNdx)
    {
        if (targetProps[propNdx].extension == DE_NULL ||
            m_context.getContextInfo().isExtensionSupported(targetProps[propNdx].extension))
        {
            const glw::GLenum prop = targetProps[propNdx].propName;
            const bool expected    = (isInputCase) ? (targetProps[propNdx].shaderType == m_program->getFirstStage()) :
                                                     (targetProps[propNdx].shaderType == m_program->getLastStage());
            glw::GLint value       = -1;
            glw::GLint written     = -1;

            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying " << glu::getProgramResourcePropertyName(prop)
                               << ", expecting " << ((expected) ? ("TRUE") : ("FALSE")) << tcu::TestLog::EndMessage;

            gl.getProgramResourceiv(program.getProgram(), programGLInterface, resourceIndex, 1, &prop, 1, &written,
                                    &value);
            GLU_EXPECT_NO_ERROR(gl.getError(), "query buffer binding");

            if (written != 1)
            {
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Error, query for referenced_by_* returned invalid number of values."
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "property query failed");
                continue;
            }

            m_testCtx.getLog() << tcu::TestLog::Message << glu::getProgramResourcePropertyName(prop) << " = "
                               << glu::getBooleanStr(value) << tcu::TestLog::EndMessage;

            if (value != ((expected) ? (GL_TRUE) : (GL_FALSE)))
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "Error, got unexpected value"
                                   << tcu::TestLog::EndMessage;
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "unexpected property value");
                continue;
            }
        }
    }

    return STOP;
}

class FeedbackResourceListTestCase : public ResourceListTestCase
{
public:
    FeedbackResourceListTestCase(Context &context, const ResourceDefinition::Node::SharedPtr &resource,
                                 const char *name);
    ~FeedbackResourceListTestCase(void);

private:
    IterateResult iterate(void);
};

FeedbackResourceListTestCase::FeedbackResourceListTestCase(Context &context,
                                                           const ResourceDefinition::Node::SharedPtr &resource,
                                                           const char *name)
    : ResourceListTestCase(context, resource, PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, name)
{
}

FeedbackResourceListTestCase::~FeedbackResourceListTestCase(void)
{
    deinit();
}

FeedbackResourceListTestCase::IterateResult FeedbackResourceListTestCase::iterate(void)
{
    const glu::ShaderProgram program(m_context.getRenderContext(),
                                     generateProgramInterfaceProgramSources(m_programDefinition));

    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

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

    checkAndLogProgram(program, m_programDefinition, m_context.getRenderContext().getFunctions(), m_testCtx.getLog());

    // Check resource list
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "ResourceList", "Resource list");
        std::vector<std::string> resourceList;
        std::vector<std::string> expectedResources;

        queryResourceList(resourceList, program.getProgram());
        expectedResources = getProgramInterfaceResourceList(m_programDefinition, m_programInterface);

        // verify the list and the expected list match

        if (!verifyResourceList(resourceList, expectedResources))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid resource list");

        // verify GetProgramResourceIndex() matches the indices of the list

        if (!verifyResourceIndexQuery(resourceList, expectedResources, program.getProgram()))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "GetProgramResourceIndex returned unexpected values");

        // Verify MAX_NAME_LENGTH
        if (!verifyMaxNameLength(resourceList, program.getProgram()))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "MAX_NAME_LENGTH invalid");
    }

    return STOP;
}

int InterfaceBlockDataSizeTestCase::getBlockMinDataSize(const glu::InterfaceBlock &block) const
{
    int dataSize = 0;

    for (int ndx = 0; ndx < (int)block.variables.size(); ++ndx)
        dataSize += getVarTypeSize(block.variables[ndx].varType);

    return dataSize;
}

static bool isDataTypeLayoutQualified(glu::DataType type)
{
    return glu::isDataTypeImage(type) || glu::isDataTypeAtomicCounter(type);
}

static void generateVariableCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                  tcu::TestCaseGroup *const targetGroup,
                                  const ProgramResourceQueryTestTarget &queryTarget, int expandLevel = 3,
                                  bool createTestGroup = true)
{
    static const struct
    {
        int level;
        glu::DataType dataType;
    } variableTypes[] = {
        {0, glu::TYPE_FLOAT},        {1, glu::TYPE_INT},          {1, glu::TYPE_UINT},
        {1, glu::TYPE_BOOL},

        {3, glu::TYPE_FLOAT_VEC2},   {1, glu::TYPE_FLOAT_VEC3},   {1, glu::TYPE_FLOAT_VEC4},

        {3, glu::TYPE_INT_VEC2},     {2, glu::TYPE_INT_VEC3},     {3, glu::TYPE_INT_VEC4},

        {3, glu::TYPE_UINT_VEC2},    {2, glu::TYPE_UINT_VEC3},    {3, glu::TYPE_UINT_VEC4},

        {3, glu::TYPE_BOOL_VEC2},    {2, glu::TYPE_BOOL_VEC3},    {3, glu::TYPE_BOOL_VEC4},

        {2, glu::TYPE_FLOAT_MAT2},   {3, glu::TYPE_FLOAT_MAT2X3}, {3, glu::TYPE_FLOAT_MAT2X4},
        {2, glu::TYPE_FLOAT_MAT3X2}, {2, glu::TYPE_FLOAT_MAT3},   {3, glu::TYPE_FLOAT_MAT3X4},
        {2, glu::TYPE_FLOAT_MAT4X2}, {3, glu::TYPE_FLOAT_MAT4X3}, {2, glu::TYPE_FLOAT_MAT4},
    };

    tcu::TestCaseGroup *group;

    if (createTestGroup)
    {
        group = new tcu::TestCaseGroup(context.getTestContext(), "basic_type", "Basic variable");
        targetGroup->addChild(group);
    }
    else
        group = targetGroup;

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
    {
        if (variableTypes[ndx].level <= expandLevel)
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].dataType));
            group->addChild(new ResourceTestCase(context, variable, queryTarget));
        }
    }
}

static void generateOpaqueTypeCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                    tcu::TestCaseGroup *const targetGroup,
                                    const ProgramResourceQueryTestTarget &queryTarget, int expandLevel = 3,
                                    bool createTestGroup = true)
{
    static const struct
    {
        int level;
        glu::DataType dataType;
    } variableTypes[] = {
        {0, glu::TYPE_SAMPLER_2D},
        {2, glu::TYPE_SAMPLER_CUBE},
        {1, glu::TYPE_SAMPLER_2D_ARRAY},
        {1, glu::TYPE_SAMPLER_3D},
        {2, glu::TYPE_SAMPLER_2D_SHADOW},
        {3, glu::TYPE_SAMPLER_CUBE_SHADOW},
        {3, glu::TYPE_SAMPLER_2D_ARRAY_SHADOW},
        {1, glu::TYPE_INT_SAMPLER_2D},
        {3, glu::TYPE_INT_SAMPLER_CUBE},
        {3, glu::TYPE_INT_SAMPLER_2D_ARRAY},
        {3, glu::TYPE_INT_SAMPLER_3D},
        {2, glu::TYPE_UINT_SAMPLER_2D},
        {3, glu::TYPE_UINT_SAMPLER_CUBE},
        {3, glu::TYPE_UINT_SAMPLER_2D_ARRAY},
        {3, glu::TYPE_UINT_SAMPLER_3D},
        {2, glu::TYPE_SAMPLER_2D_MULTISAMPLE},
        {2, glu::TYPE_INT_SAMPLER_2D_MULTISAMPLE},
        {3, glu::TYPE_UINT_SAMPLER_2D_MULTISAMPLE},
        {1, glu::TYPE_IMAGE_2D},
        {3, glu::TYPE_IMAGE_CUBE},
        {3, glu::TYPE_IMAGE_2D_ARRAY},
        {3, glu::TYPE_IMAGE_3D},
        {3, glu::TYPE_INT_IMAGE_2D},
        {3, glu::TYPE_INT_IMAGE_CUBE},
        {1, glu::TYPE_INT_IMAGE_2D_ARRAY},
        {3, glu::TYPE_INT_IMAGE_3D},
        {2, glu::TYPE_UINT_IMAGE_2D},
        {3, glu::TYPE_UINT_IMAGE_CUBE},
        {3, glu::TYPE_UINT_IMAGE_2D_ARRAY},
        {3, glu::TYPE_UINT_IMAGE_3D},
        {1, glu::TYPE_UINT_ATOMIC_COUNTER},
    };

    bool isStructMember = false;

    // Requirements
    for (const ResourceDefinition::Node *node = parentStructure.get(); node; node = node->getEnclosingNode())
    {
        // Don't insert inside a interface block
        if (node->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
            return;

        isStructMember |= (node->getType() == ResourceDefinition::Node::TYPE_STRUCT_MEMBER);
    }

    // Add cases
    {
        tcu::TestCaseGroup *group;

        if (createTestGroup)
        {
            group = new tcu::TestCaseGroup(context.getTestContext(), "opaque_type", "Opaque types");
            targetGroup->addChild(group);
        }
        else
            group = targetGroup;

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
        {
            if (variableTypes[ndx].level > expandLevel)
                continue;

            // Layout qualifiers are not allowed on struct members
            if (isDataTypeLayoutQualified(variableTypes[ndx].dataType) && isStructMember)
                continue;

            {
                const ResourceDefinition::Node::SharedPtr variable(
                    new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].dataType));
                group->addChild(new ResourceTestCase(context, variable, queryTarget));
            }
        }
    }
}

static void generateCompoundVariableCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                          tcu::TestCaseGroup *const targetGroup,
                                          const ProgramResourceQueryTestTarget &queryTarget, int expandLevel = 3);

static void generateVariableArrayCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                       tcu::TestCaseGroup *const targetGroup,
                                       const ProgramResourceQueryTestTarget &queryTarget, int expandLevel = 3)
{
    if (expandLevel > 0)
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "array", "Arrays");

        targetGroup->addChild(blockGroup);

        // Arrays of basic variables
        generateVariableCases(context, arrayElement, blockGroup, queryTarget, expandLevel, expandLevel != 1);

        // Arrays of opaque types
        generateOpaqueTypeCases(context, arrayElement, blockGroup, queryTarget, expandLevel, expandLevel != 1);

        // Arrays of arrays
        generateVariableArrayCases(context, arrayElement, blockGroup, queryTarget, expandLevel - 1);

        // Arrays of structs
        generateCompoundVariableCases(context, arrayElement, blockGroup, queryTarget, expandLevel - 1);
    }
}

static void generateCompoundVariableCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                          tcu::TestCaseGroup *const targetGroup,
                                          const ProgramResourceQueryTestTarget &queryTarget, int expandLevel)
{
    if (expandLevel > 0)
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "struct", "Structs");

        targetGroup->addChild(blockGroup);

        // Struct containing basic variable
        generateVariableCases(context, structMember, blockGroup, queryTarget, expandLevel, expandLevel != 1);

        // Struct containing opaque types
        generateOpaqueTypeCases(context, structMember, blockGroup, queryTarget, expandLevel, expandLevel != 1);

        // Struct containing arrays
        generateVariableArrayCases(context, structMember, blockGroup, queryTarget, expandLevel - 1);

        // Struct containing struct
        generateCompoundVariableCases(context, structMember, blockGroup, queryTarget, expandLevel - 1);
    }
}

// Resource list cases

enum BlockFlags
{
    BLOCKFLAG_DEFAULT = 0x01,
    BLOCKFLAG_NAMED   = 0x02,
    BLOCKFLAG_UNNAMED = 0x04,
    BLOCKFLAG_ARRAY   = 0x08,

    BLOCKFLAG_ALL = 0x0F
};

static void generateUniformCaseBlocks(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                      tcu::TestCaseGroup *const targetGroup, uint32_t blockFlags,
                                      void (*blockContentGenerator)(Context &,
                                                                    const ResourceDefinition::Node::SharedPtr &,
                                                                    tcu::TestCaseGroup *const))
{
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr uniform(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));

    // .default_block
    if (blockFlags & BLOCKFLAG_DEFAULT)
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(context.getTestContext(), "default_block", "Default block");
        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, uniform, blockGroup);
    }

    // .named_block
    if (blockFlags & BLOCKFLAG_NAMED)
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(uniform, true));

        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(context.getTestContext(), "named_block", "Named uniform block");
        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, block, blockGroup);
    }

    // .unnamed_block
    if (blockFlags & BLOCKFLAG_UNNAMED)
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(uniform, false));

        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(context.getTestContext(), "unnamed_block", "Unnamed uniform block");
        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, block, blockGroup);
    }

    // .block_array
    if (blockFlags & BLOCKFLAG_ARRAY)
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(uniform));
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(arrayElement, true));

        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(context.getTestContext(), "block_array", "Uniform block array");
        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, block, blockGroup);
    }
}

static void generateBufferBackedResourceListBlockContentCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup,
    ProgramInterface interface, int depth)
{
    // variable
    {
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(parentStructure, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new ResourceListTestCase(context, variable, interface));
    }

    // struct
    if (depth > 0)
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        generateBufferBackedResourceListBlockContentCases(context, structMember, targetGroup, interface, depth - 1);
    }

    // array
    if (depth > 0)
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
        generateBufferBackedResourceListBlockContentCases(context, arrayElement, targetGroup, interface, depth - 1);
    }
}

static void generateBufferBackedVariableAggregateTypeCases(Context &context,
                                                           const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                           tcu::TestCaseGroup *const targetGroup,
                                                           ProgramInterface interface,
                                                           ProgramResourcePropFlags targetProp, glu::DataType dataType,
                                                           const std::string &nameSuffix, int depth)
{
    // variable
    {
        const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, dataType));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(interface, targetProp), ("var" + nameSuffix).c_str()));
    }

    // struct
    if (depth > 0)
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        generateBufferBackedVariableAggregateTypeCases(context, structMember, targetGroup, interface, targetProp,
                                                       dataType, "_struct" + nameSuffix, depth - 1);
    }

    // array
    if (depth > 0)
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
        generateBufferBackedVariableAggregateTypeCases(context, arrayElement, targetGroup, interface, targetProp,
                                                       dataType, "_array" + nameSuffix, depth - 1);
    }
}

static void generateUniformResourceListBlockContents(Context &context,
                                                     const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                     tcu::TestCaseGroup *const targetGroup)
{
    generateBufferBackedResourceListBlockContentCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_UNIFORM,
                                                      4);
}

static void generateUniformBlockArraySizeContents(Context &context,
                                                  const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                  tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_ARRAY_SIZE);
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
    const bool namedNonArrayBlock =
        isInterfaceBlock && static_cast<const ResourceDefinition::InterfaceBlock *>(parentStructure.get())->m_named &&
        parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;

    if (!isInterfaceBlock || namedNonArrayBlock)
    {
        // .types
        {
            tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
            targetGroup->addChild(blockGroup);

            generateVariableCases(context, parentStructure, blockGroup, queryTarget, 2, false);
            generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 2, false);
        }

        // aggregates
        {
            tcu::TestCaseGroup *const blockGroup =
                new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
            targetGroup->addChild(blockGroup);

            generateBufferBackedVariableAggregateTypeCases(context, parentStructure, blockGroup, queryTarget.interface,
                                                           PROGRAMRESOURCEPROP_ARRAY_SIZE, glu::TYPE_FLOAT, "", 3);
        }
    }
    else
    {
        // aggregates
        generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, queryTarget.interface,
                                                       PROGRAMRESOURCEPROP_ARRAY_SIZE, glu::TYPE_FLOAT, "", 2);
    }
}

static void generateBufferBackedArrayStrideTypeAggregateSubCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup,
    const std::string &namePrefix, ProgramInterface interface, glu::DataType type, int expandLevel)
{
    // case
    {
        const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, type));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_ARRAY_STRIDE),
            namePrefix.c_str()));
    }

    if (expandLevel > 0)
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));

        // _struct
        generateBufferBackedArrayStrideTypeAggregateSubCases(context, structMember, targetGroup, namePrefix + "_struct",
                                                             interface, type, expandLevel - 1);

        // _array
        generateBufferBackedArrayStrideTypeAggregateSubCases(context, arrayElement, targetGroup, namePrefix + "_array",
                                                             interface, type, expandLevel - 1);
    }
}

static void generateBufferBackedArrayStrideTypeAggregateCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup,
    ProgramInterface interface, glu::DataType type, int expandLevel, bool includeBaseCase)
{
    const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
    const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
    const std::string namePrefix = glu::getDataTypeName(type);

    if (expandLevel == 0 || includeBaseCase)
    {
        const ResourceDefinition::Node::SharedPtr variable(new ResourceDefinition::Variable(parentStructure, type));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_ARRAY_STRIDE),
            namePrefix.c_str()));
    }
    if (expandLevel >= 1)
    {
        // _struct
        if (!glu::isDataTypeAtomicCounter(type))
            generateBufferBackedArrayStrideTypeAggregateSubCases(
                context, structMember, targetGroup, namePrefix + "_struct", interface, type, expandLevel - 1);

        // _array
        generateBufferBackedArrayStrideTypeAggregateSubCases(context, arrayElement, targetGroup, namePrefix + "_array",
                                                             interface, type, expandLevel - 1);
    }
}

static void generateUniformBlockArrayStrideContents(Context &context,
                                                    const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                    tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_ARRAY_STRIDE);
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
    const bool namedNonArrayBlock =
        isInterfaceBlock && static_cast<const ResourceDefinition::InterfaceBlock *>(parentStructure.get())->m_named &&
        parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;

    if (!isInterfaceBlock || namedNonArrayBlock)
    {
        // .types
        {
            tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
            targetGroup->addChild(blockGroup);

            generateVariableCases(context, parentStructure, blockGroup, queryTarget, 2, false);
            generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 2, false);
        }

        // .aggregates
        {
            tcu::TestCaseGroup *const blockGroup =
                new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
            targetGroup->addChild(blockGroup);

            // .sampler_2d_*
            if (!isInterfaceBlock)
                generateBufferBackedArrayStrideTypeAggregateCases(
                    context, parentStructure, blockGroup, queryTarget.interface, glu::TYPE_SAMPLER_2D, 1, false);

            // .atomic_counter_*
            if (!isInterfaceBlock)
            {
                const ResourceDefinition::Node::SharedPtr layout(
                    new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, 0)));
                generateBufferBackedArrayStrideTypeAggregateCases(context, layout, blockGroup, queryTarget.interface,
                                                                  glu::TYPE_UINT_ATOMIC_COUNTER, 1, false);
            }

            // .float_*
            generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup,
                                                              queryTarget.interface, glu::TYPE_FLOAT, 2, false);

            // .bool_*
            generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup,
                                                              queryTarget.interface, glu::TYPE_BOOL, 1, false);

            // .bvec3_*
            generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup,
                                                              queryTarget.interface, glu::TYPE_BOOL_VEC3, 2, false);

            // .vec3_*
            generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup,
                                                              queryTarget.interface, glu::TYPE_FLOAT_VEC3, 2, false);

            // .ivec2_*
            generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, blockGroup,
                                                              queryTarget.interface, glu::TYPE_INT_VEC3, 2, false);
        }
    }
    else
    {
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
    }
}

static void generateUniformBlockLocationContents(Context &context,
                                                 const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                 tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_LOCATION);
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);

    if (!isInterfaceBlock)
    {
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 3);
        generateOpaqueTypeCases(context, parentStructure, targetGroup, queryTarget, 3);
        generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 2);
        generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 2);
    }
    else
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1, false);
}

static void generateUniformBlockBlockIndexContents(Context &context, tcu::TestCaseGroup *const targetGroup,
                                                   glu::GLSLVersion glslVersion)
{
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr uniform(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
    const ResourceDefinition::Node::SharedPtr binding(
        new ResourceDefinition::LayoutQualifier(uniform, glu::Layout(-1, 0)));

    // .default_block
    {
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(uniform, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX),
            "default_block"));
    }

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(binding, true));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX), "named_block"));
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(binding, false));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX),
            "unnamed_block"));
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(binding));
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_BLOCK_INDEX), "block_array"));
    }
}

static void generateUniformBlockAtomicCounterBufferIndexContents(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM,
                                                     PROGRAMRESOURCEPROP_ATOMIC_COUNTER_BUFFER_INDEX);
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);

    if (!isInterfaceBlock)
    {
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 3);
        generateOpaqueTypeCases(context, parentStructure, targetGroup, queryTarget, 3);

        // .array
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(
                new ResourceDefinition::ArrayElement(parentStructure));
            const ResourceDefinition::Node::SharedPtr arrayArrayElement(
                new ResourceDefinition::ArrayElement(arrayElement));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));
            const ResourceDefinition::Node::SharedPtr elementvariable(
                new ResourceDefinition::Variable(arrayArrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));
            tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "array", "Arrays");

            targetGroup->addChild(blockGroup);

            blockGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "var_array"));
            blockGroup->addChild(new ResourceTestCase(context, elementvariable, queryTarget, "var_array_array"));
        }
    }
    else
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1, false);
}

static void generateUniformBlockNameLengthContents(Context &context,
                                                   const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                   tcu::TestCaseGroup *const targetGroup)
{
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
    const bool namedNonArrayBlock =
        isInterfaceBlock && static_cast<const ResourceDefinition::InterfaceBlock *>(parentStructure.get())->m_named &&
        parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;

    if (!isInterfaceBlock || namedNonArrayBlock)
        generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_UNIFORM,
                                                       PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 2);
    else
        generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup, PROGRAMINTERFACE_UNIFORM,
                                                       PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 1);
}

static void generateUniformBlockTypeContents(Context &context,
                                             const ResourceDefinition::Node::SharedPtr &parentStructure,
                                             tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_TYPE);
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
    const bool namedNonArrayBlock =
        isInterfaceBlock && static_cast<const ResourceDefinition::InterfaceBlock *>(parentStructure.get())->m_named &&
        parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;

    if (!isInterfaceBlock || namedNonArrayBlock)
    {
        // .types
        {
            tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
            targetGroup->addChild(blockGroup);

            generateVariableCases(context, parentStructure, blockGroup, queryTarget, 3, false);
            generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 3, false);
        }

        generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
    }
    else
    {
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
    }
}

static void generateUniformBlockOffsetContents(Context &context,
                                               const ResourceDefinition::Node::SharedPtr &parentStructure,
                                               tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_OFFSET);
    const bool isInterfaceBlock = (parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);
    const bool namedNonArrayBlock =
        isInterfaceBlock && static_cast<const ResourceDefinition::InterfaceBlock *>(parentStructure.get())->m_named &&
        parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;

    if (!isInterfaceBlock)
    {
        // .types
        {
            tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
            targetGroup->addChild(blockGroup);

            generateVariableCases(context, parentStructure, blockGroup, queryTarget, 3, false);
            generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 3, false);
        }

        // .aggregates
        {
            tcu::TestCaseGroup *const blockGroup =
                new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
            targetGroup->addChild(blockGroup);

            // .atomic_uint_struct
            // .atomic_uint_array
            {
                const ResourceDefinition::Node::SharedPtr offset(
                    new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, -1, 4)));
                const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(offset));
                const ResourceDefinition::Node::SharedPtr elementVariable(
                    new ResourceDefinition::Variable(arrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));

                blockGroup->addChild(new ResourceTestCase(context, elementVariable, queryTarget, "atomic_uint_array"));
            }

            // .float_array
            // .float_struct
            {
                const ResourceDefinition::Node::SharedPtr structMember(
                    new ResourceDefinition::StructMember(parentStructure));
                const ResourceDefinition::Node::SharedPtr arrayElement(
                    new ResourceDefinition::ArrayElement(parentStructure));
                const ResourceDefinition::Node::SharedPtr memberVariable(
                    new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));
                const ResourceDefinition::Node::SharedPtr elementVariable(
                    new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));

                blockGroup->addChild(new ResourceTestCase(context, memberVariable, queryTarget, "float_struct"));
                blockGroup->addChild(new ResourceTestCase(context, elementVariable, queryTarget, "float_array"));
            }
        }
    }
    else if (namedNonArrayBlock)
    {
        // .types
        {
            tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
            targetGroup->addChild(blockGroup);

            generateVariableCases(context, parentStructure, blockGroup, queryTarget, 3, false);
            generateOpaqueTypeCases(context, parentStructure, blockGroup, queryTarget, 3, false);
        }

        // .aggregates
        {
            tcu::TestCaseGroup *const blockGroup =
                new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
            targetGroup->addChild(blockGroup);

            // .float_array
            // .float_struct
            {
                const ResourceDefinition::Node::SharedPtr structMember(
                    new ResourceDefinition::StructMember(parentStructure));
                const ResourceDefinition::Node::SharedPtr arrayElement(
                    new ResourceDefinition::StructMember(parentStructure));
                const ResourceDefinition::Node::SharedPtr memberVariable(
                    new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));
                const ResourceDefinition::Node::SharedPtr elementVariable(
                    new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));

                blockGroup->addChild(new ResourceTestCase(context, memberVariable, queryTarget, "float_struct"));
                blockGroup->addChild(new ResourceTestCase(context, elementVariable, queryTarget, "float_array"));
            }
        }
    }
    else
    {
        generateVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateVariableArrayCases(context, parentStructure, targetGroup, queryTarget, 1);
        generateCompoundVariableCases(context, parentStructure, targetGroup, queryTarget, 1);
    }
}

static void generateMatrixVariableCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                        tcu::TestCaseGroup *const targetGroup,
                                        const ProgramResourceQueryTestTarget &queryTarget, bool createTestGroup = true,
                                        int expandLevel = 2)
{
    static const struct
    {
        int priority;
        glu::DataType type;
    } variableTypes[] = {
        {0, glu::TYPE_FLOAT_MAT2},   {1, glu::TYPE_FLOAT_MAT2X3}, {2, glu::TYPE_FLOAT_MAT2X4},
        {2, glu::TYPE_FLOAT_MAT3X2}, {1, glu::TYPE_FLOAT_MAT3},   {0, glu::TYPE_FLOAT_MAT3X4},
        {2, glu::TYPE_FLOAT_MAT4X2}, {1, glu::TYPE_FLOAT_MAT4X3}, {0, glu::TYPE_FLOAT_MAT4},
    };

    tcu::TestCaseGroup *group;

    if (createTestGroup)
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(context.getTestContext(), "matrix", "Basic matrix type");
        targetGroup->addChild(blockGroup);
        group = blockGroup;
    }
    else
        group = targetGroup;

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
    {
        if (variableTypes[ndx].priority < expandLevel)
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].type));
            group->addChild(new ResourceTestCase(context, variable, queryTarget));
        }
    }
}

static void generateMatrixStructCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                      tcu::TestCaseGroup *const targetGroup,
                                      const ProgramResourceQueryTestTarget &queryTarget, int expandLevel);

static void generateMatrixArrayCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                     tcu::TestCaseGroup *const targetGroup,
                                     const ProgramResourceQueryTestTarget &queryTarget, int expandLevel)
{
    if (expandLevel > 0)
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "array", "Arrays");

        targetGroup->addChild(blockGroup);

        // Arrays of basic variables
        generateMatrixVariableCases(context, arrayElement, blockGroup, queryTarget, expandLevel != 1, expandLevel);

        // Arrays of arrays
        generateMatrixArrayCases(context, arrayElement, blockGroup, queryTarget, expandLevel - 1);

        // Arrays of structs
        generateMatrixStructCases(context, arrayElement, blockGroup, queryTarget, expandLevel - 1);
    }
}

static void generateMatrixStructCases(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                                      tcu::TestCaseGroup *const targetGroup,
                                      const ProgramResourceQueryTestTarget &queryTarget, int expandLevel)
{
    if (expandLevel > 0)
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "struct", "Structs");

        targetGroup->addChild(blockGroup);

        // Struct containing basic variable
        generateMatrixVariableCases(context, structMember, blockGroup, queryTarget, expandLevel != 1, expandLevel);

        // Struct containing arrays
        generateMatrixArrayCases(context, structMember, blockGroup, queryTarget, expandLevel - 1);

        // Struct containing struct
        generateMatrixStructCases(context, structMember, blockGroup, queryTarget, expandLevel - 1);
    }
}

static void generateUniformMatrixOrderCaseBlockContentCases(Context &context,
                                                            const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                            tcu::TestCaseGroup *const targetGroup,
                                                            bool extendedBasicTypeCases, bool opaqueCases)
{
    static const struct
    {
        const char *name;
        glu::MatrixOrder order;
    } qualifiers[] = {
        {"no_qualifier", glu::MATRIXORDER_LAST},
        {"row_major", glu::MATRIXORDER_ROW_MAJOR},
        {"column_major", glu::MATRIXORDER_COLUMN_MAJOR},
    };

    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR);

    for (int qualifierNdx = 0; qualifierNdx < DE_LENGTH_OF_ARRAY(qualifiers); ++qualifierNdx)
    {
        // Add layout qualifiers only for block members
        if (qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST ||
            parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
        {
            ResourceDefinition::Node::SharedPtr subStructure = parentStructure;
            tcu::TestCaseGroup *const qualifierGroup =
                new tcu::TestCaseGroup(context.getTestContext(), qualifiers[qualifierNdx].name, "");

            targetGroup->addChild(qualifierGroup);

            if (qualifiers[qualifierNdx].order != glu::MATRIXORDER_LAST)
            {
                glu::Layout layout;
                layout.matrixOrder = qualifiers[qualifierNdx].order;
                subStructure =
                    ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(subStructure, layout));
            }

            if (extendedBasicTypeCases && qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST)
            {
                // .types
                {
                    tcu::TestCaseGroup *const blockGroup =
                        new tcu::TestCaseGroup(context.getTestContext(), "types", "");
                    qualifierGroup->addChild(blockGroup);

                    generateVariableCases(context, subStructure, blockGroup, queryTarget, 1, false);
                    generateMatrixVariableCases(context, subStructure, blockGroup, queryTarget, false);
                    if (opaqueCases)
                        generateOpaqueTypeCases(context, subStructure, blockGroup, queryTarget, 2, false);
                }

                // .aggregates
                {
                    tcu::TestCaseGroup *const blockGroup =
                        new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "");
                    qualifierGroup->addChild(blockGroup);

                    generateBufferBackedVariableAggregateTypeCases(
                        context, subStructure, blockGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR,
                        glu::TYPE_FLOAT_MAT3X2, "", 1);
                }
            }
            else
            {
                generateBufferBackedVariableAggregateTypeCases(
                    context, subStructure, qualifierGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR,
                    glu::TYPE_FLOAT_MAT3X2, "", 1);
            }
        }
    }
}

static void generateUniformMatrixStrideCaseBlockContentCases(Context &context,
                                                             const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                             tcu::TestCaseGroup *const targetGroup,
                                                             bool extendedBasicTypeCases, bool opaqueCases)
{
    static const struct
    {
        const char *name;
        glu::MatrixOrder order;
    } qualifiers[] = {
        {"no_qualifier", glu::MATRIXORDER_LAST},
        {"row_major", glu::MATRIXORDER_ROW_MAJOR},
        {"column_major", glu::MATRIXORDER_COLUMN_MAJOR},
    };

    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_MATRIX_STRIDE);

    for (int qualifierNdx = 0; qualifierNdx < DE_LENGTH_OF_ARRAY(qualifiers); ++qualifierNdx)
    {
        // Add layout qualifiers only for block members
        if (qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST ||
            parentStructure->getType() == ResourceDefinition::Node::TYPE_INTERFACE_BLOCK)
        {
            ResourceDefinition::Node::SharedPtr subStructure = parentStructure;
            tcu::TestCaseGroup *const qualifierGroup =
                new tcu::TestCaseGroup(context.getTestContext(), qualifiers[qualifierNdx].name, "");

            targetGroup->addChild(qualifierGroup);

            if (qualifiers[qualifierNdx].order != glu::MATRIXORDER_LAST)
            {
                glu::Layout layout;
                layout.matrixOrder = qualifiers[qualifierNdx].order;
                subStructure =
                    ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(subStructure, layout));
            }

            if (extendedBasicTypeCases)
            {
                // .types
                // .matrix
                if (qualifiers[qualifierNdx].order == glu::MATRIXORDER_LAST)
                {
                    tcu::TestCaseGroup *const blockGroup =
                        new tcu::TestCaseGroup(context.getTestContext(), "types", "");
                    qualifierGroup->addChild(blockGroup);

                    generateVariableCases(context, subStructure, blockGroup, queryTarget, 1, false);
                    generateMatrixVariableCases(context, subStructure, blockGroup, queryTarget, false);
                    if (opaqueCases)
                        generateOpaqueTypeCases(context, subStructure, blockGroup, queryTarget, 2, false);
                }
                else
                    generateMatrixVariableCases(context, subStructure, qualifierGroup, queryTarget);

                // .aggregates
                {
                    tcu::TestCaseGroup *const blockGroup =
                        new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "");
                    qualifierGroup->addChild(blockGroup);

                    generateBufferBackedVariableAggregateTypeCases(
                        context, subStructure, blockGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR,
                        glu::TYPE_FLOAT_MAT3X2, "", 1);
                }
            }
            else
                generateBufferBackedVariableAggregateTypeCases(
                    context, subStructure, qualifierGroup, queryTarget.interface, PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR,
                    glu::TYPE_FLOAT_MAT3X2, "", 1);
        }
    }
}

static void generateUniformMatrixCaseBlocks(Context &context,
                                            const ResourceDefinition::Node::SharedPtr &parentStructure,
                                            tcu::TestCaseGroup *const targetGroup,
                                            void (*blockContentGenerator)(Context &,
                                                                          const ResourceDefinition::Node::SharedPtr &,
                                                                          tcu::TestCaseGroup *const, bool, bool))
{
    static const struct
    {
        const char *name;
        const char *description;
        bool block;
        bool namedBlock;
        bool extendedBasicTypeCases;
        glu::MatrixOrder order;
    } children[] = {
        {"default_block", "Default block", false, true, true, glu::MATRIXORDER_LAST},
        {"named_block", "Named uniform block", true, true, true, glu::MATRIXORDER_LAST},
        {"named_block_row_major", "Named uniform block", true, true, false, glu::MATRIXORDER_ROW_MAJOR},
        {"named_block_col_major", "Named uniform block", true, true, false, glu::MATRIXORDER_COLUMN_MAJOR},
        {"unnamed_block", "Unnamed uniform block", true, false, false, glu::MATRIXORDER_LAST},
        {"unnamed_block_row_major", "Unnamed uniform block", true, false, false, glu::MATRIXORDER_ROW_MAJOR},
        {"unnamed_block_col_major", "Unnamed uniform block", true, false, false, glu::MATRIXORDER_COLUMN_MAJOR},
    };

    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr uniform(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));

    for (int childNdx = 0; childNdx < (int)DE_LENGTH_OF_ARRAY(children); ++childNdx)
    {
        ResourceDefinition::Node::SharedPtr subStructure = uniform;
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(context.getTestContext(), children[childNdx].name, children[childNdx].description);
        const bool addOpaqueCases = children[childNdx].extendedBasicTypeCases && !children[childNdx].block;

        targetGroup->addChild(blockGroup);

        if (children[childNdx].order != glu::MATRIXORDER_LAST)
        {
            glu::Layout layout;
            layout.matrixOrder = children[childNdx].order;
            subStructure =
                ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(subStructure, layout));
        }

        if (children[childNdx].block)
            subStructure = ResourceDefinition::Node::SharedPtr(
                new ResourceDefinition::InterfaceBlock(subStructure, children[childNdx].namedBlock));

        blockContentGenerator(context, subStructure, blockGroup, children[childNdx].extendedBasicTypeCases,
                              addOpaqueCases);
    }
}

static void generateBufferReferencedByShaderInterfaceBlockCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup,
    const ProgramResourceQueryTestTarget &queryTarget, bool extendedCases)
{
    const bool isDefaultBlock = (parentStructure->getType() != ResourceDefinition::Node::TYPE_INTERFACE_BLOCK);

    // .float
    // .float_array
    // .float_struct
    {
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(parentStructure, glu::TYPE_FLOAT));
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        const ResourceDefinition::Node::SharedPtr variableArray(
            new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));
        const ResourceDefinition::Node::SharedPtr variableStruct(
            new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));

        targetGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "float"));
        targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "float_array"));
        targetGroup->addChild(new ResourceTestCase(context, variableStruct, queryTarget, "float_struct"));
    }

    // .sampler
    // .sampler_array
    // .sampler_struct
    if (isDefaultBlock)
    {
        const ResourceDefinition::Node::SharedPtr layout(
            new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, 0)));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(layout, glu::TYPE_SAMPLER_2D));
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(layout));
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        const ResourceDefinition::Node::SharedPtr variableArray(
            new ResourceDefinition::Variable(arrayElement, glu::TYPE_SAMPLER_2D));
        const ResourceDefinition::Node::SharedPtr variableStruct(
            new ResourceDefinition::Variable(structMember, glu::TYPE_SAMPLER_2D));

        targetGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "sampler"));
        targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "sampler_array"));
        targetGroup->addChild(new ResourceTestCase(context, variableStruct, queryTarget, "sampler_struct"));
    }

    // .atomic_uint
    // .atomic_uint_array
    if (isDefaultBlock)
    {
        const ResourceDefinition::Node::SharedPtr layout(
            new ResourceDefinition::LayoutQualifier(parentStructure, glu::Layout(-1, 0)));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(layout, glu::TYPE_UINT_ATOMIC_COUNTER));
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(layout));
        const ResourceDefinition::Node::SharedPtr variableArray(
            new ResourceDefinition::Variable(arrayElement, glu::TYPE_UINT_ATOMIC_COUNTER));

        targetGroup->addChild(new ResourceTestCase(context, variable, queryTarget, "atomic_uint"));
        targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "atomic_uint_array"));
    }

    if (extendedCases)
    {
        // .float_array_struct
        {
            const ResourceDefinition::Node::SharedPtr structMember(
                new ResourceDefinition::StructMember(parentStructure));
            const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(structMember));
            const ResourceDefinition::Node::SharedPtr variableArrayStruct(
                new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));

            targetGroup->addChild(
                new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_array_struct"));
        }

        // .float_struct_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(
                new ResourceDefinition::ArrayElement(parentStructure));
            const ResourceDefinition::Node::SharedPtr arrayStructMember(
                new ResourceDefinition::StructMember(arrayElement));
            const ResourceDefinition::Node::SharedPtr variableArrayStruct(
                new ResourceDefinition::Variable(arrayStructMember, glu::TYPE_FLOAT));

            targetGroup->addChild(
                new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_struct_array"));
        }

        // .float_array_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(
                new ResourceDefinition::ArrayElement(parentStructure));
            const ResourceDefinition::Node::SharedPtr subArrayElement(
                new ResourceDefinition::ArrayElement(arrayElement));
            const ResourceDefinition::Node::SharedPtr variableArrayStruct(
                new ResourceDefinition::Variable(subArrayElement, glu::TYPE_FLOAT));

            targetGroup->addChild(new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_array_array"));
        }

        // .float_struct_struct
        {
            const ResourceDefinition::Node::SharedPtr structMember(
                new ResourceDefinition::StructMember(parentStructure));
            const ResourceDefinition::Node::SharedPtr subStructMember(
                new ResourceDefinition::StructMember(structMember));
            const ResourceDefinition::Node::SharedPtr variableArrayStruct(
                new ResourceDefinition::Variable(subStructMember, glu::TYPE_FLOAT));

            targetGroup->addChild(
                new ResourceTestCase(context, variableArrayStruct, queryTarget, "float_struct_struct"));
        }

        if (queryTarget.interface == PROGRAMINTERFACE_BUFFER_VARIABLE)
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(
                new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));

            // .float_unsized_array
            {
                const ResourceDefinition::Node::SharedPtr variableArray(
                    new ResourceDefinition::Variable(arrayElement, glu::TYPE_FLOAT));

                targetGroup->addChild(new ResourceTestCase(context, variableArray, queryTarget, "float_unsized_array"));
            }

            // .float_unsized_struct_array
            {
                const ResourceDefinition::Node::SharedPtr structMember(
                    new ResourceDefinition::StructMember(arrayElement));
                const ResourceDefinition::Node::SharedPtr variableArray(
                    new ResourceDefinition::Variable(structMember, glu::TYPE_FLOAT));

                targetGroup->addChild(
                    new ResourceTestCase(context, variableArray, queryTarget, "float_unsized_struct_array"));
            }
        }
    }
}

static void generateUniformReferencedByShaderSingleBlockContentCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup,
    int expandLevel)
{
    DE_UNREF(expandLevel);

    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr uniform(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_UNIFORM,
                                                     PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER);
    const bool singleShaderCase = parentStructure->getType() == ResourceDefinition::Node::TYPE_SHADER;

    // .default_block
    {
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, "default_block", "");
        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, uniform, blockGroup, queryTarget,
                                                            singleShaderCase);
    }

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(uniform, true));
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, "uniform_block", "");

        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, block, blockGroup, queryTarget, singleShaderCase);
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(uniform, false));
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unnamed_block", "");

        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, block, blockGroup, queryTarget, false);
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(uniform));
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, "block_array", "");

        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, block, blockGroup, queryTarget, false);
    }
}

static void generateReferencedByShaderCaseBlocks(
    Context &context, tcu::TestCaseGroup *const targetGroup, glu::GLSLVersion glslVersion,
    void (*generateBlockContent)(Context &, const ResourceDefinition::Node::SharedPtr &, tcu::TestCaseGroup *,
                                 int expandLevel))
{
    static const struct
    {
        const char *name;
        glu::ShaderType stage;
        int expandLevel;
    } singleStageCases[] = {
        {"compute", glu::SHADERTYPE_COMPUTE, 3},
        {"separable_vertex", glu::SHADERTYPE_VERTEX, 2},
        {"separable_fragment", glu::SHADERTYPE_FRAGMENT, 2},
        {"separable_tess_ctrl", glu::SHADERTYPE_TESSELLATION_CONTROL, 2},
        {"separable_tess_eval", glu::SHADERTYPE_TESSELLATION_EVALUATION, 2},
        {"separable_geometry", glu::SHADERTYPE_GEOMETRY, 2},
    };
    static const struct
    {
        const char *name;
        uint32_t flags;
        int expandLevel;
        int subExpandLevel;
    } pipelines[] = {
        {
            "vertex_fragment",
            (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),
            3,
            2,
        },
        {
            "vertex_tess_fragment",
            (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) |
                (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) | (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION),
            2,
            2,
        },
        {
            "vertex_geo_fragment",
            (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) | (1 << glu::SHADERTYPE_GEOMETRY),
            2,
            2,
        },
        {
            "vertex_tess_geo_fragment",
            (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) |
                (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) | (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION) |
                (1 << glu::SHADERTYPE_GEOMETRY),
            2,
            1,
        },
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(singleStageCases); ++ndx)
    {
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, singleStageCases[ndx].name, "");
        const bool programSeparable     = (singleStageCases[ndx].stage != glu::SHADERTYPE_COMPUTE);
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(programSeparable));
        const ResourceDefinition::Node::SharedPtr stage(
            new ResourceDefinition::Shader(program, singleStageCases[ndx].stage, glslVersion));

        targetGroup->addChild(blockGroup);

        generateBlockContent(context, stage, blockGroup, singleStageCases[ndx].expandLevel);
    }

    for (int pipelineNdx = 0; pipelineNdx < DE_LENGTH_OF_ARRAY(pipelines); ++pipelineNdx)
    {
        // whole pipeline
        {
            TestCaseGroup *const blockGroup = new TestCaseGroup(context, pipelines[pipelineNdx].name, "");
            const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
            ResourceDefinition::ShaderSet *shaderSet = new ResourceDefinition::ShaderSet(
                program, glslVersion, pipelines[pipelineNdx].flags, pipelines[pipelineNdx].flags);
            targetGroup->addChild(blockGroup);

            {
                const ResourceDefinition::Node::SharedPtr shaders(shaderSet);
                generateBlockContent(context, shaders, blockGroup, pipelines[pipelineNdx].expandLevel);
            }
        }

        // only one stage
        for (int selectedStageBit = 0; selectedStageBit < glu::SHADERTYPE_LAST; ++selectedStageBit)
        {
            if (pipelines[pipelineNdx].flags & (1 << selectedStageBit))
            {
                const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
                ResourceDefinition::ShaderSet *shaderSet = new ResourceDefinition::ShaderSet(
                    program, glslVersion, pipelines[pipelineNdx].flags, (1u << selectedStageBit));
                const char *stageName           = (selectedStageBit == glu::SHADERTYPE_VERTEX)   ? ("vertex") :
                                                  (selectedStageBit == glu::SHADERTYPE_FRAGMENT) ? ("fragment") :
                                                  (selectedStageBit == glu::SHADERTYPE_GEOMETRY) ? ("geo") :
                                                  (selectedStageBit == glu::SHADERTYPE_TESSELLATION_CONTROL) ? ("tess_ctrl") :
                                                  (selectedStageBit == glu::SHADERTYPE_TESSELLATION_EVALUATION) ? ("tess_eval") :
                                                                                                                  (DE_NULL);
                const std::string setName       = std::string() + pipelines[pipelineNdx].name + "_only_" + stageName;
                TestCaseGroup *const blockGroup = new TestCaseGroup(context, setName.c_str(), "");
                const ResourceDefinition::Node::SharedPtr shaders(shaderSet);

                generateBlockContent(context, shaders, blockGroup, pipelines[pipelineNdx].subExpandLevel);
                targetGroup->addChild(blockGroup);
            }
        }
    }
}

static glu::DataType generateRandomDataType(de::Random &rnd, bool excludeOpaqueTypes)
{
    static const glu::DataType s_types[] = {glu::TYPE_FLOAT,
                                            glu::TYPE_INT,
                                            glu::TYPE_UINT,
                                            glu::TYPE_BOOL,
                                            glu::TYPE_FLOAT_VEC2,
                                            glu::TYPE_FLOAT_VEC3,
                                            glu::TYPE_FLOAT_VEC4,
                                            glu::TYPE_INT_VEC2,
                                            glu::TYPE_INT_VEC3,
                                            glu::TYPE_INT_VEC4,
                                            glu::TYPE_UINT_VEC2,
                                            glu::TYPE_UINT_VEC3,
                                            glu::TYPE_UINT_VEC4,
                                            glu::TYPE_BOOL_VEC2,
                                            glu::TYPE_BOOL_VEC3,
                                            glu::TYPE_BOOL_VEC4,
                                            glu::TYPE_FLOAT_MAT2,
                                            glu::TYPE_FLOAT_MAT2X3,
                                            glu::TYPE_FLOAT_MAT2X4,
                                            glu::TYPE_FLOAT_MAT3X2,
                                            glu::TYPE_FLOAT_MAT3,
                                            glu::TYPE_FLOAT_MAT3X4,
                                            glu::TYPE_FLOAT_MAT4X2,
                                            glu::TYPE_FLOAT_MAT4X3,
                                            glu::TYPE_FLOAT_MAT4,

                                            glu::TYPE_SAMPLER_2D,
                                            glu::TYPE_SAMPLER_CUBE,
                                            glu::TYPE_SAMPLER_2D_ARRAY,
                                            glu::TYPE_SAMPLER_3D,
                                            glu::TYPE_SAMPLER_2D_SHADOW,
                                            glu::TYPE_SAMPLER_CUBE_SHADOW,
                                            glu::TYPE_SAMPLER_2D_ARRAY_SHADOW,
                                            glu::TYPE_INT_SAMPLER_2D,
                                            glu::TYPE_INT_SAMPLER_CUBE,
                                            glu::TYPE_INT_SAMPLER_2D_ARRAY,
                                            glu::TYPE_INT_SAMPLER_3D,
                                            glu::TYPE_UINT_SAMPLER_2D,
                                            glu::TYPE_UINT_SAMPLER_CUBE,
                                            glu::TYPE_UINT_SAMPLER_2D_ARRAY,
                                            glu::TYPE_UINT_SAMPLER_3D,
                                            glu::TYPE_SAMPLER_2D_MULTISAMPLE,
                                            glu::TYPE_INT_SAMPLER_2D_MULTISAMPLE,
                                            glu::TYPE_UINT_SAMPLER_2D_MULTISAMPLE,
                                            glu::TYPE_IMAGE_2D,
                                            glu::TYPE_IMAGE_CUBE,
                                            glu::TYPE_IMAGE_2D_ARRAY,
                                            glu::TYPE_IMAGE_3D,
                                            glu::TYPE_INT_IMAGE_2D,
                                            glu::TYPE_INT_IMAGE_CUBE,
                                            glu::TYPE_INT_IMAGE_2D_ARRAY,
                                            glu::TYPE_INT_IMAGE_3D,
                                            glu::TYPE_UINT_IMAGE_2D,
                                            glu::TYPE_UINT_IMAGE_CUBE,
                                            glu::TYPE_UINT_IMAGE_2D_ARRAY,
                                            glu::TYPE_UINT_IMAGE_3D,
                                            glu::TYPE_UINT_ATOMIC_COUNTER};

    for (;;)
    {
        const glu::DataType type = s_types[rnd.getInt(0, DE_LENGTH_OF_ARRAY(s_types) - 1)];

        if (!excludeOpaqueTypes || glu::isDataTypeScalarOrVector(type) || glu::isDataTypeMatrix(type))
            return type;
    }
}

static ResourceDefinition::Node::SharedPtr generateRandomVariableDefinition(
    de::Random &rnd, const ResourceDefinition::Node::SharedPtr &parentStructure, glu::DataType baseType,
    const glu::Layout &layout, bool allowUnsized)
{
    const int maxNesting                                 = 4;
    ResourceDefinition::Node::SharedPtr currentStructure = parentStructure;
    const bool canBeInsideAStruct                        = layout.binding == -1 && !isDataTypeLayoutQualified(baseType);

    for (int nestNdx = 0; nestNdx < maxNesting; ++nestNdx)
    {
        if (allowUnsized && nestNdx == 0 && rnd.getFloat() < 0.2)
            currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(
                currentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
        else if (rnd.getFloat() < 0.3 && canBeInsideAStruct)
            currentStructure =
                ResourceDefinition::Node::SharedPtr(new ResourceDefinition::StructMember(currentStructure));
        else if (rnd.getFloat() < 0.3)
            currentStructure =
                ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure));
        else
            break;
    }

    return ResourceDefinition::Node::SharedPtr(new ResourceDefinition::Variable(currentStructure, baseType));
}

static ResourceDefinition::Node::SharedPtr generateRandomCoreShaderSet(de::Random &rnd, glu::GLSLVersion glslVersion)
{
    if (rnd.getFloat() < 0.5f)
    {
        // compute only
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
        return ResourceDefinition::Node::SharedPtr(
            new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    }
    else if (rnd.getFloat() < 0.5f)
    {
        // vertex and fragment
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
        ResourceDefinition::ShaderSet *shaderSet = new ResourceDefinition::ShaderSet(program, glslVersion);

        if (rnd.getBool())
        {
            shaderSet->setStage(glu::SHADERTYPE_VERTEX, true);
            shaderSet->setStage(glu::SHADERTYPE_FRAGMENT, rnd.getBool());
        }
        else
        {
            shaderSet->setStage(glu::SHADERTYPE_VERTEX, rnd.getBool());
            shaderSet->setStage(glu::SHADERTYPE_FRAGMENT, true);
        }

        return ResourceDefinition::Node::SharedPtr(shaderSet);
    }
    else
    {
        // separate vertex or fragment
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(true));
        const glu::ShaderType shaderType = (rnd.getBool()) ? (glu::SHADERTYPE_VERTEX) : (glu::SHADERTYPE_FRAGMENT);

        return ResourceDefinition::Node::SharedPtr(new ResourceDefinition::Shader(program, shaderType, glslVersion));
    }
}

static ResourceDefinition::Node::SharedPtr generateRandomExtShaderSet(de::Random &rnd, glu::GLSLVersion glslVersion)
{
    if (rnd.getFloat() < 0.5f)
    {
        // whole pipeline
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
        ResourceDefinition::ShaderSet *shaderSet = new ResourceDefinition::ShaderSet(program, glslVersion);

        shaderSet->setStage(glu::SHADERTYPE_VERTEX, rnd.getBool());
        shaderSet->setStage(glu::SHADERTYPE_FRAGMENT, rnd.getBool());

        // tess shader are either both or neither present. Make cases interesting
        // by forcing one extended shader to always have reference
        if (rnd.getBool())
        {
            shaderSet->setStage(glu::SHADERTYPE_GEOMETRY, true);

            if (rnd.getBool())
            {
                shaderSet->setStage(glu::SHADERTYPE_TESSELLATION_CONTROL, rnd.getBool());
                shaderSet->setStage(glu::SHADERTYPE_TESSELLATION_EVALUATION, rnd.getBool());
            }
        }
        else
        {
            shaderSet->setStage(glu::SHADERTYPE_GEOMETRY, rnd.getBool());

            if (rnd.getBool())
            {
                shaderSet->setStage(glu::SHADERTYPE_TESSELLATION_CONTROL, true);
                shaderSet->setStage(glu::SHADERTYPE_TESSELLATION_EVALUATION, rnd.getBool());
            }
            else
            {
                shaderSet->setStage(glu::SHADERTYPE_TESSELLATION_CONTROL, rnd.getBool());
                shaderSet->setStage(glu::SHADERTYPE_TESSELLATION_EVALUATION, true);
            }
        }

        return ResourceDefinition::Node::SharedPtr(shaderSet);
    }
    else
    {
        // separate
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(true));
        const int selector               = rnd.getInt(0, 2);
        const glu::ShaderType shaderType = (selector == 0) ? (glu::SHADERTYPE_GEOMETRY) :
                                           (selector == 1) ? (glu::SHADERTYPE_TESSELLATION_CONTROL) :
                                           (selector == 2) ? (glu::SHADERTYPE_TESSELLATION_EVALUATION) :
                                                             (glu::SHADERTYPE_LAST);

        return ResourceDefinition::Node::SharedPtr(new ResourceDefinition::Shader(program, shaderType, glslVersion));
    }
}

static ResourceDefinition::Node::SharedPtr generateRandomShaderSet(de::Random &rnd, glu::GLSLVersion glslVersion,
                                                                   bool onlyExtensionStages)
{
    if (!onlyExtensionStages)
        return generateRandomCoreShaderSet(rnd, glslVersion);
    else
        return generateRandomExtShaderSet(rnd, glslVersion);
}

static glu::Layout generateRandomUniformBlockLayout(de::Random &rnd)
{
    glu::Layout layout;

    if (rnd.getBool())
        layout.binding = rnd.getInt(0, 5);

    if (rnd.getBool())
        layout.matrixOrder = (rnd.getBool()) ? (glu::MATRIXORDER_COLUMN_MAJOR) : (glu::MATRIXORDER_ROW_MAJOR);

    return layout;
}

static glu::Layout generateRandomBufferBlockLayout(de::Random &rnd)
{
    return generateRandomUniformBlockLayout(rnd);
}

static glu::Layout generateRandomVariableLayout(de::Random &rnd, glu::DataType type, bool interfaceBlockMember)
{
    glu::Layout layout;

    if ((glu::isDataTypeAtomicCounter(type) || glu::isDataTypeImage(type) || glu::isDataTypeSampler(type)) &&
        rnd.getBool())
        layout.binding = rnd.getInt(0, 5);

    if (glu::isDataTypeAtomicCounter(type) && rnd.getBool())
        layout.offset = rnd.getInt(0, 3) * 4;

    if (glu::isDataTypeMatrix(type) && interfaceBlockMember && rnd.getBool())
        layout.matrixOrder = (rnd.getBool()) ? (glu::MATRIXORDER_COLUMN_MAJOR) : (glu::MATRIXORDER_ROW_MAJOR);

    return layout;
}

static void generateUniformRandomCase(Context &context, tcu::TestCaseGroup *const targetGroup,
                                      glu::GLSLVersion glslVersion, int index, bool onlyExtensionStages)
{
    de::Random rnd(index * 0x12345);
    const ResourceDefinition::Node::SharedPtr shader = generateRandomShaderSet(rnd, glslVersion, onlyExtensionStages);
    const bool interfaceBlock                        = rnd.getBool();
    const glu::DataType type                         = generateRandomDataType(rnd, interfaceBlock);
    const glu::Layout layout                         = generateRandomVariableLayout(rnd, type, interfaceBlock);
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr uniform(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_UNIFORM));
    ResourceDefinition::Node::SharedPtr currentStructure = uniform;

    if (interfaceBlock)
    {
        const bool namedBlock = rnd.getBool();

        currentStructure = ResourceDefinition::Node::SharedPtr(
            new ResourceDefinition::LayoutQualifier(currentStructure, generateRandomUniformBlockLayout(rnd)));

        if (namedBlock && rnd.getBool())
            currentStructure =
                ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure));

        currentStructure =
            ResourceDefinition::Node::SharedPtr(new ResourceDefinition::InterfaceBlock(currentStructure, namedBlock));
    }

    currentStructure =
        ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(currentStructure, layout));
    currentStructure = generateRandomVariableDefinition(rnd, currentStructure, type, layout, false);

    targetGroup->addChild(new ResourceTestCase(
        context, currentStructure,
        ProgramResourceQueryTestTarget(PROGRAMINTERFACE_UNIFORM, PROGRAMRESOURCEPROP_UNIFORM_INTERFACE_MASK),
        de::toString(index).c_str()));
}

static void generateUniformCaseRandomCases(Context &context, tcu::TestCaseGroup *const targetGroup,
                                           glu::GLSLVersion glslVersion)
{
    const int numBasicCases   = 40;
    const int numTessGeoCases = 40;

    for (int ndx = 0; ndx < numBasicCases; ++ndx)
        generateUniformRandomCase(context, targetGroup, glslVersion, ndx, false);
    for (int ndx = 0; ndx < numTessGeoCases; ++ndx)
        generateUniformRandomCase(context, targetGroup, glslVersion, numBasicCases + ndx, true);
}

class UniformInterfaceTestGroup : public TestCaseGroup
{
public:
    UniformInterfaceTestGroup(Context &context);
    void init(void);
};

UniformInterfaceTestGroup::UniformInterfaceTestGroup(Context &context)
    : TestCaseGroup(context, "uniform", "Uniform interace")
{
}

void UniformInterfaceTestGroup::init(void)
{
    glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr computeShader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));

    // .resource_list
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL,
                                  generateUniformResourceListBlockContents);
    }

    // .array_size
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_size", "Query array size");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL,
                                  generateUniformBlockArraySizeContents);
    }

    // .array_stride
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_stride", "Query array stride");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL,
                                  generateUniformBlockArrayStrideContents);
    }

    // .atomic_counter_buffer_index
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "atomic_counter_buffer_index", "Query atomic counter buffer index");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_DEFAULT | BLOCKFLAG_NAMED,
                                  generateUniformBlockAtomicCounterBufferIndexContents);
    }

    // .block_index
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "block_index", "Query block index");
        addChild(blockGroup);
        generateUniformBlockBlockIndexContents(m_context, blockGroup, glslVersion);
    }

    // .location
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "location", "Query location");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup,
                                  BLOCKFLAG_DEFAULT | BLOCKFLAG_NAMED | BLOCKFLAG_UNNAMED,
                                  generateUniformBlockLocationContents);
    }

    // .matrix_row_major
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "matrix_row_major", "Query matrix row_major");
        addChild(blockGroup);
        generateUniformMatrixCaseBlocks(m_context, computeShader, blockGroup,
                                        generateUniformMatrixOrderCaseBlockContentCases);
    }

    // .matrix_stride
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "matrix_stride", "Query matrix stride");
        addChild(blockGroup);
        generateUniformMatrixCaseBlocks(m_context, computeShader, blockGroup,
                                        generateUniformMatrixStrideCaseBlockContentCases);
    }

    // .name_length
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Query name length");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL,
                                  generateUniformBlockNameLengthContents);
    }

    // .offset
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "offset", "Query offset");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL,
                                  generateUniformBlockOffsetContents);
    }

    // .referenced_by_shader
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "referenced_by_shader", "Query referenced by shader");
        addChild(blockGroup);
        generateReferencedByShaderCaseBlocks(m_context, blockGroup, glslVersion,
                                             generateUniformReferencedByShaderSingleBlockContentCases);
    }

    // .type
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "type", "Query type");
        addChild(blockGroup);
        generateUniformCaseBlocks(m_context, computeShader, blockGroup, BLOCKFLAG_ALL,
                                  generateUniformBlockTypeContents);
    }

    // .random
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random");
        addChild(blockGroup);
        generateUniformCaseRandomCases(m_context, blockGroup, glslVersion);
    }
}

static void generateBufferBackedInterfaceResourceListCase(Context &context,
                                                          const ResourceDefinition::Node::SharedPtr &targetResource,
                                                          tcu::TestCaseGroup *const targetGroup,
                                                          ProgramInterface interface, const char *blockName)
{
    targetGroup->addChild(new ResourceListTestCase(context, targetResource, interface, blockName));
}

static void generateBufferBackedInterfaceNameLengthCase(Context &context,
                                                        const ResourceDefinition::Node::SharedPtr &targetResource,
                                                        tcu::TestCaseGroup *const targetGroup,
                                                        ProgramInterface interface, const char *blockName)
{
    targetGroup->addChild(
        new ResourceTestCase(context, targetResource,
                             ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_NAME_LENGTH), blockName));
}

static void generateBufferBackedInterfaceResourceBasicBlockTypes(
    Context &context, tcu::TestCaseGroup *targetGroup, glu::GLSLVersion glslVersion, glu::Storage storage,
    void (*blockContentGenerator)(Context &, const ResourceDefinition::Node::SharedPtr &, tcu::TestCaseGroup *const,
                                  ProgramInterface interface, const char *blockName))
{
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr storageQualifier(
        new ResourceDefinition::StorageQualifier(defaultBlock, storage));
    const ResourceDefinition::Node::SharedPtr binding(
        new ResourceDefinition::LayoutQualifier(storageQualifier, glu::Layout(-1, 1)));
    const ProgramInterface programInterface =
        (storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) : (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK);

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(binding, true));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        blockContentGenerator(context, unusedVariable, targetGroup, programInterface, "named_block");
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(binding, false));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        blockContentGenerator(context, unusedVariable, targetGroup, programInterface, "unnamed_block");
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(binding, 3));
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        blockContentGenerator(context, unusedVariable, targetGroup, programInterface, "block_array");
    }

    // .block_array_single_element
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(binding, 1));
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        blockContentGenerator(context, unusedVariable, targetGroup, programInterface, "block_array_single_element");
    }
}

static void generateBufferBackedInterfaceResourceBufferBindingCases(Context &context, tcu::TestCaseGroup *targetGroup,
                                                                    glu::GLSLVersion glslVersion, glu::Storage storage)
{
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr storageQualifier(
        new ResourceDefinition::StorageQualifier(defaultBlock, storage));

    for (int ndx = 0; ndx < 2; ++ndx)
    {
        const bool explicitBinding   = (ndx == 1);
        const int bindingNdx         = (explicitBinding) ? (1) : (-1);
        const std::string nameSuffix = (explicitBinding) ? ("_explicit_binding") : ("");
        const ResourceDefinition::Node::SharedPtr binding(
            new ResourceDefinition::LayoutQualifier(storageQualifier, glu::Layout(-1, bindingNdx)));
        const ProgramInterface programInterface = (storage == glu::STORAGE_UNIFORM) ?
                                                      (PROGRAMINTERFACE_UNIFORM_BLOCK) :
                                                      (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK);

        // .named_block*
        {
            const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(binding, true));
            const ResourceDefinition::Node::SharedPtr unusedVariable(
                new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

            targetGroup->addChild(new ResourceTestCase(
                context, unusedVariable,
                ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_BUFFER_BINDING),
                ("named_block" + nameSuffix).c_str()));
        }

        // .unnamed_block*
        {
            const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(binding, false));
            const ResourceDefinition::Node::SharedPtr unusedVariable(
                new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

            targetGroup->addChild(new ResourceTestCase(
                context, unusedVariable,
                ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_BUFFER_BINDING),
                ("unnamed_block" + nameSuffix).c_str()));
        }

        // .block_array*
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(binding, 3));
            const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(arrayElement, true));
            const ResourceDefinition::Node::SharedPtr unusedVariable(
                new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

            targetGroup->addChild(new ResourceTestCase(
                context, unusedVariable,
                ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_BUFFER_BINDING),
                ("block_array" + nameSuffix).c_str()));
        }
    }
}

template <glu::Storage Storage>
static void generateBufferBlockReferencedByShaderSingleBlockContentCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *targetGroup,
    int expandLevel)
{
    const ProgramInterface programInterface =
        (Storage == glu::STORAGE_UNIFORM) ? (PROGRAMINTERFACE_UNIFORM_BLOCK) :
        (Storage == glu::STORAGE_BUFFER)  ? (PROGRAMINTERFACE_SHADER_STORAGE_BLOCK) :
                                            (PROGRAMINTERFACE_LAST);
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr storage(new ResourceDefinition::StorageQualifier(defaultBlock, Storage));

    DE_UNREF(expandLevel);

    DE_ASSERT(programInterface != PROGRAMINTERFACE_LAST);

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(storage, true));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        targetGroup->addChild(new ResourceTestCase(
            context, unusedVariable,
            ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER), "named_block"));
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(storage, false));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        targetGroup->addChild(new ResourceTestCase(
            context, unusedVariable,
            ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER),
            "unnamed_block"));
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(storage, 3));
        const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        const ResourceDefinition::Node::SharedPtr unusedVariable(
            new ResourceDefinition::Variable(block, glu::TYPE_BOOL_VEC3));

        targetGroup->addChild(new ResourceTestCase(
            context, unusedVariable,
            ProgramResourceQueryTestTarget(programInterface, PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER), "block_array"));
    }
}

static void generateBufferBackedInterfaceResourceActiveVariablesCase(Context &context, tcu::TestCaseGroup *targetGroup,
                                                                     glu::Storage storage)
{
    targetGroup->addChild(new InterfaceBlockActiveVariablesTestCase(
        context, "named_block", "Named block", storage, InterfaceBlockActiveVariablesTestCase::CASE_NAMED_BLOCK));
    targetGroup->addChild(new InterfaceBlockActiveVariablesTestCase(
        context, "unnamed_block", "Unnamed block", storage, InterfaceBlockActiveVariablesTestCase::CASE_UNNAMED_BLOCK));
    targetGroup->addChild(new InterfaceBlockActiveVariablesTestCase(
        context, "block_array", "Block array", storage, InterfaceBlockActiveVariablesTestCase::CASE_BLOCK_ARRAY));
}

static void generateBufferBackedInterfaceResourceBufferDataSizeCases(Context &context, tcu::TestCaseGroup *targetGroup,
                                                                     glu::Storage storage)
{
    targetGroup->addChild(new InterfaceBlockDataSizeTestCase(context, "named_block", "Named block", storage,
                                                             InterfaceBlockDataSizeTestCase::CASE_NAMED_BLOCK));
    targetGroup->addChild(new InterfaceBlockDataSizeTestCase(context, "unnamed_block", "Unnamed block", storage,
                                                             InterfaceBlockDataSizeTestCase::CASE_UNNAMED_BLOCK));
    targetGroup->addChild(new InterfaceBlockDataSizeTestCase(context, "block_array", "Block array", storage,
                                                             InterfaceBlockDataSizeTestCase::CASE_BLOCK_ARRAY));
}

class BufferBackedBlockInterfaceTestGroup : public TestCaseGroup
{
public:
    BufferBackedBlockInterfaceTestGroup(Context &context, glu::Storage interfaceBlockStorage);
    void init(void);

private:
    static const char *getGroupName(glu::Storage storage);
    static const char *getGroupDescription(glu::Storage storage);

    const glu::Storage m_storage;
};

BufferBackedBlockInterfaceTestGroup::BufferBackedBlockInterfaceTestGroup(Context &context, glu::Storage storage)
    : TestCaseGroup(context, getGroupName(storage), getGroupDescription(storage))
    , m_storage(storage)
{
    DE_ASSERT(storage == glu::STORAGE_BUFFER || storage == glu::STORAGE_UNIFORM);
}

void BufferBackedBlockInterfaceTestGroup::init(void)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    // .resource_list
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
        addChild(blockGroup);
        generateBufferBackedInterfaceResourceBasicBlockTypes(m_context, blockGroup, glslVersion, m_storage,
                                                             generateBufferBackedInterfaceResourceListCase);
    }

    // .active_variables
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "active_variables", "Active variables");
        addChild(blockGroup);
        generateBufferBackedInterfaceResourceActiveVariablesCase(m_context, blockGroup, m_storage);
    }

    // .buffer_binding
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "buffer_binding", "Buffer binding");
        addChild(blockGroup);
        generateBufferBackedInterfaceResourceBufferBindingCases(m_context, blockGroup, glslVersion, m_storage);
    }

    // .buffer_data_size
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "buffer_data_size", "Buffer data size");
        addChild(blockGroup);
        generateBufferBackedInterfaceResourceBufferDataSizeCases(m_context, blockGroup, m_storage);
    }

    // .name_length
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Name length");
        addChild(blockGroup);
        generateBufferBackedInterfaceResourceBasicBlockTypes(m_context, blockGroup, glslVersion, m_storage,
                                                             generateBufferBackedInterfaceNameLengthCase);
    }

    // .referenced_by
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "referenced_by", "Referenced by shader");
        addChild(blockGroup);

        if (m_storage == glu::STORAGE_UNIFORM)
            generateReferencedByShaderCaseBlocks(
                m_context, blockGroup, glslVersion,
                generateBufferBlockReferencedByShaderSingleBlockContentCases<glu::STORAGE_UNIFORM>);
        else if (m_storage == glu::STORAGE_BUFFER)
            generateReferencedByShaderCaseBlocks(
                m_context, blockGroup, glslVersion,
                generateBufferBlockReferencedByShaderSingleBlockContentCases<glu::STORAGE_BUFFER>);
        else
            DE_ASSERT(false);
    }
}

const char *BufferBackedBlockInterfaceTestGroup::getGroupName(glu::Storage storage)
{
    switch (storage)
    {
    case glu::STORAGE_UNIFORM:
        return "uniform_block";
    case glu::STORAGE_BUFFER:
        return "shader_storage_block";
    default:
        DE_FATAL("invalid storage enum value");
        return DE_NULL;
    }
}

const char *BufferBackedBlockInterfaceTestGroup::getGroupDescription(glu::Storage storage)
{
    switch (storage)
    {
    case glu::STORAGE_UNIFORM:
        return "Uniform block interface";
    case glu::STORAGE_BUFFER:
        return "Shader storage block interface";
    default:
        DE_FATAL("invalid storage enum value");
        return DE_NULL;
    }
}

class AtomicCounterTestGroup : public TestCaseGroup
{
public:
    AtomicCounterTestGroup(Context &context);
    void init(void);
};

AtomicCounterTestGroup::AtomicCounterTestGroup(Context &context)
    : TestCaseGroup(context, "atomic_counter_buffer", "Atomic counter buffer")
{
}

void AtomicCounterTestGroup::init(void)
{
    static const struct
    {
        const char *name;
        uint32_t flags;
    } pipelines[] = {
        {"vertex_fragment", (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT)},
        {"vertex_tess_fragment", (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) |
                                     (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) |
                                     (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION)},
        {"vertex_geo_fragment",
         (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) | (1 << glu::SHADERTYPE_GEOMETRY)},
        {
            "vertex_tess_geo_fragment",
            (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) |
                (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) | (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION) |
                (1 << glu::SHADERTYPE_GEOMETRY),
        },
    };

    // .resource_list
    addChild(new AtomicCounterResourceListCase(m_context, "resource_list", "Resource list"));

    // .active_variables
    addChild(new AtomicCounterActiveVariablesCase(m_context, "active_variables", "Active variables"));

    // .buffer_binding
    addChild(new AtomicCounterBufferBindingCase(m_context, "buffer_binding", "Buffer binding"));

    // .buffer_data_size
    addChild(new AtomicCounterBufferDataSizeCase(m_context, "buffer_data_size", "Buffer binding"));

    // .referenced_by
    addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_compute", "", false,
                                               (1 << glu::SHADERTYPE_COMPUTE), (1 << glu::SHADERTYPE_COMPUTE)));
    addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_vertex", "", true,
                                               (1 << glu::SHADERTYPE_VERTEX), (1 << glu::SHADERTYPE_VERTEX)));
    addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_fragment", "", true,
                                               (1 << glu::SHADERTYPE_FRAGMENT), (1 << glu::SHADERTYPE_FRAGMENT)));
    addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_geometry", "", true,
                                               (1 << glu::SHADERTYPE_GEOMETRY), (1 << glu::SHADERTYPE_GEOMETRY)));
    addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_tess_ctrl", "", true,
                                               (1 << glu::SHADERTYPE_TESSELLATION_CONTROL),
                                               (1 << glu::SHADERTYPE_TESSELLATION_CONTROL)));
    addChild(new AtomicCounterReferencedByCase(m_context, "referenced_by_separable_tess_eval", "", true,
                                               (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION),
                                               (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION)));

    for (int pipelineNdx = 0; pipelineNdx < DE_LENGTH_OF_ARRAY(pipelines); ++pipelineNdx)
    {
        addChild(new AtomicCounterReferencedByCase(
            m_context, (std::string() + "referenced_by_" + pipelines[pipelineNdx].name).c_str(), "", false,
            pipelines[pipelineNdx].flags, pipelines[pipelineNdx].flags));

        for (uint32_t stageNdx = 0; stageNdx < glu::SHADERTYPE_LAST; ++stageNdx)
        {
            const uint32_t currentBit = (1u << stageNdx);
            if (currentBit > pipelines[pipelineNdx].flags)
                break;
            if (currentBit & pipelines[pipelineNdx].flags)
            {
                const char *stageName = (stageNdx == glu::SHADERTYPE_VERTEX)                  ? ("vertex") :
                                        (stageNdx == glu::SHADERTYPE_FRAGMENT)                ? ("fragment") :
                                        (stageNdx == glu::SHADERTYPE_GEOMETRY)                ? ("geo") :
                                        (stageNdx == glu::SHADERTYPE_TESSELLATION_CONTROL)    ? ("tess_ctrl") :
                                        (stageNdx == glu::SHADERTYPE_TESSELLATION_EVALUATION) ? ("tess_eval") :
                                                                                                (DE_NULL);
                const std::string name =
                    std::string() + "referenced_by_" + pipelines[pipelineNdx].name + "_only_" + stageName;

                addChild(new AtomicCounterReferencedByCase(m_context, name.c_str(), "", false,
                                                           pipelines[pipelineNdx].flags, currentBit));
            }
        }
    }
}

static void generateProgramInputOutputShaderCaseBlocks(
    Context &context, tcu::TestCaseGroup *targetGroup, glu::GLSLVersion glslVersion, bool withCompute, bool inputCase,
    bool isGL45,
    void (*blockContentGenerator)(Context &, const ResourceDefinition::Node::SharedPtr &, tcu::TestCaseGroup *,
                                  uint32_t, bool))
{
    static const struct
    {
        const char *name;
        glu::ShaderType stage;
    } singleStageCases[] = {
        {"separable_vertex", glu::SHADERTYPE_VERTEX},
        {"separable_fragment", glu::SHADERTYPE_FRAGMENT},
        {"separable_tess_ctrl", glu::SHADERTYPE_TESSELLATION_CONTROL},
        {"separable_tess_eval", glu::SHADERTYPE_TESSELLATION_EVALUATION},
        {"separable_geometry", glu::SHADERTYPE_GEOMETRY},
    };

    // .vertex_fragment
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "vertex_fragment", "Vertex and fragment");
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(false));
        ResourceDefinition::ShaderSet *shaderSetPtr = new ResourceDefinition::ShaderSet(program, glslVersion);
        const ResourceDefinition::Node::SharedPtr shaderSet(shaderSetPtr);
        const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shaderSet));

        shaderSetPtr->setStage(glu::SHADERTYPE_VERTEX, inputCase);
        shaderSetPtr->setStage(glu::SHADERTYPE_FRAGMENT, !inputCase);

        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, defaultBlock, blockGroup,
                              (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT), isGL45);
    }

    // .separable_*
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(singleStageCases); ++ndx)
    {
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, singleStageCases[ndx].name, "");
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(true));
        const ResourceDefinition::Node::SharedPtr shader(
            new ResourceDefinition::Shader(program, singleStageCases[ndx].stage, glslVersion));
        const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));

        targetGroup->addChild(blockGroup);
        blockContentGenerator(context, defaultBlock, blockGroup, (1 << singleStageCases[ndx].stage), isGL45);
    }

    // .compute
    if (withCompute)
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "compute", "Compute");
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(true));
        const ResourceDefinition::Node::SharedPtr shader(
            new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
        const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));

        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, defaultBlock, blockGroup, (1 << glu::SHADERTYPE_COMPUTE), isGL45);
    }

    // .interface_blocks
    {
        static const struct
        {
            const char *inputName;
            glu::ShaderType inputStage;
            glu::Storage inputStorage;
            const char *outputName;
            glu::ShaderType outputStage;
            glu::Storage outputStorage;
        } ioBlockTypes[] = {
            {
                "in",
                glu::SHADERTYPE_FRAGMENT,
                glu::STORAGE_IN,
                "out",
                glu::SHADERTYPE_VERTEX,
                glu::STORAGE_OUT,
            },
            {
                "patch_in",
                glu::SHADERTYPE_TESSELLATION_EVALUATION,
                glu::STORAGE_PATCH_IN,
                "patch_out",
                glu::SHADERTYPE_TESSELLATION_CONTROL,
                glu::STORAGE_PATCH_OUT,
            },
        };

        tcu::TestCaseGroup *const ioBlocksGroup = new TestCaseGroup(context, "interface_blocks", "Interface blocks");
        targetGroup->addChild(ioBlocksGroup);

        // .in/out
        // .sample in/out
        // .patch in/out
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(ioBlockTypes); ++ndx)
        {
            const char *const name = (inputCase) ? (ioBlockTypes[ndx].inputName) : (ioBlockTypes[ndx].outputName);
            const glu::ShaderType shaderType =
                (inputCase) ? (ioBlockTypes[ndx].inputStage) : (ioBlockTypes[ndx].outputStage);
            const glu::Storage storageType =
                (inputCase) ? (ioBlockTypes[ndx].inputStorage) : (ioBlockTypes[ndx].outputStorage);
            tcu::TestCaseGroup *const ioBlockGroup = new TestCaseGroup(context, name, "");
            const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(true));
            const ResourceDefinition::Node::SharedPtr shader(
                new ResourceDefinition::Shader(program, shaderType, glslVersion));
            const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
            const ResourceDefinition::Node::SharedPtr storage(
                new ResourceDefinition::StorageQualifier(defaultBlock, storageType));

            ioBlocksGroup->addChild(ioBlockGroup);

            // .named_block
            {
                const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(storage, true));
                tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "named_block", "Named block");

                ioBlockGroup->addChild(blockGroup);

                blockContentGenerator(context, block, blockGroup, (1 << shaderType), isGL45);
            }

            // .named_block_explicit_location
            {
                const ResourceDefinition::Node::SharedPtr layout(
                    new ResourceDefinition::LayoutQualifier(storage, glu::Layout(3)));
                const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(layout, true));
                tcu::TestCaseGroup *const blockGroup =
                    new TestCaseGroup(context, "named_block_explicit_location", "Named block with explicit location");

                ioBlockGroup->addChild(blockGroup);

                blockContentGenerator(context, block, blockGroup, (1 << shaderType), isGL45);
            }

            // .unnamed_block
            if (!isGL45)
            {
                const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(storage, false));
                tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unnamed_block", "Unnamed block");

                ioBlockGroup->addChild(blockGroup);

                blockContentGenerator(context, block, blockGroup, (1 << shaderType), isGL45);
            }

            // .block_array
            {
                const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(storage));
                const ResourceDefinition::Node::SharedPtr block(
                    new ResourceDefinition::InterfaceBlock(arrayElement, true));
                tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "block_array", "Block array");

                ioBlockGroup->addChild(blockGroup);

                blockContentGenerator(context, block, blockGroup, (1 << shaderType), isGL45);
            }
        }
    }
}

static void generateProgramInputBlockContents(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *targetGroup,
    uint32_t presentShadersMask, bool includeEmpty,
    void (*genCase)(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                    tcu::TestCaseGroup *targetGroup, ProgramInterface interface, const char *name))
{
    const bool inDefaultBlock = parentStructure->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK;
    const ResourceDefinition::Node::SharedPtr input =
        (inDefaultBlock) ? (ResourceDefinition::Node::SharedPtr(
                               new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_IN))) :
                           (parentStructure);
    const glu::ShaderType firstStage = getShaderMaskFirstStage(presentShadersMask);

    // .empty
    if (includeEmpty && inDefaultBlock)
        genCase(context, parentStructure, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "empty");

    if (firstStage == glu::SHADERTYPE_VERTEX)
    {
        // .var
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
        genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "var");
    }
    else if (firstStage == glu::SHADERTYPE_FRAGMENT || !inDefaultBlock)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "var");
        }
        // .var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(input));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "var_struct");
        }
        // .var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(input));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "var_array");
        }
    }
    else if (firstStage == glu::SHADERTYPE_TESSELLATION_CONTROL || firstStage == glu::SHADERTYPE_GEOMETRY)
    {
        // arrayed interface

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(input, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "var");
        }
        // extension forbids use arrays of structs
        // extension forbids use arrays of arrays
    }
    else if (firstStage == glu::SHADERTYPE_TESSELLATION_EVALUATION)
    {
        // arrayed interface
        const ResourceDefinition::Node::SharedPtr patchInput(
            new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_PATCH_IN));

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(input, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "var");
        }
        // extension forbids use arrays of structs
        // extension forbids use arrays of arrays

        // .patch_var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(patchInput, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "patch_var");
        }
        // .patch_var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(patchInput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "patch_var_struct");
        }
        // .patch_var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(patchInput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_INPUT, "patch_var_array");
        }
    }
    else if (firstStage == glu::SHADERTYPE_COMPUTE)
    {
        // nada
    }
    else
        DE_ASSERT(false);
}

static void generateProgramOutputBlockContents(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *targetGroup,
    uint32_t presentShadersMask, bool includeEmpty,
    void (*genCase)(Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure,
                    tcu::TestCaseGroup *targetGroup, ProgramInterface interface, const char *name))
{
    const bool inDefaultBlock = parentStructure->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK;
    const ResourceDefinition::Node::SharedPtr output =
        (inDefaultBlock) ? (ResourceDefinition::Node::SharedPtr(
                               new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_OUT))) :
                           (parentStructure);
    const glu::ShaderType lastStage = getShaderMaskLastStage(presentShadersMask);

    // .empty
    if (includeEmpty && inDefaultBlock)
        genCase(context, parentStructure, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "empty");

    if (lastStage == glu::SHADERTYPE_VERTEX || lastStage == glu::SHADERTYPE_GEOMETRY ||
        lastStage == glu::SHADERTYPE_TESSELLATION_EVALUATION || !inDefaultBlock)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "var");
        }
        // .var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(output));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "var_struct");
        }
        // .var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(output));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "var_array");
        }
    }
    else if (lastStage == glu::SHADERTYPE_FRAGMENT)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "var");
        }
        // .var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(output));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "var_array");
        }
    }
    else if (lastStage == glu::SHADERTYPE_TESSELLATION_CONTROL)
    {
        // arrayed interface
        const ResourceDefinition::Node::SharedPtr patchOutput(
            new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_PATCH_OUT));

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(output, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "var");
        }
        // extension forbids use arrays of structs
        // extension forbids use array of arrays

        // .patch_var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(patchOutput, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "patch_var");
        }
        // .patch_var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(patchOutput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "patch_var_struct");
        }
        // .patch_var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(patchOutput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            genCase(context, variable, targetGroup, PROGRAMINTERFACE_PROGRAM_OUTPUT, "patch_var_array");
        }
    }
    else if (lastStage == glu::SHADERTYPE_COMPUTE)
    {
        // nada
    }
    else
        DE_ASSERT(false);
}

static void addProgramInputOutputResourceListCase(Context &context,
                                                  const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                  tcu::TestCaseGroup *targetGroup, ProgramInterface programInterface,
                                                  const char *name)
{
    ResourceListTestCase *const resourceListCase = new ResourceListTestCase(context, parentStructure, programInterface);

    DE_ASSERT(deStringEqual(name, resourceListCase->getName()));
    DE_UNREF(name);
    targetGroup->addChild(resourceListCase);
}

static void generateProgramInputResourceListBlockContents(Context &context,
                                                          const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                          tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                          bool isGL45)
{
    DE_UNREF(isGL45);
    generateProgramInputBlockContents(context, parentStructure, targetGroup, presentShadersMask, true,
                                      addProgramInputOutputResourceListCase);
}

static void generateProgramOutputResourceListBlockContents(Context &context,
                                                           const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                           tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                           bool isGL45)
{
    DE_UNREF(isGL45);
    generateProgramOutputBlockContents(context, parentStructure, targetGroup, presentShadersMask, true,
                                       addProgramInputOutputResourceListCase);
}

template <ProgramResourcePropFlags TargetProp>
static void addProgramInputOutputResourceTestCase(Context &context,
                                                  const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                  tcu::TestCaseGroup *targetGroup, ProgramInterface programInterface,
                                                  const char *name)
{
    ResourceTestCase *const resourceTestCase = new ResourceTestCase(
        context, parentStructure, ProgramResourceQueryTestTarget(programInterface, TargetProp), name);
    targetGroup->addChild(resourceTestCase);
}

template <ProgramResourcePropFlags TargetProp>
static void generateProgramInputBasicBlockContents(Context &context,
                                                   const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                   tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                   bool isGL45)
{
    DE_UNREF(isGL45);
    generateProgramInputBlockContents(context, parentStructure, targetGroup, presentShadersMask, false,
                                      addProgramInputOutputResourceTestCase<TargetProp>);
}

template <ProgramResourcePropFlags TargetProp>
static void generateProgramOutputBasicBlockContents(Context &context,
                                                    const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                    tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                    bool isGL45)
{
    DE_UNREF(isGL45);
    generateProgramOutputBlockContents(context, parentStructure, targetGroup, presentShadersMask, false,
                                       addProgramInputOutputResourceTestCase<TargetProp>);
}

static void generateProgramInputLocationBlockContents(Context &context,
                                                      const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                      tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                      bool isGL45)
{
    DE_UNREF(isGL45);
    const bool inDefaultBlock = parentStructure->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK;
    const ResourceDefinition::Node::SharedPtr input =
        (inDefaultBlock) ? (ResourceDefinition::Node::SharedPtr(
                               new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_IN))) :
                           (parentStructure);
    const glu::ShaderType firstStage = getShaderMaskFirstStage(presentShadersMask);

    const bool inBlockArray = true == deStringEqual("block_array", targetGroup->getName());

    if (firstStage == glu::SHADERTYPE_VERTEX)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
    }
    else if (firstStage == glu::SHADERTYPE_FRAGMENT || !inDefaultBlock)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(input, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
        // .var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(input));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_struct"));
        }
        // .var_struct_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_struct_explicit_location"));
        }
        // .var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(input));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_array"));
        }
        // .var_array_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_array_explicit_location"));
        }
    }
    else if (firstStage == glu::SHADERTYPE_TESSELLATION_CONTROL || firstStage == glu::SHADERTYPE_GEOMETRY)
    {
        // arrayed interface

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(input, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(layout, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
        // extension forbids use arrays of structs
        // extension forbids use arrays of arrays
    }
    else if (firstStage == glu::SHADERTYPE_TESSELLATION_EVALUATION)
    {
        // arrayed interface
        const ResourceDefinition::Node::SharedPtr patchInput(
            new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_PATCH_IN));

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(input, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(input, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(layout, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
        // extension forbids use arrays of structs
        // extension forbids use arrays of arrays

        // .patch_var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(patchInput, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var"));
        }
        // .patch_var_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(patchInput, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_explicit_location"));
        }
        // .patch_var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(patchInput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_struct"));
        }
        // .patch_var_struct_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(patchInput, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_struct_explicit_location"));
        }
        // .patch_var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(patchInput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_array"));
        }
        // .patch_var_array_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(patchInput, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_INPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_array_explicit_location"));
        }
    }
    else if (firstStage == glu::SHADERTYPE_COMPUTE)
    {
        // nada
    }
    else
        DE_ASSERT(false);
}

static void generateProgramOutputLocationBlockContents(Context &context,
                                                       const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                       tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                       bool isGL45)
{
    DE_UNREF(isGL45);
    const bool inDefaultBlock = parentStructure->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK;
    const ResourceDefinition::Node::SharedPtr output =
        (inDefaultBlock) ? (ResourceDefinition::Node::SharedPtr(
                               new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_OUT))) :
                           (parentStructure);
    const glu::ShaderType lastStage = getShaderMaskLastStage(presentShadersMask);

    const bool inBlockArray = true == deStringEqual("block_array", targetGroup->getName());

    if (lastStage == glu::SHADERTYPE_VERTEX || lastStage == glu::SHADERTYPE_GEOMETRY ||
        lastStage == glu::SHADERTYPE_TESSELLATION_EVALUATION || !inDefaultBlock)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
        // .var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(output));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_struct"));
        }
        // .var_struct_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_struct_explicit_location"));
        }
        // .var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(output));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_array"));
        }
        // .var_array_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_array_explicit_location"));
        }
    }
    else if (lastStage == glu::SHADERTYPE_FRAGMENT)
    {
        // .var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(output, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
        // .var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(output));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_array"));
        }
        // .var_array_explicit_location
        if (!inBlockArray)
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(output, glu::Layout(1)));
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_array_explicit_location"));
        }
    }
    else if (lastStage == glu::SHADERTYPE_TESSELLATION_CONTROL)
    {
        // arrayed interface
        const ResourceDefinition::Node::SharedPtr patchOutput(
            new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_PATCH_OUT));

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(output, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION), "var"));
        }
        // .var_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(output, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(layout, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "var_explicit_location"));
        }
        // extension forbids use arrays of structs
        // extension forbids use array of arrays

        // .patch_var
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(patchOutput, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var"));
        }
        // .patch_var_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(patchOutput, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(layout, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_explicit_location"));
        }
        // .patch_var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(patchOutput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_struct"));
        }
        // .patch_var_struct_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(patchOutput, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(structMbr, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_struct_explicit_location"));
        }
        // .patch_var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(patchOutput));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_array"));
        }
        // .patch_var_array_explicit_location
        {
            const ResourceDefinition::Node::SharedPtr layout(
                new ResourceDefinition::LayoutQualifier(patchOutput, glu::Layout(2)));
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(layout));
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_PROGRAM_OUTPUT, PROGRAMRESOURCEPROP_LOCATION),
                "patch_var_array_explicit_location"));
        }
    }
    else if (lastStage == glu::SHADERTYPE_COMPUTE)
    {
        // nada
    }
    else
        DE_ASSERT(false);
}

static void generateProgramInputOutputReferencedByCases(Context &context, tcu::TestCaseGroup *targetGroup,
                                                        glu::Storage storage)
{
    // all whole pipelines
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_vertex_fragment", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_VERTEX_FRAGMENT));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_vertex_tess_fragment", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_VERTEX_TESS_FRAGMENT));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_vertex_geo_fragment", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_VERTEX_GEO_FRAGMENT));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_vertex_tess_geo_fragment", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_VERTEX_TESS_GEO_FRAGMENT));

    // all partial pipelines
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_vertex", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_SEPARABLE_VERTEX));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_fragment", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_SEPARABLE_FRAGMENT));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_geometry", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_SEPARABLE_GEOMETRY));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_tess_eval", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_SEPARABLE_TESS_EVAL));
    targetGroup->addChild(
        new ProgramInputOutputReferencedByCase(context, "referenced_by_separable_tess_ctrl", "", storage,
                                               ProgramInputOutputReferencedByCase::CASE_SEPARABLE_TESS_CTRL));

    // patch
    if (storage == glu::STORAGE_IN)
        targetGroup->addChild(new ProgramInputOutputReferencedByCase(
            context, "referenced_by_separable_tess_eval_patch_in", "", glu::STORAGE_PATCH_IN,
            ProgramInputOutputReferencedByCase::CASE_SEPARABLE_TESS_EVAL));
    else if (storage == glu::STORAGE_OUT)
        targetGroup->addChild(new ProgramInputOutputReferencedByCase(
            context, "referenced_by_separable_tess_ctrl_patch_out", "", glu::STORAGE_PATCH_OUT,
            ProgramInputOutputReferencedByCase::CASE_SEPARABLE_TESS_CTRL));
    else
        DE_ASSERT(false);
}

template <ProgramInterface interface>
static void generateProgramInputOutputTypeBasicTypeCases(Context &context,
                                                         const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                         tcu::TestCaseGroup *targetGroup, bool allowMatrixCases,
                                                         int expandLevel)
{
    static const struct
    {
        glu::DataType type;
        bool isMatrix;
        int level;
    } variableTypes[] = {
        {glu::TYPE_FLOAT, false, 0},       {glu::TYPE_INT, false, 1},         {glu::TYPE_UINT, false, 1},
        {glu::TYPE_FLOAT_VEC2, false, 2},  {glu::TYPE_FLOAT_VEC3, false, 1},  {glu::TYPE_FLOAT_VEC4, false, 2},
        {glu::TYPE_INT_VEC2, false, 0},    {glu::TYPE_INT_VEC3, false, 2},    {glu::TYPE_INT_VEC4, false, 2},
        {glu::TYPE_UINT_VEC2, false, 2},   {glu::TYPE_UINT_VEC3, false, 2},   {glu::TYPE_UINT_VEC4, false, 0},
        {glu::TYPE_FLOAT_MAT2, true, 2},   {glu::TYPE_FLOAT_MAT2X3, true, 2}, {glu::TYPE_FLOAT_MAT2X4, true, 2},
        {glu::TYPE_FLOAT_MAT3X2, true, 0}, {glu::TYPE_FLOAT_MAT3, true, 2},   {glu::TYPE_FLOAT_MAT3X4, true, 2},
        {glu::TYPE_FLOAT_MAT4X2, true, 2}, {glu::TYPE_FLOAT_MAT4X3, true, 2}, {glu::TYPE_FLOAT_MAT4, true, 2},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
    {
        if (!allowMatrixCases && variableTypes[ndx].isMatrix)
            continue;

        if (variableTypes[ndx].level <= expandLevel)
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].type));
            targetGroup->addChild(new ResourceTestCase(
                context, variable, ProgramResourceQueryTestTarget(interface, PROGRAMRESOURCEPROP_TYPE)));
        }
    }
}

static void generateProgramInputTypeBlockContents(Context &context,
                                                  const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                  tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                  bool isGL45)
{
    DE_UNREF(isGL45);
    const bool inDefaultBlock = parentStructure->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK;
    const ResourceDefinition::Node::SharedPtr input =
        (inDefaultBlock) ? (ResourceDefinition::Node::SharedPtr(
                               new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_IN))) :
                           (parentStructure);
    const glu::ShaderType firstStage = getShaderMaskFirstStage(presentShadersMask);
    const int interfaceBlockExpansionReducement =
        (!inDefaultBlock) ? (1) : (0); // lesser expansions on block members to keep test counts reasonable

    if (firstStage == glu::SHADERTYPE_VERTEX)
    {
        // Only basic types (and no booleans)
        generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(
            context, input, targetGroup, true, 2 - interfaceBlockExpansionReducement);
    }
    else if (firstStage == glu::SHADERTYPE_FRAGMENT || !inDefaultBlock)
    {
        const ResourceDefinition::Node::SharedPtr flatShading(
            new ResourceDefinition::InterpolationQualifier(input, glu::INTERPOLATION_FLAT));

        // Only basic types, arrays of basic types, struct of basic types (and no booleans)
        {
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");
            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(
                context, flatShading, blockGroup, true, 2 - interfaceBlockExpansionReducement);
        }
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(flatShading));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "array", "Array types");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(
                context, arrayElement, blockGroup, true, 2 - interfaceBlockExpansionReducement);
        }
        {
            const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(flatShading));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "struct", "Struct types");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(
                context, structMember, blockGroup, true, 2 - interfaceBlockExpansionReducement);
        }
    }
    else if (firstStage == glu::SHADERTYPE_TESSELLATION_CONTROL || firstStage == glu::SHADERTYPE_GEOMETRY)
    {
        // arrayed interface

        // Only basic types (and no booleans)
        const ResourceDefinition::Node::SharedPtr arrayElement(
            new ResourceDefinition::ArrayElement(input, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
        generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(context, arrayElement, targetGroup,
                                                                                     true, 2);
    }
    else if (firstStage == glu::SHADERTYPE_TESSELLATION_EVALUATION)
    {
        // arrayed interface
        const ResourceDefinition::Node::SharedPtr patchInput(
            new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_PATCH_IN));

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(input, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(context, arrayElem, blockGroup,
                                                                                         true, 2);
        }
        // extension forbids use arrays of structs
        // extension forbids use arrays of arrays

        // .patch_var
        {
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "patch_var", "Basic types, per-patch");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(context, patchInput,
                                                                                         blockGroup, true, 1);
        }
        // .patch_var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(patchInput));
            tcu::TestCaseGroup *const blockGroup =
                new TestCaseGroup(context, "patch_var_struct", "Struct types, per-patch");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(context, structMbr, blockGroup,
                                                                                         true, 1);
        }
        // .patch_var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(patchInput));
            tcu::TestCaseGroup *const blockGroup =
                new TestCaseGroup(context, "patch_var_array", "Array types, per-patch");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_INPUT>(context, arrayElem, blockGroup,
                                                                                         true, 1);
        }
    }
    else if (firstStage == glu::SHADERTYPE_COMPUTE)
    {
        // nada
    }
    else
        DE_ASSERT(false);
}

static void generateProgramOutputTypeBlockContents(Context &context,
                                                   const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                   tcu::TestCaseGroup *targetGroup, uint32_t presentShadersMask,
                                                   bool isGL45)
{
    DE_UNREF(isGL45);
    const bool inDefaultBlock = parentStructure->getType() == ResourceDefinition::Node::TYPE_DEFAULT_BLOCK;
    const ResourceDefinition::Node::SharedPtr output =
        (inDefaultBlock) ? (ResourceDefinition::Node::SharedPtr(
                               new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_OUT))) :
                           (parentStructure);
    const glu::ShaderType lastStage = getShaderMaskLastStage(presentShadersMask);
    const int interfaceBlockExpansionReducement =
        (!inDefaultBlock) ? (1) : (0); // lesser expansions on block members to keep test counts reasonable

    if (lastStage == glu::SHADERTYPE_VERTEX || lastStage == glu::SHADERTYPE_GEOMETRY ||
        lastStage == glu::SHADERTYPE_TESSELLATION_EVALUATION || !inDefaultBlock)
    {
        const ResourceDefinition::Node::SharedPtr flatShading(
            new ResourceDefinition::InterpolationQualifier(output, glu::INTERPOLATION_FLAT));

        // Only basic types, arrays of basic types, struct of basic types (and no booleans)
        {
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");
            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(
                context, flatShading, blockGroup, true, 2 - interfaceBlockExpansionReducement);
        }
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(flatShading));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "array", "Array types");
            const int typeExpansionReducement =
                (lastStage != glu::SHADERTYPE_VERTEX) ? (1) : (0); // lesser expansions on other stages
            const int expansionLevel = 2 - interfaceBlockExpansionReducement - typeExpansionReducement;

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(
                context, arrayElement, blockGroup, true, expansionLevel);
        }
        {
            const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(flatShading));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "struct", "Struct types");
            const int typeExpansionReducement =
                (lastStage != glu::SHADERTYPE_VERTEX) ? (1) : (0); // lesser expansions on other stages
            const int expansionLevel = 2 - interfaceBlockExpansionReducement - typeExpansionReducement;

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(
                context, structMember, blockGroup, true, expansionLevel);
        }
    }
    else if (lastStage == glu::SHADERTYPE_FRAGMENT)
    {
        // only basic type and basic type array (and no booleans or matrices)
        {
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");
            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(context, output, blockGroup,
                                                                                          false, 2);
        }
        {
            const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(output));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "array", "Array types");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(context, arrayElement,
                                                                                          blockGroup, false, 2);
        }
    }
    else if (lastStage == glu::SHADERTYPE_TESSELLATION_CONTROL)
    {
        // arrayed interface
        const ResourceDefinition::Node::SharedPtr patchOutput(
            new ResourceDefinition::StorageQualifier(parentStructure, glu::STORAGE_PATCH_OUT));

        // .var
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(
                new ResourceDefinition::ArrayElement(output, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(context, arrayElem,
                                                                                          blockGroup, true, 2);
        }
        // extension forbids use arrays of structs
        // extension forbids use arrays of arrays

        // .patch_var
        {
            tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "patch_var", "Basic types, per-patch");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(context, patchOutput,
                                                                                          blockGroup, true, 1);
        }
        // .patch_var_struct
        {
            const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(patchOutput));
            tcu::TestCaseGroup *const blockGroup =
                new TestCaseGroup(context, "patch_var_struct", "Struct types, per-patch");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(context, structMbr,
                                                                                          blockGroup, true, 1);
        }
        // .patch_var_array
        {
            const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(patchOutput));
            tcu::TestCaseGroup *const blockGroup =
                new TestCaseGroup(context, "patch_var_array", "Array types, per-patch");

            targetGroup->addChild(blockGroup);
            generateProgramInputOutputTypeBasicTypeCases<PROGRAMINTERFACE_PROGRAM_OUTPUT>(context, arrayElem,
                                                                                          blockGroup, true, 1);
        }
    }
    else if (lastStage == glu::SHADERTYPE_COMPUTE)
    {
        // nada
    }
    else
        DE_ASSERT(false);
}

class ProgramInputTestGroup : public TestCaseGroup
{
public:
    ProgramInputTestGroup(Context &context, bool is_GL45);
    void init(void);

private:
    bool m_isGL45;
};

ProgramInputTestGroup::ProgramInputTestGroup(Context &context, bool is_GL45)
    : TestCaseGroup(context, "program_input", "Program input")
    , m_isGL45(is_GL45)
{
}

void ProgramInputTestGroup::init(void)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    // .resource_list
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, glslVersion, true, true, m_isGL45,
                                                   generateProgramInputResourceListBlockContents);
    }

    // .array_size
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_size", "Array size");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(
            m_context, blockGroup, glslVersion, false, true, m_isGL45,
            generateProgramInputBasicBlockContents<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
    }

    // .location
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "location", "Location");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, glslVersion, false, true, m_isGL45,
                                                   generateProgramInputLocationBlockContents);
    }

    // .name_length
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Name length");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(
            m_context, blockGroup, glslVersion, false, true, m_isGL45,
            generateProgramInputBasicBlockContents<PROGRAMRESOURCEPROP_NAME_LENGTH>);
    }

    // .referenced_by
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "referenced_by", "Reference by shader");
        addChild(blockGroup);
        generateProgramInputOutputReferencedByCases(m_context, blockGroup, glu::STORAGE_IN);
    }

    // .type
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "type", "Type");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, glslVersion, false, true, m_isGL45,
                                                   generateProgramInputTypeBlockContents);
    }

    // .is_per_patch
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "is_per_patch", "Is per patch");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(
            m_context, blockGroup, glslVersion, false, true, m_isGL45,
            generateProgramInputBasicBlockContents<PROGRAMRESOURCEPROP_IS_PER_PATCH>);
    }
}

class ProgramOutputTestGroup : public TestCaseGroup
{
public:
    ProgramOutputTestGroup(Context &context, bool is_GL45);
    void init(void);

private:
    bool m_isGL45;
};

ProgramOutputTestGroup::ProgramOutputTestGroup(Context &context, bool is_GL45)
    : TestCaseGroup(context, "program_output", "Program output")
    , m_isGL45(is_GL45)
{
}

void ProgramOutputTestGroup::init(void)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    // .resource_list
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "resource_list", "Resource list");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, glslVersion, true, false, m_isGL45,
                                                   generateProgramOutputResourceListBlockContents);
    }

    // .array_size
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "array_size", "Array size");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(
            m_context, blockGroup, glslVersion, false, false, m_isGL45,
            generateProgramOutputBasicBlockContents<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
    }

    // .location
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "location", "Location");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, glslVersion, false, false, m_isGL45,
                                                   generateProgramOutputLocationBlockContents);
    }

    // .name_length
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "name_length", "Name length");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(
            m_context, blockGroup, glslVersion, false, false, m_isGL45,
            generateProgramOutputBasicBlockContents<PROGRAMRESOURCEPROP_NAME_LENGTH>);
    }

    // .referenced_by
    {
        tcu::TestCaseGroup *const blockGroup =
            new tcu::TestCaseGroup(m_testCtx, "referenced_by", "Reference by shader");
        addChild(blockGroup);
        generateProgramInputOutputReferencedByCases(m_context, blockGroup, glu::STORAGE_OUT);
    }

    // .type
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "type", "Type");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(m_context, blockGroup, glslVersion, false, false, m_isGL45,
                                                   generateProgramOutputTypeBlockContents);
    }

    // .is_per_patch
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(m_testCtx, "is_per_patch", "Is per patch");
        addChild(blockGroup);
        generateProgramInputOutputShaderCaseBlocks(
            m_context, blockGroup, glslVersion, false, false, m_isGL45,
            generateProgramOutputBasicBlockContents<PROGRAMRESOURCEPROP_IS_PER_PATCH>);
    }
}

static void generateTransformFeedbackShaderCaseBlocks(
    Context &context, tcu::TestCaseGroup *targetGroup, glu::GLSLVersion glslVersion,
    void (*blockContentGenerator)(Context &, const ResourceDefinition::Node::SharedPtr &, tcu::TestCaseGroup *, bool))
{
    static const struct
    {
        const char *name;
        uint32_t stageBits;
        uint32_t lastStageBit;
        bool reducedSet;
    } pipelines[] = {
        {"vertex_fragment", (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT),
         (1 << glu::SHADERTYPE_VERTEX), false},
        {"vertex_tess_fragment",
         (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) | (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) |
             (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION),
         (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION), true},
        {"vertex_geo_fragment",
         (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) | (1 << glu::SHADERTYPE_GEOMETRY),
         (1 << glu::SHADERTYPE_GEOMETRY), true},
        {"vertex_tess_geo_fragment",
         (1 << glu::SHADERTYPE_VERTEX) | (1 << glu::SHADERTYPE_FRAGMENT) | (1 << glu::SHADERTYPE_TESSELLATION_CONTROL) |
             (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION) | (1 << glu::SHADERTYPE_GEOMETRY),
         (1 << glu::SHADERTYPE_GEOMETRY), true},
    };
    static const struct
    {
        const char *name;
        glu::ShaderType stage;
        bool reducedSet;
    } singleStageCases[] = {
        {"separable_vertex", glu::SHADERTYPE_VERTEX, false},
        {"separable_tess_eval", glu::SHADERTYPE_TESSELLATION_EVALUATION, true},
        {"separable_geometry", glu::SHADERTYPE_GEOMETRY, true},
    };

    // monolithic pipeline
    for (int pipelineNdx = 0; pipelineNdx < DE_LENGTH_OF_ARRAY(pipelines); ++pipelineNdx)
    {
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, pipelines[pipelineNdx].name, "");
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
        const ResourceDefinition::Node::SharedPtr shaderSet(new ResourceDefinition::ShaderSet(
            program, glslVersion, pipelines[pipelineNdx].stageBits, pipelines[pipelineNdx].lastStageBit));

        targetGroup->addChild(blockGroup);
        blockContentGenerator(context, shaderSet, blockGroup, pipelines[pipelineNdx].reducedSet);
    }

    // separable pipeline
    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(singleStageCases); ++ndx)
    {
        TestCaseGroup *const blockGroup = new TestCaseGroup(context, singleStageCases[ndx].name, "");
        const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program(true));
        const ResourceDefinition::Node::SharedPtr shader(
            new ResourceDefinition::Shader(program, singleStageCases[ndx].stage, glslVersion));

        targetGroup->addChild(blockGroup);
        blockContentGenerator(context, shader, blockGroup, singleStageCases[ndx].reducedSet);
    }
}

static void generateTransformFeedbackResourceListBlockContents(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *targetGroup,
    bool reducedSet)
{
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr output(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));

    DE_UNREF(reducedSet);

    // .builtin_gl_position
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(defaultBlock, "gl_Position"));
        targetGroup->addChild(new FeedbackResourceListTestCase(context, xfbTarget, "builtin_gl_position"));
    }
    // .default_block_basic_type
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(output));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_basic_type"));
    }
    // .default_block_struct_member
    {
        const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(output));
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(structMbr));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_struct_member"));
    }
    // .default_block_array
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(output));
        const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(xfbTarget));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_array"));
    }
    // .default_block_array_element
    {
        const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(output));
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(arrayElem));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new FeedbackResourceListTestCase(context, variable, "default_block_array_element"));
    }
}

template <ProgramResourcePropFlags TargetProp>
static void generateTransformFeedbackVariableBlockContents(Context &context,
                                                           const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                           tcu::TestCaseGroup *targetGroup, bool reducedSet)
{
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr output(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));

    DE_UNREF(reducedSet);

    // .builtin_gl_position
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(defaultBlock, "gl_Position"));
        targetGroup->addChild(new ResourceTestCase(
            context, xfbTarget, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp),
            "builtin_gl_position"));
    }
    // .default_block_basic_type
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(output));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp),
            "default_block_basic_type"));
    }
    // .default_block_struct_member
    {
        const ResourceDefinition::Node::SharedPtr structMbr(new ResourceDefinition::StructMember(output));
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(structMbr));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp),
            "default_block_struct_member"));
    }
    // .default_block_array
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(output));
        const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(xfbTarget));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(arrayElem, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp),
            "default_block_array"));
    }
    // .default_block_array_element
    {
        const ResourceDefinition::Node::SharedPtr arrayElem(new ResourceDefinition::ArrayElement(output));
        const ResourceDefinition::Node::SharedPtr xfbTarget(new ResourceDefinition::TransformFeedbackTarget(arrayElem));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(xfbTarget, glu::TYPE_FLOAT_VEC4));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, TargetProp),
            "default_block_array_element"));
    }
}

static void generateTransformFeedbackVariableBasicTypeCases(Context &context,
                                                            const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                            tcu::TestCaseGroup *targetGroup, bool reducedSet)
{
    static const struct
    {
        glu::DataType type;
        bool important;
    } variableTypes[] = {
        {glu::TYPE_FLOAT, true},         {glu::TYPE_INT, true},           {glu::TYPE_UINT, true},

        {glu::TYPE_FLOAT_VEC2, false},   {glu::TYPE_FLOAT_VEC3, true},    {glu::TYPE_FLOAT_VEC4, false},

        {glu::TYPE_INT_VEC2, false},     {glu::TYPE_INT_VEC3, true},      {glu::TYPE_INT_VEC4, false},

        {glu::TYPE_UINT_VEC2, true},     {glu::TYPE_UINT_VEC3, false},    {glu::TYPE_UINT_VEC4, false},

        {glu::TYPE_FLOAT_MAT2, false},   {glu::TYPE_FLOAT_MAT2X3, false}, {glu::TYPE_FLOAT_MAT2X4, false},
        {glu::TYPE_FLOAT_MAT3X2, false}, {glu::TYPE_FLOAT_MAT3, false},   {glu::TYPE_FLOAT_MAT3X4, true},
        {glu::TYPE_FLOAT_MAT4X2, false}, {glu::TYPE_FLOAT_MAT4X3, false}, {glu::TYPE_FLOAT_MAT4, false},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
    {
        if (variableTypes[ndx].important || !reducedSet)
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].type));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, PROGRAMRESOURCEPROP_TYPE)));
        }
    }
}

static void generateTransformFeedbackVariableTypeBlockContents(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *targetGroup,
    bool reducedSet)
{
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr output(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_OUT));
    const ResourceDefinition::Node::SharedPtr flatShading(
        new ResourceDefinition::InterpolationQualifier(output, glu::INTERPOLATION_FLAT));

    // Only builtins, basic types, arrays of basic types, struct of basic types (and no booleans)
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(defaultBlock, "gl_Position"));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "builtin", "Built-in outputs");

        targetGroup->addChild(blockGroup);
        blockGroup->addChild(new ResourceTestCase(
            context, xfbTarget,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_TRANSFORM_FEEDBACK_VARYING, PROGRAMRESOURCEPROP_TYPE),
            "gl_position"));
    }
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(flatShading));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic types");

        targetGroup->addChild(blockGroup);
        generateTransformFeedbackVariableBasicTypeCases(context, xfbTarget, blockGroup, reducedSet);
    }
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(flatShading));
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(arrayElement));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "array", "Array types");

        targetGroup->addChild(blockGroup);
        generateTransformFeedbackVariableBasicTypeCases(context, xfbTarget, blockGroup, reducedSet);
    }
    {
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(flatShading));
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(xfbTarget));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "whole_array", "Whole array");

        targetGroup->addChild(blockGroup);
        generateTransformFeedbackVariableBasicTypeCases(context, arrayElement, blockGroup, reducedSet);
    }
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(flatShading));
        const ResourceDefinition::Node::SharedPtr xfbTarget(
            new ResourceDefinition::TransformFeedbackTarget(structMember));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "struct", "Struct types");

        targetGroup->addChild(blockGroup);
        generateTransformFeedbackVariableBasicTypeCases(context, xfbTarget, blockGroup, reducedSet);
    }
}

class TransformFeedbackVaryingTestGroup : public TestCaseGroup
{
public:
    TransformFeedbackVaryingTestGroup(Context &context);
    void init(void);
};

TransformFeedbackVaryingTestGroup::TransformFeedbackVaryingTestGroup(Context &context)
    : TestCaseGroup(context, "transform_feedback_varying", "Transform feedback varyings")
{
}

void TransformFeedbackVaryingTestGroup::init(void)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    // .resource_list
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "resource_list", "Resource list");
        addChild(blockGroup);
        generateTransformFeedbackShaderCaseBlocks(m_context, blockGroup, glslVersion,
                                                  generateTransformFeedbackResourceListBlockContents);
    }

    // .array_size
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "array_size", "Array size");
        addChild(blockGroup);
        generateTransformFeedbackShaderCaseBlocks(
            m_context, blockGroup, glslVersion,
            generateTransformFeedbackVariableBlockContents<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
    }

    // .name_length
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "name_length", "Name length");
        addChild(blockGroup);
        generateTransformFeedbackShaderCaseBlocks(
            m_context, blockGroup, glslVersion,
            generateTransformFeedbackVariableBlockContents<PROGRAMRESOURCEPROP_NAME_LENGTH>);
    }

    // .type
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "type", "Type");
        addChild(blockGroup);
        generateTransformFeedbackShaderCaseBlocks(m_context, blockGroup, glslVersion,
                                                  generateTransformFeedbackVariableTypeBlockContents);
    }
}

static void generateBufferVariableBufferCaseBlocks(
    Context &context, tcu::TestCaseGroup *targetGroup, glu::GLSLVersion glslVersion,
    void (*blockContentGenerator)(Context &, const ResourceDefinition::Node::SharedPtr &, tcu::TestCaseGroup *))
{
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr bufferStorage(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
    const ResourceDefinition::Node::SharedPtr binding(
        new ResourceDefinition::LayoutQualifier(bufferStorage, glu::Layout(-1, 0)));

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(binding, true));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "named_block", "Named block");

        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, buffer, blockGroup);
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(binding, false));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unnamed_block", "Unnamed block");

        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, buffer, blockGroup);
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(binding));
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "block_array", "Block array");

        targetGroup->addChild(blockGroup);

        blockContentGenerator(context, buffer, blockGroup);
    }
}

static void generateBufferVariableResourceListBlockContentsProxy(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *const targetGroup)
{
    generateBufferBackedResourceListBlockContentCases(context, parentStructure, targetGroup,
                                                      PROGRAMINTERFACE_BUFFER_VARIABLE, 4);
}

static void generateBufferVariableArraySizeSubCases(Context &context,
                                                    const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                    tcu::TestCaseGroup *const targetGroup,
                                                    ProgramResourcePropFlags targetProp, bool sizedArray,
                                                    bool extendedCases)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp);
    tcu::TestCaseGroup *aggregateGroup;

    // .types
    if (extendedCases)
    {
        tcu::TestCaseGroup *const blockGroup = new tcu::TestCaseGroup(context.getTestContext(), "types", "Types");
        targetGroup->addChild(blockGroup);

        generateVariableCases(context, parentStructure, blockGroup, queryTarget, (sizedArray) ? (2) : (1), false);
    }

    // .aggregates
    if (extendedCases)
    {
        aggregateGroup = new tcu::TestCaseGroup(context.getTestContext(), "aggregates", "Aggregate types");
        targetGroup->addChild(aggregateGroup);
    }
    else
        aggregateGroup = targetGroup;

    // .float_*
    generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface,
                                                      glu::TYPE_FLOAT, (extendedCases && sizedArray) ? (2) : (1),
                                                      !extendedCases);

    // .bool_*
    generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface,
                                                      glu::TYPE_BOOL, (extendedCases && sizedArray) ? (1) : (0),
                                                      !extendedCases);

    // .bvec3_*
    generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface,
                                                      glu::TYPE_BOOL_VEC3, (extendedCases && sizedArray) ? (2) : (1),
                                                      !extendedCases);

    // .vec4_*
    generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface,
                                                      glu::TYPE_FLOAT_VEC4, (extendedCases && sizedArray) ? (2) : (1),
                                                      !extendedCases);

    // .ivec2_*
    generateBufferBackedArrayStrideTypeAggregateCases(context, parentStructure, aggregateGroup, queryTarget.interface,
                                                      glu::TYPE_INT_VEC2, (extendedCases && sizedArray) ? (2) : (1),
                                                      !extendedCases);
}

template <ProgramResourcePropFlags TargetProp>
static void generateBufferVariableArrayCases(Context &context,
                                             const ResourceDefinition::Node::SharedPtr &parentStructure,
                                             tcu::TestCaseGroup *const targetGroup)
{
    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, TargetProp);
    const bool namedNonArrayBlock =
        static_cast<const ResourceDefinition::InterfaceBlock *>(parentStructure.get())->m_named &&
        parentStructure->getEnclosingNode()->getType() != ResourceDefinition::Node::TYPE_ARRAY_ELEMENT;

    // .non_array
    if (namedNonArrayBlock)
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "non_array", "Non-array target");
        targetGroup->addChild(blockGroup);

        generateVariableCases(context, parentStructure, blockGroup, queryTarget, 1, false);
    }

    // .sized
    {
        const ResourceDefinition::Node::SharedPtr sized(new ResourceDefinition::ArrayElement(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "sized", "Sized target");
        targetGroup->addChild(blockGroup);

        generateBufferVariableArraySizeSubCases(context, sized, blockGroup, TargetProp, true, namedNonArrayBlock);
    }

    // .unsized
    {
        const ResourceDefinition::Node::SharedPtr unsized(
            new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unsized", "Unsized target");
        targetGroup->addChild(blockGroup);

        generateBufferVariableArraySizeSubCases(context, unsized, blockGroup, TargetProp, false, namedNonArrayBlock);
    }
}

static void generateBufferVariableBlockIndexCases(Context &context, glu::GLSLVersion glslVersion,
                                                  tcu::TestCaseGroup *const targetGroup)
{
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr bufferStorage(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
    const ResourceDefinition::Node::SharedPtr binding(
        new ResourceDefinition::LayoutQualifier(bufferStorage, glu::Layout(-1, 0)));

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(binding, true));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BLOCK_INDEX),
            "named_block"));
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(binding, false));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BLOCK_INDEX),
            "unnamed_block"));
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(binding));
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(buffer, glu::TYPE_FLOAT_VEC4));

        targetGroup->addChild(new ResourceTestCase(
            context, variable,
            ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BLOCK_INDEX),
            "block_array"));
    }
}

static void generateBufferVariableMatrixCaseBlocks(
    Context &context, tcu::TestCaseGroup *const targetGroup, glu::GLSLVersion glslVersion,
    void (*blockContentGenerator)(Context &, const ResourceDefinition::Node::SharedPtr &, tcu::TestCaseGroup *, bool))
{
    static const struct
    {
        const char *name;
        const char *description;
        bool namedBlock;
        bool extendedBasicTypeCases;
        glu::MatrixOrder order;
    } children[] = {
        {"named_block", "Named uniform block", true, true, glu::MATRIXORDER_LAST},
        {"named_block_row_major", "Named uniform block", true, false, glu::MATRIXORDER_ROW_MAJOR},
        {"named_block_col_major", "Named uniform block", true, false, glu::MATRIXORDER_COLUMN_MAJOR},
        {"unnamed_block", "Unnamed uniform block", false, false, glu::MATRIXORDER_LAST},
        {"unnamed_block_row_major", "Unnamed uniform block", false, false, glu::MATRIXORDER_ROW_MAJOR},
        {"unnamed_block_col_major", "Unnamed uniform block", false, false, glu::MATRIXORDER_COLUMN_MAJOR},
    };

    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr buffer(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));

    for (int childNdx = 0; childNdx < (int)DE_LENGTH_OF_ARRAY(children); ++childNdx)
    {
        ResourceDefinition::Node::SharedPtr parentStructure = buffer;
        tcu::TestCaseGroup *const blockGroup =
            new TestCaseGroup(context, children[childNdx].name, children[childNdx].description);

        targetGroup->addChild(blockGroup);

        if (children[childNdx].order != glu::MATRIXORDER_LAST)
        {
            glu::Layout layout;
            layout.matrixOrder = children[childNdx].order;
            parentStructure =
                ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(parentStructure, layout));
        }

        parentStructure = ResourceDefinition::Node::SharedPtr(
            new ResourceDefinition::InterfaceBlock(parentStructure, children[childNdx].namedBlock));

        blockContentGenerator(context, parentStructure, blockGroup, children[childNdx].extendedBasicTypeCases);
    }
}

static void generateBufferVariableMatrixVariableBasicTypeCases(
    Context &context, const ResourceDefinition::Node::SharedPtr &parentStructure, tcu::TestCaseGroup *targetGroup,
    ProgramResourcePropFlags targetProp)
{
    // all matrix types and some non-matrix

    static const glu::DataType variableTypes[] = {
        glu::TYPE_FLOAT,        glu::TYPE_INT_VEC3,     glu::TYPE_FLOAT_MAT2, glu::TYPE_FLOAT_MAT2X3,
        glu::TYPE_FLOAT_MAT2X4, glu::TYPE_FLOAT_MAT3X2, glu::TYPE_FLOAT_MAT3, glu::TYPE_FLOAT_MAT3X4,
        glu::TYPE_FLOAT_MAT4X2, glu::TYPE_FLOAT_MAT4X3, glu::TYPE_FLOAT_MAT4,
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
    {
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(parentStructure, variableTypes[ndx]));
        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp)));
    }
}

static void generateBufferVariableMatrixVariableCases(Context &context,
                                                      const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                      tcu::TestCaseGroup *targetGroup,
                                                      ProgramResourcePropFlags targetProp)
{
    // Basic aggregates
    generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup,
                                                   PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp, glu::TYPE_FLOAT_MAT3X2,
                                                   "", 2);

    // Unsized array
    {
        const ResourceDefinition::Node::SharedPtr unsized(
            new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
        const ResourceDefinition::Node::SharedPtr variable(
            new ResourceDefinition::Variable(unsized, glu::TYPE_FLOAT_MAT3X2));

        targetGroup->addChild(new ResourceTestCase(
            context, variable, ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, targetProp),
            "var_unsized_array"));
    }
}

template <ProgramResourcePropFlags TargetProp>
static void generateBufferVariableMatrixCases(Context &context,
                                              const ResourceDefinition::Node::SharedPtr &parentStructure,
                                              tcu::TestCaseGroup *targetGroup, bool extendedTypeCases)
{
    // .types
    if (extendedTypeCases)
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "types", "Types");
        targetGroup->addChild(blockGroup);
        generateBufferVariableMatrixVariableBasicTypeCases(context, parentStructure, blockGroup, TargetProp);
    }

    // .no_qualifier
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "no_qualifier", "No qualifier");
        targetGroup->addChild(blockGroup);
        generateBufferVariableMatrixVariableCases(context, parentStructure, blockGroup, TargetProp);
    }

    // .column_major
    {
        const ResourceDefinition::Node::SharedPtr matrixOrder(new ResourceDefinition::LayoutQualifier(
            parentStructure, glu::Layout(-1, -1, -1, glu::FORMATLAYOUT_LAST, glu::MATRIXORDER_COLUMN_MAJOR)));

        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "column_major", "Column major qualifier");
        targetGroup->addChild(blockGroup);
        generateBufferVariableMatrixVariableCases(context, matrixOrder, blockGroup, TargetProp);
    }

    // .row_major
    {
        const ResourceDefinition::Node::SharedPtr matrixOrder(new ResourceDefinition::LayoutQualifier(
            parentStructure, glu::Layout(-1, -1, -1, glu::FORMATLAYOUT_LAST, glu::MATRIXORDER_ROW_MAJOR)));

        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "row_major", "Row major qualifier");
        targetGroup->addChild(blockGroup);
        generateBufferVariableMatrixVariableCases(context, matrixOrder, blockGroup, TargetProp);
    }
}

static void generateBufferVariableNameLengthCases(Context &context,
                                                  const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                  tcu::TestCaseGroup *targetGroup)
{
    // .sized
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "sized", "Sized target");
        targetGroup->addChild(blockGroup);

        generateBufferBackedVariableAggregateTypeCases(context, parentStructure, blockGroup,
                                                       PROGRAMINTERFACE_BUFFER_VARIABLE,
                                                       PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 3);
    }

    // .unsized
    {
        const ResourceDefinition::Node::SharedPtr unsized(
            new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unsized", "Unsized target");
        targetGroup->addChild(blockGroup);

        generateBufferBackedVariableAggregateTypeCases(context, unsized, blockGroup, PROGRAMINTERFACE_BUFFER_VARIABLE,
                                                       PROGRAMRESOURCEPROP_NAME_LENGTH, glu::TYPE_FLOAT, "", 2);
    }
}

static void generateBufferVariableOffsetCases(Context &context,
                                              const ResourceDefinition::Node::SharedPtr &parentStructure,
                                              tcu::TestCaseGroup *targetGroup)
{
    // .sized
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "sized", "Sized target");
        targetGroup->addChild(blockGroup);

        generateBufferBackedVariableAggregateTypeCases(context, parentStructure, blockGroup,
                                                       PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_OFFSET,
                                                       glu::TYPE_FLOAT, "", 3);
    }

    // .unsized
    {
        const ResourceDefinition::Node::SharedPtr unsized(
            new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unsized", "Unsized target");
        targetGroup->addChild(blockGroup);

        generateBufferBackedVariableAggregateTypeCases(context, unsized, blockGroup, PROGRAMINTERFACE_BUFFER_VARIABLE,
                                                       PROGRAMRESOURCEPROP_OFFSET, glu::TYPE_FLOAT, "", 2);
    }
}

static void generateBufferVariableReferencedByBlockContents(Context &context,
                                                            const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                            tcu::TestCaseGroup *targetGroup, int expandLevel)
{
    DE_UNREF(expandLevel);

    const ProgramResourceQueryTestTarget queryTarget(PROGRAMINTERFACE_BUFFER_VARIABLE,
                                                     PROGRAMRESOURCEPROP_REFERENCED_BY_SHADER);
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(parentStructure));
    const ResourceDefinition::Node::SharedPtr storage(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
    const bool singleShaderCase = parentStructure->getType() == ResourceDefinition::Node::TYPE_SHADER;

    // .named_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(storage, true));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "named_block", "Named block");

        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, buffer, blockGroup, queryTarget, singleShaderCase);
    }

    // .unnamed_block
    {
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(storage, false));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "unnamed_block", "Unnamed block");

        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, buffer, blockGroup, queryTarget, false);
    }

    // .block_array
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(storage));
        const ResourceDefinition::Node::SharedPtr buffer(new ResourceDefinition::InterfaceBlock(arrayElement, true));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "block_array", "Block array");

        targetGroup->addChild(blockGroup);

        generateBufferReferencedByShaderInterfaceBlockCases(context, buffer, blockGroup, queryTarget, false);
    }
}

template <ProgramResourcePropFlags TargetProp>
static void generateBufferVariableTopLevelCases(Context &context,
                                                const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                tcu::TestCaseGroup *targetGroup)
{
    // basic and aggregate types
    generateBufferBackedVariableAggregateTypeCases(context, parentStructure, targetGroup,
                                                   PROGRAMINTERFACE_BUFFER_VARIABLE, TargetProp, glu::TYPE_FLOAT_VEC4,
                                                   "", 3);

    // basic and aggregate types in an unsized array
    {
        const ResourceDefinition::Node::SharedPtr unsized(
            new ResourceDefinition::ArrayElement(parentStructure, ResourceDefinition::ArrayElement::UNSIZED_ARRAY));

        generateBufferBackedVariableAggregateTypeCases(context, unsized, targetGroup, PROGRAMINTERFACE_BUFFER_VARIABLE,
                                                       TargetProp, glu::TYPE_FLOAT_VEC4, "_unsized_array", 2);
    }
}

static void generateBufferVariableTypeBasicTypeCases(Context &context,
                                                     const ResourceDefinition::Node::SharedPtr &parentStructure,
                                                     tcu::TestCaseGroup *targetGroup, int expandLevel)
{
    static const struct
    {
        int level;
        glu::DataType dataType;
    } variableTypes[] = {
        {0, glu::TYPE_FLOAT},        {1, glu::TYPE_INT},          {1, glu::TYPE_UINT},
        {1, glu::TYPE_BOOL},

        {3, glu::TYPE_FLOAT_VEC2},   {1, glu::TYPE_FLOAT_VEC3},   {1, glu::TYPE_FLOAT_VEC4},

        {3, glu::TYPE_INT_VEC2},     {2, glu::TYPE_INT_VEC3},     {3, glu::TYPE_INT_VEC4},

        {3, glu::TYPE_UINT_VEC2},    {2, glu::TYPE_UINT_VEC3},    {3, glu::TYPE_UINT_VEC4},

        {3, glu::TYPE_BOOL_VEC2},    {2, glu::TYPE_BOOL_VEC3},    {3, glu::TYPE_BOOL_VEC4},

        {2, glu::TYPE_FLOAT_MAT2},   {3, glu::TYPE_FLOAT_MAT2X3}, {3, glu::TYPE_FLOAT_MAT2X4},
        {2, glu::TYPE_FLOAT_MAT3X2}, {2, glu::TYPE_FLOAT_MAT3},   {3, glu::TYPE_FLOAT_MAT3X4},
        {2, glu::TYPE_FLOAT_MAT4X2}, {3, glu::TYPE_FLOAT_MAT4X3}, {2, glu::TYPE_FLOAT_MAT4},
    };

    for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(variableTypes); ++ndx)
    {
        if (variableTypes[ndx].level <= expandLevel)
        {
            const ResourceDefinition::Node::SharedPtr variable(
                new ResourceDefinition::Variable(parentStructure, variableTypes[ndx].dataType));
            targetGroup->addChild(new ResourceTestCase(
                context, variable,
                ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_TYPE)));
        }
    }
}

static void generateBufferVariableTypeCases(Context &context,
                                            const ResourceDefinition::Node::SharedPtr &parentStructure,
                                            tcu::TestCaseGroup *targetGroup, int depth = 3)
{
    // .basic_type
    if (depth > 0)
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "basic_type", "Basic type");
        targetGroup->addChild(blockGroup);
        generateBufferVariableTypeBasicTypeCases(context, parentStructure, blockGroup, depth);
    }
    else
    {
        // flatten bottom-level
        generateBufferVariableTypeBasicTypeCases(context, parentStructure, targetGroup, depth);
    }

    // .array
    if (depth > 0)
    {
        const ResourceDefinition::Node::SharedPtr arrayElement(new ResourceDefinition::ArrayElement(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "array", "Arrays");

        targetGroup->addChild(blockGroup);
        generateBufferVariableTypeCases(context, arrayElement, blockGroup, depth - 1);
    }

    // .struct
    if (depth > 0)
    {
        const ResourceDefinition::Node::SharedPtr structMember(new ResourceDefinition::StructMember(parentStructure));
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(context, "struct", "Structs");

        targetGroup->addChild(blockGroup);
        generateBufferVariableTypeCases(context, structMember, blockGroup, depth - 1);
    }
}

static void generateBufferVariableTypeBlock(Context &context, tcu::TestCaseGroup *targetGroup,
                                            glu::GLSLVersion glslVersion)
{
    const ResourceDefinition::Node::SharedPtr program(new ResourceDefinition::Program());
    const ResourceDefinition::Node::SharedPtr shader(
        new ResourceDefinition::Shader(program, glu::SHADERTYPE_COMPUTE, glslVersion));
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr buffer(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
    const ResourceDefinition::Node::SharedPtr block(new ResourceDefinition::InterfaceBlock(buffer, true));

    generateBufferVariableTypeCases(context, block, targetGroup);
}

static void generateBufferVariableRandomCase(Context &context, tcu::TestCaseGroup *const targetGroup,
                                             glu::GLSLVersion glslVersion, int index, bool onlyExtensionStages)
{
    de::Random rnd(index * 0x12345);
    const ResourceDefinition::Node::SharedPtr shader = generateRandomShaderSet(rnd, glslVersion, onlyExtensionStages);
    const glu::DataType type                         = generateRandomDataType(rnd, true);
    const glu::Layout layout                         = generateRandomVariableLayout(rnd, type, true);
    const bool namedBlock                            = rnd.getBool();
    const ResourceDefinition::Node::SharedPtr defaultBlock(new ResourceDefinition::DefaultBlock(shader));
    const ResourceDefinition::Node::SharedPtr buffer(
        new ResourceDefinition::StorageQualifier(defaultBlock, glu::STORAGE_BUFFER));
    ResourceDefinition::Node::SharedPtr currentStructure(
        new ResourceDefinition::LayoutQualifier(buffer, generateRandomBufferBlockLayout(rnd)));

    if (namedBlock && rnd.getBool())
        currentStructure = ResourceDefinition::Node::SharedPtr(new ResourceDefinition::ArrayElement(currentStructure));
    currentStructure =
        ResourceDefinition::Node::SharedPtr(new ResourceDefinition::InterfaceBlock(currentStructure, namedBlock));

    currentStructure =
        ResourceDefinition::Node::SharedPtr(new ResourceDefinition::LayoutQualifier(currentStructure, layout));
    currentStructure = generateRandomVariableDefinition(rnd, currentStructure, type, layout, true);

    targetGroup->addChild(new ResourceTestCase(
        context, currentStructure,
        ProgramResourceQueryTestTarget(PROGRAMINTERFACE_BUFFER_VARIABLE, PROGRAMRESOURCEPROP_BUFFER_VARIABLE_MASK),
        de::toString(index).c_str()));
}

static void generateBufferVariableRandomCases(Context &context, tcu::TestCaseGroup *const targetGroup,
                                              glu::GLSLVersion glslVersion)
{
    const int numBasicCases   = 40;
    const int numTessGeoCases = 40;

    for (int ndx = 0; ndx < numBasicCases; ++ndx)
        generateBufferVariableRandomCase(context, targetGroup, glslVersion, ndx, false);
    for (int ndx = 0; ndx < numTessGeoCases; ++ndx)
        generateBufferVariableRandomCase(context, targetGroup, glslVersion, numBasicCases + ndx, true);
}

class BufferVariableTestGroup : public TestCaseGroup
{
public:
    BufferVariableTestGroup(Context &context);
    void init(void);
};

BufferVariableTestGroup::BufferVariableTestGroup(Context &context)
    : TestCaseGroup(context, "buffer_variable", "Buffer variable")
{
}

void BufferVariableTestGroup::init(void)
{
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    // .resource_list
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "resource_list", "Resource list");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(m_context, blockGroup, glslVersion,
                                               generateBufferVariableResourceListBlockContentsProxy);
    }

    // .array_size
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "array_size", "Array size");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(m_context, blockGroup, glslVersion,
                                               generateBufferVariableArrayCases<PROGRAMRESOURCEPROP_ARRAY_SIZE>);
    }

    // .array_stride
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "array_stride", "Array stride");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(m_context, blockGroup, glslVersion,
                                               generateBufferVariableArrayCases<PROGRAMRESOURCEPROP_ARRAY_STRIDE>);
    }

    // .block_index
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "block_index", "Block index");
        addChild(blockGroup);
        generateBufferVariableBlockIndexCases(m_context, glslVersion, blockGroup);
    }

    // .is_row_major
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "is_row_major", "Is row major");
        addChild(blockGroup);
        generateBufferVariableMatrixCaseBlocks(m_context, blockGroup, glslVersion,
                                               generateBufferVariableMatrixCases<PROGRAMRESOURCEPROP_MATRIX_ROW_MAJOR>);
    }

    // .matrix_stride
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "matrix_stride", "Matrix stride");
        addChild(blockGroup);
        generateBufferVariableMatrixCaseBlocks(m_context, blockGroup, glslVersion,
                                               generateBufferVariableMatrixCases<PROGRAMRESOURCEPROP_MATRIX_STRIDE>);
    }

    // .name_length
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "name_length", "Name length");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(m_context, blockGroup, glslVersion,
                                               generateBufferVariableNameLengthCases);
    }

    // .offset
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "offset", "Offset");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(m_context, blockGroup, glslVersion, generateBufferVariableOffsetCases);
    }

    // .referenced_by
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "referenced_by", "Referenced by");
        addChild(blockGroup);
        generateReferencedByShaderCaseBlocks(m_context, blockGroup, glslVersion,
                                             generateBufferVariableReferencedByBlockContents);
    }

    // .top_level_array_size
    {
        tcu::TestCaseGroup *const blockGroup =
            new TestCaseGroup(m_context, "top_level_array_size", "Top-level array size");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(
            m_context, blockGroup, glslVersion,
            generateBufferVariableTopLevelCases<PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_SIZE>);
    }

    // .top_level_array_stride
    {
        tcu::TestCaseGroup *const blockGroup =
            new TestCaseGroup(m_context, "top_level_array_stride", "Top-level array stride");
        addChild(blockGroup);
        generateBufferVariableBufferCaseBlocks(
            m_context, blockGroup, glslVersion,
            generateBufferVariableTopLevelCases<PROGRAMRESOURCEPROP_TOP_LEVEL_ARRAY_STRIDE>);
    }

    // .type
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "type", "Type");
        addChild(blockGroup);
        generateBufferVariableTypeBlock(m_context, blockGroup, glslVersion);
    }

    // .random
    {
        tcu::TestCaseGroup *const blockGroup = new TestCaseGroup(m_context, "random", "Random");
        addChild(blockGroup);
        generateBufferVariableRandomCases(m_context, blockGroup, glslVersion);
    }
}

} // namespace

ProgramInterfaceQueryTests::ProgramInterfaceQueryTests(Context &context, bool is_GL45)
    : TestCaseGroup(context, "program_interface_query", "Program interface query tests")
    , m_isGL45(is_GL45)
{
}

ProgramInterfaceQueryTests::~ProgramInterfaceQueryTests(void)
{
}

void ProgramInterfaceQueryTests::init(void)
{
    // Misc queries

    // .buffer_limited_query
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "buffer_limited_query", "Queries limited by the buffer size");

        addChild(group);

        group->addChild(new ResourceNameBufferLimitCase(m_context, "resource_name_query",
                                                        "Test GetProgramResourceName with too small a buffer"));
        group->addChild(new ResourceQueryBufferLimitCase(m_context, "resource_query",
                                                         "Test GetProgramResourceiv with too small a buffer"));
    }

    // Interfaces

    // .uniform
    addChild(new UniformInterfaceTestGroup(m_context));

    // .uniform_block
    addChild(new BufferBackedBlockInterfaceTestGroup(m_context, glu::STORAGE_UNIFORM));

    // .atomic_counter_buffer
    addChild(new AtomicCounterTestGroup(m_context));

    // .program_input
    addChild(new ProgramInputTestGroup(m_context, m_isGL45));

    // .program_output
    addChild(new ProgramOutputTestGroup(m_context, m_isGL45));

    // .transform_feedback_varying
    addChild(new TransformFeedbackVaryingTestGroup(m_context));

    // .buffer_variable
    addChild(new BufferVariableTestGroup(m_context));

    // .shader_storage_block
    addChild(new BufferBackedBlockInterfaceTestGroup(m_context, glu::STORAGE_BUFFER));
}

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