diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props
index 851b04618d..bd3a8fbab6 100644
--- a/Source/Core/DolphinLib.props
+++ b/Source/Core/DolphinLib.props
@@ -1285,6 +1285,7 @@
+
diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp
index 27b4f0a4a0..2a4da0efe3 100644
--- a/Source/Core/VideoBackends/D3D/D3DGfx.cpp
+++ b/Source/Core/VideoBackends/D3D/D3DGfx.cpp
@@ -251,8 +251,11 @@ void Gfx::UnbindTexture(const AbstractTexture* texture)
D3D::stateman->ApplyTextures();
}
-void Gfx::Flush()
+void Gfx::Flush(FlushType flushType)
{
+ if (flushType == FlushType::FlushToWorker)
+ return;
+
D3D::context->Flush();
}
diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.h b/Source/Core/VideoBackends/D3D/D3DGfx.h
index 07c47e93af..5f638fdfd5 100644
--- a/Source/Core/VideoBackends/D3D/D3DGfx.h
+++ b/Source/Core/VideoBackends/D3D/D3DGfx.h
@@ -65,7 +65,7 @@ public:
void SetFullscreen(bool enable_fullscreen) override;
bool IsFullscreen() const override;
- void Flush() override;
+ void Flush(FlushType flushType) override;
void WaitForGPUIdle() override;
void OnConfigChanged(u32 bits) override;
diff --git a/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp
index 51441457e0..41bbba7114 100644
--- a/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp
+++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp
@@ -93,8 +93,11 @@ std::unique_ptr Gfx::CreatePipeline(const AbstractPipelineConf
return DXPipeline::Create(config, cache_data, cache_data_length);
}
-void Gfx::Flush()
+void Gfx::Flush(FlushType flushType)
{
+ if (flushType == FlushType::FlushToWorker)
+ return;
+
ExecuteCommandList(false);
}
diff --git a/Source/Core/VideoBackends/D3D12/D3D12Gfx.h b/Source/Core/VideoBackends/D3D12/D3D12Gfx.h
index 9f537e5e09..b690fe2340 100644
--- a/Source/Core/VideoBackends/D3D12/D3D12Gfx.h
+++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.h
@@ -46,7 +46,7 @@ public:
const void* cache_data = nullptr,
size_t cache_data_length = 0) override;
- void Flush() override;
+ void Flush(FlushType flushType) override;
void WaitForGPUIdle() override;
void ClearRegion(const MathUtil::Rectangle& target_rc, bool color_enable, bool alpha_enable,
diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.h b/Source/Core/VideoBackends/Metal/MTLGfx.h
index e92ae285e6..855ebf7a14 100644
--- a/Source/Core/VideoBackends/Metal/MTLGfx.h
+++ b/Source/Core/VideoBackends/Metal/MTLGfx.h
@@ -44,7 +44,7 @@ public:
const void* cache_data = nullptr,
size_t cache_data_length = 0) override;
- void Flush() override;
+ void Flush(FlushType flushType) override;
void WaitForGPUIdle() override;
void OnConfigChanged(u32 bits) override;
diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.mm b/Source/Core/VideoBackends/Metal/MTLGfx.mm
index d594117de5..c2cb856bb4 100644
--- a/Source/Core/VideoBackends/Metal/MTLGfx.mm
+++ b/Source/Core/VideoBackends/Metal/MTLGfx.mm
@@ -266,8 +266,11 @@ std::unique_ptr Metal::Gfx::CreatePipeline(const AbstractPipel
return g_object_cache->CreatePipeline(config);
}
-void Metal::Gfx::Flush()
+void Metal::Gfx::Flush(FlushType flushType)
{
+ if (flushType == FlushType::FlushToWorker)
+ return;
+
@autoreleasepool
{
g_state_tracker->FlushEncoders();
diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.cpp b/Source/Core/VideoBackends/OGL/OGLGfx.cpp
index fb773a6a2d..66e4909a56 100644
--- a/Source/Core/VideoBackends/OGL/OGLGfx.cpp
+++ b/Source/Core/VideoBackends/OGL/OGLGfx.cpp
@@ -444,8 +444,11 @@ void OGLGfx::OnConfigChanged(u32 bits)
g_sampler_cache->Clear();
}
-void OGLGfx::Flush()
+void OGLGfx::Flush(FlushType flushType)
{
+ if (flushType == FlushType::FlushToWorker)
+ return;
+
// ensure all commands are sent to the GPU.
// Otherwise the driver could batch several frames together.
glFlush();
diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.h b/Source/Core/VideoBackends/OGL/OGLGfx.h
index 1f392c7296..e94f2b5652 100644
--- a/Source/Core/VideoBackends/OGL/OGLGfx.h
+++ b/Source/Core/VideoBackends/OGL/OGLGfx.h
@@ -63,7 +63,7 @@ public:
void BeginUtilityDrawing() override;
void EndUtilityDrawing() override;
- void Flush() override;
+ void Flush(FlushType flushType) override;
void WaitForGPUIdle() override;
void OnConfigChanged(u32 bits) override;
diff --git a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
index f48a5186de..4c1882da66 100644
--- a/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
+++ b/Source/Core/VideoBackends/Vulkan/CMakeLists.txt
@@ -37,6 +37,8 @@ add_library(videovulkan
VulkanContext.h
VulkanLoader.cpp
VulkanLoader.h
+ VKScheduler.h
+ VKScheduler.cpp
)
target_link_libraries(videovulkan
diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
index 4e674c6758..633b87dd0c 100644
--- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
+++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.cpp
@@ -19,7 +19,9 @@ namespace Vulkan
{
CommandBufferManager::CommandBufferManager(VKTimelineSemaphore* semaphore,
bool use_threaded_submission)
- : m_use_threaded_submission(use_threaded_submission), m_semaphore(semaphore)
+ : m_use_threaded_submission(use_threaded_submission),
+ m_state_tracker(std::make_unique(this)), m_last_present_done(true),
+ m_semaphore(semaphore)
{
}
@@ -43,7 +45,7 @@ bool CommandBufferManager::Initialize()
if (m_use_threaded_submission && !CreateSubmitThread())
return false;
- return true;
+ return m_state_tracker->Initialize();
}
void CommandBufferManager::Shutdown()
@@ -99,13 +101,6 @@ bool CommandBufferManager::CreateCommandBuffers()
LOG_VULKAN_ERROR(res, "vkCreateFence failed: ");
return false;
}
-
- res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &resources.semaphore);
- if (res != VK_SUCCESS)
- {
- LOG_VULKAN_ERROR(res, "vkCreateSemaphore failed: ");
- return false;
- }
}
res = vkCreateSemaphore(device, &semaphore_create_info, nullptr, &m_present_semaphore);
@@ -138,9 +133,6 @@ void CommandBufferManager::DestroyCommandBuffers()
for (auto& it : resources.cleanup_resources)
it();
- if (resources.semaphore != VK_NULL_HANDLE)
- vkDestroySemaphore(device, resources.semaphore, nullptr);
-
if (resources.fence != VK_NULL_HANDLE)
vkDestroyFence(device, resources.fence, nullptr);
}
@@ -237,7 +229,6 @@ 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);
- CmdBufferResources& resources = m_command_buffers[submit.command_buffer_index];
});
return true;
@@ -271,13 +262,14 @@ void CommandBufferManager::CleanupCompletedCommandBuffers()
}
}
-void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
+void CommandBufferManager::SubmitCommandBuffer(u64 fence_counter, bool submit_on_worker_thread,
bool wait_for_completion,
VkSwapchainKHR present_swap_chain,
uint32_t present_image_index)
{
// End the current command buffer.
CmdBufferResources& resources = GetCurrentCmdBufferResources();
+ resources.fence_counter = fence_counter;
for (VkCommandBuffer command_buffer : resources.command_buffers)
{
VkResult res = vkEndCommandBuffer(command_buffer);
@@ -310,13 +302,15 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
if (present_swap_chain != VK_NULL_HANDLE)
{
m_current_frame = (m_current_frame + 1) % NUM_FRAMES_IN_FLIGHT;
+ const u64 now_completed_counter = m_semaphore->GetCompletedFenceCounter();
// Wait for all command buffers that used the descriptor pool to finish
u32 cmd_buffer_index = (m_current_cmd_buffer + 1) % NUM_COMMAND_BUFFERS;
while (cmd_buffer_index != m_current_cmd_buffer)
{
CmdBufferResources& cmd_buffer = m_command_buffers[cmd_buffer_index];
- if (cmd_buffer.frame_index == m_current_frame)
+ if (cmd_buffer.frame_index == m_current_frame && cmd_buffer.fence_counter != 0 &&
+ cmd_buffer.fence_counter > now_completed_counter)
{
m_semaphore->WaitForFenceCounter(cmd_buffer.fence_counter);
}
@@ -350,7 +344,7 @@ void CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
// Switch to next cmdbuffer.
BeginCommandBuffer();
- StateTracker::GetInstance()->InvalidateCachedState();
+ m_state_tracker->InvalidateCachedState();
}
void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
@@ -395,8 +389,9 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
- PanicAlertFmt("Failed to submit command buffer: {} ({})", VkResultToString(res),
- static_cast(res));
+ PanicAlertFmt("Failed to submit command buffer: {} ({}), semaphore used: {}, has present sc {}",
+ VkResultToString(res), static_cast(res), resources.semaphore_used,
+ present_swap_chain != VK_NULL_HANDLE);
}
m_semaphore->PushPendingFenceValue(resources.fence, resources.fence_counter);
@@ -413,28 +408,27 @@ void CommandBufferManager::SubmitCommandBuffer(u32 command_buffer_index,
&present_swap_chain,
&present_image_index,
nullptr};
-
- m_last_present_result = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &present_info);
- m_last_present_done.Set();
- if (m_last_present_result != VK_SUCCESS)
+ res = vkQueuePresentKHR(g_vulkan_context->GetPresentQueue(), &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 (m_last_present_result != VK_ERROR_OUT_OF_DATE_KHR &&
- m_last_present_result != VK_SUBOPTIMAL_KHR &&
- m_last_present_result != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
+ if (res != VK_ERROR_OUT_OF_DATE_KHR && res != VK_SUBOPTIMAL_KHR &&
+ res != VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
{
- LOG_VULKAN_ERROR(m_last_present_result, "vkQueuePresentKHR failed: ");
+ LOG_VULKAN_ERROR(res, "vkQueuePresentKHR failed: ");
}
// Don't treat VK_SUBOPTIMAL_KHR as fatal on Android. Android 10+ requires prerotation.
// See https://twitter.com/Themaister/status/1207062674011574273
#ifdef VK_USE_PLATFORM_ANDROID_KHR
- if (m_last_present_result != VK_SUBOPTIMAL_KHR)
+ if (res != VK_SUBOPTIMAL_KHR)
m_last_present_failed.Set();
#else
m_last_present_failed.Set();
#endif
}
+ m_last_present_result.store(res);
+ m_last_present_done.Set();
}
}
@@ -447,7 +441,9 @@ void CommandBufferManager::BeginCommandBuffer()
// Wait for the GPU to finish with all resources for this command buffer.
if (resources.fence_counter > m_semaphore->GetCompletedFenceCounter() &&
resources.fence_counter != 0)
+ {
m_semaphore->WaitForFenceCounter(resources.fence_counter);
+ }
CleanupCompletedCommandBuffers();
@@ -471,10 +467,9 @@ void CommandBufferManager::BeginCommandBuffer()
LOG_VULKAN_ERROR(res, "vkBeginCommandBuffer failed: ");
}
- // Reset upload command buffer state
+ // Reset command buffer state
resources.init_command_buffer_used = false;
resources.semaphore_used = false;
- resources.fence_counter = m_semaphore->BumpNextFenceCounter();
resources.frame_index = m_current_frame;
m_current_cmd_buffer = next_buffer_index;
}
@@ -514,6 +509,4 @@ void CommandBufferManager::DeferImageViewDestruction(VkImageView object)
cmd_buffer_resources.cleanup_resources.push_back(
[object]() { vkDestroyImageView(g_vulkan_context->GetDevice(), object, nullptr); });
}
-
-std::unique_ptr g_command_buffer_mgr;
} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h
index 1545b564a0..74df416ad8 100644
--- a/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h
+++ b/Source/Core/VideoBackends/Vulkan/CommandBufferManager.h
@@ -23,6 +23,7 @@
namespace Vulkan
{
+class StateTracker;
class VKTimelineSemaphore;
class CommandBufferManager
@@ -61,23 +62,24 @@ 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.
- VkSemaphore GetCurrentCommandBufferSemaphore()
+ void SetWaitSemaphoreForCurrentCommandBuffer(VkSemaphore semaphore)
{
auto& resources = m_command_buffers[m_current_cmd_buffer];
resources.semaphore_used = true;
- return resources.semaphore;
+ resources.semaphore = semaphore;
}
// Ensure that the worker thread has submitted any previous command buffers and is idle.
void WaitForSubmitWorkerThreadIdle();
- void SubmitCommandBuffer(bool submit_on_worker_thread, bool wait_for_completion,
+ void SubmitCommandBuffer(u64 fence_counter, bool submit_on_worker_thread,
+ bool wait_for_completion,
VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE,
uint32_t present_image_index = 0xFFFFFFFF);
// Was the last present submitted to the queue a failure? If so, we must recreate our swapchain.
bool CheckLastPresentFail() { return m_last_present_failed.TestAndClear(); }
- VkResult GetLastPresentResult() const { return m_last_present_result; }
+ VkResult GetLastPresentResult() const { return m_last_present_result.load(); }
bool CheckLastPresentDone() { return m_last_present_done.TestAndClear(); }
// Schedule a vulkan resource for destruction later on. This will occur when the command buffer
@@ -88,6 +90,8 @@ public:
void DeferImageDestruction(VkImage object, VmaAllocation alloc);
void DeferImageViewDestruction(VkImageView object);
+ StateTracker* GetStateTracker() { return m_state_tracker.get(); }
+
private:
bool CreateCommandBuffers();
void DestroyCommandBuffers();
@@ -137,6 +141,10 @@ private:
u32 m_current_frame = 0;
u32 m_current_cmd_buffer = 0;
+ bool m_use_threaded_submission = false;
+
+ std::unique_ptr m_state_tracker;
+
// Threaded command buffer execution
struct PendingCommandBufferSubmit
{
@@ -148,13 +156,10 @@ private:
VkSemaphore m_present_semaphore = VK_NULL_HANDLE;
Common::Flag m_last_present_failed;
Common::Flag m_last_present_done;
- VkResult m_last_present_result = VK_SUCCESS;
- bool m_use_threaded_submission = false;
+ std::atomic m_last_present_result = VK_SUCCESS;
u32 m_descriptor_set_count = DESCRIPTOR_SETS_PER_POOL;
VKTimelineSemaphore* m_semaphore;
};
-extern std::unique_ptr g_command_buffer_mgr;
-
} // namespace Vulkan
diff --git a/Source/Core/VideoBackends/Vulkan/Constants.h b/Source/Core/VideoBackends/Vulkan/Constants.h
index 90271c60bd..99e3e5d3ad 100644
--- a/Source/Core/VideoBackends/Vulkan/Constants.h
+++ b/Source/Core/VideoBackends/Vulkan/Constants.h
@@ -12,7 +12,7 @@
namespace Vulkan
{
// Number of command buffers.
-constexpr size_t NUM_COMMAND_BUFFERS = 8;
+constexpr size_t NUM_COMMAND_BUFFERS = 16;
// Number of frames in flight, will be used to decide how many descriptor pools are used
constexpr size_t NUM_FRAMES_IN_FLIGHT = 2;
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
index c32365f3fc..b9fc9dadd0 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.cpp
@@ -420,6 +420,8 @@ VkRenderPass ObjectCache::GetRenderPass(VkFormat color_format, VkFormat depth_fo
u32 multisamples, VkAttachmentLoadOp load_op,
u8 additional_attachment_count)
{
+ std::scoped_lock lock(m_render_pass_mutex);
+
auto key =
std::tie(color_format, depth_format, multisamples, load_op, additional_attachment_count);
auto it = m_render_pass_cache.find(key);
@@ -502,6 +504,8 @@ VkRenderPass ObjectCache::GetRenderPass(VkFormat color_format, VkFormat depth_fo
void ObjectCache::DestroyRenderPassCache()
{
+ std::scoped_lock lock(m_render_pass_mutex);
+
for (auto& it : m_render_pass_cache)
vkDestroyRenderPass(g_vulkan_context->GetDevice(), it.second, nullptr);
m_render_pass_cache.clear();
diff --git a/Source/Core/VideoBackends/Vulkan/ObjectCache.h b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
index c3346ba783..ea146af69e 100644
--- a/Source/Core/VideoBackends/Vulkan/ObjectCache.h
+++ b/Source/Core/VideoBackends/Vulkan/ObjectCache.h
@@ -7,6 +7,7 @@
#include
#include