Duckstation/src/util/vulkan_swap_chain.cpp

741 lines
25 KiB
C++

// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
#include "vulkan_swap_chain.h"
#include "vulkan_builders.h"
#include "vulkan_device.h"
#include "common/assert.h"
#include "common/log.h"
#include <algorithm>
#include <array>
#include <cmath>
#if defined(VK_USE_PLATFORM_XLIB_KHR)
#include <X11/Xlib.h>
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
#include "util/metal_layer.h"
#endif
Log_SetChannel(VulkanDevice);
static_assert(VulkanSwapChain::NUM_SEMAPHORES == (VulkanDevice::NUM_COMMAND_BUFFERS + 1));
static VkFormat GetLinearFormat(VkFormat format)
{
switch (format)
{
case VK_FORMAT_R8_SRGB:
return VK_FORMAT_R8_UNORM;
case VK_FORMAT_R8G8_SRGB:
return VK_FORMAT_R8G8_UNORM;
case VK_FORMAT_R8G8B8_SRGB:
return VK_FORMAT_R8G8B8_UNORM;
case VK_FORMAT_R8G8B8A8_SRGB:
return VK_FORMAT_R8G8B8A8_UNORM;
case VK_FORMAT_B8G8R8_SRGB:
return VK_FORMAT_B8G8R8_UNORM;
case VK_FORMAT_B8G8R8A8_SRGB:
return VK_FORMAT_B8G8R8A8_UNORM;
default:
return format;
}
}
static const char* PresentModeToString(VkPresentModeKHR mode)
{
switch (mode)
{
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return "VK_PRESENT_MODE_IMMEDIATE_KHR";
case VK_PRESENT_MODE_MAILBOX_KHR:
return "VK_PRESENT_MODE_MAILBOX_KHR";
case VK_PRESENT_MODE_FIFO_KHR:
return "VK_PRESENT_MODE_FIFO_KHR";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return "VK_PRESENT_MODE_FIFO_RELAXED_KHR";
case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR";
case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
return "VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR";
default:
return "UNKNOWN_VK_PRESENT_MODE";
}
}
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control)
: m_window_info(wi), m_surface(surface), m_present_mode(present_mode),
m_exclusive_fullscreen_control(exclusive_fullscreen_control)
{
}
VulkanSwapChain::~VulkanSwapChain()
{
DestroySwapChainImages();
DestroySwapChain();
DestroySurface();
}
VkSurfaceKHR VulkanSwapChain::CreateVulkanSurface(VkInstance instance, VkPhysicalDevice physical_device, WindowInfo* wi)
{
#if defined(VK_USE_PLATFORM_WIN32_KHR)
if (wi->type == WindowInfo::Type::Win32)
{
VkWin32SurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkWin32SurfaceCreateFlagsKHR flags
nullptr, // HINSTANCE hinstance
reinterpret_cast<HWND>(wi->window_handle) // HWND hwnd
};
VkSurfaceKHR surface;
VkResult res = vkCreateWin32SurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateWin32SurfaceKHR failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_METAL_EXT)
if (wi->type == WindowInfo::Type::MacOS)
{
// TODO: FIXME
if (!wi->surface_handle && !CocoaTools::CreateMetalLayer(wi))
return VK_NULL_HANDLE;
VkMetalSurfaceCreateInfoEXT surface_create_info = {VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT, nullptr, 0,
static_cast<const CAMetalLayer*>(wi->surface_handle)};
VkSurfaceKHR surface;
VkResult res = vkCreateMetalSurfaceEXT(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateMetalSurfaceEXT failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_ANDROID_KHR)
if (wi->type == WindowInfo::Type::Android)
{
VkAndroidSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkAndroidSurfaceCreateFlagsKHR flags
reinterpret_cast<ANativeWindow*>(wi->window_handle) // ANativeWindow* window
};
VkSurfaceKHR surface;
VkResult res = vkCreateAndroidSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateAndroidSurfaceKHR failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_XLIB_KHR)
if (wi->type == WindowInfo::Type::X11)
{
VkXlibSurfaceCreateInfoKHR surface_create_info = {
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, // VkStructureType sType
nullptr, // const void* pNext
0, // VkXlibSurfaceCreateFlagsKHR flags
static_cast<Display*>(wi->display_connection), // Display* dpy
reinterpret_cast<Window>(wi->window_handle) // Window window
};
VkSurfaceKHR surface;
VkResult res = vkCreateXlibSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateXlibSurfaceKHR failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
if (wi->type == WindowInfo::Type::Wayland)
{
VkWaylandSurfaceCreateInfoKHR surface_create_info = {VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, nullptr, 0,
static_cast<struct wl_display*>(wi->display_connection),
static_cast<struct wl_surface*>(wi->window_handle)};
VkSurfaceKHR surface;
VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceEXT failed: ");
return VK_NULL_HANDLE;
}
return surface;
}
#endif
return VK_NULL_HANDLE;
}
void VulkanSwapChain::DestroyVulkanSurface(VkInstance instance, WindowInfo* wi, VkSurfaceKHR surface)
{
vkDestroySurfaceKHR(VulkanDevice::GetInstance().GetVulkanInstance(), surface, nullptr);
#if defined(__APPLE__)
if (wi->type == WindowInfo::Type::MacOS && wi->surface_handle)
CocoaTools::DestroyMetalLayer(wi);
#endif
}
std::unique_ptr<VulkanSwapChain> VulkanSwapChain::Create(const WindowInfo& wi, VkSurfaceKHR surface,
VkPresentModeKHR present_mode,
std::optional<bool> exclusive_fullscreen_control)
{
std::unique_ptr<VulkanSwapChain> swap_chain =
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, present_mode, exclusive_fullscreen_control));
if (!swap_chain->CreateSwapChain())
return nullptr;
return swap_chain;
}
std::optional<VkSurfaceFormatKHR> VulkanSwapChain::SelectSurfaceFormat(VkSurfaceKHR surface)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
u32 format_count;
VkResult res = vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, nullptr);
if (res != VK_SUCCESS || format_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
return std::nullopt;
}
std::vector<VkSurfaceFormatKHR> surface_formats(format_count);
res =
vkGetPhysicalDeviceSurfaceFormatsKHR(dev.GetVulkanPhysicalDevice(), surface, &format_count, surface_formats.data());
Assert(res == VK_SUCCESS);
// If there is a single undefined surface format, the device doesn't care, so we'll just use RGBA
const auto has_format = [&surface_formats](VkFormat fmt) {
return std::any_of(surface_formats.begin(), surface_formats.end(), [fmt](const VkSurfaceFormatKHR& sf) {
return (sf.format == fmt || GetLinearFormat(sf.format) == fmt);
});
};
if (has_format(VK_FORMAT_UNDEFINED))
return VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
// Prefer 8-bit formats.
for (VkFormat format : {VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R5G6B5_UNORM_PACK16,
VK_FORMAT_R5G5B5A1_UNORM_PACK16})
{
if (has_format(format))
return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
ERROR_LOG("Failed to find a suitable format for swap chain buffers. Available formats were:");
for (const VkSurfaceFormatKHR& sf : surface_formats)
ERROR_LOG(" {}", static_cast<unsigned>(sf.format));
return std::nullopt;
}
bool VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, GPUVSyncMode* vsync_mode, VkPresentModeKHR* present_mode)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
VkResult res;
u32 mode_count;
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count, nullptr);
if (res != VK_SUCCESS || mode_count == 0)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceFormatsKHR failed: ");
return false;
}
std::vector<VkPresentModeKHR> present_modes(mode_count);
res = vkGetPhysicalDeviceSurfacePresentModesKHR(dev.GetVulkanPhysicalDevice(), surface, &mode_count,
present_modes.data());
Assert(res == VK_SUCCESS);
// Checks if a particular mode is supported, if it is, returns that mode.
const auto CheckForMode = [&present_modes](VkPresentModeKHR check_mode) {
auto it = std::find_if(present_modes.begin(), present_modes.end(),
[check_mode](VkPresentModeKHR mode) { return check_mode == mode; });
return it != present_modes.end();
};
switch (*vsync_mode)
{
case GPUVSyncMode::Disabled:
{
// Prefer immediate > mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_IMMEDIATE_KHR))
{
*present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
else if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
WARNING_LOG("Immediate not supported for vsync-disabled, using mailbox.");
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
WARNING_LOG("Mailbox not supported for vsync-disabled, using FIFO.");
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
*vsync_mode = GPUVSyncMode::FIFO;
}
}
break;
case GPUVSyncMode::FIFO:
{
// FIFO is always available.
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
}
break;
case GPUVSyncMode::Mailbox:
{
// Mailbox > fifo.
if (CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
{
*present_mode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
WARNING_LOG("Mailbox not supported for vsync-mailbox, using FIFO.");
*present_mode = VK_PRESENT_MODE_FIFO_KHR;
*vsync_mode = GPUVSyncMode::FIFO;
}
}
break;
DefaultCaseIsUnreachable()
}
return true;
}
bool VulkanSwapChain::CreateSwapChain()
{
VulkanDevice& dev = VulkanDevice::GetInstance();
// Select swap chain format
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
if (!surface_format.has_value())
return false;
// Look up surface properties to determine image count and dimensions
VkSurfaceCapabilitiesKHR surface_capabilities;
VkResult res =
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev.GetVulkanPhysicalDevice(), m_surface, &surface_capabilities);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceCapabilitiesKHR failed: ");
return false;
}
// Select number of images in swap chain, we prefer one buffer in the background to work on in triple-buffered mode.
// maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
u32 image_count = std::clamp<u32>(
(m_present_mode == VK_PRESENT_MODE_MAILBOX_KHR) ? 3 : 2, surface_capabilities.minImageCount,
(surface_capabilities.maxImageCount == 0) ? std::numeric_limits<u32>::max() : surface_capabilities.maxImageCount);
DEV_LOG("Creating a swap chain with {} images in present mode {}", image_count, PresentModeToString(m_present_mode));
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
// determines window size? Android sometimes lags updating currentExtent, so don't use it.
VkExtent2D size = surface_capabilities.currentExtent;
#ifndef __ANDROID__
if (size.width == UINT32_MAX)
#endif
{
size.width = m_window_info.surface_width;
size.height = m_window_info.surface_height;
}
size.width =
std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
size.height =
std::clamp(size.height, surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
// Prefer identity transform if possible
VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
if (!(surface_capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
transform = surface_capabilities.currentTransform;
VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
if (!(surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR))
{
// If we only support pre-multiplied/post-multiplied... :/
if (surface_capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR)
alpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
}
// Select swap chain flags, we only need a colour attachment
VkImageUsageFlags image_usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
if ((surface_capabilities.supportedUsageFlags & image_usage) != image_usage)
{
ERROR_LOG("Vulkan: Swap chain does not support usage as color attachment");
return false;
}
// Store the old/current swap chain when recreating for resize
// Old swap chain is destroyed regardless of whether the create call succeeds
VkSwapchainKHR old_swap_chain = m_swap_chain;
m_swap_chain = VK_NULL_HANDLE;
// Now we can actually create the swap chain
VkSwapchainCreateInfoKHR swap_chain_info = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
nullptr,
0,
m_surface,
image_count,
surface_format->format,
surface_format->colorSpace,
size,
1u,
image_usage,
VK_SHARING_MODE_EXCLUSIVE,
0,
nullptr,
transform,
alpha,
m_present_mode,
VK_TRUE,
old_swap_chain};
std::array<uint32_t, 2> indices = {{
dev.GetGraphicsQueueFamilyIndex(),
dev.GetPresentQueueFamilyIndex(),
}};
if (dev.GetGraphicsQueueFamilyIndex() != dev.GetPresentQueueFamilyIndex())
{
swap_chain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
swap_chain_info.queueFamilyIndexCount = 2;
swap_chain_info.pQueueFamilyIndices = indices.data();
}
#ifdef _WIN32
VkSurfaceFullScreenExclusiveInfoEXT exclusive_info = {VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT,
nullptr, VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT};
VkSurfaceFullScreenExclusiveWin32InfoEXT exclusive_win32_info = {
VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT, nullptr, NULL};
if (m_exclusive_fullscreen_control.has_value())
{
if (dev.GetOptionalExtensions().vk_ext_full_screen_exclusive)
{
exclusive_info.fullScreenExclusive =
(m_exclusive_fullscreen_control.value() ? VK_FULL_SCREEN_EXCLUSIVE_ALLOWED_EXT :
VK_FULL_SCREEN_EXCLUSIVE_DISALLOWED_EXT);
exclusive_win32_info.hmonitor =
MonitorFromWindow(reinterpret_cast<HWND>(m_window_info.window_handle), MONITOR_DEFAULTTONEAREST);
if (!exclusive_win32_info.hmonitor)
ERROR_LOG("MonitorFromWindow() for exclusive fullscreen exclusive override failed.");
Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_info);
Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_win32_info);
}
else
{
ERROR_LOG("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported.");
}
}
#else
if (m_exclusive_fullscreen_control.has_value())
ERROR_LOG("Exclusive fullscreen control requested, but is not supported on this platform.");
#endif
res = vkCreateSwapchainKHR(dev.GetVulkanDevice(), &swap_chain_info, nullptr, &m_swap_chain);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSwapchainKHR failed: ");
return false;
}
// Now destroy the old swap chain, since it's been recreated.
// We can do this immediately since all work should have been completed before calling resize.
if (old_swap_chain != VK_NULL_HANDLE)
vkDestroySwapchainKHR(dev.GetVulkanDevice(), old_swap_chain, nullptr);
m_format = surface_format->format;
m_window_info.surface_width = std::max(1u, size.width);
m_window_info.surface_height = std::max(1u, size.height);
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
{
ERROR_LOG("Unknown Vulkan surface format {}", static_cast<u32>(surface_format->format));
return false;
}
// Get and create images.
Assert(m_images.empty());
res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, nullptr);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetSwapchainImagesKHR failed: ");
return false;
}
std::vector<VkImage> images(image_count);
res = vkGetSwapchainImagesKHR(dev.GetVulkanDevice(), m_swap_chain, &image_count, images.data());
Assert(res == VK_SUCCESS);
VkRenderPass render_pass = dev.GetSwapChainRenderPass(m_window_info.surface_format, VK_ATTACHMENT_LOAD_OP_CLEAR);
if (render_pass == VK_NULL_HANDLE)
return false;
Vulkan::FramebufferBuilder fbb;
m_images.reserve(image_count);
m_current_image = 0;
for (u32 i = 0; i < image_count; i++)
{
Image image = {};
image.image = images[i];
const VkImageViewCreateInfo view_info = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
nullptr,
0,
images[i],
VK_IMAGE_VIEW_TYPE_2D,
m_format,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY},
{VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u},
};
if ((res = vkCreateImageView(dev.GetVulkanDevice(), &view_info, nullptr, &image.view)) != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateImageView() failed: ");
return false;
}
fbb.AddAttachment(image.view);
fbb.SetRenderPass(render_pass);
fbb.SetSize(size.width, size.height, 1);
if ((image.framebuffer = fbb.Create(dev.GetVulkanDevice())) == VK_NULL_HANDLE)
{
vkDestroyImageView(dev.GetVulkanDevice(), image.view, nullptr);
return false;
}
m_images.push_back(image);
}
m_current_semaphore = 0;
for (u32 i = 0; i < NUM_SEMAPHORES; i++)
{
ImageSemaphores& sema = m_semaphores[i];
const VkSemaphoreCreateInfo semaphore_info = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.available_semaphore);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
return false;
}
res = vkCreateSemaphore(dev.GetVulkanDevice(), &semaphore_info, nullptr, &sema.rendering_finished_semaphore);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
vkDestroySemaphore(dev.GetVulkanDevice(), sema.available_semaphore, nullptr);
sema.available_semaphore = VK_NULL_HANDLE;
return false;
}
}
return true;
}
void VulkanSwapChain::DestroySwapChainImages()
{
VulkanDevice& dev = VulkanDevice::GetInstance();
for (const auto& it : m_images)
{
// don't defer view destruction, images are no longer valid
vkDestroyFramebuffer(dev.GetVulkanDevice(), it.framebuffer, nullptr);
vkDestroyImageView(dev.GetVulkanDevice(), it.view, nullptr);
}
m_images.clear();
for (auto& it : m_semaphores)
{
if (it.rendering_finished_semaphore != VK_NULL_HANDLE)
vkDestroySemaphore(dev.GetVulkanDevice(), it.rendering_finished_semaphore, nullptr);
if (it.available_semaphore != VK_NULL_HANDLE)
vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr);
}
m_semaphores = {};
m_image_acquire_result.reset();
}
void VulkanSwapChain::DestroySwapChain()
{
DestroySwapChainImages();
if (m_swap_chain == VK_NULL_HANDLE)
return;
vkDestroySwapchainKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, nullptr);
m_swap_chain = VK_NULL_HANDLE;
m_window_info.surface_width = 0;
m_window_info.surface_height = 0;
}
VkResult VulkanSwapChain::AcquireNextImage()
{
if (m_image_acquire_result.has_value())
return m_image_acquire_result.value();
if (!m_swap_chain)
return VK_ERROR_SURFACE_LOST_KHR;
// Use a different semaphore for each image.
m_current_semaphore = (m_current_semaphore + 1) % static_cast<u32>(m_semaphores.size());
const VkResult res =
vkAcquireNextImageKHR(VulkanDevice::GetInstance().GetVulkanDevice(), m_swap_chain, UINT64_MAX,
m_semaphores[m_current_semaphore].available_semaphore, VK_NULL_HANDLE, &m_current_image);
m_image_acquire_result = res;
return res;
}
void VulkanSwapChain::ReleaseCurrentImage()
{
if (!m_image_acquire_result.has_value())
return;
if ((m_image_acquire_result.value() == VK_SUCCESS || m_image_acquire_result.value() == VK_SUBOPTIMAL_KHR) &&
VulkanDevice::GetInstance().GetOptionalExtensions().vk_ext_swapchain_maintenance1)
{
VulkanDevice::GetInstance().WaitForGPUIdle();
const VkReleaseSwapchainImagesInfoEXT info = {.sType = VK_STRUCTURE_TYPE_RELEASE_SWAPCHAIN_IMAGES_INFO_EXT,
.swapchain = m_swap_chain,
.imageIndexCount = 1,
.pImageIndices = &m_current_image};
VkResult res = vkReleaseSwapchainImagesEXT(VulkanDevice::GetInstance().GetVulkanDevice(), &info);
if (res != VK_SUCCESS)
LOG_VULKAN_ERROR(res, "vkReleaseSwapchainImagesEXT() failed: ");
}
m_image_acquire_result.reset();
}
void VulkanSwapChain::ResetImageAcquireResult()
{
m_image_acquire_result.reset();
}
bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale)
{
ReleaseCurrentImage();
DestroySwapChainImages();
if (new_width != 0 && new_height != 0)
{
m_window_info.surface_width = new_width;
m_window_info.surface_height = new_height;
}
m_window_info.surface_scale = new_scale;
if (!CreateSwapChain())
{
DestroySwapChain();
return false;
}
return true;
}
bool VulkanSwapChain::SetPresentMode(VkPresentModeKHR present_mode)
{
if (m_present_mode == present_mode)
return true;
m_present_mode = present_mode;
// Recreate the swap chain with the new present mode.
VERBOSE_LOG("Recreating swap chain to change present mode.");
ReleaseCurrentImage();
DestroySwapChainImages();
if (!CreateSwapChain())
{
DestroySwapChain();
return false;
}
return true;
}
bool VulkanSwapChain::RecreateSurface(const WindowInfo& new_wi)
{
VulkanDevice& dev = VulkanDevice::GetInstance();
// Destroy the old swap chain, images, and surface.
DestroySwapChain();
DestroySurface();
// Re-create the surface with the new native handle
m_window_info = new_wi;
m_surface = CreateVulkanSurface(dev.GetVulkanInstance(), dev.GetVulkanPhysicalDevice(), &m_window_info);
if (m_surface == VK_NULL_HANDLE)
return false;
// The validation layers get angry at us if we don't call this before creating the swapchain.
VkBool32 present_supported = VK_TRUE;
VkResult res = vkGetPhysicalDeviceSurfaceSupportKHR(dev.GetVulkanPhysicalDevice(), dev.GetPresentQueueFamilyIndex(),
m_surface, &present_supported);
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkGetPhysicalDeviceSurfaceSupportKHR failed: ");
return false;
}
if (!present_supported)
{
Panic("Recreated surface does not support presenting.");
return false;
}
// Finally re-create the swap chain
if (!CreateSwapChain())
{
DestroySwapChain();
return false;
}
return true;
}
void VulkanSwapChain::DestroySurface()
{
if (m_surface == VK_NULL_HANDLE)
return;
DestroyVulkanSurface(VulkanDevice::GetInstance().GetVulkanInstance(), &m_window_info, m_surface);
m_surface = VK_NULL_HANDLE;
}