diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp index 27b4f0a4a01..a58293ef6bd 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.cpp +++ b/Source/Core/VideoBackends/D3D/D3DGfx.cpp @@ -157,10 +157,11 @@ void Gfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u D3D::context->Dispatch(groups_x, groups_y, groups_z); } -void Gfx::BindBackbuffer(const ClearColor& clear_color) +bool Gfx::BindBackbuffer(const ClearColor& clear_color) { CheckForSwapChainChanges(); SetAndClearFramebuffer(m_swap_chain->GetFramebuffer(), clear_color); + return true; } void Gfx::PresentBackbuffer() diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.h b/Source/Core/VideoBackends/D3D/D3DGfx.h index 07c47e93aff..03104c25f4f 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.h +++ b/Source/Core/VideoBackends/D3D/D3DGfx.h @@ -60,7 +60,7 @@ public: void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; + bool BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; void SetFullscreen(bool enable_fullscreen) override; bool IsFullscreen() const override; diff --git a/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp index 51441457e03..1b126a39095 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp @@ -365,10 +365,11 @@ void Gfx::DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u m_dirty_bits |= DirtyState_Pipeline; } -void Gfx::BindBackbuffer(const ClearColor& clear_color) +bool Gfx::BindBackbuffer(const ClearColor& clear_color) { CheckForSwapChainChanges(); SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(), clear_color); + return true; } void Gfx::CheckForSwapChainChanges() diff --git a/Source/Core/VideoBackends/D3D12/D3D12Gfx.h b/Source/Core/VideoBackends/D3D12/D3D12Gfx.h index 9f537e5e090..1f8e40314ba 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12Gfx.h +++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.h @@ -68,7 +68,7 @@ public: void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; + bool BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; SurfaceInfo GetSurfaceInfo() const override; diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.h b/Source/Core/VideoBackends/Metal/MTLGfx.h index e92ae285e64..f1916766a6d 100644 --- a/Source/Core/VideoBackends/Metal/MTLGfx.h +++ b/Source/Core/VideoBackends/Metal/MTLGfx.h @@ -67,7 +67,7 @@ public: void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; + bool BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; SurfaceInfo GetSurfaceInfo() const override; diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.mm b/Source/Core/VideoBackends/Metal/MTLGfx.mm index d594117de5b..a61c3d79d46 100644 --- a/Source/Core/VideoBackends/Metal/MTLGfx.mm +++ b/Source/Core/VideoBackends/Metal/MTLGfx.mm @@ -447,7 +447,7 @@ void Metal::Gfx::DispatchComputeShader(const AbstractShader* shader, // } } -void Metal::Gfx::BindBackbuffer(const ClearColor& clear_color) +bool Metal::Gfx::BindBackbuffer(const ClearColor& clear_color) { @autoreleasepool { @@ -456,6 +456,7 @@ void Metal::Gfx::BindBackbuffer(const ClearColor& clear_color) m_drawable = MRCRetain([m_layer nextDrawable]); m_backbuffer->UpdateBackbufferTexture([m_drawable texture]); SetAndClearFramebuffer(m_backbuffer.get(), clear_color); + return m_drawable != nullptr; } } diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.cpp b/Source/Core/VideoBackends/OGL/OGLGfx.cpp index fb773a6a2d2..fa20a76d113 100644 --- a/Source/Core/VideoBackends/OGL/OGLGfx.cpp +++ b/Source/Core/VideoBackends/OGL/OGLGfx.cpp @@ -407,11 +407,12 @@ void OGLGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEn glDepthMask(m_current_depth_state.updateenable); } -void OGLGfx::BindBackbuffer(const ClearColor& clear_color) +bool OGLGfx::BindBackbuffer(const ClearColor& clear_color) { CheckForSurfaceChange(); CheckForSurfaceResize(); SetAndClearFramebuffer(m_system_framebuffer.get(), clear_color); + return true; } void OGLGfx::PresentBackbuffer() diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.h b/Source/Core/VideoBackends/OGL/OGLGfx.h index 1f392c7296f..427aa3e5595 100644 --- a/Source/Core/VideoBackends/OGL/OGLGfx.h +++ b/Source/Core/VideoBackends/OGL/OGLGfx.h @@ -57,7 +57,7 @@ public: void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; + bool BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; void BeginUtilityDrawing() override; diff --git a/Source/Core/VideoBackends/Software/SWGfx.cpp b/Source/Core/VideoBackends/Software/SWGfx.cpp index 7b9196063d4..bfe7f954214 100644 --- a/Source/Core/VideoBackends/Software/SWGfx.cpp +++ b/Source/Core/VideoBackends/Software/SWGfx.cpp @@ -55,15 +55,16 @@ SWGfx::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* dep std::move(additional_color_attachments)); } -void SWGfx::BindBackbuffer(const ClearColor& clear_color) +bool SWGfx::BindBackbuffer(const ClearColor& clear_color) { // Look for framebuffer resizes if (!g_presenter->SurfaceResizedTestAndClear()) - return; + return true; GLContext* context = m_window->GetContext(); context->Update(); g_presenter->SetBackbuffer(context->GetBackBufferWidth(), context->GetBackBufferHeight()); + return true; } class SWShader final : public AbstractShader diff --git a/Source/Core/VideoBackends/Software/SWGfx.h b/Source/Core/VideoBackends/Software/SWGfx.h index 415564a19dc..83773eccdb5 100644 --- a/Source/Core/VideoBackends/Software/SWGfx.h +++ b/Source/Core/VideoBackends/Software/SWGfx.h @@ -26,7 +26,7 @@ public: CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* depth_attachment, std::vector additional_color_attachments) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; + bool BindBackbuffer(const ClearColor& clear_color = {}) override; std::unique_ptr CreateShaderFromSource(ShaderStage stage, std::string_view source, std::string_view name) override; diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp index 3aa05dbaf0f..be262d98ddd 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp @@ -300,7 +300,7 @@ void CommandBufferManager::WaitForCommandBufferCompletion(u32 index) } void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, - bool wait_for_completion, + bool wait_for_completion, bool advance_to_next_frame, VkSwapchainKHR present_swap_chain, uint32_t present_image_index) { @@ -334,7 +334,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, WaitForCommandBufferCompletion(m_current_cmd_buffer); } - if (present_swap_chain != VK_NULL_HANDLE) + if (advance_to_next_frame) { m_current_frame = (m_current_frame + 1) % NUM_FRAMES_IN_FLIGHT; diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h index 02490974238..d92cab4a49c 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -78,6 +78,7 @@ public: void WaitForFenceCounter(u64 fence_counter); void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion, + bool advance_to_next_frame = false, VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE, uint32_t present_image_index = 0xFFFFFFFF); diff --git a/Source/Core/VideoBackends/Vulkan/VKGfx.cpp b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp index ea4d972864c..d65a5d46803 100644 --- a/Source/Core/VideoBackends/Vulkan/VKGfx.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp @@ -221,7 +221,7 @@ void VKGfx::WaitForGPUIdle() ExecuteCommandBuffer(false, true); } -void VKGfx::BindBackbuffer(const ClearColor& clear_color) +bool VKGfx::BindBackbuffer(const ClearColor& clear_color) { StateTracker::GetInstance()->EndRenderPass(); @@ -284,10 +284,22 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color) res = m_swap_chain->AcquireNextImage(); if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) - PanicAlertFmt("Failed to grab image from swap chain: {:#010X} {}", Common::ToUnderlying(res), - VkResultToString(res)); + { + if (res == VK_ERROR_OUT_OF_DATE_KHR) + { + INFO_LOG_FMT(VIDEO, "Swapchain still out of date, will try again next frame..."); + } + else + { + PanicAlertFmt("Failed to grab image from swap chain: {:#010X} {}", + Common::ToUnderlying(res), VkResultToString(res)); + } + } } + if (!m_swap_chain->IsCurrentImageValid()) + return false; + // Transition from undefined (or present src, but it can be substituted) to // color attachment ready for writing. These transitions must occur outside // a render pass, unless the render pass declares a self-dependency. @@ -296,6 +308,7 @@ void VKGfx::BindBackbuffer(const ClearColor& clear_color) g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); SetAndClearFramebuffer(m_swap_chain->GetCurrentFramebuffer(), ClearColor{{0.0f, 0.0f, 0.0f, 1.0f}}); + return true; } void VKGfx::PresentBackbuffer() @@ -303,17 +316,24 @@ void VKGfx::PresentBackbuffer() // End drawing to backbuffer StateTracker::GetInstance()->EndRenderPass(); - // Transition the backbuffer to PRESENT_SRC to ensure all commands drawing - // to it have finished before present. - m_swap_chain->GetCurrentTexture()->TransitionToLayout( - g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); + if (m_swap_chain->IsCurrentImageValid()) + { + // Transition the backbuffer to PRESENT_SRC to ensure all commands drawing + // to it have finished before present. + m_swap_chain->GetCurrentTexture()->TransitionToLayout( + g_command_buffer_mgr->GetCurrentCommandBuffer(), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); - // Submit the current command buffer, signaling rendering finished semaphore when it's done - // Because this final command buffer is rendering to the swap chain, we need to wait for - // the available semaphore to be signaled before executing the buffer. This final submission - // can happen off-thread in the background while we're preparing the next frame. - g_command_buffer_mgr->SubmitCommandBuffer(true, false, m_swap_chain->GetSwapChain(), - m_swap_chain->GetCurrentImageIndex()); + // Submit the current command buffer, signaling rendering finished semaphore when it's done + // Because this final command buffer is rendering to the swap chain, we need to wait for + // the available semaphore to be signaled before executing the buffer. This final submission + // can happen off-thread in the background while we're preparing the next frame. + g_command_buffer_mgr->SubmitCommandBuffer(true, false, true, m_swap_chain->GetSwapChain(), + m_swap_chain->GetCurrentImageIndex()); + } + else + { + g_command_buffer_mgr->SubmitCommandBuffer(true, false, true); + } // New cmdbuffer, so invalidate state. StateTracker::GetInstance()->InvalidateCachedState(); diff --git a/Source/Core/VideoBackends/Vulkan/VKGfx.h b/Source/Core/VideoBackends/Vulkan/VKGfx.h index 539b427a9de..f2e411a9f65 100644 --- a/Source/Core/VideoBackends/Vulkan/VKGfx.h +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.h @@ -74,7 +74,7 @@ public: void DrawIndexed(u32 base_index, u32 num_indices, u32 base_vertex) override; void DispatchComputeShader(const AbstractShader* shader, u32 groupsize_x, u32 groupsize_y, u32 groupsize_z, u32 groups_x, u32 groups_y, u32 groups_z) override; - void BindBackbuffer(const ClearColor& clear_color = {}) override; + bool BindBackbuffer(const ClearColor& clear_color = {}) override; void PresentBackbuffer() override; void SetFullscreen(bool enable_fullscreen) override; bool IsFullscreen() const override; diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp index 58e2e439e82..e94d8e52cb8 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp @@ -497,6 +497,7 @@ VkResult SwapChain::AcquireNextImage() VkResult res = vkAcquireNextImageKHR(g_vulkan_context->GetDevice(), m_swap_chain, UINT64_MAX, g_command_buffer_mgr->GetCurrentCommandBufferSemaphore(), VK_NULL_HANDLE, &m_current_swap_chain_image_index); + m_current_swap_chain_image_is_valid = res >= 0; if (res != VK_SUCCESS && res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR) LOG_VULKAN_ERROR(res, "vkAcquireNextImageKHR failed: "); diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.h b/Source/Core/VideoBackends/Vulkan/VKSwapChain.h index 5e67217f2d7..5f173185a0e 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.h +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.h @@ -38,6 +38,7 @@ public: u32 GetWidth() const { return m_width; } u32 GetHeight() const { return m_height; } u32 GetCurrentImageIndex() const { return m_current_swap_chain_image_index; } + bool IsCurrentImageValid() const { return m_current_swap_chain_image_is_valid; } VkImage GetCurrentImage() const { return m_swap_chain_images[m_current_swap_chain_image_index].image; @@ -98,6 +99,7 @@ private: bool m_fullscreen_supported = false; bool m_current_fullscreen_state = false; bool m_next_fullscreen_state = false; + bool m_current_swap_chain_image_is_valid = false; VkSwapchainKHR m_swap_chain = VK_NULL_HANDLE; std::vector m_swap_chain_images; diff --git a/Source/Core/VideoCommon/AbstractGfx.h b/Source/Core/VideoCommon/AbstractGfx.h index 5d2679f68f2..d739c3f5faa 100644 --- a/Source/Core/VideoCommon/AbstractGfx.h +++ b/Source/Core/VideoCommon/AbstractGfx.h @@ -101,7 +101,10 @@ public: // Binds the backbuffer for rendering. The buffer will be cleared immediately after binding. // This is where any window size changes are detected, therefore m_backbuffer_width and/or // m_backbuffer_height may change after this function returns. - virtual void BindBackbuffer(const ClearColor& clear_color = {}) {} + // If this returns false, a problem occurred binding the backbuffer. + // Don't render anything to it, but still call `PresentBackbuffer`, which will reset any + // per-frame resources and prepare for the next frame. + virtual bool BindBackbuffer(const ClearColor& clear_color = {}) { return true; } // Presents the backbuffer to the window system, or "swaps buffers". virtual void PresentBackbuffer() {} diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index c7ae018bbc1..6813c7a4e31 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -844,10 +844,10 @@ void Presenter::Present() UpdateDrawRectangle(); g_gfx->BeginUtilityDrawing(); - g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); + const bool backbuffer_bound = g_gfx->BindBackbuffer({{0.0f, 0.0f, 0.0f, 1.0f}}); // Render the XFB to the screen. - if (m_xfb_entry) + if (backbuffer_bound && m_xfb_entry) { // Adjust the source rectangle instead of using an oversized viewport to render the XFB. auto render_target_rc = GetTargetRectangle(); @@ -860,7 +860,8 @@ void Presenter::Present() if (m_onscreen_ui) { m_onscreen_ui->Finalize(); - m_onscreen_ui->DrawImGui(); + if (backbuffer_bound) + m_onscreen_ui->DrawImGui(); } // Present to the window system.