/*-------------------------------------------------------------------------
 * 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 Sample variable tests
 *//*--------------------------------------------------------------------*/

#include "es31fSampleVariableTests.hpp"
#include "es31fMultisampleShaderRenderCase.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuFormatUtil.hpp"
#include "tcuStringTemplate.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"

namespace deqp
{

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

namespace gles31
{
namespace Functional
{
namespace
{

class Verifier
{
public:
    virtual bool verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const = 0;
    virtual void logInfo(tcu::TestLog &log) const                                     = 0;
};

class ColorVerifier : public Verifier
{
public:
    ColorVerifier(const tcu::Vec3 &_color, int _threshold = 8)
        : m_color(tcu::Vec4(_color.x(), _color.y(), _color.z(), 1.0f))
        , m_threshold(tcu::IVec3(_threshold))
    {
    }

    ColorVerifier(const tcu::Vec3 &_color, tcu::IVec3 _threshold)
        : m_color(tcu::Vec4(_color.x(), _color.y(), _color.z(), 1.0f))
        , m_threshold(_threshold)
    {
    }

    bool verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const
    {
        DE_UNREF(position);
        return !tcu::boolAny(
            tcu::greaterThan(tcu::abs(m_color.toIVec().swizzle(0, 1, 2) - testColor.toIVec().swizzle(0, 1, 2)),
                             tcu::IVec3(m_threshold)));
    }

    void logInfo(tcu::TestLog &log) const
    {
        // full threshold? print * for clarity
        log << tcu::TestLog::Message << "Expecting unicolored image, color = RGB("
            << ((m_threshold[0] >= 255) ? ("*") : (de::toString(m_color.getRed()))) << ", "
            << ((m_threshold[1] >= 255) ? ("*") : (de::toString(m_color.getGreen()))) << ", "
            << ((m_threshold[2] >= 255) ? ("*") : (de::toString(m_color.getBlue()))) << ")" << tcu::TestLog::EndMessage;
    }

    const tcu::RGBA m_color;
    const tcu::IVec3 m_threshold;
};

class FullBlueSomeGreenVerifier : public Verifier
{
public:
    FullBlueSomeGreenVerifier(void)
    {
    }

    bool verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const
    {
        DE_UNREF(position);

        // Values from 0.0 and 1.0 are accurate

        if (testColor.getRed() != 0)
            return false;
        if (testColor.getGreen() == 0)
            return false;
        if (testColor.getBlue() != 255)
            return false;
        return true;
    }

    void logInfo(tcu::TestLog &log) const
    {
        log << tcu::TestLog::Message << "Expecting color c = (0.0, x, 1.0), x > 0.0" << tcu::TestLog::EndMessage;
    }
};

class NoRedVerifier : public Verifier
{
public:
    NoRedVerifier(void)
    {
    }

    bool verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const
    {
        DE_UNREF(position);
        return testColor.getRed() == 0;
    }

    void logInfo(tcu::TestLog &log) const
    {
        log << tcu::TestLog::Message << "Expecting zero-valued red channel." << tcu::TestLog::EndMessage;
    }
};

class SampleAverageVerifier : public Verifier
{
public:
    SampleAverageVerifier(int _numSamples);

    bool verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const;
    void logInfo(tcu::TestLog &log) const;

    const int m_numSamples;
    const bool m_isStatisticallySignificant;
    float m_distanceThreshold;
};

SampleAverageVerifier::SampleAverageVerifier(int _numSamples)
    : m_numSamples(_numSamples)
    , m_isStatisticallySignificant(_numSamples >= 4)
    , m_distanceThreshold(0.0f)
{
    // approximate Bates distribution as normal
    const float variance          = (1.0f / (12.0f * (float)m_numSamples));
    const float standardDeviation = deFloatSqrt(variance);

    // 95% of means of sample positions are within 2 standard deviations if
    // they were randomly assigned. Sample patterns are expected to be more
    // uniform than a random pattern.
    m_distanceThreshold = 2 * standardDeviation;
}

bool SampleAverageVerifier::verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const
{
    DE_UNREF(position);
    DE_ASSERT(m_isStatisticallySignificant);

    const tcu::Vec2 avgPosition((float)testColor.getGreen() / 255.0f, (float)testColor.getBlue() / 255.0f);
    const tcu::Vec2 distanceFromCenter = tcu::abs(avgPosition - tcu::Vec2(0.5f, 0.5f));

    return distanceFromCenter.x() < m_distanceThreshold && distanceFromCenter.y() < m_distanceThreshold;
}

void SampleAverageVerifier::logInfo(tcu::TestLog &log) const
{
    log << tcu::TestLog::Message
        << "Expecting average sample position to be near the pixel center. Maximum per-axis distance "
        << m_distanceThreshold << tcu::TestLog::EndMessage;
}

class PartialDiscardVerifier : public Verifier
{
public:
    PartialDiscardVerifier(void)
    {
    }

    bool verify(const tcu::RGBA &testColor, const tcu::IVec2 &position) const
    {
        DE_UNREF(position);

        return (testColor.getGreen() != 0) && (testColor.getGreen() != 255);
    }

    void logInfo(tcu::TestLog &log) const
    {
        log << tcu::TestLog::Message << "Expecting color non-zero and non-saturated green channel"
            << tcu::TestLog::EndMessage;
    }
};

static bool verifyImageWithVerifier(const tcu::Surface &resultImage, tcu::TestLog &log, const Verifier &verifier,
                                    bool logOnSuccess = true)
{
    tcu::Surface errorMask(resultImage.getWidth(), resultImage.getHeight());
    bool error = false;

    tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));

    if (logOnSuccess)
    {
        log << tcu::TestLog::Message << "Verifying image." << tcu::TestLog::EndMessage;
        verifier.logInfo(log);
    }

    for (int y = 0; y < resultImage.getHeight(); ++y)
        for (int x = 0; x < resultImage.getWidth(); ++x)
        {
            const tcu::RGBA color = resultImage.getPixel(x, y);

            // verify color value is valid for this pixel position
            if (!verifier.verify(color, tcu::IVec2(x, y)))
            {
                error = true;
                errorMask.setPixel(x, y, tcu::RGBA::red());
            }
        }

    if (error)
    {
        // describe the verification logic if we haven't already
        if (!logOnSuccess)
            verifier.logInfo(log);

        log << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
            << tcu::TestLog::ImageSet("Verification", "Image Verification")
            << tcu::TestLog::Image("Result", "Result image", resultImage.getAccess())
            << tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess()) << tcu::TestLog::EndImageSet;
    }
    else if (logOnSuccess)
    {
        log << tcu::TestLog::Message << "Image verification passed." << tcu::TestLog::EndMessage
            << tcu::TestLog::ImageSet("Verification", "Image Verification")
            << tcu::TestLog::Image("Result", "Result image", resultImage.getAccess()) << tcu::TestLog::EndImageSet;
    }

    return !error;
}

class MultisampleRenderCase : public MultisampleShaderRenderUtil::MultisampleRenderCase
{
public:
    MultisampleRenderCase(Context &context, const char *name, const char *desc, int numSamples, RenderTarget target,
                          int renderSize, int flags = 0);
    virtual ~MultisampleRenderCase(void);

    virtual void init(void);
};

MultisampleRenderCase::MultisampleRenderCase(Context &context, const char *name, const char *desc, int numSamples,
                                             RenderTarget target, int renderSize, int flags)
    : MultisampleShaderRenderUtil::MultisampleRenderCase(context, name, desc, numSamples, target, renderSize, flags)
{
    DE_ASSERT(target < TARGET_LAST);
}

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

void MultisampleRenderCase::init(void)
{
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    if (!supportsES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_OES_sample_variables"))
        TCU_THROW(NotSupportedError,
                  "Test requires GL_OES_sample_variables extension or a context version 3.2 or higher.");

    MultisampleShaderRenderUtil::MultisampleRenderCase::init();
}

class NumSamplesCase : public MultisampleRenderCase
{
public:
    NumSamplesCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target);
    ~NumSamplesCase(void);

    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

private:
    enum
    {
        RENDER_SIZE = 64
    };
};

NumSamplesCase::NumSamplesCase(Context &context, const char *name, const char *desc, int sampleCount,
                               RenderTarget target)
    : MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE)
{
}

NumSamplesCase::~NumSamplesCase(void)
{
}

std::string NumSamplesCase::genFragmentSource(int numTargetSamples) const
{
    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = (supportsES32orGL45) ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                       getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = (supportsES32orGL45) ? "" : "#extension GL_OES_sample_variables : require";

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXTENSION}\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "    if (gl_NumSamples == "
        << numTargetSamples
        << ")\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "}\n";

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool NumSamplesCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
}

class MaxSamplesCase : public MultisampleRenderCase
{
public:
    MaxSamplesCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target);
    ~MaxSamplesCase(void);

private:
    void preDraw(void);
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

    enum
    {
        RENDER_SIZE = 64
    };
};

MaxSamplesCase::MaxSamplesCase(Context &context, const char *name, const char *desc, int sampleCount,
                               RenderTarget target)
    : MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE)
{
}

MaxSamplesCase::~MaxSamplesCase(void)
{
}

void MaxSamplesCase::preDraw(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    int32_t maxSamples       = -1;

    // query samples
    {
        gl.getIntegerv(GL_MAX_SAMPLES, &maxSamples);
        GLU_EXPECT_NO_ERROR(gl.getError(), "query GL_MAX_SAMPLES");

        m_testCtx.getLog() << tcu::TestLog::Message << "GL_MAX_SAMPLES = " << maxSamples << tcu::TestLog::EndMessage;
    }

    // set samples
    {
        const int maxSampleLoc = gl.getUniformLocation(m_program->getProgram(), "u_maxSamples");
        if (maxSampleLoc == -1)
            throw tcu::TestError("Location of u_maxSamples was -1");

        gl.uniform1i(maxSampleLoc, maxSamples);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set u_maxSamples uniform");

        m_testCtx.getLog() << tcu::TestLog::Message << "Set u_maxSamples = " << maxSamples << tcu::TestLog::EndMessage;
    }
}

std::string MaxSamplesCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = (supportsES32orGL45) ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                       getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = (supportsES32orGL45) ? "" : "#extension GL_OES_sample_variables : require";

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXTENSION}\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "uniform mediump int u_maxSamples;\n"
           "void main (void)\n"
           "{\n"
           "    fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "    if (gl_MaxSamples == u_maxSamples)\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "}\n";

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool MaxSamplesCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
}

class SampleIDCase : public MultisampleRenderCase
{
public:
    SampleIDCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target);
    ~SampleIDCase(void);

    void init(void);

private:
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);
    bool verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers);

    enum
    {
        RENDER_SIZE = 64
    };
    enum VerificationMode
    {
        VERIFY_USING_SAMPLES,
        VERIFY_USING_SELECTION,
    };

    const VerificationMode m_vericationMode;
};

SampleIDCase::SampleIDCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target)
    : MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE,
                            MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
    , m_vericationMode((target == TARGET_TEXTURE) ? (VERIFY_USING_SAMPLES) : (VERIFY_USING_SELECTION))
{
}

SampleIDCase::~SampleIDCase(void)
{
}

void SampleIDCase::init(void)
{
    // log the test method and expectations
    if (m_vericationMode == VERIFY_USING_SAMPLES)
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Writing gl_SampleID to the green channel of the texture and verifying texture values, expecting:\n"
            << "    1) 0 with non-multisample targets.\n"
            << "    2) value N at sample index N of a multisample texture\n"
            << tcu::TestLog::EndMessage;
    else if (m_vericationMode == VERIFY_USING_SELECTION)
        m_testCtx.getLog()
            << tcu::TestLog::Message
            << "Selecting a single sample id for each pixel and writing color only if gl_SampleID == selected.\n"
            << "Expecting all output pixels to be partially (multisample) or fully (singlesample) colored.\n"
            << tcu::TestLog::EndMessage;
    else
        DE_ASSERT(false);

    MultisampleRenderCase::init();
}

std::string SampleIDCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = (supportsES32orGL45) ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                       getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = (supportsES32orGL45) ? "" : "#extension GL_OES_sample_variables : require";

    if (m_vericationMode == VERIFY_USING_SAMPLES)
    {
        // encode the id to the output, and then verify it during sampling
        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "void main (void)\n"
               "{\n"
               "    highp float normalizedSample = float(gl_SampleID) / float("
            << numTargetSamples
            << ");\n"
               "    fragColor = vec4(0.0, normalizedSample, 1.0, 1.0);\n"
               "}\n";
    }
    else if (m_vericationMode == VERIFY_USING_SELECTION)
    {
        if (numTargetSamples == 1)
        {
            // single sample, just verify value is 0
            buf << "${GLSL_VERSION_DECL}\n"
                   "${GLSL_EXTENSION}\n"
                   "layout(location = 0) out mediump vec4 fragColor;\n"
                   "void main (void)\n"
                   "{\n"
                   "    if (gl_SampleID == 0)\n"
                   "        fragColor = vec4(0.0, 1.0, 1.0, 1.0);\n"
                   "    else\n"
                   "        fragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
                   "}\n";
        }
        else
        {
            // select only one sample per PIXEL
            buf << "${GLSL_VERSION_DECL}\n"
                   "${GLSL_EXTENSION}\n"
                   "in highp vec4 v_position;\n"
                   "layout(location = 0) out mediump vec4 fragColor;\n"
                   "void main (void)\n"
                   "{\n"
                   "    highp vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
                   "    highp ivec2 pixelPos = ivec2(floor(relPosition * "
                << (int)RENDER_SIZE
                << ".0));\n"
                   "    highp int selectedID = abs(pixelPos.x + 17 * pixelPos.y) % "
                << numTargetSamples
                << ";\n"
                   "\n"
                   "    if (gl_SampleID == selectedID)\n"
                   "        fragColor = vec4(0.0, 1.0, 1.0, 1.0);\n"
                   "    else\n"
                   "        fragColor = vec4(0.0, 0.0, 1.0, 1.0);\n"
                   "}\n";
        }
    }
    else
        DE_ASSERT(false);

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool SampleIDCase::verifyImage(const tcu::Surface &resultImage)
{
    if (m_vericationMode == VERIFY_USING_SAMPLES)
    {
        // never happens
        DE_ASSERT(false);
        return false;
    }
    else if (m_vericationMode == VERIFY_USING_SELECTION)
    {
        // should result in full blue and some green everywhere
        return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), FullBlueSomeGreenVerifier());
    }
    else
    {
        DE_ASSERT(false);
        return false;
    }
}

bool SampleIDCase::verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers)
{
    // Verify all sample buffers
    bool allOk = true;

    // Log layers
    {
        m_testCtx.getLog() << tcu::TestLog::ImageSet("SampleBuffers", "Image sample buffers");
        for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
            m_testCtx.getLog() << tcu::TestLog::Image("Buffer" + de::toString(sampleNdx),
                                                      "Sample " + de::toString(sampleNdx),
                                                      resultBuffers[sampleNdx].getAccess());
        m_testCtx.getLog() << tcu::TestLog::EndImageSet;
    }

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample buffers" << tcu::TestLog::EndMessage;
    for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
    {
        // sample id should be sample index
        const int threshold       = 255 / 4 / m_numTargetSamples + 1;
        const float sampleIdColor = (float)sampleNdx / (float)m_numTargetSamples;

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample " << (sampleNdx + 1) << "/"
                           << (int)resultBuffers.size() << tcu::TestLog::EndMessage;
        allOk &= verifyImageWithVerifier(
            resultBuffers[sampleNdx], m_testCtx.getLog(),
            ColorVerifier(tcu::Vec3(0.0f, sampleIdColor, 1.0f), tcu::IVec3(1, threshold, 1)), false);
    }

    if (!allOk)
        m_testCtx.getLog() << tcu::TestLog::Message << "Sample buffer verification failed" << tcu::TestLog::EndMessage;

    return allOk;
}

class SamplePosDistributionCase : public MultisampleRenderCase
{
public:
    SamplePosDistributionCase(Context &context, const char *name, const char *desc, int sampleCount,
                              RenderTarget target);
    ~SamplePosDistributionCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 64
    };

    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);
    bool verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers);
};

SamplePosDistributionCase::SamplePosDistributionCase(Context &context, const char *name, const char *desc,
                                                     int sampleCount, RenderTarget target)
    : MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE,
                            MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
{
}

SamplePosDistributionCase::~SamplePosDistributionCase(void)
{
}

void SamplePosDistributionCase::init(void)
{
    // log the test method and expectations
    if (m_renderTarget == TARGET_TEXTURE)
    {
        m_testCtx.getLog()
            << tcu::TestLog::Message << "Verifying gl_SamplePosition value:\n"
            << "    1) With non-multisample targets: Expect the center of the pixel.\n"
            << "    2) With multisample targets:\n"
            << "        a) Expect legal sample position.\n"
            << "        b) Sample position is unique within the set of all sample positions of a pixel.\n"
            << "        c) Sample position distribution is uniform or almost uniform.\n"
            << tcu::TestLog::EndMessage;
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying gl_SamplePosition value:\n"
                           << "    1) With non-multisample targets: Expect the center of the pixel.\n"
                           << "    2) With multisample targets:\n"
                           << "        a) Expect legal sample position.\n"
                           << "        b) Sample position distribution is uniform or almost uniform.\n"
                           << tcu::TestLog::EndMessage;
    }

    MultisampleRenderCase::init();
}

std::string SamplePosDistributionCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);
    DE_UNREF(numTargetSamples);

    const bool multisampleTarget = (m_numRequestedSamples > 0) || (m_renderTarget == TARGET_DEFAULT &&
                                                                   m_context.getRenderTarget().getNumSamples() > 1);
    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = (supportsES32orGL45) ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                       getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = (supportsES32orGL45) ? "\n" : "#extension GL_OES_sample_variables : require\n";

    if (multisampleTarget)
    {
        // encode the position to the output, use red channel as error channel
        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "void main (void)\n"
               "{\n"
               "    if (gl_SamplePosition.x < 0.0 || gl_SamplePosition.x > 1.0 || gl_SamplePosition.y < 0.0 || "
               "gl_SamplePosition.y > 1.0)\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(0.0, gl_SamplePosition.x, gl_SamplePosition.y, 1.0);\n"
               "}\n";
    }
    else
    {
        // verify value is ok
        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "void main (void)\n"
               "{\n"
               "    if (gl_SamplePosition.x != 0.5 || gl_SamplePosition.y != 0.5)\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(0.0, gl_SamplePosition.x, gl_SamplePosition.y, 1.0);\n"
               "}\n";
    }

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool SamplePosDistributionCase::verifyImage(const tcu::Surface &resultImage)
{
    const int sampleCount =
        (m_renderTarget == TARGET_DEFAULT) ? (m_context.getRenderTarget().getNumSamples()) : (m_numRequestedSamples);
    SampleAverageVerifier verifier(sampleCount);

    // check there is nothing in the error channel
    if (!verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier()))
        return false;

    // position average should be around 0.5, 0.5
    if (verifier.m_isStatisticallySignificant && !verifyImageWithVerifier(resultImage, m_testCtx.getLog(), verifier))
        throw MultisampleShaderRenderUtil::QualityWarning(
            "Bias detected, sample positions are not uniformly distributed within the pixel");

    return true;
}

bool SamplePosDistributionCase::verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers)
{
    const int width       = resultBuffers[0].getWidth();
    const int height      = resultBuffers[0].getHeight();
    bool allOk            = true;
    bool distibutionError = false;

    // Check sample range, uniqueness, and distribution, log layers
    {
        m_testCtx.getLog() << tcu::TestLog::ImageSet("SampleBuffers", "Image sample buffers");
        for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
            m_testCtx.getLog() << tcu::TestLog::Image("Buffer" + de::toString(sampleNdx),
                                                      "Sample " + de::toString(sampleNdx),
                                                      resultBuffers[sampleNdx].getAccess());
        m_testCtx.getLog() << tcu::TestLog::EndImageSet;
    }

    // verify range
    {
        bool rangeOk = true;

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample position range" << tcu::TestLog::EndMessage;
        for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
        {
            // shader does the check, just check the shader error output (red)
            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample " << (sampleNdx + 1) << "/"
                               << (int)resultBuffers.size() << tcu::TestLog::EndMessage;
            rangeOk &= verifyImageWithVerifier(resultBuffers[sampleNdx], m_testCtx.getLog(), NoRedVerifier(), false);
        }

        if (!rangeOk)
        {
            allOk = false;

            m_testCtx.getLog() << tcu::TestLog::Message << "Sample position verification failed."
                               << tcu::TestLog::EndMessage;
        }
    }

    // Verify uniqueness
    {
        bool uniquenessOk = true;
        tcu::Surface errorMask(width, height);
        std::vector<tcu::Vec2> samplePositions(resultBuffers.size());
        int printCount            = 0;
        const int printFloodLimit = 5;

        tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample position uniqueness."
                           << tcu::TestLog::EndMessage;

        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x)
            {
                bool samplePosNotUnique = false;

                for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
                {
                    const tcu::RGBA color = resultBuffers[sampleNdx].getPixel(x, y);
                    samplePositions[sampleNdx] =
                        tcu::Vec2((float)color.getGreen() / 255.0f, (float)color.getBlue() / 255.0f);
                }

                // Just check there are no two samples with same positions
                for (int sampleNdxA = 0;
                     sampleNdxA < (int)resultBuffers.size() && (!samplePosNotUnique || printCount < printFloodLimit);
                     ++sampleNdxA)
                    for (int sampleNdxB = sampleNdxA + 1; sampleNdxB < (int)resultBuffers.size() &&
                                                          (!samplePosNotUnique || printCount < printFloodLimit);
                         ++sampleNdxB)
                    {
                        if (samplePositions[sampleNdxA] == samplePositions[sampleNdxB])
                        {
                            if (++printCount <= printFloodLimit)
                            {
                                m_testCtx.getLog() << tcu::TestLog::Message << "Pixel (" << x << ", " << y
                                                   << "): Samples " << sampleNdxA << " and " << sampleNdxB
                                                   << " have the same position." << tcu::TestLog::EndMessage;
                            }

                            samplePosNotUnique = true;
                            uniquenessOk       = false;
                            errorMask.setPixel(x, y, tcu::RGBA::red());
                        }
                    }
            }

        // end result
        if (!uniquenessOk)
        {
            if (printCount > printFloodLimit)
                m_testCtx.getLog() << tcu::TestLog::Message << "...\n"
                                   << "Omitted " << (printCount - printFloodLimit) << " error descriptions."
                                   << tcu::TestLog::EndMessage;

            m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                               << tcu::TestLog::ImageSet("Verification", "Image Verification")
                               << tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
                               << tcu::TestLog::EndImageSet;

            allOk = false;
        }
    }

    // check distribution
    {
        const SampleAverageVerifier verifier(m_numTargetSamples);
        tcu::Surface errorMask(width, height);
        int printCount            = 0;
        const int printFloodLimit = 5;

        tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));

        // don't bother with small sample counts
        if (verifier.m_isStatisticallySignificant)
        {
            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Verifying sample position distribution is (nearly) unbiased."
                               << tcu::TestLog::EndMessage;
            verifier.logInfo(m_testCtx.getLog());

            for (int y = 0; y < height; ++y)
                for (int x = 0; x < width; ++x)
                {
                    tcu::IVec3 colorSum(0, 0, 0);

                    // color average

                    for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
                    {
                        const tcu::RGBA color = resultBuffers[sampleNdx].getPixel(x, y);
                        colorSum.x() += color.getRed();
                        colorSum.y() += color.getBlue();
                        colorSum.z() += color.getGreen();
                    }

                    colorSum.x() /= m_numTargetSamples;
                    colorSum.y() /= m_numTargetSamples;
                    colorSum.z() /= m_numTargetSamples;

                    // verify average sample position

                    if (!verifier.verify(tcu::RGBA(colorSum.x(), colorSum.y(), colorSum.z(), 0), tcu::IVec2(x, y)))
                    {
                        if (++printCount <= printFloodLimit)
                        {
                            m_testCtx.getLog() << tcu::TestLog::Message << "Pixel (" << x << ", " << y
                                               << "): Sample distribution is biased." << tcu::TestLog::EndMessage;
                        }

                        distibutionError = true;
                        errorMask.setPixel(x, y, tcu::RGBA::red());
                    }
                }

            // sub-verification result
            if (distibutionError)
            {
                if (printCount > printFloodLimit)
                    m_testCtx.getLog() << tcu::TestLog::Message << "...\n"
                                       << "Omitted " << (printCount - printFloodLimit) << " error descriptions."
                                       << tcu::TestLog::EndMessage;

                m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                                   << tcu::TestLog::ImageSet("Verification", "Image Verification")
                                   << tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
                                   << tcu::TestLog::EndImageSet;
            }
        }
    }

    // results
    if (!allOk)
        return false;
    else if (distibutionError)
        throw MultisampleShaderRenderUtil::QualityWarning(
            "Bias detected, sample positions are not uniformly distributed within the pixel");
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Verification ok." << tcu::TestLog::EndMessage;
        return true;
    }
}

class SamplePosCorrectnessCase : public MultisampleRenderCase
{
public:
    SamplePosCorrectnessCase(Context &context, const char *name, const char *desc, int sampleCount,
                             RenderTarget target);
    ~SamplePosCorrectnessCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 32
    };

    void preDraw(void);
    void postDraw(void);

    std::string genVertexSource(int numTargetSamples) const;
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

    bool m_useSampleQualifier;
};

SamplePosCorrectnessCase::SamplePosCorrectnessCase(Context &context, const char *name, const char *desc,
                                                   int sampleCount, RenderTarget target)
    : MultisampleRenderCase(context, name, desc, sampleCount, target, RENDER_SIZE)
    , m_useSampleQualifier(false)
{
}

SamplePosCorrectnessCase::~SamplePosCorrectnessCase(void)
{
}

void SamplePosCorrectnessCase::init(void)
{
    auto ctxType            = m_context.getRenderContext().getType();
    const bool isES32orGL45 = glu::contextSupports(ctxType, glu::ApiType::es(3, 2)) ||
                              glu::contextSupports(ctxType, glu::ApiType::core(4, 5));

    // requirements: per-invocation interpolation required
    if (!isES32orGL45 && !m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation") &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
        TCU_THROW(NotSupportedError, "Test requires GL_OES_shader_multisample_interpolation or GL_OES_sample_shading "
                                     "extension or a context version 3.2 or higher.");

    // prefer to use the sample qualifier path
    m_useSampleQualifier = m_context.getContextInfo().isExtensionSupported("GL_OES_shader_multisample_interpolation");

    // log the test method and expectations
    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying gl_SamplePosition correctness:\n"
                       << "    1) Varying values should be sampled at the sample position.\n"
                       << " => fract(screenSpacePosition) == gl_SamplePosition\n"
                       << tcu::TestLog::EndMessage;

    MultisampleRenderCase::init();
}

void SamplePosCorrectnessCase::preDraw(void)
{
    if (!m_useSampleQualifier)
    {
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();

        // use GL_OES_sample_shading to set per fragment sample invocation interpolation
        gl.enable(GL_SAMPLE_SHADING);
        gl.minSampleShading(1.0f);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");

        m_testCtx.getLog() << tcu::TestLog::Message << "Enabling per-sample interpolation with GL_SAMPLE_SHADING."
                           << tcu::TestLog::EndMessage;
    }
}

void SamplePosCorrectnessCase::postDraw(void)
{
    if (!m_useSampleQualifier)
    {
        const glw::Functions &gl = m_context.getRenderContext().getFunctions();

        gl.disable(GL_SAMPLE_SHADING);
        gl.minSampleShading(1.0f);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set ratio");
    }
}

std::string SamplePosCorrectnessCase::genVertexSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));

    std::ostringstream buf;
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = supportsES32orGL45   ? "" :
                                m_useSampleQualifier ? "#extension GL_OES_shader_multisample_interpolation : require" :
                                                       "";

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXTENSION}\n"
        << "in highp vec4 a_position;\n"
        << ((m_useSampleQualifier) ? ("sample ") : (""))
        << "out highp vec4 v_position;\n"
           "void main (void)\n"
           "{\n"
           "    gl_Position = a_position;\n"
           "    v_position = a_position;\n"
           "}\n";

    return tcu::StringTemplate(buf.str()).specialize(args);
}

std::string SamplePosCorrectnessCase::genFragmentSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"]     = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                         getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_SAMPLE_EXTENSION"] = supportsES32orGL45 ? "" : "#extension GL_OES_sample_variables : require";
    args["GLSL_MULTISAMPLE_EXTENSION"] =
        supportsES32orGL45   ? "" :
        m_useSampleQualifier ? "#extension GL_OES_shader_multisample_interpolation : require" :
                               "";

    // encode the position to the output, use red channel as error channel
    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_SAMPLE_EXTENSION}\n"
           "${GLSL_MULTISAMPLE_EXTENSION}\n"
        << ((m_useSampleQualifier) ? ("sample ") : (""))
        << "in highp vec4 v_position;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    const highp float maxDistance = 0.15625; // 4 subpixel bits. Assume 3 accurate bits + 0.03125 for "
           "other errors\n" // 0.03125 = mediump epsilon when value = 32 (RENDER_SIZE)
           "\n"
           "    highp vec2 screenSpacePosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0 * "
        << (int)RENDER_SIZE
        << ".0;\n"
           "    highp ivec2 nearbyPixel = ivec2(floor(screenSpacePosition));\n"
           "    bool allOk = false;\n"
           "\n"
           "    // sample at edge + inaccuaries may cause us to round to any neighboring pixel\n"
           "    // check all neighbors for any match\n"
           "    for (highp int dy = -1; dy <= 1; ++dy)\n"
           "    for (highp int dx = -1; dx <= 1; ++dx)\n"
           "    {\n"
           "        highp ivec2 currentPixel = nearbyPixel + ivec2(dx, dy);\n"
           "        highp vec2 candidateSamplingPos = vec2(currentPixel) + gl_SamplePosition.xy;\n"
           "        highp vec2 positionDiff = abs(candidateSamplingPos - screenSpacePosition);\n"
           "        if (positionDiff.x < maxDistance && positionDiff.y < maxDistance)\n"
           "            allOk = true;\n"
           "    }\n"
           "\n"
           "    if (allOk)\n"
           "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           "    else\n"
           "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           "}\n";

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool SamplePosCorrectnessCase::verifyImage(const tcu::Surface &resultImage)
{
    return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
}

class SampleMaskBaseCase : public MultisampleRenderCase
{
public:
    enum ShaderRunMode
    {
        RUN_PER_PIXEL = 0,
        RUN_PER_SAMPLE,
        RUN_PER_TWO_SAMPLES,

        RUN_LAST
    };

    SampleMaskBaseCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target,
                       int renderSize, ShaderRunMode runMode, int flags = 0);
    virtual ~SampleMaskBaseCase(void);

protected:
    virtual void init(void);
    virtual void preDraw(void);
    virtual void postDraw(void);
    virtual bool verifyImage(const tcu::Surface &resultImage);

    const ShaderRunMode m_runMode;
};

SampleMaskBaseCase::SampleMaskBaseCase(Context &context, const char *name, const char *desc, int sampleCount,
                                       RenderTarget target, int renderSize, ShaderRunMode runMode, int flags)
    : MultisampleRenderCase(context, name, desc, sampleCount, target, renderSize, flags)
    , m_runMode(runMode)
{
    DE_ASSERT(runMode < RUN_LAST);
}

SampleMaskBaseCase::~SampleMaskBaseCase(void)
{
}

void SampleMaskBaseCase::init(void)
{
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    // required extra extension
    if (m_runMode == RUN_PER_TWO_SAMPLES && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_sample_shading"))
        TCU_THROW(NotSupportedError,
                  "Test requires GL_OES_sample_shading extension or a context version 3.2 or higher.");

    MultisampleRenderCase::init();
}

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

    if (m_runMode == RUN_PER_TWO_SAMPLES)
    {
        gl.enable(GL_SAMPLE_SHADING);
        gl.minSampleShading(0.5f);
        GLU_EXPECT_NO_ERROR(gl.getError(), "enable sample shading");

        m_testCtx.getLog() << tcu::TestLog::Message << "Enabled GL_SAMPLE_SHADING, value = 0.5"
                           << tcu::TestLog::EndMessage;
    }
}

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

    if (m_runMode == RUN_PER_TWO_SAMPLES)
    {
        gl.disable(GL_SAMPLE_SHADING);
        gl.minSampleShading(1.0f);
        GLU_EXPECT_NO_ERROR(gl.getError(), "disable sample shading");
    }
}

bool SampleMaskBaseCase::verifyImage(const tcu::Surface &resultImage)
{
    // shader does the verification
    return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), NoRedVerifier());
}

class SampleMaskCase : public SampleMaskBaseCase
{
public:
    SampleMaskCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target);
    ~SampleMaskCase(void);

    void init(void);
    void preDraw(void);
    void postDraw(void);

private:
    enum
    {
        RENDER_SIZE = 64
    };

    std::string genFragmentSource(int numTargetSamples) const;
};

SampleMaskCase::SampleMaskCase(Context &context, const char *name, const char *desc, int sampleCount,
                               RenderTarget target)
    : SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, RUN_PER_PIXEL)
{
}

SampleMaskCase::~SampleMaskCase(void)
{
}

void SampleMaskCase::init(void)
{
    // log the test method and expectations
    m_testCtx.getLog()
        << tcu::TestLog::Message
        << "Verifying gl_SampleMaskIn value with SAMPLE_MASK state. gl_SampleMaskIn does not contain any bits set that "
           "are have been killed by SAMPLE_MASK state. Expecting:\n"
        << "    1) With multisample targets: gl_SampleMaskIn AND ~(SAMPLE_MASK) should be zero.\n"
        << "    2) With non-multisample targets: SAMPLE_MASK state is only ANDed as a multisample operation. "
           "gl_SampleMaskIn should only have its last bit set regardless of SAMPLE_MASK state.\n"
        << tcu::TestLog::EndMessage;

    SampleMaskBaseCase::init();
}

void SampleMaskCase::preDraw(void)
{
    const glw::Functions &gl     = m_context.getRenderContext().getFunctions();
    const bool multisampleTarget = (m_numRequestedSamples > 0) || (m_renderTarget == TARGET_DEFAULT &&
                                                                   m_context.getRenderTarget().getNumSamples() > 1);
    const uint32_t fullMask      = (uint32_t)0xAAAAAAAAUL;
    const uint32_t maskMask      = (1U << m_numTargetSamples) - 1;
    const uint32_t effectiveMask = fullMask & maskMask;

    // set test mask
    gl.enable(GL_SAMPLE_MASK);
    gl.sampleMaski(0, effectiveMask);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");

    m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask " << tcu::Format::Hex<4>(effectiveMask)
                       << tcu::TestLog::EndMessage;

    // set multisample case uniforms
    if (multisampleTarget)
    {
        const int maskLoc = gl.getUniformLocation(m_program->getProgram(), "u_sampleMask");
        if (maskLoc == -1)
            throw tcu::TestError("Location of u_mask was -1");

        gl.uniform1ui(maskLoc, effectiveMask);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set mask uniform");
    }

    // base class logic
    SampleMaskBaseCase::preDraw();
}

void SampleMaskCase::postDraw(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const uint32_t fullMask  = (1U << m_numTargetSamples) - 1;

    gl.disable(GL_SAMPLE_MASK);
    gl.sampleMaski(0, fullMask);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");

    // base class logic
    SampleMaskBaseCase::postDraw();
}

std::string SampleMaskCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);

    const bool multisampleTarget = (m_numRequestedSamples > 0) || (m_renderTarget == TARGET_DEFAULT &&
                                                                   m_context.getRenderTarget().getNumSamples() > 1);
    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = supportsES32orGL45 ? "" : "#extension GL_OES_sample_variables : require";

    // test supports only one sample mask word
    if (numTargetSamples > 32)
        TCU_THROW(NotSupportedError, "Sample count larger than 32 is not supported.");

    if (multisampleTarget)
    {
        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "uniform highp uint u_sampleMask;\n"
               "void main (void)\n"
               "{\n"
               "    if ((uint(gl_SampleMaskIn[0]) & (~u_sampleMask)) != 0u)\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "}\n";
    }
    else
    {
        // non-multisample targets don't get multisample operations like ANDing with mask

        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "uniform highp uint u_sampleMask;\n"
               "void main (void)\n"
               "{\n"
               "    if (gl_SampleMaskIn[0] != 1)\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "}\n";
    }

    return tcu::StringTemplate(buf.str()).specialize(args);
}

class SampleMaskCountCase : public SampleMaskBaseCase
{
public:
    SampleMaskCountCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target,
                        ShaderRunMode runMode);
    ~SampleMaskCountCase(void);

    void init(void);
    void preDraw(void);
    void postDraw(void);

private:
    enum
    {
        RENDER_SIZE = 64
    };

    std::string genFragmentSource(int numTargetSamples) const;
};

SampleMaskCountCase::SampleMaskCountCase(Context &context, const char *name, const char *desc, int sampleCount,
                                         RenderTarget target, ShaderRunMode runMode)
    : SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode)
{
    DE_ASSERT(runMode < RUN_LAST);
}

SampleMaskCountCase::~SampleMaskCountCase(void)
{
}

void SampleMaskCountCase::init(void)
{
    // log the test method and expectations
    if (m_runMode == RUN_PER_PIXEL)
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying gl_SampleMaskIn.\n"
                           << "    Fragment shader may be invoked [1, numSamples] times.\n"
                           << " => gl_SampleMaskIn should have the number of bits set in range [1, numSamples]\n"
                           << tcu::TestLog::EndMessage;
    else if (m_runMode == RUN_PER_SAMPLE)
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying gl_SampleMaskIn.\n"
                           << "    Fragment will be invoked numSamples times.\n"
                           << " => gl_SampleMaskIn should have only one bit set.\n"
                           << tcu::TestLog::EndMessage;
    else if (m_runMode == RUN_PER_TWO_SAMPLES)
        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying gl_SampleMaskIn.\n"
                           << "    Fragment shader may be invoked [ceil(numSamples/2), numSamples] times.\n"
                           << " => gl_SampleMaskIn should have the number of bits set in range [1, numSamples - "
                              "ceil(numSamples/2) + 1]:\n"
                           << tcu::TestLog::EndMessage;
    else
        DE_ASSERT(false);

    SampleMaskBaseCase::init();
}

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

    if (m_runMode == RUN_PER_PIXEL)
    {
        const int maxLoc      = gl.getUniformLocation(m_program->getProgram(), "u_maxBitCount");
        const int minLoc      = gl.getUniformLocation(m_program->getProgram(), "u_minBitCount");
        const int minBitCount = 1;
        const int maxBitCount = m_numTargetSamples;

        if (maxLoc == -1)
            throw tcu::TestError("Location of u_maxBitCount was -1");
        if (minLoc == -1)
            throw tcu::TestError("Location of u_minBitCount was -1");

        gl.uniform1i(minLoc, minBitCount);
        gl.uniform1i(maxLoc, maxBitCount);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set limits");

        m_testCtx.getLog() << tcu::TestLog::Message << "Setting minBitCount = " << minBitCount
                           << ", maxBitCount = " << maxBitCount << tcu::TestLog::EndMessage;
    }
    else if (m_runMode == RUN_PER_TWO_SAMPLES)
    {
        const int maxLoc = gl.getUniformLocation(m_program->getProgram(), "u_maxBitCount");
        const int minLoc = gl.getUniformLocation(m_program->getProgram(), "u_minBitCount");

        // Worst case: all but one shader invocations get one sample, one shader invocation the rest of the samples
        const int minInvocationCount = ((m_numTargetSamples + 1) / 2);
        const int minBitCount        = 1;
        const int maxBitCount        = m_numTargetSamples - ((minInvocationCount - 1) * minBitCount);

        if (maxLoc == -1)
            throw tcu::TestError("Location of u_maxBitCount was -1");
        if (minLoc == -1)
            throw tcu::TestError("Location of u_minBitCount was -1");

        gl.uniform1i(minLoc, minBitCount);
        gl.uniform1i(maxLoc, maxBitCount);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set limits");

        m_testCtx.getLog() << tcu::TestLog::Message << "Setting minBitCount = " << minBitCount
                           << ", maxBitCount = " << maxBitCount << tcu::TestLog::EndMessage;
    }

    SampleMaskBaseCase::preDraw();
}

void SampleMaskCountCase::postDraw(void)
{
    SampleMaskBaseCase::postDraw();
}

std::string SampleMaskCountCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = supportsES32orGL45 ? "" : "#extension GL_OES_sample_variables : require";

    // test supports only one sample mask word
    if (numTargetSamples > 32)
        TCU_THROW(NotSupportedError, "Sample count larger than 32 is not supported.");

    // count the number of the bits in gl_SampleMask

    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXTENSION}\n"
           "layout(location = 0) out mediump vec4 fragColor;\n";

    if (m_runMode != RUN_PER_SAMPLE)
        buf << "uniform highp int u_minBitCount;\n"
               "uniform highp int u_maxBitCount;\n";

    buf << "void main (void)\n"
           "{\n"
           "    mediump int maskBitCount = 0;\n"
           "    for (int i = 0; i < 32; ++i)\n"
           "        if (((gl_SampleMaskIn[0] >> i) & 0x01) == 0x01)\n"
           "            ++maskBitCount;\n"
           "\n";

    if (m_runMode == RUN_PER_SAMPLE)
    {
        // check the validity here
        buf << "    // force per-sample shading\n"
               "    highp float blue = float(gl_SampleID);\n"
               "\n"
               "    if (maskBitCount != 1)\n"
               "        fragColor = vec4(1.0, 0.0, blue, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(0.0, 1.0, blue, 1.0);\n"
               "}\n";
    }
    else
    {
        // check the validity here
        buf << "    if (maskBitCount < u_minBitCount || maskBitCount > u_maxBitCount)\n"
               "        fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
               "    else\n"
               "        fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
               "}\n";
    }

    return tcu::StringTemplate(buf.str()).specialize(args);
}

class SampleMaskUniqueCase : public SampleMaskBaseCase
{
public:
    SampleMaskUniqueCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target,
                         ShaderRunMode runMode);
    ~SampleMaskUniqueCase(void);

    void init(void);

private:
    enum
    {
        RENDER_SIZE = 64
    };

    std::string genFragmentSource(int numTargetSamples) const;
    bool verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers);
};

SampleMaskUniqueCase::SampleMaskUniqueCase(Context &context, const char *name, const char *desc, int sampleCount,
                                           RenderTarget target, ShaderRunMode runMode)
    : SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode,
                         MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
{
    DE_ASSERT(runMode == RUN_PER_SAMPLE);
    DE_ASSERT(target == TARGET_TEXTURE);
}

SampleMaskUniqueCase::~SampleMaskUniqueCase(void)
{
}

void SampleMaskUniqueCase::init(void)
{
    // log the test method and expectations
    m_testCtx.getLog()
        << tcu::TestLog::Message << "Verifying gl_SampleMaskIn.\n"
        << "    Fragment will be invoked numSamples times.\n"
        << " => gl_SampleMaskIn should have only one bit set\n"
        << " => and that bit index should be unique within other fragment shader invocations of that pixel.\n"
        << "    Writing sampleMask bit index to green channel in render shader. Verifying uniqueness in sampler "
           "shader.\n"
        << tcu::TestLog::EndMessage;

    SampleMaskBaseCase::init();
}

std::string SampleMaskUniqueCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = supportsES32orGL45 ? "" : "#extension GL_OES_sample_variables : require";

    // test supports only one sample mask word
    if (numTargetSamples > 32)
        TCU_THROW(NotSupportedError, "Sample count larger than 32 is not supported.");

    // find our sampleID by searching for unique bit.
    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXTENSION}\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    mediump int firstIndex = -1;\n"
           "    for (int i = 0; i < 32; ++i)\n"
           "    {\n"
           "        if (((gl_SampleMaskIn[0] >> i) & 0x01) == 0x01)\n"
           "        {\n"
           "            firstIndex = i;\n"
           "            break;\n"
           "        }\n"
           "    }\n"
           "\n"
           "    bool notUniqueError = false;\n"
           "    for (int i = firstIndex + 1; i < 32; ++i)\n"
           "        if (((gl_SampleMaskIn[0] >> i) & 0x01) == 0x01)\n"
           "            notUniqueError = true;\n"
           "\n"
           "    highp float encodedSampleId = float(firstIndex) / "
        << numTargetSamples
        << ".0;\n"
           "\n"
           "    // force per-sample shading\n"
           "    highp float blue = float(gl_SampleID);\n"
           "\n"
           "    if (notUniqueError)\n"
           "        fragColor = vec4(1.0, 0.0, blue, 1.0);\n"
           "    else\n"
           "        fragColor = vec4(0.0, encodedSampleId, blue, 1.0);\n"
           "}\n";

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool SampleMaskUniqueCase::verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers)
{
    const int width  = resultBuffers[0].getWidth();
    const int height = resultBuffers[0].getHeight();
    bool allOk       = true;

    // Log samples
    {
        m_testCtx.getLog() << tcu::TestLog::ImageSet("SampleBuffers", "Image sample buffers");
        for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
            m_testCtx.getLog() << tcu::TestLog::Image("Buffer" + de::toString(sampleNdx),
                                                      "Sample " + de::toString(sampleNdx),
                                                      resultBuffers[sampleNdx].getAccess());
        m_testCtx.getLog() << tcu::TestLog::EndImageSet;
    }

    // check for earlier errors (in fragment shader)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Verifying fragment shader invocation found only one set sample mask bit."
                           << tcu::TestLog::EndMessage;

        for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
        {
            // shader does the check, just check the shader error output (red)
            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample " << (sampleNdx + 1) << "/"
                               << (int)resultBuffers.size() << tcu::TestLog::EndMessage;
            allOk &= verifyImageWithVerifier(resultBuffers[sampleNdx], m_testCtx.getLog(), NoRedVerifier(), false);
        }

        if (!allOk)
        {
            // can't check the uniqueness if the masks don't work at all
            m_testCtx.getLog()
                << tcu::TestLog::Message
                << "Could not get mask information from the rendered image, cannot continue verification."
                << tcu::TestLog::EndMessage;
            return false;
        }
    }

    // verify index / index ranges

    if (m_numRequestedSamples == 0)
    {
        // single sample target, expect index=0

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample mask bit index is 0."
                           << tcu::TestLog::EndMessage;

        // only check the mask index
        allOk &= verifyImageWithVerifier(resultBuffers[0], m_testCtx.getLog(),
                                         ColorVerifier(tcu::Vec3(0.0f, 0.0f, 0.0f), tcu::IVec3(255, 8, 255)), false);
    }
    else
    {
        // check uniqueness

        tcu::Surface errorMask(width, height);
        bool uniquenessOk         = true;
        int printCount            = 0;
        const int printFloodLimit = 5;
        std::vector<int> maskBitIndices(resultBuffers.size());

        tcu::clear(errorMask.getAccess(), tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f));

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying per-invocation sample mask bit is unique."
                           << tcu::TestLog::EndMessage;

        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x)
            {
                bool maskNdxNotUnique = false;

                // decode index
                for (int sampleNdx = 0; sampleNdx < (int)resultBuffers.size(); ++sampleNdx)
                {
                    const tcu::RGBA color = resultBuffers[sampleNdx].getPixel(x, y);
                    maskBitIndices[sampleNdx] =
                        (int)deFloatRound((float)color.getGreen() / 255.0f * (float)m_numTargetSamples);
                }

                // just check there are no two invocations with the same bit index
                for (int sampleNdxA = 0;
                     sampleNdxA < (int)resultBuffers.size() && (!maskNdxNotUnique || printCount < printFloodLimit);
                     ++sampleNdxA)
                    for (int sampleNdxB = sampleNdxA + 1;
                         sampleNdxB < (int)resultBuffers.size() && (!maskNdxNotUnique || printCount < printFloodLimit);
                         ++sampleNdxB)
                    {
                        if (maskBitIndices[sampleNdxA] == maskBitIndices[sampleNdxB])
                        {
                            if (++printCount <= printFloodLimit)
                            {
                                m_testCtx.getLog() << tcu::TestLog::Message << "Pixel (" << x << ", " << y
                                                   << "): Samples " << sampleNdxA << " and " << sampleNdxB
                                                   << " have the same sample mask. (Single bit at index "
                                                   << maskBitIndices[sampleNdxA] << ")" << tcu::TestLog::EndMessage;
                            }

                            maskNdxNotUnique = true;
                            uniquenessOk     = false;
                            errorMask.setPixel(x, y, tcu::RGBA::red());
                        }
                    }
            }

        // end result
        if (!uniquenessOk)
        {
            if (printCount > printFloodLimit)
                m_testCtx.getLog() << tcu::TestLog::Message << "...\n"
                                   << "Omitted " << (printCount - printFloodLimit) << " error descriptions."
                                   << tcu::TestLog::EndMessage;

            m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed." << tcu::TestLog::EndMessage
                               << tcu::TestLog::ImageSet("Verification", "Image Verification")
                               << tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask.getAccess())
                               << tcu::TestLog::EndImageSet;

            allOk = false;
        }
    }

    return allOk;
}

class SampleMaskUniqueSetCase : public SampleMaskBaseCase
{
public:
    SampleMaskUniqueSetCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target,
                            ShaderRunMode runMode);
    ~SampleMaskUniqueSetCase(void);

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

private:
    enum
    {
        RENDER_SIZE = 64
    };

    void preDraw(void);
    void postDraw(void);
    std::string genFragmentSource(int numTargetSamples) const;
    bool verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers);
    std::string getIterationDescription(int iteration) const;

    void preTest(void);
    void postTest(void);

    std::vector<tcu::Surface> m_iterationSampleBuffers;
};

SampleMaskUniqueSetCase::SampleMaskUniqueSetCase(Context &context, const char *name, const char *desc, int sampleCount,
                                                 RenderTarget target, ShaderRunMode runMode)
    : SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode,
                         MultisampleShaderRenderUtil::MultisampleRenderCase::FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS)
{
    DE_ASSERT(runMode == RUN_PER_TWO_SAMPLES);
    DE_ASSERT(target == TARGET_TEXTURE);

    // high and low bits
    m_numIterations = 2;
}

SampleMaskUniqueSetCase::~SampleMaskUniqueSetCase(void)
{
}

void SampleMaskUniqueSetCase::init(void)
{
    // log the test method and expectations
    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying gl_SampleMaskIn.\n"
                       << "    Fragment shader may be invoked [ceil(numSamples/2), numSamples] times.\n"
                       << " => Each invocation should have unique bit set\n"
                       << "    Writing highest and lowest bit index to color channels in render shader. Verifying:\n"
                       << "        1) no other invocation contains these bits in sampler shader.\n"
                       << "        2) number of invocations is at least ceil(numSamples/2).\n"
                       << tcu::TestLog::EndMessage;

    SampleMaskBaseCase::init();
}

void SampleMaskUniqueSetCase::deinit(void)
{
    m_iterationSampleBuffers.clear();
}

void SampleMaskUniqueSetCase::preDraw(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int selectorLoc    = gl.getUniformLocation(m_program->getProgram(), "u_bitSelector");

    gl.uniform1ui(selectorLoc, (uint32_t)m_iteration);
    GLU_EXPECT_NO_ERROR(gl.getError(), "set u_bitSelector");

    m_testCtx.getLog() << tcu::TestLog::Message << "Setting u_bitSelector = " << m_iteration
                       << tcu::TestLog::EndMessage;

    SampleMaskBaseCase::preDraw();
}

void SampleMaskUniqueSetCase::postDraw(void)
{
    SampleMaskBaseCase::postDraw();
}

std::string SampleMaskUniqueSetCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = supportsES32orGL45 ? "" : "#extension GL_OES_sample_variables : require";

    // test supports only one sample mask word
    if (numTargetSamples > 32)
        TCU_THROW(NotSupportedError, "Sample count larger than 32 is not supported.");

    // output min and max sample id
    buf << "${GLSL_VERSION_DECL}\n"
           "${GLSL_EXTENSION}\n"
           "uniform highp uint u_bitSelector;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "void main (void)\n"
           "{\n"
           "    highp int selectedBits;\n"
           "    if (u_bitSelector == 0u)\n"
           "        selectedBits = (gl_SampleMaskIn[0] & 0xFFFF);\n"
           "    else\n"
           "        selectedBits = ((gl_SampleMaskIn[0] >> 16) & 0xFFFF);\n"
           "\n"
           "    // encode bits to color\n"
           "    highp int redBits = selectedBits & 31;\n"
           "    highp int greenBits = (selectedBits >> 5) & 63;\n"
           "    highp int blueBits = (selectedBits >> 11) & 31;\n"
           "\n"
           "    fragColor = vec4(float(redBits) / float(31), float(greenBits) / float(63), float(blueBits) / "
           "float(31), 1.0);\n"
           "}\n";

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool SampleMaskUniqueSetCase::verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers)
{
    // we need results from all passes to do verification. Store results and verify later (at postTest).

    DE_ASSERT(m_numTargetSamples == (int)resultBuffers.size());
    for (int ndx = 0; ndx < m_numTargetSamples; ++ndx)
        m_iterationSampleBuffers[m_iteration * m_numTargetSamples + ndx] = resultBuffers[ndx];

    return true;
}

std::string SampleMaskUniqueSetCase::getIterationDescription(int iteration) const
{
    if (iteration == 0)
        return "Reading low bits";
    else if (iteration == 1)
        return "Reading high bits";
    else
        DE_ASSERT(false);
    return "";
}

void SampleMaskUniqueSetCase::preTest(void)
{
    m_iterationSampleBuffers.resize(m_numTargetSamples * 2);
}

void SampleMaskUniqueSetCase::postTest(void)
{
    DE_ASSERT((m_iterationSampleBuffers.size() % 2) == 0);
    DE_ASSERT((int)m_iterationSampleBuffers.size() / 2 == m_numTargetSamples);

    const int width  = m_iterationSampleBuffers[0].getWidth();
    const int height = m_iterationSampleBuffers[0].getHeight();
    bool allOk       = true;
    std::vector<tcu::TextureLevel> sampleCoverage(m_numTargetSamples);
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Verify", "Verify masks");

    // convert color layers to 32 bit coverage masks, 2 passes per coverage

    for (int sampleNdx = 0; sampleNdx < (int)sampleCoverage.size(); ++sampleNdx)
    {
        sampleCoverage[sampleNdx].setStorage(
            tcu::TextureFormat(tcu::TextureFormat::R, tcu::TextureFormat::UNSIGNED_INT32), width, height);

        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x)
            {
                const tcu::RGBA lowColor = m_iterationSampleBuffers[sampleNdx].getPixel(x, y);
                const tcu::RGBA highColor =
                    m_iterationSampleBuffers[sampleNdx + (int)sampleCoverage.size()].getPixel(x, y);
                uint16_t low;
                uint16_t high;

                {
                    int redBits   = (int)deFloatRound((float)lowColor.getRed() / 255.0f * 31);
                    int greenBits = (int)deFloatRound((float)lowColor.getGreen() / 255.0f * 63);
                    int blueBits  = (int)deFloatRound((float)lowColor.getBlue() / 255.0f * 31);

                    low = (uint16_t)(redBits | (greenBits << 5) | (blueBits << 11));
                }
                {
                    int redBits   = (int)deFloatRound((float)highColor.getRed() / 255.0f * 31);
                    int greenBits = (int)deFloatRound((float)highColor.getGreen() / 255.0f * 63);
                    int blueBits  = (int)deFloatRound((float)highColor.getBlue() / 255.0f * 31);

                    high = (uint16_t)(redBits | (greenBits << 5) | (blueBits << 11));
                }

                sampleCoverage[sampleNdx].getAccess().setPixel(tcu::UVec4((((uint32_t)high) << 16) | low, 0, 0, 0), x,
                                                               y);
            }
    }

    // verify masks

    if (m_numRequestedSamples == 0)
    {
        // single sample target, expect mask = 0x01
        const int printFloodLimit = 5;
        int printCount            = 0;

        m_testCtx.getLog() << tcu::TestLog::Message << "Verifying sample mask is 0x00000001."
                           << tcu::TestLog::EndMessage;

        for (int y = 0; y < height; ++y)
            for (int x = 0; x < width; ++x)
            {
                uint32_t mask = sampleCoverage[0].getAccess().getPixelUint(x, y).x();
                if (mask != 0x01)
                {
                    allOk = false;

                    if (++printCount <= printFloodLimit)
                    {
                        m_testCtx.getLog()
                            << tcu::TestLog::Message << "Pixel (" << x << ", " << y << "): Invalid mask, got "
                            << tcu::Format::Hex<8>(mask) << ", expected " << tcu::Format::Hex<8>(0x01) << "\n"
                            << tcu::TestLog::EndMessage;
                    }
                }
            }

        if (!allOk && printCount > printFloodLimit)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "...\n"
                               << "Omitted " << (printCount - printFloodLimit) << " error descriptions."
                               << tcu::TestLog::EndMessage;
        }
    }
    else
    {
        // check uniqueness
        {
            bool uniquenessOk         = true;
            int printCount            = 0;
            const int printFloodLimit = 5;

            m_testCtx.getLog() << tcu::TestLog::Message << "Verifying invocation sample masks do not share bits."
                               << tcu::TestLog::EndMessage;

            for (int y = 0; y < height; ++y)
                for (int x = 0; x < width; ++x)
                {
                    bool maskBitsNotUnique = false;

                    for (int sampleNdxA = 0;
                         sampleNdxA < m_numTargetSamples && (!maskBitsNotUnique || printCount < printFloodLimit);
                         ++sampleNdxA)
                        for (int sampleNdxB = sampleNdxA + 1;
                             sampleNdxB < m_numTargetSamples && (!maskBitsNotUnique || printCount < printFloodLimit);
                             ++sampleNdxB)
                        {
                            const uint32_t maskA = sampleCoverage[sampleNdxA].getAccess().getPixelUint(x, y).x();
                            const uint32_t maskB = sampleCoverage[sampleNdxB].getAccess().getPixelUint(x, y).x();

                            // equal mask == emitted by the same invocation
                            if (maskA != maskB)
                            {
                                // shares samples?
                                if (maskA & maskB)
                                {
                                    maskBitsNotUnique = true;
                                    uniquenessOk      = false;

                                    if (++printCount <= printFloodLimit)
                                    {
                                        m_testCtx.getLog()
                                            << tcu::TestLog::Message << "Pixel (" << x << ", " << y << "):\n"
                                            << "\tSamples " << sampleNdxA << " and " << sampleNdxB
                                            << " share mask bits\n"
                                            << "\tMask" << sampleNdxA << " = " << tcu::Format::Hex<8>(maskA) << "\n"
                                            << "\tMask" << sampleNdxB << " = " << tcu::Format::Hex<8>(maskB) << "\n"
                                            << tcu::TestLog::EndMessage;
                                    }
                                }
                            }
                        }
                }

            if (!uniquenessOk)
            {
                allOk = false;

                if (printCount > printFloodLimit)
                    m_testCtx.getLog() << tcu::TestLog::Message << "...\n"
                                       << "Omitted " << (printCount - printFloodLimit) << " error descriptions."
                                       << tcu::TestLog::EndMessage;
            }
        }

        // check number of sample mask bit groups is valid ( == number of invocations )
        {
            const uint32_t minNumInvocations = (uint32_t)de::max(1, (m_numTargetSamples + 1) / 2);
            bool countOk                     = true;
            int printCount                   = 0;
            const int printFloodLimit        = 5;

            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Verifying cardinality of separate sample mask bit sets. Expecting equal to the "
                                  "number of invocations, (greater or equal to "
                               << minNumInvocations << ")" << tcu::TestLog::EndMessage;

            for (int y = 0; y < height; ++y)
                for (int x = 0; x < width; ++x)
                {
                    std::set<uint32_t> masks;

                    for (int maskNdx = 0; maskNdx < m_numTargetSamples; ++maskNdx)
                    {
                        const uint32_t mask = sampleCoverage[maskNdx].getAccess().getPixelUint(x, y).x();
                        masks.insert(mask);
                    }

                    if ((int)masks.size() < (int)minNumInvocations)
                    {
                        if (++printCount <= printFloodLimit)
                        {
                            m_testCtx.getLog() << tcu::TestLog::Message << "Pixel (" << x << ", " << y
                                               << "): Pixel invocations had only " << (int)masks.size()
                                               << " separate mask sets. Expected " << minNumInvocations
                                               << " or more. Found masks:" << tcu::TestLog::EndMessage;

                            for (std::set<uint32_t>::iterator it = masks.begin(); it != masks.end(); ++it)
                                m_testCtx.getLog()
                                    << tcu::TestLog::Message << "\tMask: " << tcu::Format::Hex<8>(*it) << "\n"
                                    << tcu::TestLog::EndMessage;
                        }

                        countOk = false;
                    }
                }

            if (!countOk)
            {
                allOk = false;

                if (printCount > printFloodLimit)
                    m_testCtx.getLog() << tcu::TestLog::Message << "...\n"
                                       << "Omitted " << (printCount - printFloodLimit) << " error descriptions."
                                       << tcu::TestLog::EndMessage;
            }
        }
    }

    if (!allOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
}

class SampleMaskWriteCase : public SampleMaskBaseCase
{
public:
    enum TestMode
    {
        TEST_DISCARD = 0,
        TEST_INVERSE,

        TEST_LAST
    };
    SampleMaskWriteCase(Context &context, const char *name, const char *desc, int sampleCount, RenderTarget target,
                        ShaderRunMode runMode, TestMode testMode);
    ~SampleMaskWriteCase(void);

    void init(void);
    void preDraw(void);
    void postDraw(void);

private:
    enum
    {
        RENDER_SIZE = 64
    };

    std::string genFragmentSource(int numTargetSamples) const;
    bool verifyImage(const tcu::Surface &resultImage);

    const TestMode m_testMode;
};

SampleMaskWriteCase::SampleMaskWriteCase(Context &context, const char *name, const char *desc, int sampleCount,
                                         RenderTarget target, ShaderRunMode runMode, TestMode testMode)
    : SampleMaskBaseCase(context, name, desc, sampleCount, target, RENDER_SIZE, runMode)
    , m_testMode(testMode)
{
    DE_ASSERT(testMode < TEST_LAST);
}

SampleMaskWriteCase::~SampleMaskWriteCase(void)
{
}

void SampleMaskWriteCase::init(void)
{
    // log the test method and expectations
    if (m_testMode == TEST_DISCARD)
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Discarding half of the samples using gl_SampleMask, expecting:\n"
                           << "    1) half intensity on multisample targets (numSamples > 1)\n"
                           << "    2) full discard on multisample targets (numSamples == 1)\n"
                           << "    3) full intensity (no discard) on singlesample targets. (Mask is only applied as a "
                              "multisample operation.)\n"
                           << tcu::TestLog::EndMessage;
    else if (m_testMode == TEST_INVERSE)
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Discarding half of the samples using GL_SAMPLE_MASK, setting inverse mask in fragment "
                              "shader using gl_SampleMask, expecting:\n"
                           << "    1) full discard on multisample targets (mask & modifiedCoverge == 0)\n"
                           << "    2) full intensity (no discard) on singlesample targets. (Mask and coverage is only "
                              "applied as a multisample operation.)\n"
                           << tcu::TestLog::EndMessage;
    else
        DE_ASSERT(false);

    SampleMaskBaseCase::init();
}

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

    if (m_testMode == TEST_INVERSE)
    {
        // set mask to 0xAAAA.., set inverse mask bit coverage in shader

        const int maskLoc   = gl.getUniformLocation(m_program->getProgram(), "u_mask");
        const uint32_t mask = (uint32_t)0xAAAAAAAAUL;

        if (maskLoc == -1)
            throw tcu::TestError("Location of u_mask was -1");

        gl.enable(GL_SAMPLE_MASK);
        gl.sampleMaski(0, mask);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");

        gl.uniform1ui(maskLoc, mask);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set mask uniform");

        m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask " << tcu::Format::Hex<4>(mask)
                           << tcu::TestLog::EndMessage;
    }

    SampleMaskBaseCase::preDraw();
}

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

    if (m_testMode == TEST_INVERSE)
    {
        const uint32_t fullMask = (1U << m_numTargetSamples) - 1;

        gl.disable(GL_SAMPLE_MASK);
        gl.sampleMaski(0, fullMask);
        GLU_EXPECT_NO_ERROR(gl.getError(), "set mask");
    }

    SampleMaskBaseCase::postDraw();
}

std::string SampleMaskWriteCase::genFragmentSource(int numTargetSamples) const
{
    DE_ASSERT(numTargetSamples != 0);
    DE_UNREF(numTargetSamples);

    std::ostringstream buf;
    const bool supportsES32orGL45 =
        glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2)) ||
        contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5));
    map<string, string> args;
    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);
    args["GLSL_EXTENSION"]    = supportsES32orGL45 ? "" : "#extension GL_OES_sample_variables : require";

    if (m_testMode == TEST_DISCARD)
    {
        // mask out every other coverage bit

        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "void main (void)\n"
               "{\n"
               "    for (int i = 0; i < gl_SampleMask.length(); ++i)\n"
               "        gl_SampleMask[i] = int(0xAAAAAAAA);\n"
               "\n";

        if (m_runMode == RUN_PER_SAMPLE)
            buf << "    // force per-sample shading\n"
                   "    highp float blue = float(gl_SampleID);\n"
                   "\n"
                   "    fragColor = vec4(0.0, 1.0, blue, 1.0);\n"
                   "}\n";
        else
            buf << "    fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
                   "}\n";
    }
    else if (m_testMode == TEST_INVERSE)
    {
        // inverse every coverage bit

        buf << "${GLSL_VERSION_DECL}\n"
               "${GLSL_EXTENSION}\n"
               "layout(location = 0) out mediump vec4 fragColor;\n"
               "uniform highp uint u_mask;\n"
               "void main (void)\n"
               "{\n"
               "    gl_SampleMask[0] = int(~u_mask);\n"
               "\n";

        if (m_runMode == RUN_PER_SAMPLE)
            buf << "    // force per-sample shading\n"
                   "    highp float blue = float(gl_SampleID);\n"
                   "\n"
                   "    fragColor = vec4(0.0, 1.0, blue, 1.0);\n"
                   "}\n";
        else
            buf << "    fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
                   "}\n";
    }
    else
        DE_ASSERT(false);

    return tcu::StringTemplate(buf.str()).specialize(args);
}

bool SampleMaskWriteCase::verifyImage(const tcu::Surface &resultImage)
{
    const bool singleSampleTarget = m_numRequestedSamples == 0 && !(m_renderTarget == TARGET_DEFAULT &&
                                                                    m_context.getRenderTarget().getNumSamples() > 1);

    if (m_testMode == TEST_DISCARD)
    {
        if (singleSampleTarget)
        {
            // single sample case => multisample operations are not effective => don't discard anything
            // expect green
            return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 1.0f, 0.0f)));
        }
        else if (m_numTargetSamples == 1)
        {
            // total discard, expect black
            return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 0.0f, 0.0f)));
        }
        else
        {
            // partial discard, expect something between black and green
            return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), PartialDiscardVerifier());
        }
    }
    else if (m_testMode == TEST_INVERSE)
    {
        if (singleSampleTarget)
        {
            // single sample case => multisample operations are not effective => don't discard anything
            // expect green
            return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 1.0f, 0.0f)));
        }
        else
        {
            // total discard, expect black
            return verifyImageWithVerifier(resultImage, m_testCtx.getLog(), ColorVerifier(tcu::Vec3(0.0f, 0.0f, 0.0f)));
        }
    }
    else
    {
        DE_ASSERT(false);
        return false;
    }
}

} // namespace

SampleVariableTests::SampleVariableTests(Context &context)
    : TestCaseGroup(context, "sample_variables", "Test sample variables")
{
}

SampleVariableTests::~SampleVariableTests(void)
{
}

void SampleVariableTests::init(void)
{
    tcu::TestCaseGroup *const numSampleGroup = new tcu::TestCaseGroup(m_testCtx, "num_samples", "Test NumSamples");
    tcu::TestCaseGroup *const maxSampleGroup = new tcu::TestCaseGroup(m_testCtx, "max_samples", "Test MaxSamples");
    tcu::TestCaseGroup *const sampleIDGroup  = new tcu::TestCaseGroup(m_testCtx, "sample_id", "Test SampleID");
    tcu::TestCaseGroup *const samplePosGroup = new tcu::TestCaseGroup(m_testCtx, "sample_pos", "Test SamplePosition");
    tcu::TestCaseGroup *const sampleMaskInGroup =
        new tcu::TestCaseGroup(m_testCtx, "sample_mask_in", "Test SampleMaskIn");
    tcu::TestCaseGroup *const sampleMaskGroup = new tcu::TestCaseGroup(m_testCtx, "sample_mask", "Test SampleMask");

    addChild(numSampleGroup);
    addChild(maxSampleGroup);
    addChild(sampleIDGroup);
    addChild(samplePosGroup);
    addChild(sampleMaskInGroup);
    addChild(sampleMaskGroup);

    static const struct RenderTarget
    {
        const char *name;
        const char *desc;
        int numSamples;
        MultisampleRenderCase::RenderTarget target;
    } targets[] = {
        {"default_framebuffer", "Test with default framebuffer", 0, MultisampleRenderCase::TARGET_DEFAULT},
        {"singlesample_texture", "Test with singlesample texture", 0, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_1", "Test with multisample texture", 1, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_2", "Test with multisample texture", 2, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_4", "Test with multisample texture", 4, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_8", "Test with multisample texture", 8, MultisampleRenderCase::TARGET_TEXTURE},
        {"multisample_texture_16", "Test with multisample texture", 16, MultisampleRenderCase::TARGET_TEXTURE},
        {"singlesample_rbo", "Test with singlesample rbo", 0, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_1", "Test with multisample rbo", 1, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_2", "Test with multisample rbo", 2, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_4", "Test with multisample rbo", 4, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_8", "Test with multisample rbo", 8, MultisampleRenderCase::TARGET_RENDERBUFFER},
        {"multisample_rbo_16", "Test with multisample rbo", 16, MultisampleRenderCase::TARGET_RENDERBUFFER},
    };

    // .num_samples
    {
        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            numSampleGroup->addChild(new NumSamplesCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                        targets[targetNdx].numSamples, targets[targetNdx].target));
    }

    // .max_samples
    {
        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            maxSampleGroup->addChild(new MaxSamplesCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                        targets[targetNdx].numSamples, targets[targetNdx].target));
    }

    // .sample_ID
    {
        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            sampleIDGroup->addChild(new SampleIDCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                     targets[targetNdx].numSamples, targets[targetNdx].target));
    }

    // .sample_pos
    {{tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "correctness", "Test SamplePos correctness");
    samplePosGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        group->addChild(new SamplePosCorrectnessCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                     targets[targetNdx].numSamples, targets[targetNdx].target));
}

{
    tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "distribution", "Test SamplePos distribution");
    samplePosGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        group->addChild(new SamplePosDistributionCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                      targets[targetNdx].numSamples, targets[targetNdx].target));
}
} // namespace Functional

// .sample_mask_in
{// .sample_mask
 {tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "sample_mask", "Test with GL_SAMPLE_MASK");
sampleMaskInGroup->addChild(group);

for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
    group->addChild(new SampleMaskCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                       targets[targetNdx].numSamples, targets[targetNdx].target));
} // namespace gles31
// .bit_count_per_pixel
{
    tcu::TestCaseGroup *const group =
        new tcu::TestCaseGroup(m_testCtx, "bit_count_per_pixel", "Test number of coverage bits");
    sampleMaskInGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        group->addChild(new SampleMaskCountCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                targets[targetNdx].numSamples, targets[targetNdx].target,
                                                SampleMaskCountCase::RUN_PER_PIXEL));
}
// .bit_count_per_sample
{
    tcu::TestCaseGroup *const group =
        new tcu::TestCaseGroup(m_testCtx, "bit_count_per_sample", "Test number of coverage bits");
    sampleMaskInGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        group->addChild(new SampleMaskCountCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                targets[targetNdx].numSamples, targets[targetNdx].target,
                                                SampleMaskCountCase::RUN_PER_SAMPLE));
}
// .bit_count_per_two_samples
{
    tcu::TestCaseGroup *const group =
        new tcu::TestCaseGroup(m_testCtx, "bit_count_per_two_samples", "Test number of coverage bits");
    sampleMaskInGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        group->addChild(new SampleMaskCountCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                targets[targetNdx].numSamples, targets[targetNdx].target,
                                                SampleMaskCountCase::RUN_PER_TWO_SAMPLES));
}
// .bits_unique_per_sample
{
    tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "bits_unique_per_sample", "Test coverage bits");
    sampleMaskInGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        if (targets[targetNdx].target == MultisampleRenderCase::TARGET_TEXTURE)
            group->addChild(new SampleMaskUniqueCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                     targets[targetNdx].numSamples, targets[targetNdx].target,
                                                     SampleMaskUniqueCase::RUN_PER_SAMPLE));
}
// .bits_unique_per_two_samples
{
    tcu::TestCaseGroup *const group =
        new tcu::TestCaseGroup(m_testCtx, "bits_unique_per_two_samples", "Test coverage bits");
    sampleMaskInGroup->addChild(group);

    for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
        if (targets[targetNdx].target == MultisampleRenderCase::TARGET_TEXTURE)
            group->addChild(new SampleMaskUniqueSetCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                        targets[targetNdx].numSamples, targets[targetNdx].target,
                                                        SampleMaskUniqueCase::RUN_PER_TWO_SAMPLES));
}
} // namespace deqp

// .sample_mask
{
    // .discard_half_per_pixel
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "discard_half_per_pixel", "Test coverage bits");
        sampleMaskGroup->addChild(group);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            group->addChild(new SampleMaskWriteCase(
                m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_PIXEL, SampleMaskWriteCase::TEST_DISCARD));
    }
    // .discard_half_per_sample
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "discard_half_per_sample", "Test coverage bits");
        sampleMaskGroup->addChild(group);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            group->addChild(new SampleMaskWriteCase(
                m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_SAMPLE, SampleMaskWriteCase::TEST_DISCARD));
    }
    // .discard_half_per_two_samples
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "discard_half_per_two_samples", "Test coverage bits");
        sampleMaskGroup->addChild(group);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                    targets[targetNdx].numSamples, targets[targetNdx].target,
                                                    SampleMaskWriteCase::RUN_PER_TWO_SAMPLES,
                                                    SampleMaskWriteCase::TEST_DISCARD));
    }

    // .discard_half_per_two_samples
    {
        tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "inverse_per_pixel", "Test coverage bits");
        sampleMaskGroup->addChild(group);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            group->addChild(new SampleMaskWriteCase(
                m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_PIXEL, SampleMaskWriteCase::TEST_INVERSE));
    }
    // .inverse_per_sample
    {
        tcu::TestCaseGroup *const group = new tcu::TestCaseGroup(m_testCtx, "inverse_per_sample", "Test coverage bits");
        sampleMaskGroup->addChild(group);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            group->addChild(new SampleMaskWriteCase(
                m_context, targets[targetNdx].name, targets[targetNdx].desc, targets[targetNdx].numSamples,
                targets[targetNdx].target, SampleMaskWriteCase::RUN_PER_SAMPLE, SampleMaskWriteCase::TEST_INVERSE));
    }
    // .inverse_per_two_samples
    {
        tcu::TestCaseGroup *const group =
            new tcu::TestCaseGroup(m_testCtx, "inverse_per_two_samples", "Test coverage bits");
        sampleMaskGroup->addChild(group);

        for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(targets); ++targetNdx)
            group->addChild(new SampleMaskWriteCase(m_context, targets[targetNdx].name, targets[targetNdx].desc,
                                                    targets[targetNdx].numSamples, targets[targetNdx].target,
                                                    SampleMaskWriteCase::RUN_PER_TWO_SAMPLES,
                                                    SampleMaskWriteCase::TEST_INVERSE));
    }
}
}

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