diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 5e184eb422..d8261526a6 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -150,8 +150,8 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool
     SetUniformData(data, framebuffer);
     SetVertexData(data, framebuffer);
 
-    auto map = buffer_commit->Map();
-    std::memcpy(map.Address(), &data, sizeof(data));
+    const std::span<u8> map = buffer_commit.Map();
+    std::memcpy(map.data(), &data, sizeof(data));
 
     if (!use_accelerated) {
         const u64 image_offset = GetRawImageOffset(framebuffer, image_index);
@@ -165,8 +165,8 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool
         constexpr u32 block_height_log2 = 4;
         const u32 bytes_per_pixel = GetBytesPerPixel(framebuffer);
         Tegra::Texture::UnswizzleTexture(
-            std::span(map.Address() + image_offset, size_bytes), std::span(host_ptr, size_bytes),
-            bytes_per_pixel, framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
+            map.subspan(image_offset, size_bytes), std::span(host_ptr, size_bytes), bytes_per_pixel,
+            framebuffer.width, framebuffer.height, 1, block_height_log2, 0);
 
         const VkBufferImageCopy copy{
             .bufferOffset = image_offset,
@@ -224,8 +224,6 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool
                                        VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
             });
     }
-    map.Release();
-
     scheduler.Record([renderpass = *renderpass, framebuffer = *framebuffers[image_index],
                       descriptor_set = descriptor_sets[image_index], buffer = *buffer,
                       size = swapchain.GetSize(), pipeline = *pipeline,
@@ -642,7 +640,7 @@ void VKBlitScreen::ReleaseRawImages() {
     raw_images.clear();
     raw_buffer_commits.clear();
     buffer.reset();
-    buffer_commit.reset();
+    buffer_commit = MemoryCommit{};
 }
 
 void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 69ed617700..1aa8e31823 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -104,14 +104,14 @@ private:
     vk::Sampler sampler;
 
     vk::Buffer buffer;
-    VKMemoryCommit buffer_commit;
+    MemoryCommit buffer_commit;
 
     std::vector<u64> resource_ticks;
 
     std::vector<vk::Semaphore> semaphores;
     std::vector<vk::Image> raw_images;
     std::vector<vk::ImageView> raw_image_views;
-    std::vector<VKMemoryCommit> raw_buffer_commits;
+    std::vector<MemoryCommit> raw_buffer_commits;
     u32 raw_width = 0;
     u32 raw_height = 0;
 };
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 58c7103448..94c2e101b2 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -37,10 +37,10 @@ constexpr VkAccessFlags TRANSFORM_FEEDBACK_WRITE_ACCESS =
 } // Anonymous namespace
 
 Buffer::Buffer(const Device& device_, VKMemoryManager& memory_manager, VKScheduler& scheduler_,
-               VKStagingBufferPool& staging_pool_, VAddr cpu_addr_, std::size_t size_)
+               StagingBufferPool& staging_pool_, VAddr cpu_addr_, std::size_t size_)
     : BufferBlock{cpu_addr_, size_}, device{device_}, scheduler{scheduler_}, staging_pool{
                                                                                  staging_pool_} {
-    const VkBufferCreateInfo ci{
+    buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{
         .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
         .pNext = nullptr,
         .flags = 0,
@@ -49,22 +49,20 @@ Buffer::Buffer(const Device& device_, VKMemoryManager& memory_manager, VKSchedul
         .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
         .queueFamilyIndexCount = 0,
         .pQueueFamilyIndices = nullptr,
-    };
-
-    buffer.handle = device.GetLogical().CreateBuffer(ci);
-    buffer.commit = memory_manager.Commit(buffer.handle, false);
+    });
+    commit = memory_manager.Commit(buffer, false);
 }
 
 Buffer::~Buffer() = default;
 
 void Buffer::Upload(std::size_t offset, std::size_t data_size, const u8* data) {
-    const auto& staging = staging_pool.GetUnusedBuffer(data_size, true);
-    std::memcpy(staging.commit->Map(data_size), data, data_size);
+    const auto& staging = staging_pool.Request(data_size, true);
+    std::memcpy(staging.mapped_span.data(), data, data_size);
 
     scheduler.RequestOutsideRenderPassOperationContext();
 
     const VkBuffer handle = Handle();
-    scheduler.Record([staging = *staging.handle, handle, offset, data_size,
+    scheduler.Record([staging = staging.buffer, handle, offset, data_size,
                       &device = device](vk::CommandBuffer cmdbuf) {
         const VkBufferMemoryBarrier read_barrier{
             .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
@@ -100,12 +98,12 @@ void Buffer::Upload(std::size_t offset, std::size_t data_size, const u8* data) {
 }
 
 void Buffer::Download(std::size_t offset, std::size_t data_size, u8* data) {
-    const auto& staging = staging_pool.GetUnusedBuffer(data_size, true);
+    auto staging = staging_pool.Request(data_size, true);
     scheduler.RequestOutsideRenderPassOperationContext();
 
     const VkBuffer handle = Handle();
     scheduler.Record(
-        [staging = *staging.handle, handle, offset, data_size](vk::CommandBuffer cmdbuf) {
+        [staging = staging.buffer, handle, offset, data_size](vk::CommandBuffer cmdbuf) {
             const VkBufferMemoryBarrier barrier{
                 .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
                 .pNext = nullptr,
@@ -126,7 +124,7 @@ void Buffer::Download(std::size_t offset, std::size_t data_size, u8* data) {
         });
     scheduler.Finish();
 
-    std::memcpy(data, staging.commit->Map(data_size), data_size);
+    std::memcpy(data, staging.mapped_span.data(), data_size);
 }
 
 void Buffer::CopyFrom(const Buffer& src, std::size_t src_offset, std::size_t dst_offset,
@@ -166,7 +164,7 @@ VKBufferCache::VKBufferCache(VideoCore::RasterizerInterface& rasterizer_,
                              Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
                              const Device& device_, VKMemoryManager& memory_manager_,
                              VKScheduler& scheduler_, VKStreamBuffer& stream_buffer_,
-                             VKStagingBufferPool& staging_pool_)
+                             StagingBufferPool& staging_pool_)
     : VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer>{rasterizer_, gpu_memory_,
                                                                  cpu_memory_, stream_buffer_},
       device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_}, staging_pool{
@@ -181,12 +179,12 @@ std::shared_ptr<Buffer> VKBufferCache::CreateBlock(VAddr cpu_addr, std::size_t s
 
 VKBufferCache::BufferInfo VKBufferCache::GetEmptyBuffer(std::size_t size) {
     size = std::max(size, std::size_t(4));
-    const auto& empty = staging_pool.GetUnusedBuffer(size, false);
+    const auto& empty = staging_pool.Request(size, false);
     scheduler.RequestOutsideRenderPassOperationContext();
-    scheduler.Record([size, buffer = *empty.handle](vk::CommandBuffer cmdbuf) {
+    scheduler.Record([size, buffer = empty.buffer](vk::CommandBuffer cmdbuf) {
         cmdbuf.FillBuffer(buffer, 0, size, 0);
     });
-    return {*empty.handle, 0, 0};
+    return {empty.buffer, 0, 0};
 }
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 1c39aed345..e54c107f21 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -10,19 +10,19 @@
 #include "video_core/buffer_cache/buffer_cache.h"
 #include "video_core/renderer_vulkan/vk_memory_manager.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
+#include "video_core/renderer_vulkan/vk_memory_manager.h"
 #include "video_core/renderer_vulkan/vk_stream_buffer.h"
 #include "video_core/vulkan_common/vulkan_wrapper.h"
 
 namespace Vulkan {
 
 class Device;
-class VKMemoryManager;
 class VKScheduler;
 
 class Buffer final : public VideoCommon::BufferBlock {
 public:
     explicit Buffer(const Device& device, VKMemoryManager& memory_manager, VKScheduler& scheduler,
-                    VKStagingBufferPool& staging_pool, VAddr cpu_addr_, std::size_t size_);
+                    StagingBufferPool& staging_pool, VAddr cpu_addr_, std::size_t size_);
     ~Buffer();
 
     void Upload(std::size_t offset, std::size_t data_size, const u8* data);
@@ -33,7 +33,7 @@ public:
                   std::size_t copy_size);
 
     VkBuffer Handle() const {
-        return *buffer.handle;
+        return *buffer;
     }
 
     u64 Address() const {
@@ -43,9 +43,10 @@ public:
 private:
     const Device& device;
     VKScheduler& scheduler;
-    VKStagingBufferPool& staging_pool;
+    StagingBufferPool& staging_pool;
 
-    VKBuffer buffer;
+    vk::Buffer buffer;
+    MemoryCommit commit;
 };
 
 class VKBufferCache final : public VideoCommon::BufferCache<Buffer, VkBuffer, VKStreamBuffer> {
@@ -54,7 +55,7 @@ public:
                            Tegra::MemoryManager& gpu_memory, Core::Memory::Memory& cpu_memory,
                            const Device& device, VKMemoryManager& memory_manager,
                            VKScheduler& scheduler, VKStreamBuffer& stream_buffer,
-                           VKStagingBufferPool& staging_pool);
+                           StagingBufferPool& staging_pool);
     ~VKBufferCache();
 
     BufferInfo GetEmptyBuffer(std::size_t size) override;
@@ -66,7 +67,7 @@ private:
     const Device& device;
     VKMemoryManager& memory_manager;
     VKScheduler& scheduler;
-    VKStagingBufferPool& staging_pool;
+    StagingBufferPool& staging_pool;
 };
 
 } // 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 02a6d54b70..d38087f41c 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -164,7 +164,7 @@ VkDescriptorSet VKComputePass::CommitDescriptorSet(
 
 QuadArrayPass::QuadArrayPass(const Device& device_, VKScheduler& scheduler_,
                              VKDescriptorPool& descriptor_pool_,
-                             VKStagingBufferPool& staging_buffer_pool_,
+                             StagingBufferPool& staging_buffer_pool_,
                              VKUpdateDescriptorQueue& update_descriptor_queue_)
     : VKComputePass(device_, descriptor_pool_, BuildQuadArrayPassDescriptorSetLayoutBinding(),
                     BuildQuadArrayPassDescriptorUpdateTemplateEntry(),
@@ -177,18 +177,18 @@ QuadArrayPass::~QuadArrayPass() = default;
 std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32 first) {
     const u32 num_triangle_vertices = (num_vertices / 4) * 6;
     const std::size_t staging_size = num_triangle_vertices * sizeof(u32);
-    auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
+    const auto staging_ref = staging_buffer_pool.Request(staging_size, false);
 
     update_descriptor_queue.Acquire();
-    update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
+    update_descriptor_queue.AddBuffer(staging_ref.buffer, 0, staging_size);
     const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
 
     scheduler.RequestOutsideRenderPassOperationContext();
 
     ASSERT(num_vertices % 4 == 0);
     const u32 num_quads = num_vertices / 4;
-    scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, num_quads,
-                      first, set](vk::CommandBuffer cmdbuf) {
+    scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging_ref.buffer,
+                      num_quads, first, set](vk::CommandBuffer cmdbuf) {
         constexpr u32 dispatch_size = 1024;
         cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
         cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, layout, 0, set, {});
@@ -208,11 +208,11 @@ std::pair<VkBuffer, VkDeviceSize> QuadArrayPass::Assemble(u32 num_vertices, u32
         cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                                VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, {barrier}, {});
     });
-    return {*buffer.handle, 0};
+    return {staging_ref.buffer, 0};
 }
 
 Uint8Pass::Uint8Pass(const Device& device, VKScheduler& scheduler_,
-                     VKDescriptorPool& descriptor_pool, VKStagingBufferPool& staging_buffer_pool_,
+                     VKDescriptorPool& descriptor_pool, StagingBufferPool& staging_buffer_pool_,
                      VKUpdateDescriptorQueue& update_descriptor_queue_)
     : VKComputePass(device, descriptor_pool, BuildInputOutputDescriptorSetBindings(),
                     BuildInputOutputDescriptorUpdateTemplate(), {}, VULKAN_UINT8_COMP_SPV),
@@ -224,15 +224,15 @@ Uint8Pass::~Uint8Pass() = default;
 std::pair<VkBuffer, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buffer,
                                              u64 src_offset) {
     const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
-    auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
+    const auto staging_ref = staging_buffer_pool.Request(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);
+    update_descriptor_queue.AddBuffer(staging_ref.buffer, 0, staging_size);
     const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
 
     scheduler.RequestOutsideRenderPassOperationContext();
-    scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
+    scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging_ref.buffer, set,
                       num_vertices](vk::CommandBuffer cmdbuf) {
         constexpr u32 dispatch_size = 1024;
         cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
@@ -252,12 +252,12 @@ std::pair<VkBuffer, u64> Uint8Pass::Assemble(u32 num_vertices, VkBuffer src_buff
         cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                                VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {});
     });
-    return {*buffer.handle, 0};
+    return {staging_ref.buffer, 0};
 }
 
 QuadIndexedPass::QuadIndexedPass(const Device& device_, VKScheduler& scheduler_,
                                  VKDescriptorPool& descriptor_pool_,
-                                 VKStagingBufferPool& staging_buffer_pool_,
+                                 StagingBufferPool& staging_buffer_pool_,
                                  VKUpdateDescriptorQueue& update_descriptor_queue_)
     : VKComputePass(device_, descriptor_pool_, BuildInputOutputDescriptorSetBindings(),
                     BuildInputOutputDescriptorUpdateTemplate(),
@@ -286,15 +286,15 @@ std::pair<VkBuffer, u64> QuadIndexedPass::Assemble(
     const u32 num_tri_vertices = (num_vertices / 4) * 6;
 
     const std::size_t staging_size = num_tri_vertices * sizeof(u32);
-    auto& buffer = staging_buffer_pool.GetUnusedBuffer(staging_size, false);
+    const auto staging_ref = staging_buffer_pool.Request(staging_size, false);
 
     update_descriptor_queue.Acquire();
     update_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
-    update_descriptor_queue.AddBuffer(*buffer.handle, 0, staging_size);
+    update_descriptor_queue.AddBuffer(staging_ref.buffer, 0, staging_size);
     const VkDescriptorSet set = CommitDescriptorSet(update_descriptor_queue);
 
     scheduler.RequestOutsideRenderPassOperationContext();
-    scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = *buffer.handle, set,
+    scheduler.Record([layout = *layout, pipeline = *pipeline, buffer = staging_ref.buffer, set,
                       num_tri_vertices, base_vertex, index_shift](vk::CommandBuffer cmdbuf) {
         static constexpr u32 dispatch_size = 1024;
         const std::array push_constants = {base_vertex, index_shift};
@@ -317,7 +317,7 @@ std::pair<VkBuffer, u64> QuadIndexedPass::Assemble(
         cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
                                VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, {}, barrier, {});
     });
-    return {*buffer.handle, 0};
+    return {staging_ref.buffer, 0};
 }
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index 7ddb09afb1..f4e4432a7b 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -16,8 +16,8 @@
 namespace Vulkan {
 
 class Device;
+class StagingBufferPool;
 class VKScheduler;
-class VKStagingBufferPool;
 class VKUpdateDescriptorQueue;
 
 class VKComputePass {
@@ -45,7 +45,7 @@ class QuadArrayPass final : public VKComputePass {
 public:
     explicit QuadArrayPass(const Device& device_, VKScheduler& scheduler_,
                            VKDescriptorPool& descriptor_pool_,
-                           VKStagingBufferPool& staging_buffer_pool_,
+                           StagingBufferPool& staging_buffer_pool_,
                            VKUpdateDescriptorQueue& update_descriptor_queue_);
     ~QuadArrayPass();
 
@@ -53,7 +53,7 @@ public:
 
 private:
     VKScheduler& scheduler;
-    VKStagingBufferPool& staging_buffer_pool;
+    StagingBufferPool& staging_buffer_pool;
     VKUpdateDescriptorQueue& update_descriptor_queue;
 };
 
@@ -61,7 +61,7 @@ class Uint8Pass final : public VKComputePass {
 public:
     explicit Uint8Pass(const Device& device_, VKScheduler& scheduler_,
                        VKDescriptorPool& descriptor_pool_,
-                       VKStagingBufferPool& staging_buffer_pool_,
+                       StagingBufferPool& staging_buffer_pool_,
                        VKUpdateDescriptorQueue& update_descriptor_queue_);
     ~Uint8Pass();
 
@@ -69,7 +69,7 @@ public:
 
 private:
     VKScheduler& scheduler;
-    VKStagingBufferPool& staging_buffer_pool;
+    StagingBufferPool& staging_buffer_pool;
     VKUpdateDescriptorQueue& update_descriptor_queue;
 };
 
@@ -77,7 +77,7 @@ class QuadIndexedPass final : public VKComputePass {
 public:
     explicit QuadIndexedPass(const Device& device_, VKScheduler& scheduler_,
                              VKDescriptorPool& descriptor_pool_,
-                             VKStagingBufferPool& staging_buffer_pool_,
+                             StagingBufferPool& staging_buffer_pool_,
                              VKUpdateDescriptorQueue& update_descriptor_queue_);
     ~QuadIndexedPass();
 
@@ -87,7 +87,7 @@ public:
 
 private:
     VKScheduler& scheduler;
-    VKStagingBufferPool& staging_buffer_pool;
+    StagingBufferPool& staging_buffer_pool;
     VKUpdateDescriptorQueue& update_descriptor_queue;
 };
 
diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.cpp b/src/video_core/renderer_vulkan/vk_memory_manager.cpp
index a6abd0eee8..102987240e 100644
--- a/src/video_core/renderer_vulkan/vk_memory_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_memory_manager.cpp
@@ -3,6 +3,7 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
+#include <bit>
 #include <optional>
 #include <tuple>
 #include <vector>
@@ -16,92 +17,93 @@
 #include "video_core/vulkan_common/vulkan_wrapper.h"
 
 namespace Vulkan {
-
 namespace {
+struct Range {
+    u64 begin;
+    u64 end;
 
-u64 GetAllocationChunkSize(u64 required_size) {
-    static constexpr u64 sizes[] = {16ULL << 20, 32ULL << 20, 64ULL << 20, 128ULL << 20};
-    auto it = std::lower_bound(std::begin(sizes), std::end(sizes), required_size);
-    return it != std::end(sizes) ? *it : Common::AlignUp(required_size, 256ULL << 20);
+    [[nodiscard]] bool Contains(u64 iterator, u64 size) const noexcept {
+        return iterator < end && begin < iterator + size;
+    }
+};
+
+[[nodiscard]] u64 GetAllocationChunkSize(u64 required_size) {
+    static constexpr std::array sizes{
+        0x1000ULL << 10,  0x1400ULL << 10,  0x1800ULL << 10,  0x1c00ULL << 10, 0x2000ULL << 10,
+        0x3200ULL << 10,  0x4000ULL << 10,  0x6000ULL << 10,  0x8000ULL << 10, 0xA000ULL << 10,
+        0x10000ULL << 10, 0x18000ULL << 10, 0x20000ULL << 10,
+    };
+    static_assert(std::is_sorted(sizes.begin(), sizes.end()));
+
+    const auto it = std::ranges::lower_bound(sizes, required_size);
+    return it != sizes.end() ? *it : Common::AlignUp(required_size, 4ULL << 20);
 }
-
 } // Anonymous namespace
 
-class VKMemoryAllocation final {
+class MemoryAllocation {
 public:
-    explicit VKMemoryAllocation(const Device& device_, vk::DeviceMemory memory_,
-                                VkMemoryPropertyFlags properties_, u64 allocation_size_, u32 type_)
+    explicit MemoryAllocation(const Device& device_, vk::DeviceMemory memory_,
+                              VkMemoryPropertyFlags properties_, u64 allocation_size_, u32 type_)
         : device{device_}, memory{std::move(memory_)}, properties{properties_},
           allocation_size{allocation_size_}, shifted_type{ShiftType(type_)} {}
 
-    VKMemoryCommit Commit(VkDeviceSize commit_size, VkDeviceSize alignment) {
-        auto found = TryFindFreeSection(free_iterator, allocation_size,
-                                        static_cast<u64>(commit_size), static_cast<u64>(alignment));
-        if (!found) {
-            found = TryFindFreeSection(0, free_iterator, static_cast<u64>(commit_size),
-                                       static_cast<u64>(alignment));
-            if (!found) {
-                // Signal out of memory, it'll try to do more allocations.
-                return nullptr;
-            }
+    [[nodiscard]] std::optional<MemoryCommit> Commit(VkDeviceSize size, VkDeviceSize alignment) {
+        const std::optional<u64> alloc = FindFreeRegion(size, alignment);
+        if (!alloc) {
+            // Signal out of memory, it'll try to do more allocations.
+            return std::nullopt;
         }
-        auto commit = std::make_unique<VKMemoryCommitImpl>(device, this, memory, *found,
-                                                           *found + commit_size);
-        commits.push_back(commit.get());
-
-        // Last commit's address is highly probable to be free.
-        free_iterator = *found + commit_size;
-
-        return commit;
+        const Range range{
+            .begin = *alloc,
+            .end = *alloc + size,
+        };
+        commits.insert(std::ranges::upper_bound(commits, *alloc, {}, &Range::begin), range);
+        return std::make_optional<MemoryCommit>(device, this, *memory, *alloc, *alloc + size);
     }
 
-    void Free(const VKMemoryCommitImpl* commit) {
-        ASSERT(commit);
-
-        const auto it = std::find(std::begin(commits), std::end(commits), commit);
-        if (it == commits.end()) {
-            UNREACHABLE_MSG("Freeing unallocated commit!");
-            return;
-        }
+    void Free(u64 begin) {
+        const auto it = std::ranges::find(commits, begin, &Range::begin);
+        ASSERT_MSG(it != commits.end(), "Invalid commit");
         commits.erase(it);
     }
 
+    [[nodiscard]] std::span<u8> Map() {
+        if (!memory_mapped_span.empty()) {
+            return memory_mapped_span;
+        }
+        u8* const raw_pointer = memory.Map(0, allocation_size);
+        memory_mapped_span = std::span<u8>(raw_pointer, allocation_size);
+        return memory_mapped_span;
+    }
+
     /// Returns whether this allocation is compatible with the arguments.
-    bool IsCompatible(VkMemoryPropertyFlags wanted_properties, u32 type_mask) const {
+    [[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags wanted_properties, u32 type_mask) const {
         return (wanted_properties & properties) && (type_mask & shifted_type) != 0;
     }
 
 private:
-    static constexpr u32 ShiftType(u32 type) {
+    [[nodiscard]] static constexpr u32 ShiftType(u32 type) {
         return 1U << type;
     }
 
-    /// A memory allocator, it may return a free region between "start" and "end" with the solicited
-    /// requirements.
-    std::optional<u64> TryFindFreeSection(u64 start, u64 end, u64 size, u64 alignment) const {
-        u64 iterator = Common::AlignUp(start, alignment);
-        while (iterator + size <= end) {
-            const u64 try_left = iterator;
-            const u64 try_right = try_left + size;
-
-            bool overlap = false;
-            for (const auto& commit : commits) {
-                const auto [commit_left, commit_right] = commit->interval;
-                if (try_left < commit_right && commit_left < try_right) {
-                    // There's an overlap, continue the search where the overlapping commit ends.
-                    iterator = Common::AlignUp(commit_right, alignment);
-                    overlap = true;
-                    break;
-                }
+    [[nodiscard]] std::optional<u64> FindFreeRegion(u64 size, u64 alignment) noexcept {
+        ASSERT(std::has_single_bit(alignment));
+        const u64 alignment_log2 = std::countr_zero(alignment);
+        std::optional<u64> candidate;
+        u64 iterator = 0;
+        auto commit = commits.begin();
+        while (iterator + size <= allocation_size) {
+            candidate = candidate.value_or(iterator);
+            if (commit == commits.end()) {
+                break;
             }
-            if (!overlap) {
-                // A free address has been found.
-                return try_left;
+            if (commit->Contains(*candidate, size)) {
+                candidate = std::nullopt;
             }
+            iterator = Common::AlignUpLog2(commit->end, alignment_log2);
+            ++commit;
         }
-
-        // No free regions where found, return an empty optional.
-        return std::nullopt;
+        return candidate;
     }
 
     const Device& device;                   ///< Vulkan device.
@@ -109,21 +111,52 @@ private:
     const VkMemoryPropertyFlags properties; ///< Vulkan properties.
     const u64 allocation_size;              ///< Size of this allocation.
     const u32 shifted_type;                 ///< Stored Vulkan type of this allocation, shifted.
-
-    /// Hints where the next free region is likely going to be.
-    u64 free_iterator{};
-
-    /// Stores all commits done from this allocation.
-    std::vector<const VKMemoryCommitImpl*> commits;
+    std::vector<Range> commits;             ///< All commit ranges done from this allocation.
+    std::span<u8> memory_mapped_span;       ///< Memory mapped span. Empty if not queried before.
 };
 
+MemoryCommit::MemoryCommit(const Device& device_, MemoryAllocation* allocation_,
+                           VkDeviceMemory memory_, u64 begin, u64 end) noexcept
+    : device{&device_}, allocation{allocation_}, memory{memory_}, interval{begin, end} {}
+
+MemoryCommit::~MemoryCommit() {
+    Release();
+}
+
+MemoryCommit& MemoryCommit::operator=(MemoryCommit&& rhs) noexcept {
+    Release();
+    device = rhs.device;
+    allocation = std::exchange(rhs.allocation, nullptr);
+    memory = rhs.memory;
+    interval = rhs.interval;
+    span = std::exchange(rhs.span, std::span<u8>{});
+    return *this;
+}
+
+MemoryCommit::MemoryCommit(MemoryCommit&& rhs) noexcept
+    : device{rhs.device}, allocation{std::exchange(rhs.allocation, nullptr)}, memory{rhs.memory},
+      interval{rhs.interval}, span{std::exchange(rhs.span, std::span<u8>{})} {}
+
+std::span<u8> MemoryCommit::Map() {
+    if (!span.empty()) {
+        return span;
+    }
+    span = allocation->Map().subspan(interval.first, interval.second - interval.first);
+    return span;
+}
+
+void MemoryCommit::Release() {
+    if (allocation) {
+        allocation->Free(interval.first);
+    }
+}
+
 VKMemoryManager::VKMemoryManager(const Device& device_)
     : device{device_}, properties{device_.GetPhysical().GetMemoryProperties()} {}
 
 VKMemoryManager::~VKMemoryManager() = default;
 
-VKMemoryCommit VKMemoryManager::Commit(const VkMemoryRequirements& requirements,
-                                       bool host_visible) {
+MemoryCommit VKMemoryManager::Commit(const VkMemoryRequirements& requirements, bool host_visible) {
     const u64 chunk_size = GetAllocationChunkSize(requirements.size);
 
     // When a host visible commit is asked, search for host visible and coherent, otherwise search
@@ -131,39 +164,31 @@ VKMemoryCommit VKMemoryManager::Commit(const VkMemoryRequirements& requirements,
     const VkMemoryPropertyFlags wanted_properties =
         host_visible ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
                      : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
-
-    if (auto commit = TryAllocCommit(requirements, wanted_properties)) {
-        return commit;
+    if (std::optional<MemoryCommit> commit = TryAllocCommit(requirements, wanted_properties)) {
+        return std::move(*commit);
     }
-
     // Commit has failed, allocate more memory.
-    if (!AllocMemory(wanted_properties, requirements.memoryTypeBits, chunk_size)) {
-        // TODO(Rodrigo): Handle these situations in some way like flushing to guest memory.
-        // Allocation has failed, panic.
-        UNREACHABLE_MSG("Ran out of VRAM!");
-        return {};
-    }
+    // TODO(Rodrigo): Handle out of memory situations in some way like flushing to guest memory.
+    AllocMemory(wanted_properties, requirements.memoryTypeBits, chunk_size);
 
-    // Commit again, this time it won't fail since there's a fresh allocation above. If it does,
-    // there's a bug.
-    auto commit = TryAllocCommit(requirements, wanted_properties);
-    ASSERT(commit);
-    return commit;
+    // Commit again, this time it won't fail since there's a fresh allocation above.
+    // If it does, there's a bug.
+    return TryAllocCommit(requirements, wanted_properties).value();
 }
 
-VKMemoryCommit VKMemoryManager::Commit(const vk::Buffer& buffer, bool host_visible) {
+MemoryCommit VKMemoryManager::Commit(const vk::Buffer& buffer, bool host_visible) {
     auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), host_visible);
-    buffer.BindMemory(commit->GetMemory(), commit->GetOffset());
+    buffer.BindMemory(commit.Memory(), commit.Offset());
     return commit;
 }
 
-VKMemoryCommit VKMemoryManager::Commit(const vk::Image& image, bool host_visible) {
+MemoryCommit VKMemoryManager::Commit(const vk::Image& image, bool host_visible) {
     auto commit = Commit(device.GetLogical().GetImageMemoryRequirements(*image), host_visible);
-    image.BindMemory(commit->GetMemory(), commit->GetOffset());
+    image.BindMemory(commit.Memory(), commit.Offset());
     return commit;
 }
 
-bool VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask,
+void VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask,
                                   u64 size) {
     const u32 type = [&] {
         for (u32 type_index = 0; type_index < properties.memoryTypeCount; ++type_index) {
@@ -176,26 +201,18 @@ bool VKMemoryManager::AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 t
         UNREACHABLE_MSG("Couldn't find a compatible memory type!");
         return 0U;
     }();
-
-    // Try to allocate found type.
-    vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
+    vk::DeviceMemory memory = device.GetLogical().AllocateMemory({
         .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
         .pNext = nullptr,
         .allocationSize = size,
         .memoryTypeIndex = type,
     });
-    if (!memory) {
-        LOG_CRITICAL(Render_Vulkan, "Device allocation failed!");
-        return false;
-    }
-
-    allocations.push_back(std::make_unique<VKMemoryAllocation>(device, std::move(memory),
-                                                               wanted_properties, size, type));
-    return true;
+    allocations.push_back(std::make_unique<MemoryAllocation>(device, std::move(memory),
+                                                             wanted_properties, size, type));
 }
 
-VKMemoryCommit VKMemoryManager::TryAllocCommit(const VkMemoryRequirements& requirements,
-                                               VkMemoryPropertyFlags wanted_properties) {
+std::optional<MemoryCommit> VKMemoryManager::TryAllocCommit(
+    const VkMemoryRequirements& requirements, VkMemoryPropertyFlags wanted_properties) {
     for (auto& allocation : allocations) {
         if (!allocation->IsCompatible(wanted_properties, requirements.memoryTypeBits)) {
             continue;
@@ -204,27 +221,7 @@ VKMemoryCommit VKMemoryManager::TryAllocCommit(const VkMemoryRequirements& requi
             return commit;
         }
     }
-    return {};
-}
-
-VKMemoryCommitImpl::VKMemoryCommitImpl(const Device& device_, VKMemoryAllocation* allocation_,
-                                       const vk::DeviceMemory& memory_, u64 begin_, u64 end_)
-    : device{device_}, memory{memory_}, interval{begin_, end_}, allocation{allocation_} {}
-
-VKMemoryCommitImpl::~VKMemoryCommitImpl() {
-    allocation->Free(this);
-}
-
-MemoryMap VKMemoryCommitImpl::Map(u64 size, u64 offset_) const {
-    return MemoryMap(this, std::span<u8>(memory.Map(interval.first + offset_, size), size));
-}
-
-void VKMemoryCommitImpl::Unmap() const {
-    memory.Unmap();
-}
-
-MemoryMap VKMemoryCommitImpl::Map() const {
-    return Map(interval.second - interval.first);
+    return std::nullopt;
 }
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_memory_manager.h b/src/video_core/renderer_vulkan/vk_memory_manager.h
index 2452bca4e4..2f7b836e13 100644
--- a/src/video_core/renderer_vulkan/vk_memory_manager.h
+++ b/src/video_core/renderer_vulkan/vk_memory_manager.h
@@ -15,118 +15,81 @@ namespace Vulkan {
 
 class Device;
 class MemoryMap;
-class VKMemoryAllocation;
-class VKMemoryCommitImpl;
+class MemoryAllocation;
 
-using VKMemoryCommit = std::unique_ptr<VKMemoryCommitImpl>;
-
-class VKMemoryManager final {
+class MemoryCommit final {
 public:
-    explicit VKMemoryManager(const Device& device_);
-    VKMemoryManager(const VKMemoryManager&) = delete;
-    ~VKMemoryManager();
+    explicit MemoryCommit() noexcept = default;
+    explicit MemoryCommit(const Device& device_, MemoryAllocation* allocation_,
+                          VkDeviceMemory memory_, u64 begin, u64 end) noexcept;
+    ~MemoryCommit();
 
-    /**
-     * Commits a memory with the specified requeriments.
-     * @param requirements Requirements returned from a Vulkan call.
-     * @param host_visible Signals the allocator that it *must* use host visible and coherent
-     *                     memory. When passing false, it will try to allocate device local memory.
-     * @returns A memory commit.
-     */
-    VKMemoryCommit Commit(const VkMemoryRequirements& requirements, bool host_visible);
+    MemoryCommit& operator=(MemoryCommit&&) noexcept;
+    MemoryCommit(MemoryCommit&&) noexcept;
 
-    /// Commits memory required by the buffer and binds it.
-    VKMemoryCommit Commit(const vk::Buffer& buffer, bool host_visible);
+    MemoryCommit& operator=(const MemoryCommit&) = delete;
+    MemoryCommit(const MemoryCommit&) = delete;
 
-    /// Commits memory required by the image and binds it.
-    VKMemoryCommit Commit(const vk::Image& image, bool host_visible);
-
-private:
-    /// Allocates a chunk of memory.
-    bool AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask, u64 size);
-
-    /// Tries to allocate a memory commit.
-    VKMemoryCommit TryAllocCommit(const VkMemoryRequirements& requirements,
-                                  VkMemoryPropertyFlags wanted_properties);
-
-    const Device& device;                                         ///< Device handler.
-    const VkPhysicalDeviceMemoryProperties properties;            ///< Physical device properties.
-    std::vector<std::unique_ptr<VKMemoryAllocation>> allocations; ///< Current allocations.
-};
-
-class VKMemoryCommitImpl final {
-    friend VKMemoryAllocation;
-    friend MemoryMap;
-
-public:
-    explicit VKMemoryCommitImpl(const Device& device_, VKMemoryAllocation* allocation_,
-                                const vk::DeviceMemory& memory_, u64 begin_, u64 end_);
-    ~VKMemoryCommitImpl();
-
-    /// Maps a memory region and returns a pointer to it.
-    /// It's illegal to have more than one memory map at the same time.
-    MemoryMap Map(u64 size, u64 offset = 0) const;
-
-    /// Maps the whole commit and returns a pointer to it.
-    /// It's illegal to have more than one memory map at the same time.
-    MemoryMap Map() const;
+    /// Returns a host visible memory map.
+    /// It will map the backing allocation if it hasn't been mapped before.
+    std::span<u8> Map();
 
     /// Returns the Vulkan memory handler.
-    VkDeviceMemory GetMemory() const {
-        return *memory;
+    VkDeviceMemory Memory() const {
+        return memory;
     }
 
     /// Returns the start position of the commit relative to the allocation.
-    VkDeviceSize GetOffset() const {
+    VkDeviceSize Offset() const {
         return static_cast<VkDeviceSize>(interval.first);
     }
 
 private:
-    /// Unmaps memory.
-    void Unmap() const;
+    void Release();
 
-    const Device& device;             ///< Vulkan device.
-    const vk::DeviceMemory& memory;   ///< Vulkan device memory handler.
-    std::pair<u64, u64> interval{};   ///< Interval where the commit exists.
-    VKMemoryAllocation* allocation{}; ///< Pointer to the large memory allocation.
+    const Device* device{};         ///< Vulkan device.
+    MemoryAllocation* allocation{}; ///< Pointer to the large memory allocation.
+    VkDeviceMemory memory{};        ///< Vulkan device memory handler.
+    std::pair<u64, u64> interval{}; ///< Interval where the commit exists.
+    std::span<u8> span;             ///< Host visible memory span. Empty if not queried before.
 };
 
-/// Holds ownership of a memory map.
-class MemoryMap final {
+class VKMemoryManager final {
 public:
-    explicit MemoryMap(const VKMemoryCommitImpl* commit_, std::span<u8> span_)
-        : commit{commit_}, span{span_} {}
+    explicit VKMemoryManager(const Device& device_);
+    ~VKMemoryManager();
 
-    ~MemoryMap() {
-        if (commit) {
-            commit->Unmap();
-        }
-    }
+    VKMemoryManager& operator=(const VKMemoryManager&) = delete;
+    VKMemoryManager(const VKMemoryManager&) = delete;
 
-    /// Prematurely releases the memory map.
-    void Release() {
-        commit->Unmap();
-        commit = nullptr;
-    }
+    /**
+     * Commits a memory with the specified requeriments.
+     *
+     * @param requirements Requirements returned from a Vulkan call.
+     * @param host_visible Signals the allocator that it *must* use host visible and coherent
+     *                     memory. When passing false, it will try to allocate device local memory.
+     *
+     * @returns A memory commit.
+     */
+    MemoryCommit Commit(const VkMemoryRequirements& requirements, bool host_visible);
 
-    /// Returns a span to the memory map.
-    [[nodiscard]] std::span<u8> Span() const noexcept {
-        return span;
-    }
+    /// Commits memory required by the buffer and binds it.
+    MemoryCommit Commit(const vk::Buffer& buffer, bool host_visible);
 
-    /// Returns the address of the memory map.
-    [[nodiscard]] u8* Address() const noexcept {
-        return span.data();
-    }
-
-    /// Returns the address of the memory map;
-    [[nodiscard]] operator u8*() const noexcept {
-        return span.data();
-    }
+    /// Commits memory required by the image and binds it.
+    MemoryCommit Commit(const vk::Image& image, bool host_visible);
 
 private:
-    const VKMemoryCommitImpl* commit{}; ///< Mapped memory commit.
-    std::span<u8> span;                 ///< Address to the mapped memory.
+    /// Allocates a chunk of memory.
+    void AllocMemory(VkMemoryPropertyFlags wanted_properties, u32 type_mask, u64 size);
+
+    /// Tries to allocate a memory commit.
+    std::optional<MemoryCommit> TryAllocCommit(const VkMemoryRequirements& requirements,
+                                               VkMemoryPropertyFlags wanted_properties);
+
+    const Device& device;                                       ///< Device handler.
+    const VkPhysicalDeviceMemoryProperties properties;          ///< Physical device properties.
+    std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
 };
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 4695718e9e..c3316742fb 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -218,7 +218,7 @@ private:
     VKScheduler& scheduler;
 
     VKStreamBuffer stream_buffer;
-    VKStagingBufferPool staging_pool;
+    StagingBufferPool staging_pool;
     VKDescriptorPool descriptor_pool;
     VKUpdateDescriptorQueue update_descriptor_queue;
     BlitImageHelper blit_image;
@@ -234,7 +234,7 @@ private:
     VKFenceManager fence_manager;
 
     vk::Buffer default_buffer;
-    VKMemoryCommit default_buffer_commit;
+    MemoryCommit default_buffer_commit;
     vk::Event wfi_event;
     VideoCommon::Shader::AsyncShaders async_shaders;
 
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 1e0b8b9223..b085dcc1cb 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.cpp
@@ -3,58 +3,64 @@
 // Refer to the license.txt file included.
 
 #include <algorithm>
-#include <unordered_map>
 #include <utility>
 #include <vector>
 
+#include <fmt/format.h>
+
 #include "common/bit_util.h"
 #include "common/common_types.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
-#include "video_core/vulkan_common/vulkan_device.h"
 #include "video_core/vulkan_common/vulkan_wrapper.h"
+#include "video_core/vulkan_common/vulkan_device.h"
 
 namespace Vulkan {
 
-VKStagingBufferPool::StagingBuffer::StagingBuffer(std::unique_ptr<VKBuffer> buffer_)
-    : buffer{std::move(buffer_)} {}
-
-VKStagingBufferPool::VKStagingBufferPool(const Device& device_, VKMemoryManager& memory_manager_,
-                                         VKScheduler& scheduler_)
+StagingBufferPool::StagingBufferPool(const Device& device_, VKMemoryManager& memory_manager_,
+                                     VKScheduler& scheduler_)
     : device{device_}, memory_manager{memory_manager_}, scheduler{scheduler_} {}
 
-VKStagingBufferPool::~VKStagingBufferPool() = default;
+StagingBufferPool::~StagingBufferPool() = default;
 
-VKBuffer& VKStagingBufferPool::GetUnusedBuffer(std::size_t size, bool host_visible) {
-    if (const auto buffer = TryGetReservedBuffer(size, host_visible)) {
-        return *buffer;
+StagingBufferRef StagingBufferPool::Request(size_t size, bool host_visible) {
+    if (const std::optional<StagingBufferRef> ref = TryGetReservedBuffer(size, host_visible)) {
+        return *ref;
     }
     return CreateStagingBuffer(size, host_visible);
 }
 
-void VKStagingBufferPool::TickFrame() {
-    current_delete_level = (current_delete_level + 1) % NumLevels;
+void StagingBufferPool::TickFrame() {
+    current_delete_level = (current_delete_level + 1) % NUM_LEVELS;
 
     ReleaseCache(true);
     ReleaseCache(false);
 }
 
-VKBuffer* VKStagingBufferPool::TryGetReservedBuffer(std::size_t size, bool host_visible) {
-    for (StagingBuffer& entry : GetCache(host_visible)[Common::Log2Ceil64(size)].entries) {
-        if (!scheduler.IsFree(entry.tick)) {
-            continue;
+std::optional<StagingBufferRef> StagingBufferPool::TryGetReservedBuffer(size_t size,
+                                                                        bool host_visible) {
+    StagingBuffers& cache_level = GetCache(host_visible)[Common::Log2Ceil64(size)];
+
+    const auto is_free = [this](const StagingBuffer& entry) {
+        return scheduler.IsFree(entry.tick);
+    };
+    auto& entries = cache_level.entries;
+    const auto hint_it = entries.begin() + cache_level.iterate_index;
+    auto it = std::find_if(entries.begin() + cache_level.iterate_index, entries.end(), is_free);
+    if (it == entries.end()) {
+        it = std::find_if(entries.begin(), hint_it, is_free);
+        if (it == hint_it) {
+            return std::nullopt;
         }
-        entry.tick = scheduler.CurrentTick();
-        return &*entry.buffer;
     }
-    return nullptr;
+    cache_level.iterate_index = std::distance(entries.begin(), it) + 1;
+    it->tick = scheduler.CurrentTick();
+    return it->Ref();
 }
 
-VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_visible) {
+StagingBufferRef StagingBufferPool::CreateStagingBuffer(size_t size, bool host_visible) {
     const u32 log2 = Common::Log2Ceil64(size);
-
-    auto buffer = std::make_unique<VKBuffer>();
-    buffer->handle = device.GetLogical().CreateBuffer({
+    vk::Buffer buffer = device.GetLogical().CreateBuffer({
         .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
         .pNext = nullptr,
         .flags = 0,
@@ -66,49 +72,53 @@ VKBuffer& VKStagingBufferPool::CreateStagingBuffer(std::size_t size, bool host_v
         .queueFamilyIndexCount = 0,
         .pQueueFamilyIndices = nullptr,
     });
-    buffer->commit = memory_manager.Commit(buffer->handle, host_visible);
+    if (device.HasDebuggingToolAttached()) {
+        ++buffer_index;
+        buffer.SetObjectNameEXT(fmt::format("Staging Buffer {}", buffer_index).c_str());
+    }
+    MemoryCommit commit = memory_manager.Commit(buffer, host_visible);
+    const std::span<u8> mapped_span = host_visible ? commit.Map() : std::span<u8>{};
 
-    std::vector<StagingBuffer>& entries = GetCache(host_visible)[log2].entries;
-    StagingBuffer& entry = entries.emplace_back(std::move(buffer));
-    entry.tick = scheduler.CurrentTick();
-    return *entry.buffer;
+    StagingBuffer& entry = GetCache(host_visible)[log2].entries.emplace_back(StagingBuffer{
+        .buffer = std::move(buffer),
+        .commit = std::move(commit),
+        .mapped_span = mapped_span,
+        .tick = scheduler.CurrentTick(),
+    });
+    return entry.Ref();
 }
 
-VKStagingBufferPool::StagingBuffersCache& VKStagingBufferPool::GetCache(bool host_visible) {
+StagingBufferPool::StagingBuffersCache& StagingBufferPool::GetCache(bool host_visible) {
     return host_visible ? host_staging_buffers : device_staging_buffers;
 }
 
-void VKStagingBufferPool::ReleaseCache(bool host_visible) {
-    auto& cache = GetCache(host_visible);
-    const u64 size = ReleaseLevel(cache, current_delete_level);
-    if (size == 0) {
-        return;
-    }
+void StagingBufferPool::ReleaseCache(bool host_visible) {
+    ReleaseLevel(GetCache(host_visible), current_delete_level);
 }
 
-u64 VKStagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, std::size_t log2) {
-    static constexpr std::size_t deletions_per_tick = 16;
-
+void StagingBufferPool::ReleaseLevel(StagingBuffersCache& cache, size_t log2) {
+    constexpr size_t deletions_per_tick = 16;
     auto& staging = cache[log2];
     auto& entries = staging.entries;
-    const std::size_t old_size = entries.size();
+    const size_t old_size = entries.size();
 
     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);
-    const auto begin = std::begin(entries) + begin_offset;
-    const auto end = std::begin(entries) + end_offset;
+    const size_t begin_offset = staging.delete_index;
+    const size_t end_offset = std::min(begin_offset + deletions_per_tick, old_size);
+    const auto begin = entries.begin() + begin_offset;
+    const auto end = entries.begin() + end_offset;
     entries.erase(std::remove_if(begin, end, is_deleteable), end);
 
-    const std::size_t new_size = entries.size();
+    const size_t new_size = entries.size();
     staging.delete_index += deletions_per_tick;
     if (staging.delete_index >= new_size) {
         staging.delete_index = 0;
     }
-
-    return (1ULL << log2) * (old_size - new_size);
+    if (staging.iterate_index > new_size) {
+        staging.iterate_index = 0;
+    }
 }
 
 } // namespace Vulkan
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 90dadcbbe5..5234a95fad 100644
--- a/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
+++ b/src/video_core/renderer_vulkan/vk_staging_buffer_pool.h
@@ -17,46 +17,54 @@ namespace Vulkan {
 class Device;
 class VKScheduler;
 
-struct VKBuffer final {
-    vk::Buffer handle;
-    VKMemoryCommit commit;
+struct StagingBufferRef {
+    VkBuffer buffer;
+    std::span<u8> mapped_span;
 };
 
-class VKStagingBufferPool final {
+class StagingBufferPool {
 public:
-    explicit VKStagingBufferPool(const Device& device, VKMemoryManager& memory_manager,
-                                 VKScheduler& scheduler);
-    ~VKStagingBufferPool();
+    explicit StagingBufferPool(const Device& device, VKMemoryManager& memory_manager,
+                               VKScheduler& scheduler);
+    ~StagingBufferPool();
 
-    VKBuffer& GetUnusedBuffer(std::size_t size, bool host_visible);
+    StagingBufferRef Request(size_t size, bool host_visible);
 
     void TickFrame();
 
 private:
-    struct StagingBuffer final {
-        explicit StagingBuffer(std::unique_ptr<VKBuffer> buffer);
-
-        std::unique_ptr<VKBuffer> buffer;
+    struct StagingBuffer {
+        vk::Buffer buffer;
+        MemoryCommit commit;
+        std::span<u8> mapped_span;
         u64 tick = 0;
+
+        StagingBufferRef Ref() const noexcept {
+            return {
+                .buffer = *buffer,
+                .mapped_span = mapped_span,
+            };
+        }
     };
 
-    struct StagingBuffers final {
+    struct StagingBuffers {
         std::vector<StagingBuffer> entries;
-        std::size_t delete_index = 0;
+        size_t delete_index = 0;
+        size_t iterate_index = 0;
     };
 
-    static constexpr std::size_t NumLevels = sizeof(std::size_t) * CHAR_BIT;
-    using StagingBuffersCache = std::array<StagingBuffers, NumLevels>;
+    static constexpr size_t NUM_LEVELS = sizeof(size_t) * CHAR_BIT;
+    using StagingBuffersCache = std::array<StagingBuffers, NUM_LEVELS>;
 
-    VKBuffer* TryGetReservedBuffer(std::size_t size, bool host_visible);
+    std::optional<StagingBufferRef> TryGetReservedBuffer(size_t size, bool host_visible);
 
-    VKBuffer& CreateStagingBuffer(std::size_t size, bool host_visible);
+    StagingBufferRef CreateStagingBuffer(size_t size, bool host_visible);
 
     StagingBuffersCache& GetCache(bool host_visible);
 
     void ReleaseCache(bool host_visible);
 
-    u64 ReleaseLevel(StagingBuffersCache& cache, std::size_t log2);
+    void ReleaseLevel(StagingBuffersCache& cache, size_t log2);
 
     const Device& device;
     VKMemoryManager& memory_manager;
@@ -65,7 +73,8 @@ private:
     StagingBuffersCache host_staging_buffers;
     StagingBuffersCache device_staging_buffers;
 
-    std::size_t current_delete_level = 0;
+    size_t current_delete_level = 0;
+    u64 buffer_index = 0;
 };
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index bd11de012c..5acbcad762 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -554,10 +554,10 @@ void TextureCacheRuntime::Finish() {
 }
 
 ImageBufferMap TextureCacheRuntime::MapUploadBuffer(size_t size) {
-    const auto& buffer = staging_buffer_pool.GetUnusedBuffer(size, true);
+    const auto staging_ref = staging_buffer_pool.Request(size, true);
     return ImageBufferMap{
-        .handle = *buffer.handle,
-        .map = buffer.commit->Map(size),
+        .handle = staging_ref.buffer,
+        .span = staging_ref.mapped_span,
     };
 }
 
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 92a7aad8b5..134465fd46 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -19,14 +19,13 @@ using VideoCommon::Offset2D;
 using VideoCommon::RenderTargets;
 using VideoCore::Surface::PixelFormat;
 
-class VKScheduler;
-class VKStagingBufferPool;
-
 class BlitImageHelper;
 class Device;
 class Image;
 class ImageView;
 class Framebuffer;
+class StagingBufferPool;
+class VKScheduler;
 
 struct RenderPassKey {
     constexpr auto operator<=>(const RenderPassKey&) const noexcept = default;
@@ -60,18 +59,18 @@ struct ImageBufferMap {
     }
 
     [[nodiscard]] std::span<u8> Span() const noexcept {
-        return map.Span();
+        return span;
     }
 
     VkBuffer handle;
-    MemoryMap map;
+    std::span<u8> span;
 };
 
 struct TextureCacheRuntime {
     const Device& device;
     VKScheduler& scheduler;
     VKMemoryManager& memory_manager;
-    VKStagingBufferPool& staging_buffer_pool;
+    StagingBufferPool& staging_buffer_pool;
     BlitImageHelper& blit_image_helper;
     std::unordered_map<RenderPassKey, vk::RenderPass> renderpass_cache;
 
@@ -141,7 +140,7 @@ private:
     VKScheduler* scheduler;
     vk::Image image;
     vk::Buffer buffer;
-    VKMemoryCommit commit;
+    MemoryCommit commit;
     VkImageAspectFlags aspect_mask = 0;
     bool initialized = false;
 };