/*-------------------------------------------------------------------------
 * 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 Advanced blending (GL_KHR_blend_equation_advanced) tests.
 *//*--------------------------------------------------------------------*/

#include "es31fAdvancedBlendTests.hpp"
#include "gluStrUtil.hpp"
#include "glsFragmentOpUtil.hpp"
#include "glsStateQueryUtil.hpp"
#include "gluPixelTransfer.hpp"
#include "gluObjectWrapper.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluStrUtil.hpp"
#include "tcuPixelFormat.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "rrFragmentOperations.hpp"
#include "sglrReferenceUtils.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <string>
#include <vector>

namespace deqp
{

using gls::FragmentOpUtil::IntegerQuad;
using gls::FragmentOpUtil::ReferenceQuadRenderer;
using std::map;
using std::string;
using std::vector;
using tcu::TestLog;
using tcu::TextureFormat;
using tcu::TextureLevel;
using tcu::UVec4;
using tcu::Vec2;
using tcu::Vec4;

namespace gles31
{
namespace Functional
{

namespace
{

enum
{
    MAX_VIEWPORT_WIDTH  = 128,
    MAX_VIEWPORT_HEIGHT = 128
};

enum RenderTargetType
{
    RENDERTARGETTYPE_DEFAULT = 0, //!< Default framebuffer
    RENDERTARGETTYPE_SRGB_FBO,
    RENDERTARGETTYPE_MSAA_FBO,

    RENDERTARGETTYPE_LAST
};

static const char *getEquationName(glw::GLenum equation)
{
    switch (equation)
    {
    case GL_MULTIPLY:
        return "multiply";
    case GL_SCREEN:
        return "screen";
    case GL_OVERLAY:
        return "overlay";
    case GL_DARKEN:
        return "darken";
    case GL_LIGHTEN:
        return "lighten";
    case GL_COLORDODGE:
        return "colordodge";
    case GL_COLORBURN:
        return "colorburn";
    case GL_HARDLIGHT:
        return "hardlight";
    case GL_SOFTLIGHT:
        return "softlight";
    case GL_DIFFERENCE:
        return "difference";
    case GL_EXCLUSION:
        return "exclusion";
    case GL_HSL_HUE:
        return "hsl_hue";
    case GL_HSL_SATURATION:
        return "hsl_saturation";
    case GL_HSL_COLOR:
        return "hsl_color";
    case GL_HSL_LUMINOSITY:
        return "hsl_luminosity";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

class AdvancedBlendCase : public TestCase
{
public:
    AdvancedBlendCase(Context &context, const char *name, const char *desc, uint32_t mode, int overdrawCount,
                      bool coherent, RenderTargetType rtType);

    ~AdvancedBlendCase(void);

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

    IterateResult iterate(void);

private:
    AdvancedBlendCase(const AdvancedBlendCase &);
    AdvancedBlendCase &operator=(const AdvancedBlendCase &);

    const uint32_t m_blendMode;
    const int m_overdrawCount;
    const bool m_coherentBlending;
    const RenderTargetType m_rtType;
    const int m_numIters;

    bool m_coherentExtensionSupported;

    uint32_t m_colorRbo;
    uint32_t m_fbo;

    uint32_t m_resolveColorRbo;
    uint32_t m_resolveFbo;

    glu::ShaderProgram *m_program;

    ReferenceQuadRenderer *m_referenceRenderer;
    TextureLevel *m_refColorBuffer;

    const int m_renderWidth;
    const int m_renderHeight;
    const int m_viewportWidth;
    const int m_viewportHeight;

    int m_iterNdx;
};

AdvancedBlendCase::AdvancedBlendCase(Context &context, const char *name, const char *desc, uint32_t mode,
                                     int overdrawCount, bool coherent, RenderTargetType rtType)
    : TestCase(context, name, desc)
    , m_blendMode(mode)
    , m_overdrawCount(overdrawCount)
    , m_coherentBlending(coherent)
    , m_rtType(rtType)
    , m_numIters(5)
    , m_coherentExtensionSupported(false)
    , m_colorRbo(0)
    , m_fbo(0)
    , m_resolveColorRbo(0)
    , m_resolveFbo(0)
    , m_program(DE_NULL)
    , m_referenceRenderer(DE_NULL)
    , m_refColorBuffer(DE_NULL)
    , m_renderWidth(rtType != RENDERTARGETTYPE_DEFAULT ? 2 * MAX_VIEWPORT_WIDTH :
                                                         m_context.getRenderTarget().getWidth())
    , m_renderHeight(rtType != RENDERTARGETTYPE_DEFAULT ? 2 * MAX_VIEWPORT_HEIGHT :
                                                          m_context.getRenderTarget().getHeight())
    , m_viewportWidth(de::min<int>(m_renderWidth, MAX_VIEWPORT_WIDTH))
    , m_viewportHeight(de::min<int>(m_renderHeight, MAX_VIEWPORT_HEIGHT))
    , m_iterNdx(0)
{
}

const char *getBlendLayoutQualifier(rr::BlendEquationAdvanced equation)
{
    static const char *s_qualifiers[] = {
        "blend_support_multiply",       "blend_support_screen",    "blend_support_overlay",
        "blend_support_darken",         "blend_support_lighten",   "blend_support_colordodge",
        "blend_support_colorburn",      "blend_support_hardlight", "blend_support_softlight",
        "blend_support_difference",     "blend_support_exclusion", "blend_support_hsl_hue",
        "blend_support_hsl_saturation", "blend_support_hsl_color", "blend_support_hsl_luminosity",
    };
    DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(s_qualifiers) == rr::BLENDEQUATION_ADVANCED_LAST);
    DE_ASSERT(de::inBounds<int>(equation, 0, rr::BLENDEQUATION_ADVANCED_LAST));
    return s_qualifiers[equation];
}

glu::ProgramSources getBlendProgramSrc(rr::BlendEquationAdvanced equation, glu::RenderContext &renderContext)
{
    const bool supportsES32 = glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2));

    static const char *s_vertSrc = "${GLSL_VERSION_DECL}\n"
                                   "in highp vec4 a_position;\n"
                                   "in mediump vec4 a_color;\n"
                                   "out mediump vec4 v_color;\n"
                                   "void main()\n"
                                   "{\n"
                                   "    gl_Position = a_position;\n"
                                   "    v_color = a_color;\n"
                                   "}\n";
    static const char *s_fragSrc = "${GLSL_VERSION_DECL}\n"
                                   "${EXTENSION}"
                                   "in mediump vec4 v_color;\n"
                                   "layout(${SUPPORT_QUALIFIER}) out;\n"
                                   "layout(location = 0) out mediump vec4 o_color;\n"
                                   "void main()\n"
                                   "{\n"
                                   "    o_color = v_color;\n"
                                   "}\n";

    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                               getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["EXTENSION"]         = supportsES32 ? "\n" : "#extension GL_KHR_blend_equation_advanced : require\n";
    args["SUPPORT_QUALIFIER"] = getBlendLayoutQualifier(equation);

    return glu::ProgramSources() << glu::VertexSource(tcu::StringTemplate(s_vertSrc).specialize(args))
                                 << glu::FragmentSource(tcu::StringTemplate(s_fragSrc).specialize(args));
}

void AdvancedBlendCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const bool useFbo        = m_rtType != RENDERTARGETTYPE_DEFAULT;
    const bool useSRGB       = m_rtType == RENDERTARGETTYPE_SRGB_FBO;

    m_coherentExtensionSupported =
        m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced_coherent");

    if (!glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
        if (!m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced"))
            TCU_THROW(NotSupportedError, "GL_KHR_blend_equation_advanced is not supported");

    if (m_coherentBlending && !m_coherentExtensionSupported)
        TCU_THROW(NotSupportedError, "GL_KHR_blend_equation_advanced_coherent is not supported");

    TCU_CHECK(gl.blendBarrier);

    DE_ASSERT(!m_program);
    DE_ASSERT(!m_referenceRenderer);
    DE_ASSERT(!m_refColorBuffer);

    m_program = new glu::ShaderProgram(
        m_context.getRenderContext(),
        getBlendProgramSrc(sglr::rr_util::mapGLBlendEquationAdvanced(m_blendMode), m_context.getRenderContext()));
    m_testCtx.getLog() << *m_program;

    if (!m_program->isOk())
    {
        delete m_program;
        m_program = DE_NULL;
        TCU_FAIL("Compile failed");
    }

    m_referenceRenderer = new ReferenceQuadRenderer;
    m_refColorBuffer =
        new TextureLevel(TextureFormat(useSRGB ? TextureFormat::sRGBA : TextureFormat::RGBA, TextureFormat::UNORM_INT8),
                         m_viewportWidth, m_viewportHeight);

    if (useFbo)
    {
        const uint32_t format = useSRGB ? GL_SRGB8_ALPHA8 : GL_RGBA8;
        const int numSamples  = m_rtType == RENDERTARGETTYPE_MSAA_FBO ? 4 : 0;

        m_testCtx.getLog() << TestLog::Message << "Using FBO of size (" << m_renderWidth << ", " << m_renderHeight
                           << ") with format " << glu::getTextureFormatStr(format) << " and " << numSamples
                           << " samples" << TestLog::EndMessage;

        gl.genRenderbuffers(1, &m_colorRbo);
        gl.bindRenderbuffer(GL_RENDERBUFFER, m_colorRbo);
        gl.renderbufferStorageMultisample(GL_RENDERBUFFER, numSamples, format, m_renderWidth, m_renderHeight);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create color RBO");

        gl.genFramebuffers(1, &m_fbo);
        gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRbo);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create FBO");

        TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

        if (numSamples > 0)
        {
            // Create resolve FBO
            gl.genRenderbuffers(1, &m_resolveColorRbo);
            gl.bindRenderbuffer(GL_RENDERBUFFER, m_resolveColorRbo);
            gl.renderbufferStorageMultisample(GL_RENDERBUFFER, 0, format, m_renderWidth, m_renderHeight);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create resolve color RBO");

            gl.genFramebuffers(1, &m_resolveFbo);
            gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
            gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_resolveColorRbo);
            GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create FBO");

            TCU_CHECK(gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);

            gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        }

        if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()) && useSRGB)
            gl.enable(GL_FRAMEBUFFER_SRGB);
    }
    else
        DE_ASSERT(m_rtType == RENDERTARGETTYPE_DEFAULT);

    m_iterNdx = 0;
}

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

void AdvancedBlendCase::deinit(void)
{
    delete m_program;
    delete m_referenceRenderer;
    delete m_refColorBuffer;

    m_program           = DE_NULL;
    m_referenceRenderer = DE_NULL;
    m_refColorBuffer    = DE_NULL;

    if (m_colorRbo || m_fbo)
    {
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();

        gl.bindRenderbuffer(GL_RENDERBUFFER, 0);
        gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

        if (m_colorRbo != 0)
        {
            gl.deleteRenderbuffers(1, &m_colorRbo);
            m_colorRbo = 0;
        }

        if (m_fbo != 0)
        {
            gl.deleteFramebuffers(1, &m_fbo);
            m_fbo = 0;
        }

        if (m_resolveColorRbo)
        {
            gl.deleteRenderbuffers(1, &m_resolveColorRbo);
            m_resolveColorRbo = 0;
        }

        if (m_resolveFbo)
        {
            gl.deleteRenderbuffers(1, &m_resolveFbo);
            m_resolveFbo = 0;
        }

        if (glu::isContextTypeGLCore(m_context.getRenderContext().getType()) && RENDERTARGETTYPE_SRGB_FBO == m_rtType)
            gl.disable(GL_FRAMEBUFFER_SRGB);
    }
}

static tcu::Vec4 randomColor(de::Random *rnd)
{
    const float rgbValues[]   = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f};
    const float alphaValues[] = {0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f, 0.9f, 1.0f};

    // \note Spec assumes premultiplied inputs.
    const float a = rnd->choose<float>(DE_ARRAY_BEGIN(alphaValues), DE_ARRAY_END(alphaValues));
    const float r = a * rnd->choose<float>(DE_ARRAY_BEGIN(rgbValues), DE_ARRAY_END(rgbValues));
    const float g = a * rnd->choose<float>(DE_ARRAY_BEGIN(rgbValues), DE_ARRAY_END(rgbValues));
    const float b = a * rnd->choose<float>(DE_ARRAY_BEGIN(rgbValues), DE_ARRAY_END(rgbValues));
    return tcu::Vec4(r, g, b, a);
}

static tcu::ConstPixelBufferAccess getLinearAccess(const tcu::ConstPixelBufferAccess &access)
{
    if (access.getFormat().order == TextureFormat::sRGBA)
        return tcu::ConstPixelBufferAccess(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8),
                                           access.getWidth(), access.getHeight(), access.getDepth(),
                                           access.getRowPitch(), access.getSlicePitch(), access.getDataPtr());
    else
        return access;
}

AdvancedBlendCase::IterateResult AdvancedBlendCase::iterate(void)
{
    const glu::RenderContext &renderCtx = m_context.getRenderContext();
    const glw::Functions &gl            = renderCtx.getFunctions();
    de::Random rnd(deStringHash(getName()) ^ deInt32Hash(m_iterNdx));
    const int viewportX        = rnd.getInt(0, m_renderWidth - m_viewportWidth);
    const int viewportY        = rnd.getInt(0, m_renderHeight - m_viewportHeight);
    const bool useFbo          = m_rtType != RENDERTARGETTYPE_DEFAULT;
    const bool requiresResolve = m_rtType == RENDERTARGETTYPE_MSAA_FBO;
    const int numQuads         = m_overdrawCount + 1;
    TextureLevel renderedImg(TextureFormat(TextureFormat::RGBA, TextureFormat::UNORM_INT8), m_viewportWidth,
                             m_viewportHeight);
    vector<Vec4> colors(numQuads * 4);

    for (vector<Vec4>::iterator col = colors.begin(); col != colors.end(); ++col)
        *col = randomColor(&rnd);

    // Render with GL.
    {
        const uint32_t program = m_program->getProgram();
        const int posLoc       = gl.getAttribLocation(program, "a_position");
        const int colorLoc     = gl.getAttribLocation(program, "a_color");
        uint32_t vao           = 0;
        const glu::Buffer indexBuffer(renderCtx);
        const glu::Buffer positionBuffer(renderCtx);
        const glu::Buffer colorBuffer(renderCtx);
        vector<Vec2> positions(numQuads * 4);
        vector<uint16_t> indices(numQuads * 6);
        const uint16_t singleQuadIndices[] = {0, 2, 1, 1, 2, 3};
        const Vec2 singleQuadPos[]         = {
            Vec2(-1.0f, -1.0f),
            Vec2(-1.0f, +1.0f),
            Vec2(+1.0f, -1.0f),
            Vec2(+1.0f, +1.0f),
        };

        TCU_CHECK(posLoc >= 0 && colorLoc >= 0);

        for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
        {
            std::copy(DE_ARRAY_BEGIN(singleQuadPos), DE_ARRAY_END(singleQuadPos), &positions[quadNdx * 4]);
            for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(singleQuadIndices); ndx++)
                indices[quadNdx * 6 + ndx] = (uint16_t)(quadNdx * 4 + singleQuadIndices[ndx]);
        }

        if (!glu::isContextTypeES(renderCtx.getType()))
        {
            gl.genVertexArrays(1, &vao);
            gl.bindVertexArray(vao);
        }

        gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer);
        gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size() * sizeof(indices[0])), &indices[0],
                      GL_STATIC_DRAW);

        gl.bindBuffer(GL_ARRAY_BUFFER, *positionBuffer);
        gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size() * sizeof(positions[0])), &positions[0],
                      GL_STATIC_DRAW);
        gl.enableVertexAttribArray(posLoc);
        gl.vertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

        gl.bindBuffer(GL_ARRAY_BUFFER, *colorBuffer);
        gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(colors.size() * sizeof(colors[0])), &colors[0],
                      GL_STATIC_DRAW);
        gl.enableVertexAttribArray(colorLoc);
        gl.vertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to create buffers");

        gl.useProgram(program);
        gl.viewport(viewportX, viewportY, m_viewportWidth, m_viewportHeight);
        gl.blendEquation(m_blendMode);

        // \note coherent extension enables GL_BLEND_ADVANCED_COHERENT_KHR by default
        if (m_coherentBlending)
            gl.enable(GL_BLEND_ADVANCED_COHERENT_KHR);
        else if (m_coherentExtensionSupported)
            gl.disable(GL_BLEND_ADVANCED_COHERENT_KHR);

        GLU_EXPECT_NO_ERROR(gl.getError(), "Failed to set render state");

        gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        gl.disable(GL_BLEND);
        gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL);
        gl.enable(GL_BLEND);

        if (!m_coherentBlending)
            gl.blendBarrier();

        if (m_coherentBlending)
        {
            gl.drawElements(GL_TRIANGLES, 6 * (numQuads - 1), GL_UNSIGNED_SHORT,
                            (const void *)(uintptr_t)(6 * sizeof(uint16_t)));
        }
        else
        {
            for (int quadNdx = 1; quadNdx < numQuads; quadNdx++)
            {
                gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT,
                                (const void *)(uintptr_t)(quadNdx * 6 * sizeof(uint16_t)));
                gl.blendBarrier();
            }
        }

        if (vao)
            gl.deleteVertexArrays(1, &vao);

        gl.flush();
        GLU_EXPECT_NO_ERROR(gl.getError(), "Render failed");
    }

    // Render reference.
    {
        rr::FragmentOperationState referenceState;
        const tcu::PixelBufferAccess colorAccess =
            gls::FragmentOpUtil::getMultisampleAccess(m_refColorBuffer->getAccess());
        const tcu::PixelBufferAccess nullAccess = tcu::PixelBufferAccess();
        IntegerQuad quad;

        if (!useFbo && m_context.getRenderTarget().getPixelFormat().alphaBits == 0)
        {
            // Emulate lack of alpha by clearing to 1 and masking out alpha writes
            tcu::clear(*m_refColorBuffer, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
            referenceState.colorMask = tcu::BVec4(true, true, true, false);
        }

        referenceState.blendEquationAdvaced = sglr::rr_util::mapGLBlendEquationAdvanced(m_blendMode);

        quad.posA = tcu::IVec2(0, 0);
        quad.posB = tcu::IVec2(m_viewportWidth - 1, m_viewportHeight - 1);

        for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
        {
            referenceState.blendMode = quadNdx == 0 ? rr::BLENDMODE_NONE : rr::BLENDMODE_ADVANCED;
            std::copy(&colors[4 * quadNdx], &colors[4 * quadNdx] + 4, &quad.color[0]);
            m_referenceRenderer->render(colorAccess, nullAccess /* no depth */, nullAccess /* no stencil */, quad,
                                        referenceState);
        }
    }

    if (requiresResolve)
    {
        gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo);
        gl.blitFramebuffer(0, 0, m_renderWidth, m_renderHeight, 0, 0, m_renderWidth, m_renderHeight,
                           GL_COLOR_BUFFER_BIT, GL_NEAREST);
        GLU_EXPECT_NO_ERROR(gl.getError(), "Resolve blit failed");

        gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo);
    }

    glu::readPixels(renderCtx, viewportX, viewportY, renderedImg.getAccess());
    GLU_EXPECT_NO_ERROR(gl.getError(), "glReadPixels()");

    if (requiresResolve)
        gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);

    {
        const bool isHSLMode = m_blendMode == GL_HSL_HUE || m_blendMode == GL_HSL_SATURATION ||
                               m_blendMode == GL_HSL_COLOR || m_blendMode == GL_HSL_LUMINOSITY;
        bool comparePass = false;

        if (isHSLMode)
        {
            // Compensate for more demanding HSL code by using fuzzy comparison.
            const float threshold = 0.002f;
            comparePass           = tcu::fuzzyCompare(m_testCtx.getLog(), "CompareResult", "Image Comparison Result",
                                                      getLinearAccess(m_refColorBuffer->getAccess()), renderedImg.getAccess(),
                                                      threshold, tcu::COMPARE_LOG_RESULT);
        }
        else
        {
            const UVec4 compareThreshold =
                (useFbo ? tcu::PixelFormat(8, 8, 8, 8) : m_context.getRenderTarget().getPixelFormat())
                        .getColorThreshold()
                        .toIVec()
                        .asUint() *
                    UVec4(5) / UVec4(2) +
                UVec4(3 * m_overdrawCount);

            comparePass = tcu::bilinearCompare(
                m_testCtx.getLog(), "CompareResult", "Image Comparison Result",
                getLinearAccess(m_refColorBuffer->getAccess()), renderedImg.getAccess(),
                tcu::RGBA(compareThreshold[0], compareThreshold[1], compareThreshold[2], compareThreshold[3]),
                tcu::COMPARE_LOG_RESULT);
        }

        if (!comparePass)
        {
            m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");
            return STOP;
        }
    }

    m_iterNdx += 1;

    if (m_iterNdx < m_numIters)
        return CONTINUE;
    else
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        return STOP;
    }
}

class BlendAdvancedCoherentStateCase : public TestCase
{
public:
    BlendAdvancedCoherentStateCase(Context &context, const char *name, const char *description,
                                   gls::StateQueryUtil::QueryType type);

private:
    IterateResult iterate(void);

    const gls::StateQueryUtil::QueryType m_type;
};

BlendAdvancedCoherentStateCase::BlendAdvancedCoherentStateCase(Context &context, const char *name,
                                                               const char *description,
                                                               gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
{
}

BlendAdvancedCoherentStateCase::IterateResult BlendAdvancedCoherentStateCase::iterate(void)
{
    TCU_CHECK_AND_THROW(NotSupportedError,
                        m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced_coherent"),
                        "GL_KHR_blend_equation_advanced_coherent is not supported");

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

    gl.enableLogging(true);

    // check inital value
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Initial", "Initial");
        gls::StateQueryUtil::verifyStateBoolean(result, gl, GL_BLEND_ADVANCED_COHERENT_KHR, true, m_type);
    }

    // check toggle
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Toggle", "Toggle");
        gl.glDisable(GL_BLEND_ADVANCED_COHERENT_KHR);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "glDisable");

        gls::StateQueryUtil::verifyStateBoolean(result, gl, GL_BLEND_ADVANCED_COHERENT_KHR, false, m_type);

        gl.glEnable(GL_BLEND_ADVANCED_COHERENT_KHR);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "glEnable");

        gls::StateQueryUtil::verifyStateBoolean(result, gl, GL_BLEND_ADVANCED_COHERENT_KHR, true, m_type);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BlendEquationStateCase : public TestCase
{
public:
    BlendEquationStateCase(Context &context, const char *name, const char *description, const glw::GLenum *equations,
                           int numEquations, gls::StateQueryUtil::QueryType type);

private:
    IterateResult iterate(void);

    const gls::StateQueryUtil::QueryType m_type;
    const glw::GLenum *m_equations;
    const int m_numEquations;
};

BlendEquationStateCase::BlendEquationStateCase(Context &context, const char *name, const char *description,
                                               const glw::GLenum *equations, int numEquations,
                                               gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
    , m_equations(equations)
    , m_numEquations(numEquations)
{
}

BlendEquationStateCase::IterateResult BlendEquationStateCase::iterate(void)
{
    if (!glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)))
        TCU_CHECK_AND_THROW(NotSupportedError,
                            m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced"),
                            "GL_KHR_blend_equation_advanced is not supported");

    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 < m_numEquations; ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type",
                                            "Test " + de::toString(glu::getBlendEquationStr(m_equations[ndx])));

        gl.glBlendEquation(m_equations[ndx]);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "glBlendEquation");

        gls::StateQueryUtil::verifyStateInteger(result, gl, GL_BLEND_EQUATION, m_equations[ndx], m_type);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

class BlendEquationIndexedStateCase : public TestCase
{
public:
    BlendEquationIndexedStateCase(Context &context, const char *name, const char *description,
                                  const glw::GLenum *equations, int numEquations, gls::StateQueryUtil::QueryType type);

private:
    IterateResult iterate(void);

    const gls::StateQueryUtil::QueryType m_type;
    const glw::GLenum *m_equations;
    const int m_numEquations;
};

BlendEquationIndexedStateCase::BlendEquationIndexedStateCase(Context &context, const char *name,
                                                             const char *description, const glw::GLenum *equations,
                                                             int numEquations, gls::StateQueryUtil::QueryType type)
    : TestCase(context, name, description)
    , m_type(type)
    , m_equations(equations)
    , m_numEquations(numEquations)
{
}

BlendEquationIndexedStateCase::IterateResult BlendEquationIndexedStateCase::iterate(void)
{
    const auto &renderContext = m_context.getRenderContext();
    if (!glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2)) &&
        !glu::contextSupports(renderContext.getType(), glu::ApiType::core(4, 5)))
    {
        TCU_CHECK_AND_THROW(NotSupportedError,
                            m_context.getContextInfo().isExtensionSupported("GL_KHR_blend_equation_advanced"),
                            "GL_KHR_blend_equation_advanced is not supported");
        TCU_CHECK_AND_THROW(NotSupportedError,
                            m_context.getContextInfo().isExtensionSupported("GL_EXT_draw_buffers_indexed"),
                            "GL_EXT_draw_buffers_indexed is not supported");
    }

    glu::CallLogWrapper gl(renderContext.getFunctions(), m_testCtx.getLog());
    tcu::ResultCollector result(m_testCtx.getLog(), " // ERROR: ");

    gl.enableLogging(true);

    for (int ndx = 0; ndx < m_numEquations; ++ndx)
    {
        const tcu::ScopedLogSection section(m_testCtx.getLog(), "Type",
                                            "Test " + de::toString(glu::getBlendEquationStr(m_equations[ndx])));

        gl.glBlendEquationi(2, m_equations[ndx]);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "glBlendEquationi");

        gls::StateQueryUtil::verifyStateIndexedInteger(result, gl, GL_BLEND_EQUATION, 2, m_equations[ndx], m_type);
    }

    result.setTestContextResult(m_testCtx);
    return STOP;
}

} // namespace

AdvancedBlendTests::AdvancedBlendTests(Context &context)
    : TestCaseGroup(context, "blend_equation_advanced", "GL_blend_equation_advanced Tests")
{
}

AdvancedBlendTests::~AdvancedBlendTests(void)
{
}

void AdvancedBlendTests::init(void)
{
    static const glw::GLenum s_blendEquations[] = {
        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,

    };

    tcu::TestCaseGroup *const stateQueryGroup = new tcu::TestCaseGroup(m_testCtx, "state_query", "State query tests");
    tcu::TestCaseGroup *const basicGroup      = new tcu::TestCaseGroup(m_testCtx, "basic", "Single quad only");
    tcu::TestCaseGroup *const srgbGroup = new tcu::TestCaseGroup(m_testCtx, "srgb", "Advanced blending with sRGB FBO");
    tcu::TestCaseGroup *const msaaGroup = new tcu::TestCaseGroup(m_testCtx, "msaa", "Advanced blending with MSAA FBO");
    tcu::TestCaseGroup *const barrierGroup =
        new tcu::TestCaseGroup(m_testCtx, "barrier", "Multiple overlapping quads with blend barriers");
    tcu::TestCaseGroup *const coherentGroup =
        new tcu::TestCaseGroup(m_testCtx, "coherent", "Overlapping quads with coherent blending");
    tcu::TestCaseGroup *const coherentMsaaGroup =
        new tcu::TestCaseGroup(m_testCtx, "coherent_msaa", "Overlapping quads with coherent blending with MSAA FBO");

    addChild(stateQueryGroup);
    addChild(basicGroup);
    addChild(srgbGroup);
    addChild(msaaGroup);
    addChild(barrierGroup);
    addChild(coherentGroup);
    addChild(coherentMsaaGroup);

    // .state_query
    {
        using namespace gls::StateQueryUtil;

        stateQueryGroup->addChild(new BlendAdvancedCoherentStateCase(
            m_context, "blend_advanced_coherent_getboolean", "Test BLEND_ADVANCED_COHERENT_KHR", QUERY_BOOLEAN));
        stateQueryGroup->addChild(new BlendAdvancedCoherentStateCase(
            m_context, "blend_advanced_coherent_isenabled", "Test BLEND_ADVANCED_COHERENT_KHR", QUERY_ISENABLED));
        stateQueryGroup->addChild(new BlendAdvancedCoherentStateCase(
            m_context, "blend_advanced_coherent_getinteger", "Test BLEND_ADVANCED_COHERENT_KHR", QUERY_INTEGER));
        stateQueryGroup->addChild(new BlendAdvancedCoherentStateCase(
            m_context, "blend_advanced_coherent_getinteger64", "Test BLEND_ADVANCED_COHERENT_KHR", QUERY_INTEGER64));
        stateQueryGroup->addChild(new BlendAdvancedCoherentStateCase(m_context, "blend_advanced_coherent_getfloat",
                                                                     "Test BLEND_ADVANCED_COHERENT_KHR", QUERY_FLOAT));

        stateQueryGroup->addChild(new BlendEquationStateCase(m_context, "blend_equation_getboolean",
                                                             "Test BLEND_EQUATION", s_blendEquations,
                                                             DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_BOOLEAN));
        stateQueryGroup->addChild(new BlendEquationStateCase(m_context, "blend_equation_getinteger",
                                                             "Test BLEND_EQUATION", s_blendEquations,
                                                             DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_INTEGER));
        stateQueryGroup->addChild(new BlendEquationStateCase(m_context, "blend_equation_getinteger64",
                                                             "Test BLEND_EQUATION", s_blendEquations,
                                                             DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_INTEGER64));
        stateQueryGroup->addChild(new BlendEquationStateCase(m_context, "blend_equation_getfloat",
                                                             "Test BLEND_EQUATION", s_blendEquations,
                                                             DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_FLOAT));

        stateQueryGroup->addChild(new BlendEquationIndexedStateCase(
            m_context, "blend_equation_getbooleani_v", "Test per-attchment BLEND_EQUATION", s_blendEquations,
            DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_INDEXED_BOOLEAN));
        stateQueryGroup->addChild(new BlendEquationIndexedStateCase(
            m_context, "blend_equation_getintegeri_v", "Test per-attchment BLEND_EQUATION", s_blendEquations,
            DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_INDEXED_INTEGER));
        stateQueryGroup->addChild(new BlendEquationIndexedStateCase(
            m_context, "blend_equation_getinteger64i_v", "Test per-attchment BLEND_EQUATION", s_blendEquations,
            DE_LENGTH_OF_ARRAY(s_blendEquations), QUERY_INDEXED_INTEGER64));
    }

    // others
    for (int modeNdx = 0; modeNdx < DE_LENGTH_OF_ARRAY(s_blendEquations); modeNdx++)
    {
        const char *const name = getEquationName(s_blendEquations[modeNdx]);
        const char *const desc = "";
        const uint32_t mode    = s_blendEquations[modeNdx];

        basicGroup->addChild(new AdvancedBlendCase(m_context, name, desc, mode, 1, false, RENDERTARGETTYPE_DEFAULT));
        srgbGroup->addChild(new AdvancedBlendCase(m_context, name, desc, mode, 1, false, RENDERTARGETTYPE_SRGB_FBO));
        msaaGroup->addChild(new AdvancedBlendCase(m_context, name, desc, mode, 1, false, RENDERTARGETTYPE_MSAA_FBO));
        barrierGroup->addChild(new AdvancedBlendCase(m_context, name, desc, mode, 4, false, RENDERTARGETTYPE_DEFAULT));
        coherentGroup->addChild(new AdvancedBlendCase(m_context, name, desc, mode, 4, true, RENDERTARGETTYPE_DEFAULT));
        coherentMsaaGroup->addChild(
            new AdvancedBlendCase(m_context, name, desc, mode, 4, true, RENDERTARGETTYPE_MSAA_FBO));
    }
}

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