mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2025-01-20 15:25:38 +00:00
741 lines
25 KiB
C++
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;
|
|
}
|