/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 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 Fragment shader output tests.
 *
 * \todo [2012-04-10 pyry] Missing:
 *  + non-contiguous attachments in framebuffer
 *//*--------------------------------------------------------------------*/

#include "es3fFragmentOutputTests.hpp"
#include "gluShaderUtil.hpp"
#include "gluShaderProgram.hpp"
#include "gluTextureUtil.hpp"
#include "gluStrUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuTexture.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuVector.hpp"
#include "tcuVectorUtil.hpp"
#include "tcuImageCompare.hpp"
#include "deRandom.hpp"
#include "deStringUtil.hpp"
#include "deMath.h"

// For getFormatName() \todo [pyry] Move to glu?
#include "es3fFboTestUtil.hpp"

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

namespace deqp
{
namespace gles3
{
namespace Functional
{

using FboTestUtil::getFormatName;
using FboTestUtil::getFramebufferReadFormat;
using std::string;
using std::vector;
using tcu::BVec4;
using tcu::IVec2;
using tcu::IVec4;
using tcu::TestLog;
using tcu::UVec2;
using tcu::UVec4;
using tcu::Vec2;
using tcu::Vec3;
using tcu::Vec4;

struct BufferSpec
{
    BufferSpec(void) : format(GL_NONE), width(0), height(0), samples(0)
    {
    }

    BufferSpec(uint32_t format_, int width_, int height_, int samples_)
        : format(format_)
        , width(width_)
        , height(height_)
        , samples(samples_)
    {
    }

    uint32_t format;
    int width;
    int height;
    int samples;
};

struct FragmentOutput
{
    FragmentOutput(void) : type(glu::TYPE_LAST), precision(glu::PRECISION_LAST), location(0), arrayLength(0)
    {
    }

    FragmentOutput(glu::DataType type_, glu::Precision precision_, int location_, int arrayLength_ = 0)
        : type(type_)
        , precision(precision_)
        , location(location_)
        , arrayLength(arrayLength_)
    {
    }

    glu::DataType type;
    glu::Precision precision;
    int location;
    int arrayLength; //!< 0 if not an array.
};

struct OutputVec
{
    vector<FragmentOutput> outputs;

    OutputVec &operator<<(const FragmentOutput &output)
    {
        outputs.push_back(output);
        return *this;
    }

    vector<FragmentOutput> toVec(void) const
    {
        return outputs;
    }
};

class FragmentOutputCase : public TestCase
{
public:
    FragmentOutputCase(Context &context, const char *name, const char *desc, const vector<BufferSpec> &fboSpec,
                       const vector<FragmentOutput> &outputs);
    ~FragmentOutputCase(void);

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

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

    vector<BufferSpec> m_fboSpec;
    vector<FragmentOutput> m_outputs;

    glu::ShaderProgram *m_program;
    uint32_t m_framebuffer;
    vector<uint32_t> m_renderbuffers;
};

FragmentOutputCase::FragmentOutputCase(Context &context, const char *name, const char *desc,
                                       const vector<BufferSpec> &fboSpec, const vector<FragmentOutput> &outputs)
    : TestCase(context, name, desc)
    , m_fboSpec(fboSpec)
    , m_outputs(outputs)
    , m_program(DE_NULL)
    , m_framebuffer(0)
{
}

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

static glu::ShaderProgram *createProgram(const glu::RenderContext &context, const vector<FragmentOutput> &outputs)
{
    std::ostringstream vtx;
    std::ostringstream frag;

    vtx << "#version 300 es\n"
        << "in highp vec4 a_position;\n";
    frag << "#version 300 es\n";

    // Input-output declarations.
    for (int outNdx = 0; outNdx < (int)outputs.size(); outNdx++)
    {
        const FragmentOutput &output = outputs[outNdx];
        bool isArray                 = output.arrayLength > 0;
        const char *typeName         = glu::getDataTypeName(output.type);
        const char *outputPrec       = glu::getPrecisionName(output.precision);
        bool isFloat                 = glu::isDataTypeFloatOrVec(output.type);
        const char *interp           = isFloat ? "smooth" : "flat";
        const char *interpPrec       = isFloat ? "highp" : outputPrec;

        if (isArray)
        {
            for (int elemNdx = 0; elemNdx < output.arrayLength; elemNdx++)
            {
                vtx << "in " << interpPrec << " " << typeName << " in" << outNdx << "_" << elemNdx << ";\n"
                    << interp << " out " << interpPrec << " " << typeName << " var" << outNdx << "_" << elemNdx
                    << ";\n";
                frag << interp << " in " << interpPrec << " " << typeName << " var" << outNdx << "_" << elemNdx
                     << ";\n";
            }
            frag << "layout(location = " << output.location << ") out " << outputPrec << " " << typeName << " out"
                 << outNdx << "[" << output.arrayLength << "];\n";
        }
        else
        {
            vtx << "in " << interpPrec << " " << typeName << " in" << outNdx << ";\n"
                << interp << " out " << interpPrec << " " << typeName << " var" << outNdx << ";\n";
            frag << interp << " in " << interpPrec << " " << typeName << " var" << outNdx << ";\n"
                 << "layout(location = " << output.location << ") out " << outputPrec << " " << typeName << " out"
                 << outNdx << ";\n";
        }
    }

    vtx << "\nvoid main()\n{\n";
    frag << "\nvoid main()\n{\n";

    vtx << "    gl_Position = a_position;\n";

    // Copy body
    for (int outNdx = 0; outNdx < (int)outputs.size(); outNdx++)
    {
        const FragmentOutput &output = outputs[outNdx];
        bool isArray                 = output.arrayLength > 0;

        if (isArray)
        {
            for (int elemNdx = 0; elemNdx < output.arrayLength; elemNdx++)
            {
                vtx << "\tvar" << outNdx << "_" << elemNdx << " = in" << outNdx << "_" << elemNdx << ";\n";
                frag << "\tout" << outNdx << "[" << elemNdx << "] = var" << outNdx << "_" << elemNdx << ";\n";
            }
        }
        else
        {
            vtx << "\tvar" << outNdx << " = in" << outNdx << ";\n";
            frag << "\tout" << outNdx << " = var" << outNdx << ";\n";
        }
    }

    vtx << "}\n";
    frag << "}\n";

    return new glu::ShaderProgram(context, glu::makeVtxFragSources(vtx.str(), frag.str()));
}

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

    // Check that all attachments are supported
    for (std::vector<BufferSpec>::const_iterator bufIter = m_fboSpec.begin(); bufIter != m_fboSpec.end(); ++bufIter)
    {
        if (!glu::isSizedFormatColorRenderable(m_context.getRenderContext(), m_context.getContextInfo(),
                                               bufIter->format))
            throw tcu::NotSupportedError("Unsupported attachment format");
    }

    DE_ASSERT(!m_program);
    m_program = createProgram(m_context.getRenderContext(), m_outputs);

    log << *m_program;
    if (!m_program->isOk())
        TCU_FAIL("Compile failed");

    // Print render target info to log.
    log << TestLog::Section("Framebuffer", "Framebuffer configuration");

    for (int ndx = 0; ndx < (int)m_fboSpec.size(); ndx++)
        log << TestLog::Message << "COLOR_ATTACHMENT" << ndx << ": " << glu::getTextureFormatStr(m_fboSpec[ndx].format)
            << ", " << m_fboSpec[ndx].width << "x" << m_fboSpec[ndx].height << ", " << m_fboSpec[ndx].samples
            << " samples" << TestLog::EndMessage;

    log << TestLog::EndSection;

    // Create framebuffer.
    m_renderbuffers.resize(m_fboSpec.size(), 0);
    gl.genFramebuffers(1, &m_framebuffer);
    gl.genRenderbuffers((int)m_renderbuffers.size(), &m_renderbuffers[0]);

    gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);

    for (int bufNdx = 0; bufNdx < (int)m_renderbuffers.size(); bufNdx++)
    {
        uint32_t rbo              = m_renderbuffers[bufNdx];
        const BufferSpec &bufSpec = m_fboSpec[bufNdx];
        uint32_t attachment       = GL_COLOR_ATTACHMENT0 + bufNdx;

        gl.bindRenderbuffer(GL_RENDERBUFFER, rbo);
        gl.renderbufferStorageMultisample(GL_RENDERBUFFER, bufSpec.samples, bufSpec.format, bufSpec.width,
                                          bufSpec.height);
        gl.framebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, rbo);
    }
    GLU_EXPECT_NO_ERROR(gl.getError(), "After framebuffer setup");

    uint32_t fboStatus = gl.checkFramebufferStatus(GL_FRAMEBUFFER);
    if (fboStatus == GL_FRAMEBUFFER_UNSUPPORTED)
        throw tcu::NotSupportedError("Framebuffer not supported", "", __FILE__, __LINE__);
    else if (fboStatus != GL_FRAMEBUFFER_COMPLETE)
        throw tcu::TestError(
            (string("Incomplete framebuffer: ") + glu::getFramebufferStatusStr(fboStatus).toString()).c_str(), "",
            __FILE__, __LINE__);

    gl.bindFramebuffer(GL_FRAMEBUFFER, 0);
    GLU_EXPECT_NO_ERROR(gl.getError(), "After init");
}

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

    if (m_framebuffer)
    {
        gl.deleteFramebuffers(1, &m_framebuffer);
        m_framebuffer = 0;
    }

    if (!m_renderbuffers.empty())
    {
        gl.deleteRenderbuffers((int)m_renderbuffers.size(), &m_renderbuffers[0]);
        m_renderbuffers.clear();
    }

    delete m_program;
    m_program = DE_NULL;
}

static IVec2 getMinSize(const vector<BufferSpec> &fboSpec)
{
    IVec2 minSize(0x7fffffff, 0x7fffffff);
    for (vector<BufferSpec>::const_iterator i = fboSpec.begin(); i != fboSpec.end(); i++)
    {
        minSize.x() = de::min(minSize.x(), i->width);
        minSize.y() = de::min(minSize.y(), i->height);
    }
    return minSize;
}

static int getNumInputVectors(const vector<FragmentOutput> &outputs)
{
    int numVecs = 0;
    for (vector<FragmentOutput>::const_iterator i = outputs.begin(); i != outputs.end(); i++)
        numVecs += (i->arrayLength > 0 ? i->arrayLength : 1);
    return numVecs;
}

static Vec2 getFloatRange(glu::Precision precision)
{
    // \todo [2012-04-09 pyry] Not quite the full ranges.
    static const Vec2 ranges[] = {Vec2(-2.0f, 2.0f), Vec2(-16000.0f, 16000.0f), Vec2(-1e35f, 1e35f)};
    DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
    DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
    return ranges[precision];
}

static IVec2 getIntRange(glu::Precision precision)
{
    static const IVec2 ranges[] = {IVec2(-(1 << 7), (1 << 7) - 1), IVec2(-(1 << 15), (1 << 15) - 1),
                                   IVec2(0x80000000, 0x7fffffff)};
    DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
    DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
    return ranges[precision];
}

static UVec2 getUintRange(glu::Precision precision)
{
    static const UVec2 ranges[] = {UVec2(0, (1 << 8) - 1), UVec2(0, (1 << 16) - 1), UVec2(0, 0xffffffffu)};
    DE_STATIC_ASSERT(DE_LENGTH_OF_ARRAY(ranges) == glu::PRECISION_LAST);
    DE_ASSERT(de::inBounds<int>(precision, 0, DE_LENGTH_OF_ARRAY(ranges)));
    return ranges[precision];
}

static inline Vec4 readVec4(const float *ptr, int numComponents)
{
    DE_ASSERT(numComponents >= 1);
    return Vec4(ptr[0], numComponents >= 2 ? ptr[1] : 0.0f, numComponents >= 3 ? ptr[2] : 0.0f,
                numComponents >= 4 ? ptr[3] : 0.0f);
}

static inline IVec4 readIVec4(const int *ptr, int numComponents)
{
    DE_ASSERT(numComponents >= 1);
    return IVec4(ptr[0], numComponents >= 2 ? ptr[1] : 0, numComponents >= 3 ? ptr[2] : 0,
                 numComponents >= 4 ? ptr[3] : 0);
}

static void renderFloatReference(const tcu::PixelBufferAccess &dst, int gridWidth, int gridHeight, int numComponents,
                                 const float *vertices)
{
    const bool isSRGB = tcu::isSRGB(dst.getFormat());
    const float cellW = (float)dst.getWidth() / (float)(gridWidth - 1);
    const float cellH = (float)dst.getHeight() / (float)(gridHeight - 1);

    for (int y = 0; y < dst.getHeight(); y++)
    {
        for (int x = 0; x < dst.getWidth(); x++)
        {
            const int cellX = de::clamp(deFloorFloatToInt32((float)x / cellW), 0, gridWidth - 2);
            const int cellY = de::clamp(deFloorFloatToInt32((float)y / cellH), 0, gridHeight - 2);
            const float xf  = ((float)x - (float)cellX * cellW + 0.5f) / cellW;
            const float yf  = ((float)y - (float)cellY * cellH + 0.5f) / cellH;
            const Vec4 v00  = readVec4(vertices + ((cellY + 0) * gridWidth + cellX + 0) * numComponents, numComponents);
            const Vec4 v01  = readVec4(vertices + ((cellY + 1) * gridWidth + cellX + 0) * numComponents, numComponents);
            const Vec4 v10  = readVec4(vertices + ((cellY + 0) * gridWidth + cellX + 1) * numComponents, numComponents);
            const Vec4 v11  = readVec4(vertices + ((cellY + 1) * gridWidth + cellX + 1) * numComponents, numComponents);
            const bool tri  = xf + yf >= 1.0f;
            const Vec4 &v0  = tri ? v11 : v00;
            const Vec4 &v1  = tri ? v01 : v10;
            const Vec4 &v2  = tri ? v10 : v01;
            const float s   = tri ? 1.0f - xf : xf;
            const float t   = tri ? 1.0f - yf : yf;
            const Vec4 color = v0 + (v1 - v0) * s + (v2 - v0) * t;

            dst.setPixel(isSRGB ? tcu::linearToSRGB(color) : color, x, y);
        }
    }
}

static void renderIntReference(const tcu::PixelBufferAccess &dst, int gridWidth, int gridHeight, int numComponents,
                               const int *vertices)
{
    float cellW = (float)dst.getWidth() / (float)(gridWidth - 1);
    float cellH = (float)dst.getHeight() / (float)(gridHeight - 1);

    for (int y = 0; y < dst.getHeight(); y++)
    {
        for (int x = 0; x < dst.getWidth(); x++)
        {
            int cellX = de::clamp(deFloorFloatToInt32((float)x / cellW), 0, gridWidth - 2);
            int cellY = de::clamp(deFloorFloatToInt32((float)y / cellH), 0, gridHeight - 2);
            IVec4 c   = readIVec4(vertices + (cellY * gridWidth + cellX + 1) * numComponents, numComponents);

            dst.setPixel(c, x, y);
        }
    }
}

static const IVec4 s_swizzles[] = {IVec4(0, 1, 2, 3), IVec4(1, 2, 3, 0), IVec4(2, 3, 0, 1), IVec4(3, 0, 1, 2),
                                   IVec4(3, 2, 1, 0), IVec4(2, 1, 0, 3), IVec4(1, 0, 3, 2), IVec4(0, 3, 2, 1)};

template <typename T>
inline tcu::Vector<T, 4> swizzleVec(const tcu::Vector<T, 4> &vec, int swzNdx)
{
    const IVec4 &swz = s_swizzles[swzNdx % DE_LENGTH_OF_ARRAY(s_swizzles)];
    return vec.swizzle(swz[0], swz[1], swz[2], swz[3]);
}

namespace
{

struct AttachmentData
{
    tcu::TextureFormat format;          //!< Actual format of attachment.
    tcu::TextureFormat referenceFormat; //!< Used for reference rendering.
    tcu::TextureFormat readFormat;
    int numWrittenChannels;
    glu::Precision outPrecision;
    vector<uint8_t> renderedData;
    vector<uint8_t> referenceData;
};

template <typename Type>
string valueRangeToString(int numValidChannels, const tcu::Vector<Type, 4> &minValue,
                          const tcu::Vector<Type, 4> &maxValue)
{
    std::ostringstream stream;

    stream << "(";

    for (int i = 0; i < 4; i++)
    {
        if (i != 0)
            stream << ", ";

        if (i < numValidChannels)
            stream << minValue[i] << " -> " << maxValue[i];
        else
            stream << "Undef";
    }

    stream << ")";

    return stream.str();
}

void clearUndefined(const tcu::PixelBufferAccess &access, int numValidChannels)
{
    for (int y = 0; y < access.getHeight(); y++)
        for (int x = 0; x < access.getWidth(); x++)
        {
            switch (tcu::getTextureChannelClass(access.getFormat().type))
            {
            case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
            {
                const Vec4 srcPixel = access.getPixel(x, y);
                Vec4 dstPixel(0.0f, 0.0f, 0.0f, 1.0f);

                for (int channelNdx = 0; channelNdx < numValidChannels; channelNdx++)
                    dstPixel[channelNdx] = srcPixel[channelNdx];

                access.setPixel(dstPixel, x, y);
                break;
            }

            case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
            case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
            case tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT:
            case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
            {
                const IVec4 bitDepth = tcu::getTextureFormatBitDepth(access.getFormat());
                const IVec4 srcPixel = access.getPixelInt(x, y);
                IVec4 dstPixel(0, 0, 0, (int)(((int64_t)0x1u << bitDepth.w()) - 1));

                for (int channelNdx = 0; channelNdx < numValidChannels; channelNdx++)
                    dstPixel[channelNdx] = srcPixel[channelNdx];

                access.setPixel(dstPixel, x, y);
                break;
            }

            default:
                DE_ASSERT(false);
            }
        }
}

} // namespace

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

    // Compute grid size & index list.
    const int minCellSize  = 8;
    const IVec2 minBufSize = getMinSize(m_fboSpec);
    const int gridWidth    = de::clamp(minBufSize.x() / minCellSize, 1, 255) + 1;
    const int gridHeight   = de::clamp(minBufSize.y() / minCellSize, 1, 255) + 1;
    const int numVertices  = gridWidth * gridHeight;
    const int numQuads     = (gridWidth - 1) * (gridHeight - 1);
    const int numIndices   = numQuads * 6;

    const int numInputVecs = getNumInputVectors(m_outputs);
    vector<vector<uint32_t>> inputs(numInputVecs);
    vector<float> positions(numVertices * 4);
    vector<uint16_t> indices(numIndices);

    const int readAlignment  = 4;
    const int viewportW      = minBufSize.x();
    const int viewportH      = minBufSize.y();
    const int numAttachments = (int)m_fboSpec.size();

    vector<uint32_t> drawBuffers(numAttachments);
    vector<AttachmentData> attachments(numAttachments);

    // Initialize attachment data.
    for (int ndx = 0; ndx < numAttachments; ndx++)
    {
        const tcu::TextureFormat texFmt         = glu::mapGLInternalFormat(m_fboSpec[ndx].format);
        const tcu::TextureChannelClass chnClass = tcu::getTextureChannelClass(texFmt.type);
        const bool isFixedPoint                 = chnClass == tcu::TEXTURECHANNELCLASS_SIGNED_FIXED_POINT ||
                                  chnClass == tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT;

        // \note Fixed-point formats use float reference to enable more accurate result verification.
        const tcu::TextureFormat refFmt =
            isFixedPoint ? tcu::TextureFormat(texFmt.order, tcu::TextureFormat::FLOAT) : texFmt;
        const tcu::TextureFormat readFmt = getFramebufferReadFormat(texFmt);
        const int attachmentW            = m_fboSpec[ndx].width;
        const int attachmentH            = m_fboSpec[ndx].height;

        drawBuffers[ndx]                 = GL_COLOR_ATTACHMENT0 + ndx;
        attachments[ndx].format          = texFmt;
        attachments[ndx].readFormat      = readFmt;
        attachments[ndx].referenceFormat = refFmt;
        attachments[ndx].renderedData.resize(readFmt.getPixelSize() * attachmentW * attachmentH);
        attachments[ndx].referenceData.resize(refFmt.getPixelSize() * attachmentW * attachmentH);
    }

    // Initialize indices.
    for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
    {
        int quadY = quadNdx / (gridWidth - 1);
        int quadX = quadNdx - quadY * (gridWidth - 1);

        indices[quadNdx * 6 + 0] = uint16_t(quadX + quadY * gridWidth);
        indices[quadNdx * 6 + 1] = uint16_t(quadX + (quadY + 1) * gridWidth);
        indices[quadNdx * 6 + 2] = uint16_t(quadX + quadY * gridWidth + 1);
        indices[quadNdx * 6 + 3] = indices[quadNdx * 6 + 1];
        indices[quadNdx * 6 + 4] = uint16_t(quadX + (quadY + 1) * gridWidth + 1);
        indices[quadNdx * 6 + 5] = indices[quadNdx * 6 + 2];
    }

    for (int y = 0; y < gridHeight; y++)
    {
        for (int x = 0; x < gridWidth; x++)
        {
            float xf = (float)x / (float)(gridWidth - 1);
            float yf = (float)y / (float)(gridHeight - 1);

            positions[(y * gridWidth + x) * 4 + 0] = 2.0f * xf - 1.0f;
            positions[(y * gridWidth + x) * 4 + 1] = 2.0f * yf - 1.0f;
            positions[(y * gridWidth + x) * 4 + 2] = 0.0f;
            positions[(y * gridWidth + x) * 4 + 3] = 1.0f;
        }
    }

    // Initialize input vectors.
    {
        int curInVec = 0;
        for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
        {
            const FragmentOutput &output = m_outputs[outputNdx];
            bool isFloat                 = glu::isDataTypeFloatOrVec(output.type);
            bool isInt                   = glu::isDataTypeIntOrIVec(output.type);
            bool isUint                  = glu::isDataTypeUintOrUVec(output.type);
            int numVecs                  = output.arrayLength > 0 ? output.arrayLength : 1;
            int numScalars               = glu::getDataTypeScalarSize(output.type);

            for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
            {
                inputs[curInVec].resize(numVertices * numScalars);

                // Record how many outputs are written in attachment.
                DE_ASSERT(output.location + vecNdx < (int)attachments.size());
                attachments[output.location + vecNdx].numWrittenChannels = numScalars;
                attachments[output.location + vecNdx].outPrecision       = output.precision;

                if (isFloat)
                {
                    Vec2 range = getFloatRange(output.precision);
                    Vec4 minVal(range.x());
                    Vec4 maxVal(range.y());
                    float *dst = (float *)&inputs[curInVec][0];

                    if (de::inBounds(output.location + vecNdx, 0, (int)attachments.size()))
                    {
                        // \note Floating-point precision conversion is not well-defined. For that reason we must
                        //       limit value range to intersection of both data type and render target value ranges.
                        const tcu::TextureFormatInfo fmtInfo =
                            tcu::getTextureFormatInfo(attachments[output.location + vecNdx].format);
                        minVal = tcu::max(minVal, fmtInfo.valueMin);
                        maxVal = tcu::min(maxVal, fmtInfo.valueMax);
                    }

                    m_testCtx.getLog() << TestLog::Message << "out" << curInVec
                                       << " value range: " << valueRangeToString(numScalars, minVal, maxVal)
                                       << TestLog::EndMessage;

                    for (int y = 0; y < gridHeight; y++)
                    {
                        for (int x = 0; x < gridWidth; x++)
                        {
                            float xf = (float)x / (float)(gridWidth - 1);
                            float yf = (float)y / (float)(gridHeight - 1);

                            float f0 = (xf + yf) * 0.5f;
                            float f1 = 0.5f + (xf - yf) * 0.5f;
                            Vec4 f   = swizzleVec(Vec4(f0, f1, 1.0f - f0, 1.0f - f1), curInVec);
                            Vec4 c   = minVal + (maxVal - minVal) * f;
                            float *v = dst + (y * gridWidth + x) * numScalars;

                            for (int ndx = 0; ndx < numScalars; ndx++)
                                v[ndx] = c[ndx];
                        }
                    }
                }
                else if (isInt)
                {
                    const IVec2 range = getIntRange(output.precision);
                    IVec4 minVal(range.x());
                    IVec4 maxVal(range.y());

                    if (de::inBounds(output.location + vecNdx, 0, (int)attachments.size()))
                    {
                        // Limit to range of output format as conversion mode is not specified.
                        const IVec4 fmtBits =
                            tcu::getTextureFormatBitDepth(attachments[output.location + vecNdx].format);
                        const BVec4 isZero    = lessThanEqual(fmtBits, IVec4(0));
                        const IVec4 shift     = tcu::clamp(fmtBits - 1, tcu::IVec4(0), tcu::IVec4(256));
                        const IVec4 fmtMinVal = (-(tcu::Vector<int64_t, 4>(1) << shift.cast<int64_t>())).asInt();
                        const IVec4 fmtMaxVal =
                            ((tcu::Vector<int64_t, 4>(1) << shift.cast<int64_t>()) - int64_t(1)).asInt();

                        minVal = select(minVal, tcu::max(minVal, fmtMinVal), isZero);
                        maxVal = select(maxVal, tcu::min(maxVal, fmtMaxVal), isZero);
                    }

                    m_testCtx.getLog() << TestLog::Message << "out" << curInVec
                                       << " value range: " << valueRangeToString(numScalars, minVal, maxVal)
                                       << TestLog::EndMessage;

                    const IVec4 rangeDiv =
                        swizzleVec((IVec4(gridWidth, gridHeight, gridWidth, gridHeight) - 1), curInVec);
                    const IVec4 step =
                        ((maxVal.cast<int64_t>() - minVal.cast<int64_t>()) / (rangeDiv.cast<int64_t>())).asInt();
                    int32_t *dst = (int32_t *)&inputs[curInVec][0];

                    for (int y = 0; y < gridHeight; y++)
                    {
                        for (int x = 0; x < gridWidth; x++)
                        {
                            int ix     = gridWidth - x - 1;
                            int iy     = gridHeight - y - 1;
                            IVec4 c    = minVal + step * swizzleVec(IVec4(x, y, ix, iy), curInVec);
                            int32_t *v = dst + (y * gridWidth + x) * numScalars;

                            DE_ASSERT(boolAll(logicalAnd(greaterThanEqual(c, minVal), lessThanEqual(c, maxVal))));

                            for (int ndx = 0; ndx < numScalars; ndx++)
                                v[ndx] = c[ndx];
                        }
                    }
                }
                else if (isUint)
                {
                    const UVec2 range = getUintRange(output.precision);
                    UVec4 maxVal(range.y());

                    if (de::inBounds(output.location + vecNdx, 0, (int)attachments.size()))
                    {
                        // Limit to range of output format as conversion mode is not specified.
                        const IVec4 fmtBits =
                            tcu::getTextureFormatBitDepth(attachments[output.location + vecNdx].format);
                        const UVec4 fmtMaxVal =
                            ((tcu::Vector<uint64_t, 4>(1) << fmtBits.cast<uint64_t>()) - uint64_t(1)).asUint();

                        maxVal = tcu::min(maxVal, fmtMaxVal);
                    }

                    m_testCtx.getLog() << TestLog::Message << "out" << curInVec
                                       << " value range: " << valueRangeToString(numScalars, UVec4(0), maxVal)
                                       << TestLog::EndMessage;

                    const IVec4 rangeDiv =
                        swizzleVec((IVec4(gridWidth, gridHeight, gridWidth, gridHeight) - 1), curInVec);
                    const UVec4 step = maxVal / rangeDiv.asUint();
                    uint32_t *dst    = &inputs[curInVec][0];

                    DE_ASSERT(range.x() == 0);

                    for (int y = 0; y < gridHeight; y++)
                    {
                        for (int x = 0; x < gridWidth; x++)
                        {
                            int ix      = gridWidth - x - 1;
                            int iy      = gridHeight - y - 1;
                            UVec4 c     = step * swizzleVec(IVec4(x, y, ix, iy).asUint(), curInVec);
                            uint32_t *v = dst + (y * gridWidth + x) * numScalars;

                            DE_ASSERT(boolAll(lessThanEqual(c, maxVal)));

                            for (int ndx = 0; ndx < numScalars; ndx++)
                                v[ndx] = c[ndx];
                        }
                    }
                }
                else
                    DE_ASSERT(false);

                curInVec += 1;
            }
        }
    }

    // Render using gl.
    gl.useProgram(m_program->getProgram());
    gl.bindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
    gl.viewport(0, 0, viewportW, viewportH);
    gl.drawBuffers((int)drawBuffers.size(), &drawBuffers[0]);
    gl.disable(
        GL_DITHER); // Dithering causes issues with unorm formats. Those issues could be worked around in threshold, but it makes validation less accurate.
    GLU_EXPECT_NO_ERROR(gl.getError(), "After program setup");

    {
        int curInVec = 0;
        for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
        {
            const FragmentOutput &output = m_outputs[outputNdx];
            bool isArray                 = output.arrayLength > 0;
            bool isFloat                 = glu::isDataTypeFloatOrVec(output.type);
            bool isInt                   = glu::isDataTypeIntOrIVec(output.type);
            bool isUint                  = glu::isDataTypeUintOrUVec(output.type);
            int scalarSize               = glu::getDataTypeScalarSize(output.type);
            uint32_t glScalarType        = isFloat ? GL_FLOAT : isInt ? GL_INT : isUint ? GL_UNSIGNED_INT : GL_NONE;
            int numVecs                  = isArray ? output.arrayLength : 1;

            for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
            {
                string name =
                    string("in") + de::toString(outputNdx) + (isArray ? string("_") + de::toString(vecNdx) : string());
                int loc = gl.getAttribLocation(m_program->getProgram(), name.c_str());

                if (loc >= 0)
                {
                    gl.enableVertexAttribArray(loc);
                    if (isFloat)
                        gl.vertexAttribPointer(loc, scalarSize, glScalarType, GL_FALSE, 0, &inputs[curInVec][0]);
                    else
                        gl.vertexAttribIPointer(loc, scalarSize, glScalarType, 0, &inputs[curInVec][0]);
                }
                else
                    log << TestLog::Message << "Warning: No location for attribute '" << name << "' found."
                        << TestLog::EndMessage;

                curInVec += 1;
            }
        }
    }
    {
        int posLoc = gl.getAttribLocation(m_program->getProgram(), "a_position");
        TCU_CHECK(posLoc >= 0);
        gl.enableVertexAttribArray(posLoc);
        gl.vertexAttribPointer(posLoc, 4, GL_FLOAT, GL_FALSE, 0, &positions[0]);
    }
    GLU_EXPECT_NO_ERROR(gl.getError(), "After attribute setup");

    gl.drawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, &indices[0]);
    GLU_EXPECT_NO_ERROR(gl.getError(), "glDrawElements");

    // Read all attachment points.
    for (int ndx = 0; ndx < numAttachments; ndx++)
    {
        const glu::TransferFormat transferFmt = glu::getTransferFormat(attachments[ndx].readFormat);
        void *dst                             = &attachments[ndx].renderedData[0];
        const int attachmentW                 = m_fboSpec[ndx].width;
        const int attachmentH                 = m_fboSpec[ndx].height;
        const int numValidChannels            = attachments[ndx].numWrittenChannels;
        const tcu::PixelBufferAccess rendered(
            attachments[ndx].readFormat, attachmentW, attachmentH, 1,
            deAlign32(attachments[ndx].readFormat.getPixelSize() * attachmentW, readAlignment), 0,
            &attachments[ndx].renderedData[0]);

        gl.readBuffer(GL_COLOR_ATTACHMENT0 + ndx);
        gl.readPixels(0, 0, minBufSize.x(), minBufSize.y(), transferFmt.format, transferFmt.dataType, dst);

        clearUndefined(rendered, numValidChannels);
    }

    // Render reference images.
    {
        int curInNdx = 0;
        for (int outputNdx = 0; outputNdx < (int)m_outputs.size(); outputNdx++)
        {
            const FragmentOutput &output = m_outputs[outputNdx];
            const bool isArray           = output.arrayLength > 0;
            const bool isFloat           = glu::isDataTypeFloatOrVec(output.type);
            const bool isInt             = glu::isDataTypeIntOrIVec(output.type);
            const bool isUint            = glu::isDataTypeUintOrUVec(output.type);
            const int scalarSize         = glu::getDataTypeScalarSize(output.type);
            const int numVecs            = isArray ? output.arrayLength : 1;

            for (int vecNdx = 0; vecNdx < numVecs; vecNdx++)
            {
                const int location    = output.location + vecNdx;
                const void *inputData = &inputs[curInNdx][0];

                DE_ASSERT(de::inBounds(location, 0, (int)m_fboSpec.size()));

                const int bufW = m_fboSpec[location].width;
                const int bufH = m_fboSpec[location].height;
                const tcu::PixelBufferAccess buf(attachments[location].referenceFormat, bufW, bufH, 1,
                                                 &attachments[location].referenceData[0]);
                const tcu::PixelBufferAccess viewportBuf = getSubregion(buf, 0, 0, 0, viewportW, viewportH, 1);

                if (isInt || isUint)
                    renderIntReference(viewportBuf, gridWidth, gridHeight, scalarSize, (const int *)inputData);
                else if (isFloat)
                    renderFloatReference(viewportBuf, gridWidth, gridHeight, scalarSize, (const float *)inputData);
                else
                    DE_ASSERT(false);

                curInNdx += 1;
            }
        }
    }

    // Compare all images.
    bool allLevelsOk = true;
    for (int attachNdx = 0; attachNdx < numAttachments; attachNdx++)
    {
        const int attachmentW      = m_fboSpec[attachNdx].width;
        const int attachmentH      = m_fboSpec[attachNdx].height;
        const int numValidChannels = attachments[attachNdx].numWrittenChannels;
        const tcu::BVec4 cmpMask(numValidChannels >= 1, numValidChannels >= 2, numValidChannels >= 3,
                                 numValidChannels >= 4);
        const glu::Precision outPrecision = attachments[attachNdx].outPrecision;
        const tcu::TextureFormat &format  = attachments[attachNdx].format;
        tcu::ConstPixelBufferAccess rendered(
            attachments[attachNdx].readFormat, attachmentW, attachmentH, 1,
            deAlign32(attachments[attachNdx].readFormat.getPixelSize() * attachmentW, readAlignment), 0,
            &attachments[attachNdx].renderedData[0]);
        tcu::ConstPixelBufferAccess reference(attachments[attachNdx].referenceFormat, attachmentW, attachmentH, 1,
                                              &attachments[attachNdx].referenceData[0]);
        tcu::TextureChannelClass texClass = tcu::getTextureChannelClass(format.type);
        bool isOk                         = true;
        const string name                 = string("Attachment") + de::toString(attachNdx);
        const string desc                 = string("Color attachment ") + de::toString(attachNdx);

        log << TestLog::Message << "Attachment " << attachNdx << ": " << numValidChannels
            << " channels have defined values and used for comparison" << TestLog::EndMessage;

        switch (texClass)
        {
        case tcu::TEXTURECHANNELCLASS_FLOATING_POINT:
        {
            const uint32_t interpThreshold = 4; //!< 4 ULP interpolation threshold (interpolation always in highp)
            uint32_t outTypeThreshold      = 0; //!< Threshold based on output type
            UVec4 formatThreshold;              //!< Threshold computed based on format.
            UVec4 finalThreshold;

            // 1 ULP rounding error is allowed for smaller floating-point formats
            switch (format.type)
            {
            case tcu::TextureFormat::FLOAT:
                formatThreshold = UVec4(0);
                break;
            case tcu::TextureFormat::HALF_FLOAT:
                formatThreshold = UVec4((1 << (23 - 10)));
                break;
            case tcu::TextureFormat::UNSIGNED_INT_11F_11F_10F_REV:
                formatThreshold = UVec4((1 << (23 - 6)), (1 << (23 - 6)), (1 << (23 - 5)), 0);
                break;
            default:
                DE_ASSERT(false);
                break;
            }

            // 1 ULP rounding error for highp -> output precision cast
            switch (outPrecision)
            {
            case glu::PRECISION_LOWP:
                outTypeThreshold = (1 << (23 - 8));
                break;
            case glu::PRECISION_MEDIUMP:
                outTypeThreshold = (1 << (23 - 10));
                break;
            case glu::PRECISION_HIGHP:
                outTypeThreshold = 0;
                break;
            default:
                DE_ASSERT(false);
            }

            finalThreshold =
                select(max(formatThreshold, UVec4(deMax32(interpThreshold, outTypeThreshold))), UVec4(~0u), cmpMask);

            isOk = tcu::floatUlpThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, finalThreshold,
                                                 tcu::COMPARE_LOG_RESULT);
            break;
        }

        case tcu::TEXTURECHANNELCLASS_UNSIGNED_FIXED_POINT:
        {
            // \note glReadPixels() allows only 8 bits to be read. This means that RGB10_A2 will loose some
            // bits in the process and it must be taken into account when computing threshold.
            const IVec4 bits         = min(IVec4(8), tcu::getTextureFormatBitDepth(format));
            const Vec4 baseThreshold = 1.0f / ((IVec4(1) << bits) - 1).asFloat();
            const Vec4 threshold     = select(baseThreshold, Vec4(2.0f), cmpMask);

            isOk = tcu::floatThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, threshold,
                                              tcu::COMPARE_LOG_RESULT);
            break;
        }

        case tcu::TEXTURECHANNELCLASS_SIGNED_INTEGER:
        case tcu::TEXTURECHANNELCLASS_UNSIGNED_INTEGER:
        {
            const tcu::UVec4 threshold = select(UVec4(0u), UVec4(~0u), cmpMask);
            isOk = tcu::intThresholdCompare(log, name.c_str(), desc.c_str(), reference, rendered, threshold,
                                            tcu::COMPARE_LOG_RESULT);
            break;
        }

        default:
            TCU_FAIL("Unsupported comparison");
        }

        if (!isOk)
            allLevelsOk = false;
    }

    m_testCtx.setTestResult(allLevelsOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                            allLevelsOk ? "Pass" : "Image comparison failed");
    return STOP;
}

FragmentOutputTests::FragmentOutputTests(Context &context)
    : TestCaseGroup(context, "fragment_out", "Fragment output tests")
{
}

FragmentOutputTests::~FragmentOutputTests(void)
{
}

static FragmentOutputCase *createRandomCase(Context &context, int minRenderTargets, int maxRenderTargets, uint32_t seed)
{
    static const glu::DataType outputTypes[] = {glu::TYPE_FLOAT,      glu::TYPE_FLOAT_VEC2, glu::TYPE_FLOAT_VEC3,
                                                glu::TYPE_FLOAT_VEC4, glu::TYPE_INT,        glu::TYPE_INT_VEC2,
                                                glu::TYPE_INT_VEC3,   glu::TYPE_INT_VEC4,   glu::TYPE_UINT,
                                                glu::TYPE_UINT_VEC2,  glu::TYPE_UINT_VEC3,  glu::TYPE_UINT_VEC4};
    static const glu::Precision precisions[] = {glu::PRECISION_LOWP, glu::PRECISION_MEDIUMP, glu::PRECISION_HIGHP};
    static const uint32_t floatFormats[]     = {
        GL_RGBA32F,      GL_RGBA16F,  GL_R11F_G11F_B10F, GL_RG32F,   GL_RG16F, GL_R32F,   GL_R16F, GL_RGBA8,
        GL_SRGB8_ALPHA8, GL_RGB10_A2, GL_RGBA4,          GL_RGB5_A1, GL_RGB8,  GL_RGB565, GL_RG8,  GL_R8};
    static const uint32_t intFormats[]  = {GL_RGBA32I, GL_RGBA16I, GL_RGBA8I, GL_RG32I, GL_RG16I,
                                           GL_RG8I,    GL_R32I,    GL_R16I,   GL_R8I};
    static const uint32_t uintFormats[] = {GL_RGBA32UI, GL_RGBA16UI, GL_RGBA8UI, GL_RGB10_A2UI, GL_RG32UI,
                                           GL_RG16UI,   GL_RG8UI,    GL_R32UI,   GL_R16UI,      GL_R8UI};

    de::Random rnd(seed);
    vector<FragmentOutput> outputs;
    vector<BufferSpec> targets;
    vector<glu::DataType> outTypes;

    int numTargets    = rnd.getInt(minRenderTargets, maxRenderTargets);
    const int width   = 128; // \todo [2012-04-10 pyry] Separate randomized sizes per target?
    const int height  = 64;
    const int samples = 0;

    // Compute outputs.
    int curLoc = 0;
    while (curLoc < numTargets)
    {
        bool useArray   = rnd.getFloat() < 0.3f;
        int maxArrayLen = numTargets - curLoc;
        int arrayLen    = useArray ? rnd.getInt(1, maxArrayLen) : 0;
        glu::DataType basicType =
            rnd.choose<glu::DataType>(&outputTypes[0], &outputTypes[0] + DE_LENGTH_OF_ARRAY(outputTypes));
        glu::Precision precision =
            rnd.choose<glu::Precision>(&precisions[0], &precisions[0] + DE_LENGTH_OF_ARRAY(precisions));
        int numLocations = useArray ? arrayLen : 1;

        outputs.push_back(FragmentOutput(basicType, precision, curLoc, arrayLen));

        for (int ndx = 0; ndx < numLocations; ndx++)
            outTypes.push_back(basicType);

        curLoc += numLocations;
    }
    DE_ASSERT(curLoc == numTargets);
    DE_ASSERT((int)outTypes.size() == numTargets);

    // Compute buffers.
    while ((int)targets.size() < numTargets)
    {
        glu::DataType outType = outTypes[targets.size()];
        bool isFloat          = glu::isDataTypeFloatOrVec(outType);
        bool isInt            = glu::isDataTypeIntOrIVec(outType);
        bool isUint           = glu::isDataTypeUintOrUVec(outType);
        uint32_t format       = 0;

        if (isFloat)
            format = rnd.choose<uint32_t>(&floatFormats[0], &floatFormats[0] + DE_LENGTH_OF_ARRAY(floatFormats));
        else if (isInt)
            format = rnd.choose<uint32_t>(&intFormats[0], &intFormats[0] + DE_LENGTH_OF_ARRAY(intFormats));
        else if (isUint)
            format = rnd.choose<uint32_t>(&uintFormats[0], &uintFormats[0] + DE_LENGTH_OF_ARRAY(uintFormats));
        else
            DE_ASSERT(false);

        targets.push_back(BufferSpec(format, width, height, samples));
    }

    return new FragmentOutputCase(context, de::toString(seed).c_str(), "", targets, outputs);
}

void FragmentOutputTests::init(void)
{
    static const uint32_t requiredFloatFormats[] = {GL_RGBA32F, GL_RGBA16F, GL_R11F_G11F_B10F, GL_RG32F, GL_RG16F,
                                                    GL_R32F,    GL_R16F};
    static const uint32_t requiredFixedFormats[] = {GL_RGBA8, GL_SRGB8_ALPHA8, GL_RGB10_A2, GL_RGBA4, GL_RGB5_A1,
                                                    GL_RGB8,  GL_RGB565,       GL_RG8,      GL_R8};
    static const uint32_t requiredIntFormats[]   = {GL_RGBA32I, GL_RGBA16I, GL_RGBA8I, GL_RG32I, GL_RG16I,
                                                    GL_RG8I,    GL_R32I,    GL_R16I,   GL_R8I};
    static const uint32_t requiredUintFormats[]  = {GL_RGBA32UI, GL_RGBA16UI, GL_RGBA8UI, GL_RGB10_A2UI, GL_RG32UI,
                                                    GL_RG16UI,   GL_RG8UI,    GL_R32UI,   GL_R16UI,      GL_R8UI};

    static const glu::Precision precisions[] = {glu::PRECISION_LOWP, glu::PRECISION_MEDIUMP, glu::PRECISION_HIGHP};

    // .basic.
    {
        tcu::TestCaseGroup *basicGroup = new tcu::TestCaseGroup(m_testCtx, "basic", "Basic fragment output tests");
        addChild(basicGroup);

        const int width   = 64;
        const int height  = 64;
        const int samples = 0;

        // .float
        tcu::TestCaseGroup *floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point output tests");
        basicGroup->addChild(floatGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFloatFormats); fmtNdx++)
        {
            uint32_t format = requiredFloatFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                floatGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT, prec, 0)).toVec()));
                floatGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2, prec, 0)).toVec()));
                floatGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3, prec, 0)).toVec()));
                floatGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4, prec, 0)).toVec()));
            }
        }

        // .fixed
        tcu::TestCaseGroup *fixedGroup = new tcu::TestCaseGroup(m_testCtx, "fixed", "Fixed-point output tests");
        basicGroup->addChild(fixedGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFixedFormats); fmtNdx++)
        {
            uint32_t format = requiredFixedFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                fixedGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_float").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT, prec, 0)).toVec()));
                fixedGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec2").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2, prec, 0)).toVec()));
                fixedGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec3").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3, prec, 0)).toVec()));
                fixedGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_vec4").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4, prec, 0)).toVec()));
            }
        }

        // .int
        tcu::TestCaseGroup *intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer output tests");
        basicGroup->addChild(intGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredIntFormats); fmtNdx++)
        {
            uint32_t format = requiredIntFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                intGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_int").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_INT, prec, 0)).toVec()));
                intGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec2").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC2, prec, 0)).toVec()));
                intGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec3").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC3, prec, 0)).toVec()));
                intGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_ivec4").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC4, prec, 0)).toVec()));
            }
        }

        // .uint
        tcu::TestCaseGroup *uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Usigned integer output tests");
        basicGroup->addChild(uintGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredUintFormats); fmtNdx++)
        {
            uint32_t format = requiredUintFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                uintGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uint").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_UINT, prec, 0)).toVec()));
                uintGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec2").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC2, prec, 0)).toVec()));
                uintGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec3").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC3, prec, 0)).toVec()));
                uintGroup->addChild(
                    new FragmentOutputCase(m_context, (fmtName + "_" + precName + "_uvec4").c_str(), "", fboSpec,
                                           (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC4, prec, 0)).toVec()));
            }
        }
    }

    // .array
    {
        tcu::TestCaseGroup *arrayGroup = new tcu::TestCaseGroup(m_testCtx, "array", "Array outputs");
        addChild(arrayGroup);

        const int width      = 64;
        const int height     = 64;
        const int samples    = 0;
        const int numTargets = 3;

        // .float
        tcu::TestCaseGroup *floatGroup = new tcu::TestCaseGroup(m_testCtx, "float", "Floating-point output tests");
        arrayGroup->addChild(floatGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFloatFormats); fmtNdx++)
        {
            uint32_t format = requiredFloatFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            for (int ndx = 0; ndx < numTargets; ndx++)
                fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                floatGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_float").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT, prec, 0, numTargets)).toVec()));
                floatGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_vec2").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2, prec, 0, numTargets)).toVec()));
                floatGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_vec3").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3, prec, 0, numTargets)).toVec()));
                floatGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_vec4").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4, prec, 0, numTargets)).toVec()));
            }
        }

        // .fixed
        tcu::TestCaseGroup *fixedGroup = new tcu::TestCaseGroup(m_testCtx, "fixed", "Fixed-point output tests");
        arrayGroup->addChild(fixedGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredFixedFormats); fmtNdx++)
        {
            uint32_t format = requiredFixedFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            for (int ndx = 0; ndx < numTargets; ndx++)
                fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                fixedGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_float").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT, prec, 0, numTargets)).toVec()));
                fixedGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_vec2").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC2, prec, 0, numTargets)).toVec()));
                fixedGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_vec3").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC3, prec, 0, numTargets)).toVec()));
                fixedGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_vec4").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_FLOAT_VEC4, prec, 0, numTargets)).toVec()));
            }
        }

        // .int
        tcu::TestCaseGroup *intGroup = new tcu::TestCaseGroup(m_testCtx, "int", "Integer output tests");
        arrayGroup->addChild(intGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredIntFormats); fmtNdx++)
        {
            uint32_t format = requiredIntFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            for (int ndx = 0; ndx < numTargets; ndx++)
                fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                intGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_int").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_INT, prec, 0, numTargets)).toVec()));
                intGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_ivec2").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC2, prec, 0, numTargets)).toVec()));
                intGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_ivec3").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC3, prec, 0, numTargets)).toVec()));
                intGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_ivec4").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_INT_VEC4, prec, 0, numTargets)).toVec()));
            }
        }

        // .uint
        tcu::TestCaseGroup *uintGroup = new tcu::TestCaseGroup(m_testCtx, "uint", "Usigned integer output tests");
        arrayGroup->addChild(uintGroup);
        for (int fmtNdx = 0; fmtNdx < DE_LENGTH_OF_ARRAY(requiredUintFormats); fmtNdx++)
        {
            uint32_t format = requiredUintFormats[fmtNdx];
            string fmtName  = getFormatName(format);
            vector<BufferSpec> fboSpec;

            for (int ndx = 0; ndx < numTargets; ndx++)
                fboSpec.push_back(BufferSpec(format, width, height, samples));

            for (int precNdx = 0; precNdx < DE_LENGTH_OF_ARRAY(precisions); precNdx++)
            {
                glu::Precision prec = precisions[precNdx];
                string precName     = glu::getPrecisionName(prec);

                uintGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_uint").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_UINT, prec, 0, numTargets)).toVec()));
                uintGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_uvec2").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC2, prec, 0, numTargets)).toVec()));
                uintGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_uvec3").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC3, prec, 0, numTargets)).toVec()));
                uintGroup->addChild(new FragmentOutputCase(
                    m_context, (fmtName + "_" + precName + "_uvec4").c_str(), "", fboSpec,
                    (OutputVec() << FragmentOutput(glu::TYPE_UINT_VEC4, prec, 0, numTargets)).toVec()));
            }
        }
    }

    // .random
    {
        tcu::TestCaseGroup *randomGroup = new tcu::TestCaseGroup(m_testCtx, "random", "Random fragment output cases");
        addChild(randomGroup);

        for (uint32_t seed = 0; seed < 100; seed++)
            randomGroup->addChild(createRandomCase(m_context, 2, 4, seed));
    }
}

} // namespace Functional
} // namespace gles3
} // namespace deqp
