diff --git a/README.md b/README.md
index 807b42145..64b93f5c1 100755
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
 yuzu emulator early access
 =============
 
-This is the source code for early-access 3599.
+This is the source code for early-access 3600.
 
 ## Legal Notice
 
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
index ff6b0bd5c..0ddf71abc 100755
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -10,11 +10,16 @@
 
 namespace Vulkan {
 
+constexpr u64 FENCE_RESERVE_SIZE = 8;
+
 MasterSemaphore::MasterSemaphore(const Device& device_) : device(device_) {
     if (!device.HasTimelineSemaphore()) {
         static constexpr VkFenceCreateInfo fence_ci{
             .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0};
-        fence = device.GetLogical().CreateFence(fence_ci);
+        free_queue.resize(FENCE_RESERVE_SIZE);
+        std::ranges::generate(free_queue,
+                              [&] { return device.GetLogical().CreateFence(fence_ci); });
+        wait_thread = std::jthread([this](std::stop_token token) { WaitThread(token); });
         return;
     }
 
@@ -167,16 +172,53 @@ VkResult MasterSemaphore::SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphor
         .pSignalSemaphores = &signal_semaphore,
     };
 
+    auto fence = GetFreeFence();
     auto result = device.GetGraphicsQueue().Submit(submit_info, *fence);
 
     if (result == VK_SUCCESS) {
-        fence.Wait();
-        fence.Reset();
-        gpu_tick.store(host_tick);
-        gpu_tick.notify_all();
+        std::scoped_lock lock{wait_mutex};
+        wait_queue.emplace(host_tick, std::move(fence));
+        wait_cv.notify_one();
     }
 
     return result;
 }
 
+void MasterSemaphore::WaitThread(std::stop_token token) {
+    while (!token.stop_requested()) {
+        u64 host_tick;
+        vk::Fence fence;
+        {
+            std::unique_lock lock{wait_mutex};
+            Common::CondvarWait(wait_cv, lock, token, [this] { return !wait_queue.empty(); });
+            if (token.stop_requested()) {
+                return;
+            }
+            std::tie(host_tick, fence) = std::move(wait_queue.front());
+            wait_queue.pop();
+        }
+
+        fence.Wait();
+        fence.Reset();
+        gpu_tick.store(host_tick);
+        gpu_tick.notify_all();
+
+        std::scoped_lock lock{free_mutex};
+        free_queue.push_front(std::move(fence));
+    }
+}
+
+vk::Fence MasterSemaphore::GetFreeFence() {
+    std::scoped_lock lock{free_mutex};
+    if (free_queue.empty()) {
+        static constexpr VkFenceCreateInfo fence_ci{
+            .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .pNext = nullptr, .flags = 0};
+        return device.GetLogical().CreateFence(fence_ci);
+    }
+
+    auto fence = std::move(free_queue.back());
+    free_queue.pop_back();
+    return fence;
+}
+
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
index 93d99d270..93542a3b4 100755
--- a/src/video_core/renderer_vulkan/vk_master_semaphore.h
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -5,8 +5,10 @@
 
 #include <atomic>
 #include <condition_variable>
+#include <deque>
 #include <mutex>
 #include <thread>
+#include <queue>
 
 #include "common/common_types.h"
 #include "common/polyfill_thread.h"
@@ -17,6 +19,8 @@ namespace Vulkan {
 class Device;
 
 class MasterSemaphore {
+    using Waitable = std::pair<u64, vk::Fence>;
+
 public:
     explicit MasterSemaphore(const Device& device);
     ~MasterSemaphore();
@@ -57,13 +61,22 @@ private:
     VkResult SubmitQueueFence(vk::CommandBuffer& cmdbuf, VkSemaphore signal_semaphore,
                               VkSemaphore wait_semaphore, u64 host_tick);
 
+    void WaitThread(std::stop_token token);
+
+    vk::Fence GetFreeFence();
+
 private:
     const Device& device;             ///< Device.
-    vk::Fence fence;                  ///< Fence.
     vk::Semaphore semaphore;          ///< Timeline semaphore.
     std::atomic<u64> gpu_tick{0};     ///< Current known GPU tick.
     std::atomic<u64> current_tick{1}; ///< Current logical tick.
+    std::mutex wait_mutex;
+    std::mutex free_mutex;
+    std::condition_variable_any wait_cv;
+    std::queue<Waitable> wait_queue;  ///< Queue for the fences to be waited on by the wait thread.
+    std::deque<vk::Fence> free_queue; ///< Holds available fences for submission.
     std::jthread debug_thread;        ///< Debug thread to workaround validation layer bugs.
+    std::jthread wait_thread;         ///< Helper thread that waits for submitted fences.
 };
 
 } // namespace Vulkan