/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 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 Rbo state query tests.
 *//*--------------------------------------------------------------------*/

#include "es3fShaderStateQueryTests.hpp"
#include "glsStateQueryUtil.hpp"
#include "es3fApiCase.hpp"
#include "gluRenderContext.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "deRandom.hpp"
#include "deMath.h"
#include "deString.h"

using namespace glw; // GLint and other GL types
using deqp::gls::StateQueryUtil::StateQueryMemoryWriteGuard;

namespace deqp
{
namespace gles3
{
namespace Functional
{
namespace
{

static const char *commonTestVertSource = "#version 300 es\n"
                                          "void main (void)\n"
                                          "{\n"
                                          "    gl_Position = vec4(0.0);\n"
                                          "}\n\0";
static const char *commonTestFragSource = "#version 300 es\n"
                                          "layout(location = 0) out mediump vec4 fragColor;\n"
                                          "void main (void)\n"
                                          "{\n"
                                          "    fragColor = vec4(0.0);\n"
                                          "}\n\0";

static const char *brokenShader = "#version 300 es\n"
                                  "broken, this should not compile!\n"
                                  "\n\0";

// rounds x.1 to x+1
template <typename T>
T roundGLfloatToNearestIntegerUp(GLfloat val)
{
    return (T)(ceil(val));
}

// rounds x.9 to x
template <typename T>
T roundGLfloatToNearestIntegerDown(GLfloat val)
{
    return (T)(floor(val));
}

bool checkIntEquals(tcu::TestContext &testCtx, GLint got, GLint expected)
{
    using tcu::TestLog;

    if (got != expected)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got
                         << TestLog::EndMessage;
        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
        return false;
    }
    return true;
}

void checkPointerEquals(tcu::TestContext &testCtx, const void *got, const void *expected)
{
    using tcu::TestLog;

    if (got != expected)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << expected << "; got " << got
                         << TestLog::EndMessage;
        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid value");
    }
}

void verifyShaderParam(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint shader, GLenum pname,
                       GLenum reference)
{
    StateQueryMemoryWriteGuard<GLint> state;
    gl.glGetShaderiv(shader, pname, &state);

    if (state.verifyValidity(testCtx))
        checkIntEquals(testCtx, state, reference);
}

bool verifyProgramParam(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLenum pname,
                        GLenum reference)
{
    StateQueryMemoryWriteGuard<GLint> state;
    gl.glGetProgramiv(program, pname, &state);

    return state.verifyValidity(testCtx) && checkIntEquals(testCtx, state, reference);
}

void verifyActiveUniformParam(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLuint index,
                              GLenum pname, GLenum reference)
{
    StateQueryMemoryWriteGuard<GLint> state;
    gl.glGetActiveUniformsiv(program, 1, &index, pname, &state);

    if (state.verifyValidity(testCtx))
        checkIntEquals(testCtx, state, reference);
}

void verifyActiveUniformBlockParam(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program,
                                   GLuint blockIndex, GLenum pname, GLenum reference)
{
    StateQueryMemoryWriteGuard<GLint> state;
    gl.glGetActiveUniformBlockiv(program, blockIndex, pname, &state);

    if (state.verifyValidity(testCtx))
        checkIntEquals(testCtx, state, reference);
}

void verifyCurrentVertexAttribf(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLint index, GLfloat x, GLfloat y,
                                GLfloat z, GLfloat w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[4]> attribValue;
    gl.glGetVertexAttribfv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);

    attribValue.verifyValidity(testCtx);

    if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
                         << "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << ","
                         << attribValue[3] << "]" << TestLog::EndMessage;
        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
    }
}

void verifyCurrentVertexAttribIi(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLint index, GLint x, GLint y,
                                 GLint z, GLint w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLint[4]> attribValue;
    gl.glGetVertexAttribIiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);

    attribValue.verifyValidity(testCtx);

    if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
                         << "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << ","
                         << attribValue[3] << "]" << TestLog::EndMessage;
        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
    }
}

void verifyCurrentVertexAttribIui(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLint index, GLuint x, GLuint y,
                                  GLuint z, GLuint w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLuint[4]> attribValue;
    gl.glGetVertexAttribIuiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);

    attribValue.verifyValidity(testCtx);

    if (attribValue[0] != x || attribValue[1] != y || attribValue[2] != z || attribValue[3] != w)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: Expected [" << x << "," << y << "," << z << "," << w << "];"
                         << "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << ","
                         << attribValue[3] << "]" << TestLog::EndMessage;
        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
    }
}

void verifyCurrentVertexAttribConversion(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLint index, GLfloat x,
                                         GLfloat y, GLfloat z, GLfloat w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLint[4]> attribValue;
    gl.glGetVertexAttribiv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);

    attribValue.verifyValidity(testCtx);

    const GLint referenceAsGLintMin[] = {
        roundGLfloatToNearestIntegerDown<GLint>(x), roundGLfloatToNearestIntegerDown<GLint>(y),
        roundGLfloatToNearestIntegerDown<GLint>(z), roundGLfloatToNearestIntegerDown<GLint>(w)};
    const GLint referenceAsGLintMax[] = {
        roundGLfloatToNearestIntegerUp<GLint>(x), roundGLfloatToNearestIntegerUp<GLint>(y),
        roundGLfloatToNearestIntegerUp<GLint>(z), roundGLfloatToNearestIntegerUp<GLint>(w)};

    if (attribValue[0] < referenceAsGLintMin[0] || attribValue[0] > referenceAsGLintMax[0] ||
        attribValue[1] < referenceAsGLintMin[1] || attribValue[1] > referenceAsGLintMax[1] ||
        attribValue[2] < referenceAsGLintMin[2] || attribValue[2] > referenceAsGLintMax[2] ||
        attribValue[3] < referenceAsGLintMin[3] || attribValue[3] > referenceAsGLintMax[3])
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected in range "
                         << "[" << referenceAsGLintMin[0] << " " << referenceAsGLintMax[0] << "], "
                         << "[" << referenceAsGLintMin[1] << " " << referenceAsGLintMax[1] << "], "
                         << "[" << referenceAsGLintMin[2] << " " << referenceAsGLintMax[2] << "], "
                         << "[" << referenceAsGLintMin[3] << " " << referenceAsGLintMax[3] << "]"
                         << "; got " << attribValue[0] << ", " << attribValue[1] << ", " << attribValue[2] << ", "
                         << attribValue[3] << " "
                         << "; Input=" << x << "; " << y << "; " << z << "; " << w << " " << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid attribute value");
    }
}

void verifyVertexAttrib(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLint index, GLenum pname, GLenum reference)
{
    StateQueryMemoryWriteGuard<GLint> state;
    gl.glGetVertexAttribIiv(index, pname, &state);

    if (state.verifyValidity(testCtx))
        checkIntEquals(testCtx, state, reference);
}

void verifyUniformValue1f(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, float x)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[1]> state;
    gl.glGetUniformfv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << "]; got [" << state[0] << "]"
                         << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue2f(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, float x,
                          float y)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[2]> state;
    gl.glGetUniformfv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << "]; got [" << state[0]
                         << ", " << state[1] << "]" << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue3f(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, float x,
                          float y, float z)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[3]> state;
    gl.glGetUniformfv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y || state[2] != z)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << ", " << z << "]; got ["
                         << state[0] << ", " << state[1] << ", " << state[2] << "]" << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue4f(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, float x,
                          float y, float z, float w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[4]> state;
    gl.glGetUniformfv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y || state[2] != z || state[3] != w)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << ", " << z << ", " << w
                         << "]; got [" << state[0] << ", " << state[1] << ", " << state[2] << ", " << state[3] << "]"
                         << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue1i(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLint x)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLint[1]> state;
    gl.glGetUniformiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << "]; got [" << state[0] << "]"
                         << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue2i(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLint x,
                          GLint y)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLint[2]> state;
    gl.glGetUniformiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << "]; got [" << state[0]
                         << ", " << state[1] << "]" << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue3i(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLint x,
                          GLint y, GLint z)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLint[3]> state;
    gl.glGetUniformiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y || state[2] != z)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << ", " << z << "]; got ["
                         << state[0] << ", " << state[1] << ", " << state[2] << "]" << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue4i(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLint x,
                          GLint y, GLint z, GLint w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLint[4]> state;
    gl.glGetUniformiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y || state[2] != z || state[3] != w)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << ", " << z << ", " << w
                         << "]; got [" << state[0] << ", " << state[1] << ", " << state[2] << ", " << state[3] << "]"
                         << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue1ui(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLuint x)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLuint[1]> state;
    gl.glGetUniformuiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << "]; got [" << state[0] << "]"
                         << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue2ui(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLuint x,
                           GLuint y)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLuint[2]> state;
    gl.glGetUniformuiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << "]; got [" << state[0]
                         << ", " << state[1] << "]" << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue3ui(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLuint x,
                           GLuint y, GLuint z)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLuint[3]> state;
    gl.glGetUniformuiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y || state[2] != z)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << ", " << z << "]; got ["
                         << state[0] << ", " << state[1] << ", " << state[2] << "]" << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

void verifyUniformValue4ui(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location, GLuint x,
                           GLuint y, GLuint z, GLuint w)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLuint[4]> state;
    gl.glGetUniformuiv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    if (state[0] != x || state[1] != y || state[2] != z || state[3] != w)
    {
        testCtx.getLog() << TestLog::Message << "// ERROR: expected [" << x << ", " << y << ", " << z << ", " << w
                         << "]; got [" << state[0] << ", " << state[1] << ", " << state[2] << ", " << state[3] << "]"
                         << TestLog::EndMessage;

        if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
    }
}

template <int Count>
void verifyUniformValues(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location,
                         const GLfloat *values)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[Count]> state;
    gl.glGetUniformfv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    for (int ndx = 0; ndx < Count; ++ndx)
    {
        if (values[ndx] != state[ndx])
        {
            testCtx.getLog() << TestLog::Message << "// ERROR: at index " << ndx << " expected " << values[ndx]
                             << "; got " << state[ndx] << TestLog::EndMessage;

            if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
        }
    }
}

template <int N>
void verifyUniformMatrixValues(tcu::TestContext &testCtx, glu::CallLogWrapper &gl, GLuint program, GLint location,
                               const GLfloat *values, bool transpose)
{
    using tcu::TestLog;

    StateQueryMemoryWriteGuard<GLfloat[N * N]> state;
    gl.glGetUniformfv(program, location, state);

    if (!state.verifyValidity(testCtx))
        return;

    for (int y = 0; y < N; ++y)
        for (int x = 0; x < N; ++x)
        {
            const int refIndex   = y * N + x;
            const int stateIndex = transpose ? (x * N + y) : (y * N + x);

            if (values[refIndex] != state[stateIndex])
            {
                testCtx.getLog() << TestLog::Message << "// ERROR: at index [" << y << "][" << x << "] expected "
                                 << values[refIndex] << "; got " << state[stateIndex] << TestLog::EndMessage;

                if (testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid uniform value");
            }
        }
}

class ShaderTypeCase : public ApiCase
{
public:
    ShaderTypeCase(Context &context, const char *name, const char *description) : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const GLenum shaderTypes[] = {GL_VERTEX_SHADER, GL_FRAGMENT_SHADER};
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(shaderTypes); ++ndx)
        {
            const GLuint shader = glCreateShader(shaderTypes[ndx]);
            verifyShaderParam(m_testCtx, *this, shader, GL_SHADER_TYPE, shaderTypes[ndx]);
            glDeleteShader(shader);
        }
    }
};

class ShaderCompileStatusCase : public ApiCase
{
public:
    ShaderCompileStatusCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_FALSE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_FALSE);

        glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        expectError(GL_NO_ERROR);
    }
};

class ShaderInfoLogCase : public ApiCase
{
public:
    ShaderInfoLogCase(Context &context, const char *name, const char *description) : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        // INFO_LOG_LENGTH is 0 by default and it includes null-terminator
        const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
        verifyShaderParam(m_testCtx, *this, shader, GL_INFO_LOG_LENGTH, 0);

        glShaderSource(shader, 1, &brokenShader, DE_NULL);
        glCompileShader(shader);
        expectError(GL_NO_ERROR);

        // check the log length
        StateQueryMemoryWriteGuard<GLint> logLength;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);
        if (!logLength.verifyValidity(m_testCtx))
        {
            glDeleteShader(shader);
            return;
        }
        if (logLength == 0)
        {
            glDeleteShader(shader);
            return;
        }

        // check normal case
        {
            char buffer[2048] = {'x'}; // non-zero initialization

            GLint written = 0; // written does not include null terminator
            glGetShaderInfoLog(shader, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);

            // check lengths are consistent
            if (logLength <= DE_LENGTH_OF_ARRAY(buffer))
            {
                if (written != logLength - 1)
                {
                    m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << logLength - 1 << "; got "
                                       << written << TestLog::EndMessage;
                    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
                }
            }

            // check null-terminator, either at end of buffer or at buffer[written]
            const char *terminator = &buffer[DE_LENGTH_OF_ARRAY(buffer) - 1];
            if (logLength < DE_LENGTH_OF_ARRAY(buffer))
                terminator = &buffer[written];

            if (*terminator != '\0')
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator, got " << (int)*terminator
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log terminator");
            }
        }

        // check with too small buffer
        {
            char buffer[2048] = {'x'}; // non-zero initialization

            // check string always ends with \0, even with small buffers
            GLint written = 0;
            glGetShaderInfoLog(shader, 1, &written, buffer);
            if (written != 0)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length 0; got " << written
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
            }
            if (buffer[0] != '\0')
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator, got " << (int)buffer[0]
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log terminator");
            }
        }

        glDeleteShader(shader);
        expectError(GL_NO_ERROR);
    }
};

class ShaderSourceCase : public ApiCase
{
public:
    ShaderSourceCase(Context &context, const char *name, const char *description) : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        // SHADER_SOURCE_LENGTH does include 0-terminator
        const GLuint shader = glCreateShader(GL_VERTEX_SHADER);
        verifyShaderParam(m_testCtx, *this, shader, GL_SHADER_SOURCE_LENGTH, 0);

        // check the SHADER_SOURCE_LENGTH
        {
            glShaderSource(shader, 1, &brokenShader, DE_NULL);
            expectError(GL_NO_ERROR);

            StateQueryMemoryWriteGuard<GLint> sourceLength;
            glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);

            sourceLength.verifyValidity(m_testCtx);

            const GLint referenceLength =
                (GLint)std::string(brokenShader).length() + 1; // including the null terminator
            if (sourceLength != referenceLength)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << referenceLength << "; got "
                                   << sourceLength << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
            }
        }

        // check the concat source SHADER_SOURCE_LENGTH
        {
            const char *shaders[] = {brokenShader, brokenShader};
            glShaderSource(shader, 2, shaders, DE_NULL);
            expectError(GL_NO_ERROR);

            StateQueryMemoryWriteGuard<GLint> sourceLength;
            glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);

            sourceLength.verifyValidity(m_testCtx);

            const GLint referenceLength =
                2 * (GLint)std::string(brokenShader).length() + 1; // including the null terminator
            if (sourceLength != referenceLength)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected length " << referenceLength << "; got "
                                   << sourceLength << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
            }
        }

        // check the string length
        {
            char buffer[2048] = {'x'};
            DE_ASSERT(DE_LENGTH_OF_ARRAY(buffer) > 2 * (int)std::string(brokenShader).length());

            GLint written = 0; // not inluding null-terminator
            glGetShaderSource(shader, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);

            const GLint referenceLength = 2 * (GLint)std::string(brokenShader).length();
            if (written != referenceLength)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length " << referenceLength
                                   << "; got " << written << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
            }
            // check null pointer at
            else
            {
                if (buffer[referenceLength] != '\0')
                {
                    m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator at "
                                       << referenceLength << TestLog::EndMessage;
                    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "did not get a null terminator");
                }
            }
        }

        // check with small buffer
        {
            char buffer[2048] = {'x'};

            GLint written = 0;
            glGetShaderSource(shader, 1, &written, buffer);

            if (written != 0)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length 0; got " << written
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid source length");
            }
            if (buffer[0] != '\0')
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator; got=" << int(buffer[0])
                                   << ", char=" << buffer[0] << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid terminator");
            }
        }

        glDeleteShader(shader);
        expectError(GL_NO_ERROR);
    }
};

class DeleteStatusCase : public ApiCase
{
public:
    DeleteStatusCase(Context &context, const char *name, const char *description) : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);

        GLuint shaderProg = glCreateProgram();
        glAttachShader(shaderProg, shaderVert);
        glAttachShader(shaderProg, shaderFrag);
        glLinkProgram(shaderProg);
        expectError(GL_NO_ERROR);

        verifyProgramParam(m_testCtx, *this, shaderProg, GL_LINK_STATUS, GL_TRUE);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_DELETE_STATUS, GL_FALSE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_DELETE_STATUS, GL_FALSE);
        verifyProgramParam(m_testCtx, *this, shaderProg, GL_DELETE_STATUS, GL_FALSE);
        expectError(GL_NO_ERROR);

        glUseProgram(shaderProg);

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(shaderProg);
        expectError(GL_NO_ERROR);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_DELETE_STATUS, GL_TRUE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_DELETE_STATUS, GL_TRUE);
        verifyProgramParam(m_testCtx, *this, shaderProg, GL_DELETE_STATUS, GL_TRUE);
        expectError(GL_NO_ERROR);

        glUseProgram(0);
        expectError(GL_NO_ERROR);
    }
};

class CurrentVertexAttribInitialCase : public ApiCase
{
public:
    CurrentVertexAttribInitialCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        int attribute_count = 16;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);

        // initial

        for (int index = 0; index < attribute_count; ++index)
        {
            StateQueryMemoryWriteGuard<GLfloat[4]> attribValue;
            glGetVertexAttribfv(index, GL_CURRENT_VERTEX_ATTRIB, attribValue);
            attribValue.verifyValidity(m_testCtx);

            if (attribValue[0] != 0.0f || attribValue[1] != 0.0f || attribValue[2] != 0.0f || attribValue[3] != 1.0f)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected [0, 0, 0, 1];"
                                   << "got [" << attribValue[0] << "," << attribValue[1] << "," << attribValue[2] << ","
                                   << attribValue[3] << "]" << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid attribute value");
            }
        }
    }
};

class CurrentVertexAttribFloatCase : public ApiCase
{
public:
    CurrentVertexAttribFloatCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        de::Random rnd(0xabcdef);

        int attribute_count = 16;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);

        // test write float/read float

        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = rnd.getFloat(-64000, 64000);
            const GLfloat z = rnd.getFloat(-64000, 64000);
            const GLfloat w = rnd.getFloat(-64000, 64000);

            glVertexAttrib4f(index, x, y, z, w);
            verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
        }
        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = rnd.getFloat(-64000, 64000);
            const GLfloat z = rnd.getFloat(-64000, 64000);
            const GLfloat w = 1.0f;

            glVertexAttrib3f(index, x, y, z);
            verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
        }
        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = rnd.getFloat(-64000, 64000);
            const GLfloat z = 0.0f;
            const GLfloat w = 1.0f;

            glVertexAttrib2f(index, x, y);
            verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
        }
        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = 0.0f;
            const GLfloat z = 0.0f;
            const GLfloat w = 1.0f;

            glVertexAttrib1f(index, x);
            verifyCurrentVertexAttribf(m_testCtx, *this, index, x, y, z, w);
        }
    }
};

class CurrentVertexAttribIntCase : public ApiCase
{
public:
    CurrentVertexAttribIntCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        de::Random rnd(0xabcdef);

        int attribute_count = 16;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);

        // test write float/read float

        for (int index = 0; index < attribute_count; ++index)
        {
            const GLint x = rnd.getInt(-64000, 64000);
            const GLint y = rnd.getInt(-64000, 64000);
            const GLint z = rnd.getInt(-64000, 64000);
            const GLint w = rnd.getInt(-64000, 64000);

            glVertexAttribI4i(index, x, y, z, w);
            verifyCurrentVertexAttribIi(m_testCtx, *this, index, x, y, z, w);
        }
    }
};

class CurrentVertexAttribUintCase : public ApiCase
{
public:
    CurrentVertexAttribUintCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        de::Random rnd(0xabcdef);

        int attribute_count = 16;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);

        // test write float/read float

        for (int index = 0; index < attribute_count; ++index)
        {
            const GLuint x = rnd.getInt(0, 64000);
            const GLuint y = rnd.getInt(0, 64000);
            const GLuint z = rnd.getInt(0, 64000);
            const GLuint w = rnd.getInt(0, 64000);

            glVertexAttribI4ui(index, x, y, z, w);
            verifyCurrentVertexAttribIui(m_testCtx, *this, index, x, y, z, w);
        }
    }
};

class CurrentVertexAttribConversionCase : public ApiCase
{
public:
    CurrentVertexAttribConversionCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        de::Random rnd(0xabcdef);

        int attribute_count = 16;
        glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &attribute_count);

        // test write float/read float

        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = rnd.getFloat(-64000, 64000);
            const GLfloat z = rnd.getFloat(-64000, 64000);
            const GLfloat w = rnd.getFloat(-64000, 64000);

            glVertexAttrib4f(index, x, y, z, w);
            verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
        }
        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = rnd.getFloat(-64000, 64000);
            const GLfloat z = rnd.getFloat(-64000, 64000);
            const GLfloat w = 1.0f;

            glVertexAttrib3f(index, x, y, z);
            verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
        }
        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = rnd.getFloat(-64000, 64000);
            const GLfloat z = 0.0f;
            const GLfloat w = 1.0f;

            glVertexAttrib2f(index, x, y);
            verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
        }
        for (int index = 0; index < attribute_count; ++index)
        {
            const GLfloat x = rnd.getFloat(-64000, 64000);
            const GLfloat y = 0.0f;
            const GLfloat z = 0.0f;
            const GLfloat w = 1.0f;

            glVertexAttrib1f(index, x);
            verifyCurrentVertexAttribConversion(m_testCtx, *this, index, x, y, z, w);
        }
    }
};

class ProgramInfoLogCase : public ApiCase
{
public:
    enum BuildErrorType
    {
        BUILDERROR_COMPILE = 0,
        BUILDERROR_LINK
    };

    ProgramInfoLogCase(Context &context, const char *name, const char *description, BuildErrorType buildErrorType)
        : ApiCase(context, name, description)
        , m_buildErrorType(buildErrorType)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        enum
        {
            BUF_SIZE = 2048
        };

        static const char *const linkErrorVtxSource = "#version 300 es\n"
                                                      "in highp vec4 a_pos;\n"
                                                      "uniform highp vec4 u_uniform;\n"
                                                      "void main ()\n"
                                                      "{\n"
                                                      "    gl_Position = a_pos + u_uniform;\n"
                                                      "}\n";
        static const char *const linkErrorFrgSource = "#version 300 es\n"
                                                      "in highp vec4 v_missingVar;\n"
                                                      "uniform highp int u_uniform;\n"
                                                      "layout(location = 0) out mediump vec4 fragColor;\n"
                                                      "void main ()\n"
                                                      "{\n"
                                                      "    fragColor = v_missingVar + vec4(float(u_uniform));\n"
                                                      "}\n";

        const char *vtxSource = (m_buildErrorType == BUILDERROR_COMPILE) ? (brokenShader) : (linkErrorVtxSource);
        const char *frgSource = (m_buildErrorType == BUILDERROR_COMPILE) ? (brokenShader) : (linkErrorFrgSource);

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &vtxSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &frgSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);

        StateQueryMemoryWriteGuard<GLint> logLength;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength);
        logLength.verifyValidity(m_testCtx);

        // check INFO_LOG_LENGTH == GetProgramInfoLog len
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryLarge", "Query to large buffer");
            char buffer[BUF_SIZE] = {'x'};
            GLint written         = 0;

            glGetProgramInfoLog(program, BUF_SIZE, &written, buffer);

            if (logLength != 0 && written + 1 != logLength) // INFO_LOG_LENGTH contains 0-terminator
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected INFO_LOG_LENGTH " << written + 1
                                   << "; got " << logLength << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
            }
            else if (logLength != 0 && buffer[written] != '\0')
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator at index " << written
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing null terminator");
            }
        }

        // check query to just correct sized buffer
        if (BUF_SIZE > logLength)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryAll",
                                                "Query all to exactly right sized buffer");
            char buffer[BUF_SIZE] = {'x'};
            GLint written         = 0;

            glGetProgramInfoLog(program, logLength, &written, buffer);

            if (logLength != 0 && written + 1 != logLength) // INFO_LOG_LENGTH contains 0-terminator
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected INFO_LOG_LENGTH " << written + 1
                                   << "; got " << logLength << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
            }
            else if (logLength != 0 && buffer[written] != '\0')
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected null terminator at index " << written
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "missing null terminator");
            }
        }

        // check GetProgramInfoLog works with too small buffer
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "QueryNone", "Query none");
            char buffer[BUF_SIZE] = {'x'};
            GLint written         = 0;

            glGetProgramInfoLog(program, 1, &written, buffer);

            if (written != 0)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected write length 0; got " << written
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid log length");
            }
        }

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }

    const BuildErrorType m_buildErrorType;
};

class ProgramValidateStatusCase : public ApiCase
{
public:
    ProgramValidateStatusCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        // test validate ok
        {
            GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
            GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

            glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
            glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);

            glCompileShader(shaderVert);
            glCompileShader(shaderFrag);
            expectError(GL_NO_ERROR);

            GLuint program = glCreateProgram();
            glAttachShader(program, shaderVert);
            glAttachShader(program, shaderFrag);
            glLinkProgram(program);
            expectError(GL_NO_ERROR);

            verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
            verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
            verifyProgramParam(m_testCtx, *this, program, GL_LINK_STATUS, GL_TRUE);

            glValidateProgram(program);
            verifyProgramParam(m_testCtx, *this, program, GL_VALIDATE_STATUS, GL_TRUE);

            glDeleteShader(shaderVert);
            glDeleteShader(shaderFrag);
            glDeleteProgram(program);
            expectError(GL_NO_ERROR);
        }

        // test with broken shader
        {
            GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
            GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

            glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
            glShaderSource(shaderFrag, 1, &brokenShader, DE_NULL);

            glCompileShader(shaderVert);
            glCompileShader(shaderFrag);
            expectError(GL_NO_ERROR);

            GLuint program = glCreateProgram();
            glAttachShader(program, shaderVert);
            glAttachShader(program, shaderFrag);
            glLinkProgram(program);
            expectError(GL_NO_ERROR);

            verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
            verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_FALSE);
            verifyProgramParam(m_testCtx, *this, program, GL_LINK_STATUS, GL_FALSE);

            glValidateProgram(program);
            verifyProgramParam(m_testCtx, *this, program, GL_VALIDATE_STATUS, GL_FALSE);

            glDeleteShader(shaderVert);
            glDeleteShader(shaderFrag);
            glDeleteProgram(program);
            expectError(GL_NO_ERROR);
        }
    }
};

class ProgramAttachedShadersCase : public ApiCase
{
public:
    ProgramAttachedShadersCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        // check ATTACHED_SHADERS

        GLuint program = glCreateProgram();
        verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 0);
        expectError(GL_NO_ERROR);

        glAttachShader(program, shaderVert);
        verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 1);
        expectError(GL_NO_ERROR);

        glAttachShader(program, shaderFrag);
        verifyProgramParam(m_testCtx, *this, program, GL_ATTACHED_SHADERS, 2);
        expectError(GL_NO_ERROR);

        // check GetAttachedShaders
        {
            GLuint shaders[2] = {0, 0};
            GLint count       = 0;
            glGetAttachedShaders(program, DE_LENGTH_OF_ARRAY(shaders), &count, shaders);

            if (count != 2)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 2; got " << count << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
            }
            // shaders are the attached shaders?
            if (!((shaders[0] == shaderVert && shaders[1] == shaderFrag) ||
                  (shaders[0] == shaderFrag && shaders[1] == shaderVert)))
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected {" << shaderVert << ", " << shaderFrag
                                   << "}; got {" << shaders[0] << ", " << shaders[1] << "}" << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
            }
        }

        // check GetAttachedShaders with too small buffer
        {
            GLuint shaders[2] = {0, 0};
            GLint count       = 0;

            glGetAttachedShaders(program, 0, &count, shaders);
            if (count != 0)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0; got " << count << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
            }

            count = 0;
            glGetAttachedShaders(program, 1, &count, shaders);
            if (count != 1)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 1; got " << count << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong shader count");
            }
        }

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class ProgramActiveUniformNameCase : public ApiCase
{
public:
    ProgramActiveUniformNameCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        static const char *testVertSource = "#version 300 es\n"
                                            "uniform highp float uniformNameWithLength23;\n"
                                            "uniform highp vec2 uniformVec2;\n"
                                            "uniform highp mat4 uniformMat4;\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_Position = vec4(0.0) + vec4(uniformNameWithLength23) + "
                                            "vec4(uniformVec2.x) + vec4(uniformMat4[2][3]);\n"
                                            "}\n\0";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n\0";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        expectError(GL_NO_ERROR);

        verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORMS, 3);
        verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORM_MAX_LENGTH,
                           (GLint)std::string("uniformNameWithLength23").length() + 1); // including a null terminator
        expectError(GL_NO_ERROR);

        const char *uniformNames[] = {"uniformNameWithLength23", "uniformVec2", "uniformMat4"};
        StateQueryMemoryWriteGuard<GLuint[DE_LENGTH_OF_ARRAY(uniformNames)]> uniformIndices;
        glGetUniformIndices(program, DE_LENGTH_OF_ARRAY(uniformNames), uniformNames, uniformIndices);
        uniformIndices.verifyValidity(m_testCtx);

        // check name lengths
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformNames); ++ndx)
        {
            const GLuint uniformIndex = uniformIndices[ndx];

            StateQueryMemoryWriteGuard<GLint> uniformNameLen;
            glGetActiveUniformsiv(program, 1, &uniformIndex, GL_UNIFORM_NAME_LENGTH, &uniformNameLen);

            uniformNameLen.verifyValidity(m_testCtx);

            const GLint referenceLength = (GLint)std::string(uniformNames[ndx]).length() + 1;
            if (referenceLength != uniformNameLen) // uniformNameLen is with null terminator
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << referenceLength << "got "
                                   << uniformNameLen << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
            }
        }

        // check names
        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformNames); ++ndx)
        {
            char buffer[2048] = {'x'};

            const GLuint uniformIndex = uniformIndices[ndx];

            GLint written = 0; // null terminator not included
            GLint size    = 0;
            GLenum type   = 0;
            glGetActiveUniform(program, uniformIndex, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);

            const GLint referenceLength = (GLint)std::string(uniformNames[ndx]).length();
            if (referenceLength != written)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected " << referenceLength << "got " << written
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
            }

            // and with too small buffer
            written = 0;
            glGetActiveUniform(program, uniformIndex, 1, &written, &size, &type, buffer);

            if (written != 0)
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected 0 got " << written << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform name length");
            }
        }

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class ProgramUniformCase : public ApiCase
{
public:
    ProgramUniformCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const struct UniformType
        {
            const char *declaration;
            const char *postDeclaration;
            const char *precision;
            const char *layout;
            const char *getter;
            GLenum type;
            GLint size;
            GLint isRowMajor;
        } uniformTypes[] = {
            {"float", "", "highp", "", "uniformValue", GL_FLOAT, 1, GL_FALSE},
            {"float[2]", "", "highp", "", "uniformValue[1]", GL_FLOAT, 2, GL_FALSE},
            {"vec2", "", "highp", "", "uniformValue.x", GL_FLOAT_VEC2, 1, GL_FALSE},
            {"vec3", "", "highp", "", "uniformValue.x", GL_FLOAT_VEC3, 1, GL_FALSE},
            {"vec4", "", "highp", "", "uniformValue.x", GL_FLOAT_VEC4, 1, GL_FALSE},
            {"int", "", "highp", "", "float(uniformValue)", GL_INT, 1, GL_FALSE},
            {"ivec2", "", "highp", "", "float(uniformValue.x)", GL_INT_VEC2, 1, GL_FALSE},
            {"ivec3", "", "highp", "", "float(uniformValue.x)", GL_INT_VEC3, 1, GL_FALSE},
            {"ivec4", "", "highp", "", "float(uniformValue.x)", GL_INT_VEC4, 1, GL_FALSE},
            {"uint", "", "highp", "", "float(uniformValue)", GL_UNSIGNED_INT, 1, GL_FALSE},
            {"uvec2", "", "highp", "", "float(uniformValue.x)", GL_UNSIGNED_INT_VEC2, 1, GL_FALSE},
            {"uvec3", "", "highp", "", "float(uniformValue.x)", GL_UNSIGNED_INT_VEC3, 1, GL_FALSE},
            {"uvec4", "", "highp", "", "float(uniformValue.x)", GL_UNSIGNED_INT_VEC4, 1, GL_FALSE},
            {"bool", "", "", "", "float(uniformValue)", GL_BOOL, 1, GL_FALSE},
            {"bvec2", "", "", "", "float(uniformValue.x)", GL_BOOL_VEC2, 1, GL_FALSE},
            {"bvec3", "", "", "", "float(uniformValue.x)", GL_BOOL_VEC3, 1, GL_FALSE},
            {"bvec4", "", "", "", "float(uniformValue.x)", GL_BOOL_VEC4, 1, GL_FALSE},
            {"mat2", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT2, 1, GL_FALSE},
            {"mat3", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT3, 1, GL_FALSE},
            {"mat4", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT4, 1, GL_FALSE},
            {"mat2x3", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT2x3, 1, GL_FALSE},
            {"mat2x4", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT2x4, 1, GL_FALSE},
            {"mat3x2", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT3x2, 1, GL_FALSE},
            {"mat3x4", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT3x4, 1, GL_FALSE},
            {"mat4x2", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT4x2, 1, GL_FALSE},
            {"mat4x3", "", "highp", "", "float(uniformValue[0][0])", GL_FLOAT_MAT4x3, 1, GL_FALSE},
            {"sampler2D", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_SAMPLER_2D, 1, GL_FALSE},
            {"sampler3D", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_SAMPLER_3D, 1, GL_FALSE},
            {"samplerCube", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_SAMPLER_CUBE, 1, GL_FALSE},
            {"sampler2DShadow", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_SAMPLER_2D_SHADOW, 1,
             GL_FALSE},
            {"sampler2DArray", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_SAMPLER_2D_ARRAY, 1,
             GL_FALSE},
            {"sampler2DArrayShadow", "", "highp", "", "float(textureSize(uniformValue,0).r)",
             GL_SAMPLER_2D_ARRAY_SHADOW, 1, GL_FALSE},
            {"samplerCubeShadow", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_SAMPLER_CUBE_SHADOW, 1,
             GL_FALSE},
            {"isampler2D", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_INT_SAMPLER_2D, 1, GL_FALSE},
            {"isampler3D", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_INT_SAMPLER_3D, 1, GL_FALSE},
            {"isamplerCube", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_INT_SAMPLER_CUBE, 1, GL_FALSE},
            {"isampler2DArray", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_INT_SAMPLER_2D_ARRAY, 1,
             GL_FALSE},
            {"usampler2D", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_UNSIGNED_INT_SAMPLER_2D, 1,
             GL_FALSE},
            {"usampler3D", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_UNSIGNED_INT_SAMPLER_3D, 1,
             GL_FALSE},
            {"usamplerCube", "", "highp", "", "float(textureSize(uniformValue,0).r)", GL_UNSIGNED_INT_SAMPLER_CUBE, 1,
             GL_FALSE},
            {"usampler2DArray", "", "highp", "", "float(textureSize(uniformValue,0).r)",
             GL_UNSIGNED_INT_SAMPLER_2D_ARRAY, 1, GL_FALSE},
        };

        static const char *vertSource = "#version 300 es\n"
                                        "void main (void)\n"
                                        "{\n"
                                        "    gl_Position = vec4(0.0);\n"
                                        "}\n\0";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
        GLuint program    = glCreateProgram();

        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);

        glShaderSource(shaderVert, 1, &vertSource, DE_NULL);
        glCompileShader(shaderVert);
        expectError(GL_NO_ERROR);

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(uniformTypes); ++ndx)
        {
            tcu::ScopedLogSection(m_log, uniformTypes[ndx].declaration,
                                  std::string("Verify type of ") + uniformTypes[ndx].declaration + " variable" +
                                      uniformTypes[ndx].postDeclaration);

            // gen fragment shader

            std::ostringstream frag;
            frag << "#version 300 es\n";
            frag << uniformTypes[ndx].layout << "uniform " << uniformTypes[ndx].precision << " "
                 << uniformTypes[ndx].declaration << " uniformValue" << uniformTypes[ndx].postDeclaration << ";\n";
            frag << "layout(location = 0) out mediump vec4 fragColor;\n";
            frag << "void main (void)\n";
            frag << "{\n";
            frag << "    fragColor = vec4(" << uniformTypes[ndx].getter << ");\n";
            frag << "}\n";

            {
                std::string fragmentSource     = frag.str();
                const char *fragmentSourceCStr = fragmentSource.c_str();
                glShaderSource(shaderFrag, 1, &fragmentSourceCStr, DE_NULL);
            }

            // compile & link

            glCompileShader(shaderFrag);
            glLinkProgram(program);

            // test
            if (verifyProgramParam(m_testCtx, *this, program, GL_LINK_STATUS, GL_TRUE))
            {
                const char *uniformNames[] = {"uniformValue"};
                StateQueryMemoryWriteGuard<GLuint> uniformIndex;
                glGetUniformIndices(program, 1, uniformNames, &uniformIndex);
                uniformIndex.verifyValidity(m_testCtx);

                verifyActiveUniformParam(m_testCtx, *this, program, uniformIndex, GL_UNIFORM_TYPE,
                                         uniformTypes[ndx].type);
                verifyActiveUniformParam(m_testCtx, *this, program, uniformIndex, GL_UNIFORM_SIZE,
                                         uniformTypes[ndx].size);
                verifyActiveUniformParam(m_testCtx, *this, program, uniformIndex, GL_UNIFORM_IS_ROW_MAJOR,
                                         uniformTypes[ndx].isRowMajor);
            }
        }

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class ProgramActiveUniformBlocksCase : public ApiCase
{
public:
    ProgramActiveUniformBlocksCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        static const char *testVertSource =
            "#version 300 es\n"
            "uniform longlongUniformBlockName {highp vec2 vector2;} longlongUniformInstanceName;\n"
            "uniform shortUniformBlockName {highp vec2 vector2;highp vec4 vector4;} shortUniformInstanceName;\n"
            "void main (void)\n"
            "{\n"
            "    gl_Position = shortUniformInstanceName.vector4 + vec4(longlongUniformInstanceName.vector2.x) + "
            "vec4(shortUniformInstanceName.vector2.x);\n"
            "}\n\0";
        static const char *testFragSource =
            "#version 300 es\n"
            "uniform longlongUniformBlockName {highp vec2 vector2;} longlongUniformInstanceName;\n"
            "layout(location = 0) out mediump vec4 fragColor;"
            "void main (void)\n"
            "{\n"
            "    fragColor = vec4(longlongUniformInstanceName.vector2.y);\n"
            "}\n\0";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        expectError(GL_NO_ERROR);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);
        verifyProgramParam(m_testCtx, *this, program, GL_LINK_STATUS, GL_TRUE);

        verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORM_BLOCKS, 2);
        verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_UNIFORM_BLOCK_MAX_NAME_LENGTH,
                           (GLint)std::string("longlongUniformBlockName").length() + 1); // including a null terminator
        expectError(GL_NO_ERROR);

        GLint longlongUniformBlockIndex = glGetUniformBlockIndex(program, "longlongUniformBlockName");
        GLint shortUniformBlockIndex    = glGetUniformBlockIndex(program, "shortUniformBlockName");

        const char *uniformNames[] = {"longlongUniformBlockName.vector2", "shortUniformBlockName.vector2",
                                      "shortUniformBlockName.vector4"};

        // test UNIFORM_BLOCK_INDEX

        DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(uniformNames) == 3);

        StateQueryMemoryWriteGuard<GLuint[DE_LENGTH_OF_ARRAY(uniformNames)]> uniformIndices;
        StateQueryMemoryWriteGuard<GLint[DE_LENGTH_OF_ARRAY(uniformNames)]> uniformsBlockIndices;

        glGetUniformIndices(program, DE_LENGTH_OF_ARRAY(uniformNames), uniformNames, uniformIndices);
        uniformIndices.verifyValidity(m_testCtx);
        expectError(GL_NO_ERROR);

        glGetActiveUniformsiv(program, DE_LENGTH_OF_ARRAY(uniformNames), uniformIndices, GL_UNIFORM_BLOCK_INDEX,
                              uniformsBlockIndices);
        uniformsBlockIndices.verifyValidity(m_testCtx);
        expectError(GL_NO_ERROR);

        if (uniformsBlockIndices[0] != longlongUniformBlockIndex || uniformsBlockIndices[1] != shortUniformBlockIndex ||
            uniformsBlockIndices[2] != shortUniformBlockIndex)
        {
            m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected [" << longlongUniformBlockIndex << ", "
                               << shortUniformBlockIndex << ", " << shortUniformBlockIndex << "];"
                               << "got [" << uniformsBlockIndices[0] << ", " << uniformsBlockIndices[1] << ", "
                               << uniformsBlockIndices[2] << "]" << TestLog::EndMessage;
            if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform block index");
        }

        // test UNIFORM_BLOCK_NAME_LENGTH

        verifyActiveUniformBlockParam(
            m_testCtx, *this, program, longlongUniformBlockIndex, GL_UNIFORM_BLOCK_NAME_LENGTH,
            (GLint)std::string("longlongUniformBlockName").length() + 1); // including null-terminator
        verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex, GL_UNIFORM_BLOCK_NAME_LENGTH,
                                      (GLint)std::string("shortUniformBlockName").length() +
                                          1); // including null-terminator
        expectError(GL_NO_ERROR);

        // test UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER & UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER

        verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,
                                      GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER, GL_TRUE);
        verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,
                                      GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, GL_TRUE);
        verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,
                                      GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER, GL_TRUE);
        verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,
                                      GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER, GL_FALSE);
        expectError(GL_NO_ERROR);

        // test UNIFORM_BLOCK_ACTIVE_UNIFORMS

        verifyActiveUniformBlockParam(m_testCtx, *this, program, longlongUniformBlockIndex,
                                      GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, 1);
        verifyActiveUniformBlockParam(m_testCtx, *this, program, shortUniformBlockIndex,
                                      GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, 2);
        expectError(GL_NO_ERROR);

        // test UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES

        {
            StateQueryMemoryWriteGuard<GLint> longlongUniformBlockUniforms;
            glGetActiveUniformBlockiv(program, longlongUniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS,
                                      &longlongUniformBlockUniforms);
            longlongUniformBlockUniforms.verifyValidity(m_testCtx);

            if (longlongUniformBlockUniforms == 2)
            {
                StateQueryMemoryWriteGuard<GLint[2]> longlongUniformBlockUniformIndices;
                glGetActiveUniformBlockiv(program, longlongUniformBlockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES,
                                          longlongUniformBlockUniformIndices);
                longlongUniformBlockUniformIndices.verifyValidity(m_testCtx);

                if ((GLuint(longlongUniformBlockUniformIndices[0]) != uniformIndices[0] ||
                     GLuint(longlongUniformBlockUniformIndices[1]) != uniformIndices[1]) &&
                    (GLuint(longlongUniformBlockUniformIndices[1]) != uniformIndices[0] ||
                     GLuint(longlongUniformBlockUniformIndices[0]) != uniformIndices[1]))
                {
                    m_testCtx.getLog() << TestLog::Message << "// ERROR: Expected {" << uniformIndices[0] << ", "
                                       << uniformIndices[1] << "};"
                                       << "got {" << longlongUniformBlockUniformIndices[0] << ", "
                                       << longlongUniformBlockUniformIndices[1] << "}" << TestLog::EndMessage;

                    if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got wrong uniform indices");
                }
            }
        }

        // check block names

        {
            char buffer[2048] = {'x'};
            GLint written     = 0;
            glGetActiveUniformBlockName(program, longlongUniformBlockIndex, DE_LENGTH_OF_ARRAY(buffer), &written,
                                        buffer);
            checkIntEquals(m_testCtx, written, (GLint)std::string("longlongUniformBlockName").length());

            written = 0;
            glGetActiveUniformBlockName(program, shortUniformBlockIndex, DE_LENGTH_OF_ARRAY(buffer), &written, buffer);
            checkIntEquals(m_testCtx, written, (GLint)std::string("shortUniformBlockName").length());

            // and one with too small buffer
            written = 0;
            glGetActiveUniformBlockName(program, longlongUniformBlockIndex, 1, &written, buffer);
            checkIntEquals(m_testCtx, written, 0);
        }

        expectError(GL_NO_ERROR);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class ProgramBinaryCase : public ApiCase
{
public:
    ProgramBinaryCase(Context &context, const char *name, const char *description) : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &commonTestVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &commonTestFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        expectError(GL_NO_ERROR);

        // test PROGRAM_BINARY_RETRIEVABLE_HINT
        verifyProgramParam(m_testCtx, *this, program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_FALSE);

        glProgramParameteri(program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
        expectError(GL_NO_ERROR);

        glLinkProgram(program);
        expectError(GL_NO_ERROR);

        verifyProgramParam(m_testCtx, *this, program, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);

        // test PROGRAM_BINARY_LENGTH does something

        StateQueryMemoryWriteGuard<GLint> programLength;
        glGetProgramiv(program, GL_PROGRAM_BINARY_LENGTH, &programLength);
        expectError(GL_NO_ERROR);
        programLength.verifyValidity(m_testCtx);

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class TransformFeedbackCase : public ApiCase
{
public:
    TransformFeedbackCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        static const char *transformFeedbackTestVertSource = "#version 300 es\n"
                                                             "out highp vec4 tfOutput2withLongName;\n"
                                                             "void main (void)\n"
                                                             "{\n"
                                                             "    gl_Position = vec4(0.0);\n"
                                                             "    tfOutput2withLongName = vec4(0.0);\n"
                                                             "}\n";
        static const char *transformFeedbackTestFragSource = "#version 300 es\n"
                                                             "layout(location = 0) out highp vec4 fragColor;\n"
                                                             "void main (void)\n"
                                                             "{\n"
                                                             "    fragColor = vec4(0.0);\n"
                                                             "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);
        GLuint shaderProg = glCreateProgram();

        verifyProgramParam(m_testCtx, *this, shaderProg, GL_TRANSFORM_FEEDBACK_BUFFER_MODE, GL_INTERLEAVED_ATTRIBS);

        glShaderSource(shaderVert, 1, &transformFeedbackTestVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &transformFeedbackTestFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);

        verifyShaderParam(m_testCtx, *this, shaderVert, GL_COMPILE_STATUS, GL_TRUE);
        verifyShaderParam(m_testCtx, *this, shaderFrag, GL_COMPILE_STATUS, GL_TRUE);

        glAttachShader(shaderProg, shaderVert);
        glAttachShader(shaderProg, shaderFrag);

        // check TRANSFORM_FEEDBACK_BUFFER_MODE

        const char *transform_feedback_outputs[] = {"gl_Position", "tfOutput2withLongName"};
        const char *longest_output               = transform_feedback_outputs[1];
        const GLenum bufferModes[]               = {GL_SEPARATE_ATTRIBS, GL_INTERLEAVED_ATTRIBS};

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(bufferModes); ++ndx)
        {
            glTransformFeedbackVaryings(shaderProg, DE_LENGTH_OF_ARRAY(transform_feedback_outputs),
                                        transform_feedback_outputs, bufferModes[ndx]);
            glLinkProgram(shaderProg);
            expectError(GL_NO_ERROR);

            verifyProgramParam(m_testCtx, *this, shaderProg, GL_LINK_STATUS, GL_TRUE);
            verifyProgramParam(m_testCtx, *this, shaderProg, GL_TRANSFORM_FEEDBACK_BUFFER_MODE, bufferModes[ndx]);
        }

        // TRANSFORM_FEEDBACK_VARYINGS
        verifyProgramParam(m_testCtx, *this, shaderProg, GL_TRANSFORM_FEEDBACK_VARYINGS, 2);

        // TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH
        {
            StateQueryMemoryWriteGuard<GLint> maxOutputLen;
            glGetProgramiv(shaderProg, GL_TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH, &maxOutputLen);

            maxOutputLen.verifyValidity(m_testCtx);

            const GLint referenceLength = (GLint)std::string(longest_output).length() + 1;
            checkIntEquals(m_testCtx, maxOutputLen, referenceLength);
        }

        // check varyings
        {
            StateQueryMemoryWriteGuard<GLint> varyings;
            glGetProgramiv(shaderProg, GL_TRANSFORM_FEEDBACK_VARYINGS, &varyings);

            if (!varyings.isUndefined())
                for (int index = 0; index < varyings; ++index)
                {
                    char buffer[2048] = {'x'};

                    GLint written = 0;
                    GLint size    = 0;
                    GLenum type   = 0;
                    glGetTransformFeedbackVarying(shaderProg, index, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type,
                                                  buffer);

                    if (written < DE_LENGTH_OF_ARRAY(buffer) && buffer[written] != '\0')
                    {
                        m_testCtx.getLog()
                            << TestLog::Message << "// ERROR: Expected null terminator" << TestLog::EndMessage;
                        if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid string terminator");
                    }

                    // check with too small buffer
                    written = 0;
                    glGetTransformFeedbackVarying(shaderProg, index, 1, &written, &size, &type, buffer);
                    if (written != 0)
                    {
                        m_testCtx.getLog()
                            << TestLog::Message << "// ERROR: Expected 0; got " << written << TestLog::EndMessage;
                        if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got invalid write length");
                    }
                }
        }

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(shaderProg);
        expectError(GL_NO_ERROR);
    }
};

class ActiveAttributesCase : public ApiCase
{
public:
    ActiveAttributesCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        using tcu::TestLog;

        static const char *testVertSource = "#version 300 es\n"
                                            "in highp vec2 longInputAttributeName;\n"
                                            "in highp vec2 shortName;\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_Position = longInputAttributeName.yxxy + shortName.xyxy;\n"
                                            "}\n\0";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n\0";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        expectError(GL_NO_ERROR);

        verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_ATTRIBUTES, 2);
        verifyProgramParam(m_testCtx, *this, program, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH,
                           (GLint)std::string("longInputAttributeName").length() + 1); // does include null-terminator

        // check names
        for (int attributeNdx = 0; attributeNdx < 2; ++attributeNdx)
        {
            char buffer[2048] = {'x'};

            GLint written = 0;
            GLint size    = 0;
            GLenum type   = 0;
            glGetActiveAttrib(program, attributeNdx, DE_LENGTH_OF_ARRAY(buffer), &written, &size, &type, buffer);
            expectError(GL_NO_ERROR);

            if (deStringBeginsWith(buffer, "longInputAttributeName"))
            {
                checkIntEquals(
                    m_testCtx, written,
                    (GLint)std::string("longInputAttributeName").length()); // does NOT include null-terminator
            }
            else if (deStringBeginsWith(buffer, "shortName"))
            {
                checkIntEquals(m_testCtx, written,
                               (GLint)std::string("shortName").length()); // does NOT include null-terminator
            }
            else
            {
                m_testCtx.getLog() << TestLog::Message << "// ERROR: Got unexpected attribute name."
                                   << TestLog::EndMessage;
                if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
                    m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "got unexpected name");
            }
        }

        // and with too short buffer
        {
            char buffer[2048] = {'x'};

            GLint written = 0;
            GLint size    = 0;
            GLenum type   = 0;

            glGetActiveAttrib(program, 0, 1, &written, &size, &type, buffer);
            expectError(GL_NO_ERROR);
            checkIntEquals(m_testCtx, written, 0);
        }

        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

struct PointerData
{
    GLint size;
    GLenum type;
    GLint stride;
    GLboolean normalized;
    const void *pointer;
};

class VertexAttributeSizeCase : public ApiCase
{
public:
    VertexAttributeSizeCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        GLfloat vertexData[4] = {0.0f}; // never accessed

        const PointerData pointers[] = {
            // size test
            {4, GL_FLOAT, 0, GL_FALSE, vertexData}, {3, GL_FLOAT, 0, GL_FALSE, vertexData},
            {2, GL_FLOAT, 0, GL_FALSE, vertexData}, {1, GL_FLOAT, 0, GL_FALSE, vertexData},
            {4, GL_INT, 0, GL_FALSE, vertexData},   {3, GL_INT, 0, GL_FALSE, vertexData},
            {2, GL_INT, 0, GL_FALSE, vertexData},   {1, GL_INT, 0, GL_FALSE, vertexData},
        };

        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
            {
                glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized,
                                      pointers[ndx].stride, pointers[ndx].pointer);
                expectError(GL_NO_ERROR);

                verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[ndx].size);
            }
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint buf     = 0;
            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            glGenBuffers(1, &buf);
            glBindBuffer(GL_ARRAY_BUFFER, buf);
            expectError(GL_NO_ERROR);

            // initial
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, 4);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glVertexAttribPointer(0, pointers[0].size, pointers[0].type, pointers[0].normalized, pointers[0].stride,
                                  DE_NULL);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glVertexAttribPointer(0, pointers[1].size, pointers[1].type, pointers[1].normalized, pointers[1].stride,
                                  DE_NULL);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[1].size);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_SIZE, pointers[0].size);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(1, &buf);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeTypeCase : public ApiCase
{
public:
    VertexAttributeTypeCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            const GLfloat vertexData[4] = {0.0f}; // never accessed

            // test VertexAttribPointer
            {
                const PointerData pointers[] = {
                    {1, GL_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_INT, 0, GL_FALSE, vertexData},
                    {1, GL_FIXED, 0, GL_FALSE, vertexData},
                    {1, GL_FLOAT, 0, GL_FALSE, vertexData},
                    {1, GL_HALF_FLOAT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_FALSE, vertexData},
                    {4, GL_INT_2_10_10_10_REV, 0, GL_FALSE, vertexData},
                    {4, GL_UNSIGNED_INT_2_10_10_10_REV, 0, GL_FALSE, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized,
                                          pointers[ndx].stride, pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, pointers[ndx].type);
                }
            }

            // test glVertexAttribIPointer
            {
                const PointerData pointers[] = {
                    {1, GL_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_INT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_FALSE, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride,
                                           pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, pointers[ndx].type);
                }
            }
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint buf     = 0;
            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            glGenBuffers(1, &buf);
            glBindBuffer(GL_ARRAY_BUFFER, buf);
            expectError(GL_NO_ERROR);

            // initial
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_FLOAT);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glVertexAttribPointer(0, 1, GL_SHORT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_SHORT);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_TYPE, GL_FLOAT);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(1, &buf);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeStrideCase : public ApiCase
{
public:
    VertexAttributeStrideCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            const GLfloat vertexData[4] = {0.0f}; // never accessed

            struct StridePointerData
            {
                GLint size;
                GLenum type;
                GLint stride;
                const void *pointer;
            };

            // test VertexAttribPointer
            {
                const StridePointerData pointers[] = {
                    {1, GL_FLOAT, 0, vertexData},      {1, GL_FLOAT, 1, vertexData},
                    {1, GL_FLOAT, 4, vertexData},      {1, GL_HALF_FLOAT, 0, vertexData},
                    {1, GL_HALF_FLOAT, 1, vertexData}, {1, GL_HALF_FLOAT, 4, vertexData},
                    {1, GL_FIXED, 0, vertexData},      {1, GL_FIXED, 1, vertexData},
                    {1, GL_FIXED, 4, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, GL_FALSE, pointers[ndx].stride,
                                          pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, pointers[ndx].stride);
                }
            }

            // test glVertexAttribIPointer
            {
                const StridePointerData pointers[] = {
                    {1, GL_INT, 0, vertexData},           {1, GL_INT, 1, vertexData},
                    {1, GL_INT, 4, vertexData},           {4, GL_UNSIGNED_BYTE, 0, vertexData},
                    {4, GL_UNSIGNED_BYTE, 1, vertexData}, {4, GL_UNSIGNED_BYTE, 4, vertexData},
                    {2, GL_SHORT, 0, vertexData},         {2, GL_SHORT, 1, vertexData},
                    {2, GL_SHORT, 4, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride,
                                           pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, pointers[ndx].stride);
                }
            }
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint buf     = 0;
            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            glGenBuffers(1, &buf);
            glBindBuffer(GL_ARRAY_BUFFER, buf);
            expectError(GL_NO_ERROR);

            // initial
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, 0);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 4, DE_NULL);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glVertexAttribPointer(0, 1, GL_SHORT, GL_FALSE, 8, DE_NULL);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, 8);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_STRIDE, 4);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(1, &buf);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeNormalizedCase : public ApiCase
{
public:
    VertexAttributeNormalizedCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            const GLfloat vertexData[4] = {0.0f}; // never accessed

            // test VertexAttribPointer
            {
                const PointerData pointers[] = {
                    {1, GL_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_INT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_FALSE, vertexData},
                    {4, GL_INT_2_10_10_10_REV, 0, GL_FALSE, vertexData},
                    {4, GL_UNSIGNED_INT_2_10_10_10_REV, 0, GL_FALSE, vertexData},
                    {1, GL_BYTE, 0, GL_TRUE, vertexData},
                    {1, GL_SHORT, 0, GL_TRUE, vertexData},
                    {1, GL_INT, 0, GL_TRUE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_TRUE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_TRUE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_TRUE, vertexData},
                    {4, GL_INT_2_10_10_10_REV, 0, GL_TRUE, vertexData},
                    {4, GL_UNSIGNED_INT_2_10_10_10_REV, 0, GL_TRUE, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized,
                                          pointers[ndx].stride, pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED,
                                       pointers[ndx].normalized);
                }
            }

            // test glVertexAttribIPointer
            {
                const PointerData pointers[] = {
                    {1, GL_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_INT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_FALSE, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride,
                                           pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_FALSE);
                }
            }
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint buf     = 0;
            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            glGenBuffers(1, &buf);
            glBindBuffer(GL_ARRAY_BUFFER, buf);
            expectError(GL_NO_ERROR);

            // initial
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_FALSE);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glVertexAttribPointer(0, 1, GL_INT, GL_TRUE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glVertexAttribPointer(0, 1, GL_INT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_FALSE);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_NORMALIZED, GL_TRUE);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(1, &buf);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeIntegerCase : public ApiCase
{
public:
    VertexAttributeIntegerCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            const GLfloat vertexData[4] = {0.0f}; // never accessed

            // test VertexAttribPointer
            {
                const PointerData pointers[] = {
                    {1, GL_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_INT, 0, GL_FALSE, vertexData},
                    {1, GL_FIXED, 0, GL_FALSE, vertexData},
                    {1, GL_FLOAT, 0, GL_FALSE, vertexData},
                    {1, GL_HALF_FLOAT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_FALSE, vertexData},
                    {4, GL_INT_2_10_10_10_REV, 0, GL_FALSE, vertexData},
                    {4, GL_UNSIGNED_INT_2_10_10_10_REV, 0, GL_FALSE, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized,
                                          pointers[ndx].stride, pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_FALSE);
                }
            }

            // test glVertexAttribIPointer
            {
                const PointerData pointers[] = {
                    {1, GL_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_INT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_BYTE, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_SHORT, 0, GL_FALSE, vertexData},
                    {1, GL_UNSIGNED_INT, 0, GL_FALSE, vertexData},
                };

                for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
                {
                    glVertexAttribIPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].stride,
                                           pointers[ndx].pointer);
                    expectError(GL_NO_ERROR);

                    verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_TRUE);
                }
            }
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint buf     = 0;
            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            glGenBuffers(1, &buf);
            glBindBuffer(GL_ARRAY_BUFFER, buf);
            expectError(GL_NO_ERROR);

            // initial
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_FALSE);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glVertexAttribIPointer(0, 1, GL_INT, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_FALSE);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_INTEGER, GL_TRUE);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(1, &buf);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeEnabledCase : public ApiCase
{
public:
    VertexAttributeEnabledCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        // VERTEX_ATTRIB_ARRAY_ENABLED

        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
            glEnableVertexAttribArray(0);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_TRUE);
            glDisableVertexAttribArray(0);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glBindVertexArray(vaos[0]);
            glEnableVertexAttribArray(0);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glDisableVertexAttribArray(0);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_FALSE);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_ENABLED, GL_TRUE);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeDivisorCase : public ApiCase
{
public:
    VertexAttributeDivisorCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 0);
            glVertexAttribDivisor(0, 1);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 1);
            glVertexAttribDivisor(0, 5);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 5);
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint vaos[2] = {0};

            glGenVertexArrays(2, vaos);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glBindVertexArray(vaos[0]);
            glVertexAttribDivisor(0, 1);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glVertexAttribDivisor(0, 5);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 5);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_DIVISOR, 1);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributeBufferBindingCase : public ApiCase
{
public:
    VertexAttributeBufferBindingCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            // initial
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, 0);

            GLuint bufferID;
            glGenBuffers(1, &bufferID);
            glBindBuffer(GL_ARRAY_BUFFER, bufferID);
            expectError(GL_NO_ERROR);

            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufferID);

            glDeleteBuffers(1, &bufferID);
            expectError(GL_NO_ERROR);
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint vaos[2] = {0};
            GLuint bufs[2] = {0};

            glGenBuffers(2, bufs);
            expectError(GL_NO_ERROR);

            glGenVertexArrays(2, vaos);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glBindVertexArray(vaos[0]);
            glBindBuffer(GL_ARRAY_BUFFER, bufs[0]);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glBindBuffer(GL_ARRAY_BUFFER, bufs[1]);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufs[1]);
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            verifyVertexAttrib(m_testCtx, *this, 0, GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING, bufs[0]);
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(2, bufs);
            expectError(GL_NO_ERROR);
        }
    }
};

class VertexAttributePointerCase : public ApiCase
{
public:
    VertexAttributePointerCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        const glu::ContextType &contextType = m_context.getRenderContext().getType();
        const bool isCoreGL45               = glu::contextSupports(contextType, glu::ApiType::core(4, 5));

        // Test with default VAO
        if (!isCoreGL45)
        {
            const tcu::ScopedLogSection section(m_log, "DefaultVAO", "Test with default VAO");

            StateQueryMemoryWriteGuard<GLvoid *> initialState;
            glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &initialState);
            initialState.verifyValidity(m_testCtx);
            checkPointerEquals(m_testCtx, initialState, 0);

            const GLfloat vertexData[4]  = {0.0f}; // never accessed
            const PointerData pointers[] = {
                {1, GL_BYTE, 0, GL_FALSE, &vertexData[2]},       {1, GL_SHORT, 0, GL_FALSE, &vertexData[1]},
                {1, GL_INT, 0, GL_FALSE, &vertexData[2]},        {1, GL_FIXED, 0, GL_FALSE, &vertexData[2]},
                {1, GL_FIXED, 0, GL_FALSE, &vertexData[1]},      {1, GL_FLOAT, 0, GL_FALSE, &vertexData[0]},
                {1, GL_FLOAT, 0, GL_FALSE, &vertexData[3]},      {1, GL_FLOAT, 0, GL_FALSE, &vertexData[2]},
                {1, GL_HALF_FLOAT, 0, GL_FALSE, &vertexData[0]}, {4, GL_HALF_FLOAT, 0, GL_FALSE, &vertexData[1]},
                {4, GL_HALF_FLOAT, 0, GL_FALSE, &vertexData[2]},
            };

            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(pointers); ++ndx)
            {
                glVertexAttribPointer(0, pointers[ndx].size, pointers[ndx].type, pointers[ndx].normalized,
                                      pointers[ndx].stride, pointers[ndx].pointer);
                expectError(GL_NO_ERROR);

                StateQueryMemoryWriteGuard<GLvoid *> state;
                glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
                state.verifyValidity(m_testCtx);
                checkPointerEquals(m_testCtx, state, pointers[ndx].pointer);
            }
        }

        // Test with multiple VAOs
        {
            const tcu::ScopedLogSection section(m_log, "WithVAO", "Test with VAO");

            GLuint vaos[2] = {0};
            GLuint bufs[2] = {0};

            glGenBuffers(2, bufs);
            expectError(GL_NO_ERROR);

            glGenVertexArrays(2, vaos);
            expectError(GL_NO_ERROR);

            // set vao 0 to some value
            glBindVertexArray(vaos[0]);
            glBindBuffer(GL_ARRAY_BUFFER, bufs[0]);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, glu::BufferOffsetAsPointer(8));
            expectError(GL_NO_ERROR);

            // set vao 1 to some other value
            glBindVertexArray(vaos[1]);
            glBindBuffer(GL_ARRAY_BUFFER, bufs[1]);
            glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, glu::BufferOffsetAsPointer(4));
            expectError(GL_NO_ERROR);

            // verify vao 1 state
            {
                StateQueryMemoryWriteGuard<GLvoid *> state;
                glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
                state.verifyValidity(m_testCtx);
                checkPointerEquals(m_testCtx, state, glu::BufferOffsetAsPointer(4));
            }
            expectError(GL_NO_ERROR);

            // verify vao 0 state
            glBindVertexArray(vaos[0]);
            {
                StateQueryMemoryWriteGuard<GLvoid *> state;
                glGetVertexAttribPointerv(0, GL_VERTEX_ATTRIB_ARRAY_POINTER, &state);
                state.verifyValidity(m_testCtx);
                checkPointerEquals(m_testCtx, state, glu::BufferOffsetAsPointer(8));
            }
            expectError(GL_NO_ERROR);

            glDeleteVertexArrays(2, vaos);
            glDeleteBuffers(2, bufs);
            expectError(GL_NO_ERROR);
        }
    }
};

class UniformValueFloatCase : public ApiCase
{
public:
    UniformValueFloatCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource =
            "#version 300 es\n"
            "uniform highp float floatUniform;\n"
            "uniform highp vec2 float2Uniform;\n"
            "uniform highp vec3 float3Uniform;\n"
            "uniform highp vec4 float4Uniform;\n"
            "void main (void)\n"
            "{\n"
            "    gl_Position = vec4(floatUniform + float2Uniform.x + float3Uniform.x + float4Uniform.x);\n"
            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        location = glGetUniformLocation(program, "floatUniform");
        glUniform1f(location, 1.0f);
        verifyUniformValue1f(m_testCtx, *this, program, location, 1.0f);

        location = glGetUniformLocation(program, "float2Uniform");
        glUniform2f(location, 1.0f, 2.0f);
        verifyUniformValue2f(m_testCtx, *this, program, location, 1.0f, 2.0f);

        location = glGetUniformLocation(program, "float3Uniform");
        glUniform3f(location, 1.0f, 2.0f, 3.0f);
        verifyUniformValue3f(m_testCtx, *this, program, location, 1.0f, 2.0f, 3.0f);

        location = glGetUniformLocation(program, "float4Uniform");
        glUniform4f(location, 1.0f, 2.0f, 3.0f, 4.0f);
        verifyUniformValue4f(m_testCtx, *this, program, location, 1.0f, 2.0f, 3.0f, 4.0f);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class UniformValueIntCase : public ApiCase
{
public:
    UniformValueIntCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource =
            "#version 300 es\n"
            "uniform highp int intUniform;\n"
            "uniform highp ivec2 int2Uniform;\n"
            "uniform highp ivec3 int3Uniform;\n"
            "uniform highp ivec4 int4Uniform;\n"
            "void main (void)\n"
            "{\n"
            "    gl_Position = vec4(float(intUniform + int2Uniform.x + int3Uniform.x + int4Uniform.x));\n"
            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        location = glGetUniformLocation(program, "intUniform");
        glUniform1i(location, 1);
        verifyUniformValue1i(m_testCtx, *this, program, location, 1);

        location = glGetUniformLocation(program, "int2Uniform");
        glUniform2i(location, 1, 2);
        verifyUniformValue2i(m_testCtx, *this, program, location, 1, 2);

        location = glGetUniformLocation(program, "int3Uniform");
        glUniform3i(location, 1, 2, 3);
        verifyUniformValue3i(m_testCtx, *this, program, location, 1, 2, 3);

        location = glGetUniformLocation(program, "int4Uniform");
        glUniform4i(location, 1, 2, 3, 4);
        verifyUniformValue4i(m_testCtx, *this, program, location, 1, 2, 3, 4);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class UniformValueUintCase : public ApiCase
{
public:
    UniformValueUintCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource =
            "#version 300 es\n"
            "uniform highp uint uintUniform;\n"
            "uniform highp uvec2 uint2Uniform;\n"
            "uniform highp uvec3 uint3Uniform;\n"
            "uniform highp uvec4 uint4Uniform;\n"
            "void main (void)\n"
            "{\n"
            "    gl_Position = vec4(float(uintUniform + uint2Uniform.x + uint3Uniform.x + uint4Uniform.x));\n"
            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        location = glGetUniformLocation(program, "uintUniform");
        glUniform1ui(location, 1);
        verifyUniformValue1ui(m_testCtx, *this, program, location, 1);

        location = glGetUniformLocation(program, "uint2Uniform");
        glUniform2ui(location, 1, 2);
        verifyUniformValue2ui(m_testCtx, *this, program, location, 1, 2);

        location = glGetUniformLocation(program, "uint3Uniform");
        glUniform3ui(location, 1, 2, 3);
        verifyUniformValue3ui(m_testCtx, *this, program, location, 1, 2, 3);

        location = glGetUniformLocation(program, "uint4Uniform");
        glUniform4ui(location, 1, 2, 3, 4);
        verifyUniformValue4ui(m_testCtx, *this, program, location, 1, 2, 3, 4);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class UniformValueBooleanCase : public ApiCase
{
public:
    UniformValueBooleanCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource = "#version 300 es\n"
                                            "uniform bool boolUniform;\n"
                                            "uniform bvec2 bool2Uniform;\n"
                                            "uniform bvec3 bool3Uniform;\n"
                                            "uniform bvec4 bool4Uniform;\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_Position = vec4(float(boolUniform) + float(bool2Uniform.x) + "
                                            "float(bool3Uniform.x) + float(bool4Uniform.x));\n"
                                            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        // int conversion

        location = glGetUniformLocation(program, "boolUniform");
        glUniform1i(location, 1);
        verifyUniformValue1i(m_testCtx, *this, program, location, 1);

        location = glGetUniformLocation(program, "bool2Uniform");
        glUniform2i(location, 1, 2);
        verifyUniformValue2i(m_testCtx, *this, program, location, 1, 1);

        location = glGetUniformLocation(program, "bool3Uniform");
        glUniform3i(location, 0, 1, 2);
        verifyUniformValue3i(m_testCtx, *this, program, location, 0, 1, 1);

        location = glGetUniformLocation(program, "bool4Uniform");
        glUniform4i(location, 1, 0, 1, -1);
        verifyUniformValue4i(m_testCtx, *this, program, location, 1, 0, 1, 1);

        // float conversion

        location = glGetUniformLocation(program, "boolUniform");
        glUniform1f(location, 1.0f);
        verifyUniformValue1i(m_testCtx, *this, program, location, 1);

        location = glGetUniformLocation(program, "bool2Uniform");
        glUniform2f(location, 1.0f, 0.1f);
        verifyUniformValue2i(m_testCtx, *this, program, location, 1, 1);

        location = glGetUniformLocation(program, "bool3Uniform");
        glUniform3f(location, 0.0f, 0.1f, -0.1f);
        verifyUniformValue3i(m_testCtx, *this, program, location, 0, 1, 1);

        location = glGetUniformLocation(program, "bool4Uniform");
        glUniform4f(location, 1.0f, 0.0f, 0.1f, -0.9f);
        verifyUniformValue4i(m_testCtx, *this, program, location, 1, 0, 1, 1);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class UniformValueSamplerCase : public ApiCase
{
public:
    UniformValueSamplerCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource = "#version 300 es\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_Position = vec4(0.0);\n"
                                            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "uniform highp sampler2D uniformSampler;\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(textureSize(uniformSampler, 0).x);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        location = glGetUniformLocation(program, "uniformSampler");
        glUniform1i(location, 1);
        verifyUniformValue1i(m_testCtx, *this, program, location, 1);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class UniformValueArrayCase : public ApiCase
{
public:
    UniformValueArrayCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource = "#version 300 es\n"
                                            "uniform highp float arrayUniform[5];"
                                            "uniform highp vec2 array2Uniform[5];"
                                            "uniform highp vec3 array3Uniform[5];"
                                            "uniform highp vec4 array4Uniform[5];"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_Position = \n"
                                            "        + vec4(arrayUniform[0]        + arrayUniform[1]        + "
                                            "arrayUniform[2]        + arrayUniform[3]        + arrayUniform[4])\n"
                                            "        + vec4(array2Uniform[0].x    + array2Uniform[1].x    + "
                                            "array2Uniform[2].x    + array2Uniform[3].x    + array2Uniform[4].x)\n"
                                            "        + vec4(array3Uniform[0].x    + array3Uniform[1].x    + "
                                            "array3Uniform[2].x    + array3Uniform[3].x    + array3Uniform[4].x)\n"
                                            "        + vec4(array4Uniform[0].x    + array4Uniform[1].x    + "
                                            "array4Uniform[2].x    + array4Uniform[3].x    + array4Uniform[4].x);\n"
                                            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        float uniformValue[5 * 4] = {-1.0f, 0.1f,  4.0f,  800.0f, 13.0f, 55.0f, 12.0f, 91.0f, -55.1f, 1.1f,
                                     98.0f, 19.0f, 41.0f, 65.0f,  4.0f,  12.2f, 95.0f, 77.0f, 32.0f,  48.0f};

        location = glGetUniformLocation(program, "arrayUniform");
        glUniform1fv(location, 5, uniformValue);
        expectError(GL_NO_ERROR);

        verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program, "arrayUniform[0]"),
                             uniformValue[0]);
        verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program, "arrayUniform[1]"),
                             uniformValue[1]);
        verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program, "arrayUniform[2]"),
                             uniformValue[2]);
        verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program, "arrayUniform[3]"),
                             uniformValue[3]);
        verifyUniformValue1f(m_testCtx, *this, program, glGetUniformLocation(program, "arrayUniform[4]"),
                             uniformValue[4]);
        expectError(GL_NO_ERROR);

        location = glGetUniformLocation(program, "array2Uniform");
        glUniform2fv(location, 5, uniformValue);
        expectError(GL_NO_ERROR);

        verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program, "array2Uniform[0]"),
                             uniformValue[2 * 0], uniformValue[(2 * 0) + 1]);
        verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program, "array2Uniform[1]"),
                             uniformValue[2 * 1], uniformValue[(2 * 1) + 1]);
        verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program, "array2Uniform[2]"),
                             uniformValue[2 * 2], uniformValue[(2 * 2) + 1]);
        verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program, "array2Uniform[3]"),
                             uniformValue[2 * 3], uniformValue[(2 * 3) + 1]);
        verifyUniformValue2f(m_testCtx, *this, program, glGetUniformLocation(program, "array2Uniform[4]"),
                             uniformValue[2 * 4], uniformValue[(2 * 4) + 1]);
        expectError(GL_NO_ERROR);

        location = glGetUniformLocation(program, "array3Uniform");
        glUniform3fv(location, 5, uniformValue);
        expectError(GL_NO_ERROR);

        verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program, "array3Uniform[0]"),
                             uniformValue[3 * 0], uniformValue[(3 * 0) + 1], uniformValue[(3 * 0) + 2]);
        verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program, "array3Uniform[1]"),
                             uniformValue[3 * 1], uniformValue[(3 * 1) + 1], uniformValue[(3 * 1) + 2]);
        verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program, "array3Uniform[2]"),
                             uniformValue[3 * 2], uniformValue[(3 * 2) + 1], uniformValue[(3 * 2) + 2]);
        verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program, "array3Uniform[3]"),
                             uniformValue[3 * 3], uniformValue[(3 * 3) + 1], uniformValue[(3 * 3) + 2]);
        verifyUniformValue3f(m_testCtx, *this, program, glGetUniformLocation(program, "array3Uniform[4]"),
                             uniformValue[3 * 4], uniformValue[(3 * 4) + 1], uniformValue[(3 * 4) + 2]);
        expectError(GL_NO_ERROR);

        location = glGetUniformLocation(program, "array4Uniform");
        glUniform4fv(location, 5, uniformValue);
        expectError(GL_NO_ERROR);

        verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program, "array4Uniform[0]"),
                             uniformValue[4 * 0], uniformValue[(4 * 0) + 1], uniformValue[(4 * 0) + 2],
                             uniformValue[(4 * 0) + 3]);
        verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program, "array4Uniform[1]"),
                             uniformValue[4 * 1], uniformValue[(4 * 1) + 1], uniformValue[(4 * 1) + 2],
                             uniformValue[(4 * 1) + 3]);
        verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program, "array4Uniform[2]"),
                             uniformValue[4 * 2], uniformValue[(4 * 2) + 1], uniformValue[(4 * 2) + 2],
                             uniformValue[(4 * 2) + 3]);
        verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program, "array4Uniform[3]"),
                             uniformValue[4 * 3], uniformValue[(4 * 3) + 1], uniformValue[(4 * 3) + 2],
                             uniformValue[(4 * 3) + 3]);
        verifyUniformValue4f(m_testCtx, *this, program, glGetUniformLocation(program, "array4Uniform[4]"),
                             uniformValue[4 * 4], uniformValue[(4 * 4) + 1], uniformValue[(4 * 4) + 2],
                             uniformValue[(4 * 4) + 3]);
        expectError(GL_NO_ERROR);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class UniformValueMatrixCase : public ApiCase
{
public:
    UniformValueMatrixCase(Context &context, const char *name, const char *description)
        : ApiCase(context, name, description)
    {
    }

    void test(void)
    {
        static const char *testVertSource =
            "#version 300 es\n"
            "uniform highp mat2 mat2Uniform;"
            "uniform highp mat3 mat3Uniform;"
            "uniform highp mat4 mat4Uniform;"
            "void main (void)\n"
            "{\n"
            "    gl_Position = vec4(mat2Uniform[0][0] + mat3Uniform[0][0] + mat4Uniform[0][0]);\n"
            "}\n";
        static const char *testFragSource = "#version 300 es\n"
                                            "layout(location = 0) out mediump vec4 fragColor;"
                                            "void main (void)\n"
                                            "{\n"
                                            "    fragColor = vec4(0.0);\n"
                                            "}\n";

        GLuint shaderVert = glCreateShader(GL_VERTEX_SHADER);
        GLuint shaderFrag = glCreateShader(GL_FRAGMENT_SHADER);

        glShaderSource(shaderVert, 1, &testVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &testFragSource, DE_NULL);

        glCompileShader(shaderVert);
        glCompileShader(shaderFrag);
        expectError(GL_NO_ERROR);

        GLuint program = glCreateProgram();
        glAttachShader(program, shaderVert);
        glAttachShader(program, shaderFrag);
        glLinkProgram(program);
        glUseProgram(program);
        expectError(GL_NO_ERROR);

        GLint location;

        float matrixValues[4 * 4] = {
            -1.0f,  0.1f, 4.0f,  800.0f, 13.0f, 55.0f, 12.0f, 91.0f,
            -55.1f, 1.1f, 98.0f, 19.0f,  41.0f, 65.0f, 4.0f,  12.2f,
        };

        // the values of the matrix are returned in column major order but they can be given in either order

        location = glGetUniformLocation(program, "mat2Uniform");
        glUniformMatrix2fv(location, 1, GL_FALSE, matrixValues);
        verifyUniformMatrixValues<2>(m_testCtx, *this, program, location, matrixValues, false);
        glUniformMatrix2fv(location, 1, GL_TRUE, matrixValues);
        verifyUniformMatrixValues<2>(m_testCtx, *this, program, location, matrixValues, true);

        location = glGetUniformLocation(program, "mat3Uniform");
        glUniformMatrix3fv(location, 1, GL_FALSE, matrixValues);
        verifyUniformMatrixValues<3>(m_testCtx, *this, program, location, matrixValues, false);
        glUniformMatrix3fv(location, 1, GL_TRUE, matrixValues);
        verifyUniformMatrixValues<3>(m_testCtx, *this, program, location, matrixValues, true);

        location = glGetUniformLocation(program, "mat4Uniform");
        glUniformMatrix4fv(location, 1, GL_FALSE, matrixValues);
        verifyUniformMatrixValues<4>(m_testCtx, *this, program, location, matrixValues, false);
        glUniformMatrix4fv(location, 1, GL_TRUE, matrixValues);
        verifyUniformMatrixValues<4>(m_testCtx, *this, program, location, matrixValues, true);

        glUseProgram(0);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(program);
        expectError(GL_NO_ERROR);
    }
};

class PrecisionFormatCase : public ApiCase
{
public:
    struct RequiredFormat
    {
        int negativeRange;
        int positiveRange;
        int precision;
    };

    PrecisionFormatCase(Context &context, const char *name, const char *description, glw::GLenum shaderType,
                        glw::GLenum precisionType)
        : ApiCase(context, name, description)
        , m_shaderType(shaderType)
        , m_precisionType(precisionType)
    {
    }

private:
    void test(void)
    {
        const RequiredFormat expected = getRequiredFormat();
        bool error                    = false;
        gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLboolean> shaderCompiler;
        gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint[2]> range;
        gls::StateQueryUtil::StateQueryMemoryWriteGuard<glw::GLint> precision;

        // query values
        glGetShaderPrecisionFormat(m_shaderType, m_precisionType, range, &precision);
        expectError(GL_NO_ERROR);

        if (!range.verifyValidity(m_testCtx))
            return;
        if (!precision.verifyValidity(m_testCtx))
            return;

        m_log << tcu::TestLog::Message << "range[0] = " << range[0] << "\n"
              << "range[1] = " << range[1] << "\n"
              << "precision = " << precision << tcu::TestLog::EndMessage;

        // verify values

        if (m_precisionType == GL_HIGH_FLOAT)
        {
            // highp float must be IEEE 754 single

            if (range[0] != expected.negativeRange || range[1] != expected.positiveRange ||
                precision != expected.precision)
            {
                m_log << tcu::TestLog::Message << "// ERROR: Invalid precision format, expected:\n"
                      << "\trange[0] = " << expected.negativeRange << "\n"
                      << "\trange[1] = " << expected.positiveRange << "\n"
                      << "\tprecision = " << expected.precision << tcu::TestLog::EndMessage;
                error = true;
            }
        }
        else
        {
            if (range[0] < expected.negativeRange)
            {
                m_log << tcu::TestLog::Message << "// ERROR: Invalid range[0], expected greater or equal to "
                      << expected.negativeRange << tcu::TestLog::EndMessage;
                error = true;
            }

            if (range[1] < expected.positiveRange)
            {
                m_log << tcu::TestLog::Message << "// ERROR: Invalid range[1], expected greater or equal to "
                      << expected.positiveRange << tcu::TestLog::EndMessage;
                error = true;
            }

            if (precision < expected.precision)
            {
                m_log << tcu::TestLog::Message << "// ERROR: Invalid precision, expected greater or equal to "
                      << expected.precision << tcu::TestLog::EndMessage;
                error = true;
            }
        }

        if (error)
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got invalid precision/range");
    }

    RequiredFormat getRequiredFormat(void) const
    {
        // Precisions for different types.
        const RequiredFormat requirements[] = {
            {0, 0, 8},      //!< lowp float
            {13, 13, 10},   //!< mediump float
            {127, 127, 23}, //!< highp float
            {8, 7, 0},      //!< lowp int
            {15, 14, 0},    //!< mediump int
            {31, 30, 0},    //!< highp int
        };
        const int ndx = (int)m_precisionType - (int)GL_LOW_FLOAT;

        DE_ASSERT(ndx >= 0);
        DE_ASSERT(ndx < DE_LENGTH_OF_ARRAY(requirements));
        return requirements[ndx];
    }

    const glw::GLenum m_shaderType;
    const glw::GLenum m_precisionType;
};

} // namespace

ShaderStateQueryTests::ShaderStateQueryTests(Context &context)
    : TestCaseGroup(context, "shader", "Shader State Query tests")
{
}

void ShaderStateQueryTests::init(void)
{
    // shader
    addChild(new ShaderTypeCase(m_context, "shader_type", "SHADER_TYPE"));
    addChild(new ShaderCompileStatusCase(m_context, "shader_compile_status", "COMPILE_STATUS"));
    addChild(new ShaderInfoLogCase(m_context, "shader_info_log_length", "INFO_LOG_LENGTH"));
    addChild(new ShaderSourceCase(m_context, "shader_source_length", "SHADER_SOURCE_LENGTH"));

    // shader and program
    addChild(new DeleteStatusCase(m_context, "delete_status", "DELETE_STATUS"));

    // vertex-attrib
    addChild(new CurrentVertexAttribInitialCase(m_context, "current_vertex_attrib_initial", "CURRENT_VERTEX_ATTRIB"));
    addChild(new CurrentVertexAttribFloatCase(m_context, "current_vertex_attrib_float", "CURRENT_VERTEX_ATTRIB"));
    addChild(new CurrentVertexAttribIntCase(m_context, "current_vertex_attrib_int", "CURRENT_VERTEX_ATTRIB"));
    addChild(new CurrentVertexAttribUintCase(m_context, "current_vertex_attrib_uint", "CURRENT_VERTEX_ATTRIB"));
    addChild(new CurrentVertexAttribConversionCase(m_context, "current_vertex_attrib_float_to_int",
                                                   "CURRENT_VERTEX_ATTRIB"));

    // program
    addChild(new ProgramInfoLogCase(m_context, "program_info_log_length", "INFO_LOG_LENGTH",
                                    ProgramInfoLogCase::BUILDERROR_COMPILE));
    addChild(new ProgramInfoLogCase(m_context, "program_info_log_length_link_error", "INFO_LOG_LENGTH",
                                    ProgramInfoLogCase::BUILDERROR_LINK));
    addChild(new ProgramValidateStatusCase(m_context, "program_validate_status", "VALIDATE_STATUS"));
    addChild(new ProgramAttachedShadersCase(m_context, "program_attached_shaders", "ATTACHED_SHADERS"));

    addChild(new ProgramActiveUniformNameCase(m_context, "program_active_uniform_name",
                                              "ACTIVE_UNIFORMS and ACTIVE_UNIFORM_MAX_LENGTH"));
    addChild(new ProgramUniformCase(m_context, "program_active_uniform_types",
                                    "UNIFORM_TYPE, UNIFORM_SIZE, and UNIFORM_IS_ROW_MAJOR"));
    addChild(new ProgramActiveUniformBlocksCase(m_context, "program_active_uniform_blocks", "ACTIVE_UNIFORM_BLOCK_x"));
    addChild(new ProgramBinaryCase(m_context, "program_binary",
                                   "PROGRAM_BINARY_LENGTH and PROGRAM_BINARY_RETRIEVABLE_HINT"));

    // transform feedback
    addChild(new TransformFeedbackCase(
        m_context, "transform_feedback",
        "TRANSFORM_FEEDBACK_BUFFER_MODE, TRANSFORM_FEEDBACK_VARYINGS, TRANSFORM_FEEDBACK_VARYING_MAX_LENGTH"));

    // attribute related
    addChild(
        new ActiveAttributesCase(m_context, "active_attributes", "ACTIVE_ATTRIBUTES and ACTIVE_ATTRIBUTE_MAX_LENGTH"));
    addChild(new VertexAttributeSizeCase(m_context, "vertex_attrib_size", "VERTEX_ATTRIB_ARRAY_SIZE"));
    addChild(new VertexAttributeTypeCase(m_context, "vertex_attrib_type", "VERTEX_ATTRIB_ARRAY_TYPE"));
    addChild(new VertexAttributeStrideCase(m_context, "vertex_attrib_stride", "VERTEX_ATTRIB_ARRAY_STRIDE"));
    addChild(
        new VertexAttributeNormalizedCase(m_context, "vertex_attrib_normalized", "VERTEX_ATTRIB_ARRAY_NORMALIZED"));
    addChild(new VertexAttributeIntegerCase(m_context, "vertex_attrib_integer", "VERTEX_ATTRIB_ARRAY_INTEGER"));
    addChild(new VertexAttributeEnabledCase(m_context, "vertex_attrib_array_enabled", "VERTEX_ATTRIB_ARRAY_ENABLED"));
    addChild(new VertexAttributeDivisorCase(m_context, "vertex_attrib_array_divisor", "VERTEX_ATTRIB_ARRAY_DIVISOR"));
    addChild(new VertexAttributeBufferBindingCase(m_context, "vertex_attrib_array_buffer_binding",
                                                  "VERTEX_ATTRIB_ARRAY_BUFFER_BINDING"));
    addChild(new VertexAttributePointerCase(m_context, "vertex_attrib_pointerv", "GetVertexAttribPointerv"));

    // uniform values
    addChild(new UniformValueFloatCase(m_context, "uniform_value_float", "GetUniform*"));
    addChild(new UniformValueIntCase(m_context, "uniform_value_int", "GetUniform*"));
    addChild(new UniformValueUintCase(m_context, "uniform_value_uint", "GetUniform*"));
    addChild(new UniformValueBooleanCase(m_context, "uniform_value_boolean", "GetUniform*"));
    addChild(new UniformValueSamplerCase(m_context, "uniform_value_sampler", "GetUniform*"));
    addChild(new UniformValueArrayCase(m_context, "uniform_value_array", "GetUniform*"));
    addChild(new UniformValueMatrixCase(m_context, "uniform_value_matrix", "GetUniform*"));

    // precision format query
    addChild(new PrecisionFormatCase(m_context, "precision_vertex_lowp_float", "GetShaderPrecisionFormat",
                                     GL_VERTEX_SHADER, GL_LOW_FLOAT));
    addChild(new PrecisionFormatCase(m_context, "precision_vertex_mediump_float", "GetShaderPrecisionFormat",
                                     GL_VERTEX_SHADER, GL_MEDIUM_FLOAT));
    addChild(new PrecisionFormatCase(m_context, "precision_vertex_highp_float", "GetShaderPrecisionFormat",
                                     GL_VERTEX_SHADER, GL_HIGH_FLOAT));
    addChild(new PrecisionFormatCase(m_context, "precision_vertex_lowp_int", "GetShaderPrecisionFormat",
                                     GL_VERTEX_SHADER, GL_LOW_INT));
    addChild(new PrecisionFormatCase(m_context, "precision_vertex_mediump_int", "GetShaderPrecisionFormat",
                                     GL_VERTEX_SHADER, GL_MEDIUM_INT));
    addChild(new PrecisionFormatCase(m_context, "precision_vertex_highp_int", "GetShaderPrecisionFormat",
                                     GL_VERTEX_SHADER, GL_HIGH_INT));
    addChild(new PrecisionFormatCase(m_context, "precision_fragment_lowp_float", "GetShaderPrecisionFormat",
                                     GL_FRAGMENT_SHADER, GL_LOW_FLOAT));
    addChild(new PrecisionFormatCase(m_context, "precision_fragment_mediump_float", "GetShaderPrecisionFormat",
                                     GL_FRAGMENT_SHADER, GL_MEDIUM_FLOAT));
    addChild(new PrecisionFormatCase(m_context, "precision_fragment_highp_float", "GetShaderPrecisionFormat",
                                     GL_FRAGMENT_SHADER, GL_HIGH_FLOAT));
    addChild(new PrecisionFormatCase(m_context, "precision_fragment_lowp_int", "GetShaderPrecisionFormat",
                                     GL_FRAGMENT_SHADER, GL_LOW_INT));
    addChild(new PrecisionFormatCase(m_context, "precision_fragment_mediump_int", "GetShaderPrecisionFormat",
                                     GL_FRAGMENT_SHADER, GL_MEDIUM_INT));
    addChild(new PrecisionFormatCase(m_context, "precision_fragment_highp_int", "GetShaderPrecisionFormat",
                                     GL_FRAGMENT_SHADER, GL_HIGH_INT));
}

} // namespace Functional
} // namespace gles3
} // namespace deqp
