// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gpu/vulkan/vulkan_device_queue.h"

#include <unordered_set>
#include <vector>

#include "gpu/vulkan/vulkan_command_pool.h"
#include "gpu/vulkan/vulkan_implementation.h"
#include "gpu/vulkan/vulkan_platform.h"

#if defined(VK_USE_PLATFORM_XLIB_KHR)
#include "ui/gfx/x/x11_types.h"
#endif  // defined(VK_USE_PLATFORM_XLIB_KHR)

namespace gpu {

VulkanDeviceQueue::VulkanDeviceQueue() {}

VulkanDeviceQueue::~VulkanDeviceQueue() {
  DCHECK_EQ(static_cast<VkPhysicalDevice>(VK_NULL_HANDLE), vk_physical_device_);
  DCHECK_EQ(static_cast<VkDevice>(VK_NULL_HANDLE), vk_device_);
  DCHECK_EQ(static_cast<VkQueue>(VK_NULL_HANDLE), vk_queue_);
}

bool VulkanDeviceQueue::Initialize(uint32_t options) {
  VkInstance vk_instance = gpu::GetVulkanInstance();
  if (VK_NULL_HANDLE == vk_instance)
    return false;

  VkResult result = VK_SUCCESS;

  uint32_t device_count = 0;
  result = vkEnumeratePhysicalDevices(vk_instance, &device_count, nullptr);
  if (VK_SUCCESS != result || device_count == 0)
    return false;

  std::vector<VkPhysicalDevice> devices(device_count);
  result =
      vkEnumeratePhysicalDevices(vk_instance, &device_count, devices.data());
  if (VK_SUCCESS != result) {
    DLOG(ERROR) << "vkEnumeratePhysicalDevices() failed: " << result;
    return false;
  }

#if defined(VK_USE_PLATFORM_XLIB_KHR)
  Display* xdisplay = gfx::GetXDisplay();
  VisualID visual_id =
      XVisualIDFromVisual(DefaultVisual(xdisplay, DefaultScreen(xdisplay)));
#endif  // defined(VK_USE_PLATFORM_XLIB_KHR)

  VkQueueFlags queue_flags = 0;
  if (options & DeviceQueueOption::GRAPHICS_QUEUE_FLAG)
    queue_flags |= VK_QUEUE_GRAPHICS_BIT;

  int device_index = -1;
  int queue_index = -1;
  for (size_t i = 0; i < devices.size(); ++i) {
    const VkPhysicalDevice& device = devices[i];
    uint32_t queue_count = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_count, nullptr);
    if (queue_count) {
      std::vector<VkQueueFamilyProperties> queue_properties(queue_count);
      vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_count,
                                               queue_properties.data());
      for (size_t n = 0; n < queue_properties.size(); ++n) {
        if ((queue_properties[n].queueFlags & queue_flags) != queue_flags)
          continue;

#if defined(VK_USE_PLATFORM_XLIB_KHR)
        if (options & DeviceQueueOption::PRESENTATION_SUPPORT_QUEUE_FLAG &&
            !vkGetPhysicalDeviceXlibPresentationSupportKHR(device, n, xdisplay,
                                                           visual_id)) {
          continue;
        }
#elif defined(VK_USE_PLATFORM_ANDROID_KHR)
// On Android, all physical devices and queue families must be capable of
// presentation with any native window.
// As a result there is no Android-specific query for these capabilities.
#else
#error Non-Supported Vulkan implementation.
#endif

        queue_index = static_cast<int>(n);
        break;
      }

      if (-1 != queue_index) {
        device_index = static_cast<int>(i);
        break;
      }
    }
  }

  if (queue_index == -1)
    return false;

  vk_physical_device_ = devices[device_index];
  vk_queue_index_ = queue_index;

  float queue_priority = 0.0f;
  VkDeviceQueueCreateInfo queue_create_info = {};
  queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
  queue_create_info.queueFamilyIndex = queue_index;
  queue_create_info.queueCount = 1;
  queue_create_info.pQueuePriorities = &queue_priority;

  const char* device_extensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME};

  std::vector<const char*> enabled_layer_names;
#if DCHECK_IS_ON()
  uint32_t num_device_layers = 0;
  result = vkEnumerateDeviceLayerProperties(vk_physical_device_,
                                            &num_device_layers, nullptr);
  if (VK_SUCCESS != result) {
    DLOG(ERROR) << "vkEnumerateDeviceLayerProperties(NULL) failed: "
                << result;
    return false;
  }

  std::vector<VkLayerProperties> device_layers(num_device_layers);
  result = vkEnumerateDeviceLayerProperties(vk_physical_device_,
                                            &num_device_layers,
                                            device_layers.data());
  if (VK_SUCCESS != result) {
    DLOG(ERROR) << "vkEnumerateDeviceLayerProperties() failed: " << result;
    return false;
  }

  std::unordered_set<std::string> desired_layers({
    "VK_LAYER_LUNARG_standard_validation",
  });

  for (const VkLayerProperties& layer_property : device_layers) {
    if (desired_layers.find(layer_property.layerName) != desired_layers.end())
      enabled_layer_names.push_back(layer_property.layerName);
  }
#endif

  VkDeviceCreateInfo device_create_info = {};
  device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
  device_create_info.queueCreateInfoCount = 1;
  device_create_info.pQueueCreateInfos = &queue_create_info;
  device_create_info.enabledLayerCount = enabled_layer_names.size();
  device_create_info.ppEnabledLayerNames = enabled_layer_names.data();
  device_create_info.enabledExtensionCount = arraysize(device_extensions);
  device_create_info.ppEnabledExtensionNames = device_extensions;

  result = vkCreateDevice(vk_physical_device_, &device_create_info, nullptr,
                          &vk_device_);
  if (VK_SUCCESS != result)
    return false;

  vkGetDeviceQueue(vk_device_, queue_index, 0, &vk_queue_);

  return true;
}

void VulkanDeviceQueue::Destroy() {
  if (VK_NULL_HANDLE != vk_device_) {
    vkDestroyDevice(vk_device_, nullptr);
    vk_device_ = VK_NULL_HANDLE;
  }

  vk_queue_ = VK_NULL_HANDLE;
  vk_queue_index_ = 0;

  vk_physical_device_ = VK_NULL_HANDLE;
}

std::unique_ptr<VulkanCommandPool> VulkanDeviceQueue::CreateCommandPool() {
  std::unique_ptr<VulkanCommandPool> command_pool(new VulkanCommandPool(this));
  if (!command_pool->Initialize())
    return nullptr;

  return command_pool;
}

}  // namespace gpu
