Program Listing for File VulkanDevice.cpp

Return to documentation for file (Src/GraphicsEngineVulkan/vulkan_base/VulkanDevice.cpp)

#include "vulkan_base/VulkanDevice.hpp"

#include <string.h>

#include "common/Utilities.hpp"
#include "spdlog/spdlog.h"
#include <set>
#include <stdexcept>
#include <string>

Kataglyphis::VulkanDevice::VulkanDevice(VulkanInstance *instance, VkSurfaceKHR *surface)
{
    this->instance = instance;
    this->surface = surface;
    get_physical_device();
    create_logical_device();
}

Kataglyphis::VulkanRendererInternals::SwapChainDetails Kataglyphis::VulkanDevice::getSwapchainDetails()
{
    return getSwapchainDetails(physical_device);
}

void Kataglyphis::VulkanDevice::cleanUp() { vkDestroyDevice(logical_device, nullptr); }

Kataglyphis::VulkanDevice::~VulkanDevice() {}

Kataglyphis::VulkanRendererInternals::QueueFamilyIndices Kataglyphis::VulkanDevice::getQueueFamilies()
{
    Kataglyphis::VulkanRendererInternals::QueueFamilyIndices indices{};

    uint32_t queue_family_count = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);

    std::vector<VkQueueFamilyProperties> queue_family_list(queue_family_count);
    vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_family_list.data());

    // Go through each queue family and check if it has at least 1 of required
    // types we need to keep track th eindex by our own
    int index = 0;
    for (const auto &queue_family : queue_family_list) {
        // first check if queue family has at least 1 queue in that family
        // Queue can be multiple types defined through bitfield. Need to bitwise AND
        // with VK_QUE_*_BIT to check if has required  type
        if (queue_family.queueCount > 0 && queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
            indices.graphics_family = index;// if queue family valid, than get index
        }

        if (queue_family.queueCount > 0 && queue_family.queueFlags & VK_QUEUE_COMPUTE_BIT) {
            indices.compute_family = index;
        }

        // check if queue family suppports presentation
        VkBool32 presentation_support = false;
        vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, index, *surface, &presentation_support);
        // check if queue is presentation type (can be both graphics and
        // presentation)
        if (queue_family.queueCount > 0 && presentation_support) { indices.presentation_family = index; }

        // check if queue family indices are in a valid state
        if (indices.is_valid()) { break; }

        index++;
    }

    return indices;
}

void Kataglyphis::VulkanDevice::get_physical_device()
{
    // Enumerate physical devices the vkInstance can access
    uint32_t device_count = 0;
    vkEnumeratePhysicalDevices(instance->getVulkanInstance(), &device_count, nullptr);

    // if no devices available, then none support of Vulkan
    if (device_count == 0) { spdlog::error("Can not find GPU's that support Vulkan Instance!"); }

    // Get list of physical devices
    std::vector<VkPhysicalDevice> device_list(device_count);
    vkEnumeratePhysicalDevices(instance->getVulkanInstance(), &device_count, device_list.data());

    for (const auto &device : device_list) {
        if (check_device_suitable(device)) {
            physical_device = device;
            break;
        }
    }

    // get properties of our new device
    vkGetPhysicalDeviceProperties(physical_device, &device_properties);
}

void Kataglyphis::VulkanDevice::create_logical_device()
{
    // get the queue family indices for the chosen physical device
    Kataglyphis::VulkanRendererInternals::QueueFamilyIndices indices = getQueueFamilies();

    // vector for queue creation information and set for family indices
    std::vector<VkDeviceQueueCreateInfo> queue_create_infos;
    std::set<int> queue_family_indices = {
        indices.graphics_family, indices.presentation_family, indices.compute_family
    };

    // Queue the logical device needs to create and info to do so (only 1 for now,
    // will add more later!)
    for (int queue_family_index : queue_family_indices) {
        VkDeviceQueueCreateInfo queue_create_info{};
        queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        queue_create_info.queueFamilyIndex = queue_family_index;// the index of the family to create a queue from
        queue_create_info.queueCount = 1;// number of queues to create
        float priority = 1.0f;
        queue_create_info.pQueuePriorities = &priority;// Vulkan needs to know how to handle multiple queues, so
                                                       // decide priority (1 = highest)

        queue_create_infos.push_back(queue_create_info);
    }

    // -- ALL EXTENSION WE NEED
    VkPhysicalDeviceDescriptorIndexingFeatures indexing_features{};
    indexing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES;
    indexing_features.runtimeDescriptorArray = VK_TRUE;
    indexing_features.shaderSampledImageArrayNonUniformIndexing = VK_TRUE;
    indexing_features.pNext = nullptr;

    // -- NEEDED FOR QUERING THE DEVICE ADDRESS WHEN CREATING ACCELERATION
    // STRUCTURES
    VkPhysicalDeviceBufferDeviceAddressFeaturesEXT buffer_device_address_features{};
    buffer_device_address_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES_EXT;
    buffer_device_address_features.pNext = &indexing_features;
    buffer_device_address_features.bufferDeviceAddress = VK_TRUE;
    buffer_device_address_features.bufferDeviceAddressCaptureReplay = VK_TRUE;
    buffer_device_address_features.bufferDeviceAddressMultiDevice = VK_FALSE;

    // --ENABLE RAY TRACING PIPELINE
    VkPhysicalDeviceRayTracingPipelineFeaturesKHR ray_tracing_pipeline_features{};
    ray_tracing_pipeline_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR;
    ray_tracing_pipeline_features.pNext = &buffer_device_address_features;
    ray_tracing_pipeline_features.rayTracingPipeline = VK_TRUE;

    // -- ENABLE ACCELERATION STRUCTURES
    VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration_structure_features{};
    acceleration_structure_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR;
    acceleration_structure_features.pNext = &ray_tracing_pipeline_features;
    acceleration_structure_features.accelerationStructure = VK_TRUE;
    acceleration_structure_features.accelerationStructureCaptureReplay = VK_TRUE;
    acceleration_structure_features.accelerationStructureIndirectBuild = VK_FALSE;
    acceleration_structure_features.accelerationStructureHostCommands = VK_FALSE;
    acceleration_structure_features.descriptorBindingAccelerationStructureUpdateAfterBind = VK_FALSE;

    VkPhysicalDeviceVulkan13Features features13{};
    features13.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES;
    features13.maintenance4 = VK_TRUE;
    features13.robustImageAccess = VK_FALSE;
    features13.inlineUniformBlock = VK_FALSE;
    features13.descriptorBindingInlineUniformBlockUpdateAfterBind = VK_FALSE;
    features13.pipelineCreationCacheControl = VK_FALSE;
    features13.privateData = VK_FALSE;
    features13.shaderDemoteToHelperInvocation = VK_FALSE;
    features13.shaderTerminateInvocation = VK_FALSE;
    features13.subgroupSizeControl = VK_FALSE;
    features13.computeFullSubgroups = VK_FALSE;
    features13.synchronization2 = VK_FALSE;
    features13.textureCompressionASTC_HDR = VK_FALSE;
    features13.shaderZeroInitializeWorkgroupMemory = VK_FALSE;
    features13.dynamicRendering = VK_FALSE;
    features13.shaderIntegerDotProduct = VK_FALSE;
    features13.pNext = &acceleration_structure_features;

    VkPhysicalDeviceRayQueryFeaturesKHR rayQueryFeature{};
    rayQueryFeature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_QUERY_FEATURES_KHR;
    rayQueryFeature.pNext = &features13;
    rayQueryFeature.rayQuery = VK_TRUE;

    VkPhysicalDeviceFeatures2 features2{};
    features2.pNext = &rayQueryFeature;
    features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
    features2.features.samplerAnisotropy = VK_TRUE;
    features2.features.shaderInt64 = VK_TRUE;
    features2.features.geometryShader = VK_TRUE;
    features2.features.logicOp = VK_TRUE;

    // -- PREPARE FOR HAVING MORE EXTENSION BECAUSE WE NEED RAYTRACING
    // CAPABILITIES
    std::vector<const char *> extensions(device_extensions);

    // Query available extensions for the physical device
    uint32_t extensionCount = 0;
    vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensionCount, nullptr);
    std::vector<VkExtensionProperties> availableExtensions(extensionCount);
    vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensionCount, availableExtensions.data());

    // Helper function to check if an extension is supported
    auto isExtensionSupported = [&availableExtensions](const char *extensionName) {
        for (const auto &ext : availableExtensions) {
            if (strcmp(ext.extensionName, extensionName) == 0) { return true; }
        }
        return false;
    };

    for (const char *extensionName : device_extensions_for_raytracing) {
        if (!isExtensionSupported(extensionName)) {
            deviceSupportsHardwareAcceleratedRRT = false;
            spdlog::info("Required extension not supported: {}", extensionName);
        }
    }

    if (deviceSupportsHardwareAcceleratedRRT) {
        // COPY ALL NECESSARY EXTENSIONS FOR RAYTRACING TO THE EXTENSION
        extensions.insert(
          extensions.begin(), device_extensions_for_raytracing.begin(), device_extensions_for_raytracing.end());
    }

    // information to create logical device (sometimes called "device")
    VkDeviceCreateInfo device_create_info{};
    device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    device_create_info.queueCreateInfoCount =
      static_cast<uint32_t>(queue_create_infos.size());// number of queue create infos
    device_create_info.pQueueCreateInfos = queue_create_infos.data();// list of queue create infos so device can
                                                                     // create required queues
    device_create_info.enabledExtensionCount =
      static_cast<uint32_t>(extensions.size());// number of enabled logical device extensions
    device_create_info.ppEnabledExtensionNames = extensions.data();// list of enabled logical device extensions
    device_create_info.flags = 0;
    device_create_info.pEnabledFeatures = NULL;

    if (deviceSupportsHardwareAcceleratedRRT) { device_create_info.pNext = &features2; }

    // create logical device for the given physical device
    VkResult result = vkCreateDevice(physical_device, &device_create_info, nullptr, &logical_device);
    ASSERT_VULKAN(result, "Failed to create a logical device!");

    //  Queues are created at the same time as the device...
    // So we want handle to queues
    // From given logical device of given queue family, of given queue index (0
    // since only one queue), place reference in given VkQueue
    vkGetDeviceQueue(logical_device, indices.graphics_family, 0, &graphics_queue);
    vkGetDeviceQueue(logical_device, indices.presentation_family, 0, &presentation_queue);
    vkGetDeviceQueue(logical_device, indices.compute_family, 0, &compute_queue);
}

Kataglyphis::VulkanRendererInternals::QueueFamilyIndices Kataglyphis::VulkanDevice::getQueueFamilies(
  VkPhysicalDevice physical_device)
{
    Kataglyphis::VulkanRendererInternals::QueueFamilyIndices indices{};

    uint32_t queue_family_count = 0;
    vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr);

    std::vector<VkQueueFamilyProperties> queue_family_list(queue_family_count);
    vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_family_list.data());

    // Go through each queue family and check if it has at least 1 of required
    // types we need to keep track th eindex by our own
    int index = 0;
    for (const auto &queue_family : queue_family_list) {
        // first check if queue family has at least 1 queue in that family
        // Queue can be multiple types defined through bitfield. Need to bitwise AND
        // with VK_QUE_*_BIT to check if has required  type
        if (queue_family.queueCount > 0 && queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
            indices.graphics_family = index;// if queue family valid, than get index
        }

        if (queue_family.queueCount > 0 && queue_family.queueFlags & VK_QUEUE_COMPUTE_BIT) {
            indices.compute_family = index;
        }

        // check if queue family suppports presentation
        VkBool32 presentation_support = false;
        vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, index, *surface, &presentation_support);
        // check if queue is presentation type (can be both graphics and
        // presentation)
        if (queue_family.queueCount > 0 && presentation_support) { indices.presentation_family = index; }

        // check if queue family indices are in a valid state
        if (indices.is_valid()) { break; }

        index++;
    }

    return indices;
}

Kataglyphis::VulkanRendererInternals::SwapChainDetails Kataglyphis::VulkanDevice::getSwapchainDetails(
  VkPhysicalDevice device)
{
    Kataglyphis::VulkanRendererInternals::SwapChainDetails swapchain_details{};
    // get the surface capabilities for the given surface on the given physical
    // device
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, *surface, &swapchain_details.surface_capabilities);

    uint32_t format_count = 0;
    vkGetPhysicalDeviceSurfaceFormatsKHR(device, *surface, &format_count, nullptr);

    // if formats returned, get list of formats
    if (format_count != 0) {
        swapchain_details.formats.resize(format_count);
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, *surface, &format_count, swapchain_details.formats.data());
    }

    uint32_t presentation_count = 0;
    vkGetPhysicalDeviceSurfacePresentModesKHR(device, *surface, &presentation_count, nullptr);

    // if presentation modes returned, get list of presentation modes
    if (presentation_count > 0) {
        swapchain_details.presentation_mode.resize(presentation_count);
        vkGetPhysicalDeviceSurfacePresentModesKHR(
          device, *surface, &presentation_count, swapchain_details.presentation_mode.data());
    }

    return swapchain_details;
}

bool Kataglyphis::VulkanDevice::check_device_suitable(VkPhysicalDevice device)
{
    // Information about device itself (ID, name, type, vendor, etc)
    VkPhysicalDeviceProperties device_properties;
    vkGetPhysicalDeviceProperties(device, &device_properties);

    VkPhysicalDeviceFeatures device_features;
    vkGetPhysicalDeviceFeatures(device, &device_features);

    Kataglyphis::VulkanRendererInternals::QueueFamilyIndices indices = getQueueFamilies(device);

    bool extensions_supported = check_device_extension_support(device);

    bool swap_chain_valid = false;

    if (extensions_supported) {
        Kataglyphis::VulkanRendererInternals::SwapChainDetails swap_chain_details = getSwapchainDetails(device);
        swap_chain_valid = !swap_chain_details.presentation_mode.empty() && !swap_chain_details.formats.empty();
    }

    return indices.is_valid() && extensions_supported && swap_chain_valid && device_features.samplerAnisotropy;
}

bool Kataglyphis::VulkanDevice::check_device_extension_support(VkPhysicalDevice device)
{
    uint32_t extension_count = 0;
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, nullptr);

    if (extension_count == 0) { return false; }

    // populate list of extensions
    std::vector<VkExtensionProperties> extensions(extension_count);
    vkEnumerateDeviceExtensionProperties(device, nullptr, &extension_count, extensions.data());

    for (const auto &device_extension : device_extensions) {
        bool has_extension = false;

        for (const auto &extension : extensions) {
            if (strcmp(device_extension, extension.extensionName) == 0) {
                has_extension = true;
                break;
            }
        }

        if (!has_extension) { return false; }
    }

    return true;
}