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;