diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 5bae8d24f6..e569523b6c 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -173,7 +173,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
         return;
     }
     const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
-    vk::Image staging_image = device.GetLogical().CreateImage(VkImageCreateInfo{
+    vk::Image staging_image = memory_allocator.CreateImage(VkImageCreateInfo{
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
         .pNext = nullptr,
         .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
@@ -196,7 +196,6 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
         .pQueueFamilyIndices = nullptr,
         .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
     });
-    const auto image_commit = memory_allocator.Commit(staging_image, MemoryUsage::DeviceLocal);
 
     const vk::ImageView dst_view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
         .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index acb143fc7a..82ca81c7e7 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -1071,12 +1071,8 @@ void BlitScreen::ReleaseRawImages() {
         scheduler.Wait(tick);
     }
     raw_images.clear();
-    raw_buffer_commits.clear();
-
     aa_image_view.reset();
     aa_image.reset();
-    aa_commit = MemoryCommit{};
-
     buffer.reset();
     buffer_commit = MemoryCommit{};
 }
@@ -1101,13 +1097,12 @@ void BlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer
 void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
     raw_images.resize(image_count);
     raw_image_views.resize(image_count);
-    raw_buffer_commits.resize(image_count);
 
     const auto create_image = [&](bool used_on_framebuffer = false, u32 up_scale = 1,
                                   u32 down_shift = 0) {
         u32 extra_usages = used_on_framebuffer ? VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
                                                : VK_IMAGE_USAGE_TRANSFER_DST_BIT;
-        return device.GetLogical().CreateImage(VkImageCreateInfo{
+        return memory_allocator.CreateImage(VkImageCreateInfo{
             .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
             .pNext = nullptr,
             .flags = 0,
@@ -1130,9 +1125,6 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
             .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
         });
     };
-    const auto create_commit = [&](vk::Image& image) {
-        return memory_allocator.Commit(image, MemoryUsage::DeviceLocal);
-    };
     const auto create_image_view = [&](vk::Image& image, bool used_on_framebuffer = false) {
         return device.GetLogical().CreateImageView(VkImageViewCreateInfo{
             .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
@@ -1161,7 +1153,6 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
 
     for (size_t i = 0; i < image_count; ++i) {
         raw_images[i] = create_image();
-        raw_buffer_commits[i] = create_commit(raw_images[i]);
         raw_image_views[i] = create_image_view(raw_images[i]);
     }
 
@@ -1169,7 +1160,6 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
     const u32 up_scale = Settings::values.resolution_info.up_scale;
     const u32 down_shift = Settings::values.resolution_info.down_shift;
     aa_image = create_image(true, up_scale, down_shift);
-    aa_commit = create_commit(aa_image);
     aa_image_view = create_image_view(aa_image, true);
     VkExtent2D size{
         .width = (up_scale * framebuffer.width) >> down_shift,
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 68ec20253d..7fcfa99768 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -148,7 +148,6 @@ private:
 
     std::vector<vk::Image> raw_images;
     std::vector<vk::ImageView> raw_image_views;
-    std::vector<MemoryCommit> raw_buffer_commits;
 
     vk::DescriptorPool aa_descriptor_pool;
     vk::DescriptorSetLayout aa_descriptor_set_layout;
@@ -159,7 +158,6 @@ private:
     vk::DescriptorSets aa_descriptor_sets;
     vk::Image aa_image;
     vk::ImageView aa_image_view;
-    MemoryCommit aa_commit;
 
     u32 raw_width = 0;
     u32 raw_height = 0;
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp
index df972cd545..9bcdca2fb6 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.cpp
+++ b/src/video_core/renderer_vulkan/vk_fsr.cpp
@@ -205,10 +205,9 @@ void FSR::CreateDescriptorSets() {
 void FSR::CreateImages() {
     images.resize(image_count * 2);
     image_views.resize(image_count * 2);
-    buffer_commits.resize(image_count * 2);
 
     for (size_t i = 0; i < image_count * 2; ++i) {
-        images[i] = device.GetLogical().CreateImage(VkImageCreateInfo{
+        images[i] = memory_allocator.CreateImage(VkImageCreateInfo{
             .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
             .pNext = nullptr,
             .flags = 0,
@@ -231,7 +230,6 @@ void FSR::CreateImages() {
             .pQueueFamilyIndices = nullptr,
             .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
         });
-        buffer_commits[i] = memory_allocator.Commit(images[i], MemoryUsage::DeviceLocal);
         image_views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
             .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
             .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_fsr.h b/src/video_core/renderer_vulkan/vk_fsr.h
index 5d872861f0..8bb9fc23ac 100644
--- a/src/video_core/renderer_vulkan/vk_fsr.h
+++ b/src/video_core/renderer_vulkan/vk_fsr.h
@@ -47,7 +47,6 @@ private:
     vk::Sampler sampler;
     std::vector<vk::Image> images;
     std::vector<vk::ImageView> image_views;
-    std::vector<MemoryCommit> buffer_commits;
 };
 
 } // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
index 10ace04200..d681bd22a4 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -181,7 +181,7 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_
     frame->height = height;
     frame->is_srgb = is_srgb;
 
-    frame->image = dld.CreateImage({
+    frame->image = memory_allocator.CreateImage({
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
         .pNext = nullptr,
         .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
@@ -204,8 +204,6 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_
         .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
     });
 
-    frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal);
-
     frame->image_view = dld.CreateImageView({
         .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
         .pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
index 4ac2e2395f..83e859416c 100644
--- a/src/video_core/renderer_vulkan/vk_present_manager.h
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -29,7 +29,6 @@ struct Frame {
     vk::Image image;
     vk::ImageView image_view;
     vk::Framebuffer framebuffer;
-    MemoryCommit image_commit;
     vk::CommandBuffer cmdbuf;
     vk::Semaphore render_ready;
     vk::Fence present_done;
diff --git a/src/video_core/renderer_vulkan/vk_smaa.cpp b/src/video_core/renderer_vulkan/vk_smaa.cpp
index f8735189d7..ff7c3a4191 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.cpp
+++ b/src/video_core/renderer_vulkan/vk_smaa.cpp
@@ -25,9 +25,7 @@ namespace {
 
 #define ARRAY_TO_SPAN(a) std::span(a, (sizeof(a) / sizeof(a[0])))
 
-std::pair<vk::Image, MemoryCommit> CreateWrappedImage(const Device& device,
-                                                      MemoryAllocator& allocator,
-                                                      VkExtent2D dimensions, VkFormat format) {
+vk::Image CreateWrappedImage(MemoryAllocator& allocator, VkExtent2D dimensions, VkFormat format) {
     const VkImageCreateInfo image_ci{
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
         .pNext = nullptr,
@@ -46,11 +44,7 @@ std::pair<vk::Image, MemoryCommit> CreateWrappedImage(const Device& device,
         .pQueueFamilyIndices = nullptr,
         .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
     };
-
-    auto image = device.GetLogical().CreateImage(image_ci);
-    auto commit = allocator.Commit(image, Vulkan::MemoryUsage::DeviceLocal);
-
-    return std::make_pair(std::move(image), std::move(commit));
+    return allocator.CreateImage(image_ci);
 }
 
 void TransitionImageLayout(vk::CommandBuffer& cmdbuf, VkImage image, VkImageLayout target_layout,
@@ -531,10 +525,8 @@ void SMAA::CreateImages() {
     static constexpr VkExtent2D area_extent{AREATEX_WIDTH, AREATEX_HEIGHT};
     static constexpr VkExtent2D search_extent{SEARCHTEX_WIDTH, SEARCHTEX_HEIGHT};
 
-    std::tie(m_static_images[Area], m_static_buffer_commits[Area]) =
-        CreateWrappedImage(m_device, m_allocator, area_extent, VK_FORMAT_R8G8_UNORM);
-    std::tie(m_static_images[Search], m_static_buffer_commits[Search]) =
-        CreateWrappedImage(m_device, m_allocator, search_extent, VK_FORMAT_R8_UNORM);
+    m_static_images[Area] = CreateWrappedImage(m_allocator, area_extent, VK_FORMAT_R8G8_UNORM);
+    m_static_images[Search] = CreateWrappedImage(m_allocator, search_extent, VK_FORMAT_R8_UNORM);
 
     m_static_image_views[Area] =
         CreateWrappedImageView(m_device, m_static_images[Area], VK_FORMAT_R8G8_UNORM);
@@ -544,12 +536,11 @@ void SMAA::CreateImages() {
     for (u32 i = 0; i < m_image_count; i++) {
         Images& images = m_dynamic_images.emplace_back();
 
-        std::tie(images.images[Blend], images.buffer_commits[Blend]) =
-            CreateWrappedImage(m_device, m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
-        std::tie(images.images[Edges], images.buffer_commits[Edges]) =
-            CreateWrappedImage(m_device, m_allocator, m_extent, VK_FORMAT_R16G16_SFLOAT);
-        std::tie(images.images[Output], images.buffer_commits[Output]) =
-            CreateWrappedImage(m_device, m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
+        images.images[Blend] =
+            CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
+        images.images[Edges] = CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16_SFLOAT);
+        images.images[Output] =
+            CreateWrappedImage(m_allocator, m_extent, VK_FORMAT_R16G16B16A16_SFLOAT);
 
         images.image_views[Blend] =
             CreateWrappedImageView(m_device, images.images[Blend], VK_FORMAT_R16G16B16A16_SFLOAT);
diff --git a/src/video_core/renderer_vulkan/vk_smaa.h b/src/video_core/renderer_vulkan/vk_smaa.h
index 99a369148b..0e214258a7 100644
--- a/src/video_core/renderer_vulkan/vk_smaa.h
+++ b/src/video_core/renderer_vulkan/vk_smaa.h
@@ -66,13 +66,11 @@ private:
     std::array<vk::Pipeline, MaxSMAAStage> m_pipelines{};
     std::array<vk::RenderPass, MaxSMAAStage> m_renderpasses{};
 
-    std::array<MemoryCommit, MaxStaticImage> m_static_buffer_commits;
     std::array<vk::Image, MaxStaticImage> m_static_images{};
     std::array<vk::ImageView, MaxStaticImage> m_static_image_views{};
 
     struct Images {
         vk::DescriptorSets descriptor_sets{};
-        std::array<MemoryCommit, MaxDynamicImage> buffer_commits;
         std::array<vk::Image, MaxDynamicImage> images{};
         std::array<vk::ImageView, MaxDynamicImage> image_views{};
         std::array<vk::Framebuffer, MaxSMAAStage> framebuffers{};
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index f025f618b4..10defe6cb0 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -15,7 +15,6 @@
 #include "video_core/renderer_vulkan/blit_image.h"
 #include "video_core/renderer_vulkan/maxwell_to_vk.h"
 #include "video_core/renderer_vulkan/vk_compute_pass.h"
-#include "video_core/renderer_vulkan/vk_rasterizer.h"
 #include "video_core/renderer_vulkan/vk_render_pass_cache.h"
 #include "video_core/renderer_vulkan/vk_scheduler.h"
 #include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -163,11 +162,12 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
     };
 }
 
-[[nodiscard]] vk::Image MakeImage(const Device& device, const ImageInfo& info) {
+[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
+                                  const ImageInfo& info) {
     if (info.type == ImageType::Buffer) {
         return vk::Image{};
     }
-    return device.GetLogical().CreateImage(MakeImageCreateInfo(device, info));
+    return allocator.CreateImage(MakeImageCreateInfo(device, info));
 }
 
 [[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
@@ -1266,8 +1266,8 @@ void TextureCacheRuntime::TickFrame() {}
 Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
              VAddr cpu_addr_)
     : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
-      runtime{&runtime_}, original_image(MakeImage(runtime_.device, info)),
-      commit(runtime_.memory_allocator.Commit(original_image, MemoryUsage::DeviceLocal)),
+      runtime{&runtime_},
+      original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info)),
       aspect_mask(ImageAspectMask(info.format)) {
     if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
         if (Settings::values.async_astc.GetValue()) {
@@ -1467,9 +1467,7 @@ bool Image::ScaleUp(bool ignore) {
         auto scaled_info = info;
         scaled_info.size.width = scaled_width;
         scaled_info.size.height = scaled_height;
-        scaled_image = MakeImage(runtime->device, scaled_info);
-        auto& allocator = runtime->memory_allocator;
-        scaled_commit = MemoryCommit(allocator.Commit(scaled_image, MemoryUsage::DeviceLocal));
+        scaled_image = MakeImage(runtime->device, runtime->memory_allocator, scaled_info);
         ignore = false;
     }
     current_image = *scaled_image;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index f14525dcb0..9481f2531c 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -180,12 +180,10 @@ private:
     TextureCacheRuntime* runtime{};
 
     vk::Image original_image;
-    MemoryCommit commit;
     std::vector<vk::ImageView> storage_image_views;
     VkImageAspectFlags aspect_mask = 0;
     bool initialized = false;
     vk::Image scaled_image{};
-    MemoryCommit scaled_commit{};
     VkImage current_image{};
 
     std::unique_ptr<Framebuffer> scale_framebuffer;
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 123d3b1c45..9936f56580 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -210,6 +210,11 @@ public:
         return dld;
     }
 
+    /// Returns the VMA allocator.
+    VmaAllocator GetAllocator() const {
+        return allocator;
+    }
+
     /// Returns the logical device.
     const vk::Device& GetLogical() const {
         return logical;
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index f87c99603e..7f860cccde 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -15,6 +15,10 @@
 #include "video_core/vulkan_common/vulkan_memory_allocator.h"
 #include "video_core/vulkan_common/vulkan_wrapper.h"
 
+#define VMA_STATIC_VULKAN_FUNCTIONS 0
+#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
+#include <vk_mem_alloc.h>
+
 namespace Vulkan {
 namespace {
 struct Range {
@@ -180,6 +184,24 @@ MemoryAllocator::MemoryAllocator(const Device& device_)
 
 MemoryAllocator::~MemoryAllocator() = default;
 
+vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
+    const VmaAllocationCreateInfo alloc_info = {
+        .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
+        .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
+        .requiredFlags = 0,
+        .preferredFlags = 0,
+        .pool = VK_NULL_HANDLE,
+        .pUserData = nullptr,
+    };
+
+    VkImage handle{};
+    VmaAllocation allocation{};
+    vk::Check(
+        vmaCreateImage(device.GetAllocator(), &ci, &alloc_info, &handle, &allocation, nullptr));
+    return vk::Image(handle, *device.GetLogical(), device.GetAllocator(), allocation,
+                     device.GetDispatchLoader());
+}
+
 MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {
     // Find the fastest memory flags we can afford with the current requirements
     const u32 type_mask = requirements.memoryTypeBits;
@@ -205,14 +227,6 @@ MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage
     return commit;
 }
 
-MemoryCommit MemoryAllocator::Commit(const vk::Image& image, MemoryUsage usage) {
-    VkMemoryRequirements requirements = device.GetLogical().GetImageMemoryRequirements(*image);
-    requirements.size = Common::AlignUp(requirements.size, buffer_image_granularity);
-    auto commit = Commit(requirements, usage);
-    image.BindMemory(commit.Memory(), commit.Offset());
-    return commit;
-}
-
 bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
     const u32 type = FindType(flags, type_mask).value();
     vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.h b/src/video_core/vulkan_common/vulkan_memory_allocator.h
index 494f30f512..f9ee53cfb5 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.h
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.h
@@ -80,6 +80,8 @@ public:
     MemoryAllocator& operator=(const MemoryAllocator&) = delete;
     MemoryAllocator(const MemoryAllocator&) = delete;
 
+    vk::Image CreateImage(const VkImageCreateInfo& ci) const;
+
     /**
      * Commits a memory with the specified requirements.
      *
@@ -93,9 +95,6 @@ public:
     /// Commits memory required by the buffer and binds it.
     MemoryCommit Commit(const vk::Buffer& buffer, MemoryUsage usage);
 
-    /// Commits memory required by the image and binds it.
-    MemoryCommit Commit(const vk::Image& image, MemoryUsage usage);
-
 private:
     /// Tries to allocate a chunk of memory.
     bool TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size);
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 336f537007..5d088dc586 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -12,6 +12,10 @@
 
 #include "video_core/vulkan_common/vulkan_wrapper.h"
 
+#define VMA_STATIC_VULKAN_FUNCTIONS 0
+#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
+#include <vk_mem_alloc.h>
+
 namespace Vulkan::vk {
 
 namespace {
@@ -547,6 +551,16 @@ DebugUtilsMessenger Instance::CreateDebugUtilsMessenger(
     return DebugUtilsMessenger(object, handle, *dld);
 }
 
+void Image::SetObjectNameEXT(const char* name) const {
+    SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name);
+}
+
+void Image::Release() const noexcept {
+    if (handle) {
+        vmaDestroyImage(allocator, handle, allocation);
+    }
+}
+
 void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
     Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
 }
@@ -559,14 +573,6 @@ void BufferView::SetObjectNameEXT(const char* name) const {
     SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER_VIEW, name);
 }
 
-void Image::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
-    Check(dld->vkBindImageMemory(owner, handle, memory, offset));
-}
-
-void Image::SetObjectNameEXT(const char* name) const {
-    SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE, name);
-}
-
 void ImageView::SetObjectNameEXT(const char* name) const {
     SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_IMAGE_VIEW, name);
 }
@@ -713,12 +719,6 @@ BufferView Device::CreateBufferView(const VkBufferViewCreateInfo& ci) const {
     return BufferView(object, handle, *dld);
 }
 
-Image Device::CreateImage(const VkImageCreateInfo& ci) const {
-    VkImage object;
-    Check(dld->vkCreateImage(handle, &ci, nullptr, &object));
-    return Image(object, handle, *dld);
-}
-
 ImageView Device::CreateImageView(const VkImageViewCreateInfo& ci) const {
     VkImageView object;
     Check(dld->vkCreateImageView(handle, &ci, nullptr, &object));
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.h b/src/video_core/vulkan_common/vulkan_wrapper.h
index 4ff328a21e..8ec7087740 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.h
+++ b/src/video_core/vulkan_common/vulkan_wrapper.h
@@ -32,6 +32,9 @@
 #pragma warning(disable : 26812) // Disable prefer enum class over enum
 #endif
 
+VK_DEFINE_HANDLE(VmaAllocator)
+VK_DEFINE_HANDLE(VmaAllocation)
+
 namespace Vulkan::vk {
 
 /**
@@ -616,6 +619,60 @@ public:
     }
 };
 
+class Image {
+public:
+    explicit Image(VkImage handle_, VkDevice owner_, VmaAllocator allocator_,
+                   VmaAllocation allocation_, const DeviceDispatch& dld_) noexcept
+        : handle{handle_}, owner{owner_}, allocator{allocator_},
+          allocation{allocation_}, dld{&dld_} {}
+    Image() = default;
+
+    Image(const Image&) = delete;
+    Image& operator=(const Image&) = delete;
+
+    Image(Image&& rhs) noexcept
+        : handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
+          allocation{rhs.allocation}, dld{rhs.dld} {}
+
+    Image& operator=(Image&& rhs) noexcept {
+        Release();
+        handle = std::exchange(rhs.handle, nullptr);
+        owner = rhs.owner;
+        allocator = rhs.allocator;
+        allocation = rhs.allocation;
+        dld = rhs.dld;
+        return *this;
+    }
+
+    ~Image() noexcept {
+        Release();
+    }
+
+    VkImage operator*() const noexcept {
+        return handle;
+    }
+
+    void reset() noexcept {
+        Release();
+        handle = nullptr;
+    }
+
+    explicit operator bool() const noexcept {
+        return handle != nullptr;
+    }
+
+    void SetObjectNameEXT(const char* name) const;
+
+private:
+    void Release() const noexcept;
+
+    VkImage handle = nullptr;
+    VkDevice owner = nullptr;
+    VmaAllocator allocator = nullptr;
+    VmaAllocation allocation = nullptr;
+    const DeviceDispatch* dld = nullptr;
+};
+
 class Queue {
 public:
     /// Construct an empty queue handle.
@@ -658,17 +715,6 @@ public:
     void SetObjectNameEXT(const char* name) const;
 };
 
-class Image : public Handle<VkImage, VkDevice, DeviceDispatch> {
-    using Handle<VkImage, VkDevice, DeviceDispatch>::Handle;
-
-public:
-    /// Attaches a memory allocation.
-    void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
-
-    /// Set object name.
-    void SetObjectNameEXT(const char* name) const;
-};
-
 class ImageView : public Handle<VkImageView, VkDevice, DeviceDispatch> {
     using Handle<VkImageView, VkDevice, DeviceDispatch>::Handle;
 
@@ -844,8 +890,6 @@ public:
 
     BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const;
 
-    Image CreateImage(const VkImageCreateInfo& ci) const;
-
     ImageView CreateImageView(const VkImageViewCreateInfo& ci) const;
 
     Semaphore CreateSemaphore() const;