/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2015 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 "es31fIndexedStateQueryTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuFormatUtil.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluObjectWrapper.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "glsStateQueryUtil.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using namespace gls::StateQueryUtil;

static const char *getVerifierSuffix(QueryType type)
{
    switch (type)
    {
    case QUERY_INDEXED_BOOLEAN:
        return "getbooleani_v";
    case QUERY_INDEXED_INTEGER:
        return "getintegeri_v";
    case QUERY_INDEXED_INTEGER64:
        return "getinteger64i_v";
    case QUERY_INDEXED_BOOLEAN_VEC4:
        return "getbooleani_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 SampleMaskCase : public TestCase
{
public:
    SampleMaskCase(Context &context, const char *name, const char *desc, QueryType verifierType);

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

    const QueryType m_verifierType;
    int m_maxSampleMaskWords;
};

SampleMaskCase::SampleMaskCase(Context &context, const char *name, const char *desc, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_verifierType(verifierType)
    , m_maxSampleMaskWords(-1)
{
}

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

    gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &m_maxSampleMaskWords);
    GLU_EXPECT_NO_ERROR(gl.getError(), "query sample mask words");

    // mask word count ok?
    if (m_maxSampleMaskWords <= 0)
        throw tcu::TestError("Minimum value of GL_MAX_SAMPLE_MASK_WORDS is 1. Got " +
                             de::toString(m_maxSampleMaskWords));

    m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_SAMPLE_MASK_WORDS = " << m_maxSampleMaskWords
                       << tcu::TestLog::EndMessage;
}

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

    gl.enableLogging(true);

    // initial values
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "initial", "Initial values");

        for (int ndx = 0; ndx < m_maxSampleMaskWords; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_SAMPLE_MASK_VALUE, ndx, -1, m_verifierType);
    }

    // fixed values
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "fixed", "Fixed values");

        for (int ndx = 0; ndx < m_maxSampleMaskWords; ++ndx)
        {
            gl.glSampleMaski(ndx, 0);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "glSampleMaski");

            verifyStateIndexedInteger(result, gl, GL_SAMPLE_MASK_VALUE, ndx, 0, m_verifierType);
        }
    }

    // random masks
    {
        const int numRandomTest = 20;
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "random", "Random values");
        de::Random rnd(0x4312);

        for (int testNdx = 0; testNdx < numRandomTest; ++testNdx)
        {
            const glw::GLint maskIndex = (glw::GLint)(rnd.getUint32() % m_maxSampleMaskWords);
            glw::GLint mask            = (glw::GLint)(rnd.getUint32());

            gl.glSampleMaski(maskIndex, mask);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "glSampleMaski");

            verifyStateIndexedInteger(result, gl, GL_SAMPLE_MASK_VALUE, maskIndex, mask, m_verifierType);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class MinValueIndexed3Case : public TestCase
{
public:
    MinValueIndexed3Case(Context &context, const char *name, const char *desc, glw::GLenum target,
                         const tcu::IVec3 &ref, QueryType verifierType);

private:
    IterateResult iterate(void);

    const glw::GLenum m_target;
    const tcu::IVec3 m_ref;
    const QueryType m_verifierType;
};

MinValueIndexed3Case::MinValueIndexed3Case(Context &context, const char *name, const char *desc, glw::GLenum target,
                                           const tcu::IVec3 &ref, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_target(target)
    , m_ref(ref)
    , m_verifierType(verifierType)
{
}

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

    gl.enableLogging(true);

    for (int ndx = 0; ndx < 3; ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Element", "Element " + de::toString(ndx));

        verifyStateIndexedIntegerMin(result, gl, m_target, ndx, m_ref[ndx], m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BufferBindingCase : public TestCase
{
public:
    BufferBindingCase(Context &context, const char *name, const char *desc, glw::GLenum queryTarget,
                      glw::GLenum bufferTarget, glw::GLenum numBindingsTarget, QueryType verifierType);

private:
    IterateResult iterate(void);

    const glw::GLenum m_queryTarget;
    const glw::GLenum m_bufferTarget;
    const glw::GLenum m_numBindingsTarget;
    const QueryType m_verifierType;
};

BufferBindingCase::BufferBindingCase(Context &context, const char *name, const char *desc, glw::GLenum queryTarget,
                                     glw::GLenum bufferTarget, glw::GLenum numBindingsTarget, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_queryTarget(queryTarget)
    , m_bufferTarget(bufferTarget)
    , m_numBindingsTarget(numBindingsTarget)
    , m_verifierType(verifierType)
{
}

BufferBindingCase::IterateResult BufferBindingCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxBindings = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(m_numBindingsTarget, &maxBindings);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxBindings; ++ndx)
            verifyStateIndexedInteger(result, gl, m_queryTarget, ndx, 0, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Buffer bufferA(m_context.getRenderContext());
        glu::Buffer bufferB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxBindings / 2;

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Generic", "After setting generic binding point");

            gl.glBindBuffer(m_bufferTarget, *bufferA);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "glBindBuffer");

            verifyStateIndexedInteger(result, gl, m_queryTarget, 0, 0, m_verifierType);
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Indexed", "After setting with glBindBufferBase");

            gl.glBindBufferBase(m_bufferTarget, ndxA, *bufferA);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "glBindBufferBase");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxA, *bufferA, m_verifierType);
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Indexed", "After setting with glBindBufferRange");

            gl.glBindBufferRange(m_bufferTarget, ndxB, *bufferB, 0, 8);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "glBindBufferRange");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxB, *bufferB, m_verifierType);
        }
        if (ndxA != ndxB)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "DifferentStates", "Original state did not change");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxA, *bufferA, m_verifierType);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BufferStartCase : public TestCase
{
public:
    BufferStartCase(Context &context, const char *name, const char *desc, glw::GLenum queryTarget,
                    glw::GLenum bufferTarget, glw::GLenum numBindingsTarget, QueryType verifierType);

private:
    IterateResult iterate(void);

    const glw::GLenum m_queryTarget;
    const glw::GLenum m_bufferTarget;
    const glw::GLenum m_numBindingsTarget;
    const QueryType m_verifierType;
};

BufferStartCase::BufferStartCase(Context &context, const char *name, const char *desc, glw::GLenum queryTarget,
                                 glw::GLenum bufferTarget, glw::GLenum numBindingsTarget, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_queryTarget(queryTarget)
    , m_bufferTarget(bufferTarget)
    , m_numBindingsTarget(numBindingsTarget)
    , m_verifierType(verifierType)
{
}

BufferStartCase::IterateResult BufferStartCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxBindings = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(m_numBindingsTarget, &maxBindings);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxBindings; ++ndx)
            verifyStateIndexedInteger(result, gl, m_queryTarget, ndx, 0, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Buffer bufferA(m_context.getRenderContext());
        glu::Buffer bufferB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxBindings / 2;
        int offset     = -1;

        if (m_bufferTarget == GL_ATOMIC_COUNTER_BUFFER)
            offset = 4;
        else if (m_bufferTarget == GL_SHADER_STORAGE_BUFFER)
        {
            gl.glGetIntegerv(GL_SHADER_STORAGE_BUFFER_OFFSET_ALIGNMENT, &offset);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "get align");
        }
        else
            DE_ASSERT(false);

        TCU_CHECK(offset >= 0);

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Generic", "After setting generic binding point");

            gl.glBindBuffer(m_bufferTarget, *bufferA);
            gl.glBufferData(m_bufferTarget, 16, DE_NULL, GL_DYNAMIC_READ);
            gl.glBindBuffer(m_bufferTarget, *bufferB);
            gl.glBufferData(m_bufferTarget, 32, DE_NULL, GL_DYNAMIC_READ);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen bufs");

            verifyStateIndexedInteger(result, gl, m_queryTarget, 0, 0, m_verifierType);
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Indexed", "After setting with glBindBufferBase");

            gl.glBindBufferBase(m_bufferTarget, ndxA, *bufferA);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind buf");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxA, 0, m_verifierType);
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Indexed", "After setting with glBindBufferRange");

            gl.glBindBufferRange(m_bufferTarget, ndxB, *bufferB, offset, 8);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind buf");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxB, offset, m_verifierType);
        }
        if (ndxA != ndxB)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "DifferentStates", "Original state did not change");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxA, 0, m_verifierType);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BufferSizeCase : public TestCase
{
public:
    BufferSizeCase(Context &context, const char *name, const char *desc, glw::GLenum queryTarget,
                   glw::GLenum bufferTarget, glw::GLenum numBindingsTarget, QueryType verifierType);

private:
    IterateResult iterate(void);

    const glw::GLenum m_queryTarget;
    const glw::GLenum m_bufferTarget;
    const glw::GLenum m_numBindingsTarget;
    const QueryType m_verifierType;
};

BufferSizeCase::BufferSizeCase(Context &context, const char *name, const char *desc, glw::GLenum queryTarget,
                               glw::GLenum bufferTarget, glw::GLenum numBindingsTarget, QueryType verifierType)
    : TestCase(context, name, desc)
    , m_queryTarget(queryTarget)
    , m_bufferTarget(bufferTarget)
    , m_numBindingsTarget(numBindingsTarget)
    , m_verifierType(verifierType)
{
}

BufferSizeCase::IterateResult BufferSizeCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxBindings = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(m_numBindingsTarget, &maxBindings);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxBindings; ++ndx)
            verifyStateIndexedInteger(result, gl, m_queryTarget, ndx, 0, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Buffer bufferA(m_context.getRenderContext());
        glu::Buffer bufferB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxBindings / 2;

        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Generic", "After setting generic binding point");

            gl.glBindBuffer(m_bufferTarget, *bufferA);
            gl.glBufferData(m_bufferTarget, 16, DE_NULL, GL_DYNAMIC_READ);
            gl.glBindBuffer(m_bufferTarget, *bufferB);
            gl.glBufferData(m_bufferTarget, 32, DE_NULL, GL_DYNAMIC_READ);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen bufs");

            verifyStateIndexedInteger(result, gl, m_queryTarget, 0, 0, m_verifierType);
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Indexed", "After setting with glBindBufferBase");

            gl.glBindBufferBase(m_bufferTarget, ndxA, *bufferA);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind buf");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxA, 0, m_verifierType);
        }
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "Indexed", "After setting with glBindBufferRange");

            gl.glBindBufferRange(m_bufferTarget, ndxB, *bufferB, 0, 8);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind buf");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxB, 8, m_verifierType);
        }
        if (ndxA != ndxB)
        {
            const tcu::ScopedLogSection section(m_testCtx.getLog(), "DifferentStates", "Original state did not change");

            verifyStateIndexedInteger(result, gl, m_queryTarget, ndxA, 0, m_verifierType);
        }
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

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

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

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

ImageBindingNameCase::IterateResult ImageBindingNameCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxImages = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImages);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxImages; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_NAME, ndx, 0, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Texture textureA(m_context.getRenderContext());
        glu::Texture textureB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxImages / 2;

        gl.glBindTexture(GL_TEXTURE_2D, *textureA);
        gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxA, *textureA, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *textureB);
        gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 32, 32, 4);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxB, *textureB, 0, GL_FALSE, 2, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_NAME, ndxA, *textureA, m_verifierType);
        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_NAME, ndxB, *textureB, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

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

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

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

ImageBindingLevelCase::IterateResult ImageBindingLevelCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxImages = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImages);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxImages; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_LEVEL, ndx, 0, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Texture textureA(m_context.getRenderContext());
        glu::Texture textureB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxImages / 2;

        gl.glBindTexture(GL_TEXTURE_2D, *textureA);
        gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxA, *textureA, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        gl.glBindTexture(GL_TEXTURE_2D, *textureB);
        gl.glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxB, *textureB, 2, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_LEVEL, ndxA, 0, m_verifierType);
        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_LEVEL, ndxB, 2, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

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

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

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

ImageBindingLayeredCase::IterateResult ImageBindingLayeredCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxImages = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImages);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxImages; ++ndx)
            verifyStateIndexedBoolean(result, gl, GL_IMAGE_BINDING_LAYERED, ndx, false, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Texture textureA(m_context.getRenderContext());
        glu::Texture textureB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxImages / 2;

        gl.glBindTexture(GL_TEXTURE_2D, *textureA);
        gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxA, *textureA, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *textureB);
        gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 32, 32, 4);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxB, *textureB, 0, GL_TRUE, 2, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        verifyStateIndexedBoolean(result, gl, GL_IMAGE_BINDING_LAYERED, ndxA, false, m_verifierType);
        verifyStateIndexedBoolean(result, gl, GL_IMAGE_BINDING_LAYERED, ndxB, true, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

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

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

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

ImageBindingLayerCase::IterateResult ImageBindingLayerCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxImages = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImages);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxImages; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_LAYER, ndx, 0, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Texture textureA(m_context.getRenderContext());
        glu::Texture textureB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxImages / 2;

        gl.glBindTexture(GL_TEXTURE_2D, *textureA);
        gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxA, *textureA, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *textureB);
        gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 32, 32, 4);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxB, *textureB, 0, GL_TRUE, 2, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_LAYER, ndxA, 0, m_verifierType);
        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_LAYER, ndxB, 2, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

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

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

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

ImageBindingAccessCase::IterateResult ImageBindingAccessCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxImages = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImages);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxImages; ++ndx)
            verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_ACCESS, ndx, GL_READ_ONLY, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Texture textureA(m_context.getRenderContext());
        glu::Texture textureB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxImages / 2;

        gl.glBindTexture(GL_TEXTURE_2D, *textureA);
        gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxA, *textureA, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *textureB);
        gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, 32, 32, 4);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxB, *textureB, 0, GL_TRUE, 2, GL_READ_WRITE, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_ACCESS, ndxA, GL_READ_ONLY, m_verifierType);
        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_ACCESS, ndxB, GL_READ_WRITE, m_verifierType);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

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

private:
    IterateResult iterate(void);

    const QueryType m_verifierType;
};

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

ImageBindingFormatCase::IterateResult ImageBindingFormatCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");
    int maxImages = -1;

    gl.enableLogging(true);

    gl.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImages);
    GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetIntegerv");

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

        for (int ndx = 0; ndx < maxImages; ++ndx)
            if (glu::isContextTypeES(m_context.getRenderContext().getType()))
                verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_FORMAT, ndx, GL_R32UI, m_verifierType);
            else
                verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_FORMAT, ndx, GL_R8, m_verifierType);
    }

    {
        const tcu::ScopedLogSection superSection(m_testCtx.getLog(), "AfterSetting", "After setting");
        glu::Texture textureA(m_context.getRenderContext());
        glu::Texture textureB(m_context.getRenderContext());
        const int ndxA = 0;
        const int ndxB = maxImages / 2;

        gl.glBindTexture(GL_TEXTURE_2D, *textureA);
        gl.glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 32, 32);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxA, *textureA, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        gl.glBindTexture(GL_TEXTURE_2D_ARRAY, *textureB);
        gl.glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_R32F, 32, 32, 4);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen tex");

        gl.glBindImageTexture(ndxB, *textureB, 0, GL_TRUE, 2, GL_READ_WRITE, GL_R32F);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "bind unit");

        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_FORMAT, ndxA, GL_RGBA8UI, m_verifierType);
        verifyStateIndexedInteger(result, gl, GL_IMAGE_BINDING_FORMAT, ndxB, GL_R32F, 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 state queries")
{
}

IndexedStateQueryTests::~IndexedStateQueryTests(void)
{
}

void IndexedStateQueryTests::init(void)
{
    static const QueryType verifiers[] = {QUERY_INDEXED_BOOLEAN, QUERY_INDEXED_INTEGER, QUERY_INDEXED_INTEGER64};

#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);                                                                \
    }

    FOR_EACH_VERIFIER(new SampleMaskCase(m_context, (std::string() + "sample_mask_value_" + verifierSuffix).c_str(),
                                         "Test SAMPLE_MASK_VALUE", verifier))

    FOR_EACH_VERIFIER(
        new MinValueIndexed3Case(m_context, (std::string() + "max_compute_work_group_count_" + verifierSuffix).c_str(),
                                 "Test MAX_COMPUTE_WORK_GROUP_COUNT", GL_MAX_COMPUTE_WORK_GROUP_COUNT,
                                 tcu::IVec3(65535, 65535, 65535), verifier))
    FOR_EACH_VERIFIER(new MinValueIndexed3Case(
        m_context, (std::string() + "max_compute_work_group_size_" + verifierSuffix).c_str(),
        "Test MAX_COMPUTE_WORK_GROUP_SIZE", GL_MAX_COMPUTE_WORK_GROUP_SIZE, tcu::IVec3(128, 128, 64), verifier))

    FOR_EACH_VERIFIER(new BufferBindingCase(m_context,
                                            (std::string() + "atomic_counter_buffer_binding_" + verifierSuffix).c_str(),
                                            "Test ATOMIC_COUNTER_BUFFER_BINDING", GL_ATOMIC_COUNTER_BUFFER_BINDING,
                                            GL_ATOMIC_COUNTER_BUFFER, GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, verifier))
    FOR_EACH_VERIFIER(new BufferStartCase(m_context,
                                          (std::string() + "atomic_counter_buffer_start_" + verifierSuffix).c_str(),
                                          "Test ATOMIC_COUNTER_BUFFER_START", GL_ATOMIC_COUNTER_BUFFER_START,
                                          GL_ATOMIC_COUNTER_BUFFER, GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, verifier))
    FOR_EACH_VERIFIER(new BufferSizeCase(m_context,
                                         (std::string() + "atomic_counter_buffer_size_" + verifierSuffix).c_str(),
                                         "Test ATOMIC_COUNTER_BUFFER_SIZE", GL_ATOMIC_COUNTER_BUFFER_SIZE,
                                         GL_ATOMIC_COUNTER_BUFFER, GL_MAX_ATOMIC_COUNTER_BUFFER_BINDINGS, verifier))

    FOR_EACH_VERIFIER(new BufferBindingCase(m_context,
                                            (std::string() + "shader_storage_buffer_binding_" + verifierSuffix).c_str(),
                                            "Test SHADER_STORAGE_BUFFER_BINDING", GL_SHADER_STORAGE_BUFFER_BINDING,
                                            GL_SHADER_STORAGE_BUFFER, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, verifier))
    FOR_EACH_VERIFIER(new BufferStartCase(m_context,
                                          (std::string() + "shader_storage_buffer_start_" + verifierSuffix).c_str(),
                                          "Test SHADER_STORAGE_BUFFER_START", GL_SHADER_STORAGE_BUFFER_START,
                                          GL_SHADER_STORAGE_BUFFER, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, verifier))
    FOR_EACH_VERIFIER(new BufferSizeCase(m_context,
                                         (std::string() + "shader_storage_buffer_size_" + verifierSuffix).c_str(),
                                         "Test SHADER_STORAGE_BUFFER_SIZE", GL_SHADER_STORAGE_BUFFER_SIZE,
                                         GL_SHADER_STORAGE_BUFFER, GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS, verifier))

    FOR_EACH_VERIFIER(new ImageBindingNameCase(m_context,
                                               (std::string() + "image_binding_name_" + verifierSuffix).c_str(),
                                               "Test IMAGE_BINDING_NAME", verifier))
    FOR_EACH_VERIFIER(new ImageBindingLevelCase(m_context,
                                                (std::string() + "image_binding_level_" + verifierSuffix).c_str(),
                                                "Test IMAGE_BINDING_LEVEL", verifier))
    FOR_EACH_VERIFIER(new ImageBindingLayeredCase(m_context,
                                                  (std::string() + "image_binding_layered_" + verifierSuffix).c_str(),
                                                  "Test IMAGE_BINDING_LAYERED", verifier))
    FOR_EACH_VERIFIER(new ImageBindingLayerCase(m_context,
                                                (std::string() + "image_binding_layer_" + verifierSuffix).c_str(),
                                                "Test IMAGE_BINDING_LAYER", verifier))
    FOR_EACH_VERIFIER(new ImageBindingAccessCase(m_context,
                                                 (std::string() + "image_binding_access_" + verifierSuffix).c_str(),
                                                 "Test IMAGE_BINDING_ACCESS", verifier))
    FOR_EACH_VERIFIER(new ImageBindingFormatCase(m_context,
                                                 (std::string() + "image_binding_format_" + verifierSuffix).c_str(),
                                                 "Test IMAGE_BINDING_FORMAT", verifier))

    // All non-boolean verifiers are tested in ES3 test module.
    addChild(new ColorMaskCase(m_context, "color_mask_getbooleani_v", "COLOR_WRITEMASK", QUERY_INDEXED_BOOLEAN_VEC4));
    addChild(
        new BlendFuncCase(m_context, "blend_func_getbooleani_v", "BLEND_SRC and BLEND_DST", QUERY_INDEXED_BOOLEAN));
    addChild(new BlendEquationCase(m_context, "blend_equation_getbooleani_v", "BLEND_EQUATION_RGB and BLEND_DST",
                                   QUERY_INDEXED_BOOLEAN));
    addChild(new BlendEquationAdvancedCase(m_context, "blend_equation_advanced_getbooleani_v",
                                           "BLEND_EQUATION_RGB and BLEND_DST", QUERY_INDEXED_BOOLEAN));

#undef FOR_EACH_VEC4_VERIFIER
#undef FOR_EACH_VERIFIER
}

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