//
// Copyright (c) 2014 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.
//
// TexSubImageBenchmark:
//   Performace test for ANGLE texture updates.
//

#include <sstream>

#include "ANGLEPerfTest.h"
#include "shader_utils.h"

using namespace angle;

namespace
{

struct TexSubImageParams final : public RenderTestParams
{
    TexSubImageParams()
    {
        // Common default parameters
        majorVersion = 2;
        minorVersion = 0;
        windowWidth  = 512;
        windowHeight = 512;

        imageWidth     = 1024;
        imageHeight    = 1024;
        subImageWidth  = 64;
        subImageHeight = 64;
        iterations     = 9;
    }

    std::string suffix() const override;

    // Static parameters
    int imageWidth;
    int imageHeight;
    int subImageWidth;
    int subImageHeight;
    unsigned int iterations;
};

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

class TexSubImageBenchmark : public ANGLERenderTest,
                             public ::testing::WithParamInterface<TexSubImageParams>
{
  public:
    TexSubImageBenchmark();

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

  private:
    GLuint createTexture();

    // Handle to a program object
    GLuint mProgram;

    // Attribute locations
    GLint mPositionLoc;
    GLint mTexCoordLoc;

    // Sampler location
    GLint mSamplerLoc;

    // Texture handle
    GLuint mTexture;

    // Buffer handle
    GLuint mVertexBuffer;
    GLuint mIndexBuffer;

    GLubyte *mPixels;
};

std::string TexSubImageParams::suffix() const
{
    // TODO(jmadill)
    return RenderTestParams::suffix();
}

TexSubImageBenchmark::TexSubImageBenchmark()
    : ANGLERenderTest("TexSubImage", GetParam(), {"GL_EXT_texture_storage"}),
      mProgram(0),
      mPositionLoc(-1),
      mTexCoordLoc(-1),
      mSamplerLoc(-1),
      mTexture(0),
      mVertexBuffer(0),
      mIndexBuffer(0),
      mPixels(nullptr)
{
}

GLuint TexSubImageBenchmark::createTexture()
{
    const auto &params = GetParam();

    assert(params.iterations > 0);

    // Use tightly packed data
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);

    // Generate a texture object
    GLuint texture;
    glGenTextures(1, &texture);

    // Bind the texture object
    glBindTexture(GL_TEXTURE_2D, texture);

    glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, params.imageWidth, params.imageHeight);

    // Set the filtering mode
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    return texture;
}

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

    const std::string vs =
        R"(attribute vec4 a_position;
        attribute vec2 a_texCoord;
        varying vec2 v_texCoord;
        void main()
        {
            gl_Position = a_position;
            v_texCoord  = a_texCoord;
        })";

    const std::string fs =
        R"(precision mediump float;
        varying vec2 v_texCoord;
        uniform sampler2D s_texture;
        void main()
        {
            gl_FragColor = texture2D(s_texture, v_texCoord);
        })";

    mProgram = CompileProgram(vs, fs);
    ASSERT_NE(0u, mProgram);

    // Get the attribute locations
    mPositionLoc = glGetAttribLocation(mProgram, "a_position");
    mTexCoordLoc = glGetAttribLocation(mProgram, "a_texCoord");

    // Get the sampler location
    mSamplerLoc = glGetUniformLocation(mProgram, "s_texture");

    // Build the vertex buffer
    GLfloat vertices[] = {
        -0.5f, 0.5f,  0.0f,  // Position 0
        0.0f,  0.0f,         // TexCoord 0
        -0.5f, -0.5f, 0.0f,  // Position 1
        0.0f,  1.0f,         // TexCoord 1
        0.5f,  -0.5f, 0.0f,  // Position 2
        1.0f,  1.0f,         // TexCoord 2
        0.5f,  0.5f,  0.0f,  // Position 3
        1.0f,  0.0f          // TexCoord 3
    };

    glGenBuffers(1, &mVertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    GLushort indices[] = {0, 1, 2, 0, 2, 3};
    glGenBuffers(1, &mIndexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    // Load the texture
    mTexture = createTexture();

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

    mPixels = new GLubyte[params.subImageWidth * params.subImageHeight * 4];

    // Fill the pixels structure with random data:
    for (int y = 0; y < params.subImageHeight; ++y)
    {
        for (int x = 0; x < params.subImageWidth; ++x)
        {
            int offset          = (x + (y * params.subImageWidth)) * 4;
            mPixels[offset + 0] = rand() % 255;  // Red
            mPixels[offset + 1] = rand() % 255;  // Green
            mPixels[offset + 2] = rand() % 255;  // Blue
            mPixels[offset + 3] = 255;           // Alpha
        }
    }

    ASSERT_GL_NO_ERROR();
}

void TexSubImageBenchmark::destroyBenchmark()
{
    glDeleteProgram(mProgram);
    glDeleteBuffers(1, &mVertexBuffer);
    glDeleteBuffers(1, &mIndexBuffer);
    glDeleteTextures(1, &mTexture);
    delete[] mPixels;
}

void TexSubImageBenchmark::drawBenchmark()
{
    // Set the viewport
    glViewport(0, 0, getWindow()->getWidth(), getWindow()->getHeight());

    // Clear the color buffer
    glClear(GL_COLOR_BUFFER_BIT);

    // Use the program object
    glUseProgram(mProgram);

    // Bind the buffers
    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);

    // Load the vertex position
    glVertexAttribPointer(mPositionLoc, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);
    // Load the texture coordinate
    glVertexAttribPointer(mTexCoordLoc, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat),
                          reinterpret_cast<void *>(3 * sizeof(GLfloat)));

    glEnableVertexAttribArray(mPositionLoc);
    glEnableVertexAttribArray(mTexCoordLoc);

    // Bind the texture
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mTexture);

    // Set the texture sampler to texture unit to 0
    glUniform1i(mSamplerLoc, 0);

    ASSERT_GL_NO_ERROR();

    const auto &params = GetParam();

    for (unsigned int iteration = 0; iteration < params.iterations; ++iteration)
    {
        glTexSubImage2D(GL_TEXTURE_2D, 0, rand() % (params.imageWidth - params.subImageWidth),
                        rand() % (params.imageHeight - params.subImageHeight), params.subImageWidth,
                        params.subImageHeight, GL_RGBA, GL_UNSIGNED_BYTE, mPixels);

        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);
    }

    ASSERT_GL_NO_ERROR();
}

TexSubImageParams D3D11Params()
{
    TexSubImageParams params;
    params.eglParameters = egl_platform::D3D11();
    return params;
}

TexSubImageParams D3D9Params()
{
    TexSubImageParams params;
    params.eglParameters = egl_platform::D3D9();
    return params;
}

TexSubImageParams OpenGLOrGLESParams()
{
    TexSubImageParams params;
    params.eglParameters = egl_platform::OPENGL_OR_GLES(false);
    return params;
}

TexSubImageParams VulkanParams()
{
    TexSubImageParams params;
    params.eglParameters = egl_platform::VULKAN();
    return params;
}

}  // namespace

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

ANGLE_INSTANTIATE_TEST(TexSubImageBenchmark,
                       D3D11Params(),
                       D3D9Params(),
                       OpenGLOrGLESParams(),
                       VulkanParams());
