From 909304e44763c58c0ebbe40068a58784ebaced7b Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Fri, 3 May 2024 00:40:22 +0200 Subject: Add Vulkan backend API --- deimos/vulkan/BUILD | 5 +- deimos/vulkan/vulkan.h | 48 ------ deimos/vulkan/vulkan_backend.cpp | 318 +++++++++++++++++++++++++++++++++++++++ deimos/vulkan/vulkan_backend.h | 33 ++++ deimos/vulkan/vulkan_loader.cpp | 12 +- deimos/vulkan/vulkan_loader.h | 49 ++++++ 6 files changed, 408 insertions(+), 57 deletions(-) delete mode 100644 deimos/vulkan/vulkan.h create mode 100644 deimos/vulkan/vulkan_backend.cpp create mode 100644 deimos/vulkan/vulkan_backend.h create mode 100644 deimos/vulkan/vulkan_loader.h (limited to 'deimos/vulkan') diff --git a/deimos/vulkan/BUILD b/deimos/vulkan/BUILD index 5eae9ef..238261f 100644 --- a/deimos/vulkan/BUILD +++ b/deimos/vulkan/BUILD @@ -1,10 +1,13 @@ cc_library( name = "vulkan", hdrs = [ - "vulkan.h", + "vulkan_backend.h", + "vulkan_loader.h", ], srcs = [ + "vulkan_backend.cpp", "vulkan_loader.cpp", + "vulkan_bootstrap_functions.inc", "vulkan_entry_functions.inc", "vulkan_instance_functions.inc", diff --git a/deimos/vulkan/vulkan.h b/deimos/vulkan/vulkan.h deleted file mode 100644 index f116fc0..0000000 --- a/deimos/vulkan/vulkan.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include - -#define VK_NO_STDINT_H -#define VK_NO_STDDEF_H -#include - -#include - -using HINSTANCE = void*; -using HWND = void*; -#include - -namespace deimos -{ - -class ApiRegistry; - -struct VulkanApi -{ -#define FN(NAME) PFN_vk##NAME NAME{}; -#include "deimos/vulkan/vulkan_bootstrap_functions.inc" -#include "deimos/vulkan/vulkan_entry_functions.inc" -#include "deimos/vulkan/vulkan_instance_functions.inc" -#include "deimos/vulkan/vulkan_device_functions.inc" -#undef FN -}; - -class VulkanLoaderApi -{ -public: - VulkanLoaderApi() = default; - deimos_NO_COPY_MOVE(VulkanLoaderApi); - virtual ~VulkanLoaderApi() = default; - - static constexpr IdName kApiName{"deimos::VulkanLoaderApi"}; - - virtual VulkanApi* LoadEntry() = 0; - - virtual void LoadInstance(VulkanApi*, VkInstance) = 0; - virtual void LoadDevice(VulkanApi*, VkDevice) = 0; -}; - -void RegisterVulkanLoaderApi(ApiRegistry*); - -} // namespace deimos - diff --git a/deimos/vulkan/vulkan_backend.cpp b/deimos/vulkan/vulkan_backend.cpp new file mode 100644 index 0000000..173e671 --- /dev/null +++ b/deimos/vulkan/vulkan_backend.cpp @@ -0,0 +1,318 @@ +#include "deimos/vulkan/vulkan_backend.h" + +#include "deimos/vulkan/vulkan_loader.h" + +#include +#include +#include +#include +#include + +namespace +{ + +using namespace deimos; + +LogApi* log_api; +OsApi* os_api; +ApiRegistry* api_registry; +TempAllocatorApi* temp_api; +VulkanLoaderApi* vulkan_loader_api; + +const VkAllocationCallbacks* kVkAlloc = nullptr; + +class VulkanBackendImpl : public IVulkanBackend +{ + VulkanApi* m_vk; + VkInstance m_instance; + VkPhysicalDevice m_physical_device; + uint32_t m_queue_family; + VkSurfaceKHR m_surface; + VkDevice m_device; + VkQueue m_queue = VK_NULL_HANDLE; + +public: + VulkanBackendImpl( + VulkanApi* vk, + VkInstance instance, + VkPhysicalDevice physical_device, + uint32_t queue_family, + VkSurfaceKHR surface, + VkDevice device) : + m_vk{vk}, m_instance{instance}, m_physical_device{physical_device}, + m_queue_family{queue_family}, m_surface{surface}, m_device{device} + { + m_vk->GetDeviceQueue(m_device, m_queue_family, 0, &m_queue); + Ensures(m_queue != VK_NULL_HANDLE); + } +}; + +StatusOr CreateInstance(VulkanApi* vk) +{ + const VkApplicationInfo application_info{ + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pNext = nullptr, + .pApplicationName = "Deimos game", + .applicationVersion = 0, + .pEngineName = "Deimos engine", + .engineVersion = 0, + .apiVersion = VK_API_VERSION_1_3, + }; + + const char* extensions[]{ + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_WIN32_SURFACE_EXTENSION_NAME, + }; + + // @Todo Select layers & extensions based on config + const char* layers[]{ + "VK_LAYER_KHRONOS_validation", + "VK_LAYER_LUNARG_monitor", + }; + + const VkInstanceCreateInfo create_info{ + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .pApplicationInfo = &application_info, + .enabledLayerCount = (uint32_t)ArraySize(layers), + .ppEnabledLayerNames = layers, + .enabledExtensionCount = (uint32_t)ArraySize(extensions), + .ppEnabledExtensionNames = extensions, + }; + + VkInstance instance{}; + const VkResult res = vk->CreateInstance(&create_info, kVkAlloc, &instance); + if (res != VK_SUCCESS) + { + return InternalError("vkCreateInstance failed"); + } + + return instance; +} + +StatusOr CreateSurface(VulkanApi* vk, VkInstance instance, OsWindow* window) +{ + const VkWin32SurfaceCreateInfoKHR create_info{ + .sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, + .pNext = nullptr, + .flags = 0, + .hinstance = os_api->window->Win32Hinstance(window), + .hwnd = os_api->window->Win32Hwnd(window), + }; + + VkSurfaceKHR surface{}; + const VkResult res = vk->CreateWin32SurfaceKHR(instance, &create_info, kVkAlloc, &surface); + if (res != VK_SUCCESS) + { + return InternalError("vkCreateWin32SurfaceKHR failed"); + } + + return surface; +} + +StatusOr FindQueueFamily(VulkanApi* vk, VkPhysicalDevice physical_device, VkSurfaceKHR surface) +{ + auto temp_alloc = temp_api->Acquire(); + + uint32_t queue_family_count = 0; + vk->GetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, nullptr); + if (queue_family_count == 0) + { + return InternalError("No queue on this physical device"); + } + + log_api->LogInfo("Physical device has $ queue families", queue_family_count); + + auto queue_families = temp_alloc.allocator().NewArray(queue_family_count); + vk->GetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families.data()); + + for (uint32_t index = 0; index < queue_family_count; ++index) + { + const auto& prps = queue_families[index]; + + const bool supports_graphics_compute = (prps.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0 && (prps.queueFlags & VK_QUEUE_COMPUTE_BIT) != 0; + + VkBool32 vk_supports_presentation{}; + const VkResult presentation_res = vk->GetPhysicalDeviceSurfaceSupportKHR(physical_device, index, surface, &vk_supports_presentation); + const bool supports_presentation = presentation_res == VK_SUCCESS && vk_supports_presentation == VK_TRUE; + + if (supports_graphics_compute && supports_presentation) + { + return index; + } + } + + return InternalError("Couldn't find a suitable queue"); +} + +Status FindPhysicalDevice( + VulkanApi* vk, + VkInstance instance, + VkSurfaceKHR surface, + VkPhysicalDevice* out_physical_device, + uint32_t* out_queue_family) +{ + auto temp_alloc = temp_api->Acquire(); + + uint32_t physical_device_count = 0; + vk->EnumeratePhysicalDevices(instance, &physical_device_count, nullptr); + if (physical_device_count == 0) + { + return InternalError("No Vulkan device found"); + } + + log_api->LogInfo("Found $ physical devices", physical_device_count); + + auto physical_devices = temp_alloc.allocator().NewArray(physical_device_count); + vk->EnumeratePhysicalDevices(instance, &physical_device_count, physical_devices.data()); + + for (VkPhysicalDevice physical_device: physical_devices) + { + { + VkPhysicalDeviceVulkan12Properties prps12{}; + prps12.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES; + prps12.pNext = nullptr; + + VkPhysicalDeviceProperties2 prps{}; + prps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + prps.pNext = &prps12; + + vk->GetPhysicalDeviceProperties2(physical_device, &prps); + + log_api->LogInfo("Trying Vulkan device $ ($, $)", + prps.properties.deviceName, prps12.driverName, prps12.driverInfo); + } + + auto maybe_queue_family = FindQueueFamily(vk, physical_device, surface); + if (maybe_queue_family.ok()) + { + log_api->LogInfo("This device is compatible, choosing it"); + *out_physical_device = physical_device; + *out_queue_family = maybe_queue_family.value(); + return {}; + } + + log_api->LogInfo("Incompatible because: $", maybe_queue_family); + } + + return InternalError("No suitable device found"); +} + +StatusOr CreateDevice(VulkanApi* vk, VkPhysicalDevice physical_device, uint32_t queue_family) +{ + const float queue_priority = 1.0F; + + const VkDeviceQueueCreateInfo queue_create_info{ + .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueFamilyIndex = queue_family, + .queueCount = 1, + .pQueuePriorities = &queue_priority, + }; + + const char* extensions[]{ + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + }; + + const VkDeviceCreateInfo create_info{ + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .queueCreateInfoCount = 1, + .pQueueCreateInfos = &queue_create_info, + .enabledLayerCount = 0, + .ppEnabledLayerNames = nullptr, + .enabledExtensionCount = ArraySize(extensions), + .ppEnabledExtensionNames = extensions, + .pEnabledFeatures = nullptr, + }; + + VkDevice device = VK_NULL_HANDLE; + const VkResult res = vk->CreateDevice(physical_device, &create_info, kVkAlloc, &device); + if (res != VK_SUCCESS) + { + return InternalError("vkCreateDeviceFailed"); + } + + return device; +} + +StatusOr> InitializeVulkan(Allocator* allocator, OsWindow* window) +{ + auto* vk = vulkan_loader_api->LoadEntry(allocator); + + VkInstance instance{}; + { + StatusOr s = CreateInstance(vk); + if (!s.ok()) { return s.status(); } + instance = s.value(); + log_api->LogInfo("Vulkan instance created"); + } + + vulkan_loader_api->LoadInstance(vk, instance); + + VkSurfaceKHR surface{}; + { + StatusOr s = CreateSurface(vk, instance, window); + if (!s.ok()) { return s.status(); } + surface = s.value(); + log_api->LogInfo("Vulkan surface created"); + } + + VkPhysicalDevice physical_device{}; + uint32_t queue_family{}; + { + Status s = FindPhysicalDevice(vk, instance, surface, &physical_device, &queue_family); + if (!s.ok()) { return s; } + log_api->LogInfo("Vulkan queue family: $", queue_family); + } + + VkDevice device = VK_NULL_HANDLE; + { + StatusOr s = CreateDevice(vk, physical_device, queue_family); + if (!s.ok()) { return s.status(); } + device = s.value(); + log_api->LogInfo("Vulkan device created"); + } + + vulkan_loader_api->LoadDevice(vk, device); + + return allocator->New( + vk, instance, physical_device, + queue_family, surface, device); +} + +} // anonymous namespace + +namespace deimos +{ + +class VulkanBackendApiImpl : public VulkanBackendApi +{ +public: + StatusOr> CreateBackend(Allocator* allocator, OsWindow* window) override + { + return InitializeVulkan(allocator, window); + } +}; + +void RegisterVulkanBackendApi(ApiRegistry* registry) +{ + api_registry = registry; + log_api = registry->Get(); + os_api = registry->Get(); + temp_api = registry->Get(); + vulkan_loader_api = registry->Get(); + + auto* allocator_api = registry->Get(); + + auto* impl = allocator_api->system->New(); + registry->Set(impl); + + log_api->LogInfo("Vulkan backend API registered"); +} + +} // namespace deimos + diff --git a/deimos/vulkan/vulkan_backend.h b/deimos/vulkan/vulkan_backend.h new file mode 100644 index 0000000..90aaf2a --- /dev/null +++ b/deimos/vulkan/vulkan_backend.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +namespace deimos +{ + +class ApiRegistry; +class Allocator; +struct OsWindow; + +class IVulkanBackend +{ +public: +}; + +class VulkanBackendApi +{ +public: + VulkanBackendApi() = default; + deimos_NO_COPY_MOVE(VulkanBackendApi); + virtual ~VulkanBackendApi() = default; + + static constexpr IdName kApiName{"deimos::VulkanBackendApi"}; + + virtual StatusOr> CreateBackend(Allocator*, OsWindow*) = 0; +}; + +void RegisterVulkanBackendApi(ApiRegistry*); + +} // namespace deimos + diff --git a/deimos/vulkan/vulkan_loader.cpp b/deimos/vulkan/vulkan_loader.cpp index b9980af..c4d7d87 100644 --- a/deimos/vulkan/vulkan_loader.cpp +++ b/deimos/vulkan/vulkan_loader.cpp @@ -1,4 +1,4 @@ -#include "deimos/vulkan/vulkan.h" +#include "deimos/vulkan/vulkan_loader.h" #include #include @@ -13,13 +13,10 @@ namespace deimos class VulkanLoaderImpl : public VulkanLoaderApi { - Allocator* m_allocator; gsl::owner m_vulkan_dll{}; public: - explicit VulkanLoaderImpl(Allocator* allocator) : m_allocator{allocator} {} - - VulkanApi* LoadEntry() override + VulkanApi* LoadEntry(Allocator* allocator) override { if (m_vulkan_dll == nullptr) { @@ -34,7 +31,7 @@ public: } } - VulkanApi* api = m_allocator->New(); + VulkanApi* api = allocator->New(); #define FN(NAME) api->NAME = (PFN_vk##NAME)os_api->dll->GetSymbol(m_vulkan_dll, "vk" #NAME); #include "deimos/vulkan/vulkan_bootstrap_functions.inc" @@ -76,9 +73,8 @@ void RegisterVulkanLoaderApi(ApiRegistry* registry) log_api = registry->Get(); auto* allocator_api = registry->Get(); - auto* allocator = allocator_api->CreateChild(allocator_api->system, "Vulkan"); - auto* impl = allocator->New(allocator); + auto* impl = allocator_api->system->New(); registry->Set(impl); log_api->LogInfo("Vulkan loader API registered"); diff --git a/deimos/vulkan/vulkan_loader.h b/deimos/vulkan/vulkan_loader.h new file mode 100644 index 0000000..1339ba4 --- /dev/null +++ b/deimos/vulkan/vulkan_loader.h @@ -0,0 +1,49 @@ +#pragma once + +#include + +#define VK_NO_STDINT_H +#define VK_NO_STDDEF_H +#include + +#include + +using HINSTANCE = void*; +using HWND = void*; +#include + +namespace deimos +{ + +class ApiRegistry; +class Allocator; + +struct VulkanApi +{ +#define FN(NAME) PFN_vk##NAME NAME{}; +#include "deimos/vulkan/vulkan_bootstrap_functions.inc" +#include "deimos/vulkan/vulkan_entry_functions.inc" +#include "deimos/vulkan/vulkan_instance_functions.inc" +#include "deimos/vulkan/vulkan_device_functions.inc" +#undef FN +}; + +class VulkanLoaderApi +{ +public: + VulkanLoaderApi() = default; + deimos_NO_COPY_MOVE(VulkanLoaderApi); + virtual ~VulkanLoaderApi() = default; + + static constexpr IdName kApiName{"deimos::VulkanLoaderApi"}; + + virtual VulkanApi* LoadEntry(Allocator* allocator) = 0; + + virtual void LoadInstance(VulkanApi*, VkInstance) = 0; + virtual void LoadDevice(VulkanApi*, VkDevice) = 0; +}; + +void RegisterVulkanLoaderApi(ApiRegistry*); + +} // namespace deimos + -- cgit