/*     Copyright 2015-2018 Egor Yusov
*
*  Licensed under the Apache License, Version 2.0 (the "License");
*  you may not use this file except in compliance with the License.
*  You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
*  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 OF ANY PROPRIETARY RIGHTS.
*
*  In no event and under no legal theory, whether in tort (including negligence),
*  contract, or otherwise, unless required by applicable law (such as deliberate
*  and grossly negligent acts) or agreed to in writing, shall any Contributor be
*  liable for any damages, including any direct, indirect, special, incidental,
*  or consequential damages of any character arising as a result of this License or
*  out of the use or inability to use the software (including but not limited to damages
*  for loss of goodwill, work stoppage, computer failure or malfunction, or any and
*  all other commercial damages or losses), even if such Contributor has been advised
*  of the possibility of such damages.
*/

#include <limits>
#include <cstring>
#include "VulkanErrors.h"
#include "VulkanUtilities/VulkanPhysicalDevice.h"

namespace VulkanUtilities
{
    std::unique_ptr<VulkanPhysicalDevice> VulkanPhysicalDevice::Create(VkPhysicalDevice vkDevice)
    {
        auto *PhysicalDevice = new VulkanPhysicalDevice(vkDevice);
        return std::unique_ptr<VulkanPhysicalDevice>(PhysicalDevice);
    }

    VulkanPhysicalDevice::VulkanPhysicalDevice(VkPhysicalDevice vkDevice) :
        m_VkDevice(vkDevice)
    {
        VERIFY_EXPR(m_VkDevice != VK_NULL_HANDLE);

        vkGetPhysicalDeviceProperties(m_VkDevice, &m_Properties);
        vkGetPhysicalDeviceFeatures(m_VkDevice, &m_Features);
        vkGetPhysicalDeviceMemoryProperties(m_VkDevice, &m_MemoryProperties);
        uint32_t QueueFamilyCount = 0;
        vkGetPhysicalDeviceQueueFamilyProperties(m_VkDevice, &QueueFamilyCount, nullptr);
        VERIFY_EXPR(QueueFamilyCount> 0);
        m_QueueFamilyProperties.resize(QueueFamilyCount);
        vkGetPhysicalDeviceQueueFamilyProperties(m_VkDevice, &QueueFamilyCount, m_QueueFamilyProperties.data());
        VERIFY_EXPR(QueueFamilyCount == m_QueueFamilyProperties.size());

        // Get list of supported extensions
        uint32_t ExtensionCount = 0;
        vkEnumerateDeviceExtensionProperties(m_VkDevice, nullptr, &ExtensionCount, nullptr);
        if (ExtensionCount > 0)
        {
            m_SupportedExtensions.resize(ExtensionCount);
            auto res = vkEnumerateDeviceExtensionProperties(m_VkDevice, nullptr, &ExtensionCount, m_SupportedExtensions.data());
            VERIFY_EXPR(res == VK_SUCCESS);
            VERIFY_EXPR(ExtensionCount == m_SupportedExtensions.size());
        }
    }

    uint32_t VulkanPhysicalDevice::FindQueueFamily(VkQueueFlags QueueFlags)const
    {
        // All commands that are allowed on a queue that supports transfer operations are also allowed on 
        // a queue that supports either graphics or compute operations. Thus, if the capabilities of a queue 
        // family include VK_QUEUE_GRAPHICS_BIT or VK_QUEUE_COMPUTE_BIT, then reporting the VK_QUEUE_TRANSFER_BIT 
        // capability separately for that queue family is optional (4.1).
        VkQueueFlags QueueFlagsOpt = QueueFlags;
        if (QueueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
        {
            QueueFlags &= ~VK_QUEUE_TRANSFER_BIT;
            QueueFlagsOpt = QueueFlags | VK_QUEUE_TRANSFER_BIT;
        }

        static constexpr uint32_t InvalidFamilyInd = std::numeric_limits<uint32_t>::max();
        uint32_t FamilyInd = InvalidFamilyInd;

        for(uint32_t i=0; i < m_QueueFamilyProperties.size(); ++i)
        {
            // First try to find a queue, for which the flags match exactly
            // (i.e. dedicated compute or transfer queue)
            const auto &Props = m_QueueFamilyProperties[i];
            if( Props.queueFlags == QueueFlags || 
                Props.queueFlags == QueueFlagsOpt)
            {
                FamilyInd = i;
                break;
            }
        }

        if(FamilyInd == InvalidFamilyInd)
        {
            for (uint32_t i = 0; i < m_QueueFamilyProperties.size(); ++i)
            {
                // Try to find a queue for which all requested flags are set
                const auto &Props = m_QueueFamilyProperties[i];
                // Check only QueueFlags as VK_QUEUE_TRANSFER_BIT is 
                // optional for graphics and/or compute queues
                if ((Props.queueFlags & QueueFlags) == QueueFlags)
                {
                    FamilyInd = i;
                    break;
                }
            }
        }

        if (FamilyInd != InvalidFamilyInd)
        {
            if (QueueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))
            {
                const auto &Props = m_QueueFamilyProperties[FamilyInd];
                // Queues supporting graphics and/or compute operations must report (1,1,1) 
                // in minImageTransferGranularity, meaning that there are no additional restrictions 
                // on the granularity of image transfer operations for these queues (4.1).
                VERIFY_EXPR(Props.minImageTransferGranularity.width  == 1 &&
                            Props.minImageTransferGranularity.height == 1 &&
                            Props.minImageTransferGranularity.depth  == 1);
            }
        }
        else
        {
            LOG_ERROR_AND_THROW("Failed to find suitable queue family");
        }

        return FamilyInd;
    }

    bool VulkanPhysicalDevice::IsExtensionSupported(const char *ExtensionName)const
    {
        for(const auto& Extension : m_SupportedExtensions)
            if(strcmp(Extension.extensionName, ExtensionName) == 0)
                return true;

        return false;
    }

    bool VulkanPhysicalDevice::CheckPresentSupport(uint32_t queueFamilyIndex, VkSurfaceKHR VkSurface)const
    {
        VkBool32 PresentSupport = VK_FALSE;
        vkGetPhysicalDeviceSurfaceSupportKHR(m_VkDevice, queueFamilyIndex, VkSurface, &PresentSupport);
        return PresentSupport == VK_TRUE;
    }


    // This function is used to find a device memory type that supports all the property flags we request
    // Params:
    // * memoryTypeBitsRequirement  -  a bitmask that contains one bit set for every supported memory type for 
    //                                 the resource. Bit i is set if and only if the memory type i in the 
    //                                 VkPhysicalDeviceMemoryProperties structure for the physical device is 
    //                                 supported for the resource.
    // * requiredProperties   -  required memory properties (device local, host visible, etc.)
    uint32_t VulkanPhysicalDevice::GetMemoryTypeIndex(uint32_t              memoryTypeBitsRequirement,
                                                      VkMemoryPropertyFlags requiredProperties)const
    {
        // Iterate over all memory types available for the device
        // For each pair of elements X and Y returned in memoryTypes, X must be placed at a lower index position than Y if:
        //   * either the set of bit flags of X is a strict subset of the set of bit flags of Y.
        //   * or the propertyFlags members of X and Y are equal, and X belongs to a memory heap with greater performance

        for (uint32_t memoryIndex = 0; memoryIndex < m_MemoryProperties.memoryTypeCount; memoryIndex++)
        {
            // Each memory type returned by vkGetPhysicalDeviceMemoryProperties must have its propertyFlags set 
            // to one of the following values:
            // * 0
            // * HOST_VISIBLE_BIT | HOST_COHERENT_BIT
            // * HOST_VISIBLE_BIT | HOST_CACHED_BIT
            // * HOST_VISIBLE_BIT | HOST_CACHED_BIT | HOST_COHERENT_BIT
            // * DEVICE_LOCAL_BIT
            // * DEVICE_LOCAL_BIT | HOST_VISIBLE_BIT | HOST_COHERENT_BIT
            // * DEVICE_LOCAL_BIT | HOST_VISIBLE_BIT | HOST_CACHED_BIT
            // * DEVICE_LOCAL_BIT | HOST_VISIBLE_BIT | HOST_CACHED_BIT | HOST_COHERENT_BIT
            // * DEVICE_LOCAL_BIT | LAZILY_ALLOCATED_BIT
            //
            // There must be at least one memory type with both the HOST_VISIBLE_BIT and HOST_COHERENT_BIT bits set
            // There must be at least one memory type with the DEVICE_LOCAL_BIT bit set

            const uint32_t memoryTypeBit = (1 << memoryIndex);
            const bool isRequiredMemoryType = (memoryTypeBitsRequirement & memoryTypeBit) != 0;
            if(isRequiredMemoryType)
            {
                const VkMemoryPropertyFlags properties = m_MemoryProperties.memoryTypes[memoryIndex].propertyFlags;
                const bool hasRequiredProperties = (properties & requiredProperties) == requiredProperties;

                if (hasRequiredProperties)
                    return memoryIndex;
            }
        }
        return InvalidMemoryTypeIndex;
    }
}
