#include <cstdio>
#include <volk.h>
#include <GLFW/glfw3.h>
#include "backend/rhi.h"
#include "backend/vulkan/debug.h"
#include "backend/vulkan/utils.h"
#include "backend/vulkan/vk_api.h"
#include "backend/vulkan/vk_conversion.h"
#include "core/globals.h"
#include "core/logger.h"
namespace rhi {
using namespace vk;
VkSurfaceFormat2KHR eval_surface_format(const std::vector<VkSurfaceFormat2KHR> &available_formats) {
if (available_formats.size() == 1 && available_formats[0].surfaceFormat.format == VK_FORMAT_UNDEFINED) {
VkSurfaceFormat2KHR result{
.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR,
.surfaceFormat = {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}
};
return result;
}
const VkSurfaceFormat2KHR preferred_formats[] = {
{.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR,
.surfaceFormat = {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}},
{.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR,
.surfaceFormat = {VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}}
};
for (const auto &chosen_preferred_format : preferred_formats) {
for (const auto &chosen_available_format : available_formats) {
if (chosen_available_format.surfaceFormat.format == chosen_preferred_format.surfaceFormat.format &&
chosen_available_format.surfaceFormat.colorSpace == chosen_preferred_format.surfaceFormat.colorSpace) {
return chosen_available_format;
}
}
}
return available_formats[0];
}
VkPresentModeKHR eval_present_mode(const std::vector<VkPresentModeKHR> &availablePresentModes, bool vSync = true) {
if (vSync) {
return VK_PRESENT_MODE_FIFO_KHR;
}
bool mailbox_supported = false;
bool immediate_supported = false;
for (VkPresentModeKHR mode : availablePresentModes) {
if (mode == VK_PRESENT_MODE_MAILBOX_KHR) {
mailbox_supported = true;
}
if (mode == VK_PRESENT_MODE_IMMEDIATE_KHR) {
immediate_supported = true;
}
}
if (mailbox_supported) {
return VK_PRESENT_MODE_MAILBOX_KHR;
}
if (immediate_supported) {
return VK_PRESENT_MODE_IMMEDIATE_KHR;
}
return VK_PRESENT_MODE_FIFO_KHR;
}
void create_swapchain(Device &device, const rhi::SwapchainDesc &desc, Swapchain &swapchain) {
VEL_INFO("Creating Swapchain...");
VK_CHECK(glfwCreateWindowSurface(
device.instance, static_cast<GLFWwindow *>(desc.native_window), nullptr, &device.surface
));
VK_NAME(device, device.surface);
const VkPhysicalDeviceSurfaceInfo2KHR surface_info_2{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR, .surface = device.surface
};
VkSurfaceCapabilities2KHR capabilities2{.sType = VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR};
VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilities2KHR(device.physical, &surface_info_2, &capabilities2));
u32 format_count = 0;
VK_CHECK(vkGetPhysicalDeviceSurfaceFormats2KHR(device.physical, &surface_info_2, &format_count, nullptr));
std::vector<VkSurfaceFormat2KHR> formats(format_count, {.sType = VK_STRUCTURE_TYPE_SURFACE_FORMAT_2_KHR});
VK_CHECK(vkGetPhysicalDeviceSurfaceFormats2KHR(device.physical, &surface_info_2, &format_count, formats.data()));
u32 present_mode_count = 0;
VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(device.physical, device.surface, &present_mode_count, nullptr));
std::vector<VkPresentModeKHR> present_modes(present_mode_count);
VK_CHECK(vkGetPhysicalDeviceSurfacePresentModesKHR(
device.physical, device.surface, &present_mode_count, present_modes.data()
));
const VkSurfaceFormat2KHR surface_format = eval_surface_format(formats);
const VkPresentModeKHR present_mode = eval_present_mode(present_modes, desc.vsync_enabled);
swapchain.format = vk::image_format_from_vk(surface_format.surfaceFormat.format);
const VkExtent2D &ce = capabilities2.surfaceCapabilities.currentExtent;
int fb_width = 0;
int fb_height = 0;
glfwGetFramebufferSize(static_cast<GLFWwindow *>(desc.native_window), &fb_width, &fb_height);
u32 w;
if (desc.width > 0) {
w = desc.width;
} else if (ce.width > 0) {
w = ce.width;
} else {
w = (u32)fb_width;
}
u32 h;
if (desc.height > 0) {
h = desc.height;
} else if (ce.height > 0) {
h = ce.height;
} else {
h = (u32)fb_height;
}
swapchain.extent = rhi::Extent2D{w, h};
VkSwapchainCreateInfoKHR swapchain_ci{};
swapchain_ci.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchain_ci.surface = device.surface;
swapchain_ci.minImageCount = desc.image_count;
swapchain_ci.imageFormat = vk::image_format_to_vk(swapchain.format);
swapchain_ci.imageColorSpace = surface_format.surfaceFormat.colorSpace;
VkExtent2D vk_extent = {swapchain.extent.width, swapchain.extent.height};
swapchain_ci.imageExtent = vk_extent;
swapchain_ci.imageArrayLayers = 1;
swapchain_ci.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
swapchain_ci.preTransform = capabilities2.surfaceCapabilities.currentTransform;
swapchain_ci.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchain_ci.presentMode = present_mode;
swapchain_ci.clipped = VK_TRUE;
swapchain_ci.oldSwapchain = VK_NULL_HANDLE;
VkSwapchainKHR vk_swapchain;
VK_CHECK(vkCreateSwapchainKHR(device.logical, &swapchain_ci, nullptr, &vk_swapchain));
swapchain.handle = vk_swapchain;
VK_NAME_EX(device, swapchain.handle, "swapchain");
u32 image_count = 0;
VK_CHECK(vkGetSwapchainImagesKHR(device.logical, swapchain.handle, &image_count, nullptr));
if (desc.image_count <= image_count) {
image_count = desc.image_count;
}
std::vector<VkImage> vk_images(image_count);
VK_CHECK(vkGetSwapchainImagesKHR(device.logical, swapchain.handle, &image_count, vk_images.data()));
VkSemaphoreCreateInfo sem_ci{};
sem_ci.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fence_ci{};
fence_ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
VkDevice vk_dev = device.logical;
swapchain.backbuffers.resize(image_count);
swapchain.present.resize(image_count);
VK_CHECK(vkCreateFence(vk_dev, &fence_ci, nullptr, &swapchain.acquire));
for (u32 i = 0; i < image_count; i++) {
Image &backbuffer = swapchain.backbuffers[i];
backbuffer._handle = vk_images[i];
backbuffer.desc.format = swapchain.format;
backbuffer.desc.width = swapchain.extent.width;
backbuffer.desc.height = swapchain.extent.height;
backbuffer.desc.mip_levels = 1;
backbuffer.desc.layers = 1;
backbuffer.desc.sample_count = rhi::SampleCount::Sample1;
backbuffer.state = rhi::ResourceState::Idle;
// per-image, safe to reuse
VkSemaphore vk_present = VK_NULL_HANDLE;
VK_CHECK(vkCreateSemaphore(vk_dev, &sem_ci, nullptr, &vk_present));
swapchain.present[i] = vk_present;
char name[64];
(void)sprintf(name, "swapchain_image[%u]", i);
set_name(device, backbuffer._handle, name);
}
swapchain.recreate = false;
const char *present_mode_str = "UNKNOWN";
switch (present_mode) {
case VK_PRESENT_MODE_IMMEDIATE_KHR:
present_mode_str = "IMMEDIATE";
break;
case VK_PRESENT_MODE_MAILBOX_KHR:
present_mode_str = "MAILBOX";
break;
case VK_PRESENT_MODE_FIFO_KHR:
present_mode_str = "FIFO";
break;
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
present_mode_str = "FIFO_RELAXED";
break;
default:
break;
}
VEL_INFO(
"Swapchain created: {}x{} (format: {}, present: {}, vsync: {})",
swapchain.extent.width,
swapchain.extent.height,
static_cast<int>(swapchain.format),
present_mode_str,
desc.vsync_enabled
);
};
void destroy_swapchain(Device &device, Swapchain &swapchain) {
VEL_INFO("Destroying Swapchain...");
VkDevice vk_dev = device.logical;
for (u32 i = 0; i < (u32)swapchain.backbuffers.size(); ++i) {
vkDestroySemaphore(vk_dev, swapchain.present[i], nullptr);
swapchain.present[i] = VK_NULL_HANDLE;
rhi::destroy_image_views(device, swapchain.backbuffers[i]);
}
vkDestroyFence(vk_dev, swapchain.acquire, nullptr);
swapchain.acquire = VK_NULL_HANDLE;
swapchain.backbuffers.clear();
swapchain.present.clear();
vkDestroySwapchainKHR(vk_dev, swapchain.handle, nullptr);
if (device.surface != VK_NULL_HANDLE) {
vkDestroySurfaceKHR(device.instance, device.surface, nullptr);
device.surface = VK_NULL_HANDLE;
}
}
bool acquire_swapchain_image(Device &device, Swapchain &swapchain, u32 &out_image_index, CmdBuffer &cmd) {
VkResult res = vkAcquireNextImageKHR(
device.logical, swapchain.handle, UINT64_MAX, VK_NULL_HANDLE, swapchain.acquire, &out_image_index
);
if (res == VK_ERROR_OUT_OF_DATE_KHR) {
swapchain.recreate = true;
return false;
}
if (res == VK_ERROR_SURFACE_LOST_KHR || res == VK_ERROR_DEVICE_LOST) {
swapchain.recreate = true;
return false;
}
if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) {
VK_CHECK(res);
}
VK_CHECK(vkWaitForFences(device.logical, 1, &swapchain.acquire, VK_TRUE, UINT64_MAX));
VK_CHECK(vkResetFences(device.logical, 1, &swapchain.acquire));
Image &img = swapchain.backbuffers[out_image_index];
VkImageMemoryBarrier2 barrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2};
barrier.srcStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT;
barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = img._handle;
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkDependencyInfo dep{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
dep.imageMemoryBarrierCount = 1;
dep.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(cmd.handle, &dep);
img.state = rhi::ResourceState::ColorDraw;
return true;
}
void present_swapchain(Device &device, Swapchain &swapchain, u32 image_index, SyncPoint frame_done) {
// timeline to binary for present
VkSemaphoreSubmitInfo convert_wait{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.semaphore = frame_done.sync->timeline,
.value = frame_done.value,
.stageMask = vk::pipeline_stage_flags_to_vk(rhi::PipelineStages::ALL_COMMANDS),
.deviceIndex = 0,
};
VkSemaphoreSubmitInfo convert_signal{
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO,
.semaphore = swapchain.present[image_index],
.value = 0, // binary, unused
.stageMask = vk::pipeline_stage_flags_to_vk(rhi::PipelineStages::ALL_COMMANDS),
.deviceIndex = 0,
};
VkSubmitInfo2 convert_info{
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO_2,
.waitSemaphoreInfoCount = 1,
.pWaitSemaphoreInfos = &convert_wait,
.commandBufferInfoCount = 0,
.pCommandBufferInfos = nullptr,
.signalSemaphoreInfoCount = 1,
.pSignalSemaphoreInfos = &convert_signal,
};
VK_CHECK(vkQueueSubmit2(device.graphics.handle, 1, &convert_info, VK_NULL_HANDLE));
VkSemaphore vk_sem = swapchain.present[image_index];
VkSwapchainKHR vk_swap = swapchain.handle;
VkPresentInfoKHR present_info{
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &vk_sem,
.swapchainCount = 1,
.pSwapchains = &vk_swap,
.pImageIndices = &image_index
};
VkResult res = vkQueuePresentKHR(device.graphics.handle, &present_info);
if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) {
swapchain.recreate = true;
}
}
rhi::ImageFormat swapchain_format(const Swapchain &swapchain) {
return swapchain.format;
}
rhi::Extent2D swapchain_extent(const Swapchain &swapchain) {
return swapchain.extent;
}
Image *swapchain_image(Swapchain &swapchain, u32 index) {
if (index >= swapchain.backbuffers.size()) {
return nullptr;
}
return &swapchain.backbuffers[index];
}
bool swapchain_needs_recreate(const Swapchain &swapchain) {
return swapchain.recreate;
}
} // namespace rhi