[Impeller] fold memory check into allocator_vk (#51187)

Various cleanups to Vulkan allocator implementation:

1. Fixes https://github.com/flutter/flutter/issues/137454
2. Fold device transient cap check into allocator.
3. adds debug tracking for total memory usage in MB (a followup change needs to be made to driver to plumb it through)
4. Small cleanups to mock vulkan so an allocator can be created from it.
5. depth/stencil shouldn't be input attachments.

Part of https://github.com/flutter/flutter/issues/144617
diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files
index 55412e5..9f3dafd 100644
--- a/ci/licenses_golden/excluded_files
+++ b/ci/licenses_golden/excluded_files
@@ -168,6 +168,7 @@
 ../../../flutter/impeller/playground
 ../../../flutter/impeller/renderer/backend/gles/test
 ../../../flutter/impeller/renderer/backend/metal/texture_mtl_unittests.mm
+../../../flutter/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc
 ../../../flutter/impeller/renderer/backend/vulkan/blit_command_vk_unittests.cc
 ../../../flutter/impeller/renderer/backend/vulkan/command_encoder_vk_unittests.cc
 ../../../flutter/impeller/renderer/backend/vulkan/command_pool_vk_unittests.cc
diff --git a/impeller/core/allocator.h b/impeller/core/allocator.h
index 794de16..38ac054 100644
--- a/impeller/core/allocator.h
+++ b/impeller/core/allocator.h
@@ -44,6 +44,12 @@
 
   virtual ISize GetMaxTextureSizeSupported() const = 0;
 
+  /// @brief Write debug memory usage information to the dart timeline in debug
+  ///        and profile modes.
+  ///
+  ///        This is only supported on the Vulkan backend.
+  virtual void DebugTraceMemoryStatistics() const {};
+
  protected:
   Allocator();
 
diff --git a/impeller/renderer/backend/gles/capabilities_gles.cc b/impeller/renderer/backend/gles/capabilities_gles.cc
index a67486b..4a96d6e 100644
--- a/impeller/renderer/backend/gles/capabilities_gles.cc
+++ b/impeller/renderer/backend/gles/capabilities_gles.cc
@@ -181,10 +181,6 @@
   return supports_decal_sampler_address_mode_;
 }
 
-bool CapabilitiesGLES::SupportsDeviceTransientTextures() const {
-  return false;
-}
-
 PixelFormat CapabilitiesGLES::GetDefaultColorFormat() const {
   return PixelFormat::kR8G8B8A8UNormInt;
 }
diff --git a/impeller/renderer/backend/gles/capabilities_gles.h b/impeller/renderer/backend/gles/capabilities_gles.h
index afcc71b..deb5bab 100644
--- a/impeller/renderer/backend/gles/capabilities_gles.h
+++ b/impeller/renderer/backend/gles/capabilities_gles.h
@@ -108,9 +108,6 @@
   bool SupportsDecalSamplerAddressMode() const override;
 
   // |Capabilities|
-  bool SupportsDeviceTransientTextures() const override;
-
-  // |Capabilities|
   PixelFormat GetDefaultColorFormat() const override;
 
   // |Capabilities|
diff --git a/impeller/renderer/backend/gles/test/capabilities_unittests.cc b/impeller/renderer/backend/gles/test/capabilities_unittests.cc
index 2ad2091..f0c2c5a 100644
--- a/impeller/renderer/backend/gles/test/capabilities_unittests.cc
+++ b/impeller/renderer/backend/gles/test/capabilities_unittests.cc
@@ -24,7 +24,6 @@
   EXPECT_FALSE(capabilities->SupportsComputeSubgroups());
   EXPECT_FALSE(capabilities->SupportsReadFromResolve());
   EXPECT_FALSE(capabilities->SupportsDecalSamplerAddressMode());
-  EXPECT_FALSE(capabilities->SupportsDeviceTransientTextures());
 
   EXPECT_EQ(capabilities->GetDefaultColorFormat(),
             PixelFormat::kR8G8B8A8UNormInt);
diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm
index 623f3c8..1cfdc4a 100644
--- a/impeller/renderer/backend/metal/context_mtl.mm
+++ b/impeller/renderer/backend/metal/context_mtl.mm
@@ -66,7 +66,6 @@
       .SetSupportsCompute(true)
       .SetSupportsComputeSubgroups(DeviceSupportsComputeSubgroups(device))
       .SetSupportsReadFromResolve(true)
-      .SetSupportsDeviceTransientTextures(true)
       .SetDefaultGlyphAtlasFormat(PixelFormat::kA8UNormInt)
       .Build();
 }
diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn
index 952ae0c..20ee2f0 100644
--- a/impeller/renderer/backend/vulkan/BUILD.gn
+++ b/impeller/renderer/backend/vulkan/BUILD.gn
@@ -8,6 +8,7 @@
 impeller_component("vulkan_unittests") {
   testonly = true
   sources = [
+    "allocator_vk_unittests.cc",
     "blit_command_vk_unittests.cc",
     "command_encoder_vk_unittests.cc",
     "command_pool_vk_unittests.cc",
diff --git a/impeller/renderer/backend/vulkan/allocator_vk.cc b/impeller/renderer/backend/vulkan/allocator_vk.cc
index 7147e3d..38eb4b5 100644
--- a/impeller/renderer/backend/vulkan/allocator_vk.cc
+++ b/impeller/renderer/backend/vulkan/allocator_vk.cc
@@ -6,18 +6,18 @@
 
 #include <memory>
 
-#include "flutter/fml/memory/ref_ptr.h"
-#include "flutter/fml/trace_event.h"
+#include "flutter_vma/flutter_vma.h"
 #include "impeller/core/formats.h"
 #include "impeller/renderer/backend/vulkan/device_buffer_vk.h"
 #include "impeller/renderer/backend/vulkan/formats_vk.h"
 #include "impeller/renderer/backend/vulkan/texture_vk.h"
 #include "vulkan/vulkan_enums.hpp"
+#include "vulkan/vulkan_to_string.hpp"
 
 namespace impeller {
 
-static constexpr vk::Flags<vk::MemoryPropertyFlagBits>
-ToVKBufferMemoryPropertyFlags(StorageMode mode) {
+vk::Flags<vk::MemoryPropertyFlagBits>
+AllocatorVK::ToVKBufferMemoryPropertyFlags(StorageMode mode) {
   switch (mode) {
     case StorageMode::kHostVisible:
       return vk::MemoryPropertyFlagBits::eHostVisible;
@@ -29,7 +29,7 @@
   FML_UNREACHABLE();
 }
 
-static VmaAllocationCreateFlags ToVmaAllocationBufferCreateFlags(
+VmaAllocationCreateFlags AllocatorVK::ToVmaAllocationBufferCreateFlags(
     StorageMode mode,
     bool readback) {
   VmaAllocationCreateFlags flags = 0;
@@ -52,6 +52,84 @@
   FML_UNREACHABLE();
 }
 
+vk::ImageUsageFlags AllocatorVK::ToVKImageUsageFlags(
+    PixelFormat format,
+    TextureUsageMask usage,
+    StorageMode mode,
+    bool supports_memoryless_textures) {
+  vk::ImageUsageFlags vk_usage;
+
+  switch (mode) {
+    case StorageMode::kHostVisible:
+    case StorageMode::kDevicePrivate:
+      break;
+    case StorageMode::kDeviceTransient:
+      if (supports_memoryless_textures) {
+        vk_usage |= vk::ImageUsageFlagBits::eTransientAttachment;
+      }
+      break;
+  }
+
+  if (usage & static_cast<TextureUsageMask>(TextureUsage::kRenderTarget)) {
+    if (PixelFormatIsDepthStencil(format)) {
+      vk_usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
+    } else {
+      vk_usage |= vk::ImageUsageFlagBits::eColorAttachment;
+      vk_usage |= vk::ImageUsageFlagBits::eInputAttachment;
+    }
+  }
+
+  if (usage & static_cast<TextureUsageMask>(TextureUsage::kShaderRead)) {
+    vk_usage |= vk::ImageUsageFlagBits::eSampled;
+    // Device transient images can only be used as attachments. The caller
+    // specified incorrect usage flags and is attempting to read a device
+    // transient image in a shader. Unset the transient attachment flag. See:
+    // https://github.com/flutter/flutter/issues/121633
+    if (mode == StorageMode::kDeviceTransient) {
+      vk_usage &= ~vk::ImageUsageFlagBits::eTransientAttachment;
+    }
+  }
+
+  if (usage & static_cast<TextureUsageMask>(TextureUsage::kShaderWrite)) {
+    vk_usage |= vk::ImageUsageFlagBits::eStorage;
+    // Device transient images can only be used as attachments. The caller
+    // specified incorrect usage flags and is attempting to read a device
+    // transient image in a shader. Unset the transient attachment flag. See:
+    // https://github.com/flutter/flutter/issues/121633
+    if (mode == StorageMode::kDeviceTransient) {
+      vk_usage &= ~vk::ImageUsageFlagBits::eTransientAttachment;
+    }
+  }
+
+  if (mode != StorageMode::kDeviceTransient) {
+    // Add transfer usage flags to support blit passes only if image isn't
+    // device transient.
+    vk_usage |= vk::ImageUsageFlagBits::eTransferSrc |
+                vk::ImageUsageFlagBits::eTransferDst;
+  }
+
+  return vk_usage;
+}
+
+vk::Flags<vk::MemoryPropertyFlagBits>
+AllocatorVK::ToVKTextureMemoryPropertyFlags(StorageMode mode,
+                                            bool supports_memoryless_textures) {
+  switch (mode) {
+    case StorageMode::kHostVisible:
+      return vk::MemoryPropertyFlagBits::eHostVisible |
+             vk::MemoryPropertyFlagBits::eDeviceLocal;
+    case StorageMode::kDevicePrivate:
+      return vk::MemoryPropertyFlagBits::eDeviceLocal;
+    case StorageMode::kDeviceTransient:
+      if (supports_memoryless_textures) {
+        return vk::MemoryPropertyFlagBits::eLazilyAllocated |
+               vk::MemoryPropertyFlagBits::eDeviceLocal;
+      }
+      return vk::MemoryPropertyFlagBits::eDeviceLocal;
+  }
+  FML_UNREACHABLE();
+}
+
 static PoolVMA CreateBufferPool(VmaAllocator allocator) {
   vk::BufferCreateInfo buffer_info;
   buffer_info.usage = vk::BufferUsageFlagBits::eVertexBuffer |
@@ -68,8 +146,8 @@
   VmaAllocationCreateInfo allocation_info = {};
   allocation_info.usage = VMA_MEMORY_USAGE_AUTO;
   allocation_info.preferredFlags = static_cast<VkMemoryPropertyFlags>(
-      ToVKBufferMemoryPropertyFlags(StorageMode::kHostVisible));
-  allocation_info.flags = ToVmaAllocationBufferCreateFlags(
+      AllocatorVK::ToVKBufferMemoryPropertyFlags(StorageMode::kHostVisible));
+  allocation_info.flags = AllocatorVK::ToVmaAllocationBufferCreateFlags(
       StorageMode::kHostVisible, /*readback=*/false);
 
   uint32_t memTypeIndex;
@@ -101,6 +179,7 @@
   auto limits = physical_device.getProperties().limits;
   max_texture_size_.width = max_texture_size_.height =
       limits.maxImageDimension2D;
+  physical_device.getMemoryProperties(&memory_properties_);
 
   VmaVulkanFunctions proc_table = {};
 
@@ -149,11 +228,24 @@
     VALIDATION_LOG << "Could not create memory allocator";
     return;
   }
+
+  {
+    // Query texture support.
+    // TODO(jonahwilliams):
+    // https://github.com/flutter/flutter/issues/129784
+    for (auto i = 0u; i < memory_properties_.memoryTypeCount; i++) {
+      if (memory_properties_.memoryTypes[i].propertyFlags &
+          vk::MemoryPropertyFlagBits::eLazilyAllocated) {
+        supports_memoryless_textures_ = true;
+        break;
+      }
+    }
+  }
+
   staging_buffer_pool_.reset(CreateBufferPool(allocator));
   created_buffer_pool_ &= staging_buffer_pool_.is_valid();
   allocator_.reset(allocator);
-  supports_memoryless_textures_ =
-      capabilities.SupportsDeviceTransientTextures();
+  supports_memoryless_textures_ = false;
   is_valid_ = true;
 }
 
@@ -169,101 +261,6 @@
   return max_texture_size_;
 }
 
-static constexpr vk::ImageUsageFlags ToVKImageUsageFlags(
-    PixelFormat format,
-    TextureUsageMask usage,
-    StorageMode mode,
-    bool supports_memoryless_textures) {
-  vk::ImageUsageFlags vk_usage;
-
-  switch (mode) {
-    case StorageMode::kHostVisible:
-    case StorageMode::kDevicePrivate:
-      break;
-    case StorageMode::kDeviceTransient:
-      if (supports_memoryless_textures) {
-        vk_usage |= vk::ImageUsageFlagBits::eTransientAttachment;
-      }
-      break;
-  }
-
-  if (usage & static_cast<TextureUsageMask>(TextureUsage::kRenderTarget)) {
-    if (PixelFormatIsDepthStencil(format)) {
-      vk_usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
-    } else {
-      vk_usage |= vk::ImageUsageFlagBits::eColorAttachment;
-    }
-    vk_usage |= vk::ImageUsageFlagBits::eInputAttachment;
-  }
-
-  if (usage & static_cast<TextureUsageMask>(TextureUsage::kShaderRead)) {
-    vk_usage |= vk::ImageUsageFlagBits::eSampled;
-    // Device transient images can only be used as attachments. The caller
-    // specified incorrect usage flags and is attempting to read a device
-    // transient image in a shader. Unset the transient attachment flag. See:
-    // https://github.com/flutter/flutter/issues/121633
-    if (mode == StorageMode::kDeviceTransient) {
-      vk_usage &= ~vk::ImageUsageFlagBits::eTransientAttachment;
-    }
-  }
-
-  if (usage & static_cast<TextureUsageMask>(TextureUsage::kShaderWrite)) {
-    vk_usage |= vk::ImageUsageFlagBits::eStorage;
-    // Device transient images can only be used as attachments. The caller
-    // specified incorrect usage flags and is attempting to read a device
-    // transient image in a shader. Unset the transient attachment flag. See:
-    // https://github.com/flutter/flutter/issues/121633
-    if (mode == StorageMode::kDeviceTransient) {
-      vk_usage &= ~vk::ImageUsageFlagBits::eTransientAttachment;
-    }
-  }
-
-  if (mode != StorageMode::kDeviceTransient) {
-    // Add transfer usage flags to support blit passes only if image isn't
-    // device transient.
-    vk_usage |= vk::ImageUsageFlagBits::eTransferSrc |
-                vk::ImageUsageFlagBits::eTransferDst;
-  }
-
-  return vk_usage;
-}
-
-static constexpr VmaMemoryUsage ToVMAMemoryUsage() {
-  return VMA_MEMORY_USAGE_AUTO;
-}
-
-static constexpr vk::Flags<vk::MemoryPropertyFlagBits>
-ToVKTextureMemoryPropertyFlags(StorageMode mode,
-                               bool supports_memoryless_textures) {
-  switch (mode) {
-    case StorageMode::kHostVisible:
-      return vk::MemoryPropertyFlagBits::eHostVisible |
-             vk::MemoryPropertyFlagBits::eDeviceLocal;
-    case StorageMode::kDevicePrivate:
-      return vk::MemoryPropertyFlagBits::eDeviceLocal;
-    case StorageMode::kDeviceTransient:
-      if (supports_memoryless_textures) {
-        return vk::MemoryPropertyFlagBits::eLazilyAllocated |
-               vk::MemoryPropertyFlagBits::eDeviceLocal;
-      }
-      return vk::MemoryPropertyFlagBits::eDeviceLocal;
-  }
-  FML_UNREACHABLE();
-}
-
-static VmaAllocationCreateFlags ToVmaAllocationCreateFlags(StorageMode mode) {
-  VmaAllocationCreateFlags flags = 0;
-  switch (mode) {
-    case StorageMode::kHostVisible:
-      return flags;
-    case StorageMode::kDevicePrivate:
-      return flags;
-    case StorageMode::kDeviceTransient:
-      return flags;
-  }
-  FML_UNREACHABLE();
-}
-
 class AllocatedTextureSourceVK final : public TextureSourceVK {
  public:
   AllocatedTextureSourceVK(std::weak_ptr<ResourceManagerVK> resource_manager,
@@ -287,18 +284,17 @@
     image_info.arrayLayers = ToArrayLayerCount(desc.type);
     image_info.tiling = vk::ImageTiling::eOptimal;
     image_info.initialLayout = vk::ImageLayout::eUndefined;
-    image_info.usage =
-        ToVKImageUsageFlags(desc.format, desc.usage, desc.storage_mode,
-                            supports_memoryless_textures);
+    image_info.usage = AllocatorVK::ToVKImageUsageFlags(
+        desc.format, desc.usage, desc.storage_mode,
+        supports_memoryless_textures);
     image_info.sharingMode = vk::SharingMode::eExclusive;
 
     VmaAllocationCreateInfo alloc_nfo = {};
 
-    alloc_nfo.usage = ToVMAMemoryUsage();
-    alloc_nfo.preferredFlags =
-        static_cast<VkMemoryPropertyFlags>(ToVKTextureMemoryPropertyFlags(
+    alloc_nfo.usage = VMA_MEMORY_USAGE_AUTO;
+    alloc_nfo.preferredFlags = static_cast<VkMemoryPropertyFlags>(
+        AllocatorVK::ToVKTextureMemoryPropertyFlags(
             desc.storage_mode, supports_memoryless_textures));
-    alloc_nfo.flags = ToVmaAllocationCreateFlags(desc.storage_mode);
 
     auto create_info_native =
         static_cast<vk::ImageCreateInfo::NativeType>(image_info);
@@ -459,7 +455,7 @@
       static_cast<vk::BufferCreateInfo::NativeType>(buffer_info);
 
   VmaAllocationCreateInfo allocation_info = {};
-  allocation_info.usage = ToVMAMemoryUsage();
+  allocation_info.usage = VMA_MEMORY_USAGE_AUTO;
   allocation_info.preferredFlags = static_cast<VkMemoryPropertyFlags>(
       ToVKBufferMemoryPropertyFlags(desc.storage_mode));
   allocation_info.flags =
@@ -480,6 +476,11 @@
                                              &buffer_allocation_info  //
                                              )};
 
+  bool is_host_coherent_buffer =
+      !!(memory_properties_.memoryTypes[buffer_allocation_info.memoryType]
+             .propertyFlags &
+         vk::MemoryPropertyFlagBits::eHostCoherent);
+
   if (result != vk::Result::eSuccess) {
     VALIDATION_LOG << "Unable to allocate a device buffer: "
                    << vk::to_string(result);
@@ -492,8 +493,27 @@
       UniqueBufferVMA{BufferVMA{allocator_.get(),      //
                                 buffer_allocation,     //
                                 vk::Buffer{buffer}}},  //
-      buffer_allocation_info                           //
+      buffer_allocation_info,                          //
+      is_host_coherent_buffer                          //
   );
 }
 
+void AllocatorVK::DebugTraceMemoryStatistics() const {
+#ifdef IMPELLER_DEBUG
+  auto count = memory_properties_.memoryHeapCount;
+  std::vector<VmaBudget> budgets(count);
+  vmaGetHeapBudgets(allocator_.get(), budgets.data());
+  size_t total_usage = 0;
+  for (auto i = 0u; i < count; i++) {
+    const VmaBudget& budget = budgets[i];
+    total_usage += budget.usage;
+  }
+  // Convert bytes to MB.
+  total_usage *= 1e-6;
+  FML_TRACE_COUNTER("flutter", "AllocatorVK",
+                    reinterpret_cast<int64_t>(this),  // Trace Counter ID
+                    "MemoryBudgetUsageMB", total_usage);
+#endif  // IMPELLER_DEBUG
+}
+
 }  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/allocator_vk.h b/impeller/renderer/backend/vulkan/allocator_vk.h
index caaedfc..a89cda2 100644
--- a/impeller/renderer/backend/vulkan/allocator_vk.h
+++ b/impeller/renderer/backend/vulkan/allocator_vk.h
@@ -5,15 +5,12 @@
 #ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_ALLOCATOR_VK_H_
 #define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_ALLOCATOR_VK_H_
 
-#include "flutter/fml/macros.h"
-#include "flutter/fml/memory/ref_ptr.h"
 #include "impeller/core/allocator.h"
 #include "impeller/renderer/backend/vulkan/context_vk.h"
 #include "impeller/renderer/backend/vulkan/device_buffer_vk.h"
 #include "impeller/renderer/backend/vulkan/device_holder.h"
 #include "impeller/renderer/backend/vulkan/vk.h"
 
-#include <array>
 #include <cstdint>
 #include <memory>
 
@@ -24,6 +21,27 @@
   // |Allocator|
   ~AllocatorVK() override;
 
+  // visible for testing.
+  static vk::Flags<vk::MemoryPropertyFlagBits> ToVKBufferMemoryPropertyFlags(
+      StorageMode mode);
+
+  // visible for testing.
+  static VmaAllocationCreateFlags ToVmaAllocationBufferCreateFlags(
+      StorageMode mode,
+      bool readback);
+
+  // visible for testing.
+  static vk::ImageUsageFlags ToVKImageUsageFlags(
+      PixelFormat format,
+      TextureUsageMask usage,
+      StorageMode mode,
+      bool supports_memoryless_textures);
+
+  // visible for testing.
+  static vk::Flags<vk::MemoryPropertyFlagBits> ToVKTextureMemoryPropertyFlags(
+      StorageMode mode,
+      bool supports_memoryless_textures);
+
  private:
   friend class ContextVK;
 
@@ -36,6 +54,7 @@
   bool supports_memoryless_textures_ = false;
   // TODO(jonahwilliams): figure out why CI can't create these buffer pools.
   bool created_buffer_pool_ = true;
+  vk::PhysicalDeviceMemoryProperties memory_properties_;
 
   AllocatorVK(std::weak_ptr<Context> context,
               uint32_t vulkan_api_version,
@@ -58,6 +77,9 @@
   // |Allocator|
   ISize GetMaxTextureSizeSupported() const override;
 
+  // |Allocator|
+  void DebugTraceMemoryStatistics() const override;
+
   AllocatorVK(const AllocatorVK&) = delete;
 
   AllocatorVK& operator=(const AllocatorVK&) = delete;
diff --git a/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc b/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc
new file mode 100644
index 0000000..580993c
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/allocator_vk_unittests.cc
@@ -0,0 +1,100 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter/testing/testing.h"  // IWYU pragma: keep
+#include "flutter_vma/flutter_vma.h"
+#include "gtest/gtest.h"
+#include "impeller/core/formats.h"
+#include "impeller/renderer/backend/vulkan/allocator_vk.h"
+#include "third_party/vulkan-deps/vulkan-headers/src/include/vulkan/vulkan_enums.hpp"
+
+namespace impeller {
+namespace testing {
+
+TEST(AllocatorVK, BufferCreateFlags) {
+  EXPECT_EQ(AllocatorVK::ToVmaAllocationBufferCreateFlags(
+                StorageMode::kDevicePrivate, /*readback=*/false),
+            0u);
+  EXPECT_EQ(AllocatorVK::ToVmaAllocationBufferCreateFlags(
+                StorageMode::kDeviceTransient, /*readback=*/false),
+            0u);
+  uint32_t expected_flags =
+      VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
+      VMA_ALLOCATION_CREATE_MAPPED_BIT;
+  EXPECT_EQ(AllocatorVK::ToVmaAllocationBufferCreateFlags(
+                StorageMode::kHostVisible, /*readback=*/false),
+            expected_flags);
+
+  expected_flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT |
+                   VMA_ALLOCATION_CREATE_MAPPED_BIT;
+  EXPECT_EQ(AllocatorVK::ToVmaAllocationBufferCreateFlags(
+                StorageMode::kHostVisible, /*readback=*/true),
+            expected_flags);
+}
+
+TEST(AllocatorVK, BufferMemoryPropertyFlags) {
+  EXPECT_EQ(
+      AllocatorVK::ToVKBufferMemoryPropertyFlags(StorageMode::kDevicePrivate),
+      vk::MemoryPropertyFlagBits::eDeviceLocal);
+  EXPECT_EQ(
+      AllocatorVK::ToVKBufferMemoryPropertyFlags(StorageMode::kDeviceTransient),
+      vk::MemoryPropertyFlagBits::eLazilyAllocated);
+  EXPECT_EQ(
+      AllocatorVK::ToVKBufferMemoryPropertyFlags(StorageMode::kHostVisible),
+      vk::MemoryPropertyFlagBits::eHostVisible);
+}
+
+TEST(AllocatorVK, ImageUsageFlags) {
+  // Color Format
+  EXPECT_EQ(AllocatorVK::ToVKImageUsageFlags(
+                PixelFormat::kR8G8B8A8UNormInt,                              //
+                static_cast<TextureUsageMask>(TextureUsage::kRenderTarget),  //
+                StorageMode::kDevicePrivate,                                 //
+                /*supports_memoryless_textures=*/true                        //
+                ),
+            vk::ImageUsageFlagBits::eColorAttachment |
+                vk::ImageUsageFlagBits::eInputAttachment |
+                vk::ImageUsageFlagBits::eTransferSrc |
+                vk::ImageUsageFlagBits::eTransferDst);
+
+  EXPECT_EQ(AllocatorVK::ToVKImageUsageFlags(
+                PixelFormat::kR8G8B8A8UNormInt,                              //
+                static_cast<TextureUsageMask>(TextureUsage::kRenderTarget),  //
+                StorageMode::kDeviceTransient,                               //
+                /*supports_memoryless_textures=*/true                        //
+                ),
+            vk::ImageUsageFlagBits::eColorAttachment |
+                vk::ImageUsageFlagBits::eInputAttachment |
+                vk::ImageUsageFlagBits::eTransientAttachment);
+
+  // Depth+Stencil Format
+  EXPECT_EQ(AllocatorVK::ToVKImageUsageFlags(
+                PixelFormat::kD24UnormS8Uint,                                //
+                static_cast<TextureUsageMask>(TextureUsage::kRenderTarget),  //
+                StorageMode::kDevicePrivate,                                 //
+                /*supports_memoryless_textures=*/true                        //
+                ),
+            vk::ImageUsageFlagBits::eDepthStencilAttachment |
+                vk::ImageUsageFlagBits::eTransferSrc |
+                vk::ImageUsageFlagBits::eTransferDst);
+
+  EXPECT_EQ(AllocatorVK::ToVKImageUsageFlags(
+                PixelFormat::kD24UnormS8Uint,                                //
+                static_cast<TextureUsageMask>(TextureUsage::kRenderTarget),  //
+                StorageMode::kDeviceTransient,                               //
+                /*supports_memoryless_textures=*/true                        //
+                ),
+            vk::ImageUsageFlagBits::eDepthStencilAttachment |
+                vk::ImageUsageFlagBits::eTransientAttachment);
+}
+
+TEST(AllocatorVK, TextureMemoryPropertyFlags) {
+  EXPECT_EQ(
+      AllocatorVK::ToVKTextureMemoryPropertyFlags(
+          StorageMode::kDevicePrivate, /*supports_memoryless_textures=*/true),
+      vk::MemoryPropertyFlagBits::eDeviceLocal);
+}
+
+}  // namespace testing
+}  // namespace impeller
\ No newline at end of file
diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.cc b/impeller/renderer/backend/vulkan/capabilities_vk.cc
index b5ad0ab..c301e70 100644
--- a/impeller/renderer/backend/vulkan/capabilities_vk.cc
+++ b/impeller/renderer/backend/vulkan/capabilities_vk.cc
@@ -442,21 +442,6 @@
              .supportedOperations &
          vk::SubgroupFeatureFlagBits::eArithmetic);
 
-  {
-    // Query texture support.
-    // TODO(jonahwilliams):
-    // https://github.com/flutter/flutter/issues/129784
-    vk::PhysicalDeviceMemoryProperties memory_properties;
-    device.getMemoryProperties(&memory_properties);
-
-    for (auto i = 0u; i < memory_properties.memoryTypeCount; i++) {
-      if (memory_properties.memoryTypes[i].propertyFlags &
-          vk::MemoryPropertyFlagBits::eLazilyAllocated) {
-        supports_device_transient_textures_ = true;
-      }
-    }
-  }
-
   // Determine the optional device extensions this physical device supports.
   {
     required_common_device_extensions_.clear();
@@ -544,11 +529,6 @@
 }
 
 // |Capabilities|
-bool CapabilitiesVK::SupportsDeviceTransientTextures() const {
-  return supports_device_transient_textures_;
-}
-
-// |Capabilities|
 PixelFormat CapabilitiesVK::GetDefaultColorFormat() const {
   return default_color_format_;
 }
diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.h b/impeller/renderer/backend/vulkan/capabilities_vk.h
index f06732b..834d6b4 100644
--- a/impeller/renderer/backend/vulkan/capabilities_vk.h
+++ b/impeller/renderer/backend/vulkan/capabilities_vk.h
@@ -179,9 +179,6 @@
   bool SupportsDecalSamplerAddressMode() const override;
 
   // |Capabilities|
-  bool SupportsDeviceTransientTextures() const override;
-
-  // |Capabilities|
   PixelFormat GetDefaultColorFormat() const override;
 
   // |Capabilities|
diff --git a/impeller/renderer/backend/vulkan/device_buffer_vk.cc b/impeller/renderer/backend/vulkan/device_buffer_vk.cc
index a43a582..7d791d2 100644
--- a/impeller/renderer/backend/vulkan/device_buffer_vk.cc
+++ b/impeller/renderer/backend/vulkan/device_buffer_vk.cc
@@ -5,7 +5,6 @@
 #include "impeller/renderer/backend/vulkan/device_buffer_vk.h"
 
 #include "flutter/flutter_vma/flutter_vma.h"
-#include "flutter/fml/trace_event.h"
 #include "impeller/renderer/backend/vulkan/context_vk.h"
 #include "vulkan/vulkan_core.h"
 
@@ -14,14 +13,16 @@
 DeviceBufferVK::DeviceBufferVK(DeviceBufferDescriptor desc,
                                std::weak_ptr<Context> context,
                                UniqueBufferVMA buffer,
-                               VmaAllocationInfo info)
+                               VmaAllocationInfo info,
+                               bool is_host_coherent_buffer)
     : DeviceBuffer(desc),
       context_(std::move(context)),
       resource_(ContextVK::Cast(*context_.lock().get()).GetResourceManager(),
                 BufferResource{
                     std::move(buffer),  //
                     info                //
-                }) {}
+                }),
+      is_host_coherent_buffer_(is_host_coherent_buffer) {}
 
 DeviceBufferVK::~DeviceBufferVK() = default;
 
@@ -41,9 +42,11 @@
   if (source) {
     ::memmove(dest + offset, source + source_range.offset, source_range.length);
   }
-  ::vmaFlushAllocation(resource_->buffer.get().allocator,
-                       resource_->buffer.get().allocation, offset,
-                       source_range.length);
+  if (!is_host_coherent_buffer_) {
+    ::vmaFlushAllocation(resource_->buffer.get().allocator,
+                         resource_->buffer.get().allocation, offset,
+                         source_range.length);
+  }
 
   return true;
 }
@@ -65,6 +68,9 @@
 }
 
 void DeviceBufferVK::Flush(std::optional<Range> range) const {
+  if (is_host_coherent_buffer_) {
+    return;
+  }
   auto flush_range = range.value_or(Range{0, GetDeviceBufferDescriptor().size});
   ::vmaFlushAllocation(resource_->buffer.get().allocator,
                        resource_->buffer.get().allocation, flush_range.offset,
@@ -72,6 +78,9 @@
 }
 
 void DeviceBufferVK::Invalidate(std::optional<Range> range) const {
+  if (is_host_coherent_buffer_) {
+    return;
+  }
   auto flush_range = range.value_or(Range{0, GetDeviceBufferDescriptor().size});
   ::vmaInvalidateAllocation(resource_->buffer.get().allocator,
                             resource_->buffer.get().allocation,
diff --git a/impeller/renderer/backend/vulkan/device_buffer_vk.h b/impeller/renderer/backend/vulkan/device_buffer_vk.h
index 744453d..b7a8c48 100644
--- a/impeller/renderer/backend/vulkan/device_buffer_vk.h
+++ b/impeller/renderer/backend/vulkan/device_buffer_vk.h
@@ -20,7 +20,8 @@
   DeviceBufferVK(DeviceBufferDescriptor desc,
                  std::weak_ptr<Context> context,
                  UniqueBufferVMA buffer,
-                 VmaAllocationInfo info);
+                 VmaAllocationInfo info,
+                 bool is_host_coherent_buffer);
 
   // |DeviceBuffer|
   ~DeviceBufferVK() override;
@@ -51,6 +52,7 @@
 
   std::weak_ptr<Context> context_;
   UniqueResourceVKT<BufferResource> resource_;
+  const bool is_host_coherent_buffer_;
 
   // |DeviceBuffer|
   uint8_t* OnGetContents() const override;
diff --git a/impeller/renderer/backend/vulkan/surface_context_vk.cc b/impeller/renderer/backend/vulkan/surface_context_vk.cc
index d3856d6..f0a7912 100644
--- a/impeller/renderer/backend/vulkan/surface_context_vk.cc
+++ b/impeller/renderer/backend/vulkan/surface_context_vk.cc
@@ -87,6 +87,7 @@
     impeller::PipelineLibraryVK::Cast(*pipeline_library)
         .DidAcquireSurfaceFrame();
   }
+  parent_->GetResourceAllocator()->DebugTraceMemoryStatistics();
   parent_->GetCommandPoolRecycler()->Dispose();
   return surface;
 }
diff --git a/impeller/renderer/backend/vulkan/test/mock_vulkan.cc b/impeller/renderer/backend/vulkan/test/mock_vulkan.cc
index 4c59062..3fc577a 100644
--- a/impeller/renderer/backend/vulkan/test/mock_vulkan.cc
+++ b/impeller/renderer/backend/vulkan/test/mock_vulkan.cc
@@ -44,6 +44,8 @@
 
 struct MockSemaphore {};
 
+struct MockBuffer {};
+
 static ISize currentImageSize = ISize{1, 1};
 
 class MockDevice final {
@@ -221,14 +223,20 @@
 void vkGetPhysicalDeviceMemoryProperties(
     VkPhysicalDevice physicalDevice,
     VkPhysicalDeviceMemoryProperties* pMemoryProperties) {
-  pMemoryProperties->memoryTypeCount = 1;
+  pMemoryProperties->memoryTypeCount = 2;
+  pMemoryProperties->memoryHeapCount = 2;
   pMemoryProperties->memoryTypes[0].heapIndex = 0;
-  // pMemoryProperties->memoryTypes[0].propertyFlags =
-  //     VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
-  //     VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD;
-  pMemoryProperties->memoryHeapCount = 1;
+  pMemoryProperties->memoryTypes[0].propertyFlags =
+      VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
+      VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
+      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+  pMemoryProperties->memoryTypes[1].heapIndex = 1;
+  pMemoryProperties->memoryTypes[1].propertyFlags =
+      VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
   pMemoryProperties->memoryHeaps[0].size = 1024 * 1024 * 1024;
-  pMemoryProperties->memoryHeaps[0].flags = 0;
+  pMemoryProperties->memoryHeaps[0].flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT;
+  pMemoryProperties->memoryHeaps[1].size = 1024 * 1024 * 1024;
+  pMemoryProperties->memoryHeaps[1].flags = VK_MEMORY_HEAP_DEVICE_LOCAL_BIT;
 }
 
 VkResult vkCreatePipelineCache(VkDevice device,
@@ -317,10 +325,18 @@
                         const VkBufferCreateInfo* pCreateInfo,
                         const VkAllocationCallbacks* pAllocator,
                         VkBuffer* pBuffer) {
-  *pBuffer = reinterpret_cast<VkBuffer>(0xDEADDEAD);
+  *pBuffer = reinterpret_cast<VkBuffer>(new MockBuffer());
   return VK_SUCCESS;
 }
 
+void vkDestroyBuffer(VkDevice device,
+                     VkBuffer buffer,
+                     const VkAllocationCallbacks* pAllocator) {
+  MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
+  mock_device->AddCalledFunction("vkDestroyBuffer");
+  delete reinterpret_cast<MockBuffer*>(buffer);
+}
+
 void vkGetBufferMemoryRequirements2KHR(
     VkDevice device,
     const VkBufferMemoryRequirementsInfo2* pInfo,
@@ -719,6 +735,25 @@
   return VK_SUCCESS;
 }
 
+VkResult vkFlushMappedMemoryRanges(VkDevice device,
+                                   uint32_t memoryRangeCount,
+                                   const VkMappedMemoryRange* pMemoryRanges) {
+  MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
+  mock_device->AddCalledFunction("vkFlushMappedMemoryRanges");
+  return VK_SUCCESS;
+}
+
+VkResult vkMapMemory(VkDevice device,
+                     VkDeviceMemory memory,
+                     VkDeviceSize offset,
+                     VkDeviceSize size,
+                     VkMemoryMapFlags flags,
+                     void** ppData) {
+  MockDevice* mock_device = reinterpret_cast<MockDevice*>(device);
+  mock_device->AddCalledFunction("vkMapMemory");
+  return VK_SUCCESS;
+}
+
 PFN_vkVoidFunction GetMockVulkanProcAddress(VkInstance instance,
                                             const char* pName) {
   if (strcmp("vkEnumerateInstanceExtensionProperties", pName) == 0) {
@@ -855,6 +890,12 @@
     return (PFN_vkVoidFunction)vkDestroySurfaceKHR;
   } else if (strcmp("vkAcquireNextImageKHR", pName) == 0) {
     return (PFN_vkVoidFunction)vkAcquireNextImageKHR;
+  } else if (strcmp("vkFlushMappedMemoryRanges", pName) == 0) {
+    return (PFN_vkVoidFunction)vkFlushMappedMemoryRanges;
+  } else if (strcmp("vkDestroyBuffer", pName) == 0) {
+    return (PFN_vkVoidFunction)vkDestroyBuffer;
+  } else if (strcmp("vkMapMemory", pName) == 0) {
+    return (PFN_vkVoidFunction)vkMapMemory;
   }
   return noop;
 }
diff --git a/impeller/renderer/capabilities.cc b/impeller/renderer/capabilities.cc
index 1ef1fe3..ca6ed47 100644
--- a/impeller/renderer/capabilities.cc
+++ b/impeller/renderer/capabilities.cc
@@ -76,11 +76,6 @@
   }
 
   // |Capabilities|
-  bool SupportsDeviceTransientTextures() const override {
-    return supports_device_transient_textures_;
-  }
-
-  // |Capabilities|
   PixelFormat GetDefaultGlyphAtlasFormat() const override {
     return default_glyph_atlas_format_;
   }
@@ -95,7 +90,6 @@
                        bool supports_compute_subgroups,
                        bool supports_read_from_resolve,
                        bool supports_decal_sampler_address_mode,
-                       bool supports_device_transient_textures,
                        PixelFormat default_color_format,
                        PixelFormat default_stencil_format,
                        PixelFormat default_depth_stencil_format,
@@ -110,7 +104,6 @@
         supports_read_from_resolve_(supports_read_from_resolve),
         supports_decal_sampler_address_mode_(
             supports_decal_sampler_address_mode),
-        supports_device_transient_textures_(supports_device_transient_textures),
         default_color_format_(default_color_format),
         default_stencil_format_(default_stencil_format),
         default_depth_stencil_format_(default_depth_stencil_format),
@@ -127,7 +120,6 @@
   bool supports_compute_subgroups_ = false;
   bool supports_read_from_resolve_ = false;
   bool supports_decal_sampler_address_mode_ = false;
-  bool supports_device_transient_textures_ = false;
   PixelFormat default_color_format_ = PixelFormat::kUnknown;
   PixelFormat default_stencil_format_ = PixelFormat::kUnknown;
   PixelFormat default_depth_stencil_format_ = PixelFormat::kUnknown;
@@ -211,12 +203,6 @@
   return *this;
 }
 
-CapabilitiesBuilder& CapabilitiesBuilder::SetSupportsDeviceTransientTextures(
-    bool value) {
-  supports_device_transient_textures_ = value;
-  return *this;
-}
-
 CapabilitiesBuilder& CapabilitiesBuilder::SetDefaultGlyphAtlasFormat(
     PixelFormat value) {
   default_glyph_atlas_format_ = value;
@@ -234,7 +220,6 @@
       supports_compute_subgroups_,                                        //
       supports_read_from_resolve_,                                        //
       supports_decal_sampler_address_mode_,                               //
-      supports_device_transient_textures_,                                //
       default_color_format_.value_or(PixelFormat::kUnknown),              //
       default_stencil_format_.value_or(PixelFormat::kUnknown),            //
       default_depth_stencil_format_.value_or(PixelFormat::kUnknown),      //
diff --git a/impeller/renderer/capabilities.h b/impeller/renderer/capabilities.h
index 34c5839..395b225 100644
--- a/impeller/renderer/capabilities.h
+++ b/impeller/renderer/capabilities.h
@@ -81,14 +81,6 @@
   /// @brief  Whether the context backend supports `SamplerAddressMode::Decal`.
   virtual bool SupportsDecalSamplerAddressMode() const = 0;
 
-  /// @brief  Whether the context backend supports allocating
-  ///         `StorageMode::kDeviceTransient` (aka "memoryless") textures, which
-  ///         are temporary textures kept in tile memory for the duration of the
-  ///         `RenderPass` it's attached to.
-  ///
-  ///         This feature is especially useful for MSAA and stencils.
-  virtual bool SupportsDeviceTransientTextures() const = 0;
-
   /// @brief  Returns a supported `PixelFormat` for textures that store
   ///         4-channel colors (red/green/blue/alpha).
   virtual PixelFormat GetDefaultColorFormat() const = 0;
@@ -149,8 +141,6 @@
 
   CapabilitiesBuilder& SetSupportsDecalSamplerAddressMode(bool value);
 
-  CapabilitiesBuilder& SetSupportsDeviceTransientTextures(bool value);
-
   CapabilitiesBuilder& SetDefaultGlyphAtlasFormat(PixelFormat value);
 
   std::unique_ptr<Capabilities> Build();
@@ -165,7 +155,6 @@
   bool supports_compute_subgroups_ = false;
   bool supports_read_from_resolve_ = false;
   bool supports_decal_sampler_address_mode_ = false;
-  bool supports_device_transient_textures_ = false;
   std::optional<PixelFormat> default_color_format_ = std::nullopt;
   std::optional<PixelFormat> default_stencil_format_ = std::nullopt;
   std::optional<PixelFormat> default_depth_stencil_format_ = std::nullopt;
diff --git a/impeller/renderer/capabilities_unittests.cc b/impeller/renderer/capabilities_unittests.cc
index 2692d06..1de8441 100644
--- a/impeller/renderer/capabilities_unittests.cc
+++ b/impeller/renderer/capabilities_unittests.cc
@@ -27,7 +27,6 @@
 CAPABILITY_TEST(SupportsComputeSubgroups, false);
 CAPABILITY_TEST(SupportsReadFromResolve, false);
 CAPABILITY_TEST(SupportsDecalSamplerAddressMode, false);
-CAPABILITY_TEST(SupportsDeviceTransientTextures, false);
 
 TEST(CapabilitiesTest, DefaultColorFormat) {
   auto defaults = CapabilitiesBuilder().Build();
diff --git a/impeller/renderer/testing/mocks.h b/impeller/renderer/testing/mocks.h
index 45b519c..a6c1585 100644
--- a/impeller/renderer/testing/mocks.h
+++ b/impeller/renderer/testing/mocks.h
@@ -200,7 +200,6 @@
   MOCK_METHOD(bool, SupportsComputeSubgroups, (), (const, override));
   MOCK_METHOD(bool, SupportsReadFromResolve, (), (const, override));
   MOCK_METHOD(bool, SupportsDecalSamplerAddressMode, (), (const, override));
-  MOCK_METHOD(bool, SupportsDeviceTransientTextures, (), (const, override));
   MOCK_METHOD(PixelFormat, GetDefaultColorFormat, (), (const, override));
   MOCK_METHOD(PixelFormat, GetDefaultStencilFormat, (), (const, override));
   MOCK_METHOD(PixelFormat, GetDefaultDepthStencilFormat, (), (const, override));