/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Multisample tests
 *//*--------------------------------------------------------------------*/

#include "es31fMultisampleTests.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuVector.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuStringTemplate.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deString.h"
#include "deMath.h"

using namespace glw;

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

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using std::map;
using std::string;

static std::string sampleMaskToString(const std::vector<uint32_t> &bitfield, int numBits)
{
    std::string result(numBits, '0');

    // move from back to front and set chars to 1
    for (int wordNdx = 0; wordNdx < (int)bitfield.size(); ++wordNdx)
    {
        for (int bit = 0; bit < 32; ++bit)
        {
            const int targetCharNdx = numBits - (wordNdx * 32 + bit) - 1;

            // beginning of the string reached
            if (targetCharNdx < 0)
                return result;

            if ((bitfield[wordNdx] >> bit) & 0x01)
                result[targetCharNdx] = '1';
        }
    }

    return result;
}

/*--------------------------------------------------------------------*//*!
 * \brief Returns the number of words needed to represent mask of given length
 *//*--------------------------------------------------------------------*/
static int getEffectiveSampleMaskWordCount(int highestBitNdx)
{
    const int wordSize = 32;
    const int maskLen  = highestBitNdx + 1;

    return ((maskLen - 1) / wordSize) + 1; // round_up(mask_len /  wordSize)
}

/*--------------------------------------------------------------------*//*!
 * \brief Creates sample mask with all less significant bits than nthBit set
 *//*--------------------------------------------------------------------*/
static std::vector<uint32_t> genAllSetToNthBitSampleMask(int nthBit)
{
    const int wordSize         = 32;
    const int numWords         = getEffectiveSampleMaskWordCount(nthBit - 1);
    const uint32_t topWordBits = (uint32_t)(nthBit - (numWords - 1) * wordSize);
    std::vector<uint32_t> mask(numWords);

    for (int ndx = 0; ndx < numWords - 1; ++ndx)
        mask[ndx] = 0xFFFFFFFF;

    mask[numWords - 1] = deBitMask32(0, (int)topWordBits);
    return mask;
}

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

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

SamplePosQueryCase::SamplePosQueryCase(Context &context, const char *name, const char *desc)
    : TestCase(context, name, desc)
{
}

void SamplePosQueryCase::init(void)
{
    if (m_context.getRenderTarget().getNumSamples() == 0)
        throw tcu::NotSupportedError("No multisample buffers");
}

SamplePosQueryCase::IterateResult SamplePosQueryCase::iterate(void)
{
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    bool error = false;

    gl.enableLogging(true);

    for (int ndx = 0; ndx < m_context.getRenderTarget().getNumSamples(); ++ndx)
    {
        tcu::Vec2 samplePos = tcu::Vec2(-1, -1);

        gl.glGetMultisamplefv(GL_SAMPLE_POSITION, ndx, samplePos.getPtr());
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "getMultisamplefv");

        // check value range
        if (samplePos.x() < 0.0f || samplePos.x() > 1.0f || samplePos.y() < 0.0f || samplePos.y() > 1.0f)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Sample " << ndx << " is not in valid range [0,1], got "
                               << samplePos << tcu::TestLog::EndMessage;
            error = true;
        }
    }

    if (!error)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid sample pos");

    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Abstract base class handling common stuff for default fbo multisample cases.
 *//*--------------------------------------------------------------------*/
class DefaultFBOMultisampleCase : public TestCase
{
public:
    DefaultFBOMultisampleCase(Context &context, const char *name, const char *desc, int desiredViewportSize);
    virtual ~DefaultFBOMultisampleCase(void);

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

protected:
    void renderTriangle(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec4 &c0, const Vec4 &c1,
                        const Vec4 &c2) const;
    void renderTriangle(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec4 &c0, const Vec4 &c1,
                        const Vec4 &c2) const;
    void renderTriangle(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec4 &color) const;
    void renderQuad(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, const Vec4 &c0, const Vec4 &c1,
                    const Vec4 &c2, const Vec4 &c3) const;
    void renderQuad(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, const Vec4 &color) const;

    void randomizeViewport(void);
    void readImage(tcu::Surface &dst) const;

    int m_numSamples;

    int m_viewportSize;

private:
    DefaultFBOMultisampleCase(const DefaultFBOMultisampleCase &other);
    DefaultFBOMultisampleCase &operator=(const DefaultFBOMultisampleCase &other);

    const int m_desiredViewportSize;

    glu::ShaderProgram *m_program;
    int m_attrPositionLoc;
    int m_attrColorLoc;

    int m_viewportX;
    int m_viewportY;
    de::Random m_rnd;

    bool m_initCalled;
};

DefaultFBOMultisampleCase::DefaultFBOMultisampleCase(Context &context, const char *name, const char *desc,
                                                     int desiredViewportSize)
    : TestCase(context, name, desc)
    , m_numSamples(0)
    , m_viewportSize(0)
    , m_desiredViewportSize(desiredViewportSize)
    , m_program(DE_NULL)
    , m_attrPositionLoc(-1)
    , m_attrColorLoc(-1)
    , m_viewportX(0)
    , m_viewportY(0)
    , m_rnd(deStringHash(name))
    , m_initCalled(false)
{
}

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

void DefaultFBOMultisampleCase::init(void)
{
    const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                               getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);

    static const char *vertShaderSource = "${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 *fragShaderSource = "${GLSL_VERSION_DECL}\n"
                                          "in mediump vec4 v_color;\n"
                                          "layout(location = 0) out mediump vec4 o_color;\n"
                                          "void main()\n"
                                          "{\n"
                                          "    o_color = v_color;\n"
                                          "}\n";

    TestLog &log             = m_testCtx.getLog();
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    if (m_context.getRenderTarget().getNumSamples() <= 1)
        throw tcu::NotSupportedError("No multisample buffers");

    m_initCalled = true;

    // Query and log number of samples per pixel.

    gl.getIntegerv(GL_SAMPLES, &m_numSamples);
    GLU_EXPECT_NO_ERROR(gl.getError(), "getIntegerv(GL_SAMPLES)");
    log << TestLog::Message << "GL_SAMPLES = " << m_numSamples << TestLog::EndMessage;

    // Prepare program.

    DE_ASSERT(!m_program);

    m_program = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(tcu::StringTemplate(vertShaderSource).specialize(args))
                              << glu::FragmentSource(tcu::StringTemplate(fragShaderSource).specialize(args)));
    if (!m_program->isOk())
        throw tcu::TestError("Failed to compile program", DE_NULL, __FILE__, __LINE__);

    m_attrPositionLoc = gl.getAttribLocation(m_program->getProgram(), "a_position");
    m_attrColorLoc    = gl.getAttribLocation(m_program->getProgram(), "a_color");
    GLU_EXPECT_NO_ERROR(gl.getError(), "getAttribLocation");

    if (m_attrPositionLoc < 0 || m_attrColorLoc < 0)
    {
        delete m_program;
        throw tcu::TestError("Invalid attribute locations", DE_NULL, __FILE__, __LINE__);
    }

    // Get suitable viewport size.

    m_viewportSize = de::min<int>(m_desiredViewportSize, de::min(m_context.getRenderTarget().getWidth(),
                                                                 m_context.getRenderTarget().getHeight()));
    randomizeViewport();
}

void DefaultFBOMultisampleCase::deinit(void)
{
    // Do not try to call GL functions during case list creation
    if (!m_initCalled)
        return;

    delete m_program;
    m_program = DE_NULL;
}

void DefaultFBOMultisampleCase::renderTriangle(const Vec3 &p0, const Vec3 &p1, const Vec3 &p2, const Vec4 &c0,
                                               const Vec4 &c1, const Vec4 &c2) const
{
    const float vertexPositions[] = {p0.x(), p0.y(), p0.z(), 1.0f,   p1.x(), p1.y(),
                                     p1.z(), 1.0f,   p2.x(), p2.y(), p2.z(), 1.0f};
    const float vertexColors[]    = {
        c0.x(), c0.y(), c0.z(), c0.w(), c1.x(), c1.y(), c1.z(), c1.w(), c2.x(), c2.y(), c2.z(), c2.w(),
    };

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    glu::Buffer vtxBuf(m_context.getRenderContext());
    glu::Buffer colBuf(m_context.getRenderContext());
    glu::VertexArray vao(m_context.getRenderContext());

    gl.bindVertexArray(*vao);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");

    gl.bindBuffer(GL_ARRAY_BUFFER, *vtxBuf);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), &vertexPositions[0], GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vtx buf");

    gl.enableVertexAttribArray(m_attrPositionLoc);
    gl.vertexAttribPointer(m_attrPositionLoc, 4, GL_FLOAT, false, 0, DE_NULL);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vtx vertexAttribPointer");

    gl.bindBuffer(GL_ARRAY_BUFFER, *colBuf);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(vertexColors), &vertexColors[0], GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "col buf");

    gl.enableVertexAttribArray(m_attrColorLoc);
    gl.vertexAttribPointer(m_attrColorLoc, 4, GL_FLOAT, false, 0, DE_NULL);
    GLU_EXPECT_NO_ERROR(gl.getError(), "col vertexAttribPointer");

    gl.useProgram(m_program->getProgram());
    gl.drawArrays(GL_TRIANGLES, 0, 3);
    GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
}

void DefaultFBOMultisampleCase::renderTriangle(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec4 &c0,
                                               const Vec4 &c1, const Vec4 &c2) const
{
    renderTriangle(Vec3(p0.x(), p0.y(), 0.0f), Vec3(p1.x(), p1.y(), 0.0f), Vec3(p2.x(), p2.y(), 0.0f), c0, c1, c2);
}

void DefaultFBOMultisampleCase::renderTriangle(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec4 &color) const
{
    renderTriangle(p0, p1, p2, color, color, color);
}

void DefaultFBOMultisampleCase::renderQuad(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
                                           const Vec4 &c0, const Vec4 &c1, const Vec4 &c2, const Vec4 &c3) const
{
    renderTriangle(p0, p1, p2, c0, c1, c2);
    renderTriangle(p2, p1, p3, c2, c1, c3);
}

void DefaultFBOMultisampleCase::renderQuad(const Vec2 &p0, const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
                                           const Vec4 &color) const
{
    renderQuad(p0, p1, p2, p3, color, color, color, color);
}

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

    m_viewportX = m_rnd.getInt(0, m_context.getRenderTarget().getWidth() - m_viewportSize);
    m_viewportY = m_rnd.getInt(0, m_context.getRenderTarget().getHeight() - m_viewportSize);

    gl.viewport(m_viewportX, m_viewportY, m_viewportSize, m_viewportSize);
    GLU_EXPECT_NO_ERROR(gl.getError(), "viewport");
}

void DefaultFBOMultisampleCase::readImage(tcu::Surface &dst) const
{
    glu::readPixels(m_context.getRenderContext(), m_viewportX, m_viewportY, dst.getAccess());
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests coverage mask inversion validity.
 *
 * Tests that the coverage masks obtained by masks set with glSampleMaski(mask)
 * and glSampleMaski(~mask) are indeed each others' inverses.
 *
 * This is done by drawing a pattern, with varying coverage values,
 * overlapped by a pattern that has inverted masks and is otherwise
 * identical. The resulting image is compared to one obtained by drawing
 * the same pattern but with all-ones coverage masks.
 *//*--------------------------------------------------------------------*/
class MaskInvertCase : public DefaultFBOMultisampleCase
{
public:
    MaskInvertCase(Context &context, const char *name, const char *description);
    ~MaskInvertCase(void)
    {
    }

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

private:
    void drawPattern(bool invert) const;
};

MaskInvertCase::MaskInvertCase(Context &context, const char *name, const char *description)
    : DefaultFBOMultisampleCase(context, name, description, 256)
{
}

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

    // check the test is even possible

    GLint maxSampleMaskWords = 0;
    gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
    if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
        throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

    // normal init
    DefaultFBOMultisampleCase::init();
}

MaskInvertCase::IterateResult MaskInvertCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();
    tcu::Surface renderedImgNoSampleCoverage(m_viewportSize, m_viewportSize);
    tcu::Surface renderedImgSampleCoverage(m_viewportSize, m_viewportSize);

    randomizeViewport();

    gl.enable(GL_BLEND);
    gl.blendEquation(GL_FUNC_ADD);
    gl.blendFunc(GL_ONE, GL_ONE);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set blend");
    log << TestLog::Message << "Additive blending enabled in order to detect (erroneously) overlapping samples"
        << TestLog::EndMessage;

    log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
    gl.clearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK disabled" << TestLog::EndMessage;
    drawPattern(false);
    readImage(renderedImgNoSampleCoverage);

    log << TestLog::Image("RenderedImageNoSampleMask", "Rendered image with GL_SAMPLE_MASK disabled",
                          renderedImgNoSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);

    log << TestLog::Message << "Clearing color to all-zeros" << TestLog::EndMessage;
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    gl.enable(GL_SAMPLE_MASK);
    GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)");

    log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using non-inverted sample masks"
        << TestLog::EndMessage;
    drawPattern(false);
    log << TestLog::Message << "Drawing the pattern with GL_SAMPLE_MASK enabled, using inverted sample masks"
        << TestLog::EndMessage;
    drawPattern(true);

    readImage(renderedImgSampleCoverage);

    log << TestLog::Image("RenderedImageSampleMask", "Rendered image with GL_SAMPLE_MASK enabled",
                          renderedImgSampleCoverage, QP_IMAGE_COMPRESSION_MODE_PNG);

    bool passed = tcu::pixelThresholdCompare(
        log, "CoverageVsNoCoverage", "Comparison of same pattern with GL_SAMPLE_MASK disabled and enabled",
        renderedImgNoSampleCoverage, renderedImgSampleCoverage, tcu::RGBA(0), tcu::COMPARE_LOG_ON_ERROR);

    if (passed)
        log << TestLog::Message << "Success: The two images rendered are identical" << TestLog::EndMessage;

    m_context.getTestContext().setTestResult(passed ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                             passed ? "Passed" : "Failed");

    return STOP;
}

void MaskInvertCase::drawPattern(bool invert) const
{
    const int numTriangles   = 25;
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    for (int triNdx = 0; triNdx < numTriangles; triNdx++)
    {
        const float angle0 = 2.0f * DE_PI * (float)triNdx / (float)numTriangles;
        const float angle1 = 2.0f * DE_PI * ((float)triNdx + 0.5f) / (float)numTriangles;
        const Vec4 color =
            Vec4(0.4f + (float)triNdx / (float)numTriangles * 0.6f, 0.5f + (float)triNdx / (float)numTriangles * 0.3f,
                 0.6f - (float)triNdx / (float)numTriangles * 0.5f, 0.7f - (float)triNdx / (float)numTriangles * 0.7f);

        const int wordCount            = getEffectiveSampleMaskWordCount(m_numSamples - 1);
        const GLbitfield finalWordBits = m_numSamples - 32 * ((m_numSamples - 1) / 32);
        const GLbitfield finalWordMask = (GLbitfield)deBitMask32(0, (int)finalWordBits);

        for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
        {
            const GLbitfield rawMask = (GLbitfield)deUint32Hash(wordNdx * 32 + triNdx);
            const GLbitfield mask    = (invert) ? (~rawMask) : (rawMask);
            const bool isFinalWord   = (wordNdx + 1) == wordCount;
            const GLbitfield maskMask =
                (isFinalWord) ? (finalWordMask) :
                                (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count

            gl.sampleMaski(wordNdx, mask & maskMask);
        }
        GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");

        renderTriangle(Vec2(0.0f, 0.0f), Vec2(deFloatCos(angle0) * 0.95f, deFloatSin(angle0) * 0.95f),
                       Vec2(deFloatCos(angle1) * 0.95f, deFloatSin(angle1) * 0.95f), color);
    }
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests coverage mask generation proportionality property.
 *
 * Tests that the number of coverage bits in a coverage mask set with
 * glSampleMaski is, on average, proportional to the number of set bits.
 * Draws multiple frames, each time increasing the number of mask bits set
 * and checks that the average color is changing appropriately.
 *//*--------------------------------------------------------------------*/
class MaskProportionalityCase : public DefaultFBOMultisampleCase
{
public:
    MaskProportionalityCase(Context &context, const char *name, const char *description);
    ~MaskProportionalityCase(void)
    {
    }

    void init(void);

    IterateResult iterate(void);

private:
    int m_numIterations;
    int m_currentIteration;

    int32_t m_previousIterationColorSum;
};

MaskProportionalityCase::MaskProportionalityCase(Context &context, const char *name, const char *description)
    : DefaultFBOMultisampleCase(context, name, description, 32)
    , m_numIterations(-1)
    , m_currentIteration(0)
    , m_previousIterationColorSum(-1)
{
}

void MaskProportionalityCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();

    // check the test is even possible
    GLint maxSampleMaskWords = 0;
    gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
    if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
        throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

    DefaultFBOMultisampleCase::init();

    // set state
    gl.enable(GL_SAMPLE_MASK);
    GLU_EXPECT_NO_ERROR(gl.getError(), "glEnable(GL_SAMPLE_MASK)");
    log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;

    m_numIterations = m_numSamples + 1;

    randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate.
}

MaskProportionalityCase::IterateResult MaskProportionalityCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();
    tcu::Surface renderedImg(m_viewportSize, m_viewportSize);
    int32_t numPixels = (int32_t)renderedImg.getWidth() * (int32_t)renderedImg.getHeight();

    DE_ASSERT(m_numIterations >= 0);

    log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
    gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    // Draw quad.

    {
        const Vec2 pt0(-1.0f, -1.0f);
        const Vec2 pt1(1.0f, -1.0f);
        const Vec2 pt2(-1.0f, 1.0f);
        const Vec2 pt3(1.0f, 1.0f);
        Vec4 quadColor(1.0f, 0.0f, 0.0f, 1.0f);
        const std::vector<uint32_t> sampleMask = genAllSetToNthBitSampleMask(m_currentIteration);

        DE_ASSERT(m_currentIteration <= m_numSamples + 1);

        log << TestLog::Message << "Drawing a red quad using sample mask 0b"
            << sampleMaskToString(sampleMask, m_numSamples) << TestLog::EndMessage;

        for (int wordNdx = 0; wordNdx < getEffectiveSampleMaskWordCount(m_numSamples - 1); ++wordNdx)
        {
            const GLbitfield mask = (wordNdx < (int)sampleMask.size()) ? ((GLbitfield)(sampleMask[wordNdx])) : (0);

            gl.sampleMaski(wordNdx, mask);
            GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
        }

        renderQuad(pt0, pt1, pt2, pt3, quadColor);
    }

    // Read ang log image.

    readImage(renderedImg);

    log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);

    // Compute average red component in rendered image.

    int32_t sumRed = 0;

    for (int y = 0; y < renderedImg.getHeight(); y++)
        for (int x = 0; x < renderedImg.getWidth(); x++)
            sumRed += renderedImg.getPixel(x, y).getRed();

    log << TestLog::Message
        << "Average red color component: " << de::floatToString((float)sumRed / 255.0f / (float)numPixels, 2)
        << TestLog::EndMessage;

    // Check if average color has decreased from previous frame's color.

    if (sumRed < m_previousIterationColorSum)
    {
        log << TestLog::Message << "Failure: Current average red color component is lower than previous"
            << TestLog::EndMessage;
        m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
        return STOP;
    }

    // Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted).

    if (m_currentIteration == 0 && sumRed != 0)
    {
        log << TestLog::Message << "Failure: Image should be completely black" << TestLog::EndMessage;
        m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
        return STOP;
    }

    if (m_currentIteration == m_numIterations - 1 && sumRed != 0xff * numPixels)
    {
        log << TestLog::Message << "Failure: Image should be completely red" << TestLog::EndMessage;

        m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
        return STOP;
    }

    m_previousIterationColorSum = sumRed;

    m_currentIteration++;

    if (m_currentIteration >= m_numIterations)
    {
        log << TestLog::Message
            << "Success: Number of coverage mask bits set appears to be, on average, proportional to the number of set "
               "sample mask bits"
            << TestLog::EndMessage;
        m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");
        return STOP;
    }
    else
        return CONTINUE;
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests coverage mask generation constancy property.
 *
 * Tests that the coverage mask created by GL_SAMPLE_MASK is constant at
 * given pixel coordinates. Draws two quads, with the second one fully
 * overlapping the first one such that at any given pixel, both quads have
 * the same coverage mask value. This way, if the constancy property is
 * fulfilled, only the second quad should be visible.
 *//*--------------------------------------------------------------------*/
class MaskConstancyCase : public DefaultFBOMultisampleCase
{
public:
    enum CaseBits
    {
        CASEBIT_ALPHA_TO_COVERAGE        = 1, //!< Use alpha-to-coverage.
        CASEBIT_SAMPLE_COVERAGE          = 2, //!< Use sample coverage.
        CASEBIT_SAMPLE_COVERAGE_INVERTED = 4, //!< Inverted sample coverage.
        CASEBIT_SAMPLE_MASK              = 8, //!< Use sample mask.
    };

    MaskConstancyCase(Context &context, const char *name, const char *description, uint32_t typeBits);
    ~MaskConstancyCase(void)
    {
    }

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

private:
    const bool m_isAlphaToCoverageCase;
    const bool m_isSampleCoverageCase;
    const bool m_isInvertedSampleCoverageCase;
    const bool m_isSampleMaskCase;
};

MaskConstancyCase::MaskConstancyCase(Context &context, const char *name, const char *description, uint32_t typeBits)
    : DefaultFBOMultisampleCase(context, name, description, 256)
    , m_isAlphaToCoverageCase(0 != (typeBits & CASEBIT_ALPHA_TO_COVERAGE))
    , m_isSampleCoverageCase(0 != (typeBits & CASEBIT_SAMPLE_COVERAGE))
    , m_isInvertedSampleCoverageCase(0 != (typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED))
    , m_isSampleMaskCase(0 != (typeBits & CASEBIT_SAMPLE_MASK))
{
    // CASEBIT_SAMPLE_COVERAGE_INVERT => CASEBIT_SAMPLE_COVERAGE
    DE_ASSERT((typeBits & CASEBIT_SAMPLE_COVERAGE) || ~(typeBits & CASEBIT_SAMPLE_COVERAGE_INVERTED));
    DE_ASSERT(m_isSampleMaskCase); // no point testing non-sample-mask cases, they are checked already in gles3
}

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

    // check the test is even possible
    if (m_isSampleMaskCase)
    {
        GLint maxSampleMaskWords = 0;
        gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
        if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
            throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");
    }

    // normal init
    DefaultFBOMultisampleCase::init();
}

MaskConstancyCase::IterateResult MaskConstancyCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();
    tcu::Surface renderedImg(m_viewportSize, m_viewportSize);

    randomizeViewport();

    log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    if (m_isAlphaToCoverageCase)
    {
        gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
        gl.colorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
        GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_ALPHA_TO_COVERAGE");

        log << TestLog::Message << "GL_SAMPLE_ALPHA_TO_COVERAGE is enabled" << TestLog::EndMessage;
        log << TestLog::Message << "Color mask is TRUE, TRUE, TRUE, FALSE" << TestLog::EndMessage;
    }

    if (m_isSampleCoverageCase)
    {
        gl.enable(GL_SAMPLE_COVERAGE);
        GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_COVERAGE");

        log << TestLog::Message << "GL_SAMPLE_COVERAGE is enabled" << TestLog::EndMessage;
    }

    if (m_isSampleMaskCase)
    {
        gl.enable(GL_SAMPLE_MASK);
        GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK");

        log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
    }

    log << TestLog::Message << "Drawing several green quads, each fully overlapped by a red quad with the same "
        << (m_isAlphaToCoverageCase ? "alpha" : "")
        << (m_isAlphaToCoverageCase && (m_isSampleCoverageCase || m_isSampleMaskCase) ? " and " : "")
        << (m_isInvertedSampleCoverageCase ? "inverted " : "") << (m_isSampleCoverageCase ? "sample coverage" : "")
        << (m_isSampleCoverageCase && m_isSampleMaskCase ? " and " : "") << (m_isSampleMaskCase ? "sample mask" : "")
        << " values" << TestLog::EndMessage;

    const int numQuadRowsCols = m_numSamples * 4;

    for (int row = 0; row < numQuadRowsCols; row++)
    {
        for (int col = 0; col < numQuadRowsCols; col++)
        {
            float x0 = (float)(col + 0) / (float)numQuadRowsCols * 2.0f - 1.0f;
            float x1 = (float)(col + 1) / (float)numQuadRowsCols * 2.0f - 1.0f;
            float y0 = (float)(row + 0) / (float)numQuadRowsCols * 2.0f - 1.0f;
            float y1 = (float)(row + 1) / (float)numQuadRowsCols * 2.0f - 1.0f;
            const Vec4 baseGreen(0.0f, 1.0f, 0.0f, 0.0f);
            const Vec4 baseRed(1.0f, 0.0f, 0.0f, 0.0f);
            Vec4 alpha0(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)col / (float)(numQuadRowsCols - 1) : 1.0f);
            Vec4 alpha1(0.0f, 0.0f, 0.0f, m_isAlphaToCoverageCase ? (float)row / (float)(numQuadRowsCols - 1) : 1.0f);

            if (m_isSampleCoverageCase)
            {
                float value = (float)(row * numQuadRowsCols + col) / (float)(numQuadRowsCols * numQuadRowsCols - 1);
                gl.sampleCoverage(m_isInvertedSampleCoverageCase ? 1.0f - value : value,
                                  m_isInvertedSampleCoverageCase ? GL_TRUE : GL_FALSE);
                GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleCoverage");
            }

            if (m_isSampleMaskCase)
            {
                const int wordCount            = getEffectiveSampleMaskWordCount(m_numSamples - 1);
                const GLbitfield finalWordBits = m_numSamples - 32 * ((m_numSamples - 1) / 32);
                const GLbitfield finalWordMask = (GLbitfield)deBitMask32(0, (int)finalWordBits);

                for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
                {
                    const GLbitfield mask  = (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
                    const bool isFinalWord = (wordNdx + 1) == wordCount;
                    const GLbitfield maskMask =
                        (isFinalWord) ?
                            (finalWordMask) :
                            (0xFFFFFFFFUL); // maskMask prevents setting coverage bits higher than sample count

                    gl.sampleMaski(wordNdx, mask & maskMask);
                    GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
                }
            }

            renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen + alpha0, baseGreen + alpha1,
                       baseGreen + alpha0, baseGreen + alpha1);
            renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed + alpha0, baseRed + alpha1,
                       baseRed + alpha0, baseRed + alpha1);
        }
    }

    readImage(renderedImg);

    log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);

    for (int y = 0; y < renderedImg.getHeight(); y++)
        for (int x = 0; x < renderedImg.getWidth(); x++)
        {
            if (renderedImg.getPixel(x, y).getGreen() > 0)
            {
                log << TestLog::Message
                    << "Failure: Non-zero green color component detected - should have been completely overwritten by "
                       "red quad"
                    << TestLog::EndMessage;
                m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Failed");
                return STOP;
            }
        }

    log << TestLog::Message << "Success: Coverage mask appears to be constant at a given pixel coordinate with a given "
        << (m_isAlphaToCoverageCase ? "alpha" : "")
        << (m_isAlphaToCoverageCase && m_isSampleCoverageCase ? " and " : "")
        << (m_isSampleCoverageCase ? "coverage value" : "") << TestLog::EndMessage;

    m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");

    return STOP;
}

/*--------------------------------------------------------------------*//*!
 * \brief Tests that unused bits of a sample mask have no effect
 *
 * Tests that the bits in the sample mask with positions higher than
 * the number of samples do not have effect. In multisample fragment
 * operations the sample mask is ANDed with the fragment coverage value.
 * The coverage value cannot have the corresponding bits set.
 *
 * This is done by drawing a quads with varying sample masks and then
 * redrawing the quads with identical masks but with the mask's high bits
 * having different values. Only the latter quad pattern should be visible.
 *//*--------------------------------------------------------------------*/
class SampleMaskHighBitsCase : public DefaultFBOMultisampleCase
{
public:
    SampleMaskHighBitsCase(Context &context, const char *name, const char *description);
    ~SampleMaskHighBitsCase(void)
    {
    }

    void init(void);
    IterateResult iterate(void);
};

SampleMaskHighBitsCase::SampleMaskHighBitsCase(Context &context, const char *name, const char *description)
    : DefaultFBOMultisampleCase(context, name, description, 256)
{
}

void SampleMaskHighBitsCase::init(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    GLint maxSampleMaskWords = 0;

    // check the test is even possible
    gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
    if (getEffectiveSampleMaskWordCount(m_numSamples - 1) > maxSampleMaskWords)
        throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

    // normal init
    DefaultFBOMultisampleCase::init();
}

SampleMaskHighBitsCase::IterateResult SampleMaskHighBitsCase::iterate(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    TestLog &log             = m_testCtx.getLog();
    tcu::Surface renderedImg(m_viewportSize, m_viewportSize);
    de::Random rnd(12345);

    if (m_numSamples % 32 == 0)
    {
        log << TestLog::Message
            << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping."
            << TestLog::EndMessage;
        m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Skipped");
        return STOP;
    }

    randomizeViewport();

    log << TestLog::Message << "Clearing color to black" << TestLog::EndMessage;
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

    gl.enable(GL_SAMPLE_MASK);
    GLU_EXPECT_NO_ERROR(gl.getError(), "enable GL_SAMPLE_MASK");
    log << TestLog::Message << "GL_SAMPLE_MASK is enabled" << TestLog::EndMessage;
    log << TestLog::Message
        << "Drawing several green quads, each fully overlapped by a red quad with the same effective sample mask values"
        << TestLog::EndMessage;

    const int numQuadRowsCols = m_numSamples * 4;

    for (int row = 0; row < numQuadRowsCols; row++)
    {
        for (int col = 0; col < numQuadRowsCols; col++)
        {
            float x0 = (float)(col + 0) / (float)numQuadRowsCols * 2.0f - 1.0f;
            float x1 = (float)(col + 1) / (float)numQuadRowsCols * 2.0f - 1.0f;
            float y0 = (float)(row + 0) / (float)numQuadRowsCols * 2.0f - 1.0f;
            float y1 = (float)(row + 1) / (float)numQuadRowsCols * 2.0f - 1.0f;
            const Vec4 baseGreen(0.0f, 1.0f, 0.0f, 1.0f);
            const Vec4 baseRed(1.0f, 0.0f, 0.0f, 1.0f);

            const int wordCount            = getEffectiveSampleMaskWordCount(m_numSamples - 1);
            const GLbitfield finalWordBits = m_numSamples - 32 * ((m_numSamples - 1) / 32);
            const GLbitfield finalWordMask = (GLbitfield)deBitMask32(0, (int)finalWordBits);

            for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
            {
                const GLbitfield mask  = (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
                const bool isFinalWord = (wordNdx + 1) == wordCount;
                const GLbitfield maskMask =
                    (isFinalWord) ? (finalWordMask) :
                                    (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count
                const GLbitfield highBits = rnd.getUint32();

                gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask));
                GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
            }
            renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseGreen, baseGreen, baseGreen,
                       baseGreen);

            for (int wordNdx = 0; wordNdx < wordCount; ++wordNdx)
            {
                const GLbitfield mask  = (GLbitfield)deUint32Hash((col << (m_numSamples / 2)) ^ row);
                const bool isFinalWord = (wordNdx + 1) == wordCount;
                const GLbitfield maskMask =
                    (isFinalWord) ? (finalWordMask) :
                                    (0xFFFFFFFFUL); // maskMask is 1 on bits in lower positions than sample count
                const GLbitfield highBits = rnd.getUint32();

                gl.sampleMaski(wordNdx, (mask & maskMask) | (highBits & ~maskMask));
                GLU_EXPECT_NO_ERROR(gl.getError(), "glSampleMaski");
            }
            renderQuad(Vec2(x0, y0), Vec2(x1, y0), Vec2(x0, y1), Vec2(x1, y1), baseRed, baseRed, baseRed, baseRed);
        }
    }

    readImage(renderedImg);

    log << TestLog::Image("RenderedImage", "Rendered image", renderedImg, QP_IMAGE_COMPRESSION_MODE_PNG);

    for (int y = 0; y < renderedImg.getHeight(); y++)
        for (int x = 0; x < renderedImg.getWidth(); x++)
        {
            if (renderedImg.getPixel(x, y).getGreen() > 0)
            {
                log << TestLog::Message
                    << "Failure: Non-zero green color component detected - should have been completely overwritten by "
                       "red quad. Mask unused bits have effect."
                    << TestLog::EndMessage;
                m_context.getTestContext().setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits modified mask");
                return STOP;
            }
        }

    log << TestLog::Message << "Success: Coverage mask high bits appear to have no effect." << TestLog::EndMessage;
    m_context.getTestContext().setTestResult(QP_TEST_RESULT_PASS, "Passed");

    return STOP;
}

} // namespace

MultisampleTests::MultisampleTests(Context &context) : TestCaseGroup(context, "multisample", "Multisample tests")
{
}

MultisampleTests::~MultisampleTests(void)
{
}

void MultisampleTests::init(void)
{
    tcu::TestCaseGroup *const group =
        new tcu::TestCaseGroup(m_testCtx, "default_framebuffer", "Test with default framebuffer");

    addChild(group);

    // .default_framebuffer
    {
        // sample positions
        group->addChild(new SamplePosQueryCase(m_context, "sample_position", "test SAMPLE_POSITION"));

        // sample mask
        group->addChild(new MaskInvertCase(m_context, "sample_mask_sum_of_inverses",
                                           "Test that mask and its negation's sum equal the fully set mask"));
        group->addChild(new MaskProportionalityCase(m_context, "proportionality_sample_mask",
                                                    "Test the proportionality property of GL_SAMPLE_MASK"));

        group->addChild(new MaskConstancyCase(m_context, "constancy_sample_mask",
                                              "Test that coverage mask is constant at given coordinates with a given "
                                              "alpha or coverage value, using GL_SAMPLE_MASK",
                                              MaskConstancyCase::CASEBIT_SAMPLE_MASK));
        group->addChild(new MaskConstancyCase(
            m_context, "constancy_alpha_to_coverage_sample_mask",
            "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using "
            "GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_MASK",
            MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
        group->addChild(
            new MaskConstancyCase(m_context, "constancy_sample_coverage_sample_mask",
                                  "Test that coverage mask is constant at given coordinates with a given alpha or "
                                  "coverage value, using GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK",
                                  MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_MASK));
        group->addChild(new MaskConstancyCase(
            m_context, "constancy_alpha_to_coverage_sample_coverage_sample_mask",
            "Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using "
            "GL_SAMPLE_ALPHA_TO_COVERAGE, GL_SAMPLE_COVERAGE and GL_SAMPLE_MASK",
            MaskConstancyCase::CASEBIT_ALPHA_TO_COVERAGE | MaskConstancyCase::CASEBIT_SAMPLE_COVERAGE |
                MaskConstancyCase::CASEBIT_SAMPLE_MASK));
        group->addChild(new SampleMaskHighBitsCase(
            m_context, "sample_mask_non_effective_bits",
            "Test that values of unused bits of a sample mask (bit index > sample count) have no effect"));
    }
}

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