diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index f52b55ef3e..da9e9fdda5 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -190,6 +190,8 @@ if (ENABLE_VULKAN)
         renderer_vulkan/vk_blit_screen.h
         renderer_vulkan/vk_buffer_cache.cpp
         renderer_vulkan/vk_buffer_cache.h
+        renderer_vulkan/vk_command_pool.cpp
+        renderer_vulkan/vk_command_pool.h
         renderer_vulkan/vk_compute_pass.cpp
         renderer_vulkan/vk_compute_pass.h
         renderer_vulkan/vk_compute_pipeline.cpp
@@ -204,6 +206,8 @@ if (ENABLE_VULKAN)
         renderer_vulkan/vk_graphics_pipeline.h
         renderer_vulkan/vk_image.cpp
         renderer_vulkan/vk_image.h
+        renderer_vulkan/vk_master_semaphore.cpp
+        renderer_vulkan/vk_master_semaphore.h
         renderer_vulkan/vk_memory_manager.cpp
         renderer_vulkan/vk_memory_manager.h
         renderer_vulkan/vk_pipeline_cache.cpp
@@ -214,8 +218,8 @@ if (ENABLE_VULKAN)
         renderer_vulkan/vk_rasterizer.h
         renderer_vulkan/vk_renderpass_cache.cpp
         renderer_vulkan/vk_renderpass_cache.h
-        renderer_vulkan/vk_resource_manager.cpp
-        renderer_vulkan/vk_resource_manager.h
+        renderer_vulkan/vk_resource_pool.cpp
+        renderer_vulkan/vk_resource_pool.h
         renderer_vulkan/vk_sampler_cache.cpp
         renderer_vulkan/vk_sampler_cache.h
         renderer_vulkan/vk_scheduler.cpp
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index d13a66bb62..fc54ca0eff 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -91,8 +91,7 @@ private:
     std::shared_ptr<HostCounter> last;
 };
 
-template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter,
-          class QueryPool>
+template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
 class QueryCacheBase {
 public:
     explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
@@ -206,9 +205,6 @@ public:
         committed_flushes.pop_front();
     }
 
-protected:
-    std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
-
 private:
     /// Flushes a memory range to guest memory and removes it from the cache.
     void FlushAndRemoveRegion(VAddr addr, std::size_t size) {
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index 2bb8ec2b80..1a3d9720ea 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -32,10 +32,8 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
 
 QueryCache::QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
                        Tegra::MemoryManager& gpu_memory)
-    : VideoCommon::QueryCacheBase<
-          QueryCache, CachedQuery, CounterStream, HostCounter,
-          std::vector<OGLQuery>>{static_cast<VideoCore::RasterizerInterface&>(rasterizer),
-                                 maxwell3d, gpu_memory},
+    : VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter>(
+          rasterizer, maxwell3d, gpu_memory),
       gl_rasterizer{rasterizer} {}
 
 QueryCache::~QueryCache() = default;
@@ -91,6 +89,8 @@ u64 HostCounter::BlockingQuery() const {
 CachedQuery::CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr)
     : VideoCommon::CachedQueryBase<HostCounter>{cpu_addr, host_ptr}, cache{&cache}, type{type} {}
 
+CachedQuery::~CachedQuery() = default;
+
 CachedQuery::CachedQuery(CachedQuery&& rhs) noexcept
     : VideoCommon::CachedQueryBase<HostCounter>(std::move(rhs)), cache{rhs.cache}, type{rhs.type} {}
 
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index dd626b66bd..82cac51ee5 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -26,8 +26,8 @@ class RasterizerOpenGL;
 
 using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
 
-class QueryCache final : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream,
-                                                            HostCounter, std::vector<OGLQuery>> {
+class QueryCache final
+    : public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
 public:
     explicit QueryCache(RasterizerOpenGL& rasterizer, Tegra::Engines::Maxwell3D& maxwell3d,
                         Tegra::MemoryManager& gpu_memory);
@@ -41,6 +41,7 @@ public:
 
 private:
     RasterizerOpenGL& gl_rasterizer;
+    std::array<std::vector<OGLQuery>, VideoCore::NumQueryTypes> query_pools;
 };
 
 class HostCounter final : public VideoCommon::HostCounterBase<QueryCache, HostCounter> {
@@ -63,10 +64,12 @@ class CachedQuery final : public VideoCommon::CachedQueryBase<HostCounter> {
 public:
     explicit CachedQuery(QueryCache& cache, VideoCore::QueryType type, VAddr cpu_addr,
                          u8* host_ptr);
-    CachedQuery(CachedQuery&& rhs) noexcept;
-    CachedQuery(const CachedQuery&) = delete;
+    ~CachedQuery() override;
 
+    CachedQuery(CachedQuery&& rhs) noexcept;
     CachedQuery& operator=(CachedQuery&& rhs) noexcept;
+
+    CachedQuery(const CachedQuery&) = delete;
     CachedQuery& operator=(const CachedQuery&) = delete;
 
     void Flush() override;
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 0e4583986e..4b6674f28e 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -25,9 +25,9 @@
 #include "video_core/renderer_vulkan/renderer_vulkan.h"
 #include "video_core/renderer_vulkan/vk_blit_screen.h"
 #include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
 #include "video_core/renderer_vulkan/vk_memory_manager.h"
 #include "video_core/renderer_vulkan/vk_rasterizer.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_state_tracker.h"
 #include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -56,7 +56,7 @@ VkBool32 DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity,
                        VkDebugUtilsMessageTypeFlagsEXT type,
                        const VkDebugUtilsMessengerCallbackDataEXT* data,
                        [[maybe_unused]] void* user_data) {
-    const char* message{data->pMessage};
+    const char* const message{data->pMessage};
 
     if (severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
         LOG_CRITICAL(Render_Vulkan, "{}", message);
@@ -269,11 +269,11 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
         scheduler->WaitWorker();
 
         swapchain->AcquireNextImage();
-        const auto [fence, render_semaphore] = blit_screen->Draw(*framebuffer, use_accelerated);
+        const VkSemaphore render_semaphore = blit_screen->Draw(*framebuffer, use_accelerated);
 
-        scheduler->Flush(false, render_semaphore);
+        scheduler->Flush(render_semaphore);
 
-        if (swapchain->Present(render_semaphore, fence)) {
+        if (swapchain->Present(render_semaphore)) {
             blit_screen->Recreate();
         }
 
@@ -300,23 +300,21 @@ bool RendererVulkan::Init() {
 
     memory_manager = std::make_unique<VKMemoryManager>(*device);
 
-    resource_manager = std::make_unique<VKResourceManager>(*device);
-
-    const auto& framebuffer = render_window.GetFramebufferLayout();
-    swapchain = std::make_unique<VKSwapchain>(*surface, *device);
-    swapchain->Create(framebuffer.width, framebuffer.height, false);
-
     state_tracker = std::make_unique<StateTracker>(gpu);
 
-    scheduler = std::make_unique<VKScheduler>(*device, *resource_manager, *state_tracker);
+    scheduler = std::make_unique<VKScheduler>(*device, *state_tracker);
 
-    rasterizer = std::make_unique<RasterizerVulkan>(
-        render_window, gpu, gpu.MemoryManager(), cpu_memory, screen_info, *device,
-        *resource_manager, *memory_manager, *state_tracker, *scheduler);
+    const auto& framebuffer = render_window.GetFramebufferLayout();
+    swapchain = std::make_unique<VKSwapchain>(*surface, *device, *scheduler);
+    swapchain->Create(framebuffer.width, framebuffer.height, false);
 
-    blit_screen = std::make_unique<VKBlitScreen>(cpu_memory, render_window, *rasterizer, *device,
-                                                 *resource_manager, *memory_manager, *swapchain,
-                                                 *scheduler, screen_info);
+    rasterizer = std::make_unique<RasterizerVulkan>(render_window, gpu, gpu.MemoryManager(),
+                                                    cpu_memory, screen_info, *device,
+                                                    *memory_manager, *state_tracker, *scheduler);
+
+    blit_screen =
+        std::make_unique<VKBlitScreen>(cpu_memory, render_window, *rasterizer, *device,
+                                       *memory_manager, *swapchain, *scheduler, screen_info);
 
     return true;
 }
@@ -334,7 +332,6 @@ void RendererVulkan::ShutDown() {
     scheduler.reset();
     swapchain.reset();
     memory_manager.reset();
-    resource_manager.reset();
     device.reset();
 }
 
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index ddff77942b..f9de865f6e 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -30,9 +30,7 @@ namespace Vulkan {
 class StateTracker;
 class VKBlitScreen;
 class VKDevice;
-class VKFence;
 class VKMemoryManager;
-class VKResourceManager;
 class VKSwapchain;
 class VKScheduler;
 class VKImage;
@@ -82,11 +80,10 @@ private:
 
     vk::DebugCallback debug_callback;
     std::unique_ptr<VKDevice> device;
-    std::unique_ptr<VKSwapchain> swapchain;
     std::unique_ptr<VKMemoryManager> memory_manager;
-    std::unique_ptr<VKResourceManager> resource_manager;
     std::unique_ptr<StateTracker> state_tracker;
     std::unique_ptr<VKScheduler> scheduler;
+    std::unique_ptr<VKSwapchain> swapchain;
     std::unique_ptr<VKBlitScreen> blit_screen;
 };
 
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 2bea7b24d2..b5b60309e2 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -12,11 +12,9 @@
 #include "common/assert.h"
 #include "common/common_types.h"
 #include "common/math_util.h"
-
 #include "core/core.h"
 #include "core/frontend/emu_window.h"
 #include "core/memory.h"
-
 #include "video_core/gpu.h"
 #include "video_core/morton.h"
 #include "video_core/rasterizer_interface.h"
@@ -24,8 +22,8 @@
 #include "video_core/renderer_vulkan/vk_blit_screen.h"
 #include "video_core/renderer_vulkan/vk_device.h"
 #include "video_core/renderer_vulkan/vk_image.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
 #include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_shader_util.h"
 #include "video_core/renderer_vulkan/vk_swapchain.h"
@@ -213,16 +211,12 @@ struct VKBlitScreen::BufferData {
 VKBlitScreen::VKBlitScreen(Core::Memory::Memory& cpu_memory_,
                            Core::Frontend::EmuWindow& render_window_,
                            VideoCore::RasterizerInterface& rasterizer_, const VKDevice& device_,
-                           VKResourceManager& resource_manager_, VKMemoryManager& memory_manager_,
-                           VKSwapchain& swapchain_, VKScheduler& scheduler_,
-                           const VKScreenInfo& screen_info_)
-    : cpu_memory{cpu_memory_}, render_window{render_window_},
-      rasterizer{rasterizer_}, device{device_}, resource_manager{resource_manager_},
-      memory_manager{memory_manager_}, swapchain{swapchain_}, scheduler{scheduler_},
-      image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
-    watches.resize(image_count);
-    std::generate(watches.begin(), watches.end(),
-                  []() { return std::make_unique<VKFenceWatch>(); });
+                           VKMemoryManager& memory_manager_, VKSwapchain& swapchain_,
+                           VKScheduler& scheduler_, const VKScreenInfo& screen_info_)
+    : cpu_memory{cpu_memory_}, render_window{render_window_}, rasterizer{rasterizer_},
+      device{device_}, memory_manager{memory_manager_}, swapchain{swapchain_},
+      scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
+    resource_ticks.resize(image_count);
 
     CreateStaticResources();
     CreateDynamicResources();
@@ -234,15 +228,16 @@ void VKBlitScreen::Recreate() {
     CreateDynamicResources();
 }
 
-std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
-                                                     bool use_accelerated) {
+VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) {
     RefreshResources(framebuffer);
 
     // Finish any pending renderpass
     scheduler.RequestOutsideRenderPassOperationContext();
 
     const std::size_t image_index = swapchain.GetImageIndex();
-    watches[image_index]->Watch(scheduler.GetFence());
+
+    scheduler.Wait(resource_ticks[image_index]);
+    resource_ticks[image_index] = scheduler.CurrentTick();
 
     VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get();
 
@@ -345,7 +340,7 @@ std::tuple<VKFence&, VkSemaphore> VKBlitScreen::Draw(const Tegra::FramebufferCon
         cmdbuf.EndRenderPass();
     });
 
-    return {scheduler.GetFence(), *semaphores[image_index]};
+    return *semaphores[image_index];
 }
 
 void VKBlitScreen::CreateStaticResources() {
@@ -713,7 +708,7 @@ void VKBlitScreen::CreateFramebuffers() {
 
 void VKBlitScreen::ReleaseRawImages() {
     for (std::size_t i = 0; i < raw_images.size(); ++i) {
-        watches[i]->Wait();
+        scheduler.Wait(resource_ticks.at(i));
     }
     raw_images.clear();
     raw_buffer_commits.clear();
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 838d38f690..8f2839214c 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -5,10 +5,8 @@
 #pragma once
 
 #include <memory>
-#include <tuple>
 
 #include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace Core {
@@ -34,9 +32,9 @@ class RasterizerInterface;
 namespace Vulkan {
 
 struct ScreenInfo;
+
 class RasterizerVulkan;
 class VKDevice;
-class VKFence;
 class VKImage;
 class VKScheduler;
 class VKSwapchain;
@@ -46,15 +44,14 @@ public:
     explicit VKBlitScreen(Core::Memory::Memory& cpu_memory,
                           Core::Frontend::EmuWindow& render_window,
                           VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
-                          VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
-                          VKSwapchain& swapchain, VKScheduler& scheduler,
-                          const VKScreenInfo& screen_info);
+                          VKMemoryManager& memory_manager, VKSwapchain& swapchain,
+                          VKScheduler& scheduler, const VKScreenInfo& screen_info);
     ~VKBlitScreen();
 
     void Recreate();
 
-    std::tuple<VKFence&, VkSemaphore> Draw(const Tegra::FramebufferConfig& framebuffer,
-                                           bool use_accelerated);
+    [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer,
+                                   bool use_accelerated);
 
 private:
     struct BufferData;
@@ -90,7 +87,6 @@ private:
     Core::Frontend::EmuWindow& render_window;
     VideoCore::RasterizerInterface& rasterizer;
     const VKDevice& device;
-    VKResourceManager& resource_manager;
     VKMemoryManager& memory_manager;
     VKSwapchain& swapchain;
     VKScheduler& scheduler;
@@ -111,7 +107,7 @@ private:
     vk::Buffer buffer;
     VKMemoryCommit buffer_commit;
 
-    std::vector<std::unique_ptr<VKFenceWatch>> watches;
+    std::vector<u64> resource_ticks;
 
     std::vector<vk::Semaphore> semaphores;
     std::vector<std::unique_ptr<VKImage>> raw_images;
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.cpp b/src/video_core/renderer_vulkan/vk_command_pool.cpp
new file mode 100644
index 0000000000..f1abd4b1a8
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_command_pool.cpp
@@ -0,0 +1,41 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstddef>
+
+#include "video_core/renderer_vulkan/vk_command_pool.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+constexpr size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
+
+CommandPool::CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device)
+    : ResourcePool(master_semaphore, COMMAND_BUFFER_POOL_SIZE), device{device} {}
+
+CommandPool::~CommandPool() = default;
+
+void CommandPool::Allocate(size_t begin, size_t end) {
+    // Command buffers are going to be commited, recorded, executed every single usage cycle.
+    // They are also going to be reseted when commited.
+    Pool& pool = pools.emplace_back();
+    pool.handle = device.GetLogical().CreateCommandPool({
+        .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+        .pNext = nullptr,
+        .flags =
+            VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+        .queueFamilyIndex = device.GetGraphicsFamily(),
+    });
+    pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE);
+}
+
+VkCommandBuffer CommandPool::Commit() {
+    const size_t index = CommitResource();
+    const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
+    const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
+    return pools[pool_index].cmdbufs[sub_index];
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_command_pool.h b/src/video_core/renderer_vulkan/vk_command_pool.h
new file mode 100644
index 0000000000..3aee239b97
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_command_pool.h
@@ -0,0 +1,35 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstddef>
+#include <vector>
+
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+class MasterSemaphore;
+class VKDevice;
+
+class CommandPool final : public ResourcePool {
+public:
+    explicit CommandPool(MasterSemaphore& master_semaphore, const VKDevice& device);
+    virtual ~CommandPool();
+
+    void Allocate(size_t begin, size_t end) override;
+
+    VkCommandBuffer Commit();
+
+private:
+    struct Pool {
+        vk::CommandPool handle;
+        vk::CommandBuffers cmdbufs;
+    };
+
+    const VKDevice& device;
+    std::vector<Pool> pools;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 182461ed97..9637c60596 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -112,7 +112,8 @@ constexpr u8 quad_array[] = {
     0xf9, 0x00, 0x02, 0x00, 0x21, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x23, 0x00, 0x00, 0x00,
     0xf9, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4e, 0x00, 0x00, 0x00,
     0xf9, 0x00, 0x02, 0x00, 0x4c, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00,
-    0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
+    0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+};
 
 VkDescriptorSetLayoutBinding BuildQuadArrayPassDescriptorSetLayoutBinding() {
     return {
@@ -218,7 +219,8 @@ constexpr u8 uint8_pass[] = {
     0x2a, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
     0x24, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
     0xf9, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x1d, 0x00, 0x00, 0x00,
-    0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
+    0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+};
 
 // Quad indexed SPIR-V module. Generated from the "shaders/" directory.
 constexpr u8 QUAD_INDEXED_SPV[] = {
@@ -341,7 +343,8 @@ constexpr u8 QUAD_INDEXED_SPV[] = {
     0xf9, 0x00, 0x02, 0x00, 0x35, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x37, 0x00, 0x00, 0x00,
     0xf9, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, 0x00,
     0xf9, 0x00, 0x02, 0x00, 0x74, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00, 0x73, 0x00, 0x00, 0x00,
-    0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
+    0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00,
+};
 
 std::array<VkDescriptorSetLayoutBinding, 2> BuildInputOutputDescriptorSetBindings() {
     return {{
@@ -448,12 +451,12 @@ VKComputePass::VKComputePass(const VKDevice& device, VKDescriptorPool& descripto
 
 VKComputePass::~VKComputePass() = default;
 
-VkDescriptorSet VKComputePass::CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue,
-                                                   VKFence& fence) {
+VkDescriptorSet VKComputePass::CommitDescriptorSet(
+    VKUpdateDescriptorQueue& update_descriptor_queue) {
     if (!descriptor_template) {
         return nullptr;
     }
-    const auto set = descriptor_allocator->Commit(fence);
+    const VkDescriptorSet set = descriptor_allocator->Commit();
     update_descriptor_queue.Send(*descriptor_template, set);
     return set;
 }
@@ -477,7 +480,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32
 
     update_descriptor_queue.Acquire();
     update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
-    const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+    const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
 
     scheduler.RequestOutsideRenderPassOperationContext();
 
@@ -520,13 +523,13 @@ Uint8Pass::~Uint8Pass() = default;
 
 std::pair<VkBuffer, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
                                              u64 src_offset) {
-    const auto staging_size = static_cast<u32>(num_vertices * sizeof(u16));
+    const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
     auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
 
     update_descriptor_queue.Acquire();
     update_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
     update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
-    const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+    const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
 
     scheduler.RequestOutsideRenderPassOperationContext();
     scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
@@ -589,7 +592,7 @@ std::pair<VkBuffer, u64> QuadIndexedPass::Assemble(
     update_descriptor_queue.Acquire();
     update_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
     update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
-    const auto set = CommitDescriptorSet(update_descriptor_queue, scheduler.GetFence());
+    const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
 
     scheduler.RequestOutsideRenderPassOperationContext();
     scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index 230b526bcb..acc94f27eb 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -15,7 +15,6 @@
 namespace Vulkan {
 
 class VKDevice;
-class VKFence;
 class VKScheduler;
 class VKStagingBufferPool;
 class VKUpdateDescriptorQueue;
@@ -30,8 +29,7 @@ public:
     ~VKComputePass();
 
 protected:
-    VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue,
-                                        VKFence& fence);
+    VkDescriptorSet CommitDescriptorSet(VKUpdateDescriptorQueue& update_descriptor_queue);
 
     vk::DescriptorUpdateTemplateKHR descriptor_template;
     vk::PipelineLayout layout;
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index ed9d2991c2..9be72dc9b6 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -32,7 +32,7 @@ VkDescriptorSet VKComputePipeline::CommitDescriptorSet() {
     if (!descriptor_template) {
         return {};
     }
-    const auto set = descriptor_allocator.Commit(scheduler.GetFence());
+    const VkDescriptorSet set = descriptor_allocator.Commit();
     update_descriptor_queue.Send(*descriptor_template, set);
     return set;
 }
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
index ac4a0884eb..f38e089d5e 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
@@ -7,7 +7,8 @@
 #include "common/common_types.h"
 #include "video_core/renderer_vulkan/vk_descriptor_pool.h"
 #include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace Vulkan {
@@ -15,14 +16,15 @@ namespace Vulkan {
 // Prefer small grow rates to avoid saturating the descriptor pool with barely used pipelines.
 constexpr std::size_t SETS_GROW_RATE = 0x20;
 
-DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool,
-                                         VkDescriptorSetLayout layout)
-    : VKFencedPool{SETS_GROW_RATE}, descriptor_pool{descriptor_pool}, layout{layout} {}
+DescriptorAllocator::DescriptorAllocator(VKDescriptorPool& descriptor_pool_,
+                                         VkDescriptorSetLayout layout_)
+    : ResourcePool(descriptor_pool_.master_semaphore, SETS_GROW_RATE),
+      descriptor_pool{descriptor_pool_}, layout{layout_} {}
 
 DescriptorAllocator::~DescriptorAllocator() = default;
 
-VkDescriptorSet DescriptorAllocator::Commit(VKFence& fence) {
-    const std::size_t index = CommitResource(fence);
+VkDescriptorSet DescriptorAllocator::Commit() {
+    const std::size_t index = CommitResource();
     return descriptors_allocations[index / SETS_GROW_RATE][index % SETS_GROW_RATE];
 }
 
@@ -30,8 +32,9 @@ void DescriptorAllocator::Allocate(std::size_t begin, std::size_t end) {
     descriptors_allocations.push_back(descriptor_pool.AllocateDescriptors(layout, end - begin));
 }
 
-VKDescriptorPool::VKDescriptorPool(const VKDevice& device)
-    : device{device}, active_pool{AllocateNewPool()} {}
+VKDescriptorPool::VKDescriptorPool(const VKDevice& device_, VKScheduler& scheduler)
+    : device{device_}, master_semaphore{scheduler.GetMasterSemaphore()}, active_pool{
+                                                                             AllocateNewPool()} {}
 
 VKDescriptorPool::~VKDescriptorPool() = default;
 
diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.h b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
index 9efa66beff..544f32a206 100644
--- a/src/video_core/renderer_vulkan/vk_descriptor_pool.h
+++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.h
@@ -6,21 +6,24 @@
 
 #include <vector>
 
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace Vulkan {
 
+class VKDevice;
 class VKDescriptorPool;
+class VKScheduler;
 
-class DescriptorAllocator final : public VKFencedPool {
+class DescriptorAllocator final : public ResourcePool {
 public:
     explicit DescriptorAllocator(VKDescriptorPool& descriptor_pool, VkDescriptorSetLayout layout);
     ~DescriptorAllocator() override;
 
+    DescriptorAllocator& operator=(const DescriptorAllocator&) = delete;
     DescriptorAllocator(const DescriptorAllocator&) = delete;
 
-    VkDescriptorSet Commit(VKFence& fence);
+    VkDescriptorSet Commit();
 
 protected:
     void Allocate(std::size_t begin, std::size_t end) override;
@@ -36,15 +39,19 @@ class VKDescriptorPool final {
     friend DescriptorAllocator;
 
 public:
-    explicit VKDescriptorPool(const VKDevice& device);
+    explicit VKDescriptorPool(const VKDevice& device, VKScheduler& scheduler);
     ~VKDescriptorPool();
 
+    VKDescriptorPool(const VKDescriptorPool&) = delete;
+    VKDescriptorPool& operator=(const VKDescriptorPool&) = delete;
+
 private:
     vk::DescriptorPool* AllocateNewPool();
 
     vk::DescriptorSets AllocateDescriptors(VkDescriptorSetLayout layout, std::size_t count);
 
     const VKDevice& device;
+    MasterSemaphore& master_semaphore;
 
     std::vector<vk::DescriptorPool> pools;
     vk::DescriptorPool* active_pool;
diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 4205bd5734..05e31f1de1 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -42,6 +42,7 @@ constexpr std::array REQUIRED_EXTENSIONS{
     VK_KHR_8BIT_STORAGE_EXTENSION_NAME,
     VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME,
     VK_KHR_DESCRIPTOR_UPDATE_TEMPLATE_EXTENSION_NAME,
+    VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME,
     VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME,
     VK_EXT_SHADER_SUBGROUP_BALLOT_EXTENSION_NAME,
     VK_EXT_SHADER_SUBGROUP_VOTE_EXTENSION_NAME,
@@ -250,6 +251,13 @@ bool VKDevice::Create() {
         .inheritedQueries = false,
     };
 
+    VkPhysicalDeviceTimelineSemaphoreFeaturesKHR timeline_semaphore{
+        .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR,
+        .pNext = nullptr,
+        .timelineSemaphore = true,
+    };
+    SetNext(next, timeline_semaphore);
+
     VkPhysicalDevice16BitStorageFeaturesKHR bit16_storage{
         .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR,
         .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
index 55a8348fcb..5babbdd0b7 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -29,8 +29,8 @@ void InnerFence::Queue() {
     }
     ASSERT(!event);
 
-    event = device.GetLogical().CreateNewEvent();
-    ticks = scheduler.Ticks();
+    event = device.GetLogical().CreateEvent();
+    ticks = scheduler.CurrentTick();
 
     scheduler.RequestOutsideRenderPassOperationContext();
     scheduler.Record([event = *event](vk::CommandBuffer cmdbuf) {
@@ -52,7 +52,7 @@ void InnerFence::Wait() {
     }
     ASSERT(event);
 
-    if (ticks >= scheduler.Ticks()) {
+    if (ticks >= scheduler.CurrentTick()) {
         scheduler.Flush();
     }
     while (!IsEventSignalled()) {
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 2e46c62785..a4b9e7ef52 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -93,7 +93,7 @@ VkDescriptorSet VKGraphicsPipeline::CommitDescriptorSet() {
     if (!descriptor_template) {
         return {};
     }
-    const auto set = descriptor_allocator.Commit(scheduler.GetFence());
+    const VkDescriptorSet set = descriptor_allocator.Commit();
     update_descriptor_queue.Send(*descriptor_template, set);
     return set;
 }
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
new file mode 100644
index 0000000000..ae26e558d4
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp
@@ -0,0 +1,56 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <chrono>
+
+#include "core/settings.h"
+#include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+using namespace std::chrono_literals;
+
+MasterSemaphore::MasterSemaphore(const VKDevice& device) {
+    static constexpr VkSemaphoreTypeCreateInfoKHR semaphore_type_ci{
+        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO_KHR,
+        .pNext = nullptr,
+        .semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE_KHR,
+        .initialValue = 0,
+    };
+    static constexpr VkSemaphoreCreateInfo semaphore_ci{
+        .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+        .pNext = &semaphore_type_ci,
+        .flags = 0,
+    };
+    semaphore = device.GetLogical().CreateSemaphore(semaphore_ci);
+
+    if (!Settings::values.renderer_debug) {
+        return;
+    }
+    // Validation layers have a bug where they fail to track resource usage when using timeline
+    // semaphores and synchronizing with GetSemaphoreCounterValueKHR. To workaround this issue, have
+    // a separate thread waiting for each timeline semaphore value.
+    debug_thread = std::thread([this] {
+        u64 counter = 0;
+        while (!shutdown) {
+            if (semaphore.Wait(counter, 10'000'000)) {
+                ++counter;
+            }
+        }
+    });
+}
+
+MasterSemaphore::~MasterSemaphore() {
+    shutdown = true;
+
+    // This thread might not be started
+    if (debug_thread.joinable()) {
+        debug_thread.join();
+    }
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.h b/src/video_core/renderer_vulkan/vk_master_semaphore.h
new file mode 100644
index 0000000000..0e93706d76
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_master_semaphore.h
@@ -0,0 +1,70 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <thread>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/wrapper.h"
+
+namespace Vulkan {
+
+class VKDevice;
+
+class MasterSemaphore {
+public:
+    explicit MasterSemaphore(const VKDevice& device);
+    ~MasterSemaphore();
+
+    /// Returns the current logical tick.
+    [[nodiscard]] u64 CurrentTick() const noexcept {
+        return current_tick;
+    }
+
+    /// Returns the timeline semaphore handle.
+    [[nodiscard]] VkSemaphore Handle() const noexcept {
+        return *semaphore;
+    }
+
+    /// Returns true when a tick has been hit by the GPU.
+    [[nodiscard]] bool IsFree(u64 tick) {
+        return gpu_tick >= tick;
+    }
+
+    /// Advance to the logical tick.
+    void NextTick() noexcept {
+        ++current_tick;
+    }
+
+    /// Refresh the known GPU tick
+    void Refresh() {
+        gpu_tick = semaphore.GetCounter();
+    }
+
+    /// Waits for a tick to be hit on the GPU
+    void Wait(u64 tick) {
+        // No need to wait if the GPU is ahead of the tick
+        if (IsFree(tick)) {
+            return;
+        }
+        // Update the GPU tick and try again
+        Refresh();
+        if (IsFree(tick)) {
+            return;
+        }
+        // If none of the above is hit, fallback to a regular wait
+        semaphore.Wait(tick);
+    }
+
+private:
+    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::atomic<bool> shutdown{false}; ///< True when the object is being destroyed.
+    std::thread debug_thread;          ///< Debug thread to workaround validation layer bugs.
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 1a31fd9f64..e558e66586 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -38,7 +38,6 @@ class RasterizerVulkan;
 class VKComputePipeline;
 class VKDescriptorPool;
 class VKDevice;
-class VKFence;
 class VKScheduler;
 class VKUpdateDescriptorQueue;
 
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 5a97c959da..28a793f97f 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -9,35 +9,33 @@
 
 #include "video_core/renderer_vulkan/vk_device.h"
 #include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace Vulkan {
 
+using VideoCore::QueryType;
+
 namespace {
 
 constexpr std::array QUERY_TARGETS = {VK_QUERY_TYPE_OCCLUSION};
 
-constexpr VkQueryType GetTarget(VideoCore::QueryType type) {
+constexpr VkQueryType GetTarget(QueryType type) {
     return QUERY_TARGETS[static_cast<std::size_t>(type)];
 }
 
 } // Anonymous namespace
 
-QueryPool::QueryPool() : VKFencedPool{GROW_STEP} {}
+QueryPool::QueryPool(const VKDevice& device_, VKScheduler& scheduler, QueryType type_)
+    : ResourcePool{scheduler.GetMasterSemaphore(), GROW_STEP}, device{device_}, type{type_} {}
 
 QueryPool::~QueryPool() = default;
 
-void QueryPool::Initialize(const VKDevice& device_, VideoCore::QueryType type_) {
-    device = &device_;
-    type = type_;
-}
-
-std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) {
+std::pair<VkQueryPool, u32> QueryPool::Commit() {
     std::size_t index;
     do {
-        index = CommitResource(fence);
+        index = CommitResource();
     } while (usage[index]);
     usage[index] = true;
 
@@ -47,7 +45,7 @@ std::pair<VkQueryPool, u32> QueryPool::Commit(VKFence& fence) {
 void QueryPool::Allocate(std::size_t begin, std::size_t end) {
     usage.resize(end);
 
-    pools.push_back(device->GetLogical().CreateQueryPool({
+    pools.push_back(device.GetLogical().CreateQueryPool({
         .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO,
         .pNext = nullptr,
         .flags = 0,
@@ -71,28 +69,27 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
 VKQueryCache::VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
                            Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
                            const VKDevice& device, VKScheduler& scheduler)
-    : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
-                                  QueryPool>{rasterizer, maxwell3d, gpu_memory},
-      device{device}, scheduler{scheduler} {
-    for (std::size_t i = 0; i < static_cast<std::size_t>(VideoCore::NumQueryTypes); ++i) {
-        query_pools[i].Initialize(device, static_cast<VideoCore::QueryType>(i));
-    }
-}
+    : VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream,
+                                  HostCounter>{rasterizer, maxwell3d, gpu_memory},
+      device{device}, scheduler{scheduler}, query_pools{
+                                                QueryPool{device, scheduler,
+                                                          QueryType::SamplesPassed},
+                                            } {}
 
 VKQueryCache::~VKQueryCache() = default;
 
-std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(VideoCore::QueryType type) {
-    return query_pools[static_cast<std::size_t>(type)].Commit(scheduler.GetFence());
+std::pair<VkQueryPool, u32> VKQueryCache::AllocateQuery(QueryType type) {
+    return query_pools[static_cast<std::size_t>(type)].Commit();
 }
 
-void VKQueryCache::Reserve(VideoCore::QueryType type, std::pair<VkQueryPool, u32> query) {
+void VKQueryCache::Reserve(QueryType type, std::pair<VkQueryPool, u32> query) {
     query_pools[static_cast<std::size_t>(type)].Reserve(query);
 }
 
 HostCounter::HostCounter(VKQueryCache& cache, std::shared_ptr<HostCounter> dependency,
-                         VideoCore::QueryType type)
+                         QueryType type)
     : VideoCommon::HostCounterBase<VKQueryCache, HostCounter>{std::move(dependency)}, cache{cache},
-      type{type}, query{cache.AllocateQuery(type)}, ticks{cache.Scheduler().Ticks()} {
+      type{type}, query{cache.AllocateQuery(type)}, tick{cache.Scheduler().CurrentTick()} {
     const vk::Device* logical = &cache.Device().GetLogical();
     cache.Scheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) {
         logical->ResetQueryPoolEXT(query.first, query.second, 1);
@@ -110,7 +107,7 @@ void HostCounter::EndQuery() {
 }
 
 u64 HostCounter::BlockingQuery() const {
-    if (ticks >= cache.Scheduler().Ticks()) {
+    if (tick >= cache.Scheduler().CurrentTick()) {
         cache.Scheduler().Flush();
     }
     u64 data;
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index 9be996e554..2e57fb75d4 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -11,7 +11,7 @@
 
 #include "common/common_types.h"
 #include "video_core/query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace VideoCore {
@@ -28,14 +28,12 @@ class VKScheduler;
 
 using CounterStream = VideoCommon::CounterStreamBase<VKQueryCache, HostCounter>;
 
-class QueryPool final : public VKFencedPool {
+class QueryPool final : public ResourcePool {
 public:
-    explicit QueryPool();
+    explicit QueryPool(const VKDevice& device, VKScheduler& scheduler, VideoCore::QueryType type);
     ~QueryPool() override;
 
-    void Initialize(const VKDevice& device, VideoCore::QueryType type);
-
-    std::pair<VkQueryPool, u32> Commit(VKFence& fence);
+    std::pair<VkQueryPool, u32> Commit();
 
     void Reserve(std::pair<VkQueryPool, u32> query);
 
@@ -45,16 +43,15 @@ protected:
 private:
     static constexpr std::size_t GROW_STEP = 512;
 
-    const VKDevice* device = nullptr;
-    VideoCore::QueryType type = {};
+    const VKDevice& device;
+    const VideoCore::QueryType type;
 
     std::vector<vk::QueryPool> pools;
     std::vector<bool> usage;
 };
 
 class VKQueryCache final
-    : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter,
-                                         QueryPool> {
+    : public VideoCommon::QueryCacheBase<VKQueryCache, CachedQuery, CounterStream, HostCounter> {
 public:
     explicit VKQueryCache(VideoCore::RasterizerInterface& rasterizer,
                           Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
@@ -76,6 +73,7 @@ public:
 private:
     const VKDevice& device;
     VKScheduler& scheduler;
+    std::array<QueryPool, VideoCore::NumQueryTypes> query_pools;
 };
 
 class HostCounter final : public VideoCommon::HostCounterBase<VKQueryCache, HostCounter> {
@@ -92,7 +90,7 @@ private:
     VKQueryCache& cache;
     const VideoCore::QueryType type;
     const std::pair<VkQueryPool, u32> query;
-    const u64 ticks;
+    const u64 tick;
 };
 
 class CachedQuery : public VideoCommon::CachedQueryBase<HostCounter> {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index bafebe2949..f3c2483c8f 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -31,7 +31,6 @@
 #include "video_core/renderer_vulkan/vk_pipeline_cache.h"
 #include "video_core/renderer_vulkan/vk_rasterizer.h"
 #include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_sampler_cache.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -384,27 +383,25 @@ void RasterizerVulkan::DrawParameters::Draw(vk::CommandBuffer cmdbuf) const {
 RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu_,
                                    Tegra::MemoryManager& gpu_memory_,
                                    Core::Memory::Memory& cpu_memory, VKScreenInfo& screen_info_,
-                                   const VKDevice& device_, VKResourceManager& resource_manager_,
-                                   VKMemoryManager& memory_manager_, StateTracker& state_tracker_,
-                                   VKScheduler& scheduler_)
+                                   const VKDevice& device_, VKMemoryManager& memory_manager_,
+                                   StateTracker& state_tracker_, VKScheduler& scheduler_)
     : RasterizerAccelerated(cpu_memory), gpu(gpu_), gpu_memory(gpu_memory_),
       maxwell3d(gpu.Maxwell3D()), kepler_compute(gpu.KeplerCompute()), screen_info(screen_info_),
-      device(device_), resource_manager(resource_manager_), memory_manager(memory_manager_),
-      state_tracker(state_tracker_), scheduler(scheduler_),
-      staging_pool(device, memory_manager, scheduler), descriptor_pool(device),
-      update_descriptor_queue(device, scheduler), renderpass_cache(device),
+      device(device_), memory_manager(memory_manager_), state_tracker(state_tracker_),
+      scheduler(scheduler_), staging_pool(device, memory_manager, scheduler),
+      descriptor_pool(device, scheduler_), update_descriptor_queue(device, scheduler),
+      renderpass_cache(device),
       quad_array_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
       quad_indexed_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
       uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
-      texture_cache(*this, maxwell3d, gpu_memory, device, resource_manager, memory_manager,
-                    scheduler, staging_pool),
+      texture_cache(*this, maxwell3d, gpu_memory, device, memory_manager, scheduler, staging_pool),
       pipeline_cache(*this, gpu, maxwell3d, kepler_compute, gpu_memory, device, scheduler,
                      descriptor_pool, update_descriptor_queue, renderpass_cache),
       buffer_cache(*this, gpu_memory, cpu_memory, device, memory_manager, scheduler, staging_pool),
       sampler_cache(device), query_cache(*this, maxwell3d, gpu_memory, device, scheduler),
       fence_manager(*this, gpu, gpu_memory, texture_cache, buffer_cache, query_cache, device,
                     scheduler),
-      wfi_event(device.GetLogical().CreateNewEvent()), async_shaders(emu_window) {
+      wfi_event(device.GetLogical().CreateEvent()), async_shaders(emu_window) {
     scheduler.SetQueryCache(query_cache);
     if (device.UseAsynchronousShaders()) {
         async_shaders.AllocateWorkers();
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 16251d0f69..b47c8fc137 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -25,7 +25,6 @@
 #include "video_core/renderer_vulkan/vk_pipeline_cache.h"
 #include "video_core/renderer_vulkan/vk_query_cache.h"
 #include "video_core/renderer_vulkan/vk_renderpass_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_sampler_cache.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -109,8 +108,8 @@ public:
     explicit RasterizerVulkan(Core::Frontend::EmuWindow& emu_window, Tegra::GPU& gpu,
                               Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
                               VKScreenInfo& screen_info, const VKDevice& device,
-                              VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
-                              StateTracker& state_tracker, VKScheduler& scheduler);
+                              VKMemoryManager& memory_manager, StateTracker& state_tracker,
+                              VKScheduler& scheduler);
     ~RasterizerVulkan() override;
 
     void Draw(bool is_indexed, bool is_instanced) override;
@@ -286,7 +285,6 @@ private:
 
     VKScreenInfo& screen_info;
     const VKDevice& device;
-    VKResourceManager& resource_manager;
     VKMemoryManager& memory_manager;
     StateTracker& state_tracker;
     VKScheduler& scheduler;
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.cpp b/src/video_core/renderer_vulkan/vk_resource_manager.cpp
deleted file mode 100644
index f19330a361..0000000000
--- a/src/video_core/renderer_vulkan/vk_resource_manager.cpp
+++ /dev/null
@@ -1,311 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <algorithm>
-#include <optional>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-namespace {
-
-// TODO(Rodrigo): Fine tune these numbers.
-constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 0x1000;
-constexpr std::size_t FENCES_GROW_STEP = 0x40;
-
-constexpr VkFenceCreateInfo BuildFenceCreateInfo() {
-    return {
-        .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
-        .pNext = nullptr,
-        .flags = 0,
-    };
-}
-
-} // Anonymous namespace
-
-class CommandBufferPool final : public VKFencedPool {
-public:
-    explicit CommandBufferPool(const VKDevice& device)
-        : VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {}
-
-    void Allocate(std::size_t begin, std::size_t end) override {
-        // Command buffers are going to be commited, recorded, executed every single usage cycle.
-        // They are also going to be reseted when commited.
-        Pool& pool = pools.emplace_back();
-        pool.handle = device.GetLogical().CreateCommandPool({
-            .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
-            .pNext = nullptr,
-            .flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT |
-                     VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
-            .queueFamilyIndex = device.GetGraphicsFamily(),
-        });
-        pool.cmdbufs = pool.handle.Allocate(COMMAND_BUFFER_POOL_SIZE);
-    }
-
-    VkCommandBuffer Commit(VKFence& fence) {
-        const std::size_t index = CommitResource(fence);
-        const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
-        const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
-        return pools[pool_index].cmdbufs[sub_index];
-    }
-
-private:
-    struct Pool {
-        vk::CommandPool handle;
-        vk::CommandBuffers cmdbufs;
-    };
-
-    const VKDevice& device;
-    std::vector<Pool> pools;
-};
-
-VKResource::VKResource() = default;
-
-VKResource::~VKResource() = default;
-
-VKFence::VKFence(const VKDevice& device)
-    : device{device}, handle{device.GetLogical().CreateFence(BuildFenceCreateInfo())} {}
-
-VKFence::~VKFence() = default;
-
-void VKFence::Wait() {
-    switch (const VkResult result = handle.Wait()) {
-    case VK_SUCCESS:
-        return;
-    case VK_ERROR_DEVICE_LOST:
-        device.ReportLoss();
-        [[fallthrough]];
-    default:
-        throw vk::Exception(result);
-    }
-}
-
-void VKFence::Release() {
-    ASSERT(is_owned);
-    is_owned = false;
-}
-
-void VKFence::Commit() {
-    is_owned = true;
-    is_used = true;
-}
-
-bool VKFence::Tick(bool gpu_wait, bool owner_wait) {
-    if (!is_used) {
-        // If a fence is not used it's always free.
-        return true;
-    }
-    if (is_owned && !owner_wait) {
-        // The fence is still being owned (Release has not been called) and ownership wait has
-        // not been asked.
-        return false;
-    }
-
-    if (gpu_wait) {
-        // Wait for the fence if it has been requested.
-        (void)handle.Wait();
-    } else {
-        if (handle.GetStatus() != VK_SUCCESS) {
-            // Vulkan fence is not ready, not much it can do here
-            return false;
-        }
-    }
-
-    // Broadcast resources their free state.
-    for (auto* resource : protected_resources) {
-        resource->OnFenceRemoval(this);
-    }
-    protected_resources.clear();
-
-    // Prepare fence for reusage.
-    handle.Reset();
-    is_used = false;
-    return true;
-}
-
-void VKFence::Protect(VKResource* resource) {
-    protected_resources.push_back(resource);
-}
-
-void VKFence::Unprotect(VKResource* resource) {
-    const auto it = std::find(protected_resources.begin(), protected_resources.end(), resource);
-    ASSERT(it != protected_resources.end());
-
-    resource->OnFenceRemoval(this);
-    protected_resources.erase(it);
-}
-
-void VKFence::RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept {
-    std::replace(std::begin(protected_resources), std::end(protected_resources), old_resource,
-                 new_resource);
-}
-
-VKFenceWatch::VKFenceWatch() = default;
-
-VKFenceWatch::VKFenceWatch(VKFence& initial_fence) {
-    Watch(initial_fence);
-}
-
-VKFenceWatch::VKFenceWatch(VKFenceWatch&& rhs) noexcept {
-    fence = std::exchange(rhs.fence, nullptr);
-    if (fence) {
-        fence->RedirectProtection(&rhs, this);
-    }
-}
-
-VKFenceWatch& VKFenceWatch::operator=(VKFenceWatch&& rhs) noexcept {
-    fence = std::exchange(rhs.fence, nullptr);
-    if (fence) {
-        fence->RedirectProtection(&rhs, this);
-    }
-    return *this;
-}
-
-VKFenceWatch::~VKFenceWatch() {
-    if (fence) {
-        fence->Unprotect(this);
-    }
-}
-
-void VKFenceWatch::Wait() {
-    if (fence == nullptr) {
-        return;
-    }
-    fence->Wait();
-    fence->Unprotect(this);
-}
-
-void VKFenceWatch::Watch(VKFence& new_fence) {
-    Wait();
-    fence = &new_fence;
-    fence->Protect(this);
-}
-
-bool VKFenceWatch::TryWatch(VKFence& new_fence) {
-    if (fence) {
-        return false;
-    }
-    fence = &new_fence;
-    fence->Protect(this);
-    return true;
-}
-
-void VKFenceWatch::OnFenceRemoval(VKFence* signaling_fence) {
-    ASSERT_MSG(signaling_fence == fence, "Removing the wrong fence");
-    fence = nullptr;
-}
-
-VKFencedPool::VKFencedPool(std::size_t grow_step) : grow_step{grow_step} {}
-
-VKFencedPool::~VKFencedPool() = default;
-
-std::size_t VKFencedPool::CommitResource(VKFence& fence) {
-    const auto Search = [&](std::size_t begin, std::size_t end) -> std::optional<std::size_t> {
-        for (std::size_t iterator = begin; iterator < end; ++iterator) {
-            if (watches[iterator]->TryWatch(fence)) {
-                // The resource is now being watched, a free resource was successfully found.
-                return iterator;
-            }
-        }
-        return {};
-    };
-    // Try to find a free resource from the hinted position to the end.
-    auto found = Search(free_iterator, watches.size());
-    if (!found) {
-        // Search from beginning to the hinted position.
-        found = Search(0, free_iterator);
-        if (!found) {
-            // Both searches failed, the pool is full; handle it.
-            const std::size_t free_resource = ManageOverflow();
-
-            // Watch will wait for the resource to be free.
-            watches[free_resource]->Watch(fence);
-            found = free_resource;
-        }
-    }
-    // Free iterator is hinted to the resource after the one that's been commited.
-    free_iterator = (*found + 1) % watches.size();
-    return *found;
-}
-
-std::size_t VKFencedPool::ManageOverflow() {
-    const std::size_t old_capacity = watches.size();
-    Grow();
-
-    // The last entry is guaranted to be free, since it's the first element of the freshly
-    // allocated resources.
-    return old_capacity;
-}
-
-void VKFencedPool::Grow() {
-    const std::size_t old_capacity = watches.size();
-    watches.resize(old_capacity + grow_step);
-    std::generate(watches.begin() + old_capacity, watches.end(),
-                  []() { return std::make_unique<VKFenceWatch>(); });
-    Allocate(old_capacity, old_capacity + grow_step);
-}
-
-VKResourceManager::VKResourceManager(const VKDevice& device) : device{device} {
-    GrowFences(FENCES_GROW_STEP);
-    command_buffer_pool = std::make_unique<CommandBufferPool>(device);
-}
-
-VKResourceManager::~VKResourceManager() = default;
-
-VKFence& VKResourceManager::CommitFence() {
-    const auto StepFences = [&](bool gpu_wait, bool owner_wait) -> VKFence* {
-        const auto Tick = [=](auto& fence) { return fence->Tick(gpu_wait, owner_wait); };
-        const auto hinted = fences.begin() + fences_iterator;
-
-        auto it = std::find_if(hinted, fences.end(), Tick);
-        if (it == fences.end()) {
-            it = std::find_if(fences.begin(), hinted, Tick);
-            if (it == hinted) {
-                return nullptr;
-            }
-        }
-        fences_iterator = std::distance(fences.begin(), it) + 1;
-        if (fences_iterator >= fences.size())
-            fences_iterator = 0;
-
-        auto& fence = *it;
-        fence->Commit();
-        return fence.get();
-    };
-
-    VKFence* found_fence = StepFences(false, false);
-    if (!found_fence) {
-        // Try again, this time waiting.
-        found_fence = StepFences(true, false);
-
-        if (!found_fence) {
-            // Allocate new fences and try again.
-            LOG_INFO(Render_Vulkan, "Allocating new fences {} -> {}", fences.size(),
-                     fences.size() + FENCES_GROW_STEP);
-
-            GrowFences(FENCES_GROW_STEP);
-            found_fence = StepFences(true, false);
-            ASSERT(found_fence != nullptr);
-        }
-    }
-    return *found_fence;
-}
-
-VkCommandBuffer VKResourceManager::CommitCommandBuffer(VKFence& fence) {
-    return command_buffer_pool->Commit(fence);
-}
-
-void VKResourceManager::GrowFences(std::size_t new_fences_count) {
-    const std::size_t previous_size = fences.size();
-    fences.resize(previous_size + new_fences_count);
-
-    std::generate(fences.begin() + previous_size, fences.end(),
-                  [this] { return std::make_unique<VKFence>(device); });
-}
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_manager.h b/src/video_core/renderer_vulkan/vk_resource_manager.h
deleted file mode 100644
index f683d2276a..0000000000
--- a/src/video_core/renderer_vulkan/vk_resource_manager.h
+++ /dev/null
@@ -1,196 +0,0 @@
-// Copyright 2018 yuzu Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <memory>
-#include <vector>
-#include "video_core/renderer_vulkan/wrapper.h"
-
-namespace Vulkan {
-
-class VKDevice;
-class VKFence;
-class VKResourceManager;
-
-class CommandBufferPool;
-
-/// Interface for a Vulkan resource
-class VKResource {
-public:
-    explicit VKResource();
-    virtual ~VKResource();
-
-    /**
-     * Signals the object that an owning fence has been signaled.
-     * @param signaling_fence Fence that signals its usage end.
-     */
-    virtual void OnFenceRemoval(VKFence* signaling_fence) = 0;
-};
-
-/**
- * Fences take ownership of objects, protecting them from GPU-side or driver-side concurrent access.
- * They must be commited from the resource manager. Their usage flow is: commit the fence from the
- * resource manager, protect resources with it and use them, send the fence to an execution queue
- * and Wait for it if needed and then call Release. Used resources will automatically be signaled
- * when they are free to be reused.
- * @brief Protects resources for concurrent usage and signals its release.
- */
-class VKFence {
-    friend class VKResourceManager;
-
-public:
-    explicit VKFence(const VKDevice& device);
-    ~VKFence();
-
-    /**
-     * Waits for the fence to be signaled.
-     * @warning You must have ownership of the fence and it has to be previously sent to a queue to
-     * call this function.
-     */
-    void Wait();
-
-    /**
-     * Releases ownership of the fence. Pass after it has been sent to an execution queue.
-     * Unmanaged usage of the fence after the call will result in undefined behavior because it may
-     * be being used for something else.
-     */
-    void Release();
-
-    /// Protects a resource with this fence.
-    void Protect(VKResource* resource);
-
-    /// Removes protection for a resource.
-    void Unprotect(VKResource* resource);
-
-    /// Redirects one protected resource to a new address.
-    void RedirectProtection(VKResource* old_resource, VKResource* new_resource) noexcept;
-
-    /// Retreives the fence.
-    operator VkFence() const {
-        return *handle;
-    }
-
-private:
-    /// Take ownership of the fence.
-    void Commit();
-
-    /**
-     * Updates the fence status.
-     * @warning Waiting for the owner might soft lock the execution.
-     * @param gpu_wait Wait for the fence to be signaled by the driver.
-     * @param owner_wait Wait for the owner to signal its freedom.
-     * @returns True if the fence is free. Waiting for gpu and owner will always return true.
-     */
-    bool Tick(bool gpu_wait, bool owner_wait);
-
-    const VKDevice& device;                       ///< Device handler
-    vk::Fence handle;                             ///< Vulkan fence
-    std::vector<VKResource*> protected_resources; ///< List of resources protected by this fence
-    bool is_owned = false; ///< The fence has been commited but not released yet.
-    bool is_used = false;  ///< The fence has been commited but it has not been checked to be free.
-};
-
-/**
- * A fence watch is used to keep track of the usage of a fence and protect a resource or set of
- * resources without having to inherit VKResource from their handlers.
- */
-class VKFenceWatch final : public VKResource {
-public:
-    explicit VKFenceWatch();
-    VKFenceWatch(VKFence& initial_fence);
-    VKFenceWatch(VKFenceWatch&&) noexcept;
-    VKFenceWatch(const VKFenceWatch&) = delete;
-    ~VKFenceWatch() override;
-
-    VKFenceWatch& operator=(VKFenceWatch&&) noexcept;
-
-    /// Waits for the fence to be released.
-    void Wait();
-
-    /**
-     * Waits for a previous fence and watches a new one.
-     * @param new_fence New fence to wait to.
-     */
-    void Watch(VKFence& new_fence);
-
-    /**
-     * Checks if it's currently being watched and starts watching it if it's available.
-     * @returns True if a watch has started, false if it's being watched.
-     */
-    bool TryWatch(VKFence& new_fence);
-
-    void OnFenceRemoval(VKFence* signaling_fence) override;
-
-    /**
-     * Do not use it paired with Watch. Use TryWatch instead.
-     * Returns true when the watch is free.
-     */
-    bool IsUsed() const {
-        return fence != nullptr;
-    }
-
-private:
-    VKFence* fence{}; ///< Fence watching this resource. nullptr when the watch is free.
-};
-
-/**
- * Handles a pool of resources protected by fences. Manages resource overflow allocating more
- * resources.
- */
-class VKFencedPool {
-public:
-    explicit VKFencedPool(std::size_t grow_step);
-    virtual ~VKFencedPool();
-
-protected:
-    /**
-     * Commits a free resource and protects it with a fence. It may allocate new resources.
-     * @param fence Fence that protects the commited resource.
-     * @returns Index of the resource commited.
-     */
-    std::size_t CommitResource(VKFence& fence);
-
-    /// Called when a chunk of resources have to be allocated.
-    virtual void Allocate(std::size_t begin, std::size_t end) = 0;
-
-private:
-    /// Manages pool overflow allocating new resources.
-    std::size_t ManageOverflow();
-
-    /// Allocates a new page of resources.
-    void Grow();
-
-    std::size_t grow_step = 0;     ///< Number of new resources created after an overflow
-    std::size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
-    std::vector<std::unique_ptr<VKFenceWatch>> watches; ///< Set of watched resources
-};
-
-/**
- * The resource manager handles all resources that can be protected with a fence avoiding
- * driver-side or GPU-side concurrent usage. Usage is documented in VKFence.
- */
-class VKResourceManager final {
-public:
-    explicit VKResourceManager(const VKDevice& device);
-    ~VKResourceManager();
-
-    /// Commits a fence. It has to be sent to a queue and released.
-    VKFence& CommitFence();
-
-    /// Commits an unused command buffer and protects it with a fence.
-    VkCommandBuffer CommitCommandBuffer(VKFence& fence);
-
-private:
-    /// Allocates new fences.
-    void GrowFences(std::size_t new_fences_count);
-
-    const VKDevice& device;          ///< Device handler.
-    std::size_t fences_iterator = 0; ///< Index where a free fence is likely to be found.
-    std::vector<std::unique_ptr<VKFence>> fences;           ///< Pool of fences.
-    std::unique_ptr<CommandBufferPool> command_buffer_pool; ///< Pool of command buffers.
-};
-
-} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.cpp b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
new file mode 100644
index 0000000000..ee274ac598
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.cpp
@@ -0,0 +1,63 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <optional>
+
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
+#include "video_core/renderer_vulkan/vk_resource_pool.h"
+
+namespace Vulkan {
+
+ResourcePool::ResourcePool(MasterSemaphore& master_semaphore_, size_t grow_step_)
+    : master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
+
+ResourcePool::~ResourcePool() = default;
+
+size_t ResourcePool::CommitResource() {
+    // Refresh semaphore to query updated results
+    master_semaphore.Refresh();
+
+    const auto search = [this](size_t begin, size_t end) -> std::optional<size_t> {
+        for (size_t iterator = begin; iterator < end; ++iterator) {
+            if (master_semaphore.IsFree(ticks[iterator])) {
+                ticks[iterator] = master_semaphore.CurrentTick();
+                return iterator;
+            }
+        }
+        return {};
+    };
+    // Try to find a free resource from the hinted position to the end.
+    auto found = search(free_iterator, ticks.size());
+    if (!found) {
+        // Search from beginning to the hinted position.
+        found = search(0, free_iterator);
+        if (!found) {
+            // Both searches failed, the pool is full; handle it.
+            const size_t free_resource = ManageOverflow();
+
+            ticks[free_resource] = master_semaphore.CurrentTick();
+            found = free_resource;
+        }
+    }
+    // Free iterator is hinted to the resource after the one that's been commited.
+    free_iterator = (*found + 1) % ticks.size();
+    return *found;
+}
+
+size_t ResourcePool::ManageOverflow() {
+    const size_t old_capacity = ticks.size();
+    Grow();
+
+    // The last entry is guaranted to be free, since it's the first element of the freshly
+    // allocated resources.
+    return old_capacity;
+}
+
+void ResourcePool::Grow() {
+    const size_t old_capacity = ticks.size();
+    ticks.resize(old_capacity + grow_step);
+    Allocate(old_capacity, old_capacity + grow_step);
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_resource_pool.h b/src/video_core/renderer_vulkan/vk_resource_pool.h
new file mode 100644
index 0000000000..a018c7ec22
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_resource_pool.h
@@ -0,0 +1,43 @@
+// Copyright 2020 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Vulkan {
+
+class MasterSemaphore;
+
+/**
+ * Handles a pool of resources protected by fences. Manages resource overflow allocating more
+ * resources.
+ */
+class ResourcePool {
+public:
+    explicit ResourcePool(MasterSemaphore& master_semaphore, size_t grow_step);
+    virtual ~ResourcePool();
+
+protected:
+    size_t CommitResource();
+
+    /// Called when a chunk of resources have to be allocated.
+    virtual void Allocate(size_t begin, size_t end) = 0;
+
+private:
+    /// Manages pool overflow allocating new resources.
+    size_t ManageOverflow();
+
+    /// Allocates a new page of resources.
+    void Grow();
+
+    MasterSemaphore& master_semaphore;
+    size_t grow_step = 0;     ///< Number of new resources created after an overflow
+    size_t free_iterator = 0; ///< Hint to where the next free resources is likely to be found
+    std::vector<u64> ticks;   ///< Ticks for each resource
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index dbbd0961a3..1a483dc71b 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -10,9 +10,10 @@
 
 #include "common/microprofile.h"
 #include "common/thread.h"
+#include "video_core/renderer_vulkan/vk_command_pool.h"
 #include "video_core/renderer_vulkan/vk_device.h"
+#include "video_core/renderer_vulkan/vk_master_semaphore.h"
 #include "video_core/renderer_vulkan/vk_query_cache.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_state_tracker.h"
 #include "video_core/renderer_vulkan/wrapper.h"
@@ -35,10 +36,10 @@ void VKScheduler::CommandChunk::ExecuteAll(vk::CommandBuffer cmdbuf) {
     last = nullptr;
 }
 
-VKScheduler::VKScheduler(const VKDevice& device, VKResourceManager& resource_manager,
-                         StateTracker& state_tracker)
-    : device{device}, resource_manager{resource_manager}, state_tracker{state_tracker},
-      next_fence{&resource_manager.CommitFence()} {
+VKScheduler::VKScheduler(const VKDevice& device_, StateTracker& state_tracker_)
+    : device{device_}, state_tracker{state_tracker_},
+      master_semaphore{std::make_unique<MasterSemaphore>(device)},
+      command_pool{std::make_unique<CommandPool>(*master_semaphore, device)} {
     AcquireNewChunk();
     AllocateNewContext();
     worker_thread = std::thread(&VKScheduler::WorkerThread, this);
@@ -50,20 +51,27 @@ VKScheduler::~VKScheduler() {
     worker_thread.join();
 }
 
-void VKScheduler::Flush(bool release_fence, VkSemaphore semaphore) {
+u64 VKScheduler::CurrentTick() const noexcept {
+    return master_semaphore->CurrentTick();
+}
+
+bool VKScheduler::IsFree(u64 tick) const noexcept {
+    return master_semaphore->IsFree(tick);
+}
+
+void VKScheduler::Wait(u64 tick) {
+    master_semaphore->Wait(tick);
+}
+
+void VKScheduler::Flush(VkSemaphore semaphore) {
     SubmitExecution(semaphore);
-    if (release_fence) {
-        current_fence->Release();
-    }
     AllocateNewContext();
 }
 
-void VKScheduler::Finish(bool release_fence, VkSemaphore semaphore) {
+void VKScheduler::Finish(VkSemaphore semaphore) {
+    const u64 presubmit_tick = CurrentTick();
     SubmitExecution(semaphore);
-    current_fence->Wait();
-    if (release_fence) {
-        current_fence->Release();
-    }
+    Wait(presubmit_tick);
     AllocateNewContext();
 }
 
@@ -160,18 +168,38 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) {
 
     current_cmdbuf.End();
 
+    const VkSemaphore timeline_semaphore = master_semaphore->Handle();
+    const u32 num_signal_semaphores = semaphore ? 2U : 1U;
+
+    const u64 signal_value = master_semaphore->CurrentTick();
+    const u64 wait_value = signal_value - 1;
+    const VkPipelineStageFlags wait_stage_mask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
+
+    master_semaphore->NextTick();
+
+    const std::array signal_values{signal_value, u64(0)};
+    const std::array signal_semaphores{timeline_semaphore, semaphore};
+
+    const VkTimelineSemaphoreSubmitInfoKHR timeline_si{
+        .sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO_KHR,
+        .pNext = nullptr,
+        .waitSemaphoreValueCount = 1,
+        .pWaitSemaphoreValues = &wait_value,
+        .signalSemaphoreValueCount = num_signal_semaphores,
+        .pSignalSemaphoreValues = signal_values.data(),
+    };
     const VkSubmitInfo submit_info{
         .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
-        .pNext = nullptr,
-        .waitSemaphoreCount = 0,
-        .pWaitSemaphores = nullptr,
-        .pWaitDstStageMask = nullptr,
+        .pNext = &timeline_si,
+        .waitSemaphoreCount = 1,
+        .pWaitSemaphores = &timeline_semaphore,
+        .pWaitDstStageMask = &wait_stage_mask,
         .commandBufferCount = 1,
         .pCommandBuffers = current_cmdbuf.address(),
-        .signalSemaphoreCount = semaphore ? 1U : 0U,
-        .pSignalSemaphores = &semaphore,
+        .signalSemaphoreCount = num_signal_semaphores,
+        .pSignalSemaphores = signal_semaphores.data(),
     };
-    switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info, *current_fence)) {
+    switch (const VkResult result = device.GetGraphicsQueue().Submit(submit_info)) {
     case VK_SUCCESS:
         break;
     case VK_ERROR_DEVICE_LOST:
@@ -183,14 +211,9 @@ void VKScheduler::SubmitExecution(VkSemaphore semaphore) {
 }
 
 void VKScheduler::AllocateNewContext() {
-    ++ticks;
-
     std::unique_lock lock{mutex};
-    current_fence = next_fence;
-    next_fence = &resource_manager.CommitFence();
 
-    current_cmdbuf = vk::CommandBuffer(resource_manager.CommitCommandBuffer(*current_fence),
-                                       device.GetDispatchLoader());
+    current_cmdbuf = vk::CommandBuffer(command_pool->Commit(), device.GetDispatchLoader());
     current_cmdbuf.Begin({
         .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
         .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 970a65566a..7be8a19f07 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -16,42 +16,33 @@
 
 namespace Vulkan {
 
+class CommandPool;
+class MasterSemaphore;
 class StateTracker;
 class VKDevice;
-class VKFence;
 class VKQueryCache;
-class VKResourceManager;
-
-class VKFenceView {
-public:
-    VKFenceView() = default;
-    VKFenceView(VKFence* const& fence) : fence{fence} {}
-
-    VKFence* operator->() const noexcept {
-        return fence;
-    }
-
-    operator VKFence&() const noexcept {
-        return *fence;
-    }
-
-private:
-    VKFence* const& fence;
-};
 
 /// The scheduler abstracts command buffer and fence management with an interface that's able to do
 /// OpenGL-like operations on Vulkan command buffers.
 class VKScheduler {
 public:
-    explicit VKScheduler(const VKDevice& device, VKResourceManager& resource_manager,
-                         StateTracker& state_tracker);
+    explicit VKScheduler(const VKDevice& device, StateTracker& state_tracker);
     ~VKScheduler();
 
+    /// Returns the current command buffer tick.
+    [[nodiscard]] u64 CurrentTick() const noexcept;
+
+    /// Returns true when a tick has been triggered by the GPU.
+    [[nodiscard]] bool IsFree(u64 tick) const noexcept;
+
+    /// Waits for the given tick to trigger on the GPU.
+    void Wait(u64 tick);
+
     /// Sends the current execution context to the GPU.
-    void Flush(bool release_fence = true, VkSemaphore semaphore = nullptr);
+    void Flush(VkSemaphore semaphore = nullptr);
 
     /// Sends the current execution context to the GPU and waits for it to complete.
-    void Finish(bool release_fence = true, VkSemaphore semaphore = nullptr);
+    void Finish(VkSemaphore semaphore = nullptr);
 
     /// Waits for the worker thread to finish executing everything. After this function returns it's
     /// safe to touch worker resources.
@@ -86,14 +77,9 @@ public:
         (void)chunk->Record(command);
     }
 
-    /// Gets a reference to the current fence.
-    VKFenceView GetFence() const {
-        return current_fence;
-    }
-
-    /// Returns the current command buffer tick.
-    u64 Ticks() const {
-        return ticks;
+    /// Returns the master timeline semaphore.
+    [[nodiscard]] MasterSemaphore& GetMasterSemaphore() const noexcept {
+        return *master_semaphore;
     }
 
 private:
@@ -171,6 +157,13 @@ private:
         std::array<u8, 0x8000> data{};
     };
 
+    struct State {
+        VkRenderPass renderpass = nullptr;
+        VkFramebuffer framebuffer = nullptr;
+        VkExtent2D render_area = {0, 0};
+        VkPipeline graphics_pipeline = nullptr;
+    };
+
     void WorkerThread();
 
     void SubmitExecution(VkSemaphore semaphore);
@@ -186,30 +179,23 @@ private:
     void AcquireNewChunk();
 
     const VKDevice& device;
-    VKResourceManager& resource_manager;
     StateTracker& state_tracker;
 
+    std::unique_ptr<MasterSemaphore> master_semaphore;
+    std::unique_ptr<CommandPool> command_pool;
+
     VKQueryCache* query_cache = nullptr;
 
     vk::CommandBuffer current_cmdbuf;
-    VKFence* current_fence = nullptr;
-    VKFence* next_fence = nullptr;
-
-    struct State {
-        VkRenderPass renderpass = nullptr;
-        VkFramebuffer framebuffer = nullptr;
-        VkExtent2D render_area = {0, 0};
-        VkPipeline graphics_pipeline = nullptr;
-    } state;
 
     std::unique_ptr<CommandChunk> chunk;
     std::thread worker_thread;
 
+    State state;
     Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_queue;
     Common::SPSCQueue<std::unique_ptr<CommandChunk>> chunk_reserve;
     std::mutex mutex;
     std::condition_variable cv;
-    std::atomic<u64> ticks = 0;
     bool quit = false;
 };
 
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
index 5eca0ab919..2fd3b7f390 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -10,36 +10,18 @@
 #include "common/bit_util.h"
 #include "common/common_types.h"
 #include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace Vulkan {
 
-VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence,
-                                                  u64 last_epoch)
-    : buffer{std::move(buffer)}, watch{fence}, last_epoch{last_epoch} {}
+VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer_)
+    : buffer{std::move(buffer_)} {}
 
-VKStagingBufferPool::StagingBuffer::StagingBuffer(StagingBuffer&& rhs) noexcept {
-    buffer = std::move(rhs.buffer);
-    watch = std::move(rhs.watch);
-    last_epoch = rhs.last_epoch;
-}
-
-VKStagingBufferPool::StagingBuffer::~StagingBuffer() = default;
-
-VKStagingBufferPool::StagingBuffer& VKStagingBufferPool::StagingBuffer::operator=(
-    StagingBuffer&& rhs) noexcept {
-    buffer = std::move(rhs.buffer);
-    watch = std::move(rhs.watch);
-    last_epoch = rhs.last_epoch;
-    return *this;
-}
-
-VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device, VKMemoryManager& memory_manager,
-                                         VKScheduler& scheduler)
-    : device{device}, memory_manager{memory_manager}, scheduler{scheduler} {}
+VKStagingBufferPool::VKStagingBufferPool(const VKDevice& device_, VKMemoryManager& memory_manager_,
+                                         VKScheduler& scheduler_)
+    : device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_} {}
 
 VKStagingBufferPool::~VKStagingBufferPool() = default;
 
@@ -51,7 +33,6 @@ VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visib
 }
 
 void VKStagingBufferPool::TickFrame() {
-    ++epoch;
     current_delete_level = (current_delete_level + 1) % NumLevels;
 
     ReleaseCache(true);
@@ -59,11 +40,12 @@ void VKStagingBufferPool::TickFrame() {
 }
 
 VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_visible) {
-    for (auto& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) {
-        if (entry.watch.TryWatch(scheduler.GetFence())) {
-            entry.last_epoch = epoch;
-            return &*entry.buffer;
+    for (StagingBuffer& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) {
+        if (!scheduler.IsFree(entry.tick)) {
+            continue;
         }
+        entry.tick = scheduler.CurrentTick();
+        return &*entry.buffer;
     }
     return nullptr;
 }
@@ -86,8 +68,10 @@ VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_v
     });
     buffer->commit = memory_manager.Commit(buffer->handle, host_visible);
 
-    auto& entries = GetCache(host_visible)[log2].entries;
-    return *entries.emplace_back(std::move(buffer), scheduler.GetFence(), epoch).buffer;
+    std::vector<StagingBuffer>& entries = GetCache(host_visible)[log2].entries;
+    StagingBuffer& entry = entries.emplace_back(std::move(buffer));
+    entry.tick = scheduler.CurrentTick();
+    return *entry.buffer;
 }
 
 VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) {
@@ -109,9 +93,8 @@ u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t lo
     auto& entries = staging.entries;
     const std::size_t old_size = entries.size();
 
-    const auto is_deleteable = [this](const auto& entry) {
-        static constexpr u64 epochs_to_destroy = 180;
-        return entry.last_epoch + epochs_to_destroy < epoch && !entry.watch.IsUsed();
+    const auto is_deleteable = [this](const StagingBuffer& entry) {
+        return scheduler.IsFree(entry.tick);
     };
     const std::size_t begin_offset = staging.delete_index;
     const std::size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size);
diff --git a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
index 3c49014376..2dd5049acb 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -10,13 +10,11 @@
 #include "common/common_types.h"
 
 #include "video_core/renderer_vulkan/vk_memory_manager.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
 namespace Vulkan {
 
 class VKDevice;
-class VKFenceWatch;
 class VKScheduler;
 
 struct VKBuffer final {
@@ -36,16 +34,10 @@ public:
 
 private:
     struct StagingBuffer final {
-        explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer, VKFence& fence, u64 last_epoch);
-        StagingBuffer(StagingBuffer&& rhs) noexcept;
-        StagingBuffer(const StagingBuffer&) = delete;
-        ~StagingBuffer();
-
-        StagingBuffer& operator=(StagingBuffer&& rhs) noexcept;
+        explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer);
 
         std::unique_ptr<VKBuffer> buffer;
-        VKFenceWatch watch;
-        u64 last_epoch = 0;
+        u64 tick = 0;
     };
 
     struct StagingBuffers final {
@@ -73,8 +65,6 @@ private:
     StagingBuffersCache host_staging_buffers;
     StagingBuffersCache device_staging_buffers;
 
-    u64 epoch = 0;
-
     std::size_t current_delete_level = 0;
 };
 
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
index 3c9171a5e0..5218c875b6 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.cpp
@@ -11,7 +11,6 @@
 #include "common/alignment.h"
 #include "common/assert.h"
 #include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_stream_buffer.h"
 #include "video_core/renderer_vulkan/wrapper.h"
@@ -111,7 +110,7 @@ void VKStreamBuffer::Unmap(u64 size) {
     }
     auto& watch = current_watches[current_watch_cursor++];
     watch.upper_bound = offset;
-    watch.fence.Watch(scheduler.GetFence());
+    watch.tick = scheduler.CurrentTick();
 }
 
 void VKStreamBuffer::CreateBuffers(VkBufferUsageFlags usage) {
@@ -157,7 +156,7 @@ void VKStreamBuffer::WaitPendingOperations(u64 requested_upper_bound) {
     while (requested_upper_bound < wait_bound && wait_cursor < *invalidation_mark) {
         auto& watch = previous_watches[wait_cursor];
         wait_bound = watch.upper_bound;
-        watch.fence.Wait();
+        scheduler.Wait(watch.tick);
         ++wait_cursor;
     }
 }
diff --git a/src/video_core/renderer_vulkan/vk_stream_buffer.h b/src/video_core/renderer_vulkan/vk_stream_buffer.h
index 689f0d276e..5e15ad78f4 100644
--- a/src/video_core/renderer_vulkan/vk_stream_buffer.h
+++ b/src/video_core/renderer_vulkan/vk_stream_buffer.h
@@ -14,7 +14,6 @@
 namespace Vulkan {
 
 class VKDevice;
-class VKFence;
 class VKFenceWatch;
 class VKScheduler;
 
@@ -44,8 +43,8 @@ public:
     }
 
 private:
-    struct Watch final {
-        VKFenceWatch fence;
+    struct Watch {
+        u64 tick{};
         u64 upper_bound{};
     };
 
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index 6bfd2abae4..9636a7c651 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -12,7 +12,7 @@
 #include "core/core.h"
 #include "core/frontend/framebuffer_layout.h"
 #include "video_core/renderer_vulkan/vk_device.h"
-#include "video_core/renderer_vulkan/vk_resource_manager.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_swapchain.h"
 #include "video_core/renderer_vulkan/wrapper.h"
 
@@ -56,8 +56,8 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi
 
 } // Anonymous namespace
 
-VKSwapchain::VKSwapchain(VkSurfaceKHR surface, const VKDevice& device)
-    : surface{surface}, device{device} {}
+VKSwapchain::VKSwapchain(VkSurfaceKHR surface_, const VKDevice& device_, VKScheduler& scheduler_)
+    : surface{surface_}, device{device_}, scheduler{scheduler_} {}
 
 VKSwapchain::~VKSwapchain() = default;
 
@@ -75,21 +75,18 @@ void VKSwapchain::Create(u32 width, u32 height, bool srgb) {
     CreateSemaphores();
     CreateImageViews();
 
-    fences.resize(image_count, nullptr);
+    resource_ticks.clear();
+    resource_ticks.resize(image_count);
 }
 
 void VKSwapchain::AcquireNextImage() {
     device.GetLogical().AcquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(),
                                             *present_semaphores[frame_index], {}, &image_index);
 
-    if (auto& fence = fences[image_index]; fence) {
-        fence->Wait();
-        fence->Release();
-        fence = nullptr;
-    }
+    scheduler.Wait(resource_ticks[image_index]);
 }
 
-bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) {
+bool VKSwapchain::Present(VkSemaphore render_semaphore) {
     const VkSemaphore present_semaphore{*present_semaphores[frame_index]};
     const std::array<VkSemaphore, 2> semaphores{present_semaphore, render_semaphore};
     const auto present_queue{device.GetPresentQueue()};
@@ -123,8 +120,7 @@ bool VKSwapchain::Present(VkSemaphore render_semaphore, VKFence& fence) {
         break;
     }
 
-    ASSERT(fences[image_index] == nullptr);
-    fences[image_index] = &fence;
+    resource_ticks[image_index] = scheduler.CurrentTick();
     frame_index = (frame_index + 1) % static_cast<u32>(image_count);
     return recreated;
 }
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index a35d61345b..6b39befdf6 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -16,11 +16,11 @@ struct FramebufferLayout;
 namespace Vulkan {
 
 class VKDevice;
-class VKFence;
+class VKScheduler;
 
 class VKSwapchain {
 public:
-    explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device);
+    explicit VKSwapchain(VkSurfaceKHR surface, const VKDevice& device, VKScheduler& scheduler);
     ~VKSwapchain();
 
     /// Creates (or recreates) the swapchain with a given size.
@@ -31,7 +31,7 @@ public:
 
     /// Presents the rendered image to the swapchain. Returns true when the swapchains had to be
     /// recreated. Takes responsability for the ownership of fence.
-    bool Present(VkSemaphore render_semaphore, VKFence& fence);
+    bool Present(VkSemaphore render_semaphore);
 
     /// Returns true when the framebuffer layout has changed.
     bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
@@ -74,6 +74,7 @@ private:
 
     const VkSurfaceKHR surface;
     const VKDevice& device;
+    VKScheduler& scheduler;
 
     vk::SwapchainKHR swapchain;
 
@@ -81,7 +82,7 @@ private:
     std::vector<VkImage> images;
     std::vector<vk::ImageView> image_views;
     std::vector<vk::Framebuffer> framebuffers;
-    std::vector<VKFence*> fences;
+    std::vector<u64> resource_ticks;
     std::vector<vk::Semaphore> present_semaphores;
 
     u32 image_index{};
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 06182d909b..f2c8f2ae19 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -188,13 +188,11 @@ u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, Tegra::Texture::Swizzl
 
 } // Anonymous namespace
 
-CachedSurface::CachedSurface(const VKDevice& device, VKResourceManager& resource_manager,
-                             VKMemoryManager& memory_manager, VKScheduler& scheduler,
-                             VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr,
-                             const SurfaceParams& params)
+CachedSurface::CachedSurface(const VKDevice& device, VKMemoryManager& memory_manager,
+                             VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
+                             GPUVAddr gpu_addr, const SurfaceParams& params)
     : SurfaceBase<View>{gpu_addr, params, device.IsOptimalAstcSupported()}, device{device},
-      resource_manager{resource_manager}, memory_manager{memory_manager}, scheduler{scheduler},
-      staging_pool{staging_pool} {
+      memory_manager{memory_manager}, scheduler{scheduler}, staging_pool{staging_pool} {
     if (params.IsBuffer()) {
         buffer = CreateBuffer(device, params, host_memory_size);
         commit = memory_manager.Commit(buffer, false);
@@ -493,18 +491,17 @@ VkImageView CachedSurfaceView::GetAttachment() {
 VKTextureCache::VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
                                Tegra::Engines::Maxwell3D& maxwell3d,
                                Tegra::MemoryManager& gpu_memory, const VKDevice& device_,
-                               VKResourceManager& resource_manager_,
                                VKMemoryManager& memory_manager_, VKScheduler& scheduler_,
                                VKStagingBufferPool& staging_pool_)
     : TextureCache(rasterizer, maxwell3d, gpu_memory, device_.IsOptimalAstcSupported()),
-      device{device_}, resource_manager{resource_manager_},
-      memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{staging_pool_} {}
+      device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{
+                                                                                   staging_pool_} {}
 
 VKTextureCache::~VKTextureCache() = default;
 
 Surface VKTextureCache::CreateSurface(GPUVAddr gpu_addr, const SurfaceParams& params) {
-    return std::make_shared<CachedSurface>(device, resource_manager, memory_manager, scheduler,
-                                           staging_pool, gpu_addr, params);
+    return std::make_shared<CachedSurface>(device, memory_manager, scheduler, staging_pool,
+                                           gpu_addr, params);
 }
 
 void VKTextureCache::ImageCopy(Surface& src_surface, Surface& dst_surface,
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index e47d02c41e..39202feba2 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -23,7 +23,6 @@ namespace Vulkan {
 
 class RasterizerVulkan;
 class VKDevice;
-class VKResourceManager;
 class VKScheduler;
 class VKStagingBufferPool;
 
@@ -41,10 +40,9 @@ class CachedSurface final : public VideoCommon::SurfaceBase<View> {
     friend CachedSurfaceView;
 
 public:
-    explicit CachedSurface(const VKDevice& device, VKResourceManager& resource_manager,
-                           VKMemoryManager& memory_manager, VKScheduler& scheduler,
-                           VKStagingBufferPool& staging_pool, GPUVAddr gpu_addr,
-                           const SurfaceParams& params);
+    explicit CachedSurface(const VKDevice& device, VKMemoryManager& memory_manager,
+                           VKScheduler& scheduler, VKStagingBufferPool& staging_pool,
+                           GPUVAddr gpu_addr, const SurfaceParams& params);
     ~CachedSurface();
 
     void UploadTexture(const std::vector<u8>& staging_buffer) override;
@@ -98,7 +96,6 @@ private:
     VkImageSubresourceRange GetImageSubresourceRange() const;
 
     const VKDevice& device;
-    VKResourceManager& resource_manager;
     VKMemoryManager& memory_manager;
     VKScheduler& scheduler;
     VKStagingBufferPool& staging_pool;
@@ -198,9 +195,8 @@ class VKTextureCache final : public TextureCacheBase {
 public:
     explicit VKTextureCache(VideoCore::RasterizerInterface& rasterizer,
                             Tegra::Engines::Maxwell3D& maxwell3d, Tegra::MemoryManager& gpu_memory,
-                            const VKDevice& device, VKResourceManager& resource_manager,
-                            VKMemoryManager& memory_manager, VKScheduler& scheduler,
-                            VKStagingBufferPool& staging_pool);
+                            const VKDevice& device, VKMemoryManager& memory_manager,
+                            VKScheduler& scheduler, VKStagingBufferPool& staging_pool);
     ~VKTextureCache();
 
 private:
@@ -215,7 +211,6 @@ private:
     void BufferCopy(Surface& src_surface, Surface& dst_surface) override;
 
     const VKDevice& device;
-    VKResourceManager& resource_manager;
     VKMemoryManager& memory_manager;
     VKScheduler& scheduler;
     VKStagingBufferPool& staging_pool;
diff --git a/src/video_core/renderer_vulkan/wrapper.cpp b/src/video_core/renderer_vulkan/wrapper.cpp
index fe291a1488..1fb14e1906 100644
--- a/src/video_core/renderer_vulkan/wrapper.cpp
+++ b/src/video_core/renderer_vulkan/wrapper.cpp
@@ -148,6 +148,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
     X(vkGetFenceStatus);
     X(vkGetImageMemoryRequirements);
     X(vkGetQueryPoolResults);
+    X(vkGetSemaphoreCounterValueKHR);
     X(vkMapMemory);
     X(vkQueueSubmit);
     X(vkResetFences);
@@ -156,6 +157,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
     X(vkUpdateDescriptorSetWithTemplateKHR);
     X(vkUpdateDescriptorSets);
     X(vkWaitForFences);
+    X(vkWaitSemaphoresKHR);
 #undef X
 }
 
@@ -574,7 +576,10 @@ Semaphore Device::CreateSemaphore() const {
         .pNext = nullptr,
         .flags = 0,
     };
+    return CreateSemaphore(ci);
+}
 
+Semaphore Device::CreateSemaphore(const VkSemaphoreCreateInfo& ci) const {
     VkSemaphore object;
     Check(dld->vkCreateSemaphore(handle, &ci, nullptr, &object));
     return Semaphore(object, handle, *dld);
@@ -660,7 +665,7 @@ ShaderModule Device::CreateShaderModule(const VkShaderModuleCreateInfo& ci) cons
     return ShaderModule(object, handle, *dld);
 }
 
-Event Device::CreateNewEvent() const {
+Event Device::CreateEvent() const {
     static constexpr VkEventCreateInfo ci{
         .sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO,
         .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/wrapper.h b/src/video_core/renderer_vulkan/wrapper.h
index b9d3fedc10..234e016933 100644
--- a/src/video_core/renderer_vulkan/wrapper.h
+++ b/src/video_core/renderer_vulkan/wrapper.h
@@ -267,6 +267,7 @@ struct DeviceDispatch : public InstanceDispatch {
     PFN_vkGetFenceStatus vkGetFenceStatus;
     PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
     PFN_vkGetQueryPoolResults vkGetQueryPoolResults;
+    PFN_vkGetSemaphoreCounterValueKHR vkGetSemaphoreCounterValueKHR;
     PFN_vkMapMemory vkMapMemory;
     PFN_vkQueueSubmit vkQueueSubmit;
     PFN_vkResetFences vkResetFences;
@@ -275,6 +276,7 @@ struct DeviceDispatch : public InstanceDispatch {
     PFN_vkUpdateDescriptorSetWithTemplateKHR vkUpdateDescriptorSetWithTemplateKHR;
     PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
     PFN_vkWaitForFences vkWaitForFences;
+    PFN_vkWaitSemaphoresKHR vkWaitSemaphoresKHR;
 };
 
 /// Loads instance agnostic function pointers.
@@ -550,7 +552,6 @@ using PipelineLayout = Handle<VkPipelineLayout, VkDevice, DeviceDispatch>;
 using QueryPool = Handle<VkQueryPool, VkDevice, DeviceDispatch>;
 using RenderPass = Handle<VkRenderPass, VkDevice, DeviceDispatch>;
 using Sampler = Handle<VkSampler, VkDevice, DeviceDispatch>;
-using Semaphore = Handle<VkSemaphore, VkDevice, DeviceDispatch>;
 using ShaderModule = Handle<VkShaderModule, VkDevice, DeviceDispatch>;
 using SurfaceKHR = Handle<VkSurfaceKHR, VkInstance, InstanceDispatch>;
 
@@ -582,7 +583,8 @@ public:
     /// Construct a queue handle.
     constexpr Queue(VkQueue queue, const DeviceDispatch& dld) noexcept : queue{queue}, dld{&dld} {}
 
-    VkResult Submit(Span<VkSubmitInfo> submit_infos, VkFence fence) const noexcept {
+    VkResult Submit(Span<VkSubmitInfo> submit_infos,
+                    VkFence fence = VK_NULL_HANDLE) const noexcept {
         return dld->vkQueueSubmit(queue, submit_infos.size(), submit_infos.data(), fence);
     }
 
@@ -674,6 +676,44 @@ public:
     }
 };
 
+class Semaphore : public Handle<VkSemaphore, VkDevice, DeviceDispatch> {
+    using Handle<VkSemaphore, VkDevice, DeviceDispatch>::Handle;
+
+public:
+    [[nodiscard]] u64 GetCounter() const {
+        u64 value;
+        Check(dld->vkGetSemaphoreCounterValueKHR(owner, handle, &value));
+        return value;
+    }
+
+    /**
+     * Waits for a timeline semaphore on the host.
+     *
+     * @param value   Value to wait
+     * @param timeout Time in nanoseconds to timeout
+     * @return        True on successful wait, false on timeout
+     */
+    bool Wait(u64 value, u64 timeout = std::numeric_limits<u64>::max()) const {
+        const VkSemaphoreWaitInfoKHR wait_info{
+            .sType = VK_STRUCTURE_TYPE_SEMAPHORE_WAIT_INFO_KHR,
+            .pNext = nullptr,
+            .flags = 0,
+            .semaphoreCount = 1,
+            .pSemaphores = &handle,
+            .pValues = &value,
+        };
+        const VkResult result = dld->vkWaitSemaphoresKHR(owner, &wait_info, timeout);
+        switch (result) {
+        case VK_SUCCESS:
+            return true;
+        case VK_TIMEOUT:
+            return false;
+        default:
+            throw Exception(result);
+        }
+    }
+};
+
 class Device : public Handle<VkDevice, NoOwner, DeviceDispatch> {
     using Handle<VkDevice, NoOwner, DeviceDispatch>::Handle;
 
@@ -694,6 +734,8 @@ public:
 
     Semaphore CreateSemaphore() const;
 
+    Semaphore CreateSemaphore(const VkSemaphoreCreateInfo& ci) const;
+
     Fence CreateFence(const VkFenceCreateInfo& ci) const;
 
     DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const;
@@ -721,7 +763,7 @@ public:
 
     ShaderModule CreateShaderModule(const VkShaderModuleCreateInfo& ci) const;
 
-    Event CreateNewEvent() const;
+    Event CreateEvent() const;
 
     SwapchainKHR CreateSwapchainKHR(const VkSwapchainCreateInfoKHR& ci) const;
 
diff --git a/src/video_core/shader/async_shaders.h b/src/video_core/shader/async_shaders.h
index 7cf8d994ce..7a99e1dc56 100644
--- a/src/video_core/shader/async_shaders.h
+++ b/src/video_core/shader/async_shaders.h
@@ -9,6 +9,17 @@
 #include <shared_mutex>
 #include <thread>
 
+// This header includes both Vulkan and OpenGL headers, this has to be fixed
+// Unfortunately, including OpenGL will include Windows.h that defines macros that can cause issues.
+// Forcefully include glad early and undefine macros
+#include <glad/glad.h>
+#ifdef CreateEvent
+#undef CreateEvent
+#endif
+#ifdef CreateSemaphore
+#undef CreateSemaphore
+#endif
+
 #include "common/common_types.h"
 #include "video_core/renderer_opengl/gl_device.h"
 #include "video_core/renderer_opengl/gl_resource_manager.h"