//
// Copyright 2020 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.
//
// ExternalBufferTest:
//   Tests the correctness of external buffer ext extension.
//

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

#include "common/android_util.h"

#if defined(ANGLE_PLATFORM_ANDROID) && __ANDROID_API__ >= 26
#    define ANGLE_AHARDWARE_BUFFER_SUPPORT
// NDK header file for access to Android Hardware Buffers
#    include <android/hardware_buffer.h>
#endif

namespace angle
{

class ExternalBufferTestES31 : public ANGLETest
{
  protected:
    ExternalBufferTestES31()
    {
        setWindowWidth(16);
        setWindowHeight(16);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setConfigDepthBits(24);
    }

    AHardwareBuffer *createAndroidHardwareBuffer(size_t size, const GLubyte *data)
    {
#if defined(ANGLE_AHARDWARE_BUFFER_SUPPORT)
        // The height and width are number of pixels of size format
        AHardwareBuffer_Desc aHardwareBufferDescription = {};
        aHardwareBufferDescription.width                = size;
        aHardwareBufferDescription.height               = 1;
        aHardwareBufferDescription.layers               = 1;
        aHardwareBufferDescription.format               = AHARDWAREBUFFER_FORMAT_BLOB;
        aHardwareBufferDescription.usage =
            AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER;
        aHardwareBufferDescription.stride = 0;

        // Allocate memory from Android Hardware Buffer
        AHardwareBuffer *aHardwareBuffer = nullptr;
        EXPECT_EQ(0, AHardwareBuffer_allocate(&aHardwareBufferDescription, &aHardwareBuffer));

        void *mappedMemory = nullptr;
        EXPECT_EQ(0, AHardwareBuffer_lock(aHardwareBuffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
                                          -1, nullptr, &mappedMemory));

        // Need to grab the stride the implementation might have enforced
        AHardwareBuffer_describe(aHardwareBuffer, &aHardwareBufferDescription);

        memcpy(mappedMemory, data, size);

        EXPECT_EQ(0, AHardwareBuffer_unlock(aHardwareBuffer, nullptr));
        return aHardwareBuffer;
#else
        return nullptr;
#endif  // ANGLE_PLATFORM_ANDROID
    }

    void destroyAndroidHardwareBuffer(AHardwareBuffer *aHardwareBuffer)
    {
#if defined(ANGLE_AHARDWARE_BUFFER_SUPPORT)
        AHardwareBuffer_release(aHardwareBuffer);
#endif
    }

    void *lockAndroidHardwareBuffer(AHardwareBuffer *aHardwareBuffer)
    {
        void *data = nullptr;
#if defined(ANGLE_AHARDWARE_BUFFER_SUPPORT)
        EXPECT_EQ(0, AHardwareBuffer_lock(aHardwareBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY,
                                          -1, nullptr, &data));
#endif
        return data;
    }

    void unlockAndroidHardwareBuffer(AHardwareBuffer *aHardwareBuffer)
    {
#if defined(ANGLE_AHARDWARE_BUFFER_SUPPORT)
        AHardwareBuffer_unlock(aHardwareBuffer, nullptr);
#endif
    }
};

// Testing subdata update with external buffer from AHB
TEST_P(ExternalBufferTestES31, BufferSubData)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_external_buffer") ||
                       !IsGLExtensionEnabled("GL_EXT_buffer_storage"));
    constexpr uint8_t kBufferSize = 16;
    std::vector<GLubyte> initData(kBufferSize, 0xA);

    // Create the Image
    AHardwareBuffer *aHardwareBuffer;
    constexpr GLbitfield kFlags = GL_DYNAMIC_STORAGE_BIT_EXT;
    aHardwareBuffer             = createAndroidHardwareBuffer(kBufferSize, initData.data());

    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferStorageExternalEXT(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize,
                               eglGetNativeClientBufferANDROID(aHardwareBuffer), kFlags);
    ASSERT_GL_NO_ERROR();

    std::vector<GLubyte> expectedData(kBufferSize, 0xFF);

    glBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, expectedData.data());
    glFinish();

    ASSERT_GL_NO_ERROR();

    // Inspect the data written into the buffer using CPU access.
    uint8_t *data = static_cast<uint8_t *>(lockAndroidHardwareBuffer(aHardwareBuffer));

    for (uint32_t i = 0; i < kBufferSize; ++i)
    {
        EXPECT_EQ(data[i], 0xFF);
    }

    unlockAndroidHardwareBuffer(aHardwareBuffer);
    // Delete the source AHB when in use
    destroyAndroidHardwareBuffer(aHardwareBuffer);
}

// Testing dispatch compute shader external from source AHB
TEST_P(ExternalBufferTestES31, DispatchCompute)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_external_buffer") ||
                       !IsGLExtensionEnabled("GL_EXT_buffer_storage"));
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
    layout(std430, binding=0) buffer Output {
        uint data[];
    } bOutput;
    void main() {
        bOutput.data[gl_GlobalInvocationID.x] =
            gl_GlobalInvocationID.x * 3u;
    }
)";

    constexpr uint8_t kBufferSize = 16 * 4;
    std::vector<GLubyte> initData(kBufferSize, 0xA);

    // Create the Image
    AHardwareBuffer *aHardwareBuffer;
    constexpr GLbitfield kFlags = GL_MAP_READ_BIT;
    aHardwareBuffer             = createAndroidHardwareBuffer(kBufferSize, initData.data());

    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferStorageExternalEXT(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize,
                               eglGetNativeClientBufferANDROID(aHardwareBuffer), kFlags);

    ASSERT_GL_NO_ERROR();

    GLProgram program;
    program.makeCompute(kCS);
    ASSERT_NE(program.get(), 0U);
    ASSERT_GL_NO_ERROR();

    glUseProgram(program);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, buffer);
    glDispatchCompute(kBufferSize, 1, 1);
    glFinish();
    ASSERT_GL_NO_ERROR();

    // Inspect the data written into the buffer using CPU access.
    uint32_t *data = static_cast<uint32_t *>(lockAndroidHardwareBuffer(aHardwareBuffer));

    for (uint32_t i = 0; i < (kBufferSize / sizeof(uint32_t)); ++i)
    {
        EXPECT_EQ(data[i], static_cast<uint32_t>(i * 3));
    }

    unlockAndroidHardwareBuffer(aHardwareBuffer);
    // Delete the source AHB when in use
    destroyAndroidHardwareBuffer(aHardwareBuffer);
}

// Test interaction between GL_OES_mapbuffer and GL_EXT_external_buffer extensions.
TEST_P(ExternalBufferTestES31, MapBuffer)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_external_buffer") ||
                       !IsGLExtensionEnabled("GL_EXT_buffer_storage") ||
                       !IsGLExtensionEnabled("GL_EXT_map_buffer_range"));
    constexpr uint8_t kBufferSize = 16;
    std::vector<GLubyte> initData(kBufferSize, 0xFF);

    // Create the Image
    AHardwareBuffer *aHardwareBuffer;
    constexpr GLbitfield kFlags = (GL_MAP_READ_BIT_EXT | GL_MAP_WRITE_BIT_EXT);
    aHardwareBuffer             = createAndroidHardwareBuffer(kBufferSize, initData.data());

    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferStorageExternalEXT(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize,
                               eglGetNativeClientBufferANDROID(aHardwareBuffer), kFlags);

    ASSERT_GL_NO_ERROR();

    // Inspect the data written into the buffer using CPU access.
    uint8_t *data = static_cast<uint8_t *>(
        glMapBufferRangeEXT(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT_EXT));
    ASSERT_GL_NO_ERROR();

    for (uint32_t i = 0; i < kBufferSize; ++i)
    {
        EXPECT_EQ(data[i], 0xFF);
    }

    glUnmapBufferOES(GL_SHADER_STORAGE_BUFFER);
    // Delete the source AHB when in use
    destroyAndroidHardwareBuffer(aHardwareBuffer);
}

ANGLE_INSTANTIATE_TEST_ES31(ExternalBufferTestES31);
}  // namespace angle
