/*-------------------------------------------------------------------------
 * 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 shader render case
 *//*--------------------------------------------------------------------*/

#include "es31fMultisampleShaderRenderCase.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "tcuTestLog.hpp"
#include "tcuStringTemplate.hpp"
#include "gluContextInfo.hpp"
#include "gluShaderProgram.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace MultisampleShaderRenderUtil
{
using std::map;
using std::string;
namespace
{

static const char *const s_vertexSource = "${GLSL_VERSION_DECL}\n"
                                          "in highp vec4 a_position;\n"
                                          "out highp vec4 v_position;\n"
                                          "void main (void)\n"
                                          "{\n"
                                          "    gl_Position = a_position;\n"
                                          "    v_position = a_position;\n"
                                          "}";

} // namespace

QualityWarning::QualityWarning(const std::string &message) : tcu::Exception(message)
{
}

MultisampleRenderCase::MultisampleRenderCase(Context &context, const char *name, const char *desc, int numSamples,
                                             RenderTarget target, int renderSize, int flags)
    : TestCase(context, name, desc)
    , m_numRequestedSamples(numSamples)
    , m_renderTarget(target)
    , m_renderSize(renderSize)
    , m_perIterationShader((flags & FLAG_PER_ITERATION_SHADER) != 0)
    , m_verifyTextureSampleBuffers((flags & FLAG_VERIFY_MSAA_TEXTURE_SAMPLE_BUFFERS) != 0 && target == TARGET_TEXTURE)
    , m_numTargetSamples(-1)
    , m_buffer(0)
    , m_resolveBuffer(0)
    , m_program(DE_NULL)
    , m_fbo(0)
    , m_fboTexture(0)
    , m_textureSamplerProgram(DE_NULL)
    , m_fboRbo(0)
    , m_resolveFbo(0)
    , m_resolveFboTexture(0)
    , m_iteration(0)
    , m_numIterations(1)
    , m_renderMode(0)
    , m_renderCount(0)
    , m_renderVao(0)
    , m_resolveVao(0)
{
    DE_ASSERT(target < TARGET_LAST);
}

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

void MultisampleRenderCase::init(void)
{
    const glw::Functions &gl   = m_context.getRenderContext().getFunctions();
    int32_t queriedSampleCount = -1;
    const bool supportsES32    = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
    map<string, string> args;

    args["GLSL_VERSION_DECL"] = supportsES32 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                               getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);

    // requirements

    switch (m_renderTarget)
    {
    case TARGET_DEFAULT:
    {
        if (m_context.getRenderTarget().getWidth() < m_renderSize ||
            m_context.getRenderTarget().getHeight() < m_renderSize)
            throw tcu::NotSupportedError("Test requires render target with size " + de::toString(m_renderSize) + "x" +
                                         de::toString(m_renderSize) + " or greater");
        break;
    }

    case TARGET_TEXTURE:
    {
        int32_t maxTextureSamples = getMaxConformantSampleCount(GL_TEXTURE_2D_MULTISAMPLE, GL_RGBA8);
        if (m_numRequestedSamples > maxTextureSamples)
            throw tcu::NotSupportedError("Sample count not supported");
        break;
    }

    case TARGET_RENDERBUFFER:
    {
        int32_t maxRboSamples = getMaxConformantSampleCount(GL_RENDERBUFFER, GL_RGBA8);
        if (m_numRequestedSamples > maxRboSamples)
            throw tcu::NotSupportedError("Sample count not supported");
        break;
    }

    default:
        DE_ASSERT(false);
    }

    // resources

    {
        gl.genBuffers(1, &m_buffer);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen buf");

        setupRenderData();
        GLU_EXPECT_NO_ERROR(gl.getError(), "setup data");

        gl.genVertexArrays(1, &m_renderVao);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen vao");

        // buffer for MSAA texture resolving
        {
            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),
            };

            gl.genBuffers(1, &m_resolveBuffer);
            gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
            gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
            GLU_EXPECT_NO_ERROR(gl.getError(), "setup data");
        }
    }

    // msaa targets

    if (m_renderTarget == TARGET_TEXTURE)
    {
        const uint32_t textureTarget = (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);

        gl.genVertexArrays(1, &m_resolveVao);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen vao");

        gl.genTextures(1, &m_fboTexture);
        gl.bindTexture(textureTarget, m_fboTexture);
        if (m_numRequestedSamples == 0)
        {
            gl.texStorage2D(textureTarget, 1, GL_RGBA8, m_renderSize, m_renderSize);
            gl.texParameteri(textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            gl.texParameteri(textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        }
        else
            gl.texStorage2DMultisample(textureTarget, m_numRequestedSamples, GL_RGBA8, m_renderSize, m_renderSize,
                                       GL_FALSE);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

        gl.genFramebuffers(1, &m_fbo);
        gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, textureTarget, m_fboTexture, 0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

        if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            throw tcu::TestError("fbo not complete");

        if (m_numRequestedSamples != 0)
        {
            // for shader
            gl.getTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &queriedSampleCount);

            // logging
            m_testCtx.getLog() << tcu::TestLog::Message << "Asked for " << m_numRequestedSamples << " samples, got "
                               << queriedSampleCount << " samples." << tcu::TestLog::EndMessage;

            // sanity
            if (queriedSampleCount < m_numRequestedSamples)
                throw tcu::TestError("Got less texture samples than asked for");
        }

        // texture sampler shader
        m_textureSamplerProgram = new glu::ShaderProgram(
            m_context.getRenderContext(), glu::ProgramSources()
                                              << glu::VertexSource(tcu::StringTemplate(s_vertexSource).specialize(args))
                                              << glu::FragmentSource(genMSSamplerSource(queriedSampleCount)));
        if (!m_textureSamplerProgram->isOk())
        {
            m_testCtx.getLog() << tcu::TestLog::Section("SamplerShader", "Sampler shader") << *m_textureSamplerProgram
                               << tcu::TestLog::EndSection;
            throw tcu::TestError("could not build program");
        }
    }
    else if (m_renderTarget == TARGET_RENDERBUFFER)
    {
        gl.genRenderbuffers(1, &m_fboRbo);
        gl.bindRenderbuffer(GL_RENDERBUFFER, m_fboRbo);
        gl.renderbufferStorageMultisample(GL_RENDERBUFFER, m_numRequestedSamples, GL_RGBA8, m_renderSize, m_renderSize);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen rbo");

        gl.genFramebuffers(1, &m_fbo);
        gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_fboRbo);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

        if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            throw tcu::TestError("fbo not complete");

        // logging
        gl.getRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &queriedSampleCount);
        m_testCtx.getLog() << tcu::TestLog::Message << "Asked for " << m_numRequestedSamples << " samples, got "
                           << queriedSampleCount << " samples." << tcu::TestLog::EndMessage;

        // sanity
        if (queriedSampleCount < m_numRequestedSamples)
            throw tcu::TestError("Got less renderbuffer samples samples than asked for");
    }

    // fbo for resolving the multisample fbo
    if (m_renderTarget != TARGET_DEFAULT)
    {
        gl.genTextures(1, &m_resolveFboTexture);
        gl.bindTexture(GL_TEXTURE_2D, m_resolveFboTexture);
        gl.texStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, m_renderSize, m_renderSize);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen tex");

        gl.genFramebuffers(1, &m_resolveFbo);
        gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
        gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_resolveFboTexture, 0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "gen fbo");

        if (gl.checkFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
            throw tcu::TestError("resolve fbo not complete");
    }

    // create verifier shader and set targetSampleCount

    {
        int realSampleCount = -1;

        if (m_renderTarget == TARGET_TEXTURE)
        {
            if (m_numRequestedSamples == 0)
                realSampleCount = 1; // non msaa texture
            else
                realSampleCount = de::max(1, queriedSampleCount); // msaa texture
        }
        else if (m_renderTarget == TARGET_RENDERBUFFER)
        {
            realSampleCount = de::max(1, queriedSampleCount); // msaa rbo
        }
        else if (m_renderTarget == TARGET_DEFAULT)
        {
            realSampleCount = de::max(1, m_context.getRenderTarget().getNumSamples());
        }
        else
            DE_ASSERT(false);

        // is set and is valid
        DE_ASSERT(realSampleCount != -1);
        DE_ASSERT(realSampleCount != 0);
        m_numTargetSamples = realSampleCount;
    }

    if (!m_perIterationShader)
    {
        m_program =
            new glu::ShaderProgram(m_context.getRenderContext(),
                                   glu::ProgramSources() << glu::VertexSource(genVertexSource(m_numTargetSamples))
                                                         << glu::FragmentSource(genFragmentSource(m_numTargetSamples)));
        m_testCtx.getLog() << tcu::TestLog::Section("RenderShader", "Render shader") << *m_program
                           << tcu::TestLog::EndSection;
        if (!m_program->isOk())
            throw tcu::TestError("could not build program");
    }
}

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

    if (m_resolveBuffer)
    {
        m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_resolveBuffer);
        m_resolveBuffer = 0;
    }

    delete m_program;
    m_program = DE_NULL;

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

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

    delete m_textureSamplerProgram;
    m_textureSamplerProgram = DE_NULL;

    if (m_fboRbo)
    {
        m_context.getRenderContext().getFunctions().deleteRenderbuffers(1, &m_fboRbo);
        m_fboRbo = 0;
    }

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

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

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

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

MultisampleRenderCase::IterateResult MultisampleRenderCase::iterate(void)
{
    // default value
    if (m_iteration == 0)
    {
        m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
        preTest();
    }

    drawOneIteration();

    // next iteration
    ++m_iteration;
    if (m_iteration < m_numIterations)
        return CONTINUE;
    else
    {
        postTest();
        return STOP;
    }
}

void MultisampleRenderCase::preDraw(void)
{
}

void MultisampleRenderCase::postDraw(void)
{
}

void MultisampleRenderCase::preTest(void)
{
}

void MultisampleRenderCase::postTest(void)
{
}

void MultisampleRenderCase::verifyResultImageAndSetResult(const tcu::Surface &resultImage)
{
    // verify using case-specific verification

    try
    {
        if (!verifyImage(resultImage))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
    }
    catch (const QualityWarning &ex)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Quality warning, error = " << ex.what()
                           << tcu::TestLog::EndMessage;

        // Failures are more important than warnings
        if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, ex.what());
    }
}

void MultisampleRenderCase::verifyResultBuffersAndSetResult(const std::vector<tcu::Surface> &resultBuffers)
{
    // verify using case-specific verification

    try
    {
        if (!verifySampleBuffers(resultBuffers))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");
    }
    catch (const QualityWarning &ex)
    {
        m_testCtx.getLog() << tcu::TestLog::Message << "Quality warning, error = " << ex.what()
                           << tcu::TestLog::EndMessage;

        // Failures are more important than warnings
        if (m_testCtx.getTestResult() == QP_TEST_RESULT_PASS)
            m_testCtx.setTestResult(QP_TEST_RESULT_QUALITY_WARNING, ex.what());
    }
}

std::string MultisampleRenderCase::getIterationDescription(int iteration) const
{
    DE_UNREF(iteration);
    DE_ASSERT(false);
    return "";
}

void MultisampleRenderCase::drawOneIteration(void)
{
    const glw::Functions &gl = m_context.getRenderContext().getFunctions();
    const std::string sectionDescription =
        (m_numIterations > 1) ? ("Iteration " + de::toString(m_iteration + 1) + "/" + de::toString(m_numIterations) +
                                 ": " + getIterationDescription(m_iteration)) :
                                ("Test");
    const tcu::ScopedLogSection section(m_testCtx.getLog(), "Iteration" + de::toString(m_iteration),
                                        sectionDescription);

    // Per iteration shader?
    if (m_perIterationShader)
    {
        delete m_program;
        m_program = DE_NULL;

        m_program =
            new glu::ShaderProgram(m_context.getRenderContext(),
                                   glu::ProgramSources() << glu::VertexSource(genVertexSource(m_numTargetSamples))
                                                         << glu::FragmentSource(genFragmentSource(m_numTargetSamples)));
        m_testCtx.getLog() << tcu::TestLog::Section("RenderShader", "Render shader") << *m_program
                           << tcu::TestLog::EndSection;
        if (!m_program->isOk())
            throw tcu::TestError("could not build program");
    }

    // render
    {
        if (m_renderTarget == TARGET_TEXTURE || m_renderTarget == TARGET_RENDERBUFFER)
        {
            gl.bindFramebuffer(GL_FRAMEBUFFER, m_fbo);
            GLU_EXPECT_NO_ERROR(gl.getError(), "bind fbo");

            m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << m_renderSceneDescription
                               << " with render shader to fbo." << tcu::TestLog::EndMessage;
        }
        else
            m_testCtx.getLog() << tcu::TestLog::Message << "Rendering " << m_renderSceneDescription
                               << " with render shader to default framebuffer." << tcu::TestLog::EndMessage;

        gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
        gl.clear(GL_COLOR_BUFFER_BIT);
        gl.viewport(0, 0, m_renderSize, m_renderSize);
        GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

        gl.bindVertexArray(m_renderVao);
        gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);

        // set attribs
        DE_ASSERT(!m_renderAttribs.empty());
        for (std::map<std::string, Attrib>::const_iterator it = m_renderAttribs.begin(); it != m_renderAttribs.end();
             ++it)
        {
            const int32_t location = gl.getAttribLocation(m_program->getProgram(), it->first.c_str());

            if (location != -1)
            {
                gl.vertexAttribPointer(location, 4, GL_FLOAT, GL_FALSE, it->second.stride,
                                       glu::BufferOffsetAsPointer(it->second.offset));
                gl.enableVertexAttribArray(location);
            }
        }
        GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");

        gl.useProgram(m_program->getProgram());
        preDraw();
        gl.drawArrays(m_renderMode, 0, m_renderCount);
        postDraw();
        gl.useProgram(0);
        gl.bindVertexArray(0);
        GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

        if (m_renderTarget == TARGET_TEXTURE || m_renderTarget == TARGET_RENDERBUFFER)
            gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    // read
    {
        if (m_renderTarget == TARGET_DEFAULT)
        {
            tcu::Surface resultImage(m_renderSize, m_renderSize);

            m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from default framebuffer."
                               << tcu::TestLog::EndMessage;

            // default directly
            glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
            GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

            // set test result
            verifyResultImageAndSetResult(resultImage);
        }
        else if (m_renderTarget == TARGET_RENDERBUFFER)
        {
            tcu::Surface resultImage(m_renderSize, m_renderSize);

            // rbo by blitting to non-multisample fbo

            m_testCtx.getLog() << tcu::TestLog::Message
                               << "Blitting result from fbo to single sample fbo. (Resolve multisample)"
                               << tcu::TestLog::EndMessage;

            gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_fbo);
            gl.bindFramebuffer(GL_DRAW_FRAMEBUFFER, m_resolveFbo);
            gl.blitFramebuffer(0, 0, m_renderSize, m_renderSize, 0, 0, m_renderSize, m_renderSize, GL_COLOR_BUFFER_BIT,
                               GL_NEAREST);
            GLU_EXPECT_NO_ERROR(gl.getError(), "blit resolve");

            m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from single sample framebuffer."
                               << tcu::TestLog::EndMessage;

            gl.bindFramebuffer(GL_READ_FRAMEBUFFER, m_resolveFbo);
            glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
            GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

            gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

            // set test result
            verifyResultImageAndSetResult(resultImage);
        }
        else if (m_renderTarget == TARGET_TEXTURE && !m_verifyTextureSampleBuffers)
        {
            const int32_t posLocation     = gl.getAttribLocation(m_textureSamplerProgram->getProgram(), "a_position");
            const int32_t samplerLocation = gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampler");
            const uint32_t textureTarget = (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
            tcu::Surface resultImage(m_renderSize, m_renderSize);

            if (m_numRequestedSamples)
                m_testCtx.getLog()
                    << tcu::TestLog::Message
                    << "Using sampler shader to sample the multisample texture to single sample framebuffer."
                    << tcu::TestLog::EndMessage;
            else
                m_testCtx.getLog() << tcu::TestLog::Message
                                   << "Drawing texture to single sample framebuffer. Using sampler shader."
                                   << tcu::TestLog::EndMessage;

            if (samplerLocation == -1)
                throw tcu::TestError("Location u_sampler was -1.");

            // resolve multisample texture by averaging
            gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
            gl.clear(GL_COLOR_BUFFER_BIT);
            gl.viewport(0, 0, m_renderSize, m_renderSize);
            GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

            gl.bindVertexArray(m_resolveVao);
            gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
            gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            gl.enableVertexAttribArray(posLocation);
            GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");

            gl.activeTexture(GL_TEXTURE0);
            gl.bindTexture(textureTarget, m_fboTexture);
            GLU_EXPECT_NO_ERROR(gl.getError(), "bind tex");

            gl.useProgram(m_textureSamplerProgram->getProgram());
            gl.uniform1i(samplerLocation, 0);

            gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
            gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);

            gl.useProgram(0);
            gl.bindVertexArray(0);
            GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

            m_testCtx.getLog() << tcu::TestLog::Message << "Reading pixels from single sample framebuffer."
                               << tcu::TestLog::EndMessage;

            glu::readPixels(m_context.getRenderContext(), 0, 0, resultImage.getAccess());
            GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");

            gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

            // set test result
            verifyResultImageAndSetResult(resultImage);
        }
        else if (m_renderTarget == TARGET_TEXTURE && m_verifyTextureSampleBuffers)
        {
            const int32_t posLocation     = gl.getAttribLocation(m_textureSamplerProgram->getProgram(), "a_position");
            const int32_t samplerLocation = gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampler");
            const int32_t sampleLocation  = gl.getUniformLocation(m_textureSamplerProgram->getProgram(), "u_sampleNdx");
            const uint32_t textureTarget = (m_numRequestedSamples == 0) ? (GL_TEXTURE_2D) : (GL_TEXTURE_2D_MULTISAMPLE);
            std::vector<tcu::Surface> resultBuffers(m_numTargetSamples);

            if (m_numRequestedSamples)
                m_testCtx.getLog() << tcu::TestLog::Message << "Reading multisample texture sample buffers."
                                   << tcu::TestLog::EndMessage;
            else
                m_testCtx.getLog() << tcu::TestLog::Message << "Reading texture." << tcu::TestLog::EndMessage;

            if (samplerLocation == -1)
                throw tcu::TestError("Location u_sampler was -1.");
            if (sampleLocation == -1)
                throw tcu::TestError("Location u_sampleNdx was -1.");

            for (int sampleNdx = 0; sampleNdx < m_numTargetSamples; ++sampleNdx)
                resultBuffers[sampleNdx].setSize(m_renderSize, m_renderSize);

            // read sample buffers to different surfaces
            gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
            gl.clear(GL_COLOR_BUFFER_BIT);
            gl.viewport(0, 0, m_renderSize, m_renderSize);
            GLU_EXPECT_NO_ERROR(gl.getError(), "clear");

            gl.bindVertexArray(m_resolveVao);
            gl.bindBuffer(GL_ARRAY_BUFFER, m_resolveBuffer);
            gl.vertexAttribPointer(posLocation, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
            gl.enableVertexAttribArray(posLocation);
            GLU_EXPECT_NO_ERROR(gl.getError(), "set attrib");

            gl.activeTexture(GL_TEXTURE0);
            gl.bindTexture(textureTarget, m_fboTexture);
            GLU_EXPECT_NO_ERROR(gl.getError(), "bind tex");

            gl.bindFramebuffer(GL_FRAMEBUFFER, m_resolveFbo);
            gl.useProgram(m_textureSamplerProgram->getProgram());
            gl.uniform1i(samplerLocation, 0);

            m_testCtx.getLog() << tcu::TestLog::Message << "Reading sample buffers" << tcu::TestLog::EndMessage;

            for (int sampleNdx = 0; sampleNdx < m_numTargetSamples; ++sampleNdx)
            {
                gl.uniform1i(sampleLocation, sampleNdx);
                gl.drawArrays(GL_TRIANGLE_STRIP, 0, 4);
                GLU_EXPECT_NO_ERROR(gl.getError(), "draw");

                glu::readPixels(m_context.getRenderContext(), 0, 0, resultBuffers[sampleNdx].getAccess());
                GLU_EXPECT_NO_ERROR(gl.getError(), "read pixels");
            }

            gl.useProgram(0);
            gl.bindVertexArray(0);
            gl.bindFramebuffer(GL_FRAMEBUFFER, 0);

            // verify sample buffers
            verifyResultBuffersAndSetResult(resultBuffers);
        }
        else
            DE_ASSERT(false);
    }
}

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

    map<string, string> args;

    args["GLSL_VERSION_DECL"] = supportsES32orGL45 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                                     getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);

    DE_UNREF(numTargetSamples);
    return std::string(tcu::StringTemplate(s_vertexSource).specialize(args));
}

std::string MultisampleRenderCase::genMSSamplerSource(int numTargetSamples) const
{
    if (m_verifyTextureSampleBuffers)
        return genMSTextureLayerFetchSource(numTargetSamples);
    else
        return genMSTextureResolverSource(numTargetSamples);
}

std::string MultisampleRenderCase::genMSTextureResolverSource(int numTargetSamples) const
{
    // default behavior: average

    const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
    map<string, string> args;
    const bool isSingleSampleTarget = (m_numRequestedSamples == 0);
    std::ostringstream buf;

    args["GLSL_VERSION_DECL"] = supportsES32 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                               getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);

    buf << "${GLSL_VERSION_DECL}\n"
           "in mediump vec4 v_position;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "uniform mediump "
        << ((isSingleSampleTarget) ? ("sampler2D") : ("sampler2DMS"))
        << " u_sampler;\n"
           "void main (void)\n"
           "{\n"
           "    mediump vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
           "    mediump ivec2 fetchPos = ivec2(floor(relPosition * "
        << m_renderSize
        << ".0));\n"
           "    mediump vec4 colorSum = vec4(0.0, 0.0, 0.0, 0.0);\n"
           "\n";

    if (isSingleSampleTarget)
        buf << "    colorSum = texelFetch(u_sampler, fetchPos, 0);\n"
               "\n";
    else
        buf << "    for (int sampleNdx = 0; sampleNdx < " << numTargetSamples
            << "; ++sampleNdx)\n"
               "        colorSum += texelFetch(u_sampler, fetchPos, sampleNdx);\n"
               "    colorSum /= "
            << numTargetSamples
            << ".0;\n"
               "\n";

    buf << "    fragColor = vec4(colorSum.xyz, 1.0);\n"
           "}\n";

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

std::string MultisampleRenderCase::genMSTextureLayerFetchSource(int numTargetSamples) const
{
    DE_UNREF(numTargetSamples);

    const bool supportsES32 = glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::es(3, 2));
    map<string, string> args;
    const bool isSingleSampleTarget = (m_numRequestedSamples == 0);
    std::ostringstream buf;

    args["GLSL_VERSION_DECL"] = supportsES32 ? getGLSLVersionDeclaration(glu::GLSL_VERSION_320_ES) :
                                               getGLSLVersionDeclaration(glu::GLSL_VERSION_310_ES);

    buf << "${GLSL_VERSION_DECL}\n"
           "in mediump vec4 v_position;\n"
           "layout(location = 0) out mediump vec4 fragColor;\n"
           "uniform mediump "
        << ((isSingleSampleTarget) ? ("sampler2D") : ("sampler2DMS"))
        << " u_sampler;\n"
           "uniform mediump int u_sampleNdx;\n"
           "void main (void)\n"
           "{\n"
           "    mediump vec2 relPosition = (v_position.xy + vec2(1.0, 1.0)) / 2.0;\n"
           "    mediump ivec2 fetchPos = ivec2(floor(relPosition * "
        << m_renderSize
        << ".0));\n"
           "\n"
           "    mediump vec4 color = texelFetch(u_sampler, fetchPos, u_sampleNdx);\n"
           "    fragColor = vec4(color.rgb, 1.0);\n"
           "}\n";

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

bool MultisampleRenderCase::verifySampleBuffers(const std::vector<tcu::Surface> &resultBuffers)
{
    DE_UNREF(resultBuffers);
    DE_ASSERT(false);
    return false;
}

void MultisampleRenderCase::setupRenderData(void)
{
    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();

    m_renderMode             = GL_TRIANGLE_STRIP;
    m_renderCount            = 4;
    m_renderSceneDescription = "quad";

    m_renderAttribs["a_position"].offset = 0;
    m_renderAttribs["a_position"].stride = sizeof(float[4]);

    gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
    gl.bufferData(GL_ARRAY_BUFFER, (int)sizeof(fullscreenQuad), fullscreenQuad, GL_STATIC_DRAW);
}

glw::GLint MultisampleRenderCase::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;
}

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