2023-08-13 03:42:02 +00:00
|
|
|
// SPDX-FileCopyrightText: 2019-2023 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
|
|
|
|
|
2023-08-24 08:02:55 +00:00
|
|
|
#if defined(VK_USE_PLATFORM_METAL_EXT)
|
|
|
|
#include "util/cocoa_tools.h"
|
|
|
|
#endif
|
|
|
|
|
2023-08-13 03:42:02 +00:00
|
|
|
Log_SetChannel(VulkanDevice);
|
|
|
|
|
|
|
|
VulkanSwapChain::VulkanSwapChain(const WindowInfo& wi, VkSurfaceKHR surface, bool vsync,
|
|
|
|
std::optional<bool> exclusive_fullscreen_control)
|
|
|
|
: m_window_info(wi), m_surface(surface), m_vsync_mode(vsync),
|
|
|
|
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, bool vsync,
|
|
|
|
std::optional<bool> exclusive_fullscreen_control)
|
|
|
|
{
|
|
|
|
std::unique_ptr<VulkanSwapChain> swap_chain =
|
|
|
|
std::unique_ptr<VulkanSwapChain>(new VulkanSwapChain(wi, surface, vsync, exclusive_fullscreen_control));
|
|
|
|
if (!swap_chain->CreateSwapChain())
|
|
|
|
return nullptr;
|
|
|
|
|
|
|
|
return swap_chain;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2023-09-05 10:00:18 +00:00
|
|
|
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))
|
2023-08-13 03:42:02 +00:00
|
|
|
return VkSurfaceFormatKHR{VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
|
|
|
|
|
2023-09-05 10:00:18 +00:00
|
|
|
// 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})
|
2023-08-13 03:42:02 +00:00
|
|
|
{
|
2023-09-05 10:00:18 +00:00
|
|
|
if (has_format(format))
|
|
|
|
return VkSurfaceFormatKHR{format, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
|
2023-08-13 03:42:02 +00:00
|
|
|
}
|
|
|
|
|
2023-09-05 10:00:18 +00:00
|
|
|
Log_ErrorPrintf("Failed to find a suitable format for swap chain buffers. Available formats were:");
|
|
|
|
for (const VkSurfaceFormatKHR& sf : surface_formats)
|
|
|
|
Log_ErrorPrintf(" %u", static_cast<unsigned>(sf.format));
|
|
|
|
|
2023-08-13 03:42:02 +00:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static VkPresentModeKHR GetPreferredPresentModeForVsyncMode(bool mode)
|
|
|
|
{
|
|
|
|
if (mode /*== VsyncMode::On*/)
|
|
|
|
return VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
/*else if (mode == VsyncMode::Adaptive)
|
|
|
|
return VK_PRESENT_MODE_FIFO_RELAXED_KHR;*/
|
|
|
|
else
|
|
|
|
return VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<VkPresentModeKHR> VulkanSwapChain::SelectPresentMode(VkSurfaceKHR surface, bool vsync)
|
|
|
|
{
|
|
|
|
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 std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
|
|
|
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();
|
|
|
|
};
|
|
|
|
|
|
|
|
// Use preferred mode if available.
|
|
|
|
const VkPresentModeKHR preferred_mode = GetPreferredPresentModeForVsyncMode(vsync);
|
|
|
|
VkPresentModeKHR selected_mode;
|
|
|
|
if (CheckForMode(preferred_mode))
|
|
|
|
{
|
|
|
|
selected_mode = preferred_mode;
|
|
|
|
}
|
|
|
|
else if (!vsync /*vsync != VsyncMode::On*/ && CheckForMode(VK_PRESENT_MODE_MAILBOX_KHR))
|
|
|
|
{
|
|
|
|
// Prefer mailbox over fifo for adaptive vsync/no-vsync.
|
|
|
|
selected_mode = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
|
|
}
|
|
|
|
else if (vsync /*vsync != VsyncMode::Off*/ && CheckForMode(VK_PRESENT_MODE_FIFO_KHR))
|
|
|
|
{
|
|
|
|
// Fallback to FIFO if we're using any kind of vsync.
|
|
|
|
// This should never fail, FIFO is mandated.
|
|
|
|
selected_mode = VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Fall back to whatever is available.
|
|
|
|
selected_mode = present_modes[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
Log_DevPrintf("(SwapChain) Preferred present mode: %s, selected: %s", PresentModeToString(preferred_mode),
|
|
|
|
PresentModeToString(selected_mode));
|
|
|
|
|
|
|
|
return selected_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VulkanSwapChain::CreateSwapChain()
|
|
|
|
{
|
|
|
|
VulkanDevice& dev = VulkanDevice::GetInstance();
|
|
|
|
|
|
|
|
// Select swap chain format and present mode
|
|
|
|
std::optional<VkSurfaceFormatKHR> surface_format = SelectSurfaceFormat(m_surface);
|
|
|
|
std::optional<VkPresentModeKHR> present_mode = SelectPresentMode(m_surface, m_vsync_mode);
|
|
|
|
if (!surface_format.has_value() || !present_mode.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
|
|
|
|
u32 image_count = std::max(surface_capabilities.minImageCount + 1u, 2u);
|
|
|
|
|
|
|
|
// maxImageCount can be zero, in which case there isn't an upper limit on the number of buffers.
|
|
|
|
if (surface_capabilities.maxImageCount > 0)
|
|
|
|
image_count = std::min(image_count, surface_capabilities.maxImageCount);
|
|
|
|
|
|
|
|
// Determine the dimensions of the swap chain. Values of -1 indicate the size we specify here
|
|
|
|
// determines window size?
|
|
|
|
VkExtent2D size = surface_capabilities.currentExtent;
|
|
|
|
if (size.width == UINT32_MAX)
|
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("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,
|
|
|
|
present_mode.value(),
|
|
|
|
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
|
2023-09-03 04:30:26 +00:00
|
|
|
VkSurfaceFullScreenExclusiveInfoEXT exclusive_info = {VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_INFO_EXT,
|
|
|
|
nullptr, VK_FULL_SCREEN_EXCLUSIVE_DEFAULT_EXT};
|
2023-08-13 03:42:02 +00:00
|
|
|
VkSurfaceFullScreenExclusiveWin32InfoEXT exclusive_win32_info = {
|
2023-09-03 04:30:26 +00:00
|
|
|
VK_STRUCTURE_TYPE_SURFACE_FULL_SCREEN_EXCLUSIVE_WIN32_INFO_EXT, nullptr, NULL};
|
2023-08-13 03:42:02 +00:00
|
|
|
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)
|
|
|
|
Log_ErrorPrintf("MonitorFromWindow() for exclusive fullscreen exclusive override failed.");
|
|
|
|
|
|
|
|
Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_info);
|
|
|
|
Vulkan::AddPointerToChain(&swap_chain_info, &exclusive_win32_info);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Exclusive fullscreen control requested, but VK_EXT_full_screen_exclusive is not supported.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if (m_exclusive_fullscreen_control.has_value())
|
|
|
|
Log_ErrorPrintf("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)
|
|
|
|
{
|
|
|
|
Log_ErrorPrintf("Unknown Vulkan surface format %u", 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.GetRenderPass(m_format, VK_FORMAT_UNDEFINED, VK_SAMPLE_COUNT_1_BIT, 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_semaphores.reserve(image_count);
|
|
|
|
m_current_semaphore = (image_count - 1);
|
|
|
|
for (u32 i = 0; i < image_count; i++)
|
|
|
|
{
|
|
|
|
ImageSemaphores sema;
|
|
|
|
|
|
|
|
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);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_semaphores.push_back(sema);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
vkDestroySemaphore(dev.GetVulkanDevice(), it.rendering_finished_semaphore, nullptr);
|
|
|
|
vkDestroySemaphore(dev.GetVulkanDevice(), it.available_semaphore, nullptr);
|
|
|
|
}
|
|
|
|
m_semaphores.clear();
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
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()
|
|
|
|
{
|
|
|
|
m_image_acquire_result.reset();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool VulkanSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale)
|
|
|
|
{
|
|
|
|
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::SetVSync(bool mode)
|
|
|
|
{
|
|
|
|
if (m_vsync_mode == mode)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
m_vsync_mode = mode;
|
|
|
|
|
|
|
|
// Recreate the swap chain with the new present mode.
|
|
|
|
Log_VerbosePrintf("Recreating swap chain to change present mode.");
|
|
|
|
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;
|
|
|
|
}
|