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

// vk_android_utils.cpp: Vulkan utilities for using the Android platform

#include "libANGLE/renderer/vulkan/android/vk_android_utils.h"

#include "common/android_util.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/vk_renderer.h"
#include "libANGLE/renderer/vulkan/vk_utils.h"

#if defined(ANGLE_PLATFORM_ANDROID)
#    include "libANGLE/Display.h"
#    include "libANGLE/renderer/vulkan/android/AHBFunctions.h"
#    include "libANGLE/renderer/vulkan/android/DisplayVkAndroid.h"
#endif

namespace rx
{
namespace vk
{
namespace
{
#if defined(ANGLE_PLATFORM_ANDROID)
DisplayVkAndroid *GetDisplayVkAndroid(Renderer *renderer)
{
    DisplayVk *displayVk = static_cast<DisplayVk *>(renderer->getGlobalOps());
    return static_cast<DisplayVkAndroid *>(displayVk);
}
#endif
}  // anonymous namespace

angle::Result GetClientBufferMemoryRequirements(ErrorContext *context,
                                                const AHardwareBuffer *hardwareBuffer,
                                                VkMemoryRequirements &memRequirements)
{
#if defined(ANGLE_PLATFORM_ANDROID)
    const AHBFunctions &ahbFunctions =
        GetDisplayVkAndroid(context->getRenderer())->getAHBFunctions();
    ASSERT(ahbFunctions.valid());

    AHardwareBuffer_Desc aHardwareBufferDescription = {};
    ahbFunctions.describe(hardwareBuffer, &aHardwareBufferDescription);
    if (aHardwareBufferDescription.format != AHARDWAREBUFFER_FORMAT_BLOB)
    {
        ERR() << "Trying to import non-BLOB AHB as client buffer.";
        return angle::Result::Stop;
    }

    // Get Android Buffer Properties
    VkAndroidHardwareBufferPropertiesANDROID bufferProperties = {};
    bufferProperties.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
    bufferProperties.pNext = nullptr;

    VkDevice device = context->getRenderer()->getDevice();
    ANGLE_VK_TRY(context, vkGetAndroidHardwareBufferPropertiesANDROID(device, hardwareBuffer,
                                                                      &bufferProperties));

    memRequirements.size           = bufferProperties.allocationSize;
    memRequirements.alignment      = 0;
    memRequirements.memoryTypeBits = bufferProperties.memoryTypeBits;

    return angle::Result::Continue;
#else
    ANGLE_VK_UNREACHABLE(context);
    return angle::Result::Stop;

#endif
}

angle::Result InitAndroidExternalMemory(ErrorContext *context,
                                        EGLClientBuffer clientBuffer,
                                        VkMemoryPropertyFlags memoryProperties,
                                        Buffer *buffer,
                                        VkMemoryPropertyFlags *memoryPropertyFlagsOut,
                                        uint32_t *memoryTypeIndexOut,
                                        DeviceMemory *deviceMemoryOut,
                                        VkDeviceSize *sizeOut)
{
#if defined(ANGLE_PLATFORM_ANDROID)
    const AHBFunctions &functions = GetDisplayVkAndroid(context->getRenderer())->getAHBFunctions();
    ASSERT(functions.valid());

    struct AHardwareBuffer *hardwareBuffer =
        angle::android::ClientBufferToAHardwareBuffer(clientBuffer);

    VkMemoryRequirements externalMemoryRequirements = {};
    ANGLE_TRY(
        GetClientBufferMemoryRequirements(context, hardwareBuffer, externalMemoryRequirements));

    // Import Vulkan DeviceMemory from Android Hardware Buffer.
    VkImportAndroidHardwareBufferInfoANDROID importHardwareBufferInfo = {};
    importHardwareBufferInfo.sType  = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
    importHardwareBufferInfo.buffer = hardwareBuffer;

    // Configure dedicated memory allocation for the external buffer and append it to the AHB info.
    VkMemoryDedicatedAllocateInfo dedicatedAllocateInfo = {};
    dedicatedAllocateInfo.sType    = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
    dedicatedAllocateInfo.buffer   = buffer->getHandle();
    importHardwareBufferInfo.pNext = &dedicatedAllocateInfo;

    ANGLE_VK_TRY(context, AllocateBufferMemoryWithRequirements(
                              context, MemoryAllocationType::BufferExternal, memoryProperties,
                              externalMemoryRequirements, &importHardwareBufferInfo, buffer,
                              memoryPropertyFlagsOut, memoryTypeIndexOut, deviceMemoryOut));
    *sizeOut = externalMemoryRequirements.size;

    functions.acquire(hardwareBuffer);

    return angle::Result::Continue;
#else
    ANGLE_VK_UNREACHABLE(context);
    return angle::Result::Stop;
#endif
}

void ReleaseAndroidExternalMemory(Renderer *renderer, EGLClientBuffer clientBuffer)
{
#if defined(ANGLE_PLATFORM_ANDROID)
    const AHBFunctions &functions = GetDisplayVkAndroid(renderer)->getAHBFunctions();
    ASSERT(functions.valid());
    struct AHardwareBuffer *hardwareBuffer =
        angle::android::ClientBufferToAHardwareBuffer(clientBuffer);
    functions.release(hardwareBuffer);
#endif
}
}  // namespace vk
}  // namespace rx
