diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp index 350757194e..51706fc33f 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp @@ -353,8 +353,13 @@ void CommandBufferManager::SubmitCommandBuffer(size_t index, VkSemaphore wait_se nullptr}; res = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info); - if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) - LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); + if (res != VK_SUCCESS) + { + // 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) + LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: "); + m_present_failed_flag.Set(); + } } // Command buffer has been queued, so permit the next one. diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h index 67266b69b9..9cefe100d7 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -16,6 +16,7 @@ #include #include "Common/BlockingLoop.h" +#include "Common/Flag.h" #include "Common/Semaphore.h" #include "VideoCommon/VideoCommon.h" @@ -78,6 +79,8 @@ public: void ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completion); + // Was the last present submitted to the queue a failure? If so, we must recreate our swapchain. + bool CheckLastPresentFail() { return m_present_failed_flag.TestAndClear(); } // Schedule a vulkan resource for destruction later on. This will occur when the command buffer // is next re-used, and the GPU has finished working with the specified resource. void DeferBufferDestruction(VkBuffer object); @@ -144,6 +147,7 @@ private: }; std::deque m_pending_submits; std::mutex m_pending_submit_lock; + Common::Flag m_present_failed_flag; bool m_use_threaded_submission = false; }; diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.cpp b/Source/Core/VideoBackends/Vulkan/Renderer.cpp index 2a90da6f9a..3a4907b88c 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.cpp +++ b/Source/Core/VideoBackends/Vulkan/Renderer.cpp @@ -325,10 +325,6 @@ void Renderer::BeginFrame() // Activate a new command list, and restore state ready for the next draw g_command_buffer_mgr->ActivateCommandBuffer(); - // Restore the EFB color texture to color attachment ready for rendering the next frame. - FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout( - g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); - // Ensure that the state tracker rebinds everything, and allocates a new set // of descriptors out of the next pool. StateTracker::GetInstance()->InvalidateDescriptorSets(); @@ -575,6 +571,10 @@ void Renderer::SwapImpl(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height // Prep for the next frame (get command buffer ready) before doing anything else. BeginFrame(); + // Restore the EFB color texture to color attachment ready for rendering the next frame. + FramebufferManager::GetInstance()->GetEFBColorTexture()->TransitionToLayout( + g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + // Determine what (if anything) has changed in the config. CheckForConfigChanges(); @@ -718,8 +718,18 @@ void Renderer::DrawScreen(const TargetRectangle& scaled_efb_rect, u32 xfb_addr, const XFBSourceBase* const* xfb_sources, u32 xfb_count, u32 fb_width, u32 fb_stride, u32 fb_height) { - // Grab the next image from the swap chain in preparation for drawing the window. - VkResult res = m_swap_chain->AcquireNextImage(m_image_available_semaphore); + VkResult res; + if (!g_command_buffer_mgr->CheckLastPresentFail()) + { + // Grab the next image from the swap chain in preparation for drawing the window. + res = m_swap_chain->AcquireNextImage(m_image_available_semaphore); + } + else + { + // If the last present failed, we need to recreate the swap chain. + res = VK_ERROR_OUT_OF_DATE_KHR; + } + if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { // There's an issue here. We can't resize the swap chain while the GPU is still busy with it, @@ -729,7 +739,8 @@ void Renderer::DrawScreen(const TargetRectangle& scaled_efb_rect, u32 xfb_addr, // command buffer, resize the swap chain (which calls WaitForGPUIdle), and then finally call // PrepareToSubmitCommandBuffer to return to the state that the caller expects. g_command_buffer_mgr->SubmitCommandBuffer(false); - ResizeSwapChain(); + m_swap_chain->ResizeSwapChain(); + BeginFrame(); g_command_buffer_mgr->PrepareToSubmitCommandBuffer(); res = m_swap_chain->AcquireNextImage(m_image_available_semaphore); } @@ -1065,14 +1076,17 @@ void Renderer::CheckForSurfaceChange() if (!m_surface_needs_change.IsSet()) return; - u32 old_width = m_swap_chain ? m_swap_chain->GetWidth() : 0; - u32 old_height = m_swap_chain ? m_swap_chain->GetHeight() : 0; + // Wait for the GPU to catch up since we're going to destroy the swap chain. + g_command_buffer_mgr->WaitForGPUIdle(); + + // Clear the present failed flag, since we don't want to resize after recreating. + g_command_buffer_mgr->CheckLastPresentFail(); // Fast path, if the surface handle is the same, the window has just been resized. if (m_swap_chain && m_new_surface_handle == m_swap_chain->GetNativeHandle()) { INFO_LOG(VIDEO, "Detected window resize."); - ResizeSwapChain(); + m_swap_chain->RecreateSwapChain(); // Notify the main thread we are done. m_surface_needs_change.Clear(); @@ -1081,9 +1095,6 @@ void Renderer::CheckForSurfaceChange() } else { - // Wait for the GPU to catch up since we're going to destroy the swap chain. - g_command_buffer_mgr->WaitForGPUIdle(); - // Did we previously have a swap chain? if (m_swap_chain) { @@ -1122,12 +1133,8 @@ void Renderer::CheckForSurfaceChange() m_surface_changed.Set(); } - if (m_swap_chain) - { - // Handle case where the dimensions are now different - if (old_width != m_swap_chain->GetWidth() || old_height != m_swap_chain->GetHeight()) - OnSwapChainResized(); - } + // Handle case where the dimensions are now different. + OnSwapChainResized(); } void Renderer::CheckForConfigChanges() @@ -1191,7 +1198,10 @@ void Renderer::CheckForConfigChanges() // For quad-buffered stereo we need to change the layer count, so recreate the swap chain. if (m_swap_chain && (g_ActiveConfig.iStereoMode == STEREO_QUADBUFFER) != m_swap_chain->IsStereoEnabled()) - ResizeSwapChain(); + { + g_command_buffer_mgr->WaitForGPUIdle(); + m_swap_chain->RecreateSwapChain(); + } // Wipe sampler cache if force texture filtering or anisotropy changes. if (anisotropy_changed || force_texture_filtering_changed) @@ -1237,18 +1247,6 @@ void Renderer::ResizeEFBTextures() BPFunctions::SetScissor(); } -void Renderer::ResizeSwapChain() -{ - // The worker thread may still be submitting a present on this swap chain. - g_command_buffer_mgr->WaitForGPUIdle(); - - // It's now safe to resize the swap chain. - if (!m_swap_chain->ResizeSwapChain()) - PanicAlert("Failed to resize swap chain."); - - OnSwapChainResized(); -} - void Renderer::ApplyState() { } diff --git a/Source/Core/VideoBackends/Vulkan/Renderer.h b/Source/Core/VideoBackends/Vulkan/Renderer.h index 593c2cd887..5a367f2bcd 100644 --- a/Source/Core/VideoBackends/Vulkan/Renderer.h +++ b/Source/Core/VideoBackends/Vulkan/Renderer.h @@ -81,7 +81,6 @@ private: void OnSwapChainResized(); void BindEFBToStateTracker(); void ResizeEFBTextures(); - void ResizeSwapChain(); void RecompileShaders(); bool CompileShaders(); diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp index c5b3791637..abcd38c94a 100644 --- a/Source/Core/VideoBackends/Vulkan/SwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/SwapChain.cpp @@ -472,14 +472,27 @@ bool SwapChain::ResizeSwapChain() return true; } +bool SwapChain::RecreateSwapChain() +{ + DestroySwapChainImages(); + DestroySwapChain(); + if (!CreateSwapChain() || !SetupSwapChainImages()) + { + PanicAlert("Failed to re-configure swap chain images, this is fatal (for now)"); + return false; + } + + return true; +} + bool SwapChain::SetVSync(bool enabled) { if (m_vsync_enabled == enabled) return true; - // Resizing recreates the swap chain with the new present mode. + // Recreate the swap chain with the new present mode. m_vsync_enabled = enabled; - return ResizeSwapChain(); + return RecreateSwapChain(); } bool SwapChain::RecreateSurface(void* native_handle) diff --git a/Source/Core/VideoBackends/Vulkan/SwapChain.h b/Source/Core/VideoBackends/Vulkan/SwapChain.h index 37c3460796..69ccb6dff1 100644 --- a/Source/Core/VideoBackends/Vulkan/SwapChain.h +++ b/Source/Core/VideoBackends/Vulkan/SwapChain.h @@ -55,6 +55,7 @@ public: bool RecreateSurface(void* native_handle); bool ResizeSwapChain(); + bool RecreateSwapChain(); // Change vsync enabled state. This may fail as it causes a swapchain recreation. bool SetVSync(bool enabled);