diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 996daa206d..10c5817eb4 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -227,7 +227,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.useDebugDevice, "EmuCore/GS", "UseDebugDevice", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.skipPresentingDuplicateFrames, "EmuCore/GS", "SkipDuplicateFrames", false); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableMailboxPresentation, "EmuCore/GS", "DisableMailboxPresentation", false); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.threadedPresentation, "EmuCore/GS", "DisableThreadedPresentation", false); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.exclusiveFullscreenControl, "EmuCore/GS", "ExclusiveFullscreenControl", -1, -1); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.overrideTextureBarriers, "EmuCore/GS", "OverrideTextureBarriers", -1, -1); SettingWidgetBinder::BindWidgetToIntSetting( @@ -335,7 +334,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* m_ui.useBlitSwapChain = nullptr; m_ui.disableMailboxPresentation = nullptr; m_ui.skipPresentingDuplicateFrames = nullptr; - m_ui.threadedPresentation = nullptr; m_ui.overrideTextureBarriers = nullptr; m_ui.disableFramebufferFetch = nullptr; m_ui.disableShaderCache = nullptr; @@ -767,11 +765,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget* tr("Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. " "Usually results in worse frame pacing.")); - dialog->registerWidgetHelp(m_ui.threadedPresentation, tr("Disable Threaded Presentation"), tr("Unchecked"), - tr("Presents frames on the main GS thread instead of a worker thread. Used for debugging frametime issues. " - "Could reduce chance of missing a frame or reduce tearing at the expense of more erratic frame times. " - "Only applies to the Vulkan renderer.")); - dialog->registerWidgetHelp(m_ui.useDebugDevice, tr("Enable Debug Device"), tr("Unchecked"), tr("Enables API-level validation of graphics commands.")); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index 350e8db764..2f173baed1 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -2052,13 +2052,6 @@ - - - Disable Threaded Presentation - - - - Disable Mailbox Presentation diff --git a/pcsx2/Config.h b/pcsx2/Config.h index acc30a64cd..cc4299a03b 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -602,7 +602,6 @@ struct Pcsx2Config DisableShaderCache : 1, DisableFramebufferFetch : 1, DisableVertexShaderExpand : 1, - DisableThreadedPresentation : 1, SkipDuplicateFrames : 1, OsdShowMessages : 1, OsdShowSpeed : 1, diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp index 402ce2bbb8..051a173af5 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp @@ -1008,7 +1008,6 @@ void GSDeviceVK::WaitForFenceCounter(u64 fence_counter) void GSDeviceVK::WaitForGPUIdle() { - WaitForPresentComplete(); vkDeviceWaitIdle(m_device); } @@ -1050,19 +1049,12 @@ void GSDeviceVK::ScanForCommandBufferCompletion() void GSDeviceVK::WaitForCommandBufferCompletion(u32 index) { - // We might be waiting for the buffer we just submitted to the worker thread. - if (m_queued_present.command_buffer_index == index && !m_present_done.load(std::memory_order_acquire)) - { - Console.WarningFmt("Waiting for threaded submission of cmdbuffer {}", index); - WaitForPresentComplete(); - } - // Wait for this command buffer to be completed. const VkResult res = vkWaitForFences(m_device, 1, &m_frame_resources[index].fence, VK_TRUE, UINT64_MAX); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); - m_last_submit_failed.store(true, std::memory_order_release); + m_last_submit_failed = true; return; } @@ -1085,8 +1077,7 @@ void GSDeviceVK::WaitForCommandBufferCompletion(u32 index) m_completed_fence_counter = now_completed_counter; } -void GSDeviceVK::SubmitCommandBuffer( - VKSwapChain* present_swap_chain /* = nullptr */, bool submit_on_thread /* = false */) +void GSDeviceVK::SubmitCommandBuffer(VKSwapChain* present_swap_chain) { FrameResources& resources = m_frame_resources[m_current_frame]; @@ -1154,33 +1145,9 @@ void GSDeviceVK::SubmitCommandBuffer( if (spin_cycles != 0) WaitForSpinCompletion(m_current_frame); - std::unique_lock lock(m_present_mutex); - WaitForPresentComplete(lock); - if (spin_enabled && m_optional_extensions.vk_ext_calibrated_timestamps) resources.submit_timestamp = GetCPUTimestamp(); - // Don't use threaded presentation when spinning is enabled. ScanForCommandBufferCompletion() - // calls vkGetFenceStatus(), which reads a fence that has been passed off to the thread. - if (!submit_on_thread || GSConfig.HWSpinGPUForReadbacks || !m_present_thread.joinable()) - { - DoSubmitCommandBuffer(m_current_frame, present_swap_chain, spin_cycles); - if (present_swap_chain) - DoPresent(present_swap_chain); - return; - } - - m_queued_present.command_buffer_index = m_current_frame; - m_queued_present.swap_chain = present_swap_chain; - m_queued_present.spin_cycles = spin_cycles; - m_present_done.store(false, std::memory_order_release); - m_present_queued_cv.notify_one(); -} - -void GSDeviceVK::DoSubmitCommandBuffer(u32 index, VKSwapChain* present_swap_chain, u32 spin_cycles) -{ - FrameResources& resources = m_frame_resources[index]; - uint32_t wait_bits = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkSemaphore semas[2]; VkSubmitInfo submit_info = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; @@ -1197,7 +1164,7 @@ void GSDeviceVK::DoSubmitCommandBuffer(u32 index, VKSwapChain* present_swap_chai if (spin_cycles != 0) { semas[0] = present_swap_chain->GetRenderingFinishedSemaphore(); - semas[1] = m_spin_resources[index].semaphore; + semas[1] = m_spin_resources[m_current_frame].semaphore; submit_info.signalSemaphoreCount = 2; submit_info.pSignalSemaphores = semas; } @@ -1210,105 +1177,44 @@ void GSDeviceVK::DoSubmitCommandBuffer(u32 index, VKSwapChain* present_swap_chai else if (spin_cycles != 0) { submit_info.signalSemaphoreCount = 1; - submit_info.pSignalSemaphores = &m_spin_resources[index].semaphore; + submit_info.pSignalSemaphores = &m_spin_resources[m_current_frame].semaphore; } - const VkResult res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence); + res = vkQueueSubmit(m_graphics_queue, 1, &submit_info, resources.fence); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: "); - m_last_submit_failed.store(true, std::memory_order_release); + m_last_submit_failed = true; return; } if (spin_cycles != 0) - SubmitSpinCommand(index, spin_cycles); -} + SubmitSpinCommand(m_current_frame, spin_cycles); -void GSDeviceVK::DoPresent(VKSwapChain* present_swap_chain) -{ - const VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, nullptr, 1, - present_swap_chain->GetRenderingFinishedSemaphorePtr(), 1, present_swap_chain->GetSwapChainPtr(), - present_swap_chain->GetCurrentImageIndexPtr(), nullptr}; - - present_swap_chain->ReleaseCurrentImage(); - - const VkResult res = vkQueuePresentKHR(m_present_queue, &present_info); - if (res != VK_SUCCESS) + if (present_swap_chain) { - // 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: "); + const VkPresentInfoKHR present_info = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, nullptr, 1, + present_swap_chain->GetRenderingFinishedSemaphorePtr(), 1, present_swap_chain->GetSwapChainPtr(), + present_swap_chain->GetCurrentImageIndexPtr(), nullptr}; - m_last_present_failed.store(true, std::memory_order_release); - return; + present_swap_chain->ReleaseCurrentImage(); + + const VkResult res = vkQueuePresentKHR(m_present_queue, &present_info); + 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_last_present_failed = true; + return; + } + + // Grab the next image as soon as possible, that way we spend less time blocked on the next + // submission. Don't care if it fails, we'll deal with that at the presentation call site. + // Credit to dxvk for the idea. + present_swap_chain->AcquireNextImage(); } - - // Grab the next image as soon as possible, that way we spend less time blocked on the next - // submission. Don't care if it fails, we'll deal with that at the presentation call site. - // Credit to dxvk for the idea. - present_swap_chain->AcquireNextImage(); -} - -void GSDeviceVK::WaitForPresentComplete() -{ - if (m_present_done.load(std::memory_order_acquire)) - return; - - std::unique_lock lock(m_present_mutex); - WaitForPresentComplete(lock); -} - -void GSDeviceVK::WaitForPresentComplete(std::unique_lock& lock) -{ - if (m_present_done.load(std::memory_order_acquire)) - return; - - m_present_done_cv.wait(lock, [this]() { return m_present_done.load(std::memory_order_acquire); }); -} - -void GSDeviceVK::PresentThread() -{ - std::unique_lock lock(m_present_mutex); - while (!m_present_thread_done.load(std::memory_order_acquire)) - { - m_present_queued_cv.wait(lock, [this]() { - return !m_present_done.load(std::memory_order_acquire) || - m_present_thread_done.load(std::memory_order_acquire); - }); - - if (m_present_done.load(std::memory_order_acquire)) - continue; - - DoSubmitCommandBuffer( - m_queued_present.command_buffer_index, m_queued_present.swap_chain, m_queued_present.spin_cycles); - if (m_queued_present.swap_chain) - DoPresent(m_queued_present.swap_chain); - m_present_done.store(true, std::memory_order_release); - m_present_done_cv.notify_one(); - } -} - -void GSDeviceVK::StartPresentThread() -{ - pxAssert(!m_present_thread.joinable()); - m_present_thread_done.store(false, std::memory_order_release); - m_present_thread = std::thread(&GSDeviceVK::PresentThread, this); -} - -void GSDeviceVK::StopPresentThread() -{ - if (!m_present_thread.joinable()) - return; - - { - std::unique_lock lock(m_present_mutex); - WaitForPresentComplete(lock); - m_present_thread_done.store(true, std::memory_order_release); - m_present_queued_cv.notify_one(); - } - - m_present_thread.join(); } void GSDeviceVK::CommandBufferCompleted(u32 index) @@ -1411,12 +1317,11 @@ void GSDeviceVK::ActivateCommandBuffer(u32 index) void GSDeviceVK::ExecuteCommandBuffer(WaitType wait_for_completion) { - if (m_last_submit_failed.load(std::memory_order_acquire)) + if (m_last_submit_failed) return; - // If we're waiting for completion, don't bother waking the worker thread. const u32 current_frame = m_current_frame; - SubmitCommandBuffer(); + SubmitCommandBuffer(nullptr); MoveToNextCommandBuffer(); if (wait_for_completion != WaitType::None) @@ -1433,16 +1338,6 @@ void GSDeviceVK::ExecuteCommandBuffer(WaitType wait_for_completion) } } -bool GSDeviceVK::CheckLastPresentFail() -{ - return m_last_present_failed.exchange(false, std::memory_order_acq_rel); -} - -bool GSDeviceVK::CheckLastSubmitFail() -{ - return m_last_submit_failed.load(std::memory_order_acquire); -} - void GSDeviceVK::DeferBufferDestruction(VkBuffer object, VmaAllocation allocation) { FrameResources& resources = m_frame_resources[m_current_frame]; @@ -1809,7 +1704,7 @@ void GSDeviceVK::WaitForSpinCompletion(u32 index) if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkWaitForFences failed: "); - m_last_submit_failed.store(true, std::memory_order_release); + m_last_submit_failed = true; return; } SpinCommandCompleted(index); @@ -2169,7 +2064,6 @@ void GSDeviceVK::Destroy() WaitForGPUIdle(); } - StopPresentThread(); m_swap_chain.reset(); DestroySpinResources(); @@ -2334,6 +2228,10 @@ GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip) { EndRenderPass(); + // Check if the device was lost. + if (m_last_submit_failed) + return PresentResult::DeviceLost; + if (frame_skip) return PresentResult::FrameSkipped; @@ -2344,13 +2242,6 @@ GSDevice::PresentResult GSDeviceVK::BeginPresent(bool frame_skip) return PresentResult::FrameSkipped; } - // Previous frame needs to be presented before we can acquire the swap chain. - WaitForPresentComplete(); - - // Check if the device was lost. - if (CheckLastSubmitFail()) - return PresentResult::DeviceLost; - VkResult res = m_swap_chain->AcquireNextImage(); if (res != VK_SUCCESS) { @@ -2422,7 +2313,7 @@ void GSDeviceVK::EndPresent() m_swap_chain->GetCurrentTexture()->TransitionToLayout(cmdbuffer, GSTextureVK::Layout::PresentSrc); g_perfmon.Put(GSPerfMon::RenderPasses, 1); - SubmitCommandBuffer(m_swap_chain.get(), !m_swap_chain->IsPresentModeSynchronizing()); + SubmitCommandBuffer(m_swap_chain.get()); MoveToNextCommandBuffer(); InvalidateCachedState(); @@ -2621,9 +2512,6 @@ bool GSDeviceVK::CreateDeviceAndSwapChain() VKShaderCache::Create(); - if (!GSConfig.DisableThreadedPresentation) - StartPresentThread(); - if (surface != VK_NULL_HANDLE) { VkPresentModeKHR present_mode; @@ -4554,7 +4442,7 @@ void GSDeviceVK::RenderBlankFrame() cmdbuffer, sctex->GetImage(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &s_present_clear_color.color, 1, &srr); m_swap_chain->GetCurrentTexture()->TransitionToLayout(cmdbuffer, GSTextureVK::Layout::PresentSrc); - SubmitCommandBuffer(m_swap_chain.get(), !m_swap_chain->IsPresentModeSynchronizing()); + SubmitCommandBuffer(m_swap_chain.get()); ActivateCommandBuffer((m_current_frame + 1) % NUM_COMMAND_BUFFERS); } diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h index 2cdaf7a368..9853b4a9fc 100644 --- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h +++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.h @@ -136,7 +136,7 @@ private: bool EnableDebugUtils(); void DisableDebugUtils(); - void SubmitCommandBuffer(VKSwapChain* present_swap_chain = nullptr, bool submit_on_thread = false); + void SubmitCommandBuffer(VKSwapChain* present_swap_chain); void MoveToNextCommandBuffer(); enum class WaitType @@ -148,11 +148,6 @@ private: static WaitType GetWaitType(bool wait, bool spin); void ExecuteCommandBuffer(WaitType wait_for_completion); - void WaitForPresentComplete(); - - // Was the last present submitted to the queue a failure? If so, we must recreate our swapchain. - bool CheckLastPresentFail(); - bool CheckLastSubmitFail(); // Allocates a temporary CPU staging buffer, fires the callback with it to populate, then copies to a GPU buffer. bool AllocatePreinitializedGPUBuffer(u32 size, VkBuffer* gpu_buffer, VmaAllocation* gpu_allocation, @@ -195,13 +190,6 @@ private: void ScanForCommandBufferCompletion(); void WaitForCommandBufferCompletion(u32 index); - void DoSubmitCommandBuffer(u32 index, VKSwapChain* present_swap_chain, u32 spin_cycles); - void DoPresent(VKSwapChain* present_swap_chain); - void WaitForPresentComplete(std::unique_lock& lock); - void PresentThread(); - void StartPresentThread(); - void StopPresentThread(); - bool InitSpinResources(); void DestroySpinResources(); void WaitForSpinCompletion(u32 index); @@ -283,23 +271,8 @@ private: u64 m_completed_fence_counter = 0; u32 m_current_frame = 0; - std::atomic_bool m_last_submit_failed{false}; - std::atomic_bool m_last_present_failed{false}; - std::atomic_bool m_present_done{true}; - std::mutex m_present_mutex; - std::condition_variable m_present_queued_cv; - std::condition_variable m_present_done_cv; - std::thread m_present_thread; - std::atomic_bool m_present_thread_done{false}; - - struct QueuedPresent - { - VKSwapChain* swap_chain; - u32 command_buffer_index; - u32 spin_cycles; - }; - - QueuedPresent m_queued_present = {nullptr, 0xFFFFFFFFu, 0}; + bool m_last_submit_failed = false; + bool m_last_present_failed = false; std::map m_render_pass_cache; diff --git a/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h b/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h index a75d3ab7da..c259f592d3 100644 --- a/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h +++ b/pcsx2/GS/Renderers/Vulkan/VKSwapChain.h @@ -65,10 +65,6 @@ public: return &m_semaphores[m_current_semaphore].rendering_finished_semaphore; } - // Returns true if the current present mode is synchronizing. - __fi bool IsPresentModeSynchronizing() const { return (m_present_mode == VK_PRESENT_MODE_FIFO_KHR); } - __fi VkPresentModeKHR GetPresentMode() const { return m_present_mode; } - VkFormat GetTextureFormat() const; VkResult AcquireNextImage(); void ReleaseCurrentImage(); diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 394539a7e0..6604573c07 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -3900,9 +3900,6 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad FSUI_CSTR("Skips displaying frames that don't change in 25/30fps games. Can improve speed, but increase input lag/make frame pacing " "worse."), "EmuCore/GS", "SkipDuplicateFrames", false); - DrawToggleSetting(bsi, FSUI_CSTR("Disable Threaded Presentation"), - FSUI_CSTR("Presents frames on the main GS thread instead of a worker thread. Used for debugging frametime issues."), - "EmuCore/GS", "DisableThreadedPresentation", false); DrawToggleSetting(bsi, FSUI_CSTR("Disable Mailbox Presentation"), FSUI_CSTR("Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. " "Usually results in worse frame pacing."), @@ -7108,8 +7105,6 @@ TRANSLATE_NOOP("FullscreenUI", "Applies a shader which replicates the visual eff TRANSLATE_NOOP("FullscreenUI", "Advanced"); TRANSLATE_NOOP("FullscreenUI", "Skip Presenting Duplicate Frames"); TRANSLATE_NOOP("FullscreenUI", "Skips displaying frames that don't change in 25/30fps games. Can improve speed, but increase input lag/make frame pacing worse."); -TRANSLATE_NOOP("FullscreenUI", "Disable Threaded Presentation"); -TRANSLATE_NOOP("FullscreenUI", "Presents frames on the main GS thread instead of a worker thread. Used for debugging frametime issues."); TRANSLATE_NOOP("FullscreenUI", "Disable Mailbox Presentation"); TRANSLATE_NOOP("FullscreenUI", "Forces the use of FIFO over Mailbox presentation, i.e. double buffering instead of triple buffering. Usually results in worse frame pacing."); TRANSLATE_NOOP("FullscreenUI", "Hardware Download Mode"); diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index e892f2b791..0d1e6813f8 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -621,7 +621,6 @@ Pcsx2Config::GSOptions::GSOptions() DisableShaderCache = false; DisableFramebufferFetch = false; DisableVertexShaderExpand = false; - DisableThreadedPresentation = false; SkipDuplicateFrames = false; OsdShowMessages = true; OsdShowSpeed = false; @@ -781,7 +780,6 @@ bool Pcsx2Config::GSOptions::RestartOptionsAreEqual(const GSOptions& right) cons OpEqu(DisableShaderCache) && OpEqu(DisableFramebufferFetch) && OpEqu(DisableVertexShaderExpand) && - OpEqu(DisableThreadedPresentation) && OpEqu(OverrideTextureBarriers) && OpEqu(ExclusiveFullscreenControl); } @@ -825,7 +823,6 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) SettingsWrapBitBool(DisableShaderCache); SettingsWrapBitBool(DisableFramebufferFetch); SettingsWrapBitBool(DisableVertexShaderExpand); - SettingsWrapBitBool(DisableThreadedPresentation); SettingsWrapBitBool(SkipDuplicateFrames); SettingsWrapBitBool(OsdShowMessages); SettingsWrapBitBool(OsdShowSpeed);