//
// Copyright 2021 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// MapBufferRangeBenchmark:
//   Performance test for ANGLE GLES mapped buffers.
//

#include "ANGLEPerfTest.h"
#include "common/unsafe_buffers.h"

#include <sstream>
#include <vector>

#include "common/debug.h"
#include "test_utils/draw_call_perf_utils.h"

using namespace angle;

namespace
{
constexpr unsigned int kIterationsPerStep = 10;

struct MapBufferRangeParams final : public RenderTestParams
{
    MapBufferRangeParams()
    {
        // Common default values
        majorVersion = 3;
        minorVersion = 0;
        windowWidth  = 512;
        windowHeight = 512;
        // Test intentionally small update versus buffer size to begin with.
        updateSize        = 32768;
        updateOffset      = 0;
        bufferSize        = 1048576;
        iterationsPerStep = kIterationsPerStep;
        access            = GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT;
    }

    std::string story() const override;

    GLboolean vertexNormalized;
    GLenum vertexType;
    GLint vertexComponentCount;

    // static parameters
    GLsizeiptr updateSize;
    GLsizeiptr updateOffset;
    GLsizeiptr bufferSize;
    GLbitfield access;
};

std::ostream &operator<<(std::ostream &os, const MapBufferRangeParams &params)
{
    os << params.backendAndStory().substr(1);
    return os;
}

class MapBufferRangeBenchmark : public ANGLERenderTest,
                                public ::testing::WithParamInterface<MapBufferRangeParams>
{
  public:
    MapBufferRangeBenchmark();

    void initializeBenchmark() override;
    void destroyBenchmark() override;
    void drawBenchmark() override;

  private:
    GLuint mProgram;
    GLuint mBuffer;
    std::vector<uint8_t> mVertexData;
    int mTriSize;
    int mNumUpdateTris;
};

const GLfloat *GetFloatData(GLint componentCount)
{
    static GLfloat vertices2[] = {
        1, 2, 0, 0, 2, 0,
    };

    static GLfloat vertices3[] = {
        1, 2, 1, 0, 0, 1, 2, 0, 1,
    };

    static GLfloat vertices4[] = {
        1, 2, 1, 3, 0, 0, 1, 3, 2, 0, 1, 3,
    };

    switch (componentCount)
    {
        case 2:
            return vertices2;
        case 3:
            return vertices3;
        case 4:
            return vertices4;
        default:
            UNREACHABLE();
    }

    return 0;
}

template <class T>
GLsizei GetNormalizedData(GLsizeiptr numElements,
                          const GLfloat *floatData,
                          std::vector<uint8_t> *data)
{
    GLsizei triDataSize = sizeof(T) * numElements;
    data->resize(triDataSize);

    T *destPtr = ANGLE_UNSAFE_TODO(reinterpret_cast<T *>(data->data()));

    for (GLsizeiptr dataIndex = 0; dataIndex < numElements; dataIndex++)
    {
        GLfloat scaled = ANGLE_UNSAFE_TODO(floatData[dataIndex]) * 0.25f;
        ANGLE_UNSAFE_TODO(destPtr[dataIndex]) =
            static_cast<T>(scaled * static_cast<GLfloat>(std::numeric_limits<T>::max()));
    }

    return triDataSize;
}

template <class T>
GLsizei GetIntData(GLsizeiptr numElements, const GLfloat *floatData, std::vector<uint8_t> *data)
{
    GLsizei triDataSize = sizeof(T) * numElements;
    data->resize(triDataSize);

    T *destPtr = ANGLE_UNSAFE_TODO(reinterpret_cast<T *>(data->data()));

    for (GLsizeiptr dataIndex = 0; dataIndex < numElements; dataIndex++)
    {
        ANGLE_UNSAFE_TODO(destPtr[dataIndex]) =
            static_cast<T>(ANGLE_UNSAFE_TODO(floatData[dataIndex]));
    }

    return triDataSize;
}

GLsizei GetVertexData(GLenum type,
                      GLint componentCount,
                      GLboolean normalized,
                      std::vector<uint8_t> *data)
{
    GLsizei triDataSize      = 0;
    const GLfloat *floatData = GetFloatData(componentCount);

    if (type == GL_FLOAT)
    {
        triDataSize = sizeof(GLfloat) * componentCount * 3;
        data->resize(triDataSize);
        ANGLE_UNSAFE_TODO(memcpy(data->data(), floatData, triDataSize));
    }
    else if (normalized == GL_TRUE)
    {
        GLsizeiptr numElements = componentCount * 3;

        switch (type)
        {
            case GL_BYTE:
                triDataSize = GetNormalizedData<GLbyte>(numElements, floatData, data);
                break;
            case GL_SHORT:
                triDataSize = GetNormalizedData<GLshort>(numElements, floatData, data);
                break;
            case GL_INT:
                triDataSize = GetNormalizedData<GLint>(numElements, floatData, data);
                break;
            case GL_UNSIGNED_BYTE:
                triDataSize = GetNormalizedData<GLubyte>(numElements, floatData, data);
                break;
            case GL_UNSIGNED_SHORT:
                triDataSize = GetNormalizedData<GLushort>(numElements, floatData, data);
                break;
            case GL_UNSIGNED_INT:
                triDataSize = GetNormalizedData<GLuint>(numElements, floatData, data);
                break;
            default:
                UNREACHABLE();
        }
    }
    else
    {
        GLsizeiptr numElements = componentCount * 3;

        switch (type)
        {
            case GL_BYTE:
                triDataSize = GetIntData<GLbyte>(numElements, floatData, data);
                break;
            case GL_SHORT:
                triDataSize = GetIntData<GLshort>(numElements, floatData, data);
                break;
            case GL_INT:
                triDataSize = GetIntData<GLint>(numElements, floatData, data);
                break;
            case GL_UNSIGNED_BYTE:
                triDataSize = GetIntData<GLubyte>(numElements, floatData, data);
                break;
            case GL_UNSIGNED_SHORT:
                triDataSize = GetIntData<GLushort>(numElements, floatData, data);
                break;
            case GL_UNSIGNED_INT:
                triDataSize = GetIntData<GLuint>(numElements, floatData, data);
                break;
            default:
                assert(0);
        }
    }

    return triDataSize;
}

std::string MapBufferRangeParams::story() const
{
    std::stringstream strstr;

    strstr << RenderTestParams::story();

    if (vertexNormalized)
    {
        strstr << "_norm";
    }

    switch (vertexType)
    {
        case GL_FLOAT:
            strstr << "_float";
            break;
        case GL_INT:
            strstr << "_int";
            break;
        case GL_BYTE:
            strstr << "_byte";
            break;
        case GL_SHORT:
            strstr << "_short";
            break;
        case GL_UNSIGNED_INT:
            strstr << "_uint";
            break;
        case GL_UNSIGNED_BYTE:
            strstr << "_ubyte";
            break;
        case GL_UNSIGNED_SHORT:
            strstr << "_ushort";
            break;
        default:
            UNREACHABLE();
    }

    strstr << vertexComponentCount;
    strstr << "_updateOffset" << updateOffset;
    strstr << "_updateSize" << updateSize;
    strstr << "_bufferSize" << bufferSize;
    strstr << "_access0x" << std::hex << access;

    return strstr.str();
}

MapBufferRangeBenchmark::MapBufferRangeBenchmark()
    : ANGLERenderTest("MapBufferRange", GetParam()),
      mProgram(0),
      mBuffer(0),
      mTriSize(0),
      mNumUpdateTris(0)
{}

void MapBufferRangeBenchmark::initializeBenchmark()
{
    const auto &params = GetParam();

    ASSERT_LT(1, params.vertexComponentCount);
    ASSERT_LE(params.updateSize, params.bufferSize);
    ASSERT_LT(params.updateOffset, params.bufferSize);
    ASSERT_LE(params.updateOffset + params.updateSize, params.bufferSize);

    mProgram = SetupSimpleScaleAndOffsetProgram();
    ASSERT_NE(0u, mProgram);

    if (params.vertexNormalized == GL_TRUE)
    {
        GLfloat scale  = 2.0f;
        GLfloat offset = -0.5f;
        glUniform1f(glGetUniformLocation(mProgram, "uScale"), scale);
        glUniform1f(glGetUniformLocation(mProgram, "uOffset"), offset);
    }

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    glGenBuffers(1, &mBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, mBuffer);
    glBufferData(GL_ARRAY_BUFFER, params.bufferSize, nullptr, GL_DYNAMIC_DRAW);

    glVertexAttribPointer(0, params.vertexComponentCount, params.vertexType,
                          params.vertexNormalized, 0, 0);
    glEnableVertexAttribArray(0);

    mTriSize = GetVertexData(params.vertexType, params.vertexComponentCount,
                             params.vertexNormalized, &mVertexData);

    mNumUpdateTris = static_cast<int>(params.updateSize / mTriSize);
    int totalTris  = static_cast<int>(params.updateSize / mTriSize);

    mVertexData.resize(params.bufferSize);

    for (int i = 1; i < totalTris; ++i)
    {
        ANGLE_UNSAFE_TODO(memcpy(mVertexData.data() + i * mTriSize, mVertexData.data(), mTriSize));
    }

    if (params.updateSize == 0)
    {
        mNumUpdateTris = 1;
        glBufferSubData(GL_ARRAY_BUFFER, 0, mVertexData.size(), mVertexData.data());
    }

    // Set the viewport
    glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());

    ASSERT_GL_NO_ERROR();
}

void MapBufferRangeBenchmark::destroyBenchmark()
{
    glDeleteProgram(mProgram);
    glDeleteBuffers(1, &mBuffer);
}

void MapBufferRangeBenchmark::drawBenchmark()
{
    glClear(GL_COLOR_BUFFER_BIT);

    const auto &params = GetParam();

    for (unsigned int it = 0; it < params.iterationsPerStep; it++)
    {
        if (params.updateSize > 0)
        {
            void *mapPtr = glMapBufferRange(GL_ARRAY_BUFFER, params.updateOffset, params.updateSize,
                                            params.access);
            ANGLE_UNSAFE_TODO(
                memcpy(mapPtr, mVertexData.data() + params.updateOffset, params.updateSize));
            glUnmapBuffer(GL_ARRAY_BUFFER);
        }

        glDrawArrays(GL_TRIANGLES, params.updateOffset / mTriSize, 3 * mNumUpdateTris);
    }

    ASSERT_GL_NO_ERROR();
}

MapBufferRangeParams BufferUpdateD3D11Params()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::D3D11();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    return params;
}

MapBufferRangeParams BufferUpdateMetalParams()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::METAL();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    return params;
}

MapBufferRangeParams BufferUpdateMetalParamsLargeUpdate()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::METAL();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateSize           = 524288;
    return params;
}

MapBufferRangeParams BufferUpdateOpenGLOrGLESParams()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::OPENGL_OR_GLES();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParams()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsMidBuffer()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateOffset         = 524288;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsLargeUpdate()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateSize           = 524288;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsFullBuffer()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateSize           = 1048576;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsTinyUpdate()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateSize           = 128;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsNonPowerOf2()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateSize           = 32000;
    params.bufferSize           = 800000;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsUnsynchronized()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.access               = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
    return params;
}

MapBufferRangeParams BufferUpdateVulkanParamsLargeUpdateUnsynchronized()
{
    MapBufferRangeParams params;
    params.eglParameters        = egl_platform::VULKAN();
    params.vertexType           = GL_FLOAT;
    params.vertexComponentCount = 4;
    params.vertexNormalized     = GL_FALSE;
    params.updateSize           = 524288;
    params.access               = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT;
    return params;
}

TEST_P(MapBufferRangeBenchmark, Run)
{
    run();
}

ANGLE_INSTANTIATE_TEST(MapBufferRangeBenchmark,
                       BufferUpdateD3D11Params(),
                       BufferUpdateMetalParams(),
                       BufferUpdateMetalParamsLargeUpdate(),
                       BufferUpdateOpenGLOrGLESParams(),
                       BufferUpdateVulkanParams(),
                       BufferUpdateVulkanParamsMidBuffer(),
                       BufferUpdateVulkanParamsLargeUpdate(),
                       BufferUpdateVulkanParamsFullBuffer(),
                       BufferUpdateVulkanParamsTinyUpdate(),
                       BufferUpdateVulkanParamsNonPowerOf2(),
                       BufferUpdateVulkanParamsUnsynchronized(),
                       BufferUpdateVulkanParamsLargeUpdateUnsynchronized());

}  // namespace
