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

#include "es3fIndexedStateQueryTests.hpp"
#include "es3fApiCase.hpp"
#include "glsStateQueryUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTestLog.hpp"
#include "glwEnums.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluContextInfo.hpp"
#include "deRandom.hpp"

namespace deqp
{
namespace gles3
{
namespace Functional
{
namespace
{

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

void 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");
    }
}

void checkIntEquals(tcu::TestContext &testCtx, GLint64 got, GLint64 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");
    }
}

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

    virtual void testTransformFeedback(void) = DE_NULL;

    void test(void)
    {
        static const char *transformFeedbackTestVertSource = "#version 300 es\n"
                                                             "out highp vec4 anotherOutput;\n"
                                                             "void main (void)\n"
                                                             "{\n"
                                                             "    gl_Position = vec4(0.0);\n"
                                                             "    anotherOutput = vec4(0.0);\n"
                                                             "}\n\0";
        static const char *transformFeedbackTestFragSource = "#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, &transformFeedbackTestVertSource, DE_NULL);
        glShaderSource(shaderFrag, 1, &transformFeedbackTestFragSource, DE_NULL);

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

        GLuint shaderProg = glCreateProgram();
        glAttachShader(shaderProg, shaderVert);
        glAttachShader(shaderProg, shaderFrag);

        const char *transformFeedbackOutputs[] = {"gl_Position", "anotherOutput"};

        glTransformFeedbackVaryings(shaderProg, 2, transformFeedbackOutputs, GL_INTERLEAVED_ATTRIBS);
        glLinkProgram(shaderProg);
        expectError(GL_NO_ERROR);

        glGenTransformFeedbacks(2, transformFeedbacks);
        // Also store the default transform feedback in the array.
        transformFeedbacks[2] = 0;
        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[0]);
        expectError(GL_NO_ERROR);

        testTransformFeedback();

        // cleanup

        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, 0);

        glDeleteTransformFeedbacks(2, transformFeedbacks);
        glDeleteShader(shaderVert);
        glDeleteShader(shaderFrag);
        glDeleteProgram(shaderProg);
        expectError(GL_NO_ERROR);
    }

protected:
    GLuint transformFeedbacks[3];
};

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

    void testTransformFeedback(void)
    {
        const int feedbackPositionIndex = 0;
        const int feedbackOutputIndex   = 1;
        const int feedbackIndex[2]      = {feedbackPositionIndex, feedbackOutputIndex};

        // bind bffers

        GLuint feedbackBuffers[2];
        glGenBuffers(2, feedbackBuffers);
        expectError(GL_NO_ERROR);

        for (int ndx = 0; ndx < 2; ++ndx)
        {
            glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffers[ndx]);
            glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackIndex[ndx], feedbackBuffers[ndx]);
            expectError(GL_NO_ERROR);
        }

        // test TRANSFORM_FEEDBACK_BUFFER_BINDING

        for (int ndx = 0; ndx < 2; ++ndx)
        {
            StateQueryMemoryWriteGuard<GLint> boundBuffer;
            glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, feedbackIndex[ndx], &boundBuffer);
            boundBuffer.verifyValidity(m_testCtx);
            checkIntEquals(m_testCtx, boundBuffer, feedbackBuffers[ndx]);
        }

        // cleanup

        glDeleteBuffers(2, feedbackBuffers);
    }
};

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

    void testTransformFeedback(void)
    {
        const int feedbackPositionIndex = 0;
        const int feedbackOutputIndex   = 1;

        const int rangeBufferOffset = 4;
        const int rangeBufferSize   = 8;

        // bind buffers

        GLuint feedbackBuffers[2];
        glGenBuffers(2, feedbackBuffers);
        expectError(GL_NO_ERROR);

        glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffers[0]);
        glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackPositionIndex, feedbackBuffers[0]);
        expectError(GL_NO_ERROR);

        glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackBuffers[1]);
        glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 16, NULL, GL_DYNAMIC_READ);
        glBindBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, feedbackOutputIndex, feedbackBuffers[1], rangeBufferOffset,
                          rangeBufferSize);
        expectError(GL_NO_ERROR);

        // test TRANSFORM_FEEDBACK_BUFFER_START and TRANSFORM_FEEDBACK_BUFFER_SIZE

        const struct BufferRequirements
        {
            GLint index;
            GLenum pname;
            GLint64 value;
        } requirements[] = {{feedbackPositionIndex, GL_TRANSFORM_FEEDBACK_BUFFER_START, 0},
                            {feedbackPositionIndex, GL_TRANSFORM_FEEDBACK_BUFFER_SIZE, 0},
                            {feedbackOutputIndex, GL_TRANSFORM_FEEDBACK_BUFFER_START, rangeBufferOffset},
                            {feedbackOutputIndex, GL_TRANSFORM_FEEDBACK_BUFFER_SIZE, rangeBufferSize}};

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requirements); ++ndx)
        {
            StateQueryMemoryWriteGuard<GLint64> state;
            glGetInteger64i_v(requirements[ndx].pname, requirements[ndx].index, &state);

            if (state.verifyValidity(m_testCtx))
                checkIntEquals(m_testCtx, state, requirements[ndx].value);
        }

        // cleanup

        glDeleteBuffers(2, feedbackBuffers);
    }
};

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

    void testTransformFeedback(void)
    {
        GLuint feedbackBuffers[3];
        glGenBuffers(3, feedbackBuffers);
        expectError(GL_NO_ERROR);

        for (int i = 0; i < 3; ++i)
        {
            glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[i]);
            expectError(GL_NO_ERROR);
            GLint value;
            glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, &value);
            expectError(GL_NO_ERROR);
            checkIntEquals(m_testCtx, value, 0);
            glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, feedbackBuffers[i]);
            expectError(GL_NO_ERROR);
            // glBindBufferBase should also set the generic binding point.
            glGetIntegerv(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, &value);
            expectError(GL_NO_ERROR);
            checkIntEquals(m_testCtx, value, feedbackBuffers[i]);
        }

        for (int i = 0; i < 3; ++i)
        {
            // glBindTransformFeedback should change the indexed binding points, but
            // not the generic one.
            glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[i]);
            expectError(GL_NO_ERROR);
            GLint value;
            glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, &value);
            expectError(GL_NO_ERROR);
            checkIntEquals(m_testCtx, value, feedbackBuffers[i]);
            glGetIntegerv(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, &value);
            expectError(GL_NO_ERROR);
            // Should be unchanged.
            checkIntEquals(m_testCtx, value, feedbackBuffers[2]);
        }

        glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[0]);
        expectError(GL_NO_ERROR);
        glDeleteBuffers(3, feedbackBuffers);
        expectError(GL_NO_ERROR);

        // After deleting buffers the bound state should be changed but unbound
        // state should be unchanged.

        GLint value;
        glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, &value);
        expectError(GL_NO_ERROR);
        checkIntEquals(m_testCtx, value, 0);
        glGetIntegerv(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, &value);
        expectError(GL_NO_ERROR);
        checkIntEquals(m_testCtx, value, 0);

        for (int i = 1; i < 3; ++i)
        {
            glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, transformFeedbacks[i]);
            expectError(GL_NO_ERROR);
            glGetIntegeri_v(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, 0, &value);
            expectError(GL_NO_ERROR);
            checkIntEquals(m_testCtx, value, feedbackBuffers[i]);
            glGetIntegerv(GL_TRANSFORM_FEEDBACK_BUFFER_BINDING, &value);
            expectError(GL_NO_ERROR);
            checkIntEquals(m_testCtx, value, 0);
        }
    }
};

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

    virtual void testUniformBuffers(void) = DE_NULL;

    void test(void)
    {
        static const char *testVertSource = "#version 300 es\n"
                                            "uniform highp vec4 input1;\n"
                                            "uniform highp vec4 input2;\n"
                                            "void main (void)\n"
                                            "{\n"
                                            "    gl_Position = input1 + input2;\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);

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

        testUniformBuffers();

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

protected:
    GLuint m_program;
};

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

    void testUniformBuffers(void)
    {
        const char *uniformNames[] = {"input1", "input2"};
        GLuint uniformIndices[2]   = {0};
        glGetUniformIndices(m_program, 2, uniformNames, uniformIndices);

        GLuint buffers[2];
        glGenBuffers(2, buffers);

        for (int ndx = 0; ndx < 2; ++ndx)
        {
            glBindBuffer(GL_UNIFORM_BUFFER, buffers[ndx]);
            glBufferData(GL_UNIFORM_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
            glBindBufferBase(GL_UNIFORM_BUFFER, uniformIndices[ndx], buffers[ndx]);
            expectError(GL_NO_ERROR);
        }

        for (int ndx = 0; ndx < 2; ++ndx)
        {
            StateQueryMemoryWriteGuard<GLint> boundBuffer;
            glGetIntegeri_v(GL_UNIFORM_BUFFER_BINDING, uniformIndices[ndx], &boundBuffer);

            if (boundBuffer.verifyValidity(m_testCtx))
                checkIntEquals(m_testCtx, boundBuffer, buffers[ndx]);
            expectError(GL_NO_ERROR);
        }

        glDeleteBuffers(2, buffers);
    }
};

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

    void testUniformBuffers(void)
    {
        const char *uniformNames[] = {"input1", "input2"};
        GLuint uniformIndices[2]   = {0};
        glGetUniformIndices(m_program, 2, uniformNames, uniformIndices);

        const GLint alignment = GetAlignment();
        if (alignment == -1) // cannot continue without this
            return;

        m_testCtx.getLog() << tcu::TestLog::Message << "Alignment is " << alignment << tcu::TestLog::EndMessage;

        int rangeBufferOffset    = alignment;
        int rangeBufferSize      = alignment * 2;
        int rangeBufferTotalSize = rangeBufferOffset + rangeBufferSize +
                                   8; // + 8 has no special meaning, just to make it != with the size of the range

        GLuint buffers[2];
        glGenBuffers(2, buffers);

        glBindBuffer(GL_UNIFORM_BUFFER, buffers[0]);
        glBufferData(GL_UNIFORM_BUFFER, 32, DE_NULL, GL_DYNAMIC_DRAW);
        glBindBufferBase(GL_UNIFORM_BUFFER, uniformIndices[0], buffers[0]);
        expectError(GL_NO_ERROR);

        glBindBuffer(GL_UNIFORM_BUFFER, buffers[1]);
        glBufferData(GL_UNIFORM_BUFFER, rangeBufferTotalSize, DE_NULL, GL_DYNAMIC_DRAW);
        glBindBufferRange(GL_UNIFORM_BUFFER, uniformIndices[1], buffers[1], rangeBufferOffset, rangeBufferSize);
        expectError(GL_NO_ERROR);

        // test UNIFORM_BUFFER_START and UNIFORM_BUFFER_SIZE

        const struct BufferRequirements
        {
            GLuint index;
            GLenum pname;
            GLint64 value;
        } requirements[] = {{uniformIndices[0], GL_UNIFORM_BUFFER_START, 0},
                            {uniformIndices[0], GL_UNIFORM_BUFFER_SIZE, 0},
                            {uniformIndices[1], GL_UNIFORM_BUFFER_START, rangeBufferOffset},
                            {uniformIndices[1], GL_UNIFORM_BUFFER_SIZE, rangeBufferSize}};

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(requirements); ++ndx)
        {
            StateQueryMemoryWriteGuard<GLint64> state;
            glGetInteger64i_v(requirements[ndx].pname, requirements[ndx].index, &state);

            if (state.verifyValidity(m_testCtx))
                checkIntEquals(m_testCtx, state, requirements[ndx].value);
            expectError(GL_NO_ERROR);
        }

        glDeleteBuffers(2, buffers);
    }

    int GetAlignment()
    {
        StateQueryMemoryWriteGuard<GLint> state;
        glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &state);

        if (!state.verifyValidity(m_testCtx))
            return -1;

        if (state <= 256)
            return state;

        m_testCtx.getLog() << tcu::TestLog::Message
                           << "// ERROR: UNIFORM_BUFFER_OFFSET_ALIGNMENT has a maximum value of 256."
                           << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "invalid UNIFORM_BUFFER_OFFSET_ALIGNMENT value");

        return -1;
    }
};

const char *getVerifierSuffix(QueryType type)
{
    switch (type)
    {
    case QUERY_INDEXED_INTEGER:
        return "getintegeri_v";
    case QUERY_INDEXED_INTEGER64:
        return "getinteger64i_v";
    case QUERY_INDEXED_INTEGER_VEC4:
        return "getintegeri_v";
    case QUERY_INDEXED_INTEGER64_VEC4:
        return "getinteger64i_v";
    case QUERY_INDEXED_ISENABLED:
        return "isenabledi";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

void isExtensionSupported(Context &context, std::string extensionName)
{
    if (contextSupports(context.getRenderContext().getType(), glu::ApiType::core(4, 5)))
        return;

    if (extensionName == "GL_EXT_draw_buffers_indexed" || extensionName == "GL_KHR_blend_equation_advanced")
    {
        if (!contextSupports(context.getRenderContext().getType(), glu::ApiType::es(3, 2)) &&
            !context.getContextInfo().isExtensionSupported(extensionName.c_str()))
            TCU_THROW(NotSupportedError,
                      (std::string("Extension ") + extensionName + std::string(" not supported.")).c_str());
    }
    else if (!context.getContextInfo().isExtensionSupported(extensionName.c_str()))
        TCU_THROW(NotSupportedError,
                  (std::string("Extension ") + extensionName + std::string(" not supported.")).c_str());
}

class EnableBlendCase : public TestCase
{
public:
    EnableBlendCase(Context &context, const char *name, const char *desc, QueryType verifierType);

    void init(void);

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

EnableBlendCase::EnableBlendCase(Context &context, const char *name, const char *desc, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_verifierType(verifierType)
{
}

void EnableBlendCase::init(void)
{
    isExtensionSupported(m_context, "GL_EXT_draw_buffers_indexed");
}

EnableBlendCase::IterateResult EnableBlendCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int32_t maxDrawBuffers = 0;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial value");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBoolean(result, gl, GL_BLEND, ndx, false, m_verifierType);
    }
    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSettingCommon", "After setting common");

        gl.glEnable(GL_BLEND);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBoolean(result, gl, GL_BLEND, ndx, true, m_verifierType);
    }
    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSettingIndexed", "After setting indexed");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
        {
            if (ndx % 2 == 0)
                gl.glEnablei(GL_BLEND, ndx);
            else
                gl.glDisablei(GL_BLEND, ndx);
        }

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBoolean(result, gl, GL_BLEND, ndx, (ndx % 2 == 0), m_verifierType);
    }
    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterResettingIndexedWithCommon",
                                                 "After resetting indexed with common");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
        {
            if (ndx % 2 == 0)
                gl.glEnablei(GL_BLEND, ndx);
            else
                gl.glDisablei(GL_BLEND, ndx);
        }

        gl.glEnable(GL_BLEND);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBoolean(result, gl, GL_BLEND, ndx, true, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class ColorMaskCase : public TestCase
{
public:
    ColorMaskCase(Context &context, const char *name, const char *desc, QueryType verifierType);

    void init(void);

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

ColorMaskCase::ColorMaskCase(Context &context, const char *name, const char *desc, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_verifierType(verifierType)
{
}

void ColorMaskCase::init(void)
{
    isExtensionSupported(m_context, "GL_EXT_draw_buffers_indexed");
}

ColorMaskCase::IterateResult ColorMaskCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int32_t maxDrawBuffers = 0;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial value");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBooleanVec4(result, gl, GL_COLOR_WRITEMASK, ndx, tcu::BVec4(true), m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingCommon", "After setting common");

        gl.glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBooleanVec4(result, gl, GL_COLOR_WRITEMASK, ndx, tcu::BVec4(false, true, true, false),
                                          m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingIndexed", "After setting indexed");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glColorMaski(ndx, (ndx % 2 == 0 ? GL_TRUE : GL_FALSE), (ndx % 2 == 1 ? GL_TRUE : GL_FALSE),
                            (ndx % 2 == 0 ? GL_TRUE : GL_FALSE), (ndx % 2 == 1 ? GL_TRUE : GL_FALSE));

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBooleanVec4(
                result, gl, GL_COLOR_WRITEMASK, ndx,
                (ndx % 2 == 0 ? tcu::BVec4(true, false, true, false) : tcu::BVec4(false, true, false, true)),
                m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedWithCommon",
                                            "After resetting indexed with common");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glColorMaski(ndx, (ndx % 2 == 0 ? GL_TRUE : GL_FALSE), (ndx % 2 == 1 ? GL_TRUE : GL_FALSE),
                            (ndx % 2 == 0 ? GL_TRUE : GL_FALSE), (ndx % 2 == 1 ? GL_TRUE : GL_FALSE));

        gl.glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_FALSE);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedBooleanVec4(result, gl, GL_COLOR_WRITEMASK, ndx, tcu::BVec4(false, true, true, false),
                                          m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BlendFuncCase : public TestCase
{
public:
    BlendFuncCase(Context &context, const char *name, const char *desc, QueryType verifierType);

    void init(void);

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

BlendFuncCase::BlendFuncCase(Context &context, const char *name, const char *desc, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_verifierType(verifierType)
{
}

void BlendFuncCase::init(void)
{
    isExtensionSupported(m_context, "GL_EXT_draw_buffers_indexed");
}

BlendFuncCase::IterateResult BlendFuncCase::iterate(void)
{
    const uint32_t blendFuncs[] = {GL_ZERO,
                                   GL_ONE,
                                   GL_SRC_COLOR,
                                   GL_ONE_MINUS_SRC_COLOR,
                                   GL_DST_COLOR,
                                   GL_ONE_MINUS_DST_COLOR,
                                   GL_SRC_ALPHA,
                                   GL_ONE_MINUS_SRC_ALPHA,
                                   GL_DST_ALPHA,
                                   GL_ONE_MINUS_DST_ALPHA,
                                   GL_CONSTANT_COLOR,
                                   GL_ONE_MINUS_CONSTANT_COLOR,
                                   GL_CONSTANT_ALPHA,
                                   GL_ONE_MINUS_CONSTANT_ALPHA,
                                   GL_SRC_ALPHA_SATURATE};

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int32_t maxDrawBuffers = 0;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial value");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx, GL_ONE, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx, GL_ZERO, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx, GL_ONE, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx, GL_ZERO, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingCommon", "After setting common");

        gl.glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx, GL_SRC_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx, GL_DST_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx, GL_SRC_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx, GL_DST_ALPHA, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingCommonSeparate",
                                            "After setting common separate");

        gl.glBlendFuncSeparate(GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_ALPHA);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx, GL_SRC_COLOR, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx, GL_ONE_MINUS_SRC_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx, GL_DST_COLOR, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx, GL_ONE_MINUS_DST_ALPHA, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingIndexed", "After setting indexed");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendFunci(ndx, blendFuncs[ndx % DE_LENGTH_OF_ARRAY(blendFuncs)],
                            blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)]);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx,
                                      blendFuncs[ndx % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx,
                                      blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx,
                                      blendFuncs[ndx % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx,
                                      blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingIndexedSeparate",
                                            "After setting indexed separate");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendFuncSeparatei(ndx, blendFuncs[(ndx + 3) % DE_LENGTH_OF_ARRAY(blendFuncs)],
                                    blendFuncs[(ndx + 2) % DE_LENGTH_OF_ARRAY(blendFuncs)],
                                    blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)],
                                    blendFuncs[(ndx + 0) % DE_LENGTH_OF_ARRAY(blendFuncs)]);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx,
                                      blendFuncs[(ndx + 3) % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx,
                                      blendFuncs[(ndx + 2) % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx,
                                      blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx,
                                      blendFuncs[(ndx + 0) % DE_LENGTH_OF_ARRAY(blendFuncs)], m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedWithCommon",
                                            "After resetting indexed with common");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendFunci(ndx, blendFuncs[ndx % DE_LENGTH_OF_ARRAY(blendFuncs)],
                            blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)]);

        gl.glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx, GL_SRC_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx, GL_DST_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx, GL_SRC_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx, GL_DST_ALPHA, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedWithCommonSeparate",
                                            "After resetting indexed with common separate");

        gl.glBlendFuncSeparate(GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_ALPHA);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendFuncSeparatei(ndx, blendFuncs[(ndx + 3) % DE_LENGTH_OF_ARRAY(blendFuncs)],
                                    blendFuncs[(ndx + 2) % DE_LENGTH_OF_ARRAY(blendFuncs)],
                                    blendFuncs[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendFuncs)],
                                    blendFuncs[(ndx + 0) % DE_LENGTH_OF_ARRAY(blendFuncs)]);

        gl.glBlendFuncSeparate(GL_SRC_COLOR, GL_ONE_MINUS_SRC_ALPHA, GL_DST_COLOR, GL_ONE_MINUS_DST_ALPHA);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_RGB, ndx, GL_SRC_COLOR, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_RGB, ndx, GL_ONE_MINUS_SRC_ALPHA, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_SRC_ALPHA, ndx, GL_DST_COLOR, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_DST_ALPHA, ndx, GL_ONE_MINUS_DST_ALPHA, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BlendEquationCase : public TestCase
{
public:
    BlendEquationCase(Context &context, const char *name, const char *desc, QueryType verifierType);

    void init(void);

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

BlendEquationCase::BlendEquationCase(Context &context, const char *name, const char *desc, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_verifierType(verifierType)
{
}

void BlendEquationCase::init(void)
{
    isExtensionSupported(m_context, "GL_EXT_draw_buffers_indexed");
}

BlendEquationCase::IterateResult BlendEquationCase::iterate(void)
{
    const uint32_t blendEquations[] = {GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX};

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int32_t maxDrawBuffers = 0;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial value");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_FUNC_ADD, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_FUNC_ADD, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingCommon", "After setting common");

        gl.glBlendEquation(GL_FUNC_SUBTRACT);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_FUNC_SUBTRACT, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_FUNC_SUBTRACT, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingCommonSeparate",
                                            "After setting common separate");

        gl.glBlendEquationSeparate(GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_SUBTRACT);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_FUNC_REVERSE_SUBTRACT, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_FUNC_SUBTRACT, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingIndexed", "After setting indexed");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationi(ndx, blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)]);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx,
                                      blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx,
                                      blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)], m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingIndexedSeparate",
                                            "After setting indexed separate");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationSeparatei(ndx, blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)],
                                        blendEquations[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendEquations)]);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx,
                                      blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)], m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx,
                                      blendEquations[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendEquations)], m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedWithCommon",
                                            "After resetting indexed with common");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationi(ndx, blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)]);

        gl.glBlendEquation(GL_FUNC_SUBTRACT);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_FUNC_SUBTRACT, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_FUNC_SUBTRACT, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedWithCommonSeparate",
                                            "After resetting indexed with common separate");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationSeparatei(ndx, blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)],
                                        blendEquations[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendEquations)]);

        gl.glBlendEquationSeparate(GL_FUNC_REVERSE_SUBTRACT, GL_FUNC_SUBTRACT);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_FUNC_REVERSE_SUBTRACT, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_FUNC_SUBTRACT, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BlendEquationAdvancedCase : public TestCase
{
public:
    BlendEquationAdvancedCase(Context &context, const char *name, const char *desc, QueryType verifierType);

    void init(void);

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

BlendEquationAdvancedCase::BlendEquationAdvancedCase(Context &context, const char *name, const char *desc,
                                                     QueryType verifierType)
    : TestCase(context, name, desc)
    , m_verifierType(verifierType)
{
}

void BlendEquationAdvancedCase::init(void)
{
    isExtensionSupported(m_context, "GL_EXT_draw_buffers_indexed");
    isExtensionSupported(m_context, "GL_KHR_blend_equation_advanced");
}

BlendEquationAdvancedCase::IterateResult BlendEquationAdvancedCase::iterate(void)
{
    const uint32_t blendEquations[] = {GL_FUNC_ADD, GL_FUNC_SUBTRACT, GL_FUNC_REVERSE_SUBTRACT, GL_MIN, GL_MAX};

    const uint32_t blendEquationAdvanced[] = {GL_MULTIPLY,       GL_SCREEN,     GL_OVERLAY,       GL_DARKEN,
                                              GL_LIGHTEN,        GL_COLORDODGE, GL_COLORBURN,     GL_HARDLIGHT,
                                              GL_SOFTLIGHT,      GL_DIFFERENCE, GL_EXCLUSION,     GL_HSL_HUE,
                                              GL_HSL_SATURATION, GL_HSL_COLOR,  GL_HSL_LUMINOSITY};

    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int32_t maxDrawBuffers = 0;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingCommon", "After setting common");

        gl.glBlendEquation(GL_SCREEN);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_SCREEN, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_SCREEN, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterSettingIndexed", "After setting indexed");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationi(ndx, blendEquationAdvanced[ndx % DE_LENGTH_OF_ARRAY(blendEquationAdvanced)]);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx,
                                      blendEquationAdvanced[ndx % DE_LENGTH_OF_ARRAY(blendEquationAdvanced)],
                                      m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx,
                                      blendEquationAdvanced[ndx % DE_LENGTH_OF_ARRAY(blendEquationAdvanced)],
                                      m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedWithCommon",
                                            "After resetting indexed with common");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationi(ndx, blendEquationAdvanced[ndx % DE_LENGTH_OF_ARRAY(blendEquationAdvanced)]);

        gl.glBlendEquation(GL_MULTIPLY);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_MULTIPLY, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_MULTIPLY, m_verifierType);
    }
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "AfterResettingIndexedSeparateWithCommon",
                                            "After resetting indexed separate with common");

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            gl.glBlendEquationSeparatei(ndx, blendEquations[ndx % DE_LENGTH_OF_ARRAY(blendEquations)],
                                        blendEquations[(ndx + 1) % DE_LENGTH_OF_ARRAY(blendEquations)]);

        gl.glBlendEquation(GL_LIGHTEN);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_RGB, ndx, GL_LIGHTEN, m_verifierType);

        for (int ndx = 0; ndx < maxDrawBuffers; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION_ALPHA, ndx, GL_LIGHTEN, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

} // namespace

IndexedStateQueryTests::IndexedStateQueryTests(Context &context)
    : TestCaseGroup(context, "indexed", "Indexed Integer Values")
{
}

void IndexedStateQueryTests::init(void)
{
    // transform feedback
    addChild(new TransformFeedbackBufferBindingCase(m_context, "transform_feedback_buffer_binding",
                                                    "TRANSFORM_FEEDBACK_BUFFER_BINDING"));
    addChild(
        new TransformFeedbackBufferBufferCase(m_context, "transform_feedback_buffer_start_size",
                                              "TRANSFORM_FEEDBACK_BUFFER_START and TRANSFORM_FEEDBACK_BUFFER_SIZE"));
    addChild(new TransformFeedbackSwitchingBufferCase(
        m_context, "transform_feedback_switching_buffer",
        "TRANSFORM_FEEDBACK_BUFFER_BINDING while switching transform feedback objects"));

    // uniform buffers
    addChild(new UniformBufferBindingCase(m_context, "uniform_buffer_binding", "UNIFORM_BUFFER_BINDING"));
    addChild(new UniformBufferBufferCase(m_context, "uniform_buffer_start_size",
                                         "UNIFORM_BUFFER_START and UNIFORM_BUFFER_SIZE"));

    static const QueryType verifiers[]     = {QUERY_INDEXED_INTEGER, QUERY_INDEXED_INTEGER64};
    static const QueryType vec4Verifiers[] = {QUERY_INDEXED_INTEGER_VEC4, QUERY_INDEXED_INTEGER64_VEC4};

#define FOR_EACH_VERIFIER(X)                                                              \
    for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(verifiers); ++verifierNdx) \
    {                                                                                     \
        const QueryType verifier   = verifiers[verifierNdx];                              \
        const char *verifierSuffix = getVerifierSuffix(verifier);                         \
        this->addChild(X);                                                                \
    }

#define FOR_EACH_VEC4_VERIFIER(X)                                                             \
    for (int verifierNdx = 0; verifierNdx < DE_LENGTH_OF_ARRAY(vec4Verifiers); ++verifierNdx) \
    {                                                                                         \
        const QueryType verifier   = vec4Verifiers[verifierNdx];                              \
        const char *verifierSuffix = getVerifierSuffix(verifier);                             \
        this->addChild(X);                                                                    \
    }

    addChild(new EnableBlendCase(m_context, "blend_isenabledi", "BLEND", QUERY_INDEXED_ISENABLED));
    FOR_EACH_VEC4_VERIFIER(new ColorMaskCase(m_context, (std::string() + "color_mask_" + verifierSuffix).c_str(),
                                             "COLOR_WRITEMASK", verifier))
    FOR_EACH_VERIFIER(new BlendFuncCase(m_context, (std::string() + "blend_func_" + verifierSuffix).c_str(),
                                        "BLEND_SRC and BLEND_DST", verifier))
    FOR_EACH_VERIFIER(new BlendEquationCase(m_context, (std::string() + "blend_equation_" + verifierSuffix).c_str(),
                                            "BLEND_EQUATION_RGB and BLEND_DST", verifier))
    FOR_EACH_VERIFIER(
        new BlendEquationAdvancedCase(m_context, (std::string() + "blend_equation_advanced_" + verifierSuffix).c_str(),
                                      "BLEND_EQUATION_RGB and BLEND_DST", verifier))
}

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