From 8279acc1a4754bedaad0ba8bf00541c7b2a043e9 Mon Sep 17 00:00:00 2001
From: Steven Le Rouzic <steven.lerouzic@gmail.com>
Date: Wed, 12 Jun 2024 23:57:24 +0200
Subject: Swapchain creation

---
 deimos/core/allocator.h                     |  15 ++++
 deimos/core/base.h                          |  14 +++-
 deimos/render/backend.h                     |   6 ++
 deimos/vulkan/vulkan_backend.cpp            | 109 ++++++++++++++++++++++++++++
 deimos/vulkan/vulkan_device_functions.inc   |   9 ++-
 deimos/vulkan/vulkan_instance_functions.inc |   3 +
 6 files changed, 153 insertions(+), 3 deletions(-)

(limited to 'deimos')

diff --git a/deimos/core/allocator.h b/deimos/core/allocator.h
index 9e8dd87..f6adea2 100644
--- a/deimos/core/allocator.h
+++ b/deimos/core/allocator.h
@@ -143,6 +143,21 @@ public:
             std::forward<A5>(arg5));
     }
 
+    template<typename T, typename A0, typename A1, typename A2, typename A3, typename A4, typename A5, typename A6>
+    constexpr gsl::owner<T*> New(
+        A0&& arg0, A1&& arg1, A2&& arg2, A3&& arg3, A4&& arg4, A5&& arg5, A6&& arg6,
+        const SourceLocation& source_location = {})
+    {
+        return NewInner<T>(source_location,
+            std::forward<A0>(arg0),
+            std::forward<A1>(arg1),
+            std::forward<A2>(arg2),
+            std::forward<A3>(arg3),
+            std::forward<A4>(arg4),
+            std::forward<A5>(arg5),
+            std::forward<A6>(arg6));
+    }
+
     template<typename T>
     void Delete(gsl::owner<T*> t, const SourceLocation& source_location = {})
     {
diff --git a/deimos/core/base.h b/deimos/core/base.h
index 0c15ccd..4c74b33 100644
--- a/deimos/core/base.h
+++ b/deimos/core/base.h
@@ -64,8 +64,9 @@ struct SourceLocation
     {}
 };
 
-template<typename T> T Min(T a, T b) { return (a < b) ? a : b; }
-template<typename T> T Max(T a, T b) { return (a > b) ? a : b; }
+template<typename T> constexpr T Min(T a, T b) { return (a < b) ? a : b; }
+template<typename T> constexpr T Max(T a, T b) { return (a > b) ? a : b; }
+template<typename T> constexpr T Clamp(T x, T min, T max) { return Min(Max(x, min), max); }
 
 [[maybe_unused]] static constexpr int64_t Kilobytes = 1024;
 [[maybe_unused]] static constexpr int64_t Megabytes = 1024 * 1024;
@@ -151,6 +152,15 @@ public:
         Expects(offset + size <= m_size);
         return Span(m_begin + offset, size);
     }
+
+    bool Contains(const T& v) const
+    {
+        for (const T& p: *this)
+        {
+            if (p == v) { return true; }
+        }
+        return false;
+    }
 };
 
 template<typename T>
diff --git a/deimos/render/backend.h b/deimos/render/backend.h
index cba8d90..bb94cb9 100644
--- a/deimos/render/backend.h
+++ b/deimos/render/backend.h
@@ -1,10 +1,13 @@
 #pragma once 
 
 #include <deimos/core/base.h>
+#include <deimos/core/status.h>
 
 namespace deimos
 {
 
+struct RenderSwapchain{};
+
 class IRenderBackend
 {
 public:
@@ -14,6 +17,9 @@ public:
 
     virtual void BeginFrame() = 0;
     virtual void EndFrame() = 0;
+
+    virtual StatusOr<gsl::owner<RenderSwapchain*>> CreateSwapchain() = 0;
+    virtual void DestroySwapchain(gsl::owner<RenderSwapchain*>) = 0;
 };
 
 } // namespace deimos
diff --git a/deimos/vulkan/vulkan_backend.cpp b/deimos/vulkan/vulkan_backend.cpp
index 87e4451..cc54d4d 100644
--- a/deimos/vulkan/vulkan_backend.cpp
+++ b/deimos/vulkan/vulkan_backend.cpp
@@ -22,8 +22,14 @@ VulkanLoaderApi*  vulkan_loader_api;
 
 const VkAllocationCallbacks* kVkAlloc = nullptr;
 
+struct VulkanSwapchain: public RenderSwapchain
+{
+    VkSwapchainKHR swapchain{};
+};
+
 class VulkanBackendImpl : public IVulkanBackend, public IRenderBackend
 {
+    Allocator*       m_allocator;
     VulkanApi*       m_vk;
     VkInstance       m_instance;
     VkPhysicalDevice m_physical_device;
@@ -41,12 +47,14 @@ class VulkanBackendImpl : public IVulkanBackend, public IRenderBackend
 
 public:
     VulkanBackendImpl(
+        Allocator* allocator,
         VulkanApi* vk,
         VkInstance instance,
         VkPhysicalDevice physical_device,
         uint32_t queue_family,
         VkSurfaceKHR surface,
         VkDevice device) :
+        m_allocator{allocator},
         m_vk{vk}, m_instance{instance}, m_physical_device{physical_device},
         m_queue_family{queue_family}, m_surface{surface}, m_device{device}
     {
@@ -80,6 +88,106 @@ public:
         m_vk->DeviceWaitIdle(m_device);
         m_vk->DestroyCommandPool(m_device, m_frame_resources.cmd_pool, kVkAlloc);
     }
+    
+    StatusOr<gsl::owner<RenderSwapchain*>> CreateSwapchain() override
+    {
+        auto temp_alloc = temp_api->Acquire();
+        
+        VkSurfaceCapabilitiesKHR caps;
+        VkResult res = m_vk->GetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &caps);
+        if (res != VK_SUCCESS)
+        {
+            return RuntimeError("Couldn't retrieve Vulkan surface capabitilies");
+        }
+
+        uint32_t format_count = 0;
+        uint32_t present_mode_count = 0;
+        
+        m_vk->GetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, m_surface, &format_count, nullptr);
+        m_vk->GetPhysicalDeviceSurfacePresentModesKHR(m_physical_device, m_surface, &present_mode_count, nullptr);
+        
+        auto formats       = temp_alloc.allocator().NewArray<VkSurfaceFormatKHR>(format_count);
+        auto present_modes = temp_alloc.allocator().NewArray<VkPresentModeKHR>(present_mode_count);
+
+        m_vk->GetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, m_surface, &format_count, formats.data());
+        m_vk->GetPhysicalDeviceSurfacePresentModesKHR(m_physical_device, m_surface, &present_mode_count, present_modes.data());
+
+        VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
+        if (present_modes.Contains(VK_PRESENT_MODE_MAILBOX_KHR))
+        {
+            present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
+        }
+
+        static constexpr VkFormat kAcceptableFormats[] = {
+            VK_FORMAT_R8G8B8_SRGB,
+            VK_FORMAT_B8G8R8_SRGB,
+            VK_FORMAT_R8G8B8A8_SRGB,
+            VK_FORMAT_B8G8R8A8_SRGB,
+        };
+
+        bool has_format = false;
+        VkSurfaceFormatKHR format;
+        for (const auto& candidate: kAcceptableFormats)
+        {
+            for (const auto& f: formats)
+            {
+                if (f.format == candidate && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR)
+                {
+                    has_format = true;
+                    format = f;
+                    break;
+                }
+            }
+
+            if (has_format) { break; }
+        }
+
+
+        if (!has_format)
+        {
+            return RuntimeError("No acceptable Vulkan swapchain format found");
+        }
+
+        VkSwapchainCreateInfoKHR create_info = {
+            .sType                        = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
+            .pNext                        = nullptr,
+            .flags                        = 0,
+            .surface                      = m_surface,
+            .minImageCount                = Clamp<uint32_t>(2, caps.minImageCount, caps.maxImageCount),
+            .imageFormat                  = format.format,
+            .imageColorSpace              = format.colorSpace,
+            .imageExtent                  = caps.currentExtent,
+            .imageArrayLayers             = 1,
+            .imageUsage                   = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
+            .imageSharingMode             = VK_SHARING_MODE_EXCLUSIVE,
+            .queueFamilyIndexCount        = 0,
+            .pQueueFamilyIndices          = nullptr,
+            .preTransform                 = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
+            .compositeAlpha               = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
+            .presentMode                  = present_mode,
+            .clipped                      = VK_TRUE,
+            .oldSwapchain                 = VK_NULL_HANDLE,
+        };
+
+        VkSwapchainKHR vk_swapchain{};
+        res = m_vk->CreateSwapchainKHR(m_device, &create_info, kVkAlloc, &vk_swapchain);
+        if (res != VK_SUCCESS)
+        {
+            return RuntimeError("Error when creating Vulkan swapchain");
+        }
+
+        auto* swapchain = m_allocator->New<VulkanSwapchain>();
+        swapchain->swapchain = vk_swapchain;
+
+        return swapchain;
+    }
+    
+    void DestroySwapchain(gsl::owner<RenderSwapchain*> swapchain) override
+    {
+        auto* vk_swapchain = (VulkanSwapchain*)swapchain;
+        m_vk->DestroySwapchainKHR(m_device, vk_swapchain->swapchain, kVkAlloc);
+        m_allocator->Delete(swapchain);
+    }
 };
 
 StatusOr<VkInstance> CreateInstance(VulkanApi* vk)
@@ -315,6 +423,7 @@ StatusOr<gsl::owner<IVulkanBackend*>> InitializeVulkan(Allocator* allocator, OsW
     vulkan_loader_api->LoadDevice(vk, device);
 
     return allocator->New<VulkanBackendImpl>(
+        allocator,
         vk, instance, physical_device,
         queue_family, surface, device);
 }
diff --git a/deimos/vulkan/vulkan_device_functions.inc b/deimos/vulkan/vulkan_device_functions.inc
index f83ebb7..f6e6a30 100644
--- a/deimos/vulkan/vulkan_device_functions.inc
+++ b/deimos/vulkan/vulkan_device_functions.inc
@@ -1,8 +1,15 @@
 // NOLINTBEGIN
 FN(DestroyDevice)
+FN(DeviceWaitIdle)
+
 FN(GetDeviceQueue)
 FN(CreateCommandPool)
 FN(ResetCommandPool)
 FN(DestroyCommandPool)
-FN(DeviceWaitIdle)
+
+FN(CreateSwapchainKHR)
+FN(DestroySwapchainKHR)
+FN(GetSwapchainImagesKHR)
+FN(AcquireNextImageKHR)
+FN(QueuePresentKHR)
 // NOLINTEND
diff --git a/deimos/vulkan/vulkan_instance_functions.inc b/deimos/vulkan/vulkan_instance_functions.inc
index 0a46455..89ace12 100644
--- a/deimos/vulkan/vulkan_instance_functions.inc
+++ b/deimos/vulkan/vulkan_instance_functions.inc
@@ -10,4 +10,7 @@ FN(GetDeviceProcAddr)
 FN(CreateWin32SurfaceKHR)
 FN(DestroySurfaceKHR)
 FN(GetPhysicalDeviceSurfaceSupportKHR)
+FN(GetPhysicalDeviceSurfaceCapabilitiesKHR)
+FN(GetPhysicalDeviceSurfaceFormatsKHR)
+FN(GetPhysicalDeviceSurfacePresentModesKHR)
 // NOLINTEND
-- 
cgit