//
// Copyright 2015 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.
//

// IndexBufferOffsetTest.cpp: Test glDrawElements with an offset and an index buffer

#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/test_utils.h"

using namespace angle;

class IndexBufferOffsetTest : public ANGLETest
{
  protected:
    IndexBufferOffsetTest()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
    }

    void testSetUp() override
    {
        constexpr char kVS[] =
            R"(precision highp float;
            attribute vec2 position;

            void main()
            {
                gl_Position = vec4(position, 0.0, 1.0);
            })";

        constexpr char kFS[] =
            R"(precision highp float;
            uniform vec4 color;

            void main()
            {
                gl_FragColor = color;
            })";

        mProgram = CompileProgram(kVS, kFS);
        ASSERT_NE(0u, mProgram);

        mColorUniformLocation      = glGetUniformLocation(mProgram, "color");
        mPositionAttributeLocation = glGetAttribLocation(mProgram, "position");

        const GLfloat vertices[] = {-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f};
        glGenBuffers(1, &mVertexBuffer);
        glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_STATIC_DRAW);

        glGenBuffers(1, &mIndexBuffer);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBuffer);
    }

    void testTearDown() override
    {
        glDeleteBuffers(1, &mVertexBuffer);
        glDeleteBuffers(1, &mIndexBuffer);
        glDeleteProgram(mProgram);
    }

    void preTestUpdateBuffer(GLuint framebuffer, GLuint texture, GLuint buffer, GLsizei size)
    {
        GLsizei uboSize = std::max(size, 16);
        const std::vector<uint32_t> initialData((uboSize + 3) / 4, 0x1234567u);

        glBindTexture(GL_TEXTURE_2D, texture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture,
                               0);

        glBindBuffer(GL_UNIFORM_BUFFER, buffer);
        glBufferData(GL_UNIFORM_BUFFER, uboSize, initialData.data(), GL_DYNAMIC_DRAW);
        glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);

        constexpr char kVerifyUBO[] = R"(#version 300 es
precision mediump float;
uniform block {
    uint data;
} ubo;
out vec4 colorOut;
void main()
{
    if (ubo.data == 0x1234567u)
        colorOut = vec4(0, 1.0, 0, 1.0);
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

        ANGLE_GL_PROGRAM(verifyUbo, essl3_shaders::vs::Simple(), kVerifyUBO);

        glDisable(GL_BLEND);
        drawQuad(verifyUbo, essl3_shaders::PositionAttrib(), 0.5);

        EXPECT_GL_NO_ERROR();

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    void runTest(GLenum type,
                 int typeWidth,
                 void *indexDataIn,
                 bool smallUpdates,
                 bool useBuffersAsUboFirst)
    {
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        size_t indexDataWidth = 6 * typeWidth;

        std::vector<GLubyte> indexData(6 * 3 * sizeof(GLuint), 0);
        memcpy(indexData.data() + indexDataWidth, indexDataIn, indexDataWidth);

        GLFramebuffer elementUpdateFbo;
        GLTexture elementUpdateTex;

        if (useBuffersAsUboFirst)
        {
            preTestUpdateBuffer(elementUpdateFbo, elementUpdateTex, mIndexBuffer,
                                3 * indexDataWidth);
        }
        else
        {
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, 3 * indexDataWidth, nullptr, GL_DYNAMIC_DRAW);
        }

        if (smallUpdates)
        {
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, indexDataWidth, indexData.data());
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, indexDataWidth, indexDataWidth,
                            indexData.data() + indexDataWidth);
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 2 * indexDataWidth, indexDataWidth,
                            indexData.data() + 2 * indexDataWidth);
        }
        else
        {
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, 3 * indexDataWidth, indexData.data());
        }

        glUseProgram(mProgram);

        glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
        glVertexAttribPointer(mPositionAttributeLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
        glEnableVertexAttribArray(mPositionAttributeLocation);

        glUniform4f(mColorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);

        for (int i = 0; i < 16; i++)
        {
            glDrawElements(GL_TRIANGLES, 6, type, reinterpret_cast<void *>(indexDataWidth));
            EXPECT_PIXEL_COLOR_EQ(64, 64, GLColor::red);
        }

        if (smallUpdates)
        {
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, indexDataWidth, indexDataWidth,
                            indexData.data());
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 2 * indexDataWidth, indexDataWidth,
                            indexData.data() + indexDataWidth);
        }
        else
        {
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, indexDataWidth, 2 * indexDataWidth,
                            indexData.data());
        }

        glUniform4f(mColorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
        glDrawElements(GL_TRIANGLES, 6, type, reinterpret_cast<void *>(indexDataWidth * 2));
        EXPECT_PIXEL_COLOR_EQ(64, 64, GLColor::green);

        if (useBuffersAsUboFirst)
        {
            glBindFramebuffer(GL_FRAMEBUFFER, elementUpdateFbo);
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
        }

        EXPECT_GL_NO_ERROR();
        swapBuffers();
    }

    GLuint mProgram;
    GLint mColorUniformLocation;
    GLint mPositionAttributeLocation;
    GLuint mVertexBuffer;
    GLuint mIndexBuffer;
};

class IndexBufferOffsetTestES3 : public IndexBufferOffsetTest
{};

// Test using an offset for an UInt8 index buffer
TEST_P(IndexBufferOffsetTest, UInt8Index)
{
    GLubyte indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_BYTE, 1, indexData, false, false);
}

// Test using an offset for an UInt16 index buffer
TEST_P(IndexBufferOffsetTest, UInt16Index)
{
    GLushort indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_SHORT, 2, indexData, false, false);
}

// Test using an offset for an UInt32 index buffer
TEST_P(IndexBufferOffsetTest, UInt32Index)
{
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                       !IsGLExtensionEnabled("GL_OES_element_index_uint"));

    GLuint indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_INT, 4, indexData, false, false);
}

// Test using an offset for an UInt8 index buffer with small buffer updates
TEST_P(IndexBufferOffsetTest, UInt8IndexSmallUpdates)
{
    GLubyte indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_BYTE, 1, indexData, true, false);
}

// Test using an offset for an UInt16 index buffer with small buffer updates
TEST_P(IndexBufferOffsetTest, UInt16IndexSmallUpdates)
{
    GLushort indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_SHORT, 2, indexData, true, false);
}

// Test using an offset for an UInt32 index buffer with small buffer updates
TEST_P(IndexBufferOffsetTest, UInt32IndexSmallUpdates)
{
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                       !IsGLExtensionEnabled("GL_OES_element_index_uint"));

    GLuint indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_INT, 4, indexData, true, false);
}

// Test using an offset for an UInt8 index buffer after uploading data to a buffer that is in use
TEST_P(IndexBufferOffsetTestES3, UseAsUBOThenUpdateThenUInt8Index)
{
    // http://anglebug.com/5950
    ANGLE_SKIP_TEST_IF(IsAMD() && IsVulkan() && IsWindows());

    // http://anglebug.com/5957
    ANGLE_SKIP_TEST_IF(IsVulkan() && (IsPixel2() || IsPixel2XL()));

    GLubyte indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_BYTE, 1, indexData, false, true);
}

// Test using an offset for an UInt16 index buffer after uploading data to a buffer that is in use
TEST_P(IndexBufferOffsetTestES3, UseAsUBOThenUpdateThenUInt16Index)
{
    // http://anglebug.com/5957
    ANGLE_SKIP_TEST_IF(IsVulkan() && (IsPixel2() || IsPixel2XL()));

    GLushort indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_SHORT, 2, indexData, false, true);
}

// Test using an offset for an UInt32 index buffer after uploading data to a buffer that is in use
TEST_P(IndexBufferOffsetTestES3, UseAsUBOThenUpdateThenUInt32Index)
{
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                       !IsGLExtensionEnabled("GL_OES_element_index_uint"));

    // http://anglebug.com/5957
    ANGLE_SKIP_TEST_IF(IsVulkan() && (IsPixel2() || IsPixel2XL()));

    GLuint indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_INT, 4, indexData, false, true);
}

// Test using an offset for an UInt8 index buffer after uploading data to a buffer that is in use,
// with small buffer updates
TEST_P(IndexBufferOffsetTestES3, UseAsUBOThenUpdateThenUInt8IndexSmallUpdates)
{
    // http://anglebug.com/5950
    ANGLE_SKIP_TEST_IF(IsAMD() && IsVulkan() && IsWindows());

    // http://anglebug.com/5957
    ANGLE_SKIP_TEST_IF(IsVulkan() && (IsPixel2() || IsPixel2XL()));

    GLubyte indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_BYTE, 1, indexData, true, true);
}

// Test using an offset for an UInt16 index buffer after uploading data to a buffer that is in use,
// with small buffer updates
TEST_P(IndexBufferOffsetTestES3, UseAsUBOThenUpdateThenUInt16IndexSmallUpdates)
{
    // http://anglebug.com/5957
    ANGLE_SKIP_TEST_IF(IsVulkan() && (IsPixel2() || IsPixel2XL()));

    GLushort indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_SHORT, 2, indexData, true, true);
}

// Test using an offset for an UInt32 index buffer after uploading data to a buffer that is in use,
// with small buffer updates
TEST_P(IndexBufferOffsetTestES3, UseAsUBOThenUpdateThenUInt32IndexSmallUpdates)
{
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3 &&
                       !IsGLExtensionEnabled("GL_OES_element_index_uint"));

    // http://anglebug.com/5957
    ANGLE_SKIP_TEST_IF(IsVulkan() && (IsPixel2() || IsPixel2XL()));

    GLuint indexData[] = {0, 1, 2, 1, 2, 3};
    runTest(GL_UNSIGNED_INT, 4, indexData, true, true);
}

// Uses index buffer offset and 2 drawElement calls one of the other, makes sure the second
// drawElement call will use the correct offset.
TEST_P(IndexBufferOffsetTest, DrawAtDifferentOffsets)
{
    GLushort indexData[] = {0, 1, 2, 1, 2, 3};
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    size_t indexDataWidth = 6 * sizeof(GLushort);

    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexDataWidth, indexData, GL_DYNAMIC_DRAW);
    glUseProgram(mProgram);

    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glVertexAttribPointer(mPositionAttributeLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(mPositionAttributeLocation);

    glUniform4f(mColorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);

    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, 0);
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT,
                   reinterpret_cast<void *>(indexDataWidth / 2));

    // Check the upper left triangle
    EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 4, GLColor::red);

    // Check the down right triangle
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);

    EXPECT_GL_NO_ERROR();
}

// Uses index buffer offset and 2 drawElement calls one of the other, one has aligned
// offset and one doesn't
TEST_P(IndexBufferOffsetTest, DrawAtDifferentOffsetAlignments)
{
    GLubyte indexData8[]   = {0, 1, 0, 1, 2, 3};
    GLushort indexData16[] = {0, 1, 2, 1, 2, 3};
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    size_t indexDataWidth16 = 6 * sizeof(GLushort);

    GLuint buffer[2];
    glGenBuffers(2, buffer);

    glUseProgram(mProgram);
    glUniform4f(mColorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);

    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glVertexAttribPointer(mPositionAttributeLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(mPositionAttributeLocation);

    // 8 bit index with aligned offset
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData8), indexData8, GL_DYNAMIC_DRAW);

    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, reinterpret_cast<void *>(2));

    // 16 bits index buffer, which unaligned offset
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexDataWidth16, indexData16, GL_DYNAMIC_DRAW);

    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT,
                   reinterpret_cast<void *>(indexDataWidth16 / 2));

    // Check the upper left triangle
    EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 4, GLColor::red);

    // Check the down right triangle
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);

    EXPECT_GL_NO_ERROR();
}

// Uses index buffer offset and 2 drawElement calls one of the other with different counts,
// makes sure the second drawElement call will have its data available.
TEST_P(IndexBufferOffsetTest, DrawWithDifferentCountsSameOffset)
{
    GLubyte indexData[] = {99, 0, 1, 2, 1, 2, 3};
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    size_t indexDataWidth = 7 * sizeof(GLubyte);

    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indexDataWidth, indexData, GL_DYNAMIC_DRAW);
    glUseProgram(mProgram);

    glBindBuffer(GL_ARRAY_BUFFER, mVertexBuffer);
    glVertexAttribPointer(mPositionAttributeLocation, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(mPositionAttributeLocation);

    glUniform4f(mColorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);

    // The first draw draws the first triangle, and the second draws a quad.
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_BYTE, reinterpret_cast<void *>(1));
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, reinterpret_cast<void *>(1));

    // Check the upper left triangle
    EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() / 4, GLColor::red);

    // Check the down right triangle
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::red);

    EXPECT_GL_NO_ERROR();
}

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(IndexBufferOffsetTest);

ANGLE_INSTANTIATE_TEST_ES3(IndexBufferOffsetTestES3);
