/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) 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 Buffer test utilities.
 *//*--------------------------------------------------------------------*/

#include "glsBufferTestUtil.hpp"
#include "tcuRandomValueIterator.hpp"
#include "tcuSurface.hpp"
#include "tcuImageCompare.hpp"
#include "tcuVector.hpp"
#include "tcuFormatUtil.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuTestLog.hpp"
#include "gluPixelTransfer.hpp"
#include "gluRenderContext.hpp"
#include "gluStrUtil.hpp"
#include "gluShaderProgram.hpp"
#include "deMemory.h"
#include "deStringUtil.hpp"
#include "deArrayUtil.hpp"

#include <algorithm>

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

namespace deqp
{
namespace gls
{
namespace BufferTestUtil
{

enum
{
    VERIFY_QUAD_SIZE                 = 8,   //!< Quad size in VertexArrayVerifier
    MAX_LINES_PER_INDEX_ARRAY_DRAW   = 128, //!< Maximum number of lines per one draw in IndexArrayVerifier
    INDEX_ARRAY_DRAW_VIEWPORT_WIDTH  = 128,
    INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT = 128
};

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

// Helper functions.

void fillWithRandomBytes(uint8_t *ptr, int numBytes, uint32_t seed)
{
    std::copy(tcu::RandomValueIterator<uint8_t>::begin(seed, numBytes), tcu::RandomValueIterator<uint8_t>::end(), ptr);
}

bool compareByteArrays(tcu::TestLog &log, const uint8_t *resPtr, const uint8_t *refPtr, int numBytes)
{
    bool isOk              = true;
    const int maxSpanLen   = 8;
    const int maxDiffSpans = 4;
    int numDiffSpans       = 0;
    int diffSpanStart      = -1;
    int ndx                = 0;

    log << TestLog::Section("Verify", "Verification result");

    for (; ndx < numBytes; ndx++)
    {
        if (resPtr[ndx] != refPtr[ndx])
        {
            if (diffSpanStart < 0)
                diffSpanStart = ndx;

            isOk = false;
        }
        else if (diffSpanStart >= 0)
        {
            if (numDiffSpans < maxDiffSpans)
            {
                int len      = ndx - diffSpanStart;
                int printLen = de::min(len, maxSpanLen);

                log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
                    << "  expected "
                    << tcu::formatArray(tcu::Format::HexIterator<uint8_t>(refPtr + diffSpanStart),
                                        tcu::Format::HexIterator<uint8_t>(refPtr + diffSpanStart + printLen))
                    << "\n"
                    << "  got "
                    << tcu::formatArray(tcu::Format::HexIterator<uint8_t>(resPtr + diffSpanStart),
                                        tcu::Format::HexIterator<uint8_t>(resPtr + diffSpanStart + printLen))
                    << TestLog::EndMessage;
            }
            else
                log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;

            numDiffSpans += 1;
            diffSpanStart = -1;
        }
    }

    if (diffSpanStart >= 0)
    {
        if (numDiffSpans < maxDiffSpans)
        {
            int len      = ndx - diffSpanStart;
            int printLen = de::min(len, maxSpanLen);

            log << TestLog::Message << len << " byte difference at offset " << diffSpanStart << "\n"
                << "  expected "
                << tcu::formatArray(tcu::Format::HexIterator<uint8_t>(refPtr + diffSpanStart),
                                    tcu::Format::HexIterator<uint8_t>(refPtr + diffSpanStart + printLen))
                << "\n"
                << "  got "
                << tcu::formatArray(tcu::Format::HexIterator<uint8_t>(resPtr + diffSpanStart),
                                    tcu::Format::HexIterator<uint8_t>(resPtr + diffSpanStart + printLen))
                << TestLog::EndMessage;
        }
        else
            log << TestLog::Message << "(output too long, truncated)" << TestLog::EndMessage;
    }

    log << TestLog::Message << (isOk ? "Verification passed." : "Verification FAILED!") << TestLog::EndMessage;
    log << TestLog::EndSection;

    return isOk;
}

const char *getBufferTargetName(uint32_t target)
{
    switch (target)
    {
    case GL_ARRAY_BUFFER:
        return "array";
    case GL_COPY_READ_BUFFER:
        return "copy_read";
    case GL_COPY_WRITE_BUFFER:
        return "copy_write";
    case GL_ELEMENT_ARRAY_BUFFER:
        return "element_array";
    case GL_PIXEL_PACK_BUFFER:
        return "pixel_pack";
    case GL_PIXEL_UNPACK_BUFFER:
        return "pixel_unpack";
    case GL_TEXTURE_BUFFER:
        return "texture";
    case GL_TRANSFORM_FEEDBACK_BUFFER:
        return "transform_feedback";
    case GL_UNIFORM_BUFFER:
        return "uniform";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

const char *getUsageHintName(uint32_t hint)
{
    switch (hint)
    {
    case GL_STREAM_DRAW:
        return "stream_draw";
    case GL_STREAM_READ:
        return "stream_read";
    case GL_STREAM_COPY:
        return "stream_copy";
    case GL_STATIC_DRAW:
        return "static_draw";
    case GL_STATIC_READ:
        return "static_read";
    case GL_STATIC_COPY:
        return "static_copy";
    case GL_DYNAMIC_DRAW:
        return "dynamic_draw";
    case GL_DYNAMIC_READ:
        return "dynamic_read";
    case GL_DYNAMIC_COPY:
        return "dynamic_copy";
    default:
        DE_ASSERT(false);
        return DE_NULL;
    }
}

// BufferCase

BufferCase::BufferCase(tcu::TestContext &testCtx, glu::RenderContext &renderCtx, const char *name,
                       const char *description)
    : TestCase(testCtx, name, description)
    , CallLogWrapper(renderCtx.getFunctions(), testCtx.getLog())
    , m_renderCtx(renderCtx)
{
}

BufferCase::~BufferCase(void)
{
    enableLogging(false);
    BufferCase::deinit();
}

void BufferCase::init(void)
{
    enableLogging(true);
}

void BufferCase::deinit(void)
{
    for (set<uint32_t>::const_iterator bufIter = m_allocatedBuffers.begin(); bufIter != m_allocatedBuffers.end();
         bufIter++)
        glDeleteBuffers(1, &(*bufIter));
}

uint32_t BufferCase::genBuffer(void)
{
    uint32_t buf = 0;
    glGenBuffers(1, &buf);
    if (buf != 0)
    {
        try
        {
            m_allocatedBuffers.insert(buf);
        }
        catch (const std::exception &)
        {
            glDeleteBuffers(1, &buf);
            throw;
        }
    }
    return buf;
}

void BufferCase::deleteBuffer(uint32_t buffer)
{
    glDeleteBuffers(1, &buffer);
    m_allocatedBuffers.erase(buffer);
}

void BufferCase::checkError(void)
{
    glw::GLenum err = glGetError();
    if (err != GL_NO_ERROR)
        throw tcu::TestError(string("Got ") + glu::getErrorStr(err).toString());
}

// ReferenceBuffer

void ReferenceBuffer::setSize(int numBytes)
{
    m_data.resize(numBytes);
}

void ReferenceBuffer::setData(int numBytes, const uint8_t *bytes)
{
    m_data.resize(numBytes);
    std::copy(bytes, bytes + numBytes, m_data.begin());
}

void ReferenceBuffer::setSubData(int offset, int numBytes, const uint8_t *bytes)
{
    DE_ASSERT(de::inBounds(offset, 0, (int)m_data.size()) &&
              de::inRange(offset + numBytes, offset, (int)m_data.size()));
    std::copy(bytes, bytes + numBytes, m_data.begin() + offset);
}

// BufferWriterBase

BufferWriterBase::BufferWriterBase(glu::RenderContext &renderCtx, tcu::TestLog &log)
    : CallLogWrapper(renderCtx.getFunctions(), log)
    , m_renderCtx(renderCtx)
{
    enableLogging(true);
}

void BufferWriterBase::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes, uint32_t targetHint)
{
    DE_UNREF(targetHint);
    write(buffer, offset, numBytes, bytes);
}

// BufferWriter

BufferWriter::BufferWriter(glu::RenderContext &renderCtx, tcu::TestLog &log, WriteType writeType) : m_writer(DE_NULL)
{
    switch (writeType)
    {
    case WRITE_BUFFER_SUB_DATA:
        m_writer = new BufferSubDataWriter(renderCtx, log);
        break;
    case WRITE_BUFFER_WRITE_MAP:
        m_writer = new BufferWriteMapWriter(renderCtx, log);
        break;
    default:
        TCU_FAIL("Unsupported writer");
    }
}

BufferWriter::~BufferWriter(void)
{
    delete m_writer;
}

void BufferWriter::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes)
{
    DE_ASSERT(numBytes >= getMinSize());
    DE_ASSERT(offset % getAlignment() == 0);
    DE_ASSERT((offset + numBytes) % getAlignment() == 0);
    return m_writer->write(buffer, offset, numBytes, bytes);
}

void BufferWriter::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes, uint32_t targetHint)
{
    DE_ASSERT(numBytes >= getMinSize());
    DE_ASSERT(offset % getAlignment() == 0);
    DE_ASSERT((offset + numBytes) % getAlignment() == 0);
    return m_writer->write(buffer, offset, numBytes, bytes, targetHint);
}

// BufferSubDataWriter

void BufferSubDataWriter::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes)
{
    write(buffer, offset, numBytes, bytes, GL_ARRAY_BUFFER);
}

void BufferSubDataWriter::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes, uint32_t target)
{
    glBindBuffer(target, buffer);
    glBufferSubData(target, offset, numBytes, bytes);
    glBindBuffer(target, 0);
    GLU_CHECK();
}

// BufferWriteMapWriter

void BufferWriteMapWriter::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes)
{
    write(buffer, offset, numBytes, bytes, GL_ARRAY_BUFFER);
}

void BufferWriteMapWriter::write(uint32_t buffer, int offset, int numBytes, const uint8_t *bytes, uint32_t target)
{
    glBindBuffer(target, buffer);

    void *ptr = glMapBufferRange(target, offset, numBytes, GL_MAP_WRITE_BIT);
    GLU_CHECK_MSG("glMapBufferRange");

    deMemcpy(ptr, bytes, numBytes);

    glUnmapBuffer(target);
    glBindBuffer(target, 0);
    GLU_CHECK();
}

// BufferVerifierBase

BufferVerifierBase::BufferVerifierBase(glu::RenderContext &renderCtx, tcu::TestLog &log)
    : CallLogWrapper(renderCtx.getFunctions(), log)
    , m_renderCtx(renderCtx)
    , m_log(log)
{
    enableLogging(true);
}

bool BufferVerifierBase::verify(uint32_t buffer, const uint8_t *reference, int offset, int numBytes,
                                uint32_t targetHint)
{
    DE_UNREF(targetHint);
    return verify(buffer, reference, offset, numBytes);
}

// BufferVerifier

BufferVerifier::BufferVerifier(glu::RenderContext &renderCtx, tcu::TestLog &log, VerifyType verifyType)
    : m_verifier(DE_NULL)
{
    switch (verifyType)
    {
    case VERIFY_AS_VERTEX_ARRAY:
        m_verifier = new VertexArrayVerifier(renderCtx, log);
        break;
    case VERIFY_AS_INDEX_ARRAY:
        m_verifier = new IndexArrayVerifier(renderCtx, log);
        break;
    case VERIFY_BUFFER_READ_MAP:
        m_verifier = new BufferMapVerifier(renderCtx, log);
        break;
    default:
        TCU_FAIL("Unsupported verifier");
    }
}

BufferVerifier::~BufferVerifier(void)
{
    delete m_verifier;
}

bool BufferVerifier::verify(uint32_t buffer, const uint8_t *reference, int offset, int numBytes)
{
    DE_ASSERT(numBytes >= getMinSize());
    DE_ASSERT(offset % getAlignment() == 0);
    DE_ASSERT((offset + numBytes) % getAlignment() == 0);
    return m_verifier->verify(buffer, reference, offset, numBytes);
}

bool BufferVerifier::verify(uint32_t buffer, const uint8_t *reference, int offset, int numBytes, uint32_t targetHint)
{
    DE_ASSERT(numBytes >= getMinSize());
    DE_ASSERT(offset % getAlignment() == 0);
    DE_ASSERT((offset + numBytes) % getAlignment() == 0);
    return m_verifier->verify(buffer, reference, offset, numBytes, targetHint);
}

// BufferMapVerifier

bool BufferMapVerifier::verify(uint32_t buffer, const uint8_t *reference, int offset, int numBytes)
{
    return verify(buffer, reference, offset, numBytes, GL_ARRAY_BUFFER);
}

bool BufferMapVerifier::verify(uint32_t buffer, const uint8_t *reference, int offset, int numBytes, uint32_t target)
{
    const uint8_t *mapPtr = DE_NULL;
    bool isOk             = false;

    glBindBuffer(target, buffer);
    mapPtr = (const uint8_t *)glMapBufferRange(target, offset, numBytes, GL_MAP_READ_BIT);
    GLU_CHECK_MSG("glMapBufferRange");
    TCU_CHECK(mapPtr);

    isOk = compareByteArrays(m_log, mapPtr, reference + offset, numBytes);

    glUnmapBuffer(target);
    GLU_CHECK_MSG("glUnmapBuffer");

    glBindBuffer(target, 0);

    return isOk;
}

// VertexArrayVerifier

VertexArrayVerifier::VertexArrayVerifier(glu::RenderContext &renderCtx, tcu::TestLog &log)
    : BufferVerifierBase(renderCtx, log)
    , m_program(DE_NULL)
    , m_posLoc(0)
    , m_byteVecLoc(0)
    , m_vao(0)
{
    const glu::ContextType ctxType = renderCtx.getType();
    const glu::GLSLVersion glslVersion =
        glu::isContextTypeES(ctxType) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330;

    DE_ASSERT(glu::isGLSLVersionSupported(ctxType, glslVersion));

    m_program = new glu::ShaderProgram(m_renderCtx,
                                       glu::makeVtxFragSources(string(glu::getGLSLVersionDeclaration(glslVersion)) +
                                                                   "\n"
                                                                   "in highp vec2 a_position;\n"
                                                                   "in mediump vec3 a_byteVec;\n"
                                                                   "out mediump vec3 v_byteVec;\n"
                                                                   "void main (void)\n"
                                                                   "{\n"
                                                                   "    gl_Position = vec4(a_position, 0.0, 1.0);\n"
                                                                   "    v_byteVec = a_byteVec;\n"
                                                                   "}\n",

                                                               string(glu::getGLSLVersionDeclaration(glslVersion)) +
                                                                   "\n"
                                                                   "in mediump vec3 v_byteVec;\n"
                                                                   "layout(location = 0) out mediump vec4 o_color;\n"
                                                                   "void main (void)\n"
                                                                   "{\n"
                                                                   "    o_color = vec4(v_byteVec, 1.0);\n"
                                                                   "}\n"));

    if (!m_program->isOk())
    {
        m_log << *m_program;
        delete m_program;
        TCU_FAIL("Compile failed");
    }

    const glw::Functions &gl = m_renderCtx.getFunctions();
    m_posLoc                 = gl.getAttribLocation(m_program->getProgram(), "a_position");
    m_byteVecLoc             = gl.getAttribLocation(m_program->getProgram(), "a_byteVec");

    gl.genVertexArrays(1, &m_vao);
    gl.genBuffers(1, &m_positionBuf);
    gl.genBuffers(1, &m_indexBuf);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Initialization failed");
}

VertexArrayVerifier::~VertexArrayVerifier(void)
{
    const glw::Functions &gl = m_renderCtx.getFunctions();

    if (m_vao)
        gl.deleteVertexArrays(1, &m_vao);
    if (m_positionBuf)
        gl.deleteBuffers(1, &m_positionBuf);
    if (m_indexBuf)
        gl.deleteBuffers(1, &m_indexBuf);

    delete m_program;
}

static void computePositions(vector<tcu::Vec2> &positions, int gridSizeX, int gridSizeY)
{
    positions.resize(gridSizeX * gridSizeY * 4);

    for (int y = 0; y < gridSizeY; y++)
        for (int x = 0; x < gridSizeX; x++)
        {
            float sx0   = (float)(x + 0) / (float)gridSizeX;
            float sy0   = (float)(y + 0) / (float)gridSizeY;
            float sx1   = (float)(x + 1) / (float)gridSizeX;
            float sy1   = (float)(y + 1) / (float)gridSizeY;
            float fx0   = 2.0f * sx0 - 1.0f;
            float fy0   = 2.0f * sy0 - 1.0f;
            float fx1   = 2.0f * sx1 - 1.0f;
            float fy1   = 2.0f * sy1 - 1.0f;
            int baseNdx = (y * gridSizeX + x) * 4;

            positions[baseNdx + 0] = tcu::Vec2(fx0, fy0);
            positions[baseNdx + 1] = tcu::Vec2(fx0, fy1);
            positions[baseNdx + 2] = tcu::Vec2(fx1, fy0);
            positions[baseNdx + 3] = tcu::Vec2(fx1, fy1);
        }
}

static void computeIndices(vector<uint16_t> &indices, int gridSizeX, int gridSizeY)
{
    indices.resize(3 * 2 * gridSizeX * gridSizeY);

    for (int quadNdx = 0; quadNdx < gridSizeX * gridSizeY; quadNdx++)
    {
        int v00 = quadNdx * 4 + 0;
        int v01 = quadNdx * 4 + 1;
        int v10 = quadNdx * 4 + 2;
        int v11 = quadNdx * 4 + 3;

        DE_ASSERT(v11 < (1 << 16));

        indices[quadNdx * 6 + 0] = (uint16_t)v10;
        indices[quadNdx * 6 + 1] = (uint16_t)v00;
        indices[quadNdx * 6 + 2] = (uint16_t)v01;

        indices[quadNdx * 6 + 3] = (uint16_t)v10;
        indices[quadNdx * 6 + 4] = (uint16_t)v01;
        indices[quadNdx * 6 + 5] = (uint16_t)v11;
    }
}

static inline tcu::Vec4 fetchVtxColor(const uint8_t *ptr, int vtxNdx)
{
    return tcu::RGBA(*(ptr + vtxNdx * 3 + 0), *(ptr + vtxNdx * 3 + 1), *(ptr + vtxNdx * 3 + 2), 255).toVec();
}

static void renderQuadGridReference(tcu::Surface &dst, int numQuads, int rowLength, const uint8_t *inPtr)
{
    using tcu::Vec4;

    dst.setSize(rowLength * VERIFY_QUAD_SIZE,
                (numQuads / rowLength + (numQuads % rowLength != 0 ? 1 : 0)) * VERIFY_QUAD_SIZE);

    tcu::PixelBufferAccess dstAccess = dst.getAccess();
    tcu::clear(dstAccess, tcu::IVec4(0, 0, 0, 0xff));

    for (int quadNdx = 0; quadNdx < numQuads; quadNdx++)
    {
        int x0   = (quadNdx % rowLength) * VERIFY_QUAD_SIZE;
        int y0   = (quadNdx / rowLength) * VERIFY_QUAD_SIZE;
        Vec4 v00 = fetchVtxColor(inPtr, quadNdx * 4 + 0);
        Vec4 v10 = fetchVtxColor(inPtr, quadNdx * 4 + 1);
        Vec4 v01 = fetchVtxColor(inPtr, quadNdx * 4 + 2);
        Vec4 v11 = fetchVtxColor(inPtr, quadNdx * 4 + 3);

        for (int y = 0; y < VERIFY_QUAD_SIZE; y++)
            for (int x = 0; x < VERIFY_QUAD_SIZE; x++)
            {
                float fx = ((float)x + 0.5f) / (float)VERIFY_QUAD_SIZE;
                float fy = ((float)y + 0.5f) / (float)VERIFY_QUAD_SIZE;

                bool tri       = fx + fy <= 1.0f;
                float tx       = tri ? fx : (1.0f - fx);
                float ty       = tri ? fy : (1.0f - fy);
                const Vec4 &t0 = tri ? v00 : v11;
                const Vec4 &t1 = tri ? v01 : v10;
                const Vec4 &t2 = tri ? v10 : v01;
                Vec4 color     = t0 + (t1 - t0) * tx + (t2 - t0) * ty;

                dstAccess.setPixel(color, x0 + x, y0 + y);
            }
    }
}

bool VertexArrayVerifier::verify(uint32_t buffer, const uint8_t *refPtr, int offset, int numBytes)
{
    const tcu::RenderTarget &renderTarget = m_renderCtx.getRenderTarget();
    const int numBytesInVtx               = 3;
    const int numBytesInQuad              = numBytesInVtx * 4;
    int maxQuadsX                         = de::min(128, renderTarget.getWidth() / VERIFY_QUAD_SIZE);
    int maxQuadsY                         = de::min(128, renderTarget.getHeight() / VERIFY_QUAD_SIZE);
    int maxQuadsPerBatch                  = maxQuadsX * maxQuadsY;
    int numVerified                       = 0;
    uint32_t program                      = m_program->getProgram();
    tcu::RGBA threshold                   = renderTarget.getPixelFormat().getColorThreshold() + tcu::RGBA(3, 3, 3, 3);
    bool isOk                             = true;

    vector<tcu::Vec2> positions;
    vector<uint16_t> indices;

    tcu::Surface rendered;
    tcu::Surface reference;

    DE_ASSERT(numBytes >= numBytesInQuad); // Can't render full quad with smaller buffers.

    computePositions(positions, maxQuadsX, maxQuadsY);
    computeIndices(indices, maxQuadsX, maxQuadsY);

    // Reset buffer bindings.
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

    // Setup rendering state.
    glViewport(0, 0, maxQuadsX * VERIFY_QUAD_SIZE, maxQuadsY * VERIFY_QUAD_SIZE);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glUseProgram(program);
    glBindVertexArray(m_vao);

    // Upload positions
    glBindBuffer(GL_ARRAY_BUFFER, m_positionBuf);
    glBufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size() * sizeof(positions[0])), &positions[0],
                 GL_STATIC_DRAW);
    glEnableVertexAttribArray(m_posLoc);
    glVertexAttribPointer(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

    // Upload indices
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indexBuf);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, (glw::GLsizeiptr)(indices.size() * sizeof(indices[0])), &indices[0],
                 GL_STATIC_DRAW);

    glEnableVertexAttribArray(m_byteVecLoc);
    glBindBuffer(GL_ARRAY_BUFFER, buffer);

    while (numVerified < numBytes)
    {
        int numRemaining     = numBytes - numVerified;
        bool isLeftoverBatch = numRemaining < numBytesInQuad;
        int numBytesToVerify =
            isLeftoverBatch ? numBytesInQuad :
                              de::min(maxQuadsPerBatch * numBytesInQuad, numRemaining - numRemaining % numBytesInQuad);
        int curOffset       = isLeftoverBatch ? (numBytes - numBytesInQuad) : numVerified;
        int numQuads        = numBytesToVerify / numBytesInQuad;
        int numCols         = de::min(maxQuadsX, numQuads);
        int numRows         = numQuads / maxQuadsX + (numQuads % maxQuadsX != 0 ? 1 : 0);
        string imageSetDesc = string("Bytes ") + de::toString(offset + curOffset) + " to " +
                              de::toString(offset + curOffset + numBytesToVerify - 1);

        DE_ASSERT(numBytesToVerify > 0 && numBytesToVerify % numBytesInQuad == 0);
        DE_ASSERT(de::inBounds(curOffset, 0, numBytes));
        DE_ASSERT(de::inRange(curOffset + numBytesToVerify, curOffset, numBytes));

        // Render batch.
        glClear(GL_COLOR_BUFFER_BIT);
        glVertexAttribPointer(m_byteVecLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0,
                              (const glw::GLvoid *)(uintptr_t)(offset + curOffset));
        glDrawElements(GL_TRIANGLES, numQuads * 6, GL_UNSIGNED_SHORT, DE_NULL);

        renderQuadGridReference(reference, numQuads, numCols, refPtr + offset + curOffset);

        rendered.setSize(numCols * VERIFY_QUAD_SIZE, numRows * VERIFY_QUAD_SIZE);
        glu::readPixels(m_renderCtx, 0, 0, rendered.getAccess());

        if (!tcu::pixelThresholdCompare(m_log, "RenderResult", imageSetDesc.c_str(), reference, rendered, threshold,
                                        tcu::COMPARE_LOG_RESULT))
        {
            isOk = false;
            break;
        }

        numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
    }

    glBindVertexArray(0);

    return isOk;
}

// IndexArrayVerifier

IndexArrayVerifier::IndexArrayVerifier(glu::RenderContext &renderCtx, tcu::TestLog &log)
    : BufferVerifierBase(renderCtx, log)
    , m_program(DE_NULL)
    , m_posLoc(0)
    , m_colorLoc(0)
{

    const glu::ContextType ctxType = renderCtx.getType();
    const glu::GLSLVersion glslVersion =
        glu::isContextTypeES(ctxType) ? glu::GLSL_VERSION_300_ES : glu::GLSL_VERSION_330;

    DE_ASSERT(glu::isGLSLVersionSupported(ctxType, glslVersion));

    m_program = new glu::ShaderProgram(m_renderCtx,
                                       glu::makeVtxFragSources(string(glu::getGLSLVersionDeclaration(glslVersion)) +
                                                                   "\n"
                                                                   "in highp vec2 a_position;\n"
                                                                   "in mediump vec3 a_color;\n"
                                                                   "out mediump vec3 v_color;\n"
                                                                   "void main (void)\n"
                                                                   "{\n"
                                                                   "    gl_Position = vec4(a_position, 0.0, 1.0);\n"
                                                                   "    v_color = a_color;\n"
                                                                   "}\n",

                                                               string(glu::getGLSLVersionDeclaration(glslVersion)) +
                                                                   "\n"
                                                                   "in mediump vec3 v_color;\n"
                                                                   "layout(location = 0) out mediump vec4 o_color;\n"
                                                                   "void main (void)\n"
                                                                   "{\n"
                                                                   "    o_color = vec4(v_color, 1.0);\n"
                                                                   "}\n"));

    if (!m_program->isOk())
    {
        m_log << *m_program;
        delete m_program;
        TCU_FAIL("Compile failed");
    }

    const glw::Functions &gl = m_renderCtx.getFunctions();
    m_posLoc                 = gl.getAttribLocation(m_program->getProgram(), "a_position");
    m_colorLoc               = gl.getAttribLocation(m_program->getProgram(), "a_color");

    gl.genVertexArrays(1, &m_vao);
    gl.genBuffers(1, &m_positionBuf);
    gl.genBuffers(1, &m_colorBuf);
    GLU_EXPECT_NO_ERROR(gl.getError(), "Initialization failed");
}

IndexArrayVerifier::~IndexArrayVerifier(void)
{
    const glw::Functions &gl = m_renderCtx.getFunctions();

    if (m_vao)
        gl.deleteVertexArrays(1, &m_vao);
    if (m_positionBuf)
        gl.deleteBuffers(1, &m_positionBuf);
    if (m_colorBuf)
        gl.deleteBuffers(1, &m_colorBuf);

    delete m_program;
}

static void computeIndexVerifierPositions(std::vector<tcu::Vec2> &dst)
{
    const int numPosX = 16;
    const int numPosY = 16;

    dst.resize(numPosX * numPosY);

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

            dst[y * numPosX + x] = tcu::Vec2(2.0f * xf - 1.0f, 2.0f * yf - 1.0f);
        }
    }
}

static void computeIndexVerifierColors(std::vector<tcu::Vec3> &dst)
{
    const int numColors = 256;
    const float minVal  = 0.1f;
    const float maxVal  = 0.5f;
    de::Random rnd(0xabc231);

    dst.resize(numColors);

    for (std::vector<tcu::Vec3>::iterator i = dst.begin(); i != dst.end(); ++i)
    {
        i->x() = rnd.getFloat(minVal, maxVal);
        i->y() = rnd.getFloat(minVal, maxVal);
        i->z() = rnd.getFloat(minVal, maxVal);
    }
}

template <typename T>
static void execVertexFetch(T *dst, const T *src, const uint8_t *indices, int numIndices)
{
    for (int i = 0; i < numIndices; ++i)
        dst[i] = src[indices[i]];
}

bool IndexArrayVerifier::verify(uint32_t buffer, const uint8_t *refPtr, int offset, int numBytes)
{
    const tcu::RenderTarget &renderTarget = m_renderCtx.getRenderTarget();
    const int viewportW                   = de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_WIDTH, renderTarget.getWidth());
    const int viewportH                   = de::min<int>(INDEX_ARRAY_DRAW_VIEWPORT_HEIGHT, renderTarget.getHeight());
    const int minBytesPerBatch            = 2;
    const tcu::RGBA threshold(0, 0, 0, 0);

    std::vector<tcu::Vec2> positions;
    std::vector<tcu::Vec3> colors;

    std::vector<tcu::Vec2> fetchedPos(MAX_LINES_PER_INDEX_ARRAY_DRAW + 1);
    std::vector<tcu::Vec3> fetchedColor(MAX_LINES_PER_INDEX_ARRAY_DRAW + 1);

    tcu::Surface indexBufferImg(viewportW, viewportH);
    tcu::Surface referenceImg(viewportW, viewportH);

    int numVerified = 0;
    bool isOk       = true;

    DE_STATIC_ASSERT(sizeof(tcu::Vec2) == sizeof(float) * 2);
    DE_STATIC_ASSERT(sizeof(tcu::Vec3) == sizeof(float) * 3);

    computeIndexVerifierPositions(positions);
    computeIndexVerifierColors(colors);

    // Reset buffer bindings.
    glBindVertexArray(m_vao);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer);

    // Setup rendering state.
    glViewport(0, 0, viewportW, viewportH);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glUseProgram(m_program->getProgram());
    glEnableVertexAttribArray(m_posLoc);
    glEnableVertexAttribArray(m_colorLoc);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    glBlendEquation(GL_FUNC_ADD);

    while (numVerified < numBytes)
    {
        int numRemaining     = numBytes - numVerified;
        bool isLeftoverBatch = numRemaining < minBytesPerBatch;
        int numBytesToVerify =
            isLeftoverBatch ? minBytesPerBatch : de::min(MAX_LINES_PER_INDEX_ARRAY_DRAW + 1, numRemaining);
        int curOffset       = isLeftoverBatch ? (numBytes - minBytesPerBatch) : numVerified;
        string imageSetDesc = string("Bytes ") + de::toString(offset + curOffset) + " to " +
                              de::toString(offset + curOffset + numBytesToVerify - 1);

        // Step 1: Render using index buffer.
        glClear(GL_COLOR_BUFFER_BIT);

        glBindBuffer(GL_ARRAY_BUFFER, m_positionBuf);
        glBufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(positions.size() * sizeof(positions[0])), &positions[0],
                     GL_STREAM_DRAW);
        glVertexAttribPointer(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

        glBindBuffer(GL_ARRAY_BUFFER, m_colorBuf);
        glBufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(colors.size() * sizeof(colors[0])), &colors[0], GL_STREAM_DRAW);
        glVertexAttribPointer(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);

        glDrawElements(GL_LINE_STRIP, numBytesToVerify, GL_UNSIGNED_BYTE, (void *)(uintptr_t)(offset + curOffset));
        glu::readPixels(m_renderCtx, 0, 0, indexBufferImg.getAccess());

        // Step 2: Do manual fetch and render without index buffer.
        execVertexFetch(&fetchedPos[0], &positions[0], refPtr + offset + curOffset, numBytesToVerify);
        execVertexFetch(&fetchedColor[0], &colors[0], refPtr + offset + curOffset, numBytesToVerify);

        glClear(GL_COLOR_BUFFER_BIT);

        glBindBuffer(GL_ARRAY_BUFFER, m_positionBuf);
        glBufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(fetchedPos.size() * sizeof(fetchedPos[0])), &fetchedPos[0],
                     GL_STREAM_DRAW);
        glVertexAttribPointer(m_posLoc, 2, GL_FLOAT, GL_FALSE, 0, DE_NULL);

        glBindBuffer(GL_ARRAY_BUFFER, m_colorBuf);
        glBufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)(fetchedColor.size() * sizeof(fetchedColor[0])),
                     &fetchedColor[0], GL_STREAM_DRAW);
        glVertexAttribPointer(m_colorLoc, 3, GL_FLOAT, GL_FALSE, 0, DE_NULL);

        glDrawArrays(GL_LINE_STRIP, 0, numBytesToVerify);
        glu::readPixels(m_renderCtx, 0, 0, referenceImg.getAccess());

        if (!tcu::pixelThresholdCompare(m_log, "RenderResult", imageSetDesc.c_str(), referenceImg, indexBufferImg,
                                        threshold, tcu::COMPARE_LOG_RESULT))
        {
            isOk = false;
            break;
        }

        numVerified += isLeftoverBatch ? numRemaining : numBytesToVerify;
    }

    glBindVertexArray(0);

    return isOk;
}

const char *getWriteTypeDescription(WriteType write)
{
    static const char *s_desc[] = {"glBufferSubData()", "glMapBufferRange()", "transform feedback",
                                   "glReadPixels() into PBO binding"};
    return de::getSizedArrayElement<WRITE_LAST>(s_desc, write);
}

const char *getVerifyTypeDescription(VerifyType verify)
{
    static const char *s_desc[] = {"rendering as vertex data", "rendering as index data",
                                   "reading in shader as uniform buffer data", "using as PBO and uploading to texture",
                                   "reading back using glMapBufferRange()"};
    return de::getSizedArrayElement<VERIFY_LAST>(s_desc, verify);
}

} // namespace BufferTestUtil
} // namespace gls
} // namespace deqp
