/*-------------------------------------------------------------------------
 * 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 Basic Layout Binding Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fLayoutBindingTests.hpp"

#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuRenderTarget.hpp"

#include "deString.h"
#include "deStringUtil.hpp"
#include "deRandom.hpp"

using tcu::TestLog;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

enum TestType
{
    TESTTYPE_BINDING_SINGLE = 0,
    TESTTYPE_BINDING_MAX,
    TESTTYPE_BINDING_MULTIPLE,
    TESTTYPE_BINDING_ARRAY,
    TESTTYPE_BINDING_MAX_ARRAY,

    TESTTYPE_BINDING_LAST,
};

enum ShaderType
{
    SHADERTYPE_VERTEX = 0,
    SHADERTYPE_FRAGMENT,
    SHADERTYPE_TESS_CONTROL,
    SHADERTYPE_TESS_EVALUATION,
    SHADERTYPE_ALL,

    SHADERTYPE_LAST,
};

enum
{
    MAX_UNIFORM_MULTIPLE_INSTANCES = 7,
    MAX_UNIFORM_ARRAY_SIZE         = 7,
};

std::string generateVertexShader(ShaderType shaderType, const std::string &shaderUniformDeclarations,
                                 const std::string &shaderBody)
{
    static const char *const s_simpleVertexShaderSource = "#version 310 es\n"
                                                          "in highp vec4 a_position;\n"
                                                          "void main (void)\n"
                                                          "{\n"
                                                          "    gl_Position = a_position;\n"
                                                          "}\n";

    switch (shaderType)
    {
    case SHADERTYPE_VERTEX:
    case SHADERTYPE_ALL:
    {
        std::ostringstream vertexShaderSource;
        vertexShaderSource << "#version 310 es\n"
                           << "in highp vec4 a_position;\n"
                           << "out highp vec4 v_color;\n"
                           << "uniform highp int u_arrayNdx;\n\n"
                           << shaderUniformDeclarations << "\n"
                           << "void main (void)\n"
                           << "{\n"
                           << "    highp vec4 color;\n\n"
                           << shaderBody << "\n"
                           << "    v_color = color;\n"
                           << "    gl_Position = a_position;\n"
                           << "}\n";

        return vertexShaderSource.str();
    }

    case SHADERTYPE_FRAGMENT:
    case SHADERTYPE_TESS_CONTROL:
    case SHADERTYPE_TESS_EVALUATION:
        return s_simpleVertexShaderSource;

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

std::string generateFragmentShader(ShaderType shaderType, const std::string &shaderUniformDeclarations,
                                   const std::string &shaderBody)
{
    static const char *const s_simpleFragmentShaderSource = "#version 310 es\n"
                                                            "in highp vec4 v_color;\n"
                                                            "layout(location = 0) out highp vec4 fragColor;\n"
                                                            "void main (void)\n"
                                                            "{\n"
                                                            "    fragColor = v_color;\n"
                                                            "}\n";

    switch (shaderType)
    {
    case SHADERTYPE_VERTEX:
    case SHADERTYPE_TESS_CONTROL:
    case SHADERTYPE_TESS_EVALUATION:
        return s_simpleFragmentShaderSource;

    case SHADERTYPE_FRAGMENT:
    {
        std::ostringstream fragmentShaderSource;
        fragmentShaderSource << "#version 310 es\n"
                             << "layout(location = 0) out highp vec4 fragColor;\n"
                             << "uniform highp int u_arrayNdx;\n\n"
                             << shaderUniformDeclarations << "\n"
                             << "void main (void)\n"
                             << "{\n"
                             << "    highp vec4 color;\n\n"
                             << shaderBody << "\n"
                             << "    fragColor = color;\n"
                             << "}\n";

        return fragmentShaderSource.str();
    }
    case SHADERTYPE_ALL:
    {
        std::ostringstream fragmentShaderSource;
        fragmentShaderSource << "#version 310 es\n"
                             << "in highp vec4 v_color;\n"
                             << "layout(location = 0) out highp vec4 fragColor;\n"
                             << "uniform highp int u_arrayNdx;\n\n"
                             << shaderUniformDeclarations << "\n"
                             << "void main (void)\n"
                             << "{\n"
                             << "    if (v_color.x > 2.0) discard;\n"
                             << "    highp vec4 color;\n\n"
                             << shaderBody << "\n"
                             << "    fragColor = color;\n"
                             << "}\n";

        return fragmentShaderSource.str();
    }

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

std::string generateTessControlShader(ShaderType shaderType, const std::string &shaderUniformDeclarations,
                                      const std::string &shaderBody)
{
    static const char *const s_simpleTessContorlShaderSource =
        "#version 310 es\n"
        "#extension GL_EXT_tessellation_shader : require\n"
        "layout (vertices=3) out;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
        "}\n";

    switch (shaderType)
    {
    case SHADERTYPE_VERTEX:
    case SHADERTYPE_FRAGMENT:
    case SHADERTYPE_TESS_EVALUATION:
        return s_simpleTessContorlShaderSource;

    case SHADERTYPE_TESS_CONTROL:
    case SHADERTYPE_ALL:
    {
        std::ostringstream tessControlShaderSource;
        tessControlShaderSource << "#version 310 es\n"
                                << "#extension GL_EXT_tessellation_shader : require\n"
                                << "layout (vertices=3) out;\n"
                                << "\n"
                                << "uniform highp int u_arrayNdx;\n\n"
                                << shaderUniformDeclarations << "\n"
                                << "void main (void)\n"
                                << "{\n"
                                << "    highp vec4 color;\n\n"
                                << shaderBody << "\n"
                                << "    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;\n"
                                << "}\n";

        return tessControlShaderSource.str();
    }

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

std::string generateTessEvaluationShader(ShaderType shaderType, const std::string &shaderUniformDeclarations,
                                         const std::string &shaderBody)
{
    static const char *const s_simpleTessEvaluationShaderSource =
        "#version 310 es\n"
        "#extension GL_EXT_tessellation_shader : require\n"
        "layout (triangles) in;\n"
        "\n"
        "void main (void)\n"
        "{\n"
        "    gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * gl_in[1].gl_Position + "
        "gl_TessCoord[2] * gl_in[2].gl_Position;\n"
        "}\n";

    switch (shaderType)
    {
    case SHADERTYPE_VERTEX:
    case SHADERTYPE_FRAGMENT:
    case SHADERTYPE_TESS_CONTROL:
        return s_simpleTessEvaluationShaderSource;

    case SHADERTYPE_TESS_EVALUATION:
    case SHADERTYPE_ALL:
    {
        std::ostringstream tessEvaluationShaderSource;
        tessEvaluationShaderSource << "#version 310 es\n"
                                   << "#extension GL_EXT_tessellation_shader : require\n"
                                   << "layout (triangles) in;\n"
                                   << "\n"
                                   << "uniform highp int u_arrayNdx;\n\n"
                                   << shaderUniformDeclarations << "\n"
                                   << "out mediump vec4 v_color;\n"
                                   << "void main (void)\n"
                                   << "{\n"
                                   << "    highp vec4 color;\n\n"
                                   << shaderBody << "\n"
                                   << "    v_color = color;\n"
                                   << "    gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * "
                                      "gl_in[1].gl_Position + gl_TessCoord[2] * gl_in[2].gl_Position;\n"
                                   << "}\n";

        return tessEvaluationShaderSource.str();
    }

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

std::string getUniformName(const std::string &name, int declNdx)
{
    return name + de::toString(declNdx);
}

std::string getUniformName(const std::string &name, int declNdx, int arrNdx)
{
    return name + de::toString(declNdx) + "[" + de::toString(arrNdx) + "]";
}

Vec4 getRandomColor(de::Random &rnd)
{
    const float r = rnd.getFloat(0.2f, 0.9f);
    const float g = rnd.getFloat(0.2f, 0.9f);
    const float b = rnd.getFloat(0.2f, 0.9f);
    return Vec4(r, g, b, 1.0f);
}

class LayoutBindingRenderCase : public TestCase
{
public:
    enum
    {
        MAX_TEST_RENDER_WIDTH  = 256,
        MAX_TEST_RENDER_HEIGHT = 256,
        TEST_TEXTURE_SIZE      = 1,
    };

    LayoutBindingRenderCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                            TestType testType, glw::GLenum maxBindingPointEnum, glw::GLenum maxVertexUnitsEnum,
                            glw::GLenum maxFragmentUnitsEnum, glw::GLenum maxCombinedUnitsEnum,
                            const std::string &uniformName);
    virtual ~LayoutBindingRenderCase(void);

    virtual void init(void);
    virtual void deinit(void);

    int getRenderWidth(void) const
    {
        return de::min((int)MAX_TEST_RENDER_WIDTH, m_context.getRenderTarget().getWidth());
    }
    int getRenderHeight(void) const
    {
        return de::min((int)MAX_TEST_RENDER_HEIGHT, m_context.getRenderTarget().getHeight());
    }

protected:
    virtual glu::ShaderProgram *generateShaders(void) const = 0;

    void initRenderState(void);
    bool drawAndVerifyResult(const Vec4 &expectedColor);
    void setTestResult(bool queryTestPassed, bool imageTestPassed);

    const glu::ShaderProgram *m_program;
    const ShaderType m_shaderType;
    const TestType m_testType;
    const std::string m_uniformName;

    const glw::GLenum m_maxBindingPointEnum;
    const glw::GLenum m_maxVertexUnitsEnum;
    const glw::GLenum m_maxFragmentUnitsEnum;
    const glw::GLenum m_maxCombinedUnitsEnum;

    glw::GLuint m_vao;
    glw::GLuint m_vertexBuffer;
    glw::GLuint m_indexBuffer;
    glw::GLint m_shaderProgramLoc;
    glw::GLint m_shaderProgramPosLoc;
    glw::GLint m_shaderProgramArrayNdxLoc;
    glw::GLint m_numBindings;

    std::vector<glw::GLint> m_bindings;

private:
    void initBindingPoints(int minBindingPoint, int numBindingPoints);
};

LayoutBindingRenderCase::LayoutBindingRenderCase(Context &context, const char *name, const char *desc,
                                                 ShaderType shaderType, TestType testType,
                                                 glw::GLenum maxBindingPointEnum, glw::GLenum maxVertexUnitsEnum,
                                                 glw::GLenum maxFragmentUnitsEnum, glw::GLenum maxCombinedUnitsEnum,
                                                 const std::string &uniformName)
    : TestCase(context, name, desc)
    , m_program(DE_NULL)
    , m_shaderType(shaderType)
    , m_testType(testType)
    , m_uniformName(uniformName)
    , m_maxBindingPointEnum(maxBindingPointEnum)
    , m_maxVertexUnitsEnum(maxVertexUnitsEnum)
    , m_maxFragmentUnitsEnum(maxFragmentUnitsEnum)
    , m_maxCombinedUnitsEnum(maxCombinedUnitsEnum)
    , m_vao(0)
    , m_vertexBuffer(0)
    , m_indexBuffer(0)
    , m_shaderProgramLoc(0)
    , m_shaderProgramPosLoc(0)
    , m_shaderProgramArrayNdxLoc(0)
    , m_numBindings(0)
{
}

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

void LayoutBindingRenderCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    {
        de::Random rnd(deStringHash(getName()) ^ 0xff23a4);
        glw::GLint numBindingPoints = 0; // Number of available binding points
        glw::GLint maxVertexUnits   = 0; // Available uniforms in the vertex shader
        glw::GLint maxFragmentUnits = 0; // Available uniforms in the fragment shader
        glw::GLint maxCombinedUnits = 0; // Available uniforms in all the shader stages combined
        glw::GLint maxUnits         = 0; // Maximum available uniforms for this test

        gl.getIntegerv(m_maxVertexUnitsEnum, &maxVertexUnits);
        gl.getIntegerv(m_maxFragmentUnitsEnum, &maxFragmentUnits);
        gl.getIntegerv(m_maxCombinedUnitsEnum, &maxCombinedUnits);
        gl.getIntegerv(m_maxBindingPointEnum, &numBindingPoints);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Querying available uniform numbers failed");

        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Maximum units for uniform type in the vertex shader: " << maxVertexUnits
                           << tcu::TestLog::EndMessage;
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Maximum units for uniform type in the fragment shader: " << maxFragmentUnits
                           << tcu::TestLog::EndMessage;
        m_testCtx.getLog() << tcu::TestLog::Message << "Maximum combined units for uniform type: " << maxCombinedUnits
                           << tcu::TestLog::EndMessage;
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Maximum binding point for uniform type: " << numBindingPoints - 1
                           << tcu::TestLog::EndMessage;

        // Select maximum number of uniforms used for the test
        switch (m_shaderType)
        {
        case SHADERTYPE_VERTEX:
            maxUnits = maxVertexUnits;
            break;

        case SHADERTYPE_FRAGMENT:
            maxUnits = maxFragmentUnits;
            break;

        case SHADERTYPE_ALL:
            maxUnits = maxCombinedUnits / 2;
            break;

        default:
            DE_ASSERT(false);
        }

        // Select the number of uniforms (= bindings) used for this test
        switch (m_testType)
        {
        case TESTTYPE_BINDING_SINGLE:
        case TESTTYPE_BINDING_MAX:
            m_numBindings = 1;
            break;

        case TESTTYPE_BINDING_MULTIPLE:
            if (maxUnits < 2)
                throw tcu::NotSupportedError("Not enough uniforms available for test");
            m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_MULTIPLE_INSTANCES, maxUnits));
            break;

        case TESTTYPE_BINDING_ARRAY:
        case TESTTYPE_BINDING_MAX_ARRAY:
            if (maxUnits < 2)
                throw tcu::NotSupportedError("Not enough uniforms available for test");
            m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_ARRAY_SIZE, maxUnits));
            break;

        default:
            DE_ASSERT(false);
        }

        // Check that we have enough uniforms in different shaders to perform the tests
        if (((m_shaderType == SHADERTYPE_VERTEX) || (m_shaderType == SHADERTYPE_ALL)) &&
            (maxVertexUnits < m_numBindings))
            throw tcu::NotSupportedError("Vertex shader: not enough uniforms available for test");
        if (((m_shaderType == SHADERTYPE_FRAGMENT) || (m_shaderType == SHADERTYPE_ALL)) &&
            (maxFragmentUnits < m_numBindings))
            throw tcu::NotSupportedError("Fragment shader: not enough uniforms available for test");
        if ((m_shaderType == SHADERTYPE_ALL) && (maxCombinedUnits < m_numBindings * 2))
            throw tcu::NotSupportedError("Not enough uniforms available for test");

        // Check that we have enough binding points to perform the tests
        if (numBindingPoints < m_numBindings)
            throw tcu::NotSupportedError("Not enough binding points available for test");

        // Initialize the binding points i.e. populate the two binding point vectors
        initBindingPoints(0, numBindingPoints);
    }

    // Generate the shader program - note: this must be done after deciding the binding points
    DE_ASSERT(!m_program);
    m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shaders" << tcu::TestLog::EndMessage;
    m_program = generateShaders();
    m_testCtx.getLog() << *m_program;

    if (!m_program->isOk())
        throw tcu::TestError("Shader compile failed");

    // Setup vertex and index buffers
    {
        // Get attribute and uniform locations
        const uint32_t program = m_program->getProgram();

        m_shaderProgramPosLoc      = gl.getAttribLocation(program, "a_position");
        m_shaderProgramArrayNdxLoc = gl.getUniformLocation(program, "u_arrayNdx");
        m_vertexBuffer             = 0;
        m_indexBuffer              = 0;

        // Setup buffers so that we render one quad covering the whole viewport
        const Vec3 vertices[] = {
            Vec3(-1.0f, -1.0f, +1.0f),
            Vec3(+1.0f, -1.0f, +1.0f),
            Vec3(+1.0f, +1.0f, +1.0f),
            Vec3(-1.0f, +1.0f, +1.0f),
        };

        const uint16_t indices[] = {
            0, 1, 2, 0, 2, 3,
        };

        TCU_CHECK((m_shaderProgramPosLoc >= 0) && (m_shaderProgramArrayNdxLoc >= 0));

        // Generate and bind vao
        if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
        {
            gl.genVertexArrays(1, &m_vao);
            gl.bindVertexArray(m_vao);
        }

        // Generate and bind index buffer
        gl.genBuffers(1, &m_indexBuffer);
        gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuffer);
        gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (DE_LENGTH_OF_ARRAY(indices) * (glw::GLsizeiptr)sizeof(indices[0])),
                      &indices[0], GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Index buffer setup failed");

        // Generate and bind vertex buffer
        gl.genBuffers(1, &m_vertexBuffer);
        gl.bindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
        gl.bufferData(GL_ARRAY_BUFFER, (DE_LENGTH_OF_ARRAY(vertices) * (glw::GLsizeiptr)sizeof(vertices[0])),
                      &vertices[0], GL_STATIC_DRAW);
        gl.enableVertexAttribArray(m_shaderProgramPosLoc);
        gl.vertexAttribPointer(m_shaderProgramPosLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Vertex buffer setup failed");
    }
}

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

    if (m_shaderProgramPosLoc)
        m_context.getRenderContext().getFunctions().disableVertexAttribArray(m_shaderProgramPosLoc);

    if (m_vao)
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);

    if (m_vertexBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vertexBuffer);
        m_context.getRenderContext().getFunctions().bindBuffer(GL_ARRAY_BUFFER, 0);
    }

    if (m_indexBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_indexBuffer);
        m_context.getRenderContext().getFunctions().bindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    }
}

void LayoutBindingRenderCase::initBindingPoints(int minBindingPoint, int numBindingPoints)
{
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);

    switch (m_testType)
    {
    case TESTTYPE_BINDING_SINGLE:
    {
        const int bpoint = rnd.getInt(minBindingPoint, numBindingPoints - 1);
        m_bindings.push_back(bpoint);
        break;
    }

    case TESTTYPE_BINDING_MAX:
        m_bindings.push_back(numBindingPoints - 1);
        break;

    case TESTTYPE_BINDING_MULTIPLE:
    {
        // Choose multiple unique binding points from the low and high end of available binding points
        std::vector<uint32_t> lowBindingPoints;
        std::vector<uint32_t> highBindingPoints;

        for (int bpoint = 0; bpoint < numBindingPoints / 2; ++bpoint)
            lowBindingPoints.push_back(bpoint);
        for (int bpoint = numBindingPoints / 2; bpoint < numBindingPoints; ++bpoint)
            highBindingPoints.push_back(bpoint);

        rnd.shuffle(lowBindingPoints.begin(), lowBindingPoints.end());
        rnd.shuffle(highBindingPoints.begin(), highBindingPoints.end());

        for (int ndx = 0; ndx < m_numBindings; ++ndx)
        {
            if (ndx % 2 == 0)
            {
                const int bpoint = lowBindingPoints.back();
                lowBindingPoints.pop_back();
                m_bindings.push_back(bpoint);
            }
            else
            {
                const int bpoint = highBindingPoints.back();
                highBindingPoints.pop_back();
                m_bindings.push_back(bpoint);
            }
        }
        break;
    }

    case TESTTYPE_BINDING_ARRAY:
    {
        const glw::GLint binding = rnd.getInt(minBindingPoint, numBindingPoints - m_numBindings);
        for (int ndx = 0; ndx < m_numBindings; ++ndx)
            m_bindings.push_back(binding + ndx);
        break;
    }

    case TESTTYPE_BINDING_MAX_ARRAY:
    {
        const glw::GLint binding = numBindingPoints - m_numBindings;
        for (int ndx = 0; ndx < m_numBindings; ++ndx)
            m_bindings.push_back(binding + ndx);
        break;
    }

    default:
        DE_ASSERT(false);
    }
}

void LayoutBindingRenderCase::initRenderState(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.useProgram(m_program->getProgram());
    gl.viewport(0, 0, getRenderWidth(), getRenderHeight());
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set render state");
}

bool LayoutBindingRenderCase::drawAndVerifyResult(const Vec4 &expectedColor)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    tcu::Surface reference(getRenderWidth(), getRenderHeight());

    // the point of these test is to check layout_binding. For this purpose, we can use quite
    // large thresholds.
    const tcu::RGBA surfaceThreshold =
        m_context.getRenderContext().getRenderTarget().getPixelFormat().getColorThreshold();
    const tcu::RGBA compareThreshold = tcu::RGBA(
        de::clamp(2 * surfaceThreshold.getRed(), 0, 255), de::clamp(2 * surfaceThreshold.getGreen(), 0, 255),
        de::clamp(2 * surfaceThreshold.getBlue(), 0, 255), de::clamp(2 * surfaceThreshold.getAlpha(), 0, 255));

    gl.clear(GL_COLOR_BUFFER_BIT);

    // Draw
    gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Drawing failed");

    // Verify
    tcu::Surface result(getRenderWidth(), getRenderHeight());
    m_testCtx.getLog() << TestLog::Message << "Reading pixels" << TestLog::EndMessage;
    glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
    GLU_EXPECT_NO_ERROR(gl.getError(), "Read pixels failed");

    tcu::clear(reference.getAccess(), expectedColor);
    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying output image, fragment output color is " << expectedColor
                       << tcu::TestLog::EndMessage;

    return tcu::pixelThresholdCompare(m_testCtx.getLog(), "Render result", "Result verification", reference, result,
                                      compareThreshold, tcu::COMPARE_LOG_RESULT);
}

void LayoutBindingRenderCase::setTestResult(bool queryTestPassed, bool imageTestPassed)
{
    if (queryTestPassed && imageTestPassed)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else if (!queryTestPassed && !imageTestPassed)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more binding point queries and image comparisons failed");
    else if (!queryTestPassed)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more binding point queries failed");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "One or more image comparisons failed");
}

class LayoutBindingNegativeCase : public TestCase
{
public:
    enum ErrorType
    {
        ERRORTYPE_OVER_MAX_UNITS = 0,
        ERRORTYPE_LESS_THAN_ZERO,
        ERRORTYPE_CONTRADICTORY,

        ERRORTYPE_LAST,
    };

    LayoutBindingNegativeCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                              TestType testType, ErrorType errorType, glw::GLenum maxBindingPointEnum,
                              glw::GLenum maxVertexUnitsEnum, glw::GLenum maxFragmentUnitsEnum,
                              glw::GLenum maxTessCtrlUnitsEnum, glw::GLenum maxTessEvalUnitsEnum,
                              glw::GLenum maxCombinedUnitsEnum, const std::string &uniformName);
    virtual ~LayoutBindingNegativeCase(void);

    virtual void init(void);
    virtual void deinit(void);
    virtual IterateResult iterate(void);

protected:
    virtual glu::ShaderProgram *generateShaders(void) const = 0;

    const glu::ShaderProgram *m_program;
    const ShaderType m_shaderType;
    const TestType m_testType;
    const ErrorType m_errorType;
    const glw::GLenum m_maxBindingPointEnum;
    const glw::GLenum m_maxVertexUnitsEnum;
    const glw::GLenum m_maxFragmentUnitsEnum;
    const glw::GLenum m_maxTessCtrlUnitsEnum;
    const glw::GLenum m_maxTessEvalUnitsEnum;
    const glw::GLenum m_maxCombinedUnitsEnum;
    const std::string m_uniformName;
    glw::GLint m_numBindings;
    std::vector<glw::GLint> m_vertexShaderBinding;
    std::vector<glw::GLint> m_fragmentShaderBinding;
    std::vector<glw::GLint> m_tessCtrlShaderBinding;
    std::vector<glw::GLint> m_tessEvalShaderBinding;
    bool m_tessSupport;

private:
    void initBindingPoints(int minBindingPoint, int numBindingPoints);
};

LayoutBindingNegativeCase::LayoutBindingNegativeCase(Context &context, const char *name, const char *desc,
                                                     ShaderType shaderType, TestType testType, ErrorType errorType,
                                                     glw::GLenum maxBindingPointEnum, glw::GLenum maxVertexUnitsEnum,
                                                     glw::GLenum maxTessCtrlUnitsEnum, glw::GLenum maxTessEvalUnitsEnum,
                                                     glw::GLenum maxFragmentUnitsEnum, glw::GLenum maxCombinedUnitsEnum,
                                                     const std::string &uniformName)
    : TestCase(context, name, desc)
    , m_program(DE_NULL)
    , m_shaderType(shaderType)
    , m_testType(testType)
    , m_errorType(errorType)
    , m_maxBindingPointEnum(maxBindingPointEnum)
    , m_maxVertexUnitsEnum(maxVertexUnitsEnum)
    , m_maxFragmentUnitsEnum(maxFragmentUnitsEnum)
    , m_maxTessCtrlUnitsEnum(maxTessCtrlUnitsEnum)
    , m_maxTessEvalUnitsEnum(maxTessEvalUnitsEnum)
    , m_maxCombinedUnitsEnum(maxCombinedUnitsEnum)
    , m_uniformName(uniformName)
    , m_numBindings(0)
    , m_tessSupport(false)
{
}

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

void LayoutBindingNegativeCase::init(void)
{
    // Decide appropriate binding points for the vertex and fragment shaders
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);
    glw::GLint numBindingPoints = 0; // Number of binding points
    glw::GLint maxVertexUnits   = 0; // Available uniforms in the vertex shader
    glw::GLint maxFragmentUnits = 0; // Available uniforms in the fragment shader
    glw::GLint maxCombinedUnits = 0; // Available uniforms in all the shader stages combined
    glw::GLint maxTessCtrlUnits = 0; // Available uniforms in tessellation control shader
    glw::GLint maxTessEvalUnits = 0; // Available uniforms in tessellation evaluation shader
    glw::GLint maxUnits         = 0; // Maximum available uniforms for this test

    m_tessSupport = m_context.getContextInfo().isExtensionSupported("GL_EXT_tessellation_shader") ||
                    contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
                    contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    if (!m_tessSupport && (m_shaderType == SHADERTYPE_TESS_EVALUATION || m_shaderType == SHADERTYPE_TESS_CONTROL))
        TCU_THROW(NotSupportedError, "Tesselation shaders not supported");

    int numShaderStages = m_tessSupport ? 4 : 2;

    gl.getIntegerv(m_maxVertexUnitsEnum, &maxVertexUnits);
    gl.getIntegerv(m_maxFragmentUnitsEnum, &maxFragmentUnits);

    if (m_tessSupport)
    {
        gl.getIntegerv(m_maxTessCtrlUnitsEnum, &maxTessCtrlUnits);
        gl.getIntegerv(m_maxTessEvalUnitsEnum, &maxTessEvalUnits);
    }

    gl.getIntegerv(m_maxCombinedUnitsEnum, &maxCombinedUnits);
    gl.getIntegerv(m_maxBindingPointEnum, &numBindingPoints);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Querying available uniform numbers failed");

    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Maximum units for uniform type in the vertex shader: " << maxVertexUnits
                       << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::Message
                       << "Maximum units for uniform type in the fragment shader: " << maxFragmentUnits
                       << tcu::TestLog::EndMessage;

    if (m_tessSupport)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Maximum units for uniform type in the tessellation control shader: " << maxTessCtrlUnits
                           << tcu::TestLog::EndMessage;
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Maximum units for uniform type in the tessellation evaluation shader: "
                           << maxTessCtrlUnits << tcu::TestLog::EndMessage;
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Maximum combined units for uniform type: " << maxCombinedUnits
                       << tcu::TestLog::EndMessage;
    m_testCtx.getLog() << tcu::TestLog::Message << "Maximum binding point for uniform type: " << numBindingPoints - 1
                       << tcu::TestLog::EndMessage;

    // Select maximum number of uniforms used for the test
    switch (m_shaderType)
    {
    case SHADERTYPE_VERTEX:
        maxUnits = maxVertexUnits;
        break;

    case SHADERTYPE_FRAGMENT:
        maxUnits = maxFragmentUnits;
        break;

    case SHADERTYPE_ALL:
        maxUnits =
            de::min(de::min(de::min(maxVertexUnits, maxFragmentUnits), de::min(maxTessCtrlUnits, maxTessEvalUnits)),
                    maxCombinedUnits / numShaderStages);
        break;

    case SHADERTYPE_TESS_CONTROL:
        maxUnits = maxTessCtrlUnits;
        break;

    case SHADERTYPE_TESS_EVALUATION:
        maxUnits = maxTessEvalUnits;
        break;

    default:
        DE_ASSERT(false);
    }

    // Select the number of uniforms (= bindings) used for this test
    switch (m_testType)
    {
    case TESTTYPE_BINDING_SINGLE:
    case TESTTYPE_BINDING_MAX:
        m_numBindings = 1;
        break;

    case TESTTYPE_BINDING_MULTIPLE:
    case TESTTYPE_BINDING_ARRAY:
    case TESTTYPE_BINDING_MAX_ARRAY:
        if (m_errorType == ERRORTYPE_CONTRADICTORY)
        {
            // leave room for contradictory case
            if (maxUnits < 3)
                TCU_THROW(NotSupportedError, "Not enough uniforms available for test");
            m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_ARRAY_SIZE, maxUnits - 1));
        }
        else
        {
            if (maxUnits < 2)
                TCU_THROW(NotSupportedError, "Not enough uniforms available for test");
            m_numBindings = rnd.getInt(2, deMin32(MAX_UNIFORM_ARRAY_SIZE, maxUnits));
        }
        break;

    default:
        DE_ASSERT(false);
    }

    // Check that we have enough uniforms in different shaders to perform the tests
    if (((m_shaderType == SHADERTYPE_VERTEX) || (m_shaderType == SHADERTYPE_ALL)) && (maxVertexUnits < m_numBindings))
        TCU_THROW(NotSupportedError, "Vertex shader: not enough uniforms available for test");

    if (((m_shaderType == SHADERTYPE_FRAGMENT) || (m_shaderType == SHADERTYPE_ALL)) &&
        (maxFragmentUnits < m_numBindings))
        TCU_THROW(NotSupportedError, "Fragment shader: not enough uniforms available for test");

    if (m_tessSupport && ((m_shaderType == SHADERTYPE_TESS_CONTROL) || (m_shaderType == SHADERTYPE_ALL)) &&
        (maxTessCtrlUnits < m_numBindings))
        TCU_THROW(NotSupportedError, "Tessellation control shader: not enough uniforms available for test");

    if (m_tessSupport && ((m_shaderType == SHADERTYPE_TESS_EVALUATION) || (m_shaderType == SHADERTYPE_ALL)) &&
        (maxTessEvalUnits < m_numBindings))
        TCU_THROW(NotSupportedError, "Tessellation evaluation shader: not enough uniforms available for test");

    if ((m_shaderType == SHADERTYPE_ALL) && (maxCombinedUnits < m_numBindings * numShaderStages))
        TCU_THROW(NotSupportedError, "Not enough uniforms available for test");

    // Check that we have enough binding points to perform the tests
    if (numBindingPoints < m_numBindings)
        TCU_THROW(NotSupportedError, "Not enough binding points available for test");

    if (m_errorType == ERRORTYPE_CONTRADICTORY && numBindingPoints == m_numBindings)
        TCU_THROW(NotSupportedError, "Not enough binding points available for test");

    // Initialize the binding points i.e. populate the two binding point vectors
    initBindingPoints(0, numBindingPoints);

    // Generate the shader program - note: this must be done after deciding the binding points
    DE_ASSERT(!m_program);
    m_testCtx.getLog() << tcu::TestLog::Message << "Creating test shaders" << tcu::TestLog::EndMessage;
    m_program = generateShaders();
    m_testCtx.getLog() << *m_program;
}

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

TestCase::IterateResult LayoutBindingNegativeCase::iterate(void)
{
    bool pass = false;
    std::string failMessage;

    switch (m_errorType)
    {
    case ERRORTYPE_CONTRADICTORY: // Contradictory binding points should cause a link-time error
        if (!(m_program->getProgramInfo()).linkOk)
            pass = true;
        failMessage = "Test failed - expected a link-time error";
        break;

    case ERRORTYPE_LESS_THAN_ZERO: // Out of bounds binding points should cause a compile-time error
    case ERRORTYPE_OVER_MAX_UNITS:
        if (m_tessSupport)
        {
            if (!(m_program->getShaderInfo(glu::SHADERTYPE_VERTEX)).compileOk ||
                !(m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk) ||
                !(m_program->getShaderInfo(glu::SHADERTYPE_TESSELLATION_CONTROL).compileOk) ||
                !(m_program->getShaderInfo(glu::SHADERTYPE_TESSELLATION_EVALUATION)).compileOk)
                pass = true;
        }
        else
        {
            if (!(m_program->getShaderInfo(glu::SHADERTYPE_VERTEX)).compileOk ||
                !(m_program->getShaderInfo(glu::SHADERTYPE_FRAGMENT).compileOk))
                pass = true;
        }

        failMessage = "Test failed - expected a compile-time error";
        break;

    default:
        DE_ASSERT(false);
    }

    if (pass)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, failMessage.c_str());

    return STOP;
}

void LayoutBindingNegativeCase::initBindingPoints(int minBindingPoint, int numBindingPoints)
{
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);

    switch (m_errorType)
    {
    case ERRORTYPE_OVER_MAX_UNITS: // Select a binding point that is 1 over the maximum
    {
        m_vertexShaderBinding.push_back(numBindingPoints + 1 - m_numBindings);
        m_fragmentShaderBinding.push_back(numBindingPoints + 1 - m_numBindings);
        m_tessCtrlShaderBinding.push_back(numBindingPoints + 1 - m_numBindings);
        m_tessEvalShaderBinding.push_back(numBindingPoints + 1 - m_numBindings);
        break;
    }

    case ERRORTYPE_LESS_THAN_ZERO: // Select a random negative binding point
    {
        const glw::GLint binding = -rnd.getInt(1, m_numBindings);
        m_vertexShaderBinding.push_back(binding);
        m_fragmentShaderBinding.push_back(binding);
        m_tessCtrlShaderBinding.push_back(binding);
        m_tessEvalShaderBinding.push_back(binding);
        break;
    }

    case ERRORTYPE_CONTRADICTORY: // Select two valid, but contradictory binding points
    {
        m_vertexShaderBinding.push_back(minBindingPoint);
        m_fragmentShaderBinding.push_back((minBindingPoint + 1) % numBindingPoints);
        m_tessCtrlShaderBinding.push_back((minBindingPoint + 2) % numBindingPoints);
        m_tessEvalShaderBinding.push_back((minBindingPoint + 3) % numBindingPoints);

        DE_ASSERT(m_vertexShaderBinding.back() != m_fragmentShaderBinding.back());
        DE_ASSERT(m_fragmentShaderBinding.back() != m_tessEvalShaderBinding.back());
        DE_ASSERT(m_tessEvalShaderBinding.back() != m_tessCtrlShaderBinding.back());
        DE_ASSERT(m_tessCtrlShaderBinding.back() != m_vertexShaderBinding.back());
        DE_ASSERT(m_vertexShaderBinding.back() != m_tessEvalShaderBinding.back());
        DE_ASSERT(m_tessCtrlShaderBinding.back() != m_fragmentShaderBinding.back());
        break;
    }

    default:
        DE_ASSERT(false);
    }

    // In case we are testing with multiple uniforms populate the rest of the binding points
    for (int ndx = 1; ndx < m_numBindings; ++ndx)
    {
        m_vertexShaderBinding.push_back(m_vertexShaderBinding.front() + ndx);
        m_fragmentShaderBinding.push_back(m_fragmentShaderBinding.front() + ndx);
        m_tessCtrlShaderBinding.push_back(m_tessCtrlShaderBinding.front() + ndx);
        m_tessEvalShaderBinding.push_back(m_tessCtrlShaderBinding.front() + ndx);
    }
}

class SamplerBindingRenderCase : public LayoutBindingRenderCase
{
public:
    SamplerBindingRenderCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                             TestType testType, glw::GLenum samplerType, glw::GLenum textureType);
    ~SamplerBindingRenderCase(void);

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

private:
    glu::ShaderProgram *generateShaders(void) const;
    glu::DataType getSamplerTexCoordType(void) const;
    void initializeTexture(glw::GLint bindingPoint, glw::GLint textureName, const Vec4 &color) const;

    const glw::GLenum m_samplerType;
    const glw::GLenum m_textureType;

    std::vector<glw::GLuint> m_textures;
    std::vector<Vec4> m_textureColors;
};

SamplerBindingRenderCase::SamplerBindingRenderCase(Context &context, const char *name, const char *desc,
                                                   ShaderType shaderType, TestType testType, glw::GLenum samplerType,
                                                   glw::GLenum textureType)
    : LayoutBindingRenderCase(context, name, desc, shaderType, testType, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
                              GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, GL_MAX_TEXTURE_IMAGE_UNITS,
                              GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "u_sampler")
    , m_samplerType(samplerType)
    , m_textureType(textureType)
{
}

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

void SamplerBindingRenderCase::init(void)
{
    LayoutBindingRenderCase::init();
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);

    // Initialize texture resources
    m_textures = std::vector<glw::GLuint>(m_numBindings, 0);

    // Texture colors
    for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
        m_textureColors.push_back(getRandomColor(rnd));

    // Textures
    gl.genTextures((glw::GLsizei)m_textures.size(), &m_textures[0]);

    for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
        initializeTexture(m_bindings[texNdx], m_textures[texNdx], m_textureColors[texNdx]);

    gl.activeTexture(GL_TEXTURE0);
}

void SamplerBindingRenderCase::deinit(void)
{
    LayoutBindingRenderCase::deinit();

    // Clean up texture data
    for (int i = 0; i < (int)m_textures.size(); ++i)
    {
        if (m_textures[i])
        {
            m_context.getRenderContext().getFunctions().deleteTextures(1, &m_textures[i]);
            m_context.getRenderContext().getFunctions().bindTexture(m_textureType, 0);
        }
    }
}

TestCase::IterateResult SamplerBindingRenderCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int iterations     = m_numBindings;
    const bool arrayInstance = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    bool imageTestPassed     = true;
    bool queryTestPassed     = true;

    // Set the viewport and enable the shader program
    initRenderState();

    for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
    {
        // Set the uniform value indicating the current array index
        gl.uniform1i(m_shaderProgramArrayNdxLoc, iterNdx);

        // Query binding point
        const std::string name =
            arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx);
        const glw::GLint binding = m_bindings[iterNdx];
        glw::GLint val           = -1;

        gl.getUniformiv(m_program->getProgram(), gl.getUniformLocation(m_program->getProgram(), name.c_str()), &val);
        m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val
                           << " == " << binding << tcu::TestLog::EndMessage;
        GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");

        // Draw and verify
        if (val != binding)
            queryTestPassed = false;
        if (!drawAndVerifyResult(m_textureColors[iterNdx]))
            imageTestPassed = false;
    }

    setTestResult(queryTestPassed, imageTestPassed);
    return STOP;
}

glu::ShaderProgram *SamplerBindingRenderCase::generateShaders(void) const
{
    std::ostringstream shaderUniformDecl;
    std::ostringstream shaderBody;

    const std::string texCoordType = glu::getDataTypeName(getSamplerTexCoordType());
    const std::string samplerType  = glu::getDataTypeName(glu::getDataTypeFromGLType(m_samplerType));
    const bool arrayInstance =
        (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY) ? true : false;
    const int numDeclarations = arrayInstance ? 1 : m_numBindings;

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        shaderUniformDecl << "layout(binding = " << m_bindings[declNdx] << ") uniform highp " << samplerType << " "
                          << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                              getUniformName(m_uniformName, declNdx))
                          << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
    {
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = texture("
                   << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) :
                                       getUniformName(m_uniformName, bindNdx))
                   << ", " << texCoordType << "(0.5));\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    return new glu::ShaderProgram(m_context.getRenderContext(),
                                  glu::ProgramSources()
                                      << glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(),
                                                                                shaderBody.str()))
                                      << glu::FragmentSource(generateFragmentShader(
                                             m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
}

void SamplerBindingRenderCase::initializeTexture(glw::GLint bindingPoint, glw::GLint textureName,
                                                 const Vec4 &color) const
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.activeTexture(GL_TEXTURE0 + bindingPoint);
    gl.bindTexture(m_textureType, textureName);
    gl.texParameteri(m_textureType, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    switch (m_textureType)
    {
    case GL_TEXTURE_2D:
    {
        tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8),
                                TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
        tcu::clear(level.getAccess(), color);
        glu::texImage2D(m_context.getRenderContext(), m_textureType, 0, GL_RGBA8, level.getAccess());
        break;
    }

    case GL_TEXTURE_3D:
    {
        tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8),
                                TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
        tcu::clear(level.getAccess(), color);
        glu::texImage3D(m_context.getRenderContext(), m_textureType, 0, GL_RGBA8, level.getAccess());
        break;
    }

    default:
        DE_ASSERT(false);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "Texture initialization failed");
}

glu::DataType SamplerBindingRenderCase::getSamplerTexCoordType(void) const
{
    switch (m_samplerType)
    {
    case GL_SAMPLER_2D:
        return glu::TYPE_FLOAT_VEC2;

    case GL_SAMPLER_3D:
        return glu::TYPE_FLOAT_VEC3;

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

class SamplerBindingNegativeCase : public LayoutBindingNegativeCase
{
public:
    SamplerBindingNegativeCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                               TestType testType, ErrorType errorType, glw::GLenum samplerType);
    ~SamplerBindingNegativeCase(void);

private:
    glu::ShaderProgram *generateShaders(void) const;
    glu::DataType getSamplerTexCoordType(void) const;

    const glw::GLenum m_samplerType;
};

SamplerBindingNegativeCase::SamplerBindingNegativeCase(Context &context, const char *name, const char *desc,
                                                       ShaderType shaderType, TestType testType, ErrorType errorType,
                                                       glw::GLenum samplerType)
    : LayoutBindingNegativeCase(context, name, desc, shaderType, testType, errorType,
                                GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS,
                                GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS, GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS,
                                GL_MAX_TEXTURE_IMAGE_UNITS, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, "u_sampler")
    , m_samplerType(samplerType)
{
}

SamplerBindingNegativeCase::~SamplerBindingNegativeCase(void)
{
    LayoutBindingNegativeCase::deinit();
}

glu::ShaderProgram *SamplerBindingNegativeCase::generateShaders(void) const
{
    std::ostringstream vertexUniformDecl;
    std::ostringstream fragmentUniformDecl;
    std::ostringstream tessCtrlUniformDecl;
    std::ostringstream tessEvalUniformDecl;
    std::ostringstream shaderBody;

    const std::string texCoordType = glu::getDataTypeName(getSamplerTexCoordType());
    const std::string samplerType  = glu::getDataTypeName(glu::getDataTypeFromGLType(m_samplerType));
    const bool arrayInstance       = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    const int numDeclarations      = arrayInstance ? 1 : m_numBindings;

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        vertexUniformDecl << "layout(binding = " << m_vertexShaderBinding[declNdx] << ") uniform highp " << samplerType
                          << " "
                          << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                              getUniformName(m_uniformName, declNdx))
                          << ";\n";
        fragmentUniformDecl << "layout(binding = " << m_fragmentShaderBinding[declNdx] << ") uniform highp "
                            << samplerType << " "
                            << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                                getUniformName(m_uniformName, declNdx))
                            << ";\n";
        tessCtrlUniformDecl << "layout(binding = " << m_tessCtrlShaderBinding[declNdx] << ") uniform highp "
                            << samplerType << " "
                            << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                                getUniformName(m_uniformName, declNdx))
                            << ";\n";
        tessEvalUniformDecl << "layout(binding = " << m_tessEvalShaderBinding[declNdx] << ") uniform highp "
                            << samplerType << " "
                            << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                                getUniformName(m_uniformName, declNdx))
                            << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
    {
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = texture("
                   << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) :
                                       getUniformName(m_uniformName, bindNdx))
                   << ", " << texCoordType << "(0.5));\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    glu::ProgramSources sources = glu::ProgramSources()
                                  << glu::VertexSource(
                                         generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
                                  << glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(),
                                                                                shaderBody.str()));

    if (m_tessSupport)
        sources << glu::TessellationControlSource(
                       generateTessControlShader(m_shaderType, tessCtrlUniformDecl.str(), shaderBody.str()))
                << glu::TessellationEvaluationSource(
                       generateTessEvaluationShader(m_shaderType, tessEvalUniformDecl.str(), shaderBody.str()));

    return new glu::ShaderProgram(m_context.getRenderContext(), sources);
}

glu::DataType SamplerBindingNegativeCase::getSamplerTexCoordType(void) const
{
    switch (m_samplerType)
    {
    case GL_SAMPLER_2D:
        return glu::TYPE_FLOAT_VEC2;

    case GL_SAMPLER_3D:
        return glu::TYPE_FLOAT_VEC3;

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

class ImageBindingRenderCase : public LayoutBindingRenderCase
{
public:
    ImageBindingRenderCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                           TestType testType, glw::GLenum imageType, glw::GLenum textureType);
    ~ImageBindingRenderCase(void);

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

private:
    glu::ShaderProgram *generateShaders(void) const;
    void initializeImage(glw::GLint imageBindingPoint, glw::GLint textureBindingPoint, glw::GLint textureName,
                         const Vec4 &color) const;
    glu::DataType getImageTexCoordType(void) const;

    const glw::GLenum m_imageType;
    const glw::GLenum m_textureType;

    std::vector<glw::GLuint> m_textures;
    std::vector<Vec4> m_textureColors;
};

ImageBindingRenderCase::ImageBindingRenderCase(Context &context, const char *name, const char *desc,
                                               ShaderType shaderType, TestType testType, glw::GLenum imageType,
                                               glw::GLenum textureType)
    : LayoutBindingRenderCase(context, name, desc, shaderType, testType, GL_MAX_IMAGE_UNITS,
                              GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_FRAGMENT_IMAGE_UNIFORMS,
                              GL_MAX_COMBINED_IMAGE_UNIFORMS, "u_image")
    , m_imageType(imageType)
    , m_textureType(textureType)
{
}

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

void ImageBindingRenderCase::init(void)
{
    LayoutBindingRenderCase::init();

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);

    // Initialize image / texture resources
    m_textures = std::vector<glw::GLuint>(m_numBindings, 0);

    // Texture colors
    for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
        m_textureColors.push_back(getRandomColor(rnd));

    // Image textures
    gl.genTextures(m_numBindings, &m_textures[0]);

    for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
        initializeImage(m_bindings[texNdx], texNdx, m_textures[texNdx], m_textureColors[texNdx]);
}

void ImageBindingRenderCase::deinit(void)
{
    LayoutBindingRenderCase::deinit();

    // Clean up texture data
    for (int texNdx = 0; texNdx < (int)m_textures.size(); ++texNdx)
    {
        if (m_textures[texNdx])
        {
            m_context.getRenderContext().getFunctions().deleteTextures(1, &m_textures[texNdx]);
            m_context.getRenderContext().getFunctions().bindTexture(m_textureType, 0);
        }
    }
}

TestCase::IterateResult ImageBindingRenderCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int iterations     = m_numBindings;
    const bool arrayInstance = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    bool queryTestPassed     = true;
    bool imageTestPassed     = true;

    // Set the viewport and enable the shader program
    initRenderState();

    for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
    {
        // Set the uniform value indicating the current array index
        gl.uniform1i(m_shaderProgramArrayNdxLoc, iterNdx);

        const std::string name =
            (arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx));
        const glw::GLint binding = m_bindings[iterNdx];
        glw::GLint val           = -1;

        gl.getUniformiv(m_program->getProgram(), gl.getUniformLocation(m_program->getProgram(), name.c_str()), &val);
        m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val
                           << " == " << binding << tcu::TestLog::EndMessage;
        GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");

        // Draw and verify
        if (val != binding)
            queryTestPassed = false;
        if (!drawAndVerifyResult(m_textureColors[iterNdx]))
            imageTestPassed = false;
    }

    setTestResult(queryTestPassed, imageTestPassed);
    return STOP;
}

void ImageBindingRenderCase::initializeImage(glw::GLint imageBindingPoint, glw::GLint textureBindingPoint,
                                             glw::GLint textureName, const Vec4 &color) const
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.activeTexture(GL_TEXTURE0 + textureBindingPoint);
    gl.bindTexture(m_textureType, textureName);
    gl.texParameteri(m_textureType, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

    switch (m_textureType)
    {
    case GL_TEXTURE_2D:
    {
        tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8),
                                TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
        tcu::clear(level.getAccess(), color);
        gl.texStorage2D(m_textureType, 1, GL_RGBA8, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
        gl.texSubImage2D(m_textureType, 0, 0, 0, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, GL_RGBA, GL_UNSIGNED_BYTE,
                         level.getAccess().getDataPtr());
        break;
    }

    case GL_TEXTURE_3D:
    {
        tcu::TextureLevel level(tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8),
                                TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
        tcu::clear(level.getAccess(), color);
        gl.texStorage3D(m_textureType, 1, GL_RGBA8, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE);
        gl.texSubImage3D(m_textureType, 0, 0, 0, 0, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, TEST_TEXTURE_SIZE, GL_RGBA,
                         GL_UNSIGNED_BYTE, level.getAccess().getDataPtr());
        break;
    }

    default:
        DE_ASSERT(false);
    }

    gl.bindTexture(m_textureType, 0);
    gl.bindImageTexture(imageBindingPoint, textureName, 0, GL_TRUE, 0, GL_READ_ONLY, GL_RGBA8);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Image initialization failed");
}

glu::ShaderProgram *ImageBindingRenderCase::generateShaders(void) const
{
    std::ostringstream shaderUniformDecl;
    std::ostringstream shaderBody;

    const std::string texCoordType = glu::getDataTypeName(getImageTexCoordType());
    const std::string imageType    = glu::getDataTypeName(glu::getDataTypeFromGLType(m_imageType));
    const bool arrayInstance =
        (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY) ? true : false;
    const int numDeclarations = (arrayInstance ? 1 : m_numBindings);

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        shaderUniformDecl << "layout(rgba8, binding = " << m_bindings[declNdx] << ") uniform readonly highp "
                          << imageType << " "
                          << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                              getUniformName(m_uniformName, declNdx))
                          << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
    {
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = imageLoad("
                   << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) :
                                       getUniformName(m_uniformName, bindNdx))
                   << ", " << texCoordType << "(0));\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    return new glu::ShaderProgram(m_context.getRenderContext(),
                                  glu::ProgramSources()
                                      << glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(),
                                                                                shaderBody.str()))
                                      << glu::FragmentSource(generateFragmentShader(
                                             m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
}

glu::DataType ImageBindingRenderCase::getImageTexCoordType(void) const
{
    switch (m_imageType)
    {
    case GL_IMAGE_2D:
        return glu::TYPE_INT_VEC2;

    case GL_IMAGE_3D:
        return glu::TYPE_INT_VEC3;

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

class ImageBindingNegativeCase : public LayoutBindingNegativeCase
{
public:
    ImageBindingNegativeCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                             TestType testType, ErrorType errorType, glw::GLenum imageType);
    ~ImageBindingNegativeCase(void);

private:
    glu::ShaderProgram *generateShaders(void) const;
    glu::DataType getImageTexCoordType(void) const;

    const glw::GLenum m_imageType;
};

ImageBindingNegativeCase::ImageBindingNegativeCase(Context &context, const char *name, const char *desc,
                                                   ShaderType shaderType, TestType testType, ErrorType errorType,
                                                   glw::GLenum imageType)
    : LayoutBindingNegativeCase(context, name, desc, shaderType, testType, errorType, GL_MAX_IMAGE_UNITS,
                                GL_MAX_VERTEX_IMAGE_UNIFORMS, GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS,
                                GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS, GL_MAX_FRAGMENT_IMAGE_UNIFORMS,
                                GL_MAX_COMBINED_IMAGE_UNIFORMS, "u_image")
    , m_imageType(imageType)
{
}

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

glu::ShaderProgram *ImageBindingNegativeCase::generateShaders(void) const
{
    std::ostringstream vertexUniformDecl;
    std::ostringstream fragmentUniformDecl;
    std::ostringstream tessCtrlUniformDecl;
    std::ostringstream tessEvalUniformDecl;
    std::ostringstream shaderBody;

    const std::string texCoordType = glu::getDataTypeName(getImageTexCoordType());
    const std::string imageType    = glu::getDataTypeName(glu::getDataTypeFromGLType(m_imageType));
    const bool arrayInstance       = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    const int numDeclarations      = (arrayInstance ? 1 : m_numBindings);

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        vertexUniformDecl << "layout(rgba8, binding = " << m_vertexShaderBinding[declNdx] << ") uniform readonly highp "
                          << imageType << " "
                          << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                              getUniformName(m_uniformName, declNdx))
                          << ";\n";
        fragmentUniformDecl << "layout(rgba8, binding = " << m_fragmentShaderBinding[declNdx]
                            << ") uniform readonly highp " << imageType << " "
                            << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                                getUniformName(m_uniformName, declNdx))
                            << ";\n";
        tessCtrlUniformDecl << "layout(rgba8, binding = " << m_tessCtrlShaderBinding[declNdx]
                            << ") uniform readonly highp " << imageType << " "
                            << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                                getUniformName(m_uniformName, declNdx))
                            << ";\n";
        tessEvalUniformDecl << "layout(rgba8, binding = " << m_tessEvalShaderBinding[declNdx]
                            << ") uniform readonly highp " << imageType << " "
                            << (arrayInstance ? getUniformName(m_uniformName, declNdx, m_numBindings) :
                                                getUniformName(m_uniformName, declNdx))
                            << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings; ++bindNdx)
    {
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = imageLoad("
                   << (arrayInstance ? getUniformName(m_uniformName, 0, bindNdx) :
                                       getUniformName(m_uniformName, bindNdx))
                   << ", " << texCoordType << "(0));\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    glu::ProgramSources sources = glu::ProgramSources()
                                  << glu::VertexSource(
                                         generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
                                  << glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(),
                                                                                shaderBody.str()));

    if (m_tessSupport)
        sources << glu::TessellationControlSource(
                       generateTessControlShader(m_shaderType, tessCtrlUniformDecl.str(), shaderBody.str()))
                << glu::TessellationEvaluationSource(
                       generateTessEvaluationShader(m_shaderType, tessEvalUniformDecl.str(), shaderBody.str()));

    return new glu::ShaderProgram(m_context.getRenderContext(), sources);
}

glu::DataType ImageBindingNegativeCase::getImageTexCoordType(void) const
{
    switch (m_imageType)
    {
    case GL_IMAGE_2D:
        return glu::TYPE_INT_VEC2;

    case GL_IMAGE_3D:
        return glu::TYPE_INT_VEC3;

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

class UBOBindingRenderCase : public LayoutBindingRenderCase
{
public:
    UBOBindingRenderCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                         TestType testType);
    ~UBOBindingRenderCase(void);

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

private:
    glu::ShaderProgram *generateShaders(void) const;

    std::vector<uint32_t> m_buffers;
    std::vector<Vec4> m_expectedColors;
};

UBOBindingRenderCase::UBOBindingRenderCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                                           TestType testType)
    : LayoutBindingRenderCase(context, name, desc, shaderType, testType, GL_MAX_UNIFORM_BUFFER_BINDINGS,
                              GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_FRAGMENT_UNIFORM_BLOCKS,
                              GL_MAX_COMBINED_UNIFORM_BLOCKS, "ColorBlock")
{
}

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

void UBOBindingRenderCase::init(void)
{
    LayoutBindingRenderCase::init();

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);

    // Initialize UBOs and related data
    m_buffers = std::vector<glw::GLuint>(m_numBindings, 0);
    gl.genBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);

    for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
    {
        m_expectedColors.push_back(getRandomColor(rnd));
        m_expectedColors.push_back(getRandomColor(rnd));
    }

    for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
    {
        gl.bindBuffer(GL_UNIFORM_BUFFER, m_buffers[bufNdx]);
        gl.bufferData(GL_UNIFORM_BUFFER, 2 * sizeof(Vec4), &(m_expectedColors[2 * bufNdx]), GL_STATIC_DRAW);
        gl.bindBufferBase(GL_UNIFORM_BUFFER, m_bindings[bufNdx], m_buffers[bufNdx]);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "UBO setup failed");
}

void UBOBindingRenderCase::deinit(void)
{
    LayoutBindingRenderCase::deinit();

    // Clean up UBO data
    for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
    {
        if (m_buffers[bufNdx])
        {
            m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffers[bufNdx]);
            m_context.getRenderContext().getFunctions().bindBuffer(GL_UNIFORM_BUFFER, 0);
        }
    }
}

TestCase::IterateResult UBOBindingRenderCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int iterations     = m_numBindings;
    const glw::GLenum prop   = GL_BUFFER_BINDING;
    const bool arrayInstance = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    bool queryTestPassed     = true;
    bool imageTestPassed     = true;

    // Set the viewport and enable the shader program
    initRenderState();

    for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
    {
        // Query binding point
        const std::string name =
            (arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx));
        const glw::GLint binding = m_bindings[iterNdx];
        glw::GLint val           = -1;

        gl.getProgramResourceiv(m_program->getProgram(), GL_UNIFORM_BLOCK,
                                gl.getProgramResourceIndex(m_program->getProgram(), GL_UNIFORM_BLOCK, name.c_str()), 1,
                                &prop, 1, DE_NULL, &val);
        m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val
                           << " == " << binding << tcu::TestLog::EndMessage;
        GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");

        if (val != binding)
            queryTestPassed = false;

        // Draw twice to render both colors within the UBO
        for (int drawCycle = 0; drawCycle < 2; ++drawCycle)
        {
            // Set the uniform indicating the array index to be used and set the expected color
            const int arrayNdx = iterNdx * 2 + drawCycle;
            gl.uniform1i(m_shaderProgramArrayNdxLoc, arrayNdx);

            if (!drawAndVerifyResult(m_expectedColors[arrayNdx]))
                imageTestPassed = false;
        }
    }

    setTestResult(queryTestPassed, imageTestPassed);
    return STOP;
}

glu::ShaderProgram *UBOBindingRenderCase::generateShaders(void) const
{
    std::ostringstream shaderUniformDecl;
    std::ostringstream shaderBody;
    const bool arrayInstance  = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    const int numDeclarations = (arrayInstance ? 1 : m_numBindings);

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        shaderUniformDecl << "layout(std140, binding = " << m_bindings[declNdx] << ") uniform "
                          << getUniformName(m_uniformName, declNdx) << "\n"
                          << "{\n"
                          << "    highp vec4 color1;\n"
                          << "    highp vec4 color2;\n"
                          << "} "
                          << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                              getUniformName("colors", declNdx))
                          << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings * 2;
         ++bindNdx) // Multiply by two to cover cases for both colors for each UBO
    {
        const std::string uname =
            (arrayInstance ? getUniformName("colors", 0, bindNdx / 2) : getUniformName("colors", bindNdx / 2));
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = " << uname << (bindNdx % 2 == 0 ? ".color1" : ".color2") << ";\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    return new glu::ShaderProgram(m_context.getRenderContext(),
                                  glu::ProgramSources()
                                      << glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(),
                                                                                shaderBody.str()))
                                      << glu::FragmentSource(generateFragmentShader(
                                             m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
}

class UBOBindingNegativeCase : public LayoutBindingNegativeCase
{
public:
    UBOBindingNegativeCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                           TestType testType, ErrorType errorType);
    ~UBOBindingNegativeCase(void);

private:
    glu::ShaderProgram *generateShaders(void) const;
};

UBOBindingNegativeCase::UBOBindingNegativeCase(Context &context, const char *name, const char *desc,
                                               ShaderType shaderType, TestType testType, ErrorType errorType)
    : LayoutBindingNegativeCase(context, name, desc, shaderType, testType, errorType, GL_MAX_UNIFORM_BUFFER_BINDINGS,
                                GL_MAX_VERTEX_UNIFORM_BLOCKS, GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS,
                                GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS, GL_MAX_FRAGMENT_UNIFORM_BLOCKS,
                                GL_MAX_COMBINED_UNIFORM_BLOCKS, "ColorBlock")
{
}

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

glu::ShaderProgram *UBOBindingNegativeCase::generateShaders(void) const
{
    std::ostringstream vertexUniformDecl;
    std::ostringstream fragmentUniformDecl;
    std::ostringstream tessCtrlUniformDecl;
    std::ostringstream tessEvalUniformDecl;
    std::ostringstream shaderBody;
    const bool arrayInstance  = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    const int numDeclarations = (arrayInstance ? 1 : m_numBindings);

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        vertexUniformDecl << "layout(std140, binding = " << m_vertexShaderBinding[declNdx] << ") uniform "
                          << getUniformName(m_uniformName, declNdx) << "\n"
                          << "{\n"
                          << "    highp vec4 color1;\n"
                          << "    highp vec4 color2;\n"
                          << "} "
                          << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                              getUniformName("colors", declNdx))
                          << ";\n";

        fragmentUniformDecl << "layout(std140, binding = " << m_fragmentShaderBinding[declNdx] << ") uniform "
                            << getUniformName(m_uniformName, declNdx) << "\n"
                            << "{\n"
                            << "    highp vec4 color1;\n"
                            << "    highp vec4 color2;\n"
                            << "} "
                            << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                                getUniformName("colors", declNdx))
                            << ";\n";

        tessCtrlUniformDecl << "layout(std140, binding = " << m_tessCtrlShaderBinding[declNdx] << ") uniform "
                            << getUniformName(m_uniformName, declNdx) << "\n"
                            << "{\n"
                            << "    highp vec4 color1;\n"
                            << "    highp vec4 color2;\n"
                            << "} "
                            << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                                getUniformName("colors", declNdx))
                            << ";\n";

        tessEvalUniformDecl << "layout(std140, binding = " << m_tessCtrlShaderBinding[declNdx] << ") uniform "
                            << getUniformName(m_uniformName, declNdx) << "\n"
                            << "{\n"
                            << "    highp vec4 color1;\n"
                            << "    highp vec4 color2;\n"
                            << "} "
                            << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                                getUniformName("colors", declNdx))
                            << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings * 2;
         ++bindNdx) // Multiply by two to cover cases for both colors for each UBO
    {
        const std::string uname =
            (arrayInstance ? getUniformName("colors", 0, bindNdx / 2) : getUniformName("colors", bindNdx / 2));
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = " << uname << (bindNdx % 2 == 0 ? ".color1" : ".color2") << ";\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    glu::ProgramSources sources = glu::ProgramSources()
                                  << glu::VertexSource(
                                         generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
                                  << glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(),
                                                                                shaderBody.str()));

    if (m_tessSupport)
        sources << glu::TessellationControlSource(
                       generateTessControlShader(m_shaderType, tessCtrlUniformDecl.str(), shaderBody.str()))
                << glu::TessellationEvaluationSource(
                       generateTessEvaluationShader(m_shaderType, tessEvalUniformDecl.str(), shaderBody.str()));

    return new glu::ShaderProgram(m_context.getRenderContext(), sources);
}

class SSBOBindingRenderCase : public LayoutBindingRenderCase
{
public:
    SSBOBindingRenderCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                          TestType testType);
    ~SSBOBindingRenderCase(void);

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

private:
    glu::ShaderProgram *generateShaders(void) const;

    std::vector<glw::GLuint> m_buffers;
    std::vector<Vec4> m_expectedColors;
};

SSBOBindingRenderCase::SSBOBindingRenderCase(Context &context, const char *name, const char *desc,
                                             ShaderType shaderType, TestType testType)
    : LayoutBindingRenderCase(context, name, desc, shaderType, testType, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS,
                              GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS,
                              GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, "ColorBuffer")
{
}

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

void SSBOBindingRenderCase::init(void)
{
    LayoutBindingRenderCase::init();

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    de::Random rnd(deStringHash(getName()) ^ 0xff23a4);

    // Initialize SSBOs and related data
    m_buffers = std::vector<glw::GLuint>(m_numBindings, 0);
    gl.genBuffers((glw::GLsizei)m_buffers.size(), &m_buffers[0]);

    for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
    {
        m_expectedColors.push_back(getRandomColor(rnd));
        m_expectedColors.push_back(getRandomColor(rnd));
    }

    for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
    {
        gl.bindBuffer(GL_SHADER_STORAGE_BUFFER, m_buffers[bufNdx]);
        gl.bufferData(GL_SHADER_STORAGE_BUFFER, 2 * sizeof(Vec4), &(m_expectedColors[2 * bufNdx]), GL_STATIC_DRAW);
        gl.bindBufferBase(GL_SHADER_STORAGE_BUFFER, m_bindings[bufNdx], m_buffers[bufNdx]);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "SSBO setup failed");
}

void SSBOBindingRenderCase::deinit(void)
{
    LayoutBindingRenderCase::deinit();

    // Clean up SSBO data
    for (int bufNdx = 0; bufNdx < (int)m_buffers.size(); ++bufNdx)
    {
        if (m_buffers[bufNdx])
        {
            m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffers[bufNdx]);
            m_context.getRenderContext().getFunctions().bindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
            m_buffers[bufNdx] = 0;
        }
    }
}

TestCase::IterateResult SSBOBindingRenderCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int iterations     = m_numBindings;
    const glw::GLenum prop   = GL_BUFFER_BINDING;
    const bool arrayInstance = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    bool queryTestPassed     = true;
    bool imageTestPassed     = true;

    initRenderState();

    for (int iterNdx = 0; iterNdx < iterations; ++iterNdx)
    {
        // Query binding point
        const std::string name =
            (arrayInstance ? getUniformName(m_uniformName, 0, iterNdx) : getUniformName(m_uniformName, iterNdx));
        const glw::GLint binding = m_bindings[iterNdx];
        glw::GLint val           = -1;

        gl.getProgramResourceiv(
            m_program->getProgram(), GL_SHADER_STORAGE_BLOCK,
            gl.getProgramResourceIndex(m_program->getProgram(), GL_SHADER_STORAGE_BLOCK, name.c_str()), 1, &prop, 1,
            DE_NULL, &val);
        m_testCtx.getLog() << tcu::TestLog::Message << "Querying binding point for " << name << ": " << val
                           << " == " << binding << tcu::TestLog::EndMessage;
        GLU_EXPECT_NO_ERROR(gl.getError(), "Binding point query failed");

        if (val != binding)
            queryTestPassed = false;

        // Draw twice to render both colors within the SSBO
        for (int drawCycle = 0; drawCycle < 2; ++drawCycle)
        {
            // Set the uniform indicating the array index to be used and set the expected color
            const int arrayNdx = iterNdx * 2 + drawCycle;
            gl.uniform1i(m_shaderProgramArrayNdxLoc, arrayNdx);

            if (!drawAndVerifyResult(m_expectedColors[arrayNdx]))
                imageTestPassed = false;
        }
    }

    setTestResult(queryTestPassed, imageTestPassed);
    return STOP;
}

glu::ShaderProgram *SSBOBindingRenderCase::generateShaders(void) const
{
    std::ostringstream shaderUniformDecl;
    std::ostringstream shaderBody;
    const bool arrayInstance  = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    const int numDeclarations = (arrayInstance ? 1 : m_numBindings);

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        shaderUniformDecl << "layout(std140, binding = " << m_bindings[declNdx] << ") buffer "
                          << getUniformName(m_uniformName, declNdx) << "\n"
                          << "{\n"
                          << "    highp vec4 color1;\n"
                          << "    highp vec4 color2;\n"
                          << "} "
                          << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                              getUniformName("colors", declNdx))
                          << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings * 2;
         ++bindNdx) // Multiply by two to cover cases for both colors for each UBO
    {
        const std::string uname =
            (arrayInstance ? getUniformName("colors", 0, bindNdx / 2) : getUniformName("colors", bindNdx / 2));
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = " << uname << (bindNdx % 2 == 0 ? ".color1" : ".color2") << ";\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    return new glu::ShaderProgram(m_context.getRenderContext(),
                                  glu::ProgramSources()
                                      << glu::VertexSource(generateVertexShader(m_shaderType, shaderUniformDecl.str(),
                                                                                shaderBody.str()))
                                      << glu::FragmentSource(generateFragmentShader(
                                             m_shaderType, shaderUniformDecl.str(), shaderBody.str())));
}

class SSBOBindingNegativeCase : public LayoutBindingNegativeCase
{
public:
    SSBOBindingNegativeCase(Context &context, const char *name, const char *desc, ShaderType shaderType,
                            TestType testType, ErrorType errorType);
    ~SSBOBindingNegativeCase(void);

private:
    glu::ShaderProgram *generateShaders(void) const;
};

SSBOBindingNegativeCase::SSBOBindingNegativeCase(Context &context, const char *name, const char *desc,
                                                 ShaderType shaderType, TestType testType, ErrorType errorType)
    : LayoutBindingNegativeCase(context, name, desc, shaderType, testType, errorType,
                                GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS,
                                GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS, GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS,
                                GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS,
                                "ColorBuffer")
{
}

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

glu::ShaderProgram *SSBOBindingNegativeCase::generateShaders(void) const
{
    std::ostringstream vertexUniformDecl;
    std::ostringstream fragmentUniformDecl;
    std::ostringstream tessCtrlUniformDecl;
    std::ostringstream tessEvalUniformDecl;
    std::ostringstream shaderBody;
    const bool arrayInstance  = (m_testType == TESTTYPE_BINDING_ARRAY || m_testType == TESTTYPE_BINDING_MAX_ARRAY);
    const int numDeclarations = (arrayInstance ? 1 : m_numBindings);

    // Generate the uniform declarations for the vertex and fragment shaders
    for (int declNdx = 0; declNdx < numDeclarations; ++declNdx)
    {
        vertexUniformDecl << "layout(std140, binding = " << m_vertexShaderBinding[declNdx] << ") buffer "
                          << getUniformName(m_uniformName, declNdx) << "\n"
                          << "{\n"
                          << "    highp vec4 color1;\n"
                          << "    highp vec4 color2;\n"
                          << "} "
                          << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                              getUniformName("colors", declNdx))
                          << ";\n";

        fragmentUniformDecl << "layout(std140, binding = " << m_fragmentShaderBinding[declNdx] << ") buffer "
                            << getUniformName(m_uniformName, declNdx) << "\n"
                            << "{\n"
                            << "    highp vec4 color1;\n"
                            << "    highp vec4 color2;\n"
                            << "} "
                            << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                                getUniformName("colors", declNdx))
                            << ";\n";

        tessCtrlUniformDecl << "layout(std140, binding = " << m_tessCtrlShaderBinding[declNdx] << ") buffer "
                            << getUniformName(m_uniformName, declNdx) << "\n"
                            << "{\n"
                            << "    highp vec4 color1;\n"
                            << "    highp vec4 color2;\n"
                            << "} "
                            << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                                getUniformName("colors", declNdx))
                            << ";\n";

        tessEvalUniformDecl << "layout(std140, binding = " << m_tessEvalShaderBinding[declNdx] << ") buffer "
                            << getUniformName(m_uniformName, declNdx) << "\n"
                            << "{\n"
                            << "    highp vec4 color1;\n"
                            << "    highp vec4 color2;\n"
                            << "} "
                            << (arrayInstance ? getUniformName("colors", declNdx, m_numBindings) :
                                                getUniformName("colors", declNdx))
                            << ";\n";
    }

    // Generate the shader body for the vertex and fragment shaders
    for (int bindNdx = 0; bindNdx < m_numBindings * 2;
         ++bindNdx) // Multiply by two to cover cases for both colors for each UBO
    {
        const std::string uname =
            (arrayInstance ? getUniformName("colors", 0, bindNdx / 2) : getUniformName("colors", bindNdx / 2));
        shaderBody << "    " << (bindNdx == 0 ? "if" : "else if") << " (u_arrayNdx == " << de::toString(bindNdx)
                   << ")\n"
                   << "    {\n"
                   << "        color = " << uname << (bindNdx % 2 == 0 ? ".color1" : ".color2") << ";\n"
                   << "    }\n";
    }

    shaderBody << "    else\n"
               << "    {\n"
               << "        color = vec4(0.0, 0.0, 0.0, 1.0);\n"
               << "    }\n";

    glu::ProgramSources sources = glu::ProgramSources()
                                  << glu::VertexSource(
                                         generateVertexShader(m_shaderType, vertexUniformDecl.str(), shaderBody.str()))
                                  << glu::FragmentSource(generateFragmentShader(m_shaderType, fragmentUniformDecl.str(),
                                                                                shaderBody.str()));

    if (m_tessSupport)
        sources << glu::TessellationControlSource(
                       generateTessControlShader(m_shaderType, tessCtrlUniformDecl.str(), shaderBody.str()))
                << glu::TessellationEvaluationSource(
                       generateTessEvaluationShader(m_shaderType, tessEvalUniformDecl.str(), shaderBody.str()));

    return new glu::ShaderProgram(m_context.getRenderContext(), sources);
}

} // namespace

LayoutBindingTests::LayoutBindingTests(Context &context)
    : TestCaseGroup(context, "layout_binding", "Layout binding tests")
{
}

LayoutBindingTests::~LayoutBindingTests(void)
{
}

void LayoutBindingTests::init(void)
{
    // Render test groups
    tcu::TestCaseGroup *const samplerBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "sampler", "Test sampler layout binding");
    tcu::TestCaseGroup *const sampler2dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "sampler2d", "Test sampler2d layout binding");
    tcu::TestCaseGroup *const sampler3dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "sampler3d", "Test sampler3d layout binding");

    tcu::TestCaseGroup *const imageBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "image", "Test image layout binding");
    tcu::TestCaseGroup *const image2dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "image2d", "Test image2d layout binding");
    tcu::TestCaseGroup *const image3dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "image3d", "Test image3d layout binding");

    tcu::TestCaseGroup *const UBOBindingTestGroup = new tcu::TestCaseGroup(m_testCtx, "ubo", "Test UBO layout binding");
    tcu::TestCaseGroup *const SSBOBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "ssbo", "Test SSBO layout binding");

    // Negative test groups
    tcu::TestCaseGroup *const negativeBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "negative", "Test layout binding with invalid bindings");

    tcu::TestCaseGroup *const negativeSamplerBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "sampler", "Test sampler layout binding with invalid bindings");
    tcu::TestCaseGroup *const negativeSampler2dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "sampler2d", "Test sampler2d layout binding with invalid bindings");
    tcu::TestCaseGroup *const negativeSampler3dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "sampler3d", "Test sampler3d layout binding with invalid bindings");

    tcu::TestCaseGroup *const negativeImageBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "image", "Test image layout binding with invalid bindings");
    tcu::TestCaseGroup *const negativeImage2dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "image2d", "Test image2d layout binding with invalid bindings");
    tcu::TestCaseGroup *const negativeImage3dBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "image3d", "Test image3d layout binding with invalid bindings");

    tcu::TestCaseGroup *const negativeUBOBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "ubo", "Test UBO layout binding with invalid bindings");
    tcu::TestCaseGroup *const negativeSSBOBindingTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "ssbo", "Test SSBO layout binding with invalid bindings");

    static const struct RenderTestType
    {
        ShaderType shaderType;
        TestType testType;
        std::string name;
        std::string descPostfix;
    } s_renderTestTypes[] = {
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_SINGLE, "vertex_binding_single", "a single instance"},
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_MAX, "vertex_binding_max", "maximum binding point"},
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_MULTIPLE, "vertex_binding_multiple", "multiple instances"},
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_ARRAY, "vertex_binding_array", "an array instance"},
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_MAX_ARRAY, "vertex_binding_max_array",
         "an array instance with maximum binding point"},

        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_SINGLE, "fragment_binding_single", "a single instance"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_MAX, "fragment_binding_max", "maximum binding point"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_MULTIPLE, "fragment_binding_multiple", "multiple instances"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_ARRAY, "fragment_binding_array", "an array instance"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_MAX_ARRAY, "fragment_binding_max_array",
         "an array instance with maximum binding point"},
    };

    static const struct NegativeTestType
    {
        ShaderType shaderType;
        TestType testType;
        LayoutBindingNegativeCase::ErrorType errorType;
        std::string name;
        std::string descPostfix;
    } s_negativeTestTypes[] = {
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "vertex_binding_over_max", "over maximum binding point"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "fragment_binding_over_max", "over maximum binding point"},
        {SHADERTYPE_TESS_CONTROL, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "tess_control_binding_over_max", "over maximum binding point"},
        {SHADERTYPE_TESS_EVALUATION, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "tess_evaluation_binding_over_max", "over maximum binding point"},
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "vertex_binding_neg", "negative binding point"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "fragment_binding_neg", "negative binding point"},
        {SHADERTYPE_TESS_CONTROL, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "tess_control_binding_neg", "negative binding point"},
        {SHADERTYPE_TESS_EVALUATION, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "tess_evaluation_binding_neg", "negative binding point"},

        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "vertex_binding_over_max_array", "over maximum binding point"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "fragment_binding_over_max_array", "over maximum binding point"},
        {SHADERTYPE_TESS_CONTROL, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "tess_control_binding_over_max_array", "over maximum binding point"},
        {SHADERTYPE_TESS_EVALUATION, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_OVER_MAX_UNITS,
         "tess_evaluation_binding_over_max_array", "over maximum binding point"},
        {SHADERTYPE_VERTEX, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "vertex_binding_neg_array", "negative binding point"},
        {SHADERTYPE_FRAGMENT, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "fragment_binding_neg_array", "negative binding point"},
        {SHADERTYPE_TESS_CONTROL, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "tess_control_binding_neg_array", "negative binding point"},
        {SHADERTYPE_TESS_EVALUATION, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_LESS_THAN_ZERO,
         "tess_evaluation_binding_neg_array", "negative binding point"},

        {SHADERTYPE_ALL, TESTTYPE_BINDING_SINGLE, LayoutBindingNegativeCase::ERRORTYPE_CONTRADICTORY,
         "binding_contradictory", "contradictory binding points"},
        {SHADERTYPE_ALL, TESTTYPE_BINDING_ARRAY, LayoutBindingNegativeCase::ERRORTYPE_CONTRADICTORY,
         "binding_contradictory_array", "contradictory binding points"},
    };

    // Render tests
    for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_renderTestTypes); ++testNdx)
    {
        const RenderTestType &test = s_renderTestTypes[testNdx];

        // Render sampler binding tests
        sampler2dBindingTestGroup->addChild(new SamplerBindingRenderCase(
            m_context, test.name.c_str(), ("Sampler2D layout binding with " + test.descPostfix).c_str(),
            test.shaderType, test.testType, GL_SAMPLER_2D, GL_TEXTURE_2D));
        sampler3dBindingTestGroup->addChild(new SamplerBindingRenderCase(
            m_context, test.name.c_str(), ("Sampler3D layout binding with " + test.descPostfix).c_str(),
            test.shaderType, test.testType, GL_SAMPLER_3D, GL_TEXTURE_3D));

        // Render image binding tests
        image2dBindingTestGroup->addChild(new ImageBindingRenderCase(
            m_context, test.name.c_str(), ("Image2D layout binding with " + test.descPostfix).c_str(), test.shaderType,
            test.testType, GL_IMAGE_2D, GL_TEXTURE_2D));
        image3dBindingTestGroup->addChild(new ImageBindingRenderCase(
            m_context, test.name.c_str(), ("Image3D layout binding with " + test.descPostfix).c_str(), test.shaderType,
            test.testType, GL_IMAGE_3D, GL_TEXTURE_3D));

        // Render UBO binding tests
        UBOBindingTestGroup->addChild(new UBOBindingRenderCase(m_context, test.name.c_str(),
                                                               ("UBO layout binding with " + test.descPostfix).c_str(),
                                                               test.shaderType, test.testType));

        // Render SSBO binding tests
        SSBOBindingTestGroup->addChild(new SSBOBindingRenderCase(
            m_context, test.name.c_str(), ("SSBO layout binding with " + test.descPostfix).c_str(), test.shaderType,
            test.testType));
    }

    // Negative binding tests
    for (int testNdx = 0; testNdx < DE_LENGTH_OF_ARRAY(s_negativeTestTypes); ++testNdx)
    {
        const NegativeTestType &test = s_negativeTestTypes[testNdx];

        // Negative sampler binding tests
        negativeSampler2dBindingTestGroup->addChild(new SamplerBindingNegativeCase(
            m_context, test.name.c_str(), ("Invalid sampler2d layout binding using " + test.descPostfix).c_str(),
            test.shaderType, test.testType, test.errorType, GL_SAMPLER_2D));
        negativeSampler3dBindingTestGroup->addChild(new SamplerBindingNegativeCase(
            m_context, test.name.c_str(), ("Invalid sampler3d layout binding using " + test.descPostfix).c_str(),
            test.shaderType, test.testType, test.errorType, GL_SAMPLER_3D));

        // Negative image binding tests
        negativeImage2dBindingTestGroup->addChild(new ImageBindingNegativeCase(
            m_context, test.name.c_str(), ("Invalid image2d layout binding using " + test.descPostfix).c_str(),
            test.shaderType, test.testType, test.errorType, GL_IMAGE_2D));
        negativeImage3dBindingTestGroup->addChild(new ImageBindingNegativeCase(
            m_context, test.name.c_str(), ("Invalid image3d layout binding using " + test.descPostfix).c_str(),
            test.shaderType, test.testType, test.errorType, GL_IMAGE_3D));

        // Negative UBO binding tests
        negativeUBOBindingTestGroup->addChild(new UBOBindingNegativeCase(
            m_context, test.name.c_str(), ("Invalid UBO layout binding using " + test.descPostfix).c_str(),
            test.shaderType, test.testType, test.errorType));

        // Negative SSBO binding tests
        negativeSSBOBindingTestGroup->addChild(new SSBOBindingNegativeCase(
            m_context, test.name.c_str(), ("Invalid SSBO layout binding using " + test.descPostfix).c_str(),
            test.shaderType, test.testType, test.errorType));
    }

    samplerBindingTestGroup->addChild(sampler2dBindingTestGroup);
    samplerBindingTestGroup->addChild(sampler3dBindingTestGroup);

    imageBindingTestGroup->addChild(image2dBindingTestGroup);
    imageBindingTestGroup->addChild(image3dBindingTestGroup);

    negativeSamplerBindingTestGroup->addChild(negativeSampler2dBindingTestGroup);
    negativeSamplerBindingTestGroup->addChild(negativeSampler3dBindingTestGroup);

    negativeImageBindingTestGroup->addChild(negativeImage2dBindingTestGroup);
    negativeImageBindingTestGroup->addChild(negativeImage3dBindingTestGroup);

    negativeBindingTestGroup->addChild(negativeSamplerBindingTestGroup);
    negativeBindingTestGroup->addChild(negativeUBOBindingTestGroup);
    negativeBindingTestGroup->addChild(negativeSSBOBindingTestGroup);
    negativeBindingTestGroup->addChild(negativeImageBindingTestGroup);

    addChild(samplerBindingTestGroup);
    addChild(UBOBindingTestGroup);
    addChild(SSBOBindingTestGroup);
    addChild(imageBindingTestGroup);
    addChild(negativeBindingTestGroup);
}

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