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

#include "es31fTextureMultisampleTests.hpp"
#include "tcuTestLog.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuStringTemplate.hpp"
#include "tcuTextureUtil.hpp"
#include "glsStateQueryUtil.hpp"
#include "tcuRasterizationVerifier.hpp"
#include "gluRenderContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluObjectWrapper.hpp"
#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluStrUtil.hpp"
#include "gluContextInfo.hpp"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"
#include "deStringUtil.hpp"
#include "deRandom.hpp"

using namespace glw;

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using tcu::RasterizationArguments;
using tcu::TriangleSceneSpec;

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

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

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

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

    return result;
}

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

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

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

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

    mask[numWords - 1] = (uint32_t)((1ULL << topWordBits) - (uint32_t)1);
    return mask;
}

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

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

    mask[numWords - 1] = (uint32_t)(1ULL << topWordBits);
    return mask;
}

std::string specializeShader(Context &context, const char *code)
{
    const glu::ContextType contextType = context.getRenderContext().getType();
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(contextType);
    std::map<std::string, std::string> specializationMap;

    specializationMap["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

    return tcu::StringTemplate(code).specialize(specializationMap);
}

class SamplePosRasterizationTest : public TestCase
{
public:
    SamplePosRasterizationTest(Context &context, const char *name, const char *desc, int samples);
    ~SamplePosRasterizationTest(void);

private:
    void init(void);
    void deinit(void);
    IterateResult iterate(void);
    void genMultisampleTexture(void);
    void genSamplerProgram(void);
    bool testMultisampleTexture(int sampleNdx);
    void drawSample(tcu::Surface &dst, int sampleNdx);
    void convertToSceneSpec(TriangleSceneSpec &scene, const tcu::Vec2 &samplePos) const;

    struct Triangle
    {
        tcu::Vec4 p1;
        tcu::Vec4 p2;
        tcu::Vec4 p3;
    };

    const int m_samples;
    const int m_canvasSize;
    std::vector<Triangle> m_testTriangles;

    int m_iteration;
    bool m_allIterationsOk;

    GLuint m_texID;
    GLuint m_vaoID;
    GLuint m_vboID;
    std::vector<tcu::Vec2> m_samplePositions;
    int m_subpixelBits;

    const glu::ShaderProgram *m_samplerProgram;
    GLint m_samplerProgramPosLoc;
    GLint m_samplerProgramSamplerLoc;
    GLint m_samplerProgramSampleNdxLoc;
};

SamplePosRasterizationTest::SamplePosRasterizationTest(Context &context, const char *name, const char *desc,
                                                       int samples)
    : TestCase(context, name, desc)
    , m_samples(samples)
    , m_canvasSize(256)
    , m_iteration(0)
    , m_allIterationsOk(true)
    , m_texID(0)
    , m_vaoID(0)
    , m_vboID(0)
    , m_subpixelBits(0)
    , m_samplerProgram(DE_NULL)
    , m_samplerProgramPosLoc(-1)
    , m_samplerProgramSamplerLoc(-1)
    , m_samplerProgramSampleNdxLoc(-1)
{
}

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

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

    // requirements

    if (m_context.getRenderTarget().getWidth() < m_canvasSize || m_context.getRenderTarget().getHeight() < m_canvasSize)
        throw tcu::NotSupportedError("render target size must be at least " + de::toString(m_canvasSize) + "x" +
                                     de::toString(m_canvasSize));

    gl.getIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &maxSamples);
    if (m_samples > maxSamples)
        throw tcu::NotSupportedError("Requested sample count is greater than GL_MAX_COLOR_TEXTURE_SAMPLES");

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

    gl.getIntegerv(GL_SUBPIXEL_BITS, &m_subpixelBits);
    m_testCtx.getLog() << tcu::TestLog::Message << "GL_SUBPIXEL_BITS = " << m_subpixelBits << tcu::TestLog::EndMessage;

    // generate textures & other gl stuff

    m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample texture" << tcu::TestLog::EndMessage;

    gl.genTextures(1, &m_texID);
    gl.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
    gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_samples, GL_RGBA8, m_canvasSize, m_canvasSize, GL_TRUE);
    GLU_EXPECT_NO_ERROR(gl.getError(), "texStorage2DMultisample");

    gl.genVertexArrays(1, &m_vaoID);
    gl.bindVertexArray(m_vaoID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bindVertexArray");

    gl.genBuffers(1, &m_vboID);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_vboID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bindBuffer");

    // generate test scene
    for (int i = 0; i < 20; ++i)
    {
        // vertical spikes
        Triangle tri;
        tri.p1 = tcu::Vec4(((float)i + 1.0f / (float)(i + 1)) / 20.0f, 0.0f, 0.0f, 1.0f);
        tri.p2 = tcu::Vec4(((float)i + 0.3f + 1.0f / (float)(i + 1)) / 20.0f, 0.0f, 0.0f, 1.0f);
        tri.p3 = tcu::Vec4(((float)i + 1.0f / (float)(i + 1)) / 20.0f, -1.0f, 0.0f, 1.0f);
        m_testTriangles.push_back(tri);
    }
    for (int i = 0; i < 20; ++i)
    {
        // horisontal spikes
        Triangle tri;
        tri.p1 = tcu::Vec4(-1.0f, ((float)i + 1.0f / (float)(i + 1)) / 20.0f, 0.0f, 1.0f);
        tri.p2 = tcu::Vec4(-1.0f, ((float)i + 0.3f + 1.0f / (float)(i + 1)) / 20.0f, 0.0f, 1.0f);
        tri.p3 = tcu::Vec4(0.0f, ((float)i + 1.0f / (float)(i + 1)) / 20.0f, 0.0f, 1.0f);
        m_testTriangles.push_back(tri);
    }

    for (int i = 0; i < 20; ++i)
    {
        // fan
        const tcu::Vec2 p = tcu::Vec2(deFloatCos(((float)i) / 20.0f * DE_PI * 2) * 0.5f + 0.5f,
                                      deFloatSin(((float)i) / 20.0f * DE_PI * 2) * 0.5f + 0.5f);
        const tcu::Vec2 d = tcu::Vec2(0.1f, 0.02f);

        Triangle tri;
        tri.p1 = tcu::Vec4(0.4f, 0.4f, 0.0f, 1.0f);
        tri.p2 = tcu::Vec4(p.x(), p.y(), 0.0f, 1.0f);
        tri.p3 = tcu::Vec4(p.x() + d.x(), p.y() + d.y(), 0.0f, 1.0f);
        m_testTriangles.push_back(tri);
    }
    {
        Triangle tri;
        tri.p1 = tcu::Vec4(-0.202f, -0.202f, 0.0f, 1.0f);
        tri.p2 = tcu::Vec4(-0.802f, -0.202f, 0.0f, 1.0f);
        tri.p3 = tcu::Vec4(-0.802f, -0.802f, 0.0f, 1.0f);
        m_testTriangles.push_back(tri);
    }

    // generate multisample texture (and query the sample positions in it)
    genMultisampleTexture();

    // verify queried samples are in a valid range
    for (int sampleNdx = 0; sampleNdx < m_samples; ++sampleNdx)
    {
        if (m_samplePositions[sampleNdx].x() < 0.0f || m_samplePositions[sampleNdx].x() > 1.0f ||
            m_samplePositions[sampleNdx].y() < 0.0f || m_samplePositions[sampleNdx].y() > 1.0f)
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "// ERROR: Sample position of sample " << sampleNdx
                               << " should be in range ([0, 1], [0, 1]). Got " << m_samplePositions[sampleNdx]
                               << tcu::TestLog::EndMessage;
            throw tcu::TestError("invalid sample position");
        }
    }

    // generate sampler program
    genSamplerProgram();
}

void SamplePosRasterizationTest::deinit(void)
{
    if (m_vboID)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vboID);
        m_vboID = 0;
    }

    if (m_vaoID)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
        m_vaoID = 0;
    }

    if (m_texID)
    {
        m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texID);
        m_texID = 0;
    }

    if (m_samplerProgram)
    {
        delete m_samplerProgram;
        m_samplerProgram = DE_NULL;
    }
}

SamplePosRasterizationTest::IterateResult SamplePosRasterizationTest::iterate(void)
{
    m_allIterationsOk &= testMultisampleTexture(m_iteration);
    m_iteration++;

    if (m_iteration < m_samples)
        return CONTINUE;

    // End result
    if (m_allIterationsOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Pixel comparison failed");

    return STOP;
}

void SamplePosRasterizationTest::genMultisampleTexture(void)
{
    const char *const vertexShaderSource   = "${GLSL_VERSION_DECL}\n"
                                             "in highp vec4 a_position;\n"
                                             "void main (void)\n"
                                             "{\n"
                                             "    gl_Position = a_position;\n"
                                             "}\n";
    const char *const fragmentShaderSource = "${GLSL_VERSION_DECL}\n"
                                             "layout(location = 0) out highp vec4 fragColor;\n"
                                             "void main (void)\n"
                                             "{\n"
                                             "    fragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
                                             "}\n";

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const glu::ShaderProgram program(m_context.getRenderContext(),
                                     glu::ProgramSources()
                                         << glu::VertexSource(specializeShader(m_context, vertexShaderSource))
                                         << glu::FragmentSource(specializeShader(m_context, fragmentShaderSource)));
    const GLuint posLoc = gl.getAttribLocation(program.getProgram(), "a_position");
    GLuint fboID        = 0;

    if (!program.isOk())
    {
        m_testCtx.getLog() << program;
        throw tcu::TestError("Failed to build shader.");
    }

    gl.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
    gl.bindVertexArray(m_vaoID);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_vboID);

    // Setup fbo for drawing and for sample position query

    m_testCtx.getLog() << tcu::TestLog::Message << "Attaching texture to FBO" << tcu::TestLog::EndMessage;

    gl.genFramebuffers(1, &fboID);
    gl.bindFramebuffer(GL_FRAMEBUFFER, fboID);
    gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_texID, 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "framebufferTexture2D");

    // Query sample positions of the multisample texture by querying the sample positions
    // from an fbo which has the multisample texture as attachment.

    m_testCtx.getLog() << tcu::TestLog::Message << "Sample locations:" << tcu::TestLog::EndMessage;

    for (int sampleNdx = 0; sampleNdx < m_samples; ++sampleNdx)
    {
        gls::StateQueryUtil::StateQueryMemoryWriteGuard<float[2]> position;

        gl.getMultisamplefv(GL_SAMPLE_POSITION, (uint32_t)sampleNdx, position);
        if (!position.verifyValidity(m_testCtx))
            throw tcu::TestError("Error while querying sample positions");

        m_testCtx.getLog() << tcu::TestLog::Message << "\t" << sampleNdx << ": (" << position[0] << ", " << position[1]
                           << ")" << tcu::TestLog::EndMessage;
        m_samplePositions.push_back(tcu::Vec2(position[0], position[1]));
    }

    // Draw test pattern to texture

    m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test pattern to the texture" << tcu::TestLog::EndMessage;

    gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(m_testTriangles.size() * sizeof(Triangle)), &m_testTriangles[0],
                  GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bufferData");

    gl.viewport(0, 0, m_canvasSize, m_canvasSize);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(GL_COLOR_BUFFER_BIT);
    gl.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(posLoc);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttribPointer");

    gl.useProgram(program.getProgram());
    gl.drawArrays(GL_TRIANGLES, 0, (glw::GLsizei)(m_testTriangles.size() * 3));
    GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");

    gl.disableVertexAttribArray(posLoc);
    gl.useProgram(0);
    gl.deleteFramebuffers(1, &fboID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "cleanup");
}

void SamplePosRasterizationTest::genSamplerProgram(void)
{
    const char *const vertexShaderSource = "${GLSL_VERSION_DECL}\n"
                                           "in highp vec4 a_position;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    gl_Position = a_position;\n"
                                           "}\n";
    const char *const fragShaderSource   = "${GLSL_VERSION_DECL}\n"
                                           "layout(location = 0) out highp vec4 fragColor;\n"
                                           "uniform highp sampler2DMS u_sampler;\n"
                                           "uniform highp int u_sample;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    fragColor = texelFetch(u_sampler, ivec2(int(floor(gl_FragCoord.x)), "
                                           "int(floor(gl_FragCoord.y))), u_sample);\n"
                                           "}\n";
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Generate sampler shader", "Generate sampler shader");
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    m_samplerProgram = new glu::ShaderProgram(
        m_context.getRenderContext(), glu::ProgramSources()
                                          << glu::VertexSource(specializeShader(m_context, vertexShaderSource))
                                          << glu::FragmentSource(specializeShader(m_context, fragShaderSource)));
    m_testCtx.getLog() << *m_samplerProgram;

    if (!m_samplerProgram->isOk())
        throw tcu::TestError("Could not create sampler program.");

    m_samplerProgramPosLoc       = gl.getAttribLocation(m_samplerProgram->getProgram(), "a_position");
    m_samplerProgramSamplerLoc   = gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sampler");
    m_samplerProgramSampleNdxLoc = gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sample");
}

bool SamplePosRasterizationTest::testMultisampleTexture(int sampleNdx)
{
    tcu::Surface glSurface(m_canvasSize, m_canvasSize);
    TriangleSceneSpec scene;

    // Draw sample
    drawSample(glSurface, sampleNdx);

    // Draw reference(s)
    convertToSceneSpec(scene, m_samplePositions[sampleNdx]);

    // Compare
    {
        RasterizationArguments args;
        args.redBits      = m_context.getRenderTarget().getPixelFormat().redBits;
        args.greenBits    = m_context.getRenderTarget().getPixelFormat().greenBits;
        args.blueBits     = m_context.getRenderTarget().getPixelFormat().blueBits;
        args.numSamples   = 0;
        args.subpixelBits = m_subpixelBits;

        return tcu::verifyTriangleGroupRasterization(glSurface, scene, args, m_testCtx.getLog(),
                                                     tcu::VERIFICATIONMODE_STRICT);
    }
}

void SamplePosRasterizationTest::drawSample(tcu::Surface &dst, int sampleNdx)
{
    // Downsample using only one sample
    static const tcu::Vec4 fullscreenQuad[] = {tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
                                               tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, -1.0f, 0.0f, 1.0f)};

    const tcu::ScopedLogSection section(
        m_testCtx.getLog(), "Test sample position " + de::toString(sampleNdx + 1) + "/" + de::toString(m_samples),
        "Test sample position " + de::toString(sampleNdx + 1) + "/" + de::toString(m_samples));
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();

    gl.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
    gl.bindVertexArray(m_vaoID);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_vboID);

    gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), &fullscreenQuad[0], GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bufferData");

    gl.viewport(0, 0, m_canvasSize, m_canvasSize);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(GL_COLOR_BUFFER_BIT);
    gl.vertexAttribPointer(m_samplerProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(m_samplerProgramPosLoc);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttribPointer");

    gl.useProgram(m_samplerProgram->getProgram());
    gl.uniform1i(m_samplerProgramSamplerLoc, 0);
    gl.uniform1i(m_samplerProgramSampleNdxLoc, (int32_t)sampleNdx);
    GLU_EXPECT_NO_ERROR(gl.getError(), "useprogram");

    m_testCtx.getLog() << tcu::TestLog::Message << "Reading from texture with sample index " << sampleNdx
                       << tcu::TestLog::EndMessage;

    gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");

    gl.disableVertexAttribArray(m_samplerProgramPosLoc);
    gl.useProgram(0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "cleanup");

    gl.finish();
    glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
    GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
}

void SamplePosRasterizationTest::convertToSceneSpec(TriangleSceneSpec &scene, const tcu::Vec2 &samplePos) const
{
    // Triangles are offset from the pixel center by "offset". Move the triangles back to take this into account.
    const tcu::Vec4 offset = tcu::Vec4(samplePos.x() - 0.5f, samplePos.y() - 0.5f, 0.0f, 0.0f) /
                             tcu::Vec4((float)m_canvasSize, (float)m_canvasSize, 1.0f, 1.0f) * 2.0f;

    for (int triangleNdx = 0; triangleNdx < (int)m_testTriangles.size(); ++triangleNdx)
    {
        TriangleSceneSpec::SceneTriangle triangle;

        triangle.positions[0] = m_testTriangles[triangleNdx].p1 - offset;
        triangle.positions[1] = m_testTriangles[triangleNdx].p2 - offset;
        triangle.positions[2] = m_testTriangles[triangleNdx].p3 - offset;

        triangle.sharedEdge[0] = false;
        triangle.sharedEdge[1] = false;
        triangle.sharedEdge[2] = false;

        scene.triangles.push_back(triangle);
    }
}

class SampleMaskCase : public TestCase
{
public:
    enum CaseFlags
    {
        FLAGS_NONE              = 0,
        FLAGS_ALPHA_TO_COVERAGE = (1ULL << 0),
        FLAGS_SAMPLE_COVERAGE   = (1ULL << 1),
        FLAGS_HIGH_BITS         = (1ULL << 2),
    };

    SampleMaskCase(Context &context, const char *name, const char *desc, int samples, int flags);
    ~SampleMaskCase(void);

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

    void genSamplerProgram(void);
    void genAlphaProgram(void);
    void updateTexture(int sample);
    bool verifyTexture(int sample);
    void drawSample(tcu::Surface &dst, int sample);

    glw::GLint getMaxConformantSampleCount(glw::GLenum target, glw::GLenum internalFormat);

    const int m_samples;
    const int m_canvasSize;
    const int m_gridsize;
    const int m_effectiveSampleMaskWordCount;

    int m_flags;
    int m_currentSample;
    int m_allIterationsOk;

    glw::GLuint m_texID;
    glw::GLuint m_vaoID;
    glw::GLuint m_vboID;
    glw::GLuint m_fboID;

    const glu::ShaderProgram *m_samplerProgram;
    glw::GLint m_samplerProgramPosLoc;
    glw::GLint m_samplerProgramSamplerLoc;
    glw::GLint m_samplerProgramSampleNdxLoc;

    const glu::ShaderProgram *m_alphaProgram;
    glw::GLint m_alphaProgramPosLoc;
};

SampleMaskCase::SampleMaskCase(Context &context, const char *name, const char *desc, int samples, int flags)
    : TestCase(context, name, desc)
    , m_samples(samples)
    , m_canvasSize(256)
    , m_gridsize(16)
    , m_effectiveSampleMaskWordCount(getEffectiveSampleMaskWordCount(samples - 1))
    , m_flags(flags)
    , m_currentSample(-1)
    , m_allIterationsOk(true)
    , m_texID(0)
    , m_vaoID(0)
    , m_vboID(0)
    , m_fboID(0)
    , m_samplerProgram(DE_NULL)
    , m_samplerProgramPosLoc(-1)
    , m_samplerProgramSamplerLoc(-1)
    , m_samplerProgramSampleNdxLoc(-1)
    , m_alphaProgram(DE_NULL)
    , m_alphaProgramPosLoc(-1)
{
}

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

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

    // requirements

    if (m_context.getRenderTarget().getWidth() < m_canvasSize || m_context.getRenderTarget().getHeight() < m_canvasSize)
        throw tcu::NotSupportedError("render target size must be at least " + de::toString(m_canvasSize) + "x" +
                                     de::toString(m_canvasSize));

    gl.getIntegerv(GL_MAX_SAMPLE_MASK_WORDS, &maxSampleMaskWords);
    if (m_effectiveSampleMaskWordCount > maxSampleMaskWords)
        throw tcu::NotSupportedError("Test requires larger GL_MAX_SAMPLE_MASK_WORDS");

    maxSamples = getMaxConformantSampleCount(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8);
    if (m_samples > maxSamples)
        throw tcu::NotSupportedError(
            "Requested sample count is greater than glGetInternalformativ(GL_SAMPLES) for this target/format");

    m_testCtx.getLog() << tcu::TestLog::Message << "glGetInternalformativ(GL_SAMPLES) = " << maxSamples
                       << tcu::TestLog::EndMessage;

    // Don't even try to test high bits if there are none

    if ((m_flags & FLAGS_HIGH_BITS) && (m_samples % 32 == 0))
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Sample count is multiple of word size. No unused high bits in sample mask.\nSkipping."
                           << tcu::TestLog::EndMessage;
        throw tcu::NotSupportedError("Test requires unused high bits (sample count not multiple of 32)");
    }

    // generate textures

    m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample texture with sample count " << m_samples
                       << tcu::TestLog::EndMessage;

    gl.genTextures(1, &m_texID);
    gl.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
    gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_samples, GL_RGBA8, m_canvasSize, m_canvasSize, GL_FALSE);
    GLU_EXPECT_NO_ERROR(gl.getError(), "texStorage2DMultisample");

    // attach texture to fbo

    m_testCtx.getLog() << tcu::TestLog::Message << "Attaching texture to FBO" << tcu::TestLog::EndMessage;

    gl.genFramebuffers(1, &m_fboID);
    gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
    gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, m_texID, 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "framebufferTexture2D");

    // buffers

    gl.genVertexArrays(1, &m_vaoID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "genVertexArrays");

    gl.genBuffers(1, &m_vboID);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_vboID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "genBuffers");

    // generate grid pattern
    {
        std::vector<tcu::Vec4> gridData(m_gridsize * m_gridsize * 6);

        for (int y = 0; y < m_gridsize; ++y)
            for (int x = 0; x < m_gridsize; ++x)
            {
                gridData[(y * m_gridsize + x) * 6 + 0] =
                    tcu::Vec4(((float)(x + 0) / (float)m_gridsize) * 2.0f - 1.0f,
                              ((float)(y + 0) / (float)m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
                gridData[(y * m_gridsize + x) * 6 + 1] =
                    tcu::Vec4(((float)(x + 0) / (float)m_gridsize) * 2.0f - 1.0f,
                              ((float)(y + 1) / (float)m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
                gridData[(y * m_gridsize + x) * 6 + 2] =
                    tcu::Vec4(((float)(x + 1) / (float)m_gridsize) * 2.0f - 1.0f,
                              ((float)(y + 1) / (float)m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
                gridData[(y * m_gridsize + x) * 6 + 3] =
                    tcu::Vec4(((float)(x + 0) / (float)m_gridsize) * 2.0f - 1.0f,
                              ((float)(y + 0) / (float)m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
                gridData[(y * m_gridsize + x) * 6 + 4] =
                    tcu::Vec4(((float)(x + 1) / (float)m_gridsize) * 2.0f - 1.0f,
                              ((float)(y + 1) / (float)m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
                gridData[(y * m_gridsize + x) * 6 + 5] =
                    tcu::Vec4(((float)(x + 1) / (float)m_gridsize) * 2.0f - 1.0f,
                              ((float)(y + 0) / (float)m_gridsize) * 2.0f - 1.0f, 0.0f, 1.0f);
            }

        gl.bufferData(GL_ARRAY_BUFFER, (int)(gridData.size() * sizeof(tcu::Vec4)), gridData[0].getPtr(),
                      GL_STATIC_DRAW);
        GLU_EXPECT_NO_ERROR(gl.getError(), "bufferData");
    }

    // generate programs

    genSamplerProgram();
    genAlphaProgram();
}

void SampleMaskCase::deinit(void)
{
    if (m_texID)
    {
        m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texID);
        m_texID = 0;
    }
    if (m_vaoID)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
        m_vaoID = 0;
    }
    if (m_vboID)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_vboID);
        m_vboID = 0;
    }
    if (m_fboID)
    {
        m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fboID);
        m_fboID = 0;
    }

    if (m_samplerProgram)
    {
        delete m_samplerProgram;
        m_samplerProgram = DE_NULL;
    }
    if (m_alphaProgram)
    {
        delete m_alphaProgram;
        m_alphaProgram = DE_NULL;
    }
}

SampleMaskCase::IterateResult SampleMaskCase::iterate(void)
{
    const tcu::ScopedLogSection section(
        m_testCtx.getLog(), "Iteration",
        (m_currentSample == -1) ?
            ("Verifying with zero mask") :
            (std::string() + "Verifying sample " + de::toString(m_currentSample + 1) + "/" + de::toString(m_samples)));

    bool iterationOk;

    // Mask only one sample, clear rest

    updateTexture(m_currentSample);

    // Verify only one sample set is in the texture

    iterationOk = verifyTexture(m_currentSample);
    if (!iterationOk)
        m_allIterationsOk = false;

    m_currentSample++;
    if (m_currentSample < m_samples)
        return CONTINUE;

    // End result

    if (m_allIterationsOk)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else if (m_flags & FLAGS_HIGH_BITS)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Unused mask bits have effect");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Sample test failed");

    return STOP;
}

void SampleMaskCase::genSamplerProgram(void)
{
    const char *const vertexShaderSource = "${GLSL_VERSION_DECL}\n"
                                           "in highp vec4 a_position;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    gl_Position = a_position;\n"
                                           "}\n";
    const char *const fragShaderSource =
        "${GLSL_VERSION_DECL}\n"
        "layout(location = 0) out highp vec4 fragColor;\n"
        "uniform highp sampler2DMS u_sampler;\n"
        "uniform highp int u_sample;\n"
        "void main (void)\n"
        "{\n"
        "    highp float correctCoverage = 0.0;\n"
        "    highp float incorrectCoverage = 0.0;\n"
        "    highp ivec2 texelPos = ivec2(int(floor(gl_FragCoord.x)), int(floor(gl_FragCoord.y)));\n"
        "\n"
        "    for (int sampleNdx = 0; sampleNdx < ${NUMSAMPLES}; ++sampleNdx)\n"
        "    {\n"
        "        highp float sampleColor = texelFetch(u_sampler, texelPos, sampleNdx).r;\n"
        "        if (sampleNdx == u_sample)\n"
        "            correctCoverage += sampleColor;\n"
        "        else\n"
        "            incorrectCoverage += sampleColor;\n"
        "    }\n"
        "    fragColor = vec4(correctCoverage, incorrectCoverage, 0.0, 1.0);\n"
        "}\n";
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "GenerateSamplerShader", "Generate sampler shader");
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    std::map<std::string, std::string> args;
    const glu::GLSLVersion glslVersion = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());

    args["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);
    args["NUMSAMPLES"]        = de::toString(m_samples);

    m_samplerProgram = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderSource))
                              << glu::FragmentSource(tcu::StringTemplate(fragShaderSource).specialize(args)));
    m_testCtx.getLog() << *m_samplerProgram;

    if (!m_samplerProgram->isOk())
        throw tcu::TestError("Could not create sampler program.");

    m_samplerProgramPosLoc       = gl.getAttribLocation(m_samplerProgram->getProgram(), "a_position");
    m_samplerProgramSamplerLoc   = gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sampler");
    m_samplerProgramSampleNdxLoc = gl.getUniformLocation(m_samplerProgram->getProgram(), "u_sample");
}

void SampleMaskCase::genAlphaProgram(void)
{
    const char *const vertexShaderSource = "${GLSL_VERSION_DECL}\n"
                                           "in highp vec4 a_position;\n"
                                           "out highp float v_alpha;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    gl_Position = a_position;\n"
                                           "    v_alpha = (a_position.x * 0.5 + 0.5)*(a_position.y * 0.5 + 0.5);\n"
                                           "}\n";
    const char *const fragShaderSource   = "${GLSL_VERSION_DECL}\n"
                                           "layout(location = 0) out highp vec4 fragColor;\n"
                                           "in mediump float v_alpha;\n"
                                           "void main (void)\n"
                                           "{\n"
                                           "    fragColor = vec4(1.0, 1.0, 1.0, v_alpha);\n"
                                           "}\n";
    const glw::Functions &gl             = m_context.getRenderContext().getFunctions();

    m_alphaProgram = new glu::ShaderProgram(m_context.getRenderContext(),
                                            glu::ProgramSources()
                                                << glu::VertexSource(specializeShader(m_context, vertexShaderSource))
                                                << glu::FragmentSource(specializeShader(m_context, fragShaderSource)));

    if (!m_alphaProgram->isOk())
    {
        m_testCtx.getLog() << *m_alphaProgram;
        throw tcu::TestError("Could not create aplha program.");
    }

    m_alphaProgramPosLoc = gl.getAttribLocation(m_alphaProgram->getProgram(), "a_position");
}

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

    // prepare draw

    gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
    gl.viewport(0, 0, m_canvasSize, m_canvasSize);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);

    // clear all samples

    m_testCtx.getLog() << tcu::TestLog::Message << "Clearing image" << tcu::TestLog::EndMessage;
    gl.clear(GL_COLOR_BUFFER_BIT);

    // set mask state

    if (m_flags & FLAGS_HIGH_BITS)
    {
        const std::vector<uint32_t> bitmask       = genSetNthBitSampleMask(sample);
        const std::vector<uint32_t> effectiveMask = genAllSetToNthBitSampleMask(m_samples);
        std::vector<uint32_t> totalBitmask(effectiveMask.size());

        DE_ASSERT((int)totalBitmask.size() == m_effectiveSampleMaskWordCount);

        // set some arbitrary high bits to non-effective bits
        for (int wordNdx = 0; wordNdx < (int)effectiveMask.size(); ++wordNdx)
        {
            const uint32_t randomMask = (uint32_t)deUint32Hash(wordNdx << 2 ^ sample);
            const uint32_t sampleMask = (wordNdx < (int)bitmask.size()) ? (bitmask[wordNdx]) : (0);
            const uint32_t maskMask   = effectiveMask[wordNdx];

            totalBitmask[wordNdx] = (sampleMask & maskMask) | (randomMask & ~maskMask);
        }

        m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask to 0b"
                           << sampleMaskToString(totalBitmask, (int)totalBitmask.size() * 32)
                           << tcu::TestLog::EndMessage;

        gl.enable(GL_SAMPLE_MASK);
        for (int wordNdx = 0; wordNdx < m_effectiveSampleMaskWordCount; ++wordNdx)
        {
            const GLbitfield wordmask =
                (wordNdx < (int)totalBitmask.size()) ? ((GLbitfield)(totalBitmask[wordNdx])) : (0);
            gl.sampleMaski((uint32_t)wordNdx, wordmask);
        }
    }
    else
    {
        const std::vector<uint32_t> bitmask =
            sample < 0 ? std::vector<uint32_t>(m_effectiveSampleMaskWordCount, 0) : genSetNthBitSampleMask(sample);
        DE_ASSERT((int)bitmask.size() <= m_effectiveSampleMaskWordCount);

        m_testCtx.getLog() << tcu::TestLog::Message << "Setting sample mask to 0b"
                           << sampleMaskToString(bitmask, m_samples) << tcu::TestLog::EndMessage;

        gl.enable(GL_SAMPLE_MASK);
        for (int wordNdx = 0; wordNdx < m_effectiveSampleMaskWordCount; ++wordNdx)
        {
            const GLbitfield wordmask = (wordNdx < (int)bitmask.size()) ? ((GLbitfield)(bitmask[wordNdx])) : (0);
            gl.sampleMaski((uint32_t)wordNdx, wordmask);
        }
    }
    if (m_flags & FLAGS_ALPHA_TO_COVERAGE)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Enabling alpha to coverage." << tcu::TestLog::EndMessage;
        gl.enable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    }
    if (m_flags & FLAGS_SAMPLE_COVERAGE)
    {
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "Enabling sample coverage. Varying sample coverage for grid cells."
                           << tcu::TestLog::EndMessage;
        gl.enable(GL_SAMPLE_COVERAGE);
    }

    // draw test pattern

    m_testCtx.getLog() << tcu::TestLog::Message << "Drawing test grid" << tcu::TestLog::EndMessage;

    gl.bindVertexArray(m_vaoID);
    gl.bindBuffer(GL_ARRAY_BUFFER, m_vboID);
    gl.vertexAttribPointer(m_alphaProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(m_alphaProgramPosLoc);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttribPointer");

    gl.useProgram(m_alphaProgram->getProgram());

    for (int y = 0; y < m_gridsize; ++y)
        for (int x = 0; x < m_gridsize; ++x)
        {
            if (m_flags & FLAGS_SAMPLE_COVERAGE)
                gl.sampleCoverage((float)(y * m_gridsize + x) / float(m_gridsize * m_gridsize), GL_FALSE);

            gl.drawArrays(GL_TRIANGLES, (y * m_gridsize + x) * 6, 6);
            GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");
        }

    // clean state

    gl.disableVertexAttribArray(m_alphaProgramPosLoc);
    gl.useProgram(0);
    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
    gl.disable(GL_SAMPLE_MASK);
    gl.disable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    gl.disable(GL_SAMPLE_COVERAGE);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clean");
}

bool SampleMaskCase::verifyTexture(int sample)
{
    tcu::Surface result(m_canvasSize, m_canvasSize);
    tcu::Surface errorMask(m_canvasSize, m_canvasSize);
    bool error = false;

    tcu::clear(errorMask.getAccess(), tcu::RGBA::green().toVec());

    // Draw sample:
    //    Sample sampleNdx is set to red channel
    //    Other samples are set to green channel
    drawSample(result, sample);

    // Check surface contains only sampleNdx
    for (int y = 0; y < m_canvasSize; ++y)
        for (int x = 0; x < m_canvasSize; ++x)
        {
            const tcu::RGBA color = result.getPixel(x, y);

            // Allow missing coverage with FLAGS_ALPHA_TO_COVERAGE and FLAGS_SAMPLE_COVERAGE, or if there are no samples enabled
            const bool allowMissingCoverage =
                ((m_flags & (FLAGS_ALPHA_TO_COVERAGE | FLAGS_SAMPLE_COVERAGE)) != 0) || (sample == -1);

            // disabled sample was written to
            if (color.getGreen() != 0)
            {
                error = true;
                errorMask.setPixel(x, y, tcu::RGBA::red());
            }
            // enabled sample was not written to
            else if (color.getRed() != 255 && !allowMissingCoverage)
            {
                error = true;
                errorMask.setPixel(x, y, tcu::RGBA::red());
            }
        }

    if (error)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification failed, disabled samples found."
                           << tcu::TestLog::EndMessage
                           << tcu::TestLog::ImageSet("VerificationResult", "Result of rendering")
                           << tcu::TestLog::Image("Result", "Result", result)
                           << tcu::TestLog::Image("ErrorMask", "Error Mask", errorMask) << tcu::TestLog::EndImageSet;
        return false;
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Image verification ok, no disabled samples found."
                           << tcu::TestLog::EndMessage;
        return true;
    }
}

void SampleMaskCase::drawSample(tcu::Surface &dst, int sample)
{
    // Downsample using only one sample
    static const tcu::Vec4 fullscreenQuad[] = {tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
                                               tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f), tcu::Vec4(1.0f, -1.0f, 0.0f, 1.0f)};

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    glu::Buffer vertexBuffer(m_context.getRenderContext());

    gl.bindTexture(GL_TEXTURE_2D_MULTISAMPLE, m_texID);
    gl.bindVertexArray(m_vaoID);

    gl.bindBuffer(GL_ARRAY_BUFFER, *vertexBuffer);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), &fullscreenQuad[0], GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bufferData");

    gl.viewport(0, 0, m_canvasSize, m_canvasSize);
    gl.clearColor(0, 0, 0, 1);
    gl.clear(GL_COLOR_BUFFER_BIT);
    gl.vertexAttribPointer(m_samplerProgramPosLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(m_samplerProgramPosLoc);
    GLU_EXPECT_NO_ERROR(gl.getError(), "vertexAttribPointer");

    gl.useProgram(m_samplerProgram->getProgram());
    gl.uniform1i(m_samplerProgramSamplerLoc, 0);
    gl.uniform1i(m_samplerProgramSampleNdxLoc, (int32_t)sample);
    GLU_EXPECT_NO_ERROR(gl.getError(), "useprogram");

    m_testCtx.getLog() << tcu::TestLog::Message << "Reading from texture with sampler shader, u_sample = " << sample
                       << tcu::TestLog::EndMessage;

    gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "drawArrays");

    gl.disableVertexAttribArray(m_samplerProgramPosLoc);
    gl.useProgram(0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "cleanup");

    gl.finish();
    glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
    GLU_EXPECT_NO_ERROR(gl.getError(), "readPixels");
}

glw::GLint SampleMaskCase::getMaxConformantSampleCount(glw::GLenum target, glw::GLenum internalFormat)
{
    int32_t maxTextureSamples = 0;
    const glw::Functions &gl  = m_context.getRenderContext().getFunctions();
    if (m_context.getContextInfo().isExtensionSupported("GL_NV_internalformat_sample_query"))
    {
        glw::GLint gl_sample_counts = 0;
        gl.getInternalformativ(target, internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &gl_sample_counts);
        GLU_EXPECT_NO_ERROR(gl.getError(), "glGetInternalformativ() failed for GL_NUM_SAMPLE_COUNTS pname");
        /* Check and return the first conformant sample count */
        glw::GLint *gl_supported_samples = new glw::GLint[gl_sample_counts];
        if (gl_supported_samples)
        {
            gl.getInternalformativ(target, internalFormat, GL_SAMPLES, gl_sample_counts, gl_supported_samples);
            for (glw::GLint i = 0; i < gl_sample_counts; i++)
            {
                glw::GLint isConformant = 0;
                gl.getInternalformatSampleivNV(target, internalFormat, gl_supported_samples[i], GL_CONFORMANT_NV, 1,
                                               &isConformant);
                GLU_EXPECT_NO_ERROR(gl.getError(), "glGetInternalformatSampleivNV() call(s) failed");
                if (isConformant && gl_supported_samples[i] > maxTextureSamples)
                {
                    maxTextureSamples = gl_supported_samples[i];
                }
            }
            delete[] gl_supported_samples;
        }
    }
    else
    {
        gl.getInternalformativ(target, internalFormat, GL_SAMPLES, 1, &maxTextureSamples);
    }
    return maxTextureSamples;
}

class MultisampleTextureUsageCase : public TestCase
{
public:
    enum TextureType
    {
        TEXTURE_COLOR_2D = 0,
        TEXTURE_COLOR_2D_ARRAY,
        TEXTURE_INT_2D,
        TEXTURE_INT_2D_ARRAY,
        TEXTURE_UINT_2D,
        TEXTURE_UINT_2D_ARRAY,
        TEXTURE_DEPTH_2D,
        TEXTURE_DEPTH_2D_ARRAY,

        TEXTURE_LAST
    };

    MultisampleTextureUsageCase(Context &ctx, const char *name, const char *desc, int samples, TextureType type);
    ~MultisampleTextureUsageCase(void);

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

    void genDrawShader(void);
    void genSamplerShader(void);

    void renderToTexture(float value);
    void sampleTexture(tcu::Surface &dst, float value);
    bool verifyImage(const tcu::Surface &dst);

    static const int s_textureSize      = 256;
    static const int s_textureArraySize = 8;
    static const int s_textureLayer     = 3;

    const TextureType m_type;
    const int m_numSamples;

    glw::GLuint m_fboID;
    glw::GLuint m_vaoID;
    glw::GLuint m_textureID;

    glu::ShaderProgram *m_drawShader;
    glu::ShaderProgram *m_samplerShader;

    const bool m_isColorFormat;
    const bool m_isSignedFormat;
    const bool m_isUnsignedFormat;
    const bool m_isDepthFormat;
    const bool m_isArrayType;
};

MultisampleTextureUsageCase::MultisampleTextureUsageCase(Context &ctx, const char *name, const char *desc, int samples,
                                                         TextureType type)
    : TestCase(ctx, name, desc)
    , m_type(type)
    , m_numSamples(samples)
    , m_fboID(0)
    , m_vaoID(0)
    , m_textureID(0)
    , m_drawShader(DE_NULL)
    , m_samplerShader(DE_NULL)
    , m_isColorFormat(m_type == TEXTURE_COLOR_2D || m_type == TEXTURE_COLOR_2D_ARRAY)
    , m_isSignedFormat(m_type == TEXTURE_INT_2D || m_type == TEXTURE_INT_2D_ARRAY)
    , m_isUnsignedFormat(m_type == TEXTURE_UINT_2D || m_type == TEXTURE_UINT_2D_ARRAY)
    , m_isDepthFormat(m_type == TEXTURE_DEPTH_2D || m_type == TEXTURE_DEPTH_2D_ARRAY)
    , m_isArrayType(m_type == TEXTURE_COLOR_2D_ARRAY || m_type == TEXTURE_INT_2D_ARRAY ||
                    m_type == TEXTURE_UINT_2D_ARRAY || m_type == TEXTURE_DEPTH_2D_ARRAY)
{
    DE_ASSERT(m_type < TEXTURE_LAST);
}

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

void MultisampleTextureUsageCase::init(void)
{
    const glw::Functions &gl         = m_context.getRenderContext().getFunctions();
    const glw::GLenum internalFormat = (m_isColorFormat)    ? (GL_R8) :
                                       (m_isSignedFormat)   ? (GL_R8I) :
                                       (m_isUnsignedFormat) ? (GL_R8UI) :
                                       (m_isDepthFormat)    ? (GL_DEPTH_COMPONENT32F) :
                                                              (0);
    const glw::GLenum textureTarget = (m_isArrayType) ? (GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : (GL_TEXTURE_2D_MULTISAMPLE);
    const glw::GLenum fboAttachment = (m_isDepthFormat) ? (GL_DEPTH_ATTACHMENT) : (GL_COLOR_ATTACHMENT0);
    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));

    DE_ASSERT(internalFormat);

    // requirements

    if (m_isArrayType && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
        throw tcu::NotSupportedError("Test requires OES_texture_storage_multisample_2d_array extension");
    if (m_context.getRenderTarget().getWidth() < s_textureSize ||
        m_context.getRenderTarget().getHeight() < s_textureSize)
        throw tcu::NotSupportedError("render target size must be at least " +
                                     de::toString(static_cast<int>(s_textureSize)) + "x" +
                                     de::toString(static_cast<int>(s_textureSize)));

    {
        glw::GLint maxSamples = 0;
        gl.getInternalformativ(textureTarget, internalFormat, GL_SAMPLES, 1, &maxSamples);

        if (m_numSamples > maxSamples)
            throw tcu::NotSupportedError("Requested sample count is greater than supported");

        m_testCtx.getLog() << tcu::TestLog::Message << "Max sample count for "
                           << glu::getTextureFormatStr(internalFormat) << ": " << maxSamples
                           << tcu::TestLog::EndMessage;
    }

    {
        GLint maxTextureSize = 0;
        gl.getIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);

        if (s_textureSize > maxTextureSize)
            throw tcu::NotSupportedError("Larger GL_MAX_TEXTURE_SIZE is required");
    }

    if (m_isArrayType)
    {
        GLint maxTextureLayers = 0;
        gl.getIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, &maxTextureLayers);

        if (s_textureArraySize > maxTextureLayers)
            throw tcu::NotSupportedError("Larger GL_MAX_ARRAY_TEXTURE_LAYERS is required");
    }

    // create texture

    m_testCtx.getLog() << tcu::TestLog::Message << "Creating multisample " << ((m_isDepthFormat) ? ("depth") : (""))
                       << " texture" << ((m_isArrayType) ? (" array") : ("")) << tcu::TestLog::EndMessage;

    gl.genTextures(1, &m_textureID);
    gl.bindTexture(textureTarget, m_textureID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bind texture");

    if (m_isArrayType)
        gl.texStorage3DMultisample(GL_TEXTURE_2D_MULTISAMPLE_ARRAY, m_numSamples, internalFormat, s_textureSize,
                                   s_textureSize, s_textureArraySize, GL_FALSE);
    else
        gl.texStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples, internalFormat, s_textureSize,
                                   s_textureSize, GL_FALSE);
    GLU_EXPECT_NO_ERROR(gl.getError(), "texstorage");

    // create fbo for drawing

    gl.genFramebuffers(1, &m_fboID);
    gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);

    if (m_isArrayType)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Attaching multisample texture array layer "
                           << static_cast<int>(s_textureLayer) << " to fbo" << tcu::TestLog::EndMessage;
        gl.framebufferTextureLayer(GL_FRAMEBUFFER, fboAttachment, m_textureID, 0, s_textureLayer);
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Attaching multisample texture to fbo"
                           << tcu::TestLog::EndMessage;
        gl.framebufferTexture2D(GL_FRAMEBUFFER, fboAttachment, textureTarget, m_textureID, 0);
    }
    GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

    // create vao if context is GL4.5
    if (!glu::isContextTypeES(m_context.getRenderContext().getType()))
        gl.genVertexArrays(1, &m_vaoID);

    // create shader for rendering to fbo
    genDrawShader();

    // create shader for sampling the texture rendered to
    genSamplerShader();
}

void MultisampleTextureUsageCase::deinit(void)
{
    if (m_textureID)
    {
        m_context.getRenderContext().getFunctions().deleteTextures(1, &m_textureID);
        m_textureID = 0;
    }

    if (m_fboID)
    {
        m_context.getRenderContext().getFunctions().deleteFramebuffers(1, &m_fboID);
        m_fboID = 0;
    }

    if (m_vaoID)
    {
        m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vaoID);
        m_vaoID = 0;
    }

    if (m_drawShader)
    {
        delete m_drawShader;
        m_drawShader = DE_NULL;
    }

    if (m_samplerShader)
    {
        delete m_samplerShader;
        m_samplerShader = DE_NULL;
    }
}

MultisampleTextureUsageCase::IterateResult MultisampleTextureUsageCase::iterate(void)
{
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Sample", "Render to texture and sample texture");
    tcu::Surface result(s_textureSize, s_textureSize);
    const float minValue = (m_isColorFormat || m_isDepthFormat) ? (0.0f) :
                           (m_isSignedFormat)                   ? (-100.0f) :
                           (m_isUnsignedFormat)                 ? (0.0f) :
                                                                  (1.0f);
    const float maxValue = (m_isColorFormat || m_isDepthFormat) ? (1.0f) :
                           (m_isSignedFormat)                   ? (100.0f) :
                           (m_isUnsignedFormat)                 ? (200.0f) :
                                                                  (-1.0f);
    de::Random rnd(deUint32Hash((uint32_t)m_type));
    const float rawValue      = rnd.getFloat(minValue, maxValue);
    const float preparedValue = (m_isSignedFormat || m_isUnsignedFormat) ? (deFloatFloor(rawValue)) : (rawValue);

    // draw to fbo with a random value

    renderToTexture(preparedValue);

    // draw from texture to front buffer

    sampleTexture(result, preparedValue);

    // result is ok?

    if (verifyImage(result))
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

    return STOP;
}

void MultisampleTextureUsageCase::genDrawShader(void)
{
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "RenderShader", "Generate render-to-texture shader");

    static const char *const vertexShaderSource = "${GLSL_VERSION_DECL}\n"
                                                  "in highp vec4 a_position;\n"
                                                  "void main (void)\n"
                                                  "{\n"
                                                  "    gl_Position = a_position;\n"
                                                  "}\n";
    static const char *const fragmentShaderSourceColor =
        "${GLSL_VERSION_DECL}\n"
        "layout(location = 0) out highp ${OUTTYPE} fragColor;\n"
        "uniform highp float u_writeValue;\n"
        "void main (void)\n"
        "{\n"
        "    fragColor = ${OUTTYPE}(vec4(u_writeValue, 1.0, 1.0, 1.0));\n"
        "}\n";
    static const char *const fragmentShaderSourceDepth = "${GLSL_VERSION_DECL}\n"
                                                         "layout(location = 0) out highp vec4 fragColor;\n"
                                                         "uniform highp float u_writeValue;\n"
                                                         "void main (void)\n"
                                                         "{\n"
                                                         "    fragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
                                                         "    gl_FragDepth = u_writeValue;\n"
                                                         "}\n";
    const char *const fragmentSource = (m_isDepthFormat) ? (fragmentShaderSourceDepth) : (fragmentShaderSourceColor);

    std::map<std::string, std::string> fragmentArguments;

    const glu::GLSLVersion glslVersion     = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
    fragmentArguments["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

    if (m_isColorFormat || m_isDepthFormat)
        fragmentArguments["OUTTYPE"] = "vec4";
    else if (m_isSignedFormat)
        fragmentArguments["OUTTYPE"] = "ivec4";
    else if (m_isUnsignedFormat)
        fragmentArguments["OUTTYPE"] = "uvec4";
    else
        DE_ASSERT(false);

    m_drawShader = new glu::ShaderProgram(
        m_context.getRenderContext(), glu::ProgramSources()
                                          << glu::VertexSource(specializeShader(m_context, vertexShaderSource))
                                          << glu::FragmentSource(
                                                 tcu::StringTemplate(fragmentSource).specialize(fragmentArguments)));
    m_testCtx.getLog() << *m_drawShader;

    if (!m_drawShader->isOk())
        throw tcu::TestError("could not build shader");
}

void MultisampleTextureUsageCase::genSamplerShader(void)
{
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "SamplerShader", "Generate texture sampler shader");

    static const char *const vertexShaderSource = "${GLSL_VERSION_DECL}\n"
                                                  "in highp vec4 a_position;\n"
                                                  "out highp float v_gradient;\n"
                                                  "void main (void)\n"
                                                  "{\n"
                                                  "    gl_Position = a_position;\n"
                                                  "    v_gradient = a_position.x * 0.5 + 0.5;\n"
                                                  "}\n";
    static const char *const fragmentShaderSource =
        "${GLSL_VERSION_DECL}\n"
        "${EXTENSION_STATEMENT}"
        "layout(location = 0) out highp vec4 fragColor;\n"
        "uniform highp ${SAMPLERTYPE} u_sampler;\n"
        "uniform highp int u_maxSamples;\n"
        "uniform highp int u_layer;\n"
        "uniform highp float u_cmpValue;\n"
        "in highp float v_gradient;\n"
        "void main (void)\n"
        "{\n"
        "    const highp vec4 okColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "    const highp vec4 failColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "    const highp float epsilon = ${EPSILON};\n"
        "\n"
        "    highp int sampleNdx = clamp(int(floor(v_gradient * float(u_maxSamples))), 0, u_maxSamples-1);\n"
        "    highp float value = float(texelFetch(u_sampler, ${FETCHPOS}, sampleNdx).r);\n"
        "    fragColor = (abs(u_cmpValue - value) < epsilon) ? (okColor) : (failColor);\n"
        "}\n";

    std::map<std::string, std::string> fragmentArguments;

    const glu::GLSLVersion glslVersion     = glu::getContextTypeGLSLVersion(m_context.getRenderContext().getType());
    fragmentArguments["GLSL_VERSION_DECL"] = glu::getGLSLVersionDeclaration(glslVersion);

    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 (m_isArrayType)
        fragmentArguments["FETCHPOS"] = "ivec3(floor(gl_FragCoord.xy), u_layer)";
    else
        fragmentArguments["FETCHPOS"] = "ivec2(floor(gl_FragCoord.xy))";

    if (m_isColorFormat || m_isDepthFormat)
        fragmentArguments["EPSILON"] = "0.1";
    else
        fragmentArguments["EPSILON"] = "1.0";

    if (m_isArrayType && !supportsES32orGL45)
        fragmentArguments["EXTENSION_STATEMENT"] = "#extension GL_OES_texture_storage_multisample_2d_array : require\n";
    else
        fragmentArguments["EXTENSION_STATEMENT"] = "";

    switch (m_type)
    {
    case TEXTURE_COLOR_2D:
        fragmentArguments["SAMPLERTYPE"] = "sampler2DMS";
        break;
    case TEXTURE_COLOR_2D_ARRAY:
        fragmentArguments["SAMPLERTYPE"] = "sampler2DMSArray";
        break;
    case TEXTURE_INT_2D:
        fragmentArguments["SAMPLERTYPE"] = "isampler2DMS";
        break;
    case TEXTURE_INT_2D_ARRAY:
        fragmentArguments["SAMPLERTYPE"] = "isampler2DMSArray";
        break;
    case TEXTURE_UINT_2D:
        fragmentArguments["SAMPLERTYPE"] = "usampler2DMS";
        break;
    case TEXTURE_UINT_2D_ARRAY:
        fragmentArguments["SAMPLERTYPE"] = "usampler2DMSArray";
        break;
    case TEXTURE_DEPTH_2D:
        fragmentArguments["SAMPLERTYPE"] = "sampler2DMS";
        break;
    case TEXTURE_DEPTH_2D_ARRAY:
        fragmentArguments["SAMPLERTYPE"] = "sampler2DMSArray";
        break;

    default:
        DE_ASSERT(false);
    }

    m_samplerShader = new glu::ShaderProgram(
        m_context.getRenderContext(),
        glu::ProgramSources() << glu::VertexSource(specializeShader(m_context, vertexShaderSource))
                              << glu::FragmentSource(
                                     tcu::StringTemplate(fragmentShaderSource).specialize(fragmentArguments)));
    m_testCtx.getLog() << *m_samplerShader;

    if (!m_samplerShader->isOk())
        throw tcu::TestError("could not build shader");
}

void MultisampleTextureUsageCase::renderToTexture(float value)
{
    static const tcu::Vec4 fullscreenQuad[] = {
        tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
        tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f),
        tcu::Vec4(1.0f, -1.0f, 0.0f, 1.0f),
        tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
    };

    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const int posLocation    = gl.getAttribLocation(m_drawShader->getProgram(), "a_position");
    const int valueLocation  = gl.getUniformLocation(m_drawShader->getProgram(), "u_writeValue");
    glu::Buffer vertexAttibBuffer(m_context.getRenderContext());

    m_testCtx.getLog() << tcu::TestLog::Message << "Filling multisample texture with value " << value
                       << tcu::TestLog::EndMessage;

    // upload data

    gl.bindBuffer(GL_ARRAY_BUFFER, *vertexAttibBuffer);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad[0].getPtr(), GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bufferdata");

    // clear buffer

    gl.bindFramebuffer(GL_FRAMEBUFFER, m_fboID);
    gl.viewport(0, 0, s_textureSize, s_textureSize);

    if (m_isColorFormat)
    {
        const float clearColor[4] = {0.0f, 0.0f, 0.0f, 0.0f};
        gl.clearBufferfv(GL_COLOR, 0, clearColor);
    }
    else if (m_isSignedFormat)
    {
        const int32_t clearColor[4] = {0, 0, 0, 0};
        gl.clearBufferiv(GL_COLOR, 0, clearColor);
    }
    else if (m_isUnsignedFormat)
    {
        const uint32_t clearColor[4] = {0, 0, 0, 0};
        gl.clearBufferuiv(GL_COLOR, 0, clearColor);
    }
    else if (m_isDepthFormat)
    {
        const float clearDepth = 0.5f;
        gl.clearBufferfv(GL_DEPTH, 0, &clearDepth);
    }

    GLU_EXPECT_NO_ERROR(gl.getError(), "clear buffer");

    // setup shader and draw
    if (m_vaoID)
        gl.bindVertexArray(m_vaoID);

    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(posLocation);

    gl.useProgram(m_drawShader->getProgram());
    gl.uniform1f(valueLocation, value);

    GLU_EXPECT_NO_ERROR(gl.getError(), "setup draw");

    if (m_isDepthFormat)
    {
        gl.enable(GL_DEPTH_TEST);
        gl.depthFunc(GL_ALWAYS);
    }

    gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

    // clean state

    if (m_isDepthFormat)
        gl.disable(GL_DEPTH_TEST);

    gl.disableVertexAttribArray(posLocation);
    gl.useProgram(0);
    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clean");
}

void MultisampleTextureUsageCase::sampleTexture(tcu::Surface &dst, float value)
{
    static const tcu::Vec4 fullscreenQuad[] = {
        tcu::Vec4(-1.0f, -1.0f, 0.0f, 1.0f),
        tcu::Vec4(-1.0f, 1.0f, 0.0f, 1.0f),
        tcu::Vec4(1.0f, -1.0f, 0.0f, 1.0f),
        tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f),
    };

    const glw::Functions &gl        = m_context.getRenderContext().getFunctions();
    const int posLocation           = gl.getAttribLocation(m_samplerShader->getProgram(), "a_position");
    const int samplerLocation       = gl.getUniformLocation(m_samplerShader->getProgram(), "u_sampler");
    const int maxSamplesLocation    = gl.getUniformLocation(m_samplerShader->getProgram(), "u_maxSamples");
    const int layerLocation         = gl.getUniformLocation(m_samplerShader->getProgram(), "u_layer");
    const int valueLocation         = gl.getUniformLocation(m_samplerShader->getProgram(), "u_cmpValue");
    const glw::GLenum textureTarget = (m_isArrayType) ? (GL_TEXTURE_2D_MULTISAMPLE_ARRAY) : (GL_TEXTURE_2D_MULTISAMPLE);
    glu::Buffer vertexAttibBuffer(m_context.getRenderContext());

    m_testCtx.getLog() << tcu::TestLog::Message << "Sampling from texture." << tcu::TestLog::EndMessage;

    // upload data

    gl.bindBuffer(GL_ARRAY_BUFFER, *vertexAttibBuffer);
    gl.bufferData(GL_ARRAY_BUFFER, sizeof(fullscreenQuad), fullscreenQuad[0].getPtr(), GL_STATIC_DRAW);
    GLU_EXPECT_NO_ERROR(gl.getError(), "bufferdata");

    // clear

    gl.viewport(0, 0, s_textureSize, s_textureSize);
    gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
    gl.clear(GL_COLOR_BUFFER_BIT);

    // setup shader and draw

    gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
    gl.enableVertexAttribArray(posLocation);

    gl.useProgram(m_samplerShader->getProgram());
    gl.uniform1i(samplerLocation, 0);
    gl.uniform1i(maxSamplesLocation, m_numSamples);
    if (m_isArrayType)
        gl.uniform1i(layerLocation, s_textureLayer);
    gl.uniform1f(valueLocation, value);
    gl.bindTexture(textureTarget, m_textureID);
    GLU_EXPECT_NO_ERROR(gl.getError(), "setup draw");

    gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
    GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

    // clean state

    gl.disableVertexAttribArray(posLocation);
    gl.useProgram(0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "clean");

    // read results
    gl.finish();
    glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

bool MultisampleTextureUsageCase::verifyImage(const tcu::Surface &dst)
{
    bool error = false;

    m_testCtx.getLog() << tcu::TestLog::Message << "Verifying image." << tcu::TestLog::EndMessage;

    for (int y = 0; y < dst.getHeight(); ++y)
        for (int x = 0; x < dst.getWidth(); ++x)
        {
            const tcu::RGBA color         = dst.getPixel(x, y);
            const int colorThresholdRed   = 1 << (8 - m_context.getRenderTarget().getPixelFormat().redBits);
            const int colorThresholdGreen = 1 << (8 - m_context.getRenderTarget().getPixelFormat().greenBits);
            const int colorThresholdBlue  = 1 << (8 - m_context.getRenderTarget().getPixelFormat().blueBits);

            // only green is accepted
            if (color.getRed() > colorThresholdRed || color.getGreen() < 255 - colorThresholdGreen ||
                color.getBlue() > colorThresholdBlue)
                error = true;
        }

    if (error)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Invalid pixels found." << tcu::TestLog::EndMessage;
        m_testCtx.getLog() << tcu::TestLog::ImageSet("Verification result", "Result of rendering")
                           << tcu::TestLog::Image("Result", "Result", dst) << tcu::TestLog::EndImageSet;

        return false;
    }
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "No invalid pixels found." << tcu::TestLog::EndMessage;
        return true;
    }
}

class NegativeFramebufferCase : public TestCase
{
public:
    enum CaseType
    {
        CASE_DIFFERENT_N_SAMPLES_TEX = 0,
        CASE_DIFFERENT_N_SAMPLES_RBO,
        CASE_DIFFERENT_FIXED_TEX,
        CASE_DIFFERENT_FIXED_RBO,
        CASE_NON_ZERO_LEVEL,

        CASE_LAST
    };

    NegativeFramebufferCase(Context &context, const char *name, const char *desc, CaseType caseType);
    ~NegativeFramebufferCase(void);

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

    void getFormatSamples(glw::GLenum target, std::vector<int> &samples);

    const CaseType m_caseType;
    const int m_fboSize;
    const glw::GLenum m_internalFormat;

    int m_numSamples0; // !< samples for attachment 0
    int m_numSamples1; // !< samples for attachment 1
};

NegativeFramebufferCase::NegativeFramebufferCase(Context &context, const char *name, const char *desc,
                                                 CaseType caseType)
    : TestCase(context, name, desc)
    , m_caseType(caseType)
    , m_fboSize(64)
    , m_internalFormat(GL_RGBA8)
    , m_numSamples0(-1)
    , m_numSamples1(-1)
{
}

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

void NegativeFramebufferCase::init(void)
{
    const bool colorAttachmentTexture =
        (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_TEX);
    const bool colorAttachmentRbo =
        (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) || (m_caseType == CASE_DIFFERENT_FIXED_RBO);
    const bool useDifferentSampleCounts =
        (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO);
    std::vector<int> textureSamples;
    std::vector<int> rboSamples;

    getFormatSamples(GL_TEXTURE_2D_MULTISAMPLE, textureSamples);
    getFormatSamples(GL_RENDERBUFFER, rboSamples);

    TCU_CHECK(!textureSamples.empty());
    TCU_CHECK(!rboSamples.empty());

    // select sample counts

    if (useDifferentSampleCounts)
    {
        if (colorAttachmentTexture)
        {
            m_numSamples0 = textureSamples[0];

            if (textureSamples.size() >= 2)
                m_numSamples1 = textureSamples[1];
            else
                throw tcu::NotSupportedError("Test requires multiple supported sample counts");
        }
        else if (colorAttachmentRbo)
        {
            for (int texNdx = 0; texNdx < (int)textureSamples.size(); ++texNdx)
                for (int rboNdx = 0; rboNdx < (int)rboSamples.size(); ++rboNdx)
                {
                    if (textureSamples[texNdx] != rboSamples[rboNdx])
                    {
                        m_numSamples0 = textureSamples[texNdx];
                        m_numSamples1 = rboSamples[rboNdx];
                        return;
                    }
                }

            throw tcu::NotSupportedError("Test requires multiple supported sample counts");
        }
        else
            DE_ASSERT(false);
    }
    else
    {
        if (colorAttachmentTexture)
        {
            m_numSamples0 = textureSamples[0];
            m_numSamples1 = textureSamples[0];
        }
        else if (colorAttachmentRbo)
        {
            for (int texNdx = 0; texNdx < (int)textureSamples.size(); ++texNdx)
                for (int rboNdx = 0; rboNdx < (int)rboSamples.size(); ++rboNdx)
                {
                    if (textureSamples[texNdx] == rboSamples[rboNdx])
                    {
                        m_numSamples0 = textureSamples[texNdx];
                        m_numSamples1 = rboSamples[rboNdx];
                        return;
                    }
                }

            throw tcu::NotSupportedError("Test requires a sample count supported in both rbo and texture");
        }
        else
        {
            m_numSamples0 = textureSamples[0];
        }
    }
}

void NegativeFramebufferCase::deinit(void)
{
}

NegativeFramebufferCase::IterateResult NegativeFramebufferCase::iterate(void)
{
    const bool colorAttachmentTexture =
        (m_caseType == CASE_DIFFERENT_N_SAMPLES_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_TEX);
    const bool colorAttachmentRbo =
        (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) || (m_caseType == CASE_DIFFERENT_FIXED_RBO);
    const glw::GLboolean fixedSampleLocations0 = (m_caseType == CASE_DIFFERENT_N_SAMPLES_RBO) ? (GL_TRUE) : (GL_FALSE);
    const glw::GLboolean fixedSampleLocations1 =
        ((m_caseType == CASE_DIFFERENT_FIXED_TEX) || (m_caseType == CASE_DIFFERENT_FIXED_RBO)) ? (GL_TRUE) : (GL_FALSE);
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    glw::GLuint fboId  = 0;
    glw::GLuint rboId  = 0;
    glw::GLuint tex0Id = 0;
    glw::GLuint tex1Id = 0;

    bool testFailed = false;

    gl.enableLogging(true);

    try
    {
        gl.glGenFramebuffers(1, &fboId);
        gl.glBindFramebuffer(GL_FRAMEBUFFER, fboId);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen fbo");

        gl.glGenTextures(1, &tex0Id);
        gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex0Id);
        gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples0, m_internalFormat, m_fboSize, m_fboSize,
                                     fixedSampleLocations0);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen texture 0");

        int textureSamples;
        gl.glGetTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &textureSamples);

        if (m_caseType == CASE_NON_ZERO_LEVEL)
        {
            glw::GLenum error;

            // attaching non-zero level generates invalid value
            gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex0Id, 5);
            error = gl.glGetError();

            if (error != GL_INVALID_VALUE)
            {
                m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_VALUE, got "
                                   << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
                testFailed = true;
            }
        }
        else
        {
            gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex0Id, 0);
            GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c0");

            int fbSamples = 0;

            if (colorAttachmentTexture)
            {
                gl.glGenTextures(1, &tex1Id);
                gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex1Id);
                gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, m_numSamples1, m_internalFormat, m_fboSize,
                                             m_fboSize, fixedSampleLocations1);
                GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen texture 1");
                gl.glGetTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &fbSamples);

                gl.glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D_MULTISAMPLE, tex1Id, 0);
                GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c1");
            }
            else if (colorAttachmentRbo)
            {
                gl.glGenRenderbuffers(1, &rboId);
                gl.glBindRenderbuffer(GL_RENDERBUFFER, rboId);
                gl.glRenderbufferStorageMultisample(GL_RENDERBUFFER, m_numSamples1, m_internalFormat, m_fboSize,
                                                    m_fboSize);
                GLU_EXPECT_NO_ERROR(gl.glGetError(), "gen rb");
                gl.glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &fbSamples);

                gl.glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_RENDERBUFFER, rboId);
                GLU_EXPECT_NO_ERROR(gl.glGetError(), "attach to c1");
            }
            else
                DE_ASSERT(false);

            {
                glw::GLenum status = gl.glCheckFramebufferStatus(GL_FRAMEBUFFER);

                if ((textureSamples != fbSamples) || (fixedSampleLocations0 != fixedSampleLocations1))
                {
                    if (status != GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE) // should not be complete
                    {
                        m_testCtx.getLog()
                            << tcu::TestLog::Message << "ERROR! Expected GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, got "
                            << glu::getFramebufferStatusName(status) << tcu::TestLog::EndMessage;
                        testFailed = true;
                    }
                }
                else
                {
                    if (status != GL_FRAMEBUFFER_COMPLETE) // should be complete
                    {
                        m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_FRAMEBUFFER_COMPLETE, got "
                                           << glu::getFramebufferStatusName(status) << tcu::TestLog::EndMessage;
                        testFailed = true;
                    }
                }
            }
        }
    }
    catch (...)
    {
        gl.glDeleteFramebuffers(1, &fboId);
        gl.glDeleteRenderbuffers(1, &rboId);
        gl.glDeleteTextures(1, &tex0Id);
        gl.glDeleteTextures(1, &tex1Id);
        throw;
    }

    gl.glDeleteFramebuffers(1, &fboId);
    gl.glDeleteRenderbuffers(1, &rboId);
    gl.glDeleteTextures(1, &tex0Id);
    gl.glDeleteTextures(1, &tex1Id);

    if (testFailed)
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
    else
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    return STOP;
}

void NegativeFramebufferCase::getFormatSamples(glw::GLenum target, std::vector<int> &samples)
{
    const glw::Functions gl = m_context.getRenderContext().getFunctions();
    int sampleCount         = 0;

    gl.getInternalformativ(target, m_internalFormat, GL_NUM_SAMPLE_COUNTS, 1, &sampleCount);
    samples.resize(sampleCount);

    if (sampleCount > 0)
    {
        gl.getInternalformativ(target, m_internalFormat, GL_SAMPLES, sampleCount, &samples[0]);
        GLU_EXPECT_NO_ERROR(gl.getError(), "get max samples");
    }
}

class NegativeTexParameterCase : public TestCase
{
public:
    enum TexParam
    {
        TEXTURE_MIN_FILTER = 0,
        TEXTURE_MAG_FILTER,
        TEXTURE_WRAP_S,
        TEXTURE_WRAP_T,
        TEXTURE_WRAP_R,
        TEXTURE_MIN_LOD,
        TEXTURE_MAX_LOD,
        TEXTURE_COMPARE_MODE,
        TEXTURE_COMPARE_FUNC,
        TEXTURE_BASE_LEVEL,

        TEXTURE_LAST
    };

    NegativeTexParameterCase(Context &context, const char *name, const char *desc, TexParam param);
    ~NegativeTexParameterCase(void);

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

    glw::GLenum getParamGLEnum(void) const;
    glw::GLint getParamValue(void) const;
    glw::GLenum getExpectedError(void) const;

    const TexParam m_texParam;
    int m_iteration;
};

NegativeTexParameterCase::NegativeTexParameterCase(Context &context, const char *name, const char *desc, TexParam param)
    : TestCase(context, name, desc)
    , m_texParam(param)
    , m_iteration(0)
{
    DE_ASSERT(param < TEXTURE_LAST);
}

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

void NegativeTexParameterCase::init(void)
{
    // default value
    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}

void NegativeTexParameterCase::deinit(void)
{
}

NegativeTexParameterCase::IterateResult NegativeTexParameterCase::iterate(void)
{
    static const struct TextureType
    {
        const char *name;
        glw::GLenum target;
        glw::GLenum internalFormat;
        bool isArrayType;
    } types[] = {
        {"color", GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, false},
        {"color array", GL_TEXTURE_2D_MULTISAMPLE_ARRAY, GL_RGBA8, true},
        {"signed integer", GL_TEXTURE_2D_MULTISAMPLE, GL_R8I, false},
        {"signed integer array", GL_TEXTURE_2D_MULTISAMPLE_ARRAY, GL_R8I, true},
        {"unsigned integer", GL_TEXTURE_2D_MULTISAMPLE, GL_R8UI, false},
        {"unsigned integer array", GL_TEXTURE_2D_MULTISAMPLE_ARRAY, GL_R8UI, true},
    };

    const tcu::ScopedLogSection scope(m_testCtx.getLog(), "Iteration",
                                      std::string() + "Testing parameter with " + types[m_iteration].name + " texture");
    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 (types[m_iteration].isArrayType && !supportsES32orGL45 &&
        !m_context.getContextInfo().isExtensionSupported("GL_OES_texture_storage_multisample_2d_array"))
        m_testCtx.getLog() << tcu::TestLog::Message
                           << "GL_OES_texture_storage_multisample_2d_array not supported, skipping target"
                           << tcu::TestLog::EndMessage;
    else
    {
        glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
        glu::Texture texture(m_context.getRenderContext());
        glw::GLenum error;

        gl.enableLogging(true);

        // gen texture

        gl.glBindTexture(types[m_iteration].target, *texture);

        if (types[m_iteration].isArrayType)
            gl.glTexStorage3DMultisample(types[m_iteration].target, 1, types[m_iteration].internalFormat, 16, 16, 16,
                                         GL_FALSE);
        else
            gl.glTexStorage2DMultisample(types[m_iteration].target, 1, types[m_iteration].internalFormat, 16, 16,
                                         GL_FALSE);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup texture");

        // set param

        gl.glTexParameteri(types[m_iteration].target, getParamGLEnum(), getParamValue());
        error = gl.glGetError();

        // expect failure

        if (error != getExpectedError())
        {
            m_testCtx.getLog() << tcu::TestLog::Message << "Got unexpected error: " << glu::getErrorStr(error)
                               << ", expected: " << glu::getErrorStr(getExpectedError()) << tcu::TestLog::EndMessage;
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
        }
    }

    if (++m_iteration < DE_LENGTH_OF_ARRAY(types))
        return CONTINUE;
    return STOP;
}

glw::GLenum NegativeTexParameterCase::getParamGLEnum(void) const
{
    switch (m_texParam)
    {
    case TEXTURE_MIN_FILTER:
        return GL_TEXTURE_MIN_FILTER;
    case TEXTURE_MAG_FILTER:
        return GL_TEXTURE_MAG_FILTER;
    case TEXTURE_WRAP_S:
        return GL_TEXTURE_WRAP_S;
    case TEXTURE_WRAP_T:
        return GL_TEXTURE_WRAP_T;
    case TEXTURE_WRAP_R:
        return GL_TEXTURE_WRAP_R;
    case TEXTURE_MIN_LOD:
        return GL_TEXTURE_MIN_LOD;
    case TEXTURE_MAX_LOD:
        return GL_TEXTURE_MAX_LOD;
    case TEXTURE_COMPARE_MODE:
        return GL_TEXTURE_COMPARE_MODE;
    case TEXTURE_COMPARE_FUNC:
        return GL_TEXTURE_COMPARE_FUNC;
    case TEXTURE_BASE_LEVEL:
        return GL_TEXTURE_BASE_LEVEL;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

glw::GLint NegativeTexParameterCase::getParamValue(void) const
{
    switch (m_texParam)
    {
    case TEXTURE_MIN_FILTER:
        return GL_LINEAR;
    case TEXTURE_MAG_FILTER:
        return GL_LINEAR;
    case TEXTURE_WRAP_S:
        return GL_CLAMP_TO_EDGE;
    case TEXTURE_WRAP_T:
        return GL_CLAMP_TO_EDGE;
    case TEXTURE_WRAP_R:
        return GL_CLAMP_TO_EDGE;
    case TEXTURE_MIN_LOD:
        return 1;
    case TEXTURE_MAX_LOD:
        return 5;
    case TEXTURE_COMPARE_MODE:
        return GL_NONE;
    case TEXTURE_COMPARE_FUNC:
        return GL_NOTEQUAL;
    case TEXTURE_BASE_LEVEL:
        return 2;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

glw::GLenum NegativeTexParameterCase::getExpectedError(void) const
{
    switch (m_texParam)
    {
    case TEXTURE_MIN_FILTER:
        return GL_INVALID_ENUM;
    case TEXTURE_MAG_FILTER:
        return GL_INVALID_ENUM;
    case TEXTURE_WRAP_S:
        return GL_INVALID_ENUM;
    case TEXTURE_WRAP_T:
        return GL_INVALID_ENUM;
    case TEXTURE_WRAP_R:
        return GL_INVALID_ENUM;
    case TEXTURE_MIN_LOD:
        return GL_INVALID_ENUM;
    case TEXTURE_MAX_LOD:
        return GL_INVALID_ENUM;
    case TEXTURE_COMPARE_MODE:
        return GL_INVALID_ENUM;
    case TEXTURE_COMPARE_FUNC:
        return GL_INVALID_ENUM;
    case TEXTURE_BASE_LEVEL:
        return GL_INVALID_OPERATION;
    default:
        DE_ASSERT(false);
        return 0;
    }
}

class NegativeTexureSampleCase : public TestCase
{
public:
    enum SampleCountParam
    {
        SAMPLECOUNT_HIGH = 0,
        SAMPLECOUNT_ZERO,

        SAMPLECOUNT_LAST
    };

    NegativeTexureSampleCase(Context &context, const char *name, const char *desc, SampleCountParam param);

private:
    IterateResult iterate(void);

    const SampleCountParam m_sampleParam;
};

NegativeTexureSampleCase::NegativeTexureSampleCase(Context &context, const char *name, const char *desc,
                                                   SampleCountParam param)
    : TestCase(context, name, desc)
    , m_sampleParam(param)
{
    DE_ASSERT(param < SAMPLECOUNT_LAST);
}

NegativeTexureSampleCase::IterateResult NegativeTexureSampleCase::iterate(void)
{
    const glw::GLenum expectedError = (m_sampleParam == SAMPLECOUNT_HIGH) ? (GL_INVALID_OPERATION) : (GL_INVALID_VALUE);
    glu::CallLogWrapper gl(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
    glu::Texture texture(m_context.getRenderContext());
    glw::GLenum error;
    int samples = -1;

    gl.enableLogging(true);

    // calc samples

    if (m_sampleParam == SAMPLECOUNT_HIGH)
    {
        int maxSamples = 0;

        gl.glGetInternalformativ(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8, GL_SAMPLES, 1, &maxSamples);
        GLU_EXPECT_NO_ERROR(gl.glGetError(), "glGetInternalformativ");

        samples = maxSamples + 1;
    }
    else if (m_sampleParam == SAMPLECOUNT_ZERO)
        samples = 0;
    else
        DE_ASSERT(false);

    // create texture with bad values

    gl.glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, *texture);
    gl.glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGBA8, 64, 64, GL_FALSE);
    error = gl.glGetError();

    // expect failure

    if (error == expectedError)
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
    else
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Got unexpected error: " << glu::getErrorStr(error)
                           << ", expected: " << glu::getErrorStr(expectedError) << tcu::TestLog::EndMessage;
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Got wrong error code");
    }

    return STOP;
}

} // namespace

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

TextureMultisampleTests::~TextureMultisampleTests(void)
{
}

void TextureMultisampleTests::init(void)
{
    static const int sampleCounts[] = {1, 2, 3, 4, 8, 10, 12, 13, 16, 64};

    static const struct TextureType
    {
        const char *name;
        MultisampleTextureUsageCase::TextureType type;
    } textureTypes[] = {
        {"texture_color_2d", MultisampleTextureUsageCase::TEXTURE_COLOR_2D},
        {"texture_color_2d_array", MultisampleTextureUsageCase::TEXTURE_COLOR_2D_ARRAY},
        {"texture_int_2d", MultisampleTextureUsageCase::TEXTURE_INT_2D},
        {"texture_int_2d_array", MultisampleTextureUsageCase::TEXTURE_INT_2D_ARRAY},
        {"texture_uint_2d", MultisampleTextureUsageCase::TEXTURE_UINT_2D},
        {"texture_uint_2d_array", MultisampleTextureUsageCase::TEXTURE_UINT_2D_ARRAY},
        {"texture_depth_2d", MultisampleTextureUsageCase::TEXTURE_DEPTH_2D},
        {"texture_depth_2d_array", MultisampleTextureUsageCase::TEXTURE_DEPTH_2D_ARRAY},
    };

    // .samples_x
    for (int sampleNdx = 0; sampleNdx < DE_LENGTH_OF_ARRAY(sampleCounts); ++sampleNdx)
    {
        tcu::TestCaseGroup *const sampleGroup =
            new tcu::TestCaseGroup(m_testCtx, (std::string("samples_") + de::toString(sampleCounts[sampleNdx])).c_str(),
                                   "Test with N samples");
        addChild(sampleGroup);

        // position query works
        sampleGroup->addChild(new SamplePosRasterizationTest(m_context, "sample_position", "test SAMPLE_POSITION",
                                                             sampleCounts[sampleNdx]));

        // sample mask is ANDed properly
        sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_only", "Test with SampleMask only",
                                                 sampleCounts[sampleNdx], SampleMaskCase::FLAGS_NONE));
        sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_alpha_to_coverage",
                                                 "Test with SampleMask and alpha to coverage", sampleCounts[sampleNdx],
                                                 SampleMaskCase::FLAGS_ALPHA_TO_COVERAGE));
        sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_and_sample_coverage",
                                                 "Test with SampleMask and sample coverage", sampleCounts[sampleNdx],
                                                 SampleMaskCase::FLAGS_SAMPLE_COVERAGE));
        sampleGroup->addChild(
            new SampleMaskCase(m_context, "sample_mask_and_sample_coverage_and_alpha_to_coverage",
                               "Test with SampleMask, sample coverage, and alpha to coverage", sampleCounts[sampleNdx],
                               SampleMaskCase::FLAGS_ALPHA_TO_COVERAGE | SampleMaskCase::FLAGS_SAMPLE_COVERAGE));

        // high bits cause no unexpected behavior
        sampleGroup->addChild(new SampleMaskCase(m_context, "sample_mask_non_effective_bits",
                                                 "Test with SampleMask, set higher bits than sample count",
                                                 sampleCounts[sampleNdx], SampleMaskCase::FLAGS_HIGH_BITS));

        // usage
        for (int typeNdx = 0; typeNdx < DE_LENGTH_OF_ARRAY(textureTypes); ++typeNdx)
            sampleGroup->addChild(new MultisampleTextureUsageCase(
                m_context, (std::string("use_") + textureTypes[typeNdx].name).c_str(), textureTypes[typeNdx].name,
                sampleCounts[sampleNdx], textureTypes[typeNdx].type));
    }

    // .negative
    {
        tcu::TestCaseGroup *const negativeGroup = new tcu::TestCaseGroup(m_testCtx, "negative", "Negative tests");
        addChild(negativeGroup);

        negativeGroup->addChild(new NegativeFramebufferCase(m_context, "fbo_attach_different_sample_count_tex_tex",
                                                            "Attach different sample counts",
                                                            NegativeFramebufferCase::CASE_DIFFERENT_N_SAMPLES_TEX));
        negativeGroup->addChild(new NegativeFramebufferCase(m_context, "fbo_attach_different_sample_count_tex_rbo",
                                                            "Attach different sample counts",
                                                            NegativeFramebufferCase::CASE_DIFFERENT_N_SAMPLES_RBO));
        negativeGroup->addChild(new NegativeFramebufferCase(m_context, "fbo_attach_different_fixed_state_tex_tex",
                                                            "Attach fixed and non fixed",
                                                            NegativeFramebufferCase::CASE_DIFFERENT_FIXED_TEX));
        negativeGroup->addChild(new NegativeFramebufferCase(m_context, "fbo_attach_different_fixed_state_tex_rbo",
                                                            "Attach fixed and non fixed",
                                                            NegativeFramebufferCase::CASE_DIFFERENT_FIXED_RBO));
        negativeGroup->addChild(new NegativeFramebufferCase(m_context, "fbo_attach_non_zero_level",
                                                            "Attach non-zero level",
                                                            NegativeFramebufferCase::CASE_NON_ZERO_LEVEL));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_min_filter", "set TEXTURE_MIN_FILTER",
                                                             NegativeTexParameterCase::TEXTURE_MIN_FILTER));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_mag_filter", "set TEXTURE_MAG_FILTER",
                                                             NegativeTexParameterCase::TEXTURE_MAG_FILTER));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_s", "set TEXTURE_WRAP_S",
                                                             NegativeTexParameterCase::TEXTURE_WRAP_S));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_t", "set TEXTURE_WRAP_T",
                                                             NegativeTexParameterCase::TEXTURE_WRAP_T));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_wrap_r", "set TEXTURE_WRAP_R",
                                                             NegativeTexParameterCase::TEXTURE_WRAP_R));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_min_lod", "set TEXTURE_MIN_LOD",
                                                             NegativeTexParameterCase::TEXTURE_MIN_LOD));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_max_lod", "set TEXTURE_MAX_LOD",
                                                             NegativeTexParameterCase::TEXTURE_MAX_LOD));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_compare_mode",
                                                             "set TEXTURE_COMPARE_MODE",
                                                             NegativeTexParameterCase::TEXTURE_COMPARE_MODE));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_compare_func",
                                                             "set TEXTURE_COMPARE_FUNC",
                                                             NegativeTexParameterCase::TEXTURE_COMPARE_FUNC));
        negativeGroup->addChild(new NegativeTexParameterCase(m_context, "texture_base_level", "set TEXTURE_BASE_LEVEL",
                                                             NegativeTexParameterCase::TEXTURE_BASE_LEVEL));
        negativeGroup->addChild(new NegativeTexureSampleCase(m_context, "texture_high_sample_count",
                                                             "TexStorage with high numSamples",
                                                             NegativeTexureSampleCase::SAMPLECOUNT_HIGH));
        negativeGroup->addChild(new NegativeTexureSampleCase(m_context, "texture_zero_sample_count",
                                                             "TexStorage with zero numSamples",
                                                             NegativeTexureSampleCase::SAMPLECOUNT_ZERO));
    }
}

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