Add create/resize/destroy_swapchain functions to RenderDriver

This commit is contained in:
2025-04-18 20:43:17 +10:00
parent b23d2d9c3c
commit 47a07ea6a8
4 changed files with 192 additions and 0 deletions

View File

@@ -37,5 +37,9 @@ namespace Nova {
// TODO: set_surface_size
// TODO: set_surface_mode
// TODO: set_surface_state
[[nodiscard]] virtual SwapchainID create_swapchain(SurfaceID surface) = 0;
virtual void resize_swapchain(SwapchainID swapchain) = 0;
virtual void destroy_swapchain(SwapchainID swapchain) = 0;
};
} // namespace Nova

View File

@@ -13,5 +13,8 @@ namespace Nova {
struct RenderDevice;
struct Surface;
struct Swapchain;
using SurfaceID = Surface*;
using SwapchainID = Swapchain*;
} // namespace Nova

View File

@@ -14,7 +14,9 @@
#include <nova/version.h>
#include <vulkan/vulkan.h>
#include <algorithm>
#include <format>
#include <limits>
#include <unordered_map>
#define VALIDATION_LAYER "VK_LAYER_KHRONOS_validation"
@@ -119,10 +121,176 @@ SurfaceID VulkanRenderDriver::create_surface(WindowID p_window) {
void VulkanRenderDriver::destroy_surface(SurfaceID p_surface) {
NOVA_AUTO_TRACE();
NOVA_ASSERT(p_surface);
vkDestroySurfaceKHR(m_instance, p_surface->handle, get_allocator(VK_OBJECT_TYPE_SURFACE_KHR));
delete p_surface;
}
SwapchainID VulkanRenderDriver::create_swapchain(SurfaceID p_surface) {
NOVA_AUTO_TRACE();
NOVA_ASSERT(p_surface);
Swapchain* swapchain = new Swapchain();
swapchain->surface = p_surface;
u32 count;
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, p_surface->handle, &count, nullptr); // TODO: Check result
std::vector<VkSurfaceFormatKHR> formats(count);
vkGetPhysicalDeviceSurfaceFormatsKHR(m_physical_device, p_surface->handle, &count, formats.data()); // TODO: Check result
const VkFormat preferred_format = VK_FORMAT_B8G8R8A8_UNORM; // TODO: Get from config?
const VkFormat fallback_format = VK_FORMAT_R8G8B8A8_UNORM; // TODO: Get from config?
if (count == 1 && formats[0].format == VK_FORMAT_UNDEFINED) {
swapchain->format = preferred_format;
swapchain->color_space = formats[0].colorSpace;
} else {
for (const auto& format : formats) {
if (format.format == preferred_format || format.format == fallback_format) {
swapchain->format = format.format;
swapchain->color_space = format.colorSpace;
if (swapchain->format == preferred_format) {
break;
}
}
}
}
if (swapchain->format == VK_FORMAT_UNDEFINED) {
throw std::runtime_error("Failed to find a supported swapchain format");
}
// TODO: Create render pass
return swapchain;
}
void VulkanRenderDriver::resize_swapchain(SwapchainID p_swapchain) {
NOVA_AUTO_TRACE();
NOVA_ASSERT(p_swapchain);
Surface* surface = p_swapchain->surface;
// TODO: Release old swapchain resources
VkSurfaceCapabilitiesKHR capabilities;
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, surface->handle, &capabilities) != VK_SUCCESS) {
throw std::runtime_error("Failed to get surface capabilities");
}
VkExtent2D extent;
if (capabilities.currentExtent.width == std::numeric_limits<u32>::max()) {
extent.width = std::clamp(surface->width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
extent.height =
std::clamp(surface->height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
} else {
extent = capabilities.currentExtent;
surface->width = extent.width;
surface->height = extent.height;
}
if (extent.width == 0 || extent.height == 0) {
return;
}
u32 image_count = capabilities.minImageCount + 1;
if (capabilities.maxImageCount > 0 && image_count > capabilities.maxImageCount) {
image_count = capabilities.maxImageCount;
}
u32 present_mode_count;
vkGetPhysicalDeviceSurfacePresentModesKHR(
m_physical_device,
surface->handle,
&present_mode_count,
nullptr
); // TODO: Check result
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
vkGetPhysicalDeviceSurfacePresentModesKHR(
m_physical_device,
surface->handle,
&present_mode_count,
present_modes.data()
); // TODO: Check result
VkPresentModeKHR present_mode = VK_PRESENT_MODE_MAILBOX_KHR; // TODO: Get from config
if (std::find(present_modes.begin(), present_modes.end(), present_mode) == present_modes.end()) {
NOVA_WARN("Preferred present mode not supported, falling back to FIFO");
present_mode = VK_PRESENT_MODE_FIFO_KHR;
// TODO: Update config
}
VkSwapchainCreateInfoKHR swap_create {};
swap_create.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swap_create.surface = surface->handle;
swap_create.minImageCount = image_count;
swap_create.imageFormat = p_swapchain->format;
swap_create.imageColorSpace = p_swapchain->color_space;
swap_create.imageExtent = extent;
swap_create.imageArrayLayers = 1; // TODO: Support VR
swap_create.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; // TRANSFER_DST_BIT ???
swap_create.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swap_create.preTransform = capabilities.currentTransform;
swap_create.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; // TODO: Support transparent windows
swap_create.presentMode = present_mode;
swap_create.clipped = VK_TRUE;
swap_create.oldSwapchain = VK_NULL_HANDLE; // TODO: Handle old swapchain
if (vkCreateSwapchainKHR(m_device, &swap_create, get_allocator(VK_OBJECT_TYPE_SWAPCHAIN_KHR), &p_swapchain->handle)
!= VK_SUCCESS) {
throw std::runtime_error("Failed to create swapchain");
}
NOVA_LOG("VkSwapchainKHR created");
vkGetSwapchainImagesKHR(m_device, p_swapchain->handle, &image_count, nullptr); // TODO: Check result
p_swapchain->images.resize(image_count);
vkGetSwapchainImagesKHR(m_device, p_swapchain->handle, &image_count, p_swapchain->images.data()); // TODO: Check result
VkImageViewCreateInfo view_create {};
view_create.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view_create.viewType = VK_IMAGE_VIEW_TYPE_2D;
view_create.format = p_swapchain->format;
view_create.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
view_create.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
view_create.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
view_create.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
view_create.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view_create.subresourceRange.baseMipLevel = 0;
view_create.subresourceRange.levelCount = 1;
view_create.subresourceRange.baseArrayLayer = 0; // TODO: Support VR
view_create.subresourceRange.layerCount = 1; // TODO: Support VR
p_swapchain->image_views.resize(image_count);
for (u32 i = 0; i < image_count; i++) {
view_create.image = p_swapchain->images[i];
if (vkCreateImageView(m_device, &view_create, get_allocator(VK_OBJECT_TYPE_IMAGE_VIEW), &p_swapchain->image_views[i])
!= VK_SUCCESS) {
throw std::runtime_error("Failed to create image view");
}
}
// TODO: Create framebuffers
}
void VulkanRenderDriver::destroy_swapchain(SwapchainID p_swapchain) {
NOVA_AUTO_TRACE();
NOVA_ASSERT(p_swapchain);
for (const auto& framebuffer : p_swapchain->framebuffers) {
vkDestroyFramebuffer(m_device, framebuffer, get_allocator(VK_OBJECT_TYPE_FRAMEBUFFER));
}
for (const auto& image_view : p_swapchain->image_views) {
vkDestroyImageView(m_device, image_view, get_allocator(VK_OBJECT_TYPE_IMAGE_VIEW));
}
if (p_swapchain->handle) {
vkDestroySwapchainKHR(m_device, p_swapchain->handle, get_allocator(VK_OBJECT_TYPE_SWAPCHAIN_KHR));
}
// TODO: Destroy render pass
delete p_swapchain;
}
VkInstance VulkanRenderDriver::get_instance() const {
return m_instance;
}

View File

@@ -16,6 +16,19 @@
namespace Nova {
struct Surface {
VkSurfaceKHR handle = VK_NULL_HANDLE;
u32 width = 0;
u32 height = 0;
bool dirty = false; // TODO: Use state enum
};
struct Swapchain {
VkSwapchainKHR handle = VK_NULL_HANDLE;
VkFormat format = VK_FORMAT_UNDEFINED;
VkColorSpaceKHR color_space = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
std::vector<VkImage> images;
std::vector<VkImageView> image_views;
std::vector<VkFramebuffer> framebuffers;
SurfaceID surface = nullptr;
};
class VulkanRenderDriver final : public RenderDriver {
@@ -36,6 +49,10 @@ namespace Nova {
[[nodiscard]] SurfaceID create_surface(WindowID window) override;
void destroy_surface(SurfaceID surface) override;
[[nodiscard]] SwapchainID create_swapchain(SurfaceID surface) override;
void resize_swapchain(SwapchainID swapchain) override;
void destroy_swapchain(SwapchainID swapchain) override;
[[nodiscard]] VkInstance get_instance() const;
[[nodiscard]] VkAllocationCallbacks* get_allocator(VkObjectType type) const;