/*
 * Copyright © 2023 Collabora, Ltd.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "vk_device_memory.h"

#include "vk_android.h"
#include "vk_common_entrypoints.h"
#include "vk_util.h"

#if DETECT_OS_ANDROID && ANDROID_API_LEVEL >= 26
#include <vndk/hardware_buffer.h>
#endif

void *
vk_device_memory_create(struct vk_device *device,
                        const VkMemoryAllocateInfo *pAllocateInfo,
                        const VkAllocationCallbacks *alloc,
                        size_t size)
{
   struct vk_device_memory *mem =
      vk_object_zalloc(device, alloc, size, VK_OBJECT_TYPE_DEVICE_MEMORY);
   if (mem == NULL)
      return NULL;

   assert(pAllocateInfo->sType == VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO);

   mem->size = pAllocateInfo->allocationSize;
   mem->memory_type_index = pAllocateInfo->memoryTypeIndex;

   vk_foreach_struct_const(ext, pAllocateInfo->pNext) {
      switch (ext->sType) {
      case VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO: {
         const VkExportMemoryAllocateInfo *export_info = (void *)ext;
         mem->export_handle_types = export_info->handleTypes;
         break;
      }

      case VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID: {
#if DETECT_OS_ANDROID && ANDROID_API_LEVEL >= 26
         const VkImportAndroidHardwareBufferInfoANDROID *ahb_info = (void *)ext;

         assert(mem->import_handle_type == 0);
         mem->import_handle_type =
            VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;

         /* From the Vulkan 1.3.242 spec:
          *
          *    "If the vkAllocateMemory command succeeds, the implementation
          *    must acquire a reference to the imported hardware buffer, which
          *    it must release when the device memory object is freed. If the
          *    command fails, the implementation must not retain a
          *    reference."
          *
          * We assume that if the driver fails to create its memory object,
          * it will call vk_device_memory_destroy which will delete our
          * reference.
          */
         AHardwareBuffer_acquire(ahb_info->buffer);
         mem->ahardware_buffer = ahb_info->buffer;
         break;
#else
         unreachable("AHardwareBuffer import requires Android >= 26");
#endif /* DETECT_OS_ANDROID && ANDROID_API_LEVEL >= 26 */
      }

      case VK_STRUCTURE_TYPE_IMPORT_MEMORY_FD_INFO_KHR: {
         const VkImportMemoryFdInfoKHR *fd_info = (void *)ext;
         if (fd_info->handleType) {
            assert(fd_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT ||
                   fd_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT);
            assert(mem->import_handle_type == 0);
            mem->import_handle_type = fd_info->handleType;
         }
         break;
      }

      case VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT: {
         const VkImportMemoryHostPointerInfoEXT *host_ptr_info = (void *)ext;
         if (host_ptr_info->handleType) {
            assert(host_ptr_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT ||
                   host_ptr_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_MAPPED_FOREIGN_MEMORY_BIT_EXT);

            assert(mem->import_handle_type == 0);
            mem->import_handle_type = host_ptr_info->handleType;
            mem->host_ptr = host_ptr_info->pHostPointer;
         }
         break;
      }

      case VK_STRUCTURE_TYPE_IMPORT_MEMORY_WIN32_HANDLE_INFO_KHR: {
#ifdef VK_USE_PLATFORM_WIN32_KHR
         const VkImportMemoryWin32HandleInfoKHR *w32h_info = (void *)ext;
         if (w32h_info->handleType) {
            assert(w32h_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT ||
                   w32h_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT ||
                   w32h_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_BIT ||
                   w32h_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D11_TEXTURE_KMT_BIT ||
                   w32h_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_HEAP_BIT ||
                   w32h_info->handleType == VK_EXTERNAL_MEMORY_HANDLE_TYPE_D3D12_RESOURCE_BIT);
            assert(mem->import_handle_type == 0);
            mem->import_handle_type = w32h_info->handleType;
         }
         break;
#else
         unreachable("Win32 platform support disabled");
#endif
      }

      case VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO: {
         const VkMemoryAllocateFlagsInfo *flags_info = (void *)ext;
         mem->alloc_flags = flags_info->flags;
         break;
      }

      default:
         break;
      }
   }

   /* From the Vulkan Specification 1.3.261:
    *
    *    VUID-VkMemoryAllocateInfo-allocationSize-07897
    *
    *   "If the parameters do not define an import or export operation,
    *   allocationSize must be greater than 0."
    */
   if (!mem->import_handle_type && !mem->export_handle_types)
      assert(pAllocateInfo->allocationSize > 0);

   /* From the Vulkan Specification 1.3.261:
    *
    *    VUID-VkMemoryAllocateInfo-allocationSize-07899
    *
    *    "If the parameters define an export operation and the handle type is
    *    not VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
    *    allocationSize must be greater than 0."
    */
    if (mem->export_handle_types &&
        mem->export_handle_types !=
          VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID)
      assert(pAllocateInfo->allocationSize > 0);

   if ((mem->export_handle_types &
        VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID) &&
       mem->ahardware_buffer == NULL) {
      /* If we need to be able to export an Android hardware buffer but none
       * is provided as an import, create a new one.
       */
      mem->ahardware_buffer = vk_alloc_ahardware_buffer(pAllocateInfo);
      if (mem->ahardware_buffer == NULL) {
         vk_device_memory_destroy(device, alloc, mem);
         return NULL;
      }
   }

   return mem;
}

void
vk_device_memory_destroy(struct vk_device *device,
                         const VkAllocationCallbacks *alloc,
                         struct vk_device_memory *mem)
{

#if DETECT_OS_ANDROID && ANDROID_API_LEVEL >= 26
   if (mem->ahardware_buffer)
      AHardwareBuffer_release(mem->ahardware_buffer);
#endif /* DETECT_OS_ANDROID && ANDROID_API_LEVEL >= 26 */

   vk_object_free(device, alloc, mem);
}

#if DETECT_OS_ANDROID && ANDROID_API_LEVEL >= 26
VkResult
vk_common_GetMemoryAndroidHardwareBufferANDROID(
   VkDevice _device,
   const VkMemoryGetAndroidHardwareBufferInfoANDROID *pInfo,
   struct AHardwareBuffer **pBuffer)
{
   VK_FROM_HANDLE(vk_device_memory, mem, pInfo->memory);

   /* Some quotes from Vulkan spec:
    *
    *    "If the device memory was created by importing an Android hardware
    *    buffer, vkGetMemoryAndroidHardwareBufferANDROID must return that same
    *    Android hardware buffer object."
    *
    *    "VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID
    *    must have been included in VkExportMemoryAllocateInfo::handleTypes
    *    when memory was created."
    */
   if (mem->ahardware_buffer) {
      *pBuffer = mem->ahardware_buffer;
      /* Increase refcount. */
      AHardwareBuffer_acquire(*pBuffer);
      return VK_SUCCESS;
   }

   return VK_ERROR_INVALID_EXTERNAL_HANDLE;
}
#endif
