diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp index 27b4f0a4a0..a58293ef6b 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 07c47e93af..03104c25f4 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 51441457e0..1b126a3909 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 9f537e5e09..1f8e40314b 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 e92ae285e6..f1916766a6 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 d594117de5..a61c3d79d4 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 fb773a6a2d..fa20a76d11 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 1f392c7296..427aa3e559 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 7b9196063d..bfe7f95421 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 415564a19d..83773eccdb 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 3aa05dbaf0..be262d98dd 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 0249097423..c6f167cf38 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -63,11 +63,18 @@ public: // Returns the semaphore for the current command buffer, which can be used to ensure the // swap chain image is ready before the command buffer executes. + // Once you've confirmed that the semaphore will be signalled this frame, call + // `MarkCurrentCommandBufferSemaphoreUsed`. VkSemaphore GetCurrentCommandBufferSemaphore() { - auto& resources = m_command_buffers[m_current_cmd_buffer]; - resources.semaphore_used = true; - return resources.semaphore; + return m_command_buffers[m_current_cmd_buffer].semaphore; + } + + // Marks the current command buffer's semaphore as used, so we'll wait on it in the next + // command buffer submission. + void MarkCurrentCommandBufferSemaphoreUsed() + { + m_command_buffers[m_current_cmd_buffer].semaphore_used = true; } // Ensure that the worker thread has submitted any previous command buffers and is idle. @@ -78,6 +85,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 ea4d972864..d65a5d4680 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 539b427a9d..f2e411a9f6 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 58e2e439e8..1005cc034a 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp @@ -497,6 +497,9 @@ 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 (IsCurrentImageValid()) + g_command_buffer_mgr->MarkCurrentCommandBufferSemaphoreUsed(); 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 5e67217f2d..5f173185a0 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 5d2679f68f..d739c3f5fa 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 c7ae018bbc..6813c7a4e3 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.