From 4924d8c984a11e2d4e598e414b72ad84a9f46197 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 23 Jun 2024 06:09:51 +1000 Subject: [PATCH] GS/Vulkan: Release swap chain images on acquire fail --- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp | 53 ++++++++++++++++----- pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h | 7 ++- pcsx2/GS/Renderers/Vulkan/VKEntryPoints.inl | 3 ++ pcsx2/GS/Renderers/Vulkan/VKSwapChain.cpp | 24 ++++++++++ pcsx2/GS/Renderers/Vulkan/VKSwapChain.h | 1 + 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 051a173af5..ce29dda436 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -96,10 +96,11 @@ GSDeviceVK::GSDeviceVK() GSDeviceVK::~GSDeviceVK() = default; -VkInstance GSDeviceVK::CreateVulkanInstance(const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer) +VkInstance GSDeviceVK::CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils, + bool enable_validation_layer) { ExtensionList enabled_extensions; - if (!SelectInstanceExtensions(&enabled_extensions, wi, enable_debug_utils)) + if (!SelectInstanceExtensions(&enabled_extensions, wi, oe, enable_debug_utils)) return VK_NULL_HANDLE; // Remember to manually update this every release. We don't pull in svnrev.h here, because @@ -143,7 +144,8 @@ VkInstance GSDeviceVK::CreateVulkanInstance(const WindowInfo& wi, bool enable_de return instance; } -bool GSDeviceVK::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, bool enable_debug_utils) +bool GSDeviceVK::SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, OptionalExtensions* oe, + bool enable_debug_utils) { u32 extension_count = 0; VkResult res = vkEnumerateInstanceExtensionProperties(nullptr, &extension_count, nullptr); @@ -204,6 +206,9 @@ bool GSDeviceVK::SelectInstanceExtensions(ExtensionList* extension_list, const W if (enable_debug_utils && !SupportsExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false)) Console.Warning("Vulkan: Debug report requested, but extension is not available."); + oe->vk_ext_swapchain_maintenance1 = (wi.type != WindowInfo::Type::Surfaceless && + SupportsExtension(VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME, false)); + // Needed for exclusive fullscreen control. SupportsExtension(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME, false); @@ -276,7 +281,7 @@ GSDeviceVK::GPUList GSDeviceVK::EnumerateGPUs(VkInstance instance) { if (std::find_if(available_extension_list.begin(), available_extension_list.end(), [required_extension_name](const VkExtensionProperties& ext) { return (std::strcmp(required_extension_name, ext.extensionName) == 0); - }) == available_extension_list.end()) + }) == available_extension_list.end()) { Console.Warning(fmt::format("Ignoring Vulkan GPU '{}' because is is missing required extension {}", props.deviceName, required_extension_name)); @@ -388,6 +393,10 @@ bool GSDeviceVK::SelectDeviceExtensions(ExtensionList* extension_list, bool enab SupportsExtension(VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, false); } + m_optional_extensions.vk_ext_swapchain_maintenance1 = + m_optional_extensions.vk_ext_swapchain_maintenance1 && + SupportsExtension(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME, false); + #ifdef _WIN32 m_optional_extensions.vk_ext_full_screen_exclusive = enable_surface && SupportsExtension(VK_EXT_FULL_SCREEN_EXCLUSIVE_EXTENSION_NAME, false); @@ -573,6 +582,8 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT}; VkPhysicalDeviceLineRasterizationFeaturesEXT line_rasterization_feature = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT}; + VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_feature = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT}; if (m_optional_extensions.vk_ext_provoking_vertex) { @@ -589,6 +600,11 @@ bool GSDeviceVK::CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer rasterization_order_access_feature.rasterizationOrderColorAttachmentAccess = VK_TRUE; Vulkan::AddPointerToChain(&device_info, &rasterization_order_access_feature); } + if (m_optional_extensions.vk_ext_swapchain_maintenance1) + { + swapchain_maintenance1_feature.swapchainMaintenance1 = VK_TRUE; + Vulkan::AddPointerToChain(&device_info, &swapchain_maintenance1_feature); + } VkResult res = vkCreateDevice(m_physical_device, &device_info, nullptr, &m_device); if (res != VK_SUCCESS) @@ -654,6 +670,8 @@ bool GSDeviceVK::ProcessDeviceExtensions() VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_LINE_RASTERIZATION_FEATURES_EXT}; VkPhysicalDeviceRasterizationOrderAttachmentAccessFeaturesEXT rasterization_order_access_feature = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RASTERIZATION_ORDER_ATTACHMENT_ACCESS_FEATURES_EXT}; + VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT swapchain_maintenance1_feature = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, nullptr, VK_TRUE}; // add in optional feature structs if (m_optional_extensions.vk_ext_provoking_vertex) @@ -662,6 +680,8 @@ bool GSDeviceVK::ProcessDeviceExtensions() Vulkan::AddPointerToChain(&features2, &line_rasterization_feature); if (m_optional_extensions.vk_ext_rasterization_order_attachment_access) Vulkan::AddPointerToChain(&features2, &rasterization_order_access_feature); + if (m_optional_extensions.vk_ext_swapchain_maintenance1) + Vulkan::AddPointerToChain(&features2, &swapchain_maintenance1_feature); // query vkGetPhysicalDeviceFeatures2(m_physical_device, &features2); @@ -739,6 +759,9 @@ bool GSDeviceVK::ProcessDeviceExtensions() m_optional_extensions.vk_ext_calibrated_timestamps = false; } + m_optional_extensions.vk_ext_swapchain_maintenance1 &= + (swapchain_maintenance1_feature.swapchainMaintenance1 == VK_TRUE); + Console.WriteLn( "VK_EXT_provoking_vertex is %s", m_optional_extensions.vk_ext_provoking_vertex ? "supported" : "NOT supported"); Console.WriteLn( @@ -747,6 +770,8 @@ bool GSDeviceVK::ProcessDeviceExtensions() m_optional_extensions.vk_ext_calibrated_timestamps ? "supported" : "NOT supported"); Console.WriteLn("VK_EXT_rasterization_order_attachment_access is %s", m_optional_extensions.vk_ext_rasterization_order_attachment_access ? "supported" : "NOT supported"); + Console.WriteLn("VK_EXT_swapchain_maintenance1 is %s", + m_optional_extensions.vk_ext_swapchain_maintenance1 ? "supported" : "NOT supported"); Console.WriteLn("VK_EXT_full_screen_exclusive is %s", m_optional_extensions.vk_ext_full_screen_exclusive ? "supported" : "NOT supported"); Console.WriteLn("VK_KHR_driver_properties is %s", @@ -1197,16 +1222,17 @@ void GSDeviceVK::SubmitCommandBuffer(VKSwapChain* present_swap_chain) present_swap_chain->GetRenderingFinishedSemaphorePtr(), 1, present_swap_chain->GetSwapChainPtr(), present_swap_chain->GetCurrentImageIndexPtr(), nullptr}; - present_swap_chain->ReleaseCurrentImage(); + present_swap_chain->ResetImageAcquireResult(); const VkResult res = vkQueuePresentKHR(m_present_queue, &present_info); - if (res != VK_SUCCESS) + if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) { // VK_ERROR_OUT_OF_DATE_KHR is not fatal, just means we need to recreate our swap chain. - if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) + if (res == VK_ERROR_OUT_OF_DATE_KHR) + ResizeWindow(0, 0, m_window_info.surface_scale); + else LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); - m_last_present_failed = true; return; } @@ -1926,7 +1952,8 @@ void GSDeviceVK::GetAdaptersAndFullscreenModes( { if (Vulkan::LoadVulkanLibrary(nullptr)) { - const VkInstance instance = CreateVulkanInstance(WindowInfo(), false, false); + OptionalExtensions oe = {}; + const VkInstance instance = CreateVulkanInstance(WindowInfo(), &oe, false, false); if (instance != VK_NULL_HANDLE) { if (Vulkan::LoadVulkanInstanceFunctions(instance)) @@ -2136,7 +2163,7 @@ bool GSDeviceVK::UpdateWindow() void GSDeviceVK::ResizeWindow(s32 new_window_width, s32 new_window_height, float new_window_scale) { if (!m_swap_chain || (m_swap_chain->GetWidth() == static_cast(new_window_width) && - m_swap_chain->GetHeight() == static_cast(new_window_height))) + m_swap_chain->GetHeight() == static_cast(new_window_height))) { // skip unnecessary resizes m_window_info.surface_scale = new_window_scale; @@ -2413,7 +2440,7 @@ bool GSDeviceVK::CreateDeviceAndSwapChain() if (!AcquireWindow(true)) return false; - m_instance = CreateVulkanInstance(m_window_info, enable_debug_utils, enable_validation_layer); + m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer); if (m_instance == VK_NULL_HANDLE) { if (enable_debug_utils || enable_validation_layer) @@ -2421,7 +2448,7 @@ bool GSDeviceVK::CreateDeviceAndSwapChain() // Try again without the validation layer. enable_debug_utils = false; enable_validation_layer = false; - m_instance = CreateVulkanInstance(m_window_info, enable_debug_utils, enable_validation_layer); + m_instance = CreateVulkanInstance(m_window_info, &m_optional_extensions, enable_debug_utils, enable_validation_layer); if (m_instance == VK_NULL_HANDLE) { Host::ReportErrorAsync( @@ -5714,7 +5741,7 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config) BeginRenderPass(rp, render_area); } } - + if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::StencilOne) { const VkClearAttachment ca = {VK_IMAGE_ASPECT_STENCIL_BIT, 0u, {.depthStencil = {0.0f, 1u}}}; diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 9853b4a9fc..98f4b74e15 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -41,6 +41,7 @@ public: bool vk_ext_rasterization_order_attachment_access : 1; bool vk_ext_full_screen_exclusive : 1; bool vk_ext_line_rasterization : 1; + bool vk_ext_swapchain_maintenance1 : 1; bool vk_khr_driver_properties : 1; bool vk_khr_shader_non_semantic_info : 1; }; @@ -125,7 +126,8 @@ public: private: // Helper method to create a Vulkan instance. - static VkInstance CreateVulkanInstance(const WindowInfo& wi, bool enable_debug_utils, bool enable_validation_layer); + static VkInstance CreateVulkanInstance(const WindowInfo& wi, OptionalExtensions* oe, bool enable_debug_utils, + bool enable_validation_layer); // Returns a list of Vulkan-compatible GPUs. using GPUList = std::vector>; @@ -173,7 +175,8 @@ private: }; using ExtensionList = std::vector; - static bool SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, bool enable_debug_utils); + static bool SelectInstanceExtensions(ExtensionList* extension_list, const WindowInfo& wi, OptionalExtensions* oe, + bool enable_debug_utils); bool SelectDeviceExtensions(ExtensionList* extension_list, bool enable_surface); bool SelectDeviceFeatures(); bool CreateDevice(VkSurfaceKHR surface, bool enable_validation_layer); diff --git a/pcsx2/GS/Renderers/Vulkan/VKEntryPoints.inl b/pcsx2/GS/Renderers/Vulkan/VKEntryPoints.inl index afe5263f7d..15b0b8a657 100644 --- a/pcsx2/GS/Renderers/Vulkan/VKEntryPoints.inl +++ b/pcsx2/GS/Renderers/Vulkan/VKEntryPoints.inl @@ -238,4 +238,7 @@ VULKAN_DEVICE_ENTRY_POINT(vkGetCalibratedTimestampsEXT, false) // VK_KHR_push_descriptor VULKAN_DEVICE_ENTRY_POINT(vkCmdPushDescriptorSetKHR, false) +// VK_EXT_swapchain_maintenance1 +VULKAN_DEVICE_ENTRY_POINT(vkReleaseSwapchainImagesEXT, false) + #endif // VULKAN_DEVICE_ENTRY_POINT diff --git a/pcsx2/GS/Renderers/Vulkan/VKSwapChain.cpp b/pcsx2/GS/Renderers/Vulkan/VKSwapChain.cpp index 1b4a36ea90..23ebd8a096 100644 --- a/pcsx2/GS/Renderers/Vulkan/VKSwapChain.cpp +++ b/pcsx2/GS/Renderers/Vulkan/VKSwapChain.cpp @@ -544,12 +544,35 @@ VkResult VKSwapChain::AcquireNextImage() } void VKSwapChain::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) && + GSDeviceVK::GetInstance()->GetOptionalExtensions().vk_ext_swapchain_maintenance1) + { + GSDeviceVK::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(GSDeviceVK::GetInstance()->GetDevice(), &info); + if (res != VK_SUCCESS) + LOG_VULKAN_ERROR(res, "vkReleaseSwapchainImagesEXT() failed: "); + } + + m_image_acquire_result.reset(); +} + +void VKSwapChain::ResetImageAcquireResult() { m_image_acquire_result.reset(); } bool VKSwapChain::ResizeSwapChain(u32 new_width, u32 new_height, float new_scale) { + ReleaseCurrentImage(); DestroySwapChainImages(); if (new_width != 0 && new_height != 0) @@ -578,6 +601,7 @@ bool VKSwapChain::SetPresentMode(VkPresentModeKHR present_mode) // Recreate the swap chain with the new present mode. INFO_LOG("Recreating swap chain to change present mode."); + ReleaseCurrentImage(); DestroySwapChainImages(); if (!CreateSwapChain()) { diff --git a/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h b/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h index c259f592d3..be1989363d 100644 --- a/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h +++ b/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h @@ -68,6 +68,7 @@ public: VkFormat GetTextureFormat() const; VkResult AcquireNextImage(); void ReleaseCurrentImage(); + void ResetImageAcquireResult(); bool RecreateSurface(const WindowInfo& new_wi); bool ResizeSwapChain(u32 new_width = 0, u32 new_height = 0, float new_scale = 1.0f);