/*-------------------------------------------------------------------------
 * 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 Buffer map tests.
 *//*--------------------------------------------------------------------*/

#include "es3fBufferMapTests.hpp"
#include "glsBufferTestUtil.hpp"
#include "tcuTestLog.hpp"
#include "deMemory.h"
#include "deString.h"
#include "glwEnums.hpp"
#include "glwFunctions.hpp"

#include <algorithm>

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

namespace deqp
{
namespace gles3
{
namespace Functional
{

using namespace gls::BufferTestUtil;

// Test cases.

class BufferMapReadCase : public BufferCase
{
public:
    BufferMapReadCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget, uint32_t usage,
                      int bufferSize, int mapOffset, int mapSize, WriteType write)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_bufferSize(bufferSize)
        , m_mapOffset(mapOffset)
        , m_mapSize(mapSize)
        , m_write(write)
    {
    }

    IterateResult iterate(void)
    {
        TestLog &log      = m_testCtx.getLog();
        uint32_t dataSeed = deStringHash(getName());
        ReferenceBuffer refBuf;
        BufferWriter writer(m_renderCtx, m_testCtx.getLog(), m_write);
        bool isOk = false;

        // Setup reference data.
        refBuf.setSize(m_bufferSize);
        fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);

        uint32_t buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, m_bufferSize, DE_NULL, m_usage);
        writer.write(buf, 0, m_bufferSize, refBuf.getPtr(), m_bufferTarget);

        glBindBuffer(m_bufferTarget, buf);
        void *ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_READ_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        isOk = compareByteArrays(log, (const uint8_t *)ptr, refBuf.getPtr(m_mapOffset), m_mapSize);

        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    int m_bufferSize;
    int m_mapOffset;
    int m_mapSize;
    WriteType m_write;
};

class BufferMapWriteCase : public BufferCase
{
public:
    BufferMapWriteCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget, uint32_t usage,
                       int size, VerifyType verify)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_size(size)
        , m_verify(verify)
    {
    }

    IterateResult iterate(void)
    {
        uint32_t dataSeed = deStringHash(getName());
        ReferenceBuffer refBuf;
        BufferVerifier verifier(m_renderCtx, m_testCtx.getLog(), m_verify);

        // Setup reference data.
        refBuf.setSize(m_size);
        fillWithRandomBytes(refBuf.getPtr(), m_size, dataSeed);

        uint32_t buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, m_size, DE_NULL, m_usage);

        void *ptr = glMapBufferRange(m_bufferTarget, 0, m_size, GL_MAP_WRITE_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        fillWithRandomBytes((uint8_t *)ptr, m_size, dataSeed);
        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        bool isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_size, m_bufferTarget);
        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    int m_size;
    VerifyType m_verify;
};

class BufferPartialMapWriteCase : public BufferCase
{
public:
    BufferPartialMapWriteCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget,
                              uint32_t usage, int bufferSize, int mapOffset, int mapSize, VerifyType verify)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_bufferSize(bufferSize)
        , m_mapOffset(mapOffset)
        , m_mapSize(mapSize)
        , m_verify(verify)
    {
    }

    IterateResult iterate(void)
    {
        uint32_t dataSeed = deStringHash(getName());
        ReferenceBuffer refBuf;
        BufferVerifier verifier(m_renderCtx, m_testCtx.getLog(), m_verify);

        // Setup reference data.
        refBuf.setSize(m_bufferSize);
        fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);

        uint32_t buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, m_bufferSize, refBuf.getPtr(), m_usage);

        // Do reference map.
        fillWithRandomBytes(refBuf.getPtr(m_mapOffset), m_mapSize, dataSeed & 0xabcdef);

        void *ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_WRITE_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        deMemcpy(ptr, refBuf.getPtr(m_mapOffset), m_mapSize);
        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        bool isOk = verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget);
        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    int m_bufferSize;
    int m_mapOffset;
    int m_mapSize;
    VerifyType m_verify;
};

class BufferMapInvalidateCase : public BufferCase
{
public:
    BufferMapInvalidateCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget, uint32_t usage,
                            bool partialWrite, VerifyType verify)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_partialWrite(partialWrite)
        , m_verify(verify)
    {
    }

    IterateResult iterate(void)
    {
        uint32_t dataSeed = deStringHash(getName());
        uint32_t buf      = 0;
        ReferenceBuffer refBuf;
        BufferVerifier verifier(m_renderCtx, m_testCtx.getLog(), m_verify);
        const int bufferSize   = 1300;
        const int mapOffset    = 200;
        const int mapSize      = 1011;
        const int mapWriteOffs = m_partialWrite ? 91 : 0;
        const int verifyOffset = mapOffset + mapWriteOffs;
        const int verifySize   = mapSize - mapWriteOffs;

        // Setup reference data.
        refBuf.setSize(bufferSize);
        fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);

        buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);

        // Do reference map.
        fillWithRandomBytes(refBuf.getPtr(mapOffset + mapWriteOffs), mapSize - mapWriteOffs, dataSeed & 0xabcdef);

        void *ptr =
            glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        deMemcpy((uint8_t *)ptr + mapWriteOffs, refBuf.getPtr(mapOffset + mapWriteOffs), mapSize - mapWriteOffs);
        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        bool isOk = verifier.verify(buf, refBuf.getPtr(), verifyOffset, verifySize, m_bufferTarget);
        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    bool m_partialWrite;
    VerifyType m_verify;
};

class BufferMapPartialInvalidateCase : public BufferCase
{
public:
    BufferMapPartialInvalidateCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget,
                                   uint32_t usage, bool partialWrite, VerifyType verify)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_partialWrite(partialWrite)
        , m_verify(verify)
    {
    }

    IterateResult iterate(void)
    {
        uint32_t dataSeed = deStringHash(getName());
        uint32_t buf      = 0;
        ReferenceBuffer refBuf;
        BufferVerifier verifier(m_renderCtx, m_testCtx.getLog(), m_verify);
        const int bufferSize   = 1300;
        const int mapOffset    = 200;
        const int mapSize      = 1011;
        const int mapWriteOffs = m_partialWrite ? 91 : 0;
        const int verifyOffset = m_partialWrite ? mapOffset + mapWriteOffs : 0;
        const int verifySize   = bufferSize - verifyOffset;

        // Setup reference data.
        refBuf.setSize(bufferSize);
        fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);

        buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);

        // Do reference map.
        fillWithRandomBytes(refBuf.getPtr(mapOffset + mapWriteOffs), mapSize - mapWriteOffs, dataSeed & 0xabcdef);

        void *ptr =
            glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        deMemcpy((uint8_t *)ptr + mapWriteOffs, refBuf.getPtr(mapOffset + mapWriteOffs), mapSize - mapWriteOffs);
        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        bool isOk = verifier.verify(buf, refBuf.getPtr(), verifyOffset, verifySize, m_bufferTarget);
        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    bool m_partialWrite;
    VerifyType m_verify;
};

class BufferMapExplicitFlushCase : public BufferCase
{
public:
    BufferMapExplicitFlushCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget,
                               uint32_t usage, bool partialWrite, VerifyType verify)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_partialWrite(partialWrite)
        , m_verify(verify)
    {
    }

    IterateResult iterate(void)
    {
        uint32_t dataSeed = deStringHash(getName());
        uint32_t buf      = 0;
        ReferenceBuffer refBuf;
        BufferVerifier verifier(m_renderCtx, m_testCtx.getLog(), m_verify);
        const int bufferSize = 1300;
        const int mapOffset  = 200;
        const int mapSize    = 1011;
        const int sliceAOffs = m_partialWrite ? 1 : 0;
        const int sliceASize = m_partialWrite ? 96 : 473;
        const int sliceBOffs = m_partialWrite ? 503 : sliceAOffs + sliceASize;
        const int sliceBSize = mapSize - sliceBOffs;
        bool isOk            = true;

        // Setup reference data.
        refBuf.setSize(bufferSize);
        fillWithRandomBytes(refBuf.getPtr(), bufferSize, dataSeed);

        buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, bufferSize, refBuf.getPtr(), m_usage);

        // Do reference map.
        fillWithRandomBytes(refBuf.getPtr(mapOffset), mapSize, dataSeed & 0xabcdef);

        void *ptr = glMapBufferRange(m_bufferTarget, mapOffset, mapSize, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        deMemcpy(ptr, refBuf.getPtr(mapOffset), mapSize);

        glFlushMappedBufferRange(m_bufferTarget, sliceAOffs, sliceASize);
        GLU_CHECK_MSG("glFlushMappedBufferRange");
        glFlushMappedBufferRange(m_bufferTarget, sliceBOffs, sliceBSize);
        GLU_CHECK_MSG("glFlushMappedBufferRange");

        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        if (m_partialWrite)
        {
            if (!verifier.verify(buf, refBuf.getPtr(), mapOffset + sliceAOffs, sliceASize, m_bufferTarget))
                isOk = false;

            if (!verifier.verify(buf, refBuf.getPtr(), mapOffset + sliceBOffs, sliceBSize, m_bufferTarget))
                isOk = false;
        }
        else
        {
            if (!verifier.verify(buf, refBuf.getPtr(), mapOffset, mapSize, m_bufferTarget))
                isOk = false;
        }

        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    bool m_partialWrite;
    VerifyType m_verify;
};

class BufferMapUnsyncWriteCase : public BufferCase
{
public:
    BufferMapUnsyncWriteCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget,
                             uint32_t usage)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
    {
    }

    IterateResult iterate(void)
    {
        VertexArrayVerifier verifier(m_renderCtx, m_testCtx.getLog());
        uint32_t dataSeed = deStringHash(getName());
        ReferenceBuffer refBuf;
        uint32_t buf   = 0;
        bool isOk      = true;
        const int size = 1200;

        // Setup reference data.
        refBuf.setSize(size);
        fillWithRandomBytes(refBuf.getPtr(), size, dataSeed);

        buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, size, refBuf.getPtr(), m_usage);

        // Use for rendering.
        if (!verifier.verify(buf, refBuf.getPtr(), 0, size))
            isOk = false;
        // \note ReadPixels() implies Finish

        glBindBuffer(m_bufferTarget, buf);
        void *ptr = glMapBufferRange(m_bufferTarget, 0, size, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        fillWithRandomBytes(refBuf.getPtr(), size, dataSeed & 0xabcdef);
        deMemcpy(ptr, refBuf.getPtr(), size);

        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        // Synchronize.
        glFinish();

        if (!verifier.verify(buf, refBuf.getPtr(), 0, size))
            isOk = false;

        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
};

class BufferMapReadWriteCase : public BufferCase
{
public:
    BufferMapReadWriteCase(Context &context, const char *name, const char *desc, uint32_t bufferTarget, uint32_t usage,
                           int bufferSize, int mapOffset, int mapSize, VerifyType verify)
        : BufferCase(context.getTestContext(), context.getRenderContext(), name, desc)
        , m_bufferTarget(bufferTarget)
        , m_usage(usage)
        , m_bufferSize(bufferSize)
        , m_mapOffset(mapOffset)
        , m_mapSize(mapSize)
        , m_verify(verify)
    {
    }

    IterateResult iterate(void)
    {
        TestLog &log      = m_testCtx.getLog();
        uint32_t dataSeed = deStringHash(getName());
        uint32_t buf      = 0;
        ReferenceBuffer refBuf;
        BufferVerifier verifier(m_renderCtx, m_testCtx.getLog(), m_verify);
        bool isOk = true;

        // Setup reference data.
        refBuf.setSize(m_bufferSize);
        fillWithRandomBytes(refBuf.getPtr(), m_bufferSize, dataSeed);

        buf = genBuffer();
        glBindBuffer(m_bufferTarget, buf);
        glBufferData(m_bufferTarget, m_bufferSize, refBuf.getPtr(), m_usage);

        // Verify before mapping.
        if (!verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget))
            isOk = false;

        glBindBuffer(m_bufferTarget, buf);
        void *ptr = glMapBufferRange(m_bufferTarget, m_mapOffset, m_mapSize, GL_MAP_READ_BIT | GL_MAP_WRITE_BIT);
        GLU_CHECK_MSG("glMapBufferRange");
        TCU_CHECK(ptr);

        // Compare mapped ptr.
        if (!compareByteArrays(log, (const uint8_t *)ptr, refBuf.getPtr(m_mapOffset), m_mapSize))
            isOk = false;

        fillWithRandomBytes(refBuf.getPtr(m_mapOffset), m_mapSize, dataSeed & 0xabcdef);
        deMemcpy(ptr, refBuf.getPtr(m_mapOffset), m_mapSize);

        glUnmapBuffer(m_bufferTarget);
        GLU_CHECK_MSG("glUnmapBuffer");

        // Compare final buffer.
        if (!verifier.verify(buf, refBuf.getPtr(), 0, m_bufferSize, m_bufferTarget))
            isOk = false;

        deleteBuffer(buf);

        m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS : QP_TEST_RESULT_FAIL,
                                isOk ? "Pass" : "Buffer verification failed");
        return STOP;
    }

private:
    uint32_t m_bufferTarget;
    uint32_t m_usage;
    int m_bufferSize;
    int m_mapOffset;
    int m_mapSize;
    VerifyType m_verify;
};

BufferMapTests::BufferMapTests(Context &context) : TestCaseGroup(context, "map", "Buffer map tests")
{
}

BufferMapTests::~BufferMapTests(void)
{
}

void BufferMapTests::init(void)
{
    static const uint32_t bufferTargets[] = {
        GL_ARRAY_BUFFER,      GL_COPY_READ_BUFFER,    GL_COPY_WRITE_BUFFER,         GL_ELEMENT_ARRAY_BUFFER,
        GL_PIXEL_PACK_BUFFER, GL_PIXEL_UNPACK_BUFFER, GL_TRANSFORM_FEEDBACK_BUFFER, GL_UNIFORM_BUFFER};

    static const uint32_t usageHints[] = {GL_STREAM_DRAW,  GL_STREAM_READ,  GL_STREAM_COPY,
                                          GL_STATIC_DRAW,  GL_STATIC_READ,  GL_STATIC_COPY,
                                          GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_DYNAMIC_COPY};

    static const struct
    {
        const char *name;
        WriteType write;
    } bufferDataSources[] = {{"sub_data", WRITE_BUFFER_SUB_DATA}, {"map_write", WRITE_BUFFER_WRITE_MAP}};

    static const struct
    {
        const char *name;
        VerifyType verify;
    } bufferUses[] = {{"map_read", VERIFY_BUFFER_READ_MAP},
                      {"render_as_vertex_array", VERIFY_AS_VERTEX_ARRAY},
                      {"render_as_index_array", VERIFY_AS_INDEX_ARRAY}};

    // .read
    {
        tcu::TestCaseGroup *mapReadGroup =
            new tcu::TestCaseGroup(m_testCtx, "read", "Buffer read using glMapBufferRange()");
        addChild(mapReadGroup);

        // .[data src]
        for (int srcNdx = 0; srcNdx < DE_LENGTH_OF_ARRAY(bufferDataSources); srcNdx++)
        {
            WriteType write                = bufferDataSources[srcNdx].write;
            tcu::TestCaseGroup *writeGroup = new tcu::TestCaseGroup(m_testCtx, bufferDataSources[srcNdx].name, "");
            mapReadGroup->addChild(writeGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target       = bufferTargets[targetNdx];
                const uint32_t hint   = GL_STATIC_READ;
                const int size        = 1019;
                const int partialOffs = 17;
                const int partialSize = 501;

                writeGroup->addChild(new BufferMapReadCase(m_context,
                                                           (string(getBufferTargetName(target)) + "_full").c_str(), "",
                                                           target, hint, size, 0, size, write));
                writeGroup->addChild(new BufferMapReadCase(m_context,
                                                           (string(getBufferTargetName(target)) + "_partial").c_str(),
                                                           "", target, hint, size, partialOffs, partialSize, write));
            }
        }

        // .usage_hints
        {
            tcu::TestCaseGroup *hintsGroup =
                new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Different usage hints with glMapBufferRange()");
            mapReadGroup->addChild(hintsGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
                {
                    uint32_t target = bufferTargets[targetNdx];
                    uint32_t hint   = usageHints[hintNdx];
                    const int size  = 1019;
                    string name     = string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

                    hintsGroup->addChild(new BufferMapReadCase(m_context, name.c_str(), "", target, hint, size, 0, size,
                                                               WRITE_BUFFER_SUB_DATA));
                }
            }
        }
    }

    // .write
    {
        tcu::TestCaseGroup *mapWriteGroup =
            new tcu::TestCaseGroup(m_testCtx, "write", "Buffer write using glMapBufferRange()");
        addChild(mapWriteGroup);

        // .[verify type]
        for (int useNdx = 0; useNdx < DE_LENGTH_OF_ARRAY(bufferUses); useNdx++)
        {
            VerifyType verify            = bufferUses[useNdx].verify;
            tcu::TestCaseGroup *useGroup = new tcu::TestCaseGroup(m_testCtx, bufferUses[useNdx].name, "");
            mapWriteGroup->addChild(useGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target       = bufferTargets[targetNdx];
                uint32_t hint         = GL_STATIC_DRAW;
                const int size        = 1019;
                const int partialOffs = 17;
                const int partialSize = 501;
                string name           = string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

                useGroup->addChild(new BufferMapWriteCase(m_context,
                                                          (string(getBufferTargetName(target)) + "_full").c_str(), "",
                                                          target, hint, size, verify));
                useGroup->addChild(
                    new BufferPartialMapWriteCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),
                                                  "", target, hint, size, partialOffs, partialSize, verify));
            }
        }

        // .usage_hints
        {
            tcu::TestCaseGroup *hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Usage hints");
            mapWriteGroup->addChild(hintsGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
                {
                    uint32_t target = bufferTargets[targetNdx];
                    uint32_t hint   = usageHints[hintNdx];
                    const int size  = 1019;
                    string name     = string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

                    hintsGroup->addChild(new BufferMapWriteCase(m_context, name.c_str(), "", target, hint, size,
                                                                VERIFY_AS_VERTEX_ARRAY));
                }
            }
        }

        // .invalidate
        {
            tcu::TestCaseGroup *invalidateGroup = new tcu::TestCaseGroup(m_testCtx, "invalidate", "Buffer invalidate");
            mapWriteGroup->addChild(invalidateGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target = bufferTargets[targetNdx];
                uint32_t hint   = GL_STATIC_DRAW;

                invalidateGroup->addChild(
                    new BufferMapInvalidateCase(m_context, (string(getBufferTargetName(target)) + "_write_all").c_str(),
                                                "", target, hint, false, VERIFY_AS_VERTEX_ARRAY));
                invalidateGroup->addChild(new BufferMapInvalidateCase(
                    m_context, (string(getBufferTargetName(target)) + "_write_partial").c_str(), "", target, hint, true,
                    VERIFY_AS_VERTEX_ARRAY));
            }
        }

        // .partial_invalidate
        {
            tcu::TestCaseGroup *invalidateGroup =
                new tcu::TestCaseGroup(m_testCtx, "partial_invalidate", "Partial invalidate");
            mapWriteGroup->addChild(invalidateGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target = bufferTargets[targetNdx];
                uint32_t hint   = GL_STATIC_DRAW;

                invalidateGroup->addChild(new BufferMapPartialInvalidateCase(
                    m_context, (string(getBufferTargetName(target)) + "_write_all").c_str(), "", target, hint, false,
                    VERIFY_AS_VERTEX_ARRAY));
                invalidateGroup->addChild(new BufferMapPartialInvalidateCase(
                    m_context, (string(getBufferTargetName(target)) + "_write_partial").c_str(), "", target, hint, true,
                    VERIFY_AS_VERTEX_ARRAY));
            }
        }

        // .explicit_flush
        {
            tcu::TestCaseGroup *flushGroup = new tcu::TestCaseGroup(m_testCtx, "explicit_flush", "Explicit flush");
            mapWriteGroup->addChild(flushGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target = bufferTargets[targetNdx];
                uint32_t hint   = GL_STATIC_DRAW;

                flushGroup->addChild(
                    new BufferMapExplicitFlushCase(m_context, (string(getBufferTargetName(target)) + "_all").c_str(),
                                                   "", target, hint, false, VERIFY_AS_VERTEX_ARRAY));
                flushGroup->addChild(new BufferMapExplicitFlushCase(
                    m_context, (string(getBufferTargetName(target)) + "_partial").c_str(), "", target, hint, true,
                    VERIFY_AS_VERTEX_ARRAY));
            }
        }

        // .unsynchronized
        {
            tcu::TestCaseGroup *unsyncGroup = new tcu::TestCaseGroup(m_testCtx, "unsynchronized", "Unsynchronized map");
            mapWriteGroup->addChild(unsyncGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target = bufferTargets[targetNdx];
                uint32_t hint   = GL_STATIC_DRAW;

                unsyncGroup->addChild(
                    new BufferMapUnsyncWriteCase(m_context, getBufferTargetName(target), "", target, hint));
            }
        }
    }

    // .read_write
    {
        tcu::TestCaseGroup *mapReadWriteGroup =
            new tcu::TestCaseGroup(m_testCtx, "read_write", "Buffer read and write using glMapBufferRange()");
        addChild(mapReadWriteGroup);

        // .[verify type]
        for (int useNdx = 0; useNdx < DE_LENGTH_OF_ARRAY(bufferUses); useNdx++)
        {
            VerifyType verify            = bufferUses[useNdx].verify;
            tcu::TestCaseGroup *useGroup = new tcu::TestCaseGroup(m_testCtx, bufferUses[useNdx].name, "");
            mapReadWriteGroup->addChild(useGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                uint32_t target       = bufferTargets[targetNdx];
                uint32_t hint         = GL_STATIC_DRAW;
                const int size        = 1019;
                const int partialOffs = 17;
                const int partialSize = 501;
                string name           = string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

                useGroup->addChild(new BufferMapReadWriteCase(m_context,
                                                              (string(getBufferTargetName(target)) + "_full").c_str(),
                                                              "", target, hint, size, 0, size, verify));
                useGroup->addChild(
                    new BufferMapReadWriteCase(m_context, (string(getBufferTargetName(target)) + "_partial").c_str(),
                                               "", target, hint, size, partialOffs, partialSize, verify));
            }
        }

        // .usage_hints
        {
            tcu::TestCaseGroup *hintsGroup = new tcu::TestCaseGroup(m_testCtx, "usage_hints", "Usage hints");
            mapReadWriteGroup->addChild(hintsGroup);

            for (int targetNdx = 0; targetNdx < DE_LENGTH_OF_ARRAY(bufferTargets); targetNdx++)
            {
                for (int hintNdx = 0; hintNdx < DE_LENGTH_OF_ARRAY(usageHints); hintNdx++)
                {
                    uint32_t target = bufferTargets[targetNdx];
                    uint32_t hint   = usageHints[hintNdx];
                    const int size  = 1019;
                    string name     = string(getBufferTargetName(target)) + "_" + getUsageHintName(hint);

                    hintsGroup->addChild(new BufferMapReadWriteCase(m_context, name.c_str(), "", target, hint, size, 0,
                                                                    size, VERIFY_AS_VERTEX_ARRAY));
                }
            }
        }
    }
}

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