/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2017 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 EXT Shader Framebuffer Fetch Tests.
 *//*--------------------------------------------------------------------*/

#include "es31fShaderFramebufferFetchTests.hpp"
#include "es31fFboTestUtil.hpp"

#include "tcuTestLog.hpp"
#include "tcuSurface.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVectorUtil.hpp"

#include "gluShaderProgram.hpp"
#include "gluPixelTransfer.hpp"
#include "gluTextureUtil.hpp"
#include "gluContextInfo.hpp"
#include "gluObjectWrapper.hpp"

#include "glwFunctions.hpp"
#include "glwEnums.hpp"

#include "deStringUtil.hpp"

#include <vector>

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

using std::string;
using std::vector;
using tcu::TestLog;

using namespace glw;
using namespace FboTestUtil;

static void checkExtensionSupport(Context &context, const char *extName)
{
    if (!context.getContextInfo().isExtensionSupported(extName))
        throw tcu::NotSupportedError(string(extName) + " not supported");
}

static void checkFramebufferFetchSupport(Context &context)
{
    checkExtensionSupport(context, "GL_EXT_shader_framebuffer_fetch");
}

static bool isRequiredFormat(uint32_t format, glu::RenderContext &renderContext)
{
    const bool isES32 = glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2));
    switch (format)
    {
    // Color-renderable formats
    case GL_RGBA32I:
    case GL_RGBA32UI:
    case GL_RGBA16I:
    case GL_RGBA16UI:
    case GL_RGBA8:
    case GL_RGBA8I:
    case GL_RGBA8UI:
    case GL_SRGB8_ALPHA8:
    case GL_RGB10_A2:
    case GL_RGB10_A2UI:
    case GL_RGBA4:
    case GL_RGB5_A1:
    case GL_RGB8:
    case GL_RGB565:
    case GL_RG32I:
    case GL_RG32UI:
    case GL_RG16I:
    case GL_RG16UI:
    case GL_RG8:
    case GL_RG8I:
    case GL_RG8UI:
    case GL_R32I:
    case GL_R32UI:
    case GL_R16I:
    case GL_R16UI:
    case GL_R8:
    case GL_R8I:
    case GL_R8UI:
        return true;

    // Float format
    case GL_RGBA32F:
    case GL_RGB32F:
    case GL_R11F_G11F_B10F:
    case GL_RG32F:
    case GL_R32F:
        return isES32;

    default:
        return false;
    }
}

tcu::TextureFormat getReadPixelFormat(const tcu::TextureFormat &format)
{
    switch (tcu::getTextureChannelClass(format.type))
    {
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNSIGNED_INT32);

    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::SIGNED_INT32);

    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);

    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::FLOAT);

    default:
        DE_ASSERT(false);
        return tcu::TextureFormat(tcu::TextureFormat::RGBA, tcu::TextureFormat::UNORM_INT8);
    }
}

tcu::Vec4 getFixedPointFormatThreshold(const tcu::TextureFormat &sourceFormat,
                                       const tcu::TextureFormat &readPixelsFormat)
{
    DE_ASSERT(tcu::getTextureChannelClass(sourceFormat.type) != tcu::TEXTURECHANNELCLASS_FLOATING_POINT);
    DE_ASSERT(tcu::getTextureChannelClass(readPixelsFormat.type) != tcu::TEXTURECHANNELCLASS_FLOATING_POINT);

    DE_ASSERT(tcu::getTextureChannelClass(sourceFormat.type) != tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER);
    DE_ASSERT(tcu::getTextureChannelClass(readPixelsFormat.type) != tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER);

    DE_ASSERT(tcu::getTextureChannelClass(sourceFormat.type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER);
    DE_ASSERT(tcu::getTextureChannelClass(readPixelsFormat.type) != tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER);

    const tcu::IVec4 srcBits  = tcu::getTextureFormatBitDepth(sourceFormat);
    const tcu::IVec4 readBits = tcu::getTextureFormatBitDepth(readPixelsFormat);

    tcu::IVec4 bits;
    for (uint32_t i = 0; i < 4; ++i)
        bits[i] = srcBits[i] == 0 ? readBits[i] : de::min(srcBits[i], readBits[i]);
    return tcu::Vec4(3.0f) /
           ((tcu::Vector<uint64_t, 4>(1) << (bits.cast<uint64_t>())) - tcu::Vector<uint64_t, 4>(1)).cast<float>();
}

tcu::UVec4 getFloatULPThreshold(const tcu::TextureFormat &sourceFormat, const tcu::TextureFormat &readPixelsFormat)
{
    const tcu::IVec4 srcMantissaBits  = tcu::getTextureFormatMantissaBitDepth(sourceFormat);
    const tcu::IVec4 readMantissaBits = tcu::getTextureFormatMantissaBitDepth(readPixelsFormat);
    tcu::IVec4 ULPDiff(0);

    for (int i = 0; i < 4; i++)
        if (readMantissaBits[i] >= srcMantissaBits[i])
            ULPDiff[i] = readMantissaBits[i] - srcMantissaBits[i];

    return tcu::UVec4(4) * (tcu::UVec4(1) << (ULPDiff.cast<uint32_t>()));
}

static bool isAnyExtensionSupported(Context &context, const std::vector<std::string> &requiredExts)
{
    for (std::vector<std::string>::const_iterator iter = requiredExts.begin(); iter != requiredExts.end(); iter++)
    {
        const std::string &extension = *iter;

        if (context.getContextInfo().isExtensionSupported(extension.c_str()))
            return true;
    }

    return false;
}

static std::string getColorOutputType(tcu::TextureFormat format)
{
    switch (tcu::getTextureChannelClass(format.type))
    {
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
        return "uvec4";
    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
        return "ivec4";
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
        return "vec4";
    default:
        DE_FATAL("Unsupported TEXTURECHANNELCLASS");
        return "";
    }
}

static std::vector<std::string> getEnablingExtensions(uint32_t format, glu::RenderContext &renderContext)
{
    const bool isES32 = glu::contextSupports(renderContext.getType(), glu::ApiType::es(3, 2));
    std::vector<std::string> out;

    DE_ASSERT(!isRequiredFormat(format, renderContext));

    switch (format)
    {
    case GL_RGB16F:
        out.push_back("GL_EXT_color_buffer_half_float");
        break;

    case GL_RGBA16F:
    case GL_RG16F:
    case GL_R16F:
        out.push_back("GL_EXT_color_buffer_half_float");
        // Fallthrough

    case GL_RGBA32F:
    case GL_RGB32F:
    case GL_R11F_G11F_B10F:
    case GL_RG32F:
    case GL_R32F:
        if (!isES32)
            out.push_back("GL_EXT_color_buffer_float");
        break;

    default:
        break;
    }

    return out;
}

void checkFormatSupport(Context &context, uint32_t sizedFormat)
{
    const bool isCoreFormat = isRequiredFormat(sizedFormat, context.getRenderContext());
    const std::vector<std::string> requiredExts =
        (!isCoreFormat) ? getEnablingExtensions(sizedFormat, context.getRenderContext()) : std::vector<std::string>();

    // Check that we don't try to use invalid formats.
    DE_ASSERT(isCoreFormat || !requiredExts.empty());

    if (!requiredExts.empty() && !isAnyExtensionSupported(context, requiredExts))
        throw tcu::NotSupportedError("Format not supported");
}

tcu::Vec4 scaleColorValue(tcu::TextureFormat format, const tcu::Vec4 &color)
{
    const tcu::TextureFormatInfo fmtInfo = tcu::getTextureFormatInfo(format);
    const tcu::Vec4 cScale               = fmtInfo.valueMax - fmtInfo.valueMin;
    const tcu::Vec4 cBias                = fmtInfo.valueMin;

    return tcu::RGBA(color).toVec() * cScale + cBias;
}

// Base class for framebuffer fetch test cases

class FramebufferFetchTestCase : public TestCase
{
public:
    FramebufferFetchTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~FramebufferFetchTestCase(void);

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

protected:
    string genPassThroughVertSource(void);
    virtual glu::ProgramSources genShaderSources(void);

    void genFramebufferWithTexture(const tcu::Vec4 &color);
    void genAttachementTexture(const tcu::Vec4 &color);
    void genUniformColor(const tcu::Vec4 &color);

    void render(void);
    void verifyRenderbuffer(TestLog &log, const tcu::TextureFormat &format, const tcu::TextureLevel &reference,
                            const tcu::TextureLevel &result);

    const glw::Functions &m_gl;
    const uint32_t m_format;

    glu::ShaderProgram *m_program;
    GLuint m_framebuffer;
    GLuint m_texColorBuffer;

    tcu::TextureFormat m_texFmt;
    glu::TransferFormat m_transferFmt;
    bool m_isFilterable;

    enum
    {
        VIEWPORT_WIDTH  = 64,
        VIEWPORT_HEIGHT = 64,
    };
};

FramebufferFetchTestCase::FramebufferFetchTestCase(Context &context, const char *name, const char *desc,
                                                   uint32_t format)
    : TestCase(context, name, desc)
    , m_gl(m_context.getRenderContext().getFunctions())
    , m_format(format)
    , m_program(DE_NULL)
    , m_framebuffer(0)
    , m_texColorBuffer(0)
    , m_texFmt(glu::mapGLInternalFormat(m_format))
    , m_transferFmt(glu::getTransferFormat(m_texFmt))
    , m_isFilterable(glu::isGLInternalColorFormatFilterable(m_format))
{
}

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

void FramebufferFetchTestCase::init(void)
{
    checkFramebufferFetchSupport(m_context);
    checkFormatSupport(m_context, m_format);

    if (glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5)) && tcu::isSRGB(m_texFmt))
    {
        m_gl.enable(GL_FRAMEBUFFER_SRGB);
    }

    DE_ASSERT(!m_program);
    m_program = new glu::ShaderProgram(m_context.getRenderContext(), genShaderSources());

    m_testCtx.getLog() << *m_program;

    if (!m_program->isOk())
    {
        delete m_program;
        m_program = DE_NULL;
        TCU_FAIL("Failed to compile shader program");
    }

    m_gl.useProgram(m_program->getProgram());
}

void FramebufferFetchTestCase::deinit(void)
{
    delete m_program;
    m_program = DE_NULL;

    if (glu::contextSupports(m_context.getRenderContext().getType(), glu::ApiType::core(4, 5)))
    {
        m_gl.disable(GL_FRAMEBUFFER_SRGB);
    }

    if (m_framebuffer)
    {
        m_gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
        m_gl.deleteFramebuffers(1, &m_framebuffer);
        m_framebuffer = 0;
    }

    if (m_texColorBuffer)
    {
        m_gl.deleteTextures(1, &m_texColorBuffer);
        m_texColorBuffer = 0;
    }
}

string FramebufferFetchTestCase::genPassThroughVertSource(void)
{
    std::ostringstream vertShaderSource;

    vertShaderSource << "#version 310 es\n"
                     << "in highp vec4 a_position;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    gl_Position = a_position;\n"
                     << "}\n";

    return vertShaderSource.str();
}

glu::ProgramSources FramebufferFetchTestCase::genShaderSources(void)
{
    const string vecType = getColorOutputType(m_texFmt);
    std::ostringstream fragShaderSource;
    tcu::TextureChannelClass textureChannelClass = tcu::getTextureChannelClass(m_texFmt.type);
    tcu::Vec4 maxValue                           = getTextureFormatInfo(m_texFmt).valueMax;
    tcu::Vec4 minValue                           = getTextureFormatInfo(m_texFmt).valueMin;
    string maxStr;
    string minStr;

    if (textureChannelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)
    {
        maxStr = de::toString(maxValue.asUint());
        minStr = de::toString(minValue.asUint());
    }
    else if (textureChannelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER)
    {
        maxStr = de::toString(maxValue.asInt());
        minStr = de::toString(minValue.asInt());
    }
    else
    {
        maxStr = de::toString(maxValue);
        minStr = de::toString(minValue);
    }

    fragShaderSource << "#version 310 es\n"
                     << "#extension GL_EXT_shader_framebuffer_fetch : require\n"
                     << "layout(location = 0) inout highp " << vecType << " o_color;\n"
                     << "uniform highp " << vecType << " u_color;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    o_color = clamp(o_color + u_color, " << vecType << minStr << ", " << vecType << maxStr
                     << ");\n"
                     << "}\n";

    return glu::makeVtxFragSources(genPassThroughVertSource(), fragShaderSource.str());
}

void FramebufferFetchTestCase::genFramebufferWithTexture(const tcu::Vec4 &color)
{
    m_gl.genFramebuffers(1, &m_framebuffer);
    m_gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

    genAttachementTexture(color);
    GLU_EXPECT_NO_ERROR(m_gl.getError(), "genAttachementTexture()");

    m_gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texColorBuffer, 0);
    TCU_CHECK(m_gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}

void FramebufferFetchTestCase::genAttachementTexture(const tcu::Vec4 &color)
{
    tcu::TextureLevel data(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                           VIEWPORT_HEIGHT, 1);
    tcu::TextureChannelClass textureChannelClass = tcu::getTextureChannelClass(m_texFmt.type);

    m_gl.genTextures(1, &m_texColorBuffer);
    m_gl.bindTexture(GL_TEXTURE_2D, m_texColorBuffer);

    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isFilterable ? GL_LINEAR : GL_NEAREST);
    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_isFilterable ? GL_LINEAR : GL_NEAREST);

    if (textureChannelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)
        tcu::clear(data.getAccess(), color.asUint());
    else if (textureChannelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER)
        tcu::clear(data.getAccess(), color.asInt());
    else
        tcu::clear(data.getAccess(), color);

    m_gl.texImage2D(GL_TEXTURE_2D, 0, m_format, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0, m_transferFmt.format,
                    m_transferFmt.dataType, data.getAccess().getDataPtr());
    m_gl.bindTexture(GL_TEXTURE_2D, 0);
}

void FramebufferFetchTestCase::verifyRenderbuffer(TestLog &log, const tcu::TextureFormat &format,
                                                  const tcu::TextureLevel &reference, const tcu::TextureLevel &result)
{
    m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

    switch (tcu::getTextureChannelClass(format.type))
    {
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
    {
        const string name          = "Renderbuffer";
        const string desc          = "Compare renderbuffer (floating_point)";
        const tcu::UVec4 threshold = getFloatULPThreshold(format, result.getFormat());

        if (!tcu::floatUlpThresholdCompare(log, name.c_str(), desc.c_str(), reference, result, threshold,
                                           tcu::COMPARE_LOG_RESULT))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        break;
    }

    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
    {
        const string name = "Renderbuffer";
        const string desc = "Compare renderbuffer (integer)";
        const tcu::UVec4 threshold(1, 1, 1, 1);

        if (!tcu::intThresholdCompare(log, name.c_str(), desc.c_str(), reference, result, threshold,
                                      tcu::COMPARE_LOG_RESULT))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        break;
    }

    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    {
        const string name         = "Renderbuffer";
        const string desc         = "Compare renderbuffer (fixed point)";
        const tcu::Vec4 threshold = getFixedPointFormatThreshold(format, result.getFormat());

        if (!tcu::floatThresholdCompare(log, name.c_str(), desc.c_str(), reference, result, threshold,
                                        tcu::COMPARE_LOG_RESULT))
            m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");

        break;
    }

    default:
    {
        DE_ASSERT(false);
        m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Fail");
    }
    }
}

void FramebufferFetchTestCase::genUniformColor(const tcu::Vec4 &color)
{
    const GLuint colorLocation = m_gl.getUniformLocation(m_program->getProgram(), "u_color");

    switch (tcu::getTextureChannelClass(m_texFmt.type))
    {
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
    {
        m_gl.uniform4uiv(colorLocation, 1, color.asUint().getPtr());
        break;
    }

    case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
    {
        m_gl.uniform4iv(colorLocation, 1, color.asInt().getPtr());
        break;
    }
    case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
    case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
    {
        m_gl.uniform4fv(colorLocation, 1, color.asFloat().getPtr());
        break;
    }
    default:
        DE_ASSERT(false);
    }

    GLU_EXPECT_NO_ERROR(m_gl.getError(), "genUniformColor()");
}

void FramebufferFetchTestCase::render(void)
{
    const GLfloat coords[] = {
        -1.0f, -1.0f, +1.0f, -1.0f, +1.0f, +1.0f, -1.0f, +1.0f,
    };

    const GLushort indices[] = {
        0, 1, 2, 2, 3, 0,
    };

    GLuint vaoID;
    m_gl.genVertexArrays(1, &vaoID);
    m_gl.bindVertexArray(vaoID);

    const GLuint coordLocation = m_gl.getAttribLocation(m_program->getProgram(), "a_position");

    m_gl.viewport(0, 0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    glu::Buffer coordinatesBuffer(m_context.getRenderContext());
    glu::Buffer elementsBuffer(m_context.getRenderContext());

    m_gl.bindBuffer(GL_ARRAY_BUFFER, *coordinatesBuffer);
    m_gl.bufferData(GL_ARRAY_BUFFER, (GLsizeiptr)sizeof(coords), coords, GL_STATIC_DRAW);
    m_gl.enableVertexAttribArray(coordLocation);
    m_gl.vertexAttribPointer(coordLocation, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

    m_gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, *elementsBuffer);
    m_gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)sizeof(indices), &indices[0], GL_STATIC_DRAW);

    m_gl.drawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, DE_NULL);
    GLU_EXPECT_NO_ERROR(m_gl.getError(), "render()");

    m_gl.deleteVertexArrays(1, &vaoID);
}

// Test description:
// - Attach texture containing solid color to framebuffer.
// - Draw full quad covering the entire viewport.
// - Sum framebuffer read color with passed in uniform color.
// - Compare resulting surface with reference.

class TextureFormatTestCase : public FramebufferFetchTestCase
{
public:
    TextureFormatTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~TextureFormatTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    tcu::TextureLevel genReferenceTexture(const tcu::Vec4 &fbColor, const tcu::Vec4 &uniformColor);
};

TextureFormatTestCase::TextureFormatTestCase(Context &context, const char *name, const char *desc, uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
{
}

tcu::TextureLevel TextureFormatTestCase::genReferenceTexture(const tcu::Vec4 &fbColor, const tcu::Vec4 &uniformColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);
    tcu::TextureChannelClass textureChannelClass = tcu::getTextureChannelClass(m_texFmt.type);

    tcu::Vec4 formatMaxValue = getTextureFormatInfo(m_texFmt).valueMax;
    tcu::Vec4 formatMinValue = getTextureFormatInfo(m_texFmt).valueMin;

    if (textureChannelClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER)
    {
        tcu::clear(reference.getAccess(), tcu::clamp(fbColor.asUint() + uniformColor.asUint(), formatMinValue.asUint(),
                                                     formatMaxValue.asUint()));
    }
    else if (textureChannelClass == tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER)
    {
        tcu::IVec4 clearColor;

        // Calculate using 64 bits to avoid signed integer overflow.
        for (int i = 0; i < 4; i++)
            clearColor[i] = static_cast<int>(
                (static_cast<int64_t>(fbColor.asInt()[i]) + static_cast<int64_t>(uniformColor.asInt()[i])) &
                0xffffffff);

        tcu::clear(reference.getAccess(), clearColor);
    }
    else
    {
        if (tcu::isSRGB(m_texFmt))
        {
            const tcu::Vec4 fragmentColor =
                tcu::clamp(tcu::sRGBToLinear(fbColor) + uniformColor, formatMinValue, formatMaxValue);
            tcu::clear(reference.getAccess(), tcu::linearToSRGB(fragmentColor));
        }
        else
        {
            tcu::clear(reference.getAccess(), tcu::clamp(fbColor + uniformColor, formatMinValue, formatMaxValue));
        }
    }

    return reference;
}

TextureFormatTestCase::IterateResult TextureFormatTestCase::iterate(void)
{
    const tcu::Vec4 uniformColor = scaleColorValue(m_texFmt, tcu::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
    const tcu::Vec4 fbColor      = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f));

    tcu::TextureLevel reference = genReferenceTexture(fbColor, uniformColor);
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    genFramebufferWithTexture(fbColor);
    genUniformColor(uniformColor);
    render();

    glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
    verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

    return STOP;
}

// Test description:
// - Attach multiple textures containing solid colors to framebuffer.
// - Draw full quad covering the entire viewport.
// - For each render target sum framebuffer read color with passed in uniform color.
// - Compare resulting surfaces with references.

class MultipleRenderTargetsTestCase : public FramebufferFetchTestCase
{
public:
    MultipleRenderTargetsTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~MultipleRenderTargetsTestCase(void);

    IterateResult iterate(void);
    void deinit(void);

private:
    void genFramebufferWithTextures(const vector<tcu::Vec4> &colors);
    void genAttachmentTextures(const vector<tcu::Vec4> &colors);
    tcu::TextureLevel genReferenceTexture(const tcu::Vec4 &fbColor, const tcu::Vec4 &uniformColor);
    glu::ProgramSources genShaderSources(void);

    enum
    {
        MAX_COLOR_BUFFERS = 4
    };

    GLuint m_texColorBuffers[MAX_COLOR_BUFFERS];
    GLenum m_colorBuffers[MAX_COLOR_BUFFERS];
};

MultipleRenderTargetsTestCase::MultipleRenderTargetsTestCase(Context &context, const char *name, const char *desc,
                                                             uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
    , m_texColorBuffers()
{
    m_colorBuffers[0] = GL_COLOR_ATTACHMENT0;
    m_colorBuffers[1] = GL_COLOR_ATTACHMENT1;
    m_colorBuffers[2] = GL_COLOR_ATTACHMENT2;
    m_colorBuffers[3] = GL_COLOR_ATTACHMENT3;
}

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

void MultipleRenderTargetsTestCase::deinit(void)
{
    // Clean up texture data
    for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_texColorBuffers); ++i)
    {
        if (m_texColorBuffers[i])
            m_context.getRenderContext().getFunctions().deleteTextures(1, &m_texColorBuffers[i]);
    }

    FramebufferFetchTestCase::deinit();
}

void MultipleRenderTargetsTestCase::genFramebufferWithTextures(const vector<tcu::Vec4> &colors)
{
    m_gl.genFramebuffers(1, &m_framebuffer);
    m_gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

    genAttachmentTextures(colors);

    for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_texColorBuffers); ++i)
        m_gl.framebufferTexture2D(GL_FRAMEBUFFER, m_colorBuffers[i], GL_TEXTURE_2D, m_texColorBuffers[i], 0);

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

    m_gl.drawBuffers((glw::GLsizei)MAX_COLOR_BUFFERS, &m_colorBuffers[0]);
    GLU_EXPECT_NO_ERROR(m_gl.getError(), "genFramebufferWithTextures()");
}

void MultipleRenderTargetsTestCase::genAttachmentTextures(const vector<tcu::Vec4> &colors)
{
    tcu::TextureLevel data(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                           VIEWPORT_HEIGHT, 1);

    m_gl.genTextures(MAX_COLOR_BUFFERS, m_texColorBuffers);

    for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_texColorBuffers); ++i)
    {
        m_gl.bindTexture(GL_TEXTURE_2D, m_texColorBuffers[i]);

        m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
        m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, m_isFilterable ? GL_LINEAR : GL_NEAREST);
        m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, m_isFilterable ? GL_LINEAR : GL_NEAREST);

        clear(data.getAccess(), colors[i]);
        m_gl.texImage2D(GL_TEXTURE_2D, 0, m_format, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0, m_transferFmt.format,
                        m_transferFmt.dataType, data.getAccess().getDataPtr());
    }

    m_gl.bindTexture(GL_TEXTURE_2D, 0);
    GLU_EXPECT_NO_ERROR(m_gl.getError(), "genAttachmentTextures()");
}

tcu::TextureLevel MultipleRenderTargetsTestCase::genReferenceTexture(const tcu::Vec4 &fbColor,
                                                                     const tcu::Vec4 &uniformColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);
    tcu::clear(reference.getAccess(), fbColor + uniformColor);

    return reference;
}

glu::ProgramSources MultipleRenderTargetsTestCase::genShaderSources(void)
{
    const string vecType = getColorOutputType(m_texFmt);
    std::ostringstream fragShaderSource;

    fragShaderSource << "#version 310 es\n"
                     << "#extension GL_EXT_shader_framebuffer_fetch : require\n"
                     << "layout(location = 0) inout highp " << vecType << " o_color0;\n"
                     << "layout(location = 1) inout highp " << vecType << " o_color1;\n"
                     << "layout(location = 2) inout highp " << vecType << " o_color2;\n"
                     << "layout(location = 3) inout highp " << vecType << " o_color3;\n"
                     << "uniform highp " << vecType << " u_color;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    o_color0 += u_color;\n"
                     << "    o_color1 += u_color;\n"
                     << "    o_color2 += u_color;\n"
                     << "    o_color3 += u_color;\n"
                     << "}\n";

    return glu::makeVtxFragSources(genPassThroughVertSource(), fragShaderSource.str());
}

MultipleRenderTargetsTestCase::IterateResult MultipleRenderTargetsTestCase::iterate(void)
{
    const tcu::Vec4 uniformColor = scaleColorValue(m_texFmt, tcu::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    vector<tcu::Vec4> colors;
    colors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.9f, 0.0f, 0.0f, 1.0f)));
    colors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.0f, 0.9f, 0.0f, 1.0f)));
    colors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.0f, 0.0f, 0.9f, 1.0f)));
    colors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.0f, 0.9f, 0.9f, 1.0f)));

    genFramebufferWithTextures(colors);
    genUniformColor(uniformColor);
    render();

    for (int i = 0; i < DE_LENGTH_OF_ARRAY(m_colorBuffers); ++i)
    {
        tcu::TextureLevel reference = genReferenceTexture(colors[i], uniformColor);

        m_gl.readBuffer(m_colorBuffers[i]);
        glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
        verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);
    }

    return STOP;
}

// Test description:
// - Same as TextureFormatTestCase except uses built-in fragment output of ES 2.0

class LastFragDataTestCase : public FramebufferFetchTestCase
{
public:
    LastFragDataTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~LastFragDataTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    glu::ProgramSources genShaderSources(void);
    tcu::TextureLevel genReferenceTexture(const tcu::Vec4 &fbColor, const tcu::Vec4 &uniformColor);
};

LastFragDataTestCase::LastFragDataTestCase(Context &context, const char *name, const char *desc, uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
{
}

glu::ProgramSources LastFragDataTestCase::genShaderSources(void)
{
    const string vecType = getColorOutputType(m_texFmt);
    std::ostringstream vertShaderSource;
    std::ostringstream fragShaderSource;

    vertShaderSource << "#version 100\n"
                     << "attribute vec4 a_position;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    gl_Position = a_position;\n"
                     << "}\n";

    fragShaderSource << "#version 100\n"
                     << "#extension GL_EXT_shader_framebuffer_fetch : require\n"
                     << "uniform highp " << vecType << " u_color;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    gl_FragColor = u_color + gl_LastFragData[0];\n"
                     << "}\n";

    return glu::makeVtxFragSources(vertShaderSource.str(), fragShaderSource.str());
}

tcu::TextureLevel LastFragDataTestCase::genReferenceTexture(const tcu::Vec4 &fbColor, const tcu::Vec4 &uniformColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);
    tcu::clear(reference.getAccess(), fbColor + uniformColor);

    return reference;
}

LastFragDataTestCase::IterateResult LastFragDataTestCase::iterate(void)
{
    const tcu::Vec4 uniformColor = scaleColorValue(m_texFmt, tcu::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
    const tcu::Vec4 fbColor      = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f));

    tcu::TextureLevel reference = genReferenceTexture(fbColor, uniformColor);
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    genFramebufferWithTexture(fbColor);
    genUniformColor(uniformColor);
    render();

    glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
    verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

    return STOP;
}

// Test description:
// - Attach texture containing solid color to framebuffer.
// - Create one 2D texture for sampler with a grid pattern
// - Draw full screen quad covering the entire viewport.
// - Sum color values taken from framebuffer texture and sampled texture
// - Compare resulting surface with reference.

class TexelFetchTestCase : public FramebufferFetchTestCase
{
public:
    TexelFetchTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~TexelFetchTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    glu::ProgramSources genShaderSources(void);
    tcu::TextureLevel genReferenceTexture(const tcu::Vec4 &colorEven, const tcu::Vec4 &colorOdd,
                                          const tcu::Vec4 &fbColor);
    void genSamplerTexture(const tcu::Vec4 &colorEven, const tcu::Vec4 &colorOdd);

    GLuint m_samplerTexture;
};

TexelFetchTestCase::TexelFetchTestCase(Context &context, const char *name, const char *desc, uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
    , m_samplerTexture(0)
{
}

void TexelFetchTestCase::genSamplerTexture(const tcu::Vec4 &colorEven, const tcu::Vec4 &colorOdd)
{
    tcu::TextureLevel data(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                           VIEWPORT_HEIGHT, 1);

    m_gl.activeTexture(GL_TEXTURE1);

    m_gl.genTextures(1, &m_samplerTexture);
    m_gl.bindTexture(GL_TEXTURE_2D, m_texColorBuffer);
    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    m_gl.texParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    tcu::fillWithGrid(data.getAccess(), 8, colorEven, colorOdd);

    m_gl.texImage2D(GL_TEXTURE_2D, 0, m_format, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0, m_transferFmt.format,
                    m_transferFmt.dataType, data.getAccess().getDataPtr());
    m_gl.bindTexture(GL_TEXTURE_2D, 0);

    const GLuint samplerLocation = m_gl.getUniformLocation(m_program->getProgram(), "u_sampler");
    m_gl.uniform1i(samplerLocation, 1);

    GLU_EXPECT_NO_ERROR(m_gl.getError(), "genSamplerTexture()");
}

glu::ProgramSources TexelFetchTestCase::genShaderSources(void)
{
    const string vecType = getColorOutputType(m_texFmt);
    std::ostringstream fragShaderSource;

    fragShaderSource << "#version 310 es\n"
                     << "#extension GL_EXT_shader_framebuffer_fetch : require\n"
                     << "layout(location = 0) inout highp " << vecType << " o_color;\n"
                     << "\n"
                     << "uniform sampler2D u_sampler;\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    o_color += texelFetch(u_sampler, ivec2(gl_FragCoord), 0);\n"
                     << "}\n";

    return glu::makeVtxFragSources(genPassThroughVertSource(), fragShaderSource.str());
}

tcu::TextureLevel TexelFetchTestCase::genReferenceTexture(const tcu::Vec4 &colorEven, const tcu::Vec4 &colorOdd,
                                                          const tcu::Vec4 &fbColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);
    tcu::fillWithGrid(reference.getAccess(), 8, colorEven + fbColor, colorOdd + fbColor);

    return reference;
}

TexelFetchTestCase::IterateResult TexelFetchTestCase::iterate(void)
{
    const tcu::Vec4 fbColor   = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.0f, 0.5f, 1.0f));
    const tcu::Vec4 colorEven = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.5f, 0.0f, 1.0f));
    const tcu::Vec4 colorOdd  = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.0f, 0.5f, 1.0f));

    genSamplerTexture(colorEven, colorOdd);
    tcu::TextureLevel reference = genReferenceTexture(colorEven, colorOdd, fbColor);
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    genFramebufferWithTexture(fbColor);
    render();

    glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
    verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

    // cleanup
    m_gl.deleteTextures(1, &m_samplerTexture);

    return STOP;
}

// Test description:
// - Attach texture containing solid color to framebuffer.
// - Draw full screen quad covering the entire viewport.
// - Multiple assignments are made to the output color for fragments on the right vertical half of the screen.
// - A single assignment is made to the output color for fragments on the left vertical centre of the screen.
// - Values are calculated using the sum of the passed in uniform color and the previous framebuffer color.
// - Compare resulting surface with reference.

class MultipleAssignmentTestCase : public FramebufferFetchTestCase
{
public:
    MultipleAssignmentTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~MultipleAssignmentTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    glu::ProgramSources genShaderSources(void);
    tcu::TextureLevel genReferenceTexture(const tcu::Vec4 &fbColor, const tcu::Vec4 &uniformColor);
};

MultipleAssignmentTestCase::MultipleAssignmentTestCase(Context &context, const char *name, const char *desc,
                                                       uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
{
}

glu::ProgramSources MultipleAssignmentTestCase::genShaderSources(void)
{
    const string vecType = getColorOutputType(m_texFmt);
    std::ostringstream vertShaderSource;
    std::ostringstream fragShaderSource;

    vertShaderSource << "#version 310 es\n"
                     << "in highp vec4 a_position;\n"
                     << "out highp vec4 v_position;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    gl_Position = a_position;\n"
                     << "    v_position  = gl_Position;\n"
                     << "}\n";

    fragShaderSource << "#version 310 es\n"
                     << "#extension GL_EXT_shader_framebuffer_fetch : require\n"
                     << "in highp vec4 v_position;\n"
                     << "layout(location = 0) inout highp " << vecType << " o_color;\n"
                     << "uniform highp " << vecType << " u_color;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    if (v_position.x > 0.0f)\n"
                     << "        o_color += u_color;\n"
                     << "\n"
                     << "    o_color += u_color;\n"
                     << "}\n";

    return glu::makeVtxFragSources(vertShaderSource.str(), fragShaderSource.str());
}

tcu::TextureLevel MultipleAssignmentTestCase::genReferenceTexture(const tcu::Vec4 &fbColor,
                                                                  const tcu::Vec4 &uniformColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);

    int width  = reference.getAccess().getWidth();
    int height = reference.getAccess().getHeight();
    int left   = width / 2;
    int top    = height / 2;

    tcu::Vec4 compositeColor(uniformColor * 2.0f);

    tcu::clear(getSubregion(reference.getAccess(), left, 0, 0, width - left, top, 1), fbColor + compositeColor);
    tcu::clear(getSubregion(reference.getAccess(), 0, top, 0, left, height - top, 1), fbColor + uniformColor);
    tcu::clear(getSubregion(reference.getAccess(), left, top, 0, width - left, height - top, 1),
               fbColor + compositeColor);
    tcu::clear(getSubregion(reference.getAccess(), 0, 0, 0, left, top, 1), fbColor + uniformColor);

    return reference;
}

MultipleAssignmentTestCase::IterateResult MultipleAssignmentTestCase::iterate(void)
{
    const tcu::Vec4 fbColor      = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f));
    const tcu::Vec4 uniformColor = scaleColorValue(m_texFmt, tcu::Vec4(0.25f, 0.0f, 0.0f, 1.0f));

    tcu::TextureLevel reference = genReferenceTexture(fbColor, uniformColor);
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    genFramebufferWithTexture(fbColor);
    genUniformColor(uniformColor);
    render();

    glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
    verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

    return STOP;
}

// Test description:
// - Attach texture containing grid pattern to framebuffer.
// - Using framebuffer reads discard odd squares in the grid.
// - The even squares framebuffer color is added to the passed in uniform color.

class FragmentDiscardTestCase : public FramebufferFetchTestCase
{
public:
    FragmentDiscardTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~FragmentDiscardTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    glu::ProgramSources genShaderSources(void);
    void genFramebufferWithGrid(const tcu::Vec4 &fbColorEven, const tcu::Vec4 &fbColorOdd);
    tcu::TextureLevel genReferenceTexture(const tcu::Vec4 &fbColorEven, const tcu::Vec4 &fbColorOdd);
};

FragmentDiscardTestCase::FragmentDiscardTestCase(Context &context, const char *name, const char *desc, uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
{
}

glu::ProgramSources FragmentDiscardTestCase::genShaderSources(void)
{
    const string vecType = getColorOutputType(m_texFmt);
    std::ostringstream fragShaderSource;

    fragShaderSource << "#version 310 es\n"
                     << "#extension GL_EXT_shader_framebuffer_fetch : require\n"
                     << "layout(location = 0) inout highp " << vecType << " o_color;\n"
                     << "uniform highp " << vecType << " u_color;\n"
                     << "\n"
                     << "void main (void)\n"
                     << "{\n"
                     << "    const highp float threshold = 0.0005f;\n"
                     << "    bool valuesEqual = all(lessThan(abs(o_color - u_color), vec4(threshold)));\n\n"
                     << "    if (valuesEqual)\n"
                     << "        o_color += u_color;\n"
                     << "    else\n"
                     << "        discard;\n"
                     << "}\n";

    return glu::makeVtxFragSources(genPassThroughVertSource(), fragShaderSource.str());
}

void FragmentDiscardTestCase::genFramebufferWithGrid(const tcu::Vec4 &fbColorEven, const tcu::Vec4 &fbColorOdd)
{
    tcu::TextureLevel data(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                           VIEWPORT_HEIGHT, 1);

    m_gl.genFramebuffers(1, &m_framebuffer);
    m_gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

    m_gl.genTextures(1, &m_texColorBuffer);
    m_gl.bindTexture(GL_TEXTURE_2D, m_texColorBuffer);

    tcu::fillWithGrid(data.getAccess(), 8, fbColorEven, fbColorOdd);

    m_gl.texImage2D(GL_TEXTURE_2D, 0, m_format, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0, m_transferFmt.format,
                    m_transferFmt.dataType, data.getAccess().getDataPtr());
    m_gl.bindTexture(GL_TEXTURE_2D, 0);

    m_gl.framebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texColorBuffer, 0);
    TCU_CHECK(m_gl.checkFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE);
}

tcu::TextureLevel FragmentDiscardTestCase::genReferenceTexture(const tcu::Vec4 &fbColorEven,
                                                               const tcu::Vec4 &fbColorOdd)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);
    tcu::fillWithGrid(reference.getAccess(), 8, fbColorEven + fbColorEven, fbColorOdd);

    return reference;
}

FragmentDiscardTestCase::IterateResult FragmentDiscardTestCase::iterate(void)
{
    const tcu::Vec4 fbColorEven = scaleColorValue(m_texFmt, tcu::Vec4(0.5f, 0.0f, 1.0f, 1.0f));
    const tcu::Vec4 fbColorOdd  = scaleColorValue(m_texFmt, tcu::Vec4(0.0f, 1.0f, 1.0f, 1.0f));

    tcu::TextureLevel reference = genReferenceTexture(fbColorEven, fbColorOdd);
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
    genFramebufferWithGrid(fbColorEven, fbColorOdd);

    genUniformColor(fbColorEven);
    render();

    glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
    verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

    return STOP;
}

// Test description:
// - Create 2D texture array containing three mipmaps.
// - Each mipmap level is assigned a different color.
// - Attach single mipmap level to framebuffer and draw full screen quad.
// - Sum framebuffer read color with passed in uniform color.
// - Compare resulting surface with reference.
// - Repeat for subsequent mipmap levels.

class TextureLevelTestCase : public FramebufferFetchTestCase
{
public:
    TextureLevelTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~TextureLevelTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    void create2DTextureArrayMipMaps(const vector<tcu::Vec4> &colors);
    tcu::TextureLevel genReferenceTexture(int level, const vector<tcu::Vec4> &colors, const tcu::Vec4 &uniformColor);
    void genReferenceMipmap(const tcu::Vec4 &color, tcu::TextureLevel &reference);
};

TextureLevelTestCase::TextureLevelTestCase(Context &context, const char *name, const char *desc, uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
{
}

void TextureLevelTestCase::create2DTextureArrayMipMaps(const vector<tcu::Vec4> &colors)
{
    int numLevels = (int)colors.size();
    tcu::TextureLevel levelData(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType));

    m_gl.genTextures(1, &m_texColorBuffer);
    m_gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texColorBuffer);

    m_gl.texImage3D(GL_TEXTURE_2D_ARRAY, 0, m_format, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 1, 0, m_transferFmt.format,
                    m_transferFmt.dataType, DE_NULL);
    m_gl.generateMipmap(GL_TEXTURE_2D_ARRAY);

    for (int level = 0; level < numLevels; level++)
    {
        int levelW = de::max(1, VIEWPORT_WIDTH >> level);
        int levelH = de::max(1, VIEWPORT_HEIGHT >> level);

        levelData.setSize(levelW, levelH, 1);

        clear(levelData.getAccess(), colors[level]);
        m_gl.texImage3D(GL_TEXTURE_2D_ARRAY, level, m_format, levelW, levelH, 1, 0, m_transferFmt.format,
                        m_transferFmt.dataType, levelData.getAccess().getDataPtr());
    }

    m_gl.bindTexture(GL_TEXTURE_2D_ARRAY, 0);
    GLU_EXPECT_NO_ERROR(m_gl.getError(), "create2DTextureArrayMipMaps()");
}

tcu::TextureLevel TextureLevelTestCase::genReferenceTexture(int level, const vector<tcu::Vec4> &colors,
                                                            const tcu::Vec4 &uniformColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType),
                                VIEWPORT_WIDTH >> level, VIEWPORT_HEIGHT >> level, 1);

    genReferenceMipmap(colors[level] + uniformColor, reference);

    return reference;
}

void TextureLevelTestCase::genReferenceMipmap(const tcu::Vec4 &color, tcu::TextureLevel &reference)
{
    const int width  = reference.getAccess().getWidth();
    const int height = reference.getAccess().getHeight();
    const int left   = width / 2;
    const int top    = height / 2;

    clear(getSubregion(reference.getAccess(), left, 0, 0, width - left, top, 1), color);
    clear(getSubregion(reference.getAccess(), 0, top, 0, left, height - top, 1), color);
    clear(getSubregion(reference.getAccess(), left, top, 0, width - left, height - top, 1), color);
    clear(getSubregion(reference.getAccess(), 0, 0, 0, left, top, 1), color);
}

TextureLevelTestCase::IterateResult TextureLevelTestCase::iterate(void)
{
    const tcu::Vec4 uniformColor = scaleColorValue(m_texFmt, tcu::Vec4(0.1f, 0.0f, 0.0f, 1.0f));
    vector<tcu::Vec4> levelColors;

    levelColors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.4f, 0.0f, 0.0f, 1.0f)));
    levelColors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.2f, 0.0f, 0.0f, 1.0f)));
    levelColors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)));

    m_gl.genFramebuffers(1, &m_framebuffer);
    m_gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

    create2DTextureArrayMipMaps(levelColors);

    // attach successive mipmap layers to framebuffer and render
    for (int level = 0; level < (int)levelColors.size(); ++level)
    {
        std::ostringstream name, desc;
        name << "Level " << level;
        desc << "Mipmap level " << level;

        const tcu::ScopedLogSection section(m_testCtx.getLog(), name.str(), desc.str());
        tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH >> level, VIEWPORT_HEIGHT >> level);
        tcu::TextureLevel reference = genReferenceTexture(level, levelColors, uniformColor);

        m_gl.framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texColorBuffer, level, 0);

        genUniformColor(uniformColor);
        render();

        glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
        verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

        if (m_testCtx.getTestResult() != QP_TEST_RESULT_PASS)
            return STOP;
    }

    return STOP;
}

class TextureLayerTestCase : public FramebufferFetchTestCase
{
public:
    TextureLayerTestCase(Context &context, const char *name, const char *desc, uint32_t format);
    ~TextureLayerTestCase(void)
    {
    }

    IterateResult iterate(void);

private:
    void create2DTextureArrayLayers(const vector<tcu::Vec4> &colors);
    tcu::TextureLevel genReferenceTexture(int layer, const vector<tcu::Vec4> &colors, const tcu::Vec4 &uniformColor);
};

TextureLayerTestCase::TextureLayerTestCase(Context &context, const char *name, const char *desc, uint32_t format)
    : FramebufferFetchTestCase(context, name, desc, format)
{
}

void TextureLayerTestCase::create2DTextureArrayLayers(const vector<tcu::Vec4> &colors)
{
    int numLayers = (int)colors.size();
    tcu::TextureLevel layerData(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType));

    m_gl.genTextures(1, &m_texColorBuffer);
    m_gl.bindTexture(GL_TEXTURE_2D_ARRAY, m_texColorBuffer);
    m_gl.texStorage3D(GL_TEXTURE_2D_ARRAY, 1, m_format, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, numLayers);
    m_gl.bindImageTexture(0, m_texColorBuffer, 0, GL_FALSE, 0, GL_READ_ONLY, m_format);

    layerData.setSize(VIEWPORT_WIDTH, VIEWPORT_HEIGHT, numLayers);

    for (int layer = 0; layer < numLayers; layer++)
    {
        clear(layerData.getAccess(), colors[layer]);
        m_gl.texSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, layer, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 1,
                           m_transferFmt.format, m_transferFmt.dataType, layerData.getAccess().getDataPtr());
    }

    m_gl.bindTexture(GL_TEXTURE_2D_ARRAY, 0);
    GLU_EXPECT_NO_ERROR(m_gl.getError(), "create2DTextureArrayLayers()");
}

tcu::TextureLevel TextureLayerTestCase::genReferenceTexture(int layer, const vector<tcu::Vec4> &colors,
                                                            const tcu::Vec4 &uniformColor)
{
    tcu::TextureLevel reference(glu::mapGLTransferFormat(m_transferFmt.format, m_transferFmt.dataType), VIEWPORT_WIDTH,
                                VIEWPORT_HEIGHT, 1);
    clear(reference.getAccess(), colors[layer] + uniformColor);

    return reference;
}

// Test description
// - Create 2D texture array containing three layers.
// - Each layer is assigned a different color.
// - Attach single layer to framebuffer and draw full screen quad.
// - Sum framebuffer read color with passed in uniform color.
// - Compare resulting surface with reference.
// - Repeat for subsequent texture layers.

TextureLayerTestCase::IterateResult TextureLayerTestCase::iterate(void)
{
    const tcu::Vec4 uniformColor = scaleColorValue(m_texFmt, tcu::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
    tcu::TextureLevel result(getReadPixelFormat(m_texFmt), VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
    vector<tcu::Vec4> layerColors;

    layerColors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.4f, 0.0f, 0.0f, 1.0f)));
    layerColors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.2f, 0.0f, 0.0f, 1.0f)));
    layerColors.push_back(scaleColorValue(m_texFmt, tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f)));

    m_gl.genFramebuffers(1, &m_framebuffer);
    m_gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

    create2DTextureArrayLayers(layerColors);

    for (int layer = 0; layer < (int)layerColors.size(); ++layer)
    {
        std::ostringstream name, desc;
        name << "Layer " << layer;
        desc << "Layer " << layer;

        const tcu::ScopedLogSection section(m_testCtx.getLog(), name.str(), desc.str());
        tcu::TextureLevel reference = genReferenceTexture(layer, layerColors, uniformColor);

        m_gl.framebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, m_texColorBuffer, 0, layer);

        genUniformColor(uniformColor);
        render();

        glu::readPixels(m_context.getRenderContext(), 0, 0, result.getAccess());
        verifyRenderbuffer(m_testCtx.getLog(), m_texFmt, reference, result);

        if (m_testCtx.getTestResult() != QP_TEST_RESULT_PASS)
            return STOP;
    }

    return STOP;
}

} // namespace

ShaderFramebufferFetchTests::ShaderFramebufferFetchTests(Context &context)
    : TestCaseGroup(context, "framebuffer_fetch", "GL_EXT_shader_framebuffer_fetch tests")
{
}

ShaderFramebufferFetchTests::~ShaderFramebufferFetchTests(void)
{
}

void ShaderFramebufferFetchTests::init(void)
{
    tcu::TestCaseGroup *const basicTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "basic", "Basic framebuffer shader fetch tests");
    tcu::TestCaseGroup *const framebufferFormatTestGroup =
        new tcu::TestCaseGroup(m_testCtx, "framebuffer_format", "Texture render target formats tests");

    // basic
    {
        basicTestGroup->addChild(new TexelFetchTestCase(
            m_context, "texel_fetch", "Framebuffer fetches in conjunction with shader texel fetches", GL_RGBA8));
        basicTestGroup->addChild(new LastFragDataTestCase(
            m_context, "last_frag_data", "Framebuffer fetches with built-in fragment output of ES 2.0", GL_RGBA8));
        basicTestGroup->addChild(new FragmentDiscardTestCase(
            m_context, "fragment_discard", "Framebuffer fetches in combination with fragment discards", GL_RGBA8));
        basicTestGroup->addChild(new MultipleAssignmentTestCase(
            m_context, "multiple_assignment", "Multiple assignments to fragment color inout", GL_RGBA8));
        basicTestGroup->addChild(new MultipleRenderTargetsTestCase(
            m_context, "multiple_render_targets",
            "Framebuffer fetches used in combination with multiple render targets", GL_RGBA8));
        basicTestGroup->addChild(
            new TextureLevelTestCase(m_context, "framebuffer_texture_level",
                                     "Framebuffer fetches with individual texture render target mipmaps", GL_RGBA8));
        basicTestGroup->addChild(
            new TextureLayerTestCase(m_context, "framebuffer_texture_layer",
                                     "Framebuffer fetches with individual texture render target layers", GL_RGBA8));
    }

    // framebuffer formats
    {
        static const uint32_t colorFormats[] = {
            // RGBA formats
            GL_RGBA32I, GL_RGBA32UI, GL_RGBA16I, GL_RGBA16UI, GL_RGBA8, GL_RGBA8I, GL_RGBA8UI, GL_SRGB8_ALPHA8,
            GL_RGB10_A2, GL_RGB10_A2UI, GL_RGBA4, GL_RGB5_A1,

            // RGB formats
            GL_RGB8, GL_RGB565,

            // RG formats
            GL_RG32I, GL_RG32UI, GL_RG16I, GL_RG16UI, GL_RG8, GL_RG8I, GL_RG8UI,

            // R formats
            GL_R32I, GL_R32UI, GL_R16I, GL_R16UI, GL_R8, GL_R8I, GL_R8UI,

            // GL_EXT_color_buffer_float
            GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F, GL_RG32F, GL_RG16F, GL_R32F, GL_R16F,

            // GL_EXT_color_buffer_half_float
            GL_RGB16F};

        for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(colorFormats); ndx++)
            framebufferFormatTestGroup->addChild(new TextureFormatTestCase(
                m_context, getFormatName(colorFormats[ndx]),
                "Framebuffer fetches from texture attachments with varying formats", colorFormats[ndx]));
    }

    addChild(basicTestGroup);
    addChild(framebufferFormatTestGroup);
}

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