diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 4ddf550edf..378ecee7c6 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -610,6 +610,7 @@ + @@ -1228,6 +1229,7 @@ + diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt index ec9e1b51cb..31a08d2843 100644 --- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt +++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt @@ -4,6 +4,8 @@ add_library(videovulkan Constants.h ObjectCache.cpp ObjectCache.h + PresentWait.cpp + PresentWait.h ShaderCompiler.cpp ShaderCompiler.h StagingBuffer.cpp diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp index 3aa05dbaf0..99a7715eb5 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp @@ -10,6 +10,7 @@ #include "Common/MsgHandler.h" #include "Common/Thread.h" +#include "VideoBackends/Vulkan/PresentWait.h" #include "VideoBackends/Vulkan/VulkanContext.h" #include "VideoCommon/Constants.h" @@ -223,7 +224,7 @@ bool CommandBufferManager::CreateSubmitThread() { m_submit_thread.Reset("VK submission thread", [this](PendingCommandBufferSubmit submit) { SubmitCommandBuffer(submit.command_buffer_index, submit.present_swap_chain, - submit.present_image_index); + submit.present_image_index, submit.frame_id); CmdBufferResources& resources = m_command_buffers[submit.command_buffer_index]; resources.waiting_for_submit.store(false, std::memory_order_release); }); @@ -317,19 +318,25 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, } } + uint64_t frame_id = 0; + if (present_swap_chain != VK_NULL_HANDLE) + { + frame_id = g_vulkan_context->NextPresentCount(); + } + // Submitting off-thread? if (m_use_threaded_submission && submit_on_worker_thread && !wait_for_completion) { resources.waiting_for_submit.store(true, std::memory_order_relaxed); // Push to the pending submit queue. - m_submit_thread.Push({present_swap_chain, present_image_index, m_current_cmd_buffer}); + m_submit_thread.Push({present_swap_chain, present_image_index, m_current_cmd_buffer, frame_id}); } else { WaitForWorkerThreadIdle(); // Pass through to normal submission path. - SubmitCommandBuffer(m_current_cmd_buffer, present_swap_chain, present_image_index); + SubmitCommandBuffer(m_current_cmd_buffer, present_swap_chain, present_image_index, frame_id); if (wait_for_completion) WaitForCommandBufferCompletion(m_current_cmd_buffer); } @@ -382,7 +389,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread, void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index, VkSwapchainKHR present_swap_chain, - u32 present_image_index) + u32 present_image_index, u64 frame_id) { CmdBufferResources& resources = m_command_buffers[command_buffer_index]; @@ -439,6 +446,14 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index, &present_image_index, nullptr}; + VkPresentIdKHR present_id = {VK_STRUCTURE_TYPE_PRESENT_ID_KHR, nullptr, 1, &frame_id}; + + if (g_vulkan_context->SupportsPresentWait()) + { + present_info.pNext = &present_id; + PresentQueued(frame_id, present_swap_chain); + } + m_last_present_result = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info); m_last_present_done.Set(); if (m_last_present_result != VK_SUCCESS) diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h index 0249097423..243c92c2f6 100644 --- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h +++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h @@ -102,7 +102,7 @@ private: void WaitForCommandBufferCompletion(u32 command_buffer_index); void SubmitCommandBuffer(u32 command_buffer_index, VkSwapchainKHR present_swap_chain, - u32 present_image_index); + u32 present_image_index, u64 frame_id); void BeginCommandBuffer(); VkDescriptorPool CreateDescriptorPool(u32 descriptor_sizes); @@ -152,6 +152,7 @@ private: VkSwapchainKHR present_swap_chain; u32 present_image_index; u32 command_buffer_index; + u64 frame_id; }; Common::WorkQueueThread m_submit_thread; VkSemaphore m_present_semaphore = VK_NULL_HANDLE; diff --git a/Source/Core/VideoBackends/Vulkan/PresentWait.cpp b/Source/Core/VideoBackends/Vulkan/PresentWait.cpp new file mode 100644 index 0000000000..33ad7b096f --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/PresentWait.cpp @@ -0,0 +1,81 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "VideoBackends/Vulkan/PresentWait.h" + +#include +#include +#include + +#include "Common/BlockingLoop.h" + +#include "VideoBackends/Vulkan/VulkanContext.h" +#include "VideoBackends/Vulkan/VulkanLoader.h" + +#include "VideoCommon/PerformanceMetrics.h" + +#include + +namespace Vulkan +{ + +static std::thread s_present_wait_thread; +static Common::BlockingLoop s_present_wait_loop; + +static std::deque> s_present_wait_queue; + +static void PresentWaitThreadFunc() +{ + VkDevice device = g_vulkan_context->GetDevice(); + + s_present_wait_loop.Run([device]() { + if (s_present_wait_queue.empty()) + { + s_present_wait_loop.AllowSleep(); + return; + } + u64 present_id; + VkSwapchainKHR swapchain; + std::tie(present_id, swapchain) = s_present_wait_queue.back(); + + auto start = std::chrono::high_resolution_clock::now(); + + VkResult res = vkWaitForPresentKHR(device, swapchain, present_id, 100'000'000); // 100ms + + if (res == VK_TIMEOUT) + { + WARN_LOG_FMT(VIDEO, "vkWaitForPresentKHR timed out, retrying {}", present_id); + return; + } + + s_present_wait_queue.pop_back(); + + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkWaitForPresentKHR failed: "); + } + + if (res == VK_SUCCESS) + { + auto end = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + fmt::print("vkWaitForPresentKHR took {}us\n", duration.count()); + + g_perf_metrics.CountPresent(); + } + }); +} + +void StartPresentWaitThread() +{ + fmt::print("Starting PresentWaitThread"); + s_present_wait_thread = std::thread(PresentWaitThreadFunc); +} + +void PresentQueued(u64 present_id, VkSwapchainKHR swapchain) +{ + s_present_wait_queue.emplace_front(std::make_tuple(present_id, swapchain)); + s_present_wait_loop.Wakeup(); +} + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/PresentWait.h b/Source/Core/VideoBackends/Vulkan/PresentWait.h new file mode 100644 index 0000000000..399f1cb8b3 --- /dev/null +++ b/Source/Core/VideoBackends/Vulkan/PresentWait.h @@ -0,0 +1,15 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "Common/CommonTypes.h" +#include "VideoBackends/Vulkan/VulkanLoader.h" + +namespace Vulkan +{ + +void StartPresentWaitThread(); +void PresentQueued(u64 present_id, VkSwapchainKHR swapchain); + +} // namespace Vulkan diff --git a/Source/Core/VideoBackends/Vulkan/VKMain.cpp b/Source/Core/VideoBackends/Vulkan/VKMain.cpp index 2eb9b34106..f362f28838 100644 --- a/Source/Core/VideoBackends/Vulkan/VKMain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKMain.cpp @@ -11,6 +11,7 @@ #include "VideoBackends/Vulkan/CommandBufferManager.h" #include "VideoBackends/Vulkan/Constants.h" #include "VideoBackends/Vulkan/ObjectCache.h" +#include "VideoBackends/Vulkan/PresentWait.h" #include "VideoBackends/Vulkan/StateTracker.h" #include "VideoBackends/Vulkan/VKBoundingBox.h" #include "VideoBackends/Vulkan/VKGfx.h" @@ -225,6 +226,11 @@ bool VideoBackend::Initialize(const WindowSystemInfo& wsi) Shutdown(); return false; } + + if (g_vulkan_context->SupportsPresentWait()) + { + StartPresentWaitThread(); + } } if (!StateTracker::CreateInstance()) diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.h b/Source/Core/VideoBackends/Vulkan/VulkanContext.h index 1facf127d7..a183a65312 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.h +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.h @@ -139,6 +139,9 @@ public: bool SupportsPresentWait() const { return m_device_features.present_wait.presentWait; } + // Present Count is required to be non-zero and always increasing. + uint64_t NextPresentCount() { return ++m_present_count; } + VmaAllocator GetMemoryAllocator() const { return m_allocator; } #ifdef WIN32 @@ -180,6 +183,8 @@ private: bool m_supports_shader_subgroup_operations = false; std::vector m_device_extensions; + + uint64_t m_present_count = 0; }; extern std::unique_ptr g_vulkan_context; diff --git a/Source/Core/VideoCommon/PerformanceMetrics.cpp b/Source/Core/VideoCommon/PerformanceMetrics.cpp index a3d41c9872..b1aa9ebd0e 100644 --- a/Source/Core/VideoCommon/PerformanceMetrics.cpp +++ b/Source/Core/VideoCommon/PerformanceMetrics.cpp @@ -19,6 +19,7 @@ void PerformanceMetrics::Reset() { m_fps_counter.Reset(); m_vps_counter.Reset(); + m_present_counter.Reset(); m_speed_counter.Reset(); m_time_sleeping = DT::zero(); @@ -36,6 +37,11 @@ void PerformanceMetrics::CountVBlank() m_vps_counter.Count(); } +void PerformanceMetrics::CountPresent() +{ + m_present_counter.Count(); +} + void PerformanceMetrics::CountThrottleSleep(DT sleep) { std::unique_lock lock(m_time_lock); @@ -178,6 +184,7 @@ void PerformanceMetrics::DrawImGuiStats(const float backbuffer_scale) ImPlot::SetupLegend(ImPlotLocation_SouthEast, ImPlotLegendFlags_None); m_vps_counter.ImPlotPlotLines("V-Blank (ms)"); m_fps_counter.ImPlotPlotLines("Frame (ms)"); + m_present_counter.ImPlotPlotLines("Present (ms)"); ImPlot::EndPlot(); ImPlot::PopStyleVar(2); ImPlot::PopStyleColor(2); diff --git a/Source/Core/VideoCommon/PerformanceMetrics.h b/Source/Core/VideoCommon/PerformanceMetrics.h index d1f4211a46..6f5aee5516 100644 --- a/Source/Core/VideoCommon/PerformanceMetrics.h +++ b/Source/Core/VideoCommon/PerformanceMetrics.h @@ -29,6 +29,7 @@ public: void Reset(); void CountFrame(); void CountVBlank(); + void CountPresent(); void CountThrottleSleep(DT sleep); void CountPerformanceMarker(Core::System& system, s64 cyclesLate); @@ -47,6 +48,7 @@ public: private: PerformanceTracker m_fps_counter{"render_times.txt"}; PerformanceTracker m_vps_counter{"vblank_times.txt"}; + PerformanceTracker m_present_counter{std::nullopt}; PerformanceTracker m_speed_counter{std::nullopt, 1000000}; double m_graph_max_time = 0.0;