[scenic] New Gpu Allocator Interface

GpuAllocator now allocates Memory, Buffers, and Images. All factory functions that allocate new memory are expected to go through some instance of a GpuAllocator (mostly, for tracking purposes).

All other related factory functions (e.g., GpuMem::New) have been removed. All remaining functions have been renamed to reflect their usage (e.g., Image::New has been renamed to Image::AdoptImage, since it does not allocate a new Image object, but only wraps an existing image and/or allocation).

This also converts Image, similar to Buffer, to not have its own concept of a buffer offset. A suballocated GpuMem object is passed in instead.

Finally, this fixes SCN-273, as escher::Buffer objects can now be constructed that point to host memory.

tested=scenic_tests, manual running of sketchy_service, hello_scenic
SCN-273 #done

Change-Id: Ie870f1c9795db7f29ad33c6f67ff335d29c41270
diff --git a/bin/ui/sketchy/buffer/shared_buffer.cc b/bin/ui/sketchy/buffer/shared_buffer.cc
index 8358947..43d5702 100644
--- a/bin/ui/sketchy/buffer/shared_buffer.cc
+++ b/bin/ui/sketchy/buffer/shared_buffer.cc
@@ -47,24 +47,20 @@
 namespace sketchy_service {
 
 SharedBufferPtr SharedBuffer::New(scenic::Session* session,
-                                  escher::ResourceRecycler* recycler,
+                                  escher::BufferFactory* factory,
                                   vk::DeviceSize capacity) {
-  return fxl::AdoptRef(new SharedBuffer(session, recycler, capacity));
+  return fxl::AdoptRef(new SharedBuffer(session, factory, capacity));
 }
 
 SharedBuffer::SharedBuffer(scenic::Session* session,
-                           escher::ResourceRecycler* recycler,
+                           escher::BufferFactory* factory,
                            vk::DeviceSize capacity) {
-  // Normally, this code might use a buffer factory or a gpu_allocator
-  // to construct its buffer. However, if we did that, then we might not have a
-  // unique VMO just for this one resource. Instead, we might get a VMO for a
-  // managed pool or a circular buffer. Therefore, we go through the steps here
-  // to create our own fresh slab, to make sure that the VMO is isolated in its
-  // contents.
+  // By passing an empty GpuMemPtr into NewBuffer, we are signalling to the
+  // factory that we want a dedicated allocation. This gives us the guarantees
+  // for VMO extraction described above.
   escher::GpuMemPtr mem;
-  escher_buffer_ =
-      escher::Buffer::New(recycler, nullptr, capacity, kBufferUsageFlags,
-                          kMemoryPropertyFlags, &mem);
+  escher_buffer_ = factory->NewBuffer(capacity, kBufferUsageFlags,
+                                      kMemoryPropertyFlags, &mem);
   scenic_buffer_ =
       NewScenicBufferFromEscherBuffer(escher_buffer_, session, mem);
 }
diff --git a/bin/ui/sketchy/buffer/shared_buffer.h b/bin/ui/sketchy/buffer/shared_buffer.h
index 748addf..20db280 100644
--- a/bin/ui/sketchy/buffer/shared_buffer.h
+++ b/bin/ui/sketchy/buffer/shared_buffer.h
@@ -7,7 +7,7 @@
 
 #include "garnet/public/lib/escher/impl/compute_shader.h"
 #include "lib/escher/resources/resource_recycler.h"
-#include "lib/escher/vk/buffer.h"
+#include "lib/escher/vk/buffer_factory.h"
 #include "lib/ui/scenic/cpp/resources.h"
 #include "lib/ui/scenic/cpp/session.h"
 
@@ -25,7 +25,7 @@
 class SharedBuffer final : public fxl::RefCountedThreadSafe<SharedBuffer> {
  public:
   static SharedBufferPtr New(scenic::Session* session,
-                             escher::ResourceRecycler* recycler,
+                             escher::BufferFactory* factory,
                              vk::DeviceSize capacity);
 
   // Reserve a chunk of |size| for use. The requested |size| MUST fit in the
@@ -49,7 +49,7 @@
  private:
   friend class SharedBufferPool;
 
-  SharedBuffer(scenic::Session* session, escher::ResourceRecycler* recycler,
+  SharedBuffer(scenic::Session* session, escher::BufferFactory* factory,
                vk::DeviceSize capacity);
 
   escher::BufferPtr escher_buffer_;
diff --git a/bin/ui/sketchy/buffer/shared_buffer_pool.cc b/bin/ui/sketchy/buffer/shared_buffer_pool.cc
index a3275dd..f5ab350 100644
--- a/bin/ui/sketchy/buffer/shared_buffer_pool.cc
+++ b/bin/ui/sketchy/buffer/shared_buffer_pool.cc
@@ -15,8 +15,8 @@
 namespace sketchy_service {
 
 SharedBufferPool::SharedBufferPool(scenic::Session* session,
-                                   escher::ResourceRecycler* recycler)
-    : session_(session), recycler_(recycler), weak_factory_(this) {}
+                                   escher::BufferFactory* factory)
+    : session_(session), buffer_factory_(factory), weak_factory_(this) {}
 
 SharedBufferPtr SharedBufferPool::GetBuffer(vk::DeviceSize capacity_req) {
   SharedBufferPtr buffer;
@@ -25,7 +25,7 @@
     buffer = std::move(free_buffers_[capacity].back());
     free_buffers_[capacity].pop_back();
   } else {
-    buffer = SharedBuffer::New(session_, recycler_, capacity);
+    buffer = SharedBuffer::New(session_, buffer_factory_, capacity);
   }
   used_buffers_.insert(buffer);
   return buffer;
diff --git a/bin/ui/sketchy/buffer/shared_buffer_pool.h b/bin/ui/sketchy/buffer/shared_buffer_pool.h
index 24d3108..e4f5b90 100644
--- a/bin/ui/sketchy/buffer/shared_buffer_pool.h
+++ b/bin/ui/sketchy/buffer/shared_buffer_pool.h
@@ -19,8 +19,7 @@
 // management and multi-buffering.
 class SharedBufferPool final {
  public:
-  SharedBufferPool(scenic::Session* session,
-                   escher::ResourceRecycler* recycler);
+  SharedBufferPool(scenic::Session* session, escher::BufferFactory* factory);
 
   // Gets a buffer with at least |capacity_req|.
   SharedBufferPtr GetBuffer(vk::DeviceSize capacity_req);
@@ -42,7 +41,7 @@
   void RecycleBuffer(SharedBufferPtr buffer);
 
   scenic::Session* const session_;
-  escher::ResourceRecycler* const recycler_;
+  escher::BufferFactory* const buffer_factory_;
   std::set<SharedBufferPtr> used_buffers_;
   // Groups free buffers into lists that contain buffers that have the same
   // capacity.
diff --git a/bin/ui/sketchy/canvas.cc b/bin/ui/sketchy/canvas.cc
index b6e4856..a63e96c 100644
--- a/bin/ui/sketchy/canvas.cc
+++ b/bin/ui/sketchy/canvas.cc
@@ -16,8 +16,9 @@
     : loop_(loop),
       session_(session),
       escher_(weak_escher),
-      shared_buffer_pool_(session, weak_escher->resource_recycler()),
-      unshared_buffer_factory_(weak_escher),
+      buffer_factory_(weak_escher->gpu_allocator(),
+                      weak_escher->resource_recycler()),
+      shared_buffer_pool_(session, &buffer_factory_),
       stroke_manager_(std::move(weak_escher)) {}
 
 void CanvasImpl::Init(
@@ -67,8 +68,7 @@
   };
   callbacks_.clear();
 
-  auto frame =
-      Frame(escher_.get(), &shared_buffer_pool_, &unshared_buffer_factory_);
+  auto frame = Frame(escher_.get(), &shared_buffer_pool_, &buffer_factory_);
   if (frame.init_failed()) {
     session_->Present(presentation_time, std::move(session_callback));
     return;
@@ -130,7 +130,7 @@
                               ::fuchsia::ui::sketchy::Stroke stroke) {
   return resource_map_.AddResource(
       id, fxl::MakeRefCounted<Stroke>(stroke_manager_.stroke_tessellator(),
-                                      &unshared_buffer_factory_));
+                                      &buffer_factory_));
 }
 
 bool CanvasImpl::CreateStrokeGroup(
diff --git a/bin/ui/sketchy/canvas.h b/bin/ui/sketchy/canvas.h
index 5888fbe..bb8348c 100644
--- a/bin/ui/sketchy/canvas.h
+++ b/bin/ui/sketchy/canvas.h
@@ -72,10 +72,9 @@
   async::Loop* const loop_;
   scenic::Session* const session_;
   const escher::EscherWeakPtr escher_;
+  escher::BufferFactoryAdapter buffer_factory_;
   // Buffers which are sent over the wire are constructed using this pool.
   SharedBufferPool shared_buffer_pool_;
-  // For non-shared buffers, we use this factory instead.
-  escher::BufferFactory unshared_buffer_factory_;
 
   ::fidl::VectorPtr<::fuchsia::ui::sketchy::Command> commands_;
   ResourceMap resource_map_;
diff --git a/examples/escher/common/demo_harness.cc b/examples/escher/common/demo_harness.cc
index 180f99f..1180a08 100644
--- a/examples/escher/common/demo_harness.cc
+++ b/examples/escher/common/demo_harness.cc
@@ -239,8 +239,8 @@
       image_info.height = swapchain_extent.height;
       image_info.usage = kImageUsageFlags;
 
-      auto escher_image = escher::Image::New(swapchain_image_owner_.get(),
-                                             image_info, im, nullptr);
+      auto escher_image = escher::Image::WrapVkImage(
+          swapchain_image_owner_.get(), image_info, im);
       FXL_CHECK(escher_image);
       escher_images.push_back(escher_image);
     }
diff --git a/examples/ui/shadertoy/service/imagepipe_shadertoy.cc b/examples/ui/shadertoy/service/imagepipe_shadertoy.cc
index 5b8acac..61d1e5d 100644
--- a/examples/ui/shadertoy/service/imagepipe_shadertoy.cc
+++ b/examples/ui/shadertoy/service/imagepipe_shadertoy.cc
@@ -6,11 +6,12 @@
 
 #include "garnet/examples/ui/shadertoy/service/renderer.h"
 #include "lib/escher/flib/fence.h"
+#include "lib/escher/resources/resource_recycler.h"
 #include "lib/escher/util/fuchsia_utils.h"
 #include "lib/escher/vk/framebuffer.h"
 #include "lib/escher/vk/gpu_mem.h"
 #include "lib/escher/vk/image.h"
-#include "lib/escher/vk/simple_image_factory.h"
+#include "lib/escher/vk/image_factory.h"
 
 namespace shadertoy {
 
@@ -52,8 +53,8 @@
   escher_image_info.sample_count = 1;
   escher_image_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
 
-  escher::SimpleImageFactory factory(escher()->resource_recycler(),
-                                     escher()->gpu_allocator());
+  escher::ImageFactoryAdapter factory(escher()->gpu_allocator(),
+                                      escher()->resource_recycler());
   for (size_t i = 0; i < kNumFramebuffers; ++i) {
     auto& fb = framebuffers_[i];
 
@@ -95,7 +96,7 @@
     image_info.tiling = fuchsia::images::Tiling::GPU_OPTIMAL;
 
     image_pipe_->AddImage(fb.image_pipe_id, std::move(image_info),
-                          std::move(vmo), image->memory_offset(),
+                          std::move(vmo), image->memory()->offset(),
                           image->memory()->size(),
                           fuchsia::images::MemoryType::VK_DEVICE_MEMORY);
   }
diff --git a/examples/ui/shadertoy/service/renderer.cc b/examples/ui/shadertoy/service/renderer.cc
index 882291b..5cba891 100644
--- a/examples/ui/shadertoy/service/renderer.cc
+++ b/examples/ui/shadertoy/service/renderer.cc
@@ -13,7 +13,7 @@
 #include "lib/escher/util/image_utils.h"
 #include "lib/escher/vk/framebuffer.h"
 #include "lib/escher/vk/image.h"
-#include "lib/escher/vk/simple_image_factory.h"
+#include "lib/escher/vk/image_factory.h"
 #include "lib/escher/vk/texture.h"
 
 namespace shadertoy {
@@ -196,8 +196,8 @@
   uint8_t channels[4];
   channels[0] = channels[1] = channels[2] = channels[3] = 255;
 
-  escher::SimpleImageFactory image_factory(escher()->resource_recycler(),
-                                           escher()->gpu_allocator());
+  escher::ImageFactoryAdapter image_factory(escher()->gpu_allocator(),
+                                            escher()->resource_recycler());
 
   auto image = escher::image_utils::NewRgbaImage(
       &image_factory, escher()->gpu_uploader(), 1, 1, channels);
diff --git a/lib/ui/gfx/engine/engine.cc b/lib/ui/gfx/engine/engine.cc
index dbadd1d..f8f0184 100644
--- a/lib/ui/gfx/engine/engine.cc
+++ b/lib/ui/gfx/engine/engine.cc
@@ -78,8 +78,8 @@
     : display_manager_(display_manager),
       escher_(std::move(weak_escher)),
       engine_renderer_(std::make_unique<EngineRenderer>(escher_)),
-      image_factory_(std::make_unique<escher::SimpleImageFactory>(
-          escher()->resource_recycler(), escher()->gpu_allocator())),
+      image_factory_(std::make_unique<escher::ImageFactoryAdapter>(
+          escher()->gpu_allocator(), escher()->resource_recycler())),
       rounded_rect_factory_(
           std::make_unique<escher::RoundedRectFactory>(escher_)),
       release_fence_signaller_(std::make_unique<escher::ReleaseFenceSignaller>(
diff --git a/lib/ui/gfx/engine/engine.h b/lib/ui/gfx/engine/engine.h
index 5955daf..edbc39a 100644
--- a/lib/ui/gfx/engine/engine.h
+++ b/lib/ui/gfx/engine/engine.h
@@ -16,7 +16,7 @@
 #include "lib/escher/renderer/batch_gpu_uploader.h"
 #include "lib/escher/resources/resource_recycler.h"
 #include "lib/escher/shape/rounded_rect_factory.h"
-#include "lib/escher/vk/simple_image_factory.h"
+#include "lib/escher/vk/image_factory.h"
 
 #include "garnet/lib/ui/gfx/displays/display_manager.h"
 #include "garnet/lib/ui/gfx/engine/frame_scheduler.h"
@@ -196,7 +196,7 @@
   ViewLinker view_linker_;
 
   EventTimestamper event_timestamper_;
-  std::unique_ptr<escher::SimpleImageFactory> image_factory_;
+  std::unique_ptr<escher::ImageFactoryAdapter> image_factory_;
   std::unique_ptr<escher::RoundedRectFactory> rounded_rect_factory_;
   std::unique_ptr<escher::ReleaseFenceSignaller> release_fence_signaller_;
   std::unique_ptr<SessionManager> session_manager_;
diff --git a/lib/ui/gfx/engine/session.cc b/lib/ui/gfx/engine/session.cc
index 3b24cc4..d5b26e6 100644
--- a/lib/ui/gfx/engine/session.cc
+++ b/lib/ui/gfx/engine/session.cc
@@ -1134,14 +1134,6 @@
 
 ResourcePtr Session::CreateBuffer(ResourceId id, MemoryPtr memory,
                                   uint32_t memory_offset, uint32_t num_bytes) {
-  // TODO(SCN-273): host memory should also be supported.
-  if (memory->is_host()) {
-    error_reporter_->ERROR() << "scenic_impl::gfx::Session::CreateBuffer(): "
-                                "memory must be of type "
-                                "ui.gfx.MemoryType.VK_DEVICE_MEMORY";
-    return ResourcePtr();
-  }
-
   if (memory_offset + num_bytes > memory->size()) {
     error_reporter_->ERROR() << "scenic_impl::gfx::Session::CreateBuffer(): "
                                 "buffer does not fit within memory (buffer "
@@ -1154,8 +1146,8 @@
   // Make a pointer to a subregion of the memory, if necessary.
   escher::GpuMemPtr gpu_mem =
       (memory_offset > 0 || num_bytes < memory->size())
-          ? memory->gpu_mem()->Allocate(num_bytes, memory_offset)
-          : memory->gpu_mem();
+          ? memory->GetGpuMem()->Suballocate(num_bytes, memory_offset)
+          : memory->GetGpuMem();
 
   return fxl::MakeRefCounted<Buffer>(this, id, std::move(gpu_mem),
                                      std::move(memory));
diff --git a/lib/ui/gfx/resources/gpu_image.cc b/lib/ui/gfx/resources/gpu_image.cc
index 07c4203..0ad21af 100644
--- a/lib/ui/gfx/resources/gpu_image.cc
+++ b/lib/ui/gfx/resources/gpu_image.cc
@@ -15,13 +15,12 @@
     ResourceType::kGpuImage | ResourceType::kImage | ResourceType::kImageBase,
     "GpuImage"};
 
-GpuImage::GpuImage(Session* session, ResourceId id, MemoryPtr memory,
-                   uint64_t memory_offset, escher::ImageInfo image_info,
-                   vk::Image vk_image)
-    : Image(session, id, GpuImage::kTypeInfo), memory_(std::move(memory)) {
-  image_ = escher::Image::New(session->engine()->escher_resource_recycler(),
-                              image_info, vk_image, memory_->gpu_mem(),
-                              memory_offset);
+GpuImage::GpuImage(Session* session, ResourceId id, escher::GpuMemPtr gpu_mem,
+                   escher::ImageInfo image_info, vk::Image vk_image)
+    : Image(session, id, GpuImage::kTypeInfo) {
+  image_ =
+      escher::Image::AdoptVkImage(session->engine()->escher_resource_recycler(),
+                                  image_info, vk_image, std::move(gpu_mem));
   FXL_CHECK(image_);
 }
 
@@ -105,9 +104,14 @@
     return nullptr;
   }
 
-  return fxl::AdoptRef(new GpuImage(session, id, std::move(memory),
-                                    memory_offset, escher_image_info,
-                                    vk_image));
+  // Make a pointer to a subregion of the memory, if necessary.
+  escher::GpuMemPtr gpu_mem =
+      (memory_offset > 0 || memory_reqs.size < memory->size())
+          ? memory->GetGpuMem()->Suballocate(memory_reqs.size, memory_offset)
+          : memory->GetGpuMem();
+
+  return fxl::AdoptRef(new GpuImage(session, id, std::move(gpu_mem),
+                                    escher_image_info, vk_image));
 }
 
 bool GpuImage::UpdatePixels() { return false; }
diff --git a/lib/ui/gfx/resources/gpu_image.h b/lib/ui/gfx/resources/gpu_image.h
index 35b23476..e65784f 100644
--- a/lib/ui/gfx/resources/gpu_image.h
+++ b/lib/ui/gfx/resources/gpu_image.h
@@ -46,17 +46,13 @@
  private:
   // Create an Image object from a VkImage.
   // |session| is the Session that this image can be referenced from.
+  // |id| is the ID of the resource.
+  // |gpu_mem| is the GPU memory that is associated with this image.
   // |image_info| specifies size, format, and other properties.
   // |vk_image| is the VkImage, whose lifetime is now controlled by this
   // object.
-  // |memory| is the GPU memory that is associated with this image.
-  // |memory_offset| is the offset in bytes into the memory where the image is
-  // stored.
-  GpuImage(Session* session, ResourceId id, MemoryPtr memory,
-           uint64_t memory_offset, escher::ImageInfo image_info,
-           vk::Image vk_image_);
-
-  MemoryPtr memory_;
+  GpuImage(Session* session, ResourceId id, escher::GpuMemPtr gpu_mem,
+           escher::ImageInfo image_info, vk::Image vk_image);
 };
 
 }  // namespace gfx
diff --git a/lib/ui/gfx/resources/memory.cc b/lib/ui/gfx/resources/memory.cc
index 9812273..094f542 100644
--- a/lib/ui/gfx/resources/memory.cc
+++ b/lib/ui/gfx/resources/memory.cc
@@ -92,11 +92,13 @@
     return nullptr;
   }
 
-  // TODO(SCN-1115): This currently does not request a mapped ptr, as it gets
-  // the uint8_t* directly from the VMO.
-  return escher::GpuMem::New(session()->engine()->vk_device(),
-                             vk::DeviceMemory(memory), vk::DeviceSize(size()),
-                             false /* needs_mapped_ptr */, memory_type_index);
+  // TODO(SCN-1115): If we can rely on all memory being importable into Vulkan
+  // (either as host or device memory), then we can always make a GpuMem object,
+  // and rely on its mapped pointer accessor instead of storing our own local
+  // uint8_t*.
+  return escher::GpuMem::AdoptVkMemory(
+      session()->engine()->vk_device(), vk::DeviceMemory(memory),
+      vk::DeviceSize(size()), is_host_ /* needs_mapped_ptr */);
 }
 
 zx::vmo Memory::DuplicateVmo() {
diff --git a/lib/ui/gfx/resources/memory.h b/lib/ui/gfx/resources/memory.h
index e1b5df0..0efa2f0 100644
--- a/lib/ui/gfx/resources/memory.h
+++ b/lib/ui/gfx/resources/memory.h
@@ -46,7 +46,7 @@
     // don't need additional logic here.
     return shared_vmo_->Map();
   }
-  const escher::GpuMemPtr& gpu_mem() {
+  const escher::GpuMemPtr& GetGpuMem() {
     // TODO(SCN-999): Passive lazy instantiation may not be ideal, either from a
     // performance standpoint, or from an external logic standpoint. Consider
     // acquire/release semantics. This would also map well to vkCopyBuffer
diff --git a/lib/ui/gfx/resources/snapshot/snapshotter.cc b/lib/ui/gfx/resources/snapshot/snapshotter.cc
index 8f4a009..16ea029 100644
--- a/lib/ui/gfx/resources/snapshot/snapshotter.cc
+++ b/lib/ui/gfx/resources/snapshot/snapshotter.cc
@@ -343,7 +343,7 @@
   region.imageExtent.width = image->width();
   region.imageExtent.height = image->height();
   region.imageExtent.depth = 1;
-  region.bufferOffset = image->memory_offset();
+  region.bufferOffset = image->memory()->offset();
 
   auto reader = gpu_uploader_->AcquireReader(image->memory()->size());
   reader->ReadImage(image, region, escher::SemaphorePtr());
diff --git a/lib/ui/gfx/swapchain/display_swapchain.cc b/lib/ui/gfx/swapchain/display_swapchain.cc
index f3c5d53..9d07531 100644
--- a/lib/ui/gfx/swapchain/display_swapchain.cc
+++ b/lib/ui/gfx/swapchain/display_swapchain.cc
@@ -179,9 +179,9 @@
     }
 
     Framebuffer buffer;
-    buffer.device_memory =
-        escher::GpuMem::New(device_, mem_result.value, memory_requirements.size,
-                            false /* needs_mapped_ptr */, memory_type_index);
+    buffer.device_memory = escher::GpuMem::AdoptVkMemory(
+        device_, mem_result.value, memory_requirements.size,
+        false /* needs_mapped_ptr */);
     FXL_CHECK(buffer.device_memory);
 
     // Wrap the image and device memory in a escher::Image.
@@ -191,10 +191,10 @@
     image_info.height = height_in_px;
     image_info.usage = image_usage;
 
-    // escher::Image::New() binds the memory to the image.
+    // escher::Image::AdoptVkImage() binds the memory to the image.
     buffer.escher_image =
-        escher::Image::New(resource_recycler, image_info, image_result.value,
-                           buffer.device_memory);
+        escher::Image::AdoptVkImage(resource_recycler, image_info,
+                                    image_result.value, buffer.device_memory);
 
     if (!buffer.escher_image) {
       FXL_LOG(ERROR) << "Creating escher::EscherImage failed.";
diff --git a/lib/ui/gfx/swapchain/vulkan_display_swapchain.cc b/lib/ui/gfx/swapchain/vulkan_display_swapchain.cc
index ec9bd66..50465c9 100644
--- a/lib/ui/gfx/swapchain/vulkan_display_swapchain.cc
+++ b/lib/ui/gfx/swapchain/vulkan_display_swapchain.cc
@@ -226,7 +226,9 @@
       image_info.width = swapchain_extent.width;
       image_info.height = swapchain_extent.height;
       image_info.usage = vk::ImageUsageFlagBits::eColorAttachment;
-      auto escher_image = escher::Image::New(recycler, image_info, im, nullptr);
+      // The swapchain maintains ownership of the vk::Image objects in the
+      // images array, so we only wrap them here, instead of adopting them.
+      auto escher_image = escher::Image::WrapVkImage(recycler, image_info, im);
       FXL_CHECK(escher_image);
       escher_images.push_back(escher_image);
     }
diff --git a/lib/ui/gfx/tests/image_pipe_unittest.cc b/lib/ui/gfx/tests/image_pipe_unittest.cc
index 1f5a8d3..27d9333 100644
--- a/lib/ui/gfx/tests/image_pipe_unittest.cc
+++ b/lib/ui/gfx/tests/image_pipe_unittest.cc
@@ -104,8 +104,8 @@
     escher::ImageInfo escher_info;
     escher_info.width = image_info.width;
     escher_info.height = image_info.height;
-    escher::ImagePtr escher_image = escher::Image::New(
-        dummy_resource_manager_, escher_info, vk::Image(), nullptr);
+    escher::ImagePtr escher_image = escher::Image::WrapVkImage(
+        dummy_resource_manager_, escher_info, vk::Image());
     FXL_CHECK(escher_image);
     auto image = fxl::AdoptRef(new DummyImage(session, id, escher_image));
 
diff --git a/lib/ui/gfx/tests/session_unittest.cc b/lib/ui/gfx/tests/session_unittest.cc
index f8b5279..258eb51 100644
--- a/lib/ui/gfx/tests/session_unittest.cc
+++ b/lib/ui/gfx/tests/session_unittest.cc
@@ -121,11 +121,9 @@
   status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &dup_vmo);
   EXPECT_TRUE(status == ZX_OK);
 
-  // TODO(SCN-273): When VK_HOST_MEMORY is supported, this test should be
-  // converted over. It only works now because we test on UMA platforms.
-  EXPECT_TRUE(Apply(scenic::NewCreateMemoryCmd(
-      1, std::move(dup_vmo), vmo_size,
-      fuchsia::images::MemoryType::VK_DEVICE_MEMORY)));
+  EXPECT_TRUE(Apply(
+      scenic::NewCreateMemoryCmd(1, std::move(dup_vmo), vmo_size,
+                                 fuchsia::images::MemoryType::HOST_MEMORY)));
   EXPECT_TRUE(Apply(scenic::NewCreateBufferCmd(2, 1, 0, vmo_size)));
   EXPECT_TRUE(
       Apply(scenic::NewCreateBufferCmd(3, 1, offset, vmo_size - offset)));
diff --git a/public/lib/escher/BUILD.gn b/public/lib/escher/BUILD.gn
index f8ef6cf..4b55b9c 100644
--- a/public/lib/escher/BUILD.gn
+++ b/public/lib/escher/BUILD.gn
@@ -452,12 +452,10 @@
     "util/tracer.h",
     "vk/buffer.cc",
     "vk/buffer.h",
-    "vk/buffer_factory.cc",
     "vk/buffer_factory.h",
     "vk/command_buffer.h",
     "vk/framebuffer.cc",
     "vk/framebuffer.h",
-    "vk/gpu_allocator.cc",
     "vk/gpu_allocator.h",
     "vk/gpu_mem.cc",
     "vk/gpu_mem.h",
@@ -490,8 +488,6 @@
     "vk/shader_program_factory.h",
     "vk/shader_variant_args.cc",
     "vk/shader_variant_args.h",
-    "vk/simple_image_factory.cc",
-    "vk/simple_image_factory.h",
     "vk/texture.cc",
     "vk/texture.h",
     "vk/vulkan_context.h",
diff --git a/public/lib/escher/escher.cc b/public/lib/escher/escher.cc
index c9886c8..81c7db6 100644
--- a/public/lib/escher/escher.cc
+++ b/public/lib/escher/escher.cc
@@ -196,8 +196,8 @@
                             vk::BufferUsageFlags usage_flags,
                             vk::MemoryPropertyFlags memory_property_flags) {
   TRACE_DURATION("gfx", "Escher::NewBuffer");
-  return Buffer::New(resource_recycler(), gpu_allocator(), size, usage_flags,
-                     memory_property_flags);
+  return gpu_allocator()->AllocateBuffer(resource_recycler(), size, usage_flags,
+                                         memory_property_flags);
 }
 
 TexturePtr Escher::NewTexture(vk::Format format, uint32_t width,
@@ -212,7 +212,8 @@
                        .height = height,
                        .sample_count = sample_count,
                        .usage = usage_flags};
-  ImagePtr image = Image::New(resource_recycler(), image_info, gpu_allocator());
+  ImagePtr image =
+      gpu_allocator()->AllocateImage(resource_recycler(), image_info);
   return fxl::MakeRefCounted<Texture>(resource_recycler(), std::move(image),
                                       filter, aspect_flags,
                                       use_unnormalized_coordinates);
@@ -274,7 +275,7 @@
 }
 
 uint64_t Escher::GetNumGpuBytesAllocated() {
-  return gpu_allocator()->total_slab_bytes();
+  return gpu_allocator()->GetTotalBytesAllocated();
 }
 
 uint32_t Escher::GetNumOutstandingFrames() const {
diff --git a/public/lib/escher/hmd/pose_buffer_latching_shader.cc b/public/lib/escher/hmd/pose_buffer_latching_shader.cc
index 23c37b3..7628af6 100644
--- a/public/lib/escher/hmd/pose_buffer_latching_shader.cc
+++ b/public/lib/escher/hmd/pose_buffer_latching_shader.cc
@@ -10,6 +10,7 @@
 #include "garnet/public/lib/escher/resources/resource_recycler.h"
 #include "garnet/public/lib/escher/scene/camera.h"
 #include "garnet/public/lib/escher/vk/buffer.h"
+#include "garnet/public/lib/escher/vk/gpu_allocator.h"
 #include "garnet/public/lib/escher/vk/texture.h"
 
 namespace escher {
@@ -126,9 +127,9 @@
       vk::BufferUsageFlagBits::eUniformBuffer |
       vk::BufferUsageFlagBits::eStorageBuffer;
 
-  auto output_buffer = Buffer::New(
-      escher_->resource_recycler(), frame->gpu_allocator(), buffer_size,
-      kOutputBufferUsageFlags, kOutputMemoryPropertyFlags);
+  auto output_buffer = frame->gpu_allocator()->AllocateBuffer(
+      escher_->resource_recycler(), buffer_size, kOutputBufferUsageFlags,
+      kOutputMemoryPropertyFlags);
 
   const vk::MemoryPropertyFlags kVpMemoryPropertyFlags =
       vk::MemoryPropertyFlagBits::eHostVisible |
@@ -136,9 +137,9 @@
   const vk::BufferUsageFlags kVpBufferUsageFlags =
       vk::BufferUsageFlagBits::eUniformBuffer;
 
-  auto vp_matrices_buffer = Buffer::New(
-      escher_->resource_recycler(), frame->gpu_allocator(), 4 * k4x4MatrixSize,
-      kVpBufferUsageFlags, kVpMemoryPropertyFlags);
+  auto vp_matrices_buffer = frame->gpu_allocator()->AllocateBuffer(
+      escher_->resource_recycler(), 4 * k4x4MatrixSize, kVpBufferUsageFlags,
+      kVpMemoryPropertyFlags);
 
   auto command_buffer = frame->command_buffer();
 
diff --git a/public/lib/escher/impl/gpu_mem_slab.cc b/public/lib/escher/impl/gpu_mem_slab.cc
index 7a133aa..8fcfb8d 100644
--- a/public/lib/escher/impl/gpu_mem_slab.cc
+++ b/public/lib/escher/impl/gpu_mem_slab.cc
@@ -6,7 +6,7 @@
 
 #include "lib/escher/impl/vulkan_utils.h"
 #include "lib/escher/util/trace_macros.h"
-#include "lib/escher/vk/gpu_allocator.h"
+#include "lib/escher/vk/naive_gpu_allocator.h"
 #include "lib/fxl/logging.h"
 
 namespace {
@@ -25,58 +25,16 @@
 
 GpuMemSlab::GpuMemSlab(vk::Device device, vk::DeviceMemory base,
                        vk::DeviceSize size, bool needs_mapped_ptr,
-                       uint32_t memory_type_index, GpuAllocator* allocator)
+                       NaiveGpuAllocator* allocator)
     : GpuMem(base, size, 0,
              needs_mapped_ptr ? GetMappedPtr(device, base, size) : nullptr),
       device_(device),
-      memory_type_index_(memory_type_index),
       allocator_(allocator) {
   if (allocator_) {
-    allocator_->OnSlabCreated(size);
+    allocator_->OnSlabCreated(this->size());
   }
 }
 
-GpuMemSlabPtr GpuMemSlab::New(vk::Device device,
-                              vk::PhysicalDevice physical_device,
-                              vk::MemoryRequirements reqs,
-                              vk::MemoryPropertyFlags flags,
-                              GpuAllocator* allocator) {
-  TRACE_DURATION("gfx", "escher::GpuMemSlab::New");
-  vk::DeviceMemory vk_mem;
-  uint32_t memory_type_index = 0;
-  bool needs_mapped_ptr = false;
-  if (!device) {
-    // To support testing, we allow a null device, and respond by creating
-    // a GpuMemSlab with a null vk::DeviceMemory.
-  } else {
-    // Determine whether we will need to map the memory.
-    if (flags & vk::MemoryPropertyFlagBits::eHostVisible) {
-      // We don't currently provide an interface for flushing mapped data, so
-      // ensure that the allocated memory is cache-coherent.  This is more
-      // convenient anyway.
-      flags |= vk::MemoryPropertyFlagBits::eHostCoherent;
-      needs_mapped_ptr = true;
-    }
-
-    // TODO: cache flags for efficiency? Or perhaps change signature of this
-    // method to directly take the memory-type index.
-    memory_type_index =
-        GetMemoryTypeIndex(physical_device, reqs.memoryTypeBits, flags);
-
-    {
-      TRACE_DURATION("gfx", "escher::GpuMemSlab::New[alloc]");
-      vk::MemoryAllocateInfo info;
-      info.allocationSize = reqs.size;
-      info.memoryTypeIndex = memory_type_index;
-      vk_mem = ESCHER_CHECKED_VK_RESULT(device.allocateMemory(info));
-    }
-  }
-
-  return fxl::AdoptRef(new GpuMemSlab(device, vk_mem, reqs.size,
-                                      needs_mapped_ptr, memory_type_index,
-                                      allocator));
-}
-
 GpuMemSlab::~GpuMemSlab() {
   if (device_ && base()) {
     if (mapped_ptr()) {
@@ -89,12 +47,5 @@
   }
 }
 
-void GpuMemSlab::OnAllocationDestroyed(vk::DeviceSize size,
-                                       vk::DeviceSize offset) {
-  if (allocator_) {
-    allocator_->OnSuballocationDestroyed(this, size, offset);
-  }
-}
-
 }  // namespace impl
 }  // namespace escher
diff --git a/public/lib/escher/impl/gpu_mem_slab.h b/public/lib/escher/impl/gpu_mem_slab.h
index 34f7930..350a0ba 100644
--- a/public/lib/escher/impl/gpu_mem_slab.h
+++ b/public/lib/escher/impl/gpu_mem_slab.h
@@ -12,41 +12,31 @@
 
 namespace escher {
 
-class GpuAllocator;
+class NaiveGpuAllocator;
 
 namespace impl {
 
 class GpuMemSlab;
 typedef fxl::RefPtr<GpuMemSlab> GpuMemSlabPtr;
 
-// GpuMemSlab represents a single Vulkan memory allocation, which a GpuAllocator
-// may use to sub-allocate multiple GpuMem instances.  It can only be created
-// via GpuMem::New() and GpuAllocator::AllocateSlab().  See class comment of
-// GpuAllocator for more details.
+// GpuMemSlab represents a single Vulkan memory allocation, created directly
+// through a vkDevice. It should only be created via specific subclasses of
+// GpuAllocator, or through GpuMem::AdoptMemory when transferring ownership of a
+// existing vk::DeviceMemory object.
 class GpuMemSlab final : public GpuMem {
  public:
   ~GpuMemSlab() override;
 
  private:
-  // Instances of GpuMemSlab may only be created by GpuAllocator::AllocateSlab()
-  // and GpuMem::New().
-  friend class ::escher::GpuAllocator;
+  // Instances of GpuMemSlab may only be created by
+  // NaiveGpuAllocator::AllocateMemory() and GpuMem::AdoptMemory.
+  friend class ::escher::NaiveGpuAllocator;
   friend class ::escher::GpuMem;
-  static GpuMemSlabPtr New(vk::Device device,
-                           vk::PhysicalDevice physical_device,
-                           vk::MemoryRequirements reqs,
-                           vk::MemoryPropertyFlags flags,
-                           GpuAllocator* allocator);
   GpuMemSlab(vk::Device device, vk::DeviceMemory base, vk::DeviceSize size,
-             bool needs_mapped_ptr, uint32_t memory_type_index,
-             GpuAllocator* allocator);
-
-  void OnAllocationDestroyed(vk::DeviceSize size,
-                             vk::DeviceSize offset) override;
+             bool needs_mapped_ptr, NaiveGpuAllocator* allocator);
 
   vk::Device device_;
-  uint32_t memory_type_index_;
-  GpuAllocator* allocator_;
+  NaiveGpuAllocator* allocator_;
 
   FXL_DISALLOW_COPY_AND_ASSIGN(GpuMemSlab);
 };
diff --git a/public/lib/escher/impl/gpu_mem_suballocation.cc b/public/lib/escher/impl/gpu_mem_suballocation.cc
index 00ece9d..557ca4c 100644
--- a/public/lib/escher/impl/gpu_mem_suballocation.cc
+++ b/public/lib/escher/impl/gpu_mem_suballocation.cc
@@ -13,9 +13,5 @@
              mem->mapped_ptr() ? mem->mapped_ptr() + offset : nullptr),
       mem_(std::move(mem)) {}
 
-GpuMemSuballocation::~GpuMemSuballocation() {
-  mem_->OnAllocationDestroyed(size(), offset());
-}
-
 }  // namespace impl
 }  // namespace escher
diff --git a/public/lib/escher/impl/gpu_mem_suballocation.h b/public/lib/escher/impl/gpu_mem_suballocation.h
index 5f77042..61041f1 100644
--- a/public/lib/escher/impl/gpu_mem_suballocation.h
+++ b/public/lib/escher/impl/gpu_mem_suballocation.h
@@ -10,13 +10,10 @@
 namespace escher {
 namespace impl {
 
-// Helper class for GpuMem::Allocate(), which returns an instance of this class.
-// When the instance is destroyed, it notifies the GpuMem that it was allocated
-// from.
+// Helper class for GpuMem::Suballocate(), which returns an instance of this
+// class. When this instance is destroyed, it releases its strong reference on
+// the backing memory object.
 class GpuMemSuballocation final : public GpuMem {
- public:
-  ~GpuMemSuballocation() override;
-
  private:
   friend class ::escher::GpuMem;
   GpuMemSuballocation(GpuMemPtr mem, vk::DeviceSize size,
diff --git a/public/lib/escher/impl/gpu_uploader.cc b/public/lib/escher/impl/gpu_uploader.cc
index fa8b9a0..b332bb9 100644
--- a/public/lib/escher/impl/gpu_uploader.cc
+++ b/public/lib/escher/impl/gpu_uploader.cc
@@ -170,9 +170,8 @@
   size = std::max(kMinBufferSize, size * kOverAllocationFactor);
   auto memory_properties = vk::MemoryPropertyFlagBits::eHostVisible |
                            vk::MemoryPropertyFlagBits::eHostCoherent;
-  current_buffer_ =
-      Buffer::New(this, allocator_, size, vk::BufferUsageFlagBits::eTransferSrc,
-                  memory_properties);
+  current_buffer_ = allocator_->AllocateBuffer(
+      this, size, vk::BufferUsageFlagBits::eTransferSrc, memory_properties);
 }
 
 void GpuUploader::RecycleResource(std::unique_ptr<Resource> resource) {
diff --git a/public/lib/escher/impl/image_cache.cc b/public/lib/escher/impl/image_cache.cc
index b875e59..dce8c60 100644
--- a/public/lib/escher/impl/image_cache.cc
+++ b/public/lib/escher/impl/image_cache.cc
@@ -29,9 +29,10 @@
 
   // Allocate memory and bind it to the image.
   vk::MemoryRequirements reqs = vk_device().getImageMemoryRequirements(image);
-  GpuMemPtr memory = allocator_->Allocate(reqs, info.memory_flags);
+  GpuMemPtr memory = allocator_->AllocateMemory(reqs, info.memory_flags);
 
-  ImagePtr escher_image = Image::New(this, info, image, std::move(memory));
+  ImagePtr escher_image =
+      Image::AdoptVkImage(this, info, image, std::move(memory));
   FXL_CHECK(escher_image);
 
   return escher_image;
diff --git a/public/lib/escher/impl/mesh_manager.cc b/public/lib/escher/impl/mesh_manager.cc
index c1f63e0..61e1e98 100644
--- a/public/lib/escher/impl/mesh_manager.cc
+++ b/public/lib/escher/impl/mesh_manager.cc
@@ -11,6 +11,7 @@
 #include "lib/escher/impl/vulkan_utils.h"
 #include "lib/escher/resources/resource_recycler.h"
 #include "lib/escher/vk/buffer.h"
+#include "lib/escher/vk/gpu_allocator.h"
 #include "lib/escher/vk/vulkan_context.h"
 
 namespace escher {
@@ -114,18 +115,18 @@
   GpuAllocator* allocator = manager_->allocator_;
 
   // TODO: use eTransferDstOptimal instead of eTransferDst?
-  auto vertex_buffer = Buffer::New(manager_->resource_recycler(), allocator,
-                                   vertex_count_ * vertex_stride_,
-                                   vk::BufferUsageFlagBits::eVertexBuffer |
-                                       vk::BufferUsageFlagBits::eTransferSrc |
-                                       vk::BufferUsageFlagBits::eTransferDst,
-                                   vk::MemoryPropertyFlagBits::eDeviceLocal);
-  auto index_buffer = Buffer::New(manager_->resource_recycler(), allocator,
-                                  index_count_ * sizeof(uint32_t),
-                                  vk::BufferUsageFlagBits::eIndexBuffer |
-                                      vk::BufferUsageFlagBits::eTransferSrc |
-                                      vk::BufferUsageFlagBits::eTransferDst,
-                                  vk::MemoryPropertyFlagBits::eDeviceLocal);
+  auto vertex_buffer = allocator->AllocateBuffer(
+      manager_->resource_recycler(), vertex_count_ * vertex_stride_,
+      vk::BufferUsageFlagBits::eVertexBuffer |
+          vk::BufferUsageFlagBits::eTransferSrc |
+          vk::BufferUsageFlagBits::eTransferDst,
+      vk::MemoryPropertyFlagBits::eDeviceLocal);
+  auto index_buffer = allocator->AllocateBuffer(
+      manager_->resource_recycler(), index_count_ * sizeof(uint32_t),
+      vk::BufferUsageFlagBits::eIndexBuffer |
+          vk::BufferUsageFlagBits::eTransferSrc |
+          vk::BufferUsageFlagBits::eTransferDst,
+      vk::MemoryPropertyFlagBits::eDeviceLocal);
 
   vertex_writer_.WriteBuffer(vertex_buffer, {0, 0, vertex_buffer->size()},
                              Semaphore::New(device));
diff --git a/public/lib/escher/impl/uniform_buffer_pool.cc b/public/lib/escher/impl/uniform_buffer_pool.cc
index 271b139..ffa5241 100644
--- a/public/lib/escher/impl/uniform_buffer_pool.cc
+++ b/public/lib/escher/impl/uniform_buffer_pool.cc
@@ -58,7 +58,7 @@
 
   // Allocate enough memory for all of the buffers.
   reqs.size *= kBufferBatchSize;
-  auto batch_mem = allocator_->Allocate(reqs, flags_);
+  auto batch_mem = allocator_->AllocateMemory(reqs, flags_);
 
   // See below: when OnReceiveOwnable() receives the newly-allocated buffer we
   // need to know that it is new and can therefore be used immediately instead
@@ -73,7 +73,7 @@
     vk_device().getBufferMemoryRequirements(new_buffers[i]);
 
     // Sub-allocate memory for each buffer.
-    auto mem = batch_mem->Allocate(buffer_size_, i * buffer_size_);
+    auto mem = batch_mem->Suballocate(buffer_size_, i * buffer_size_);
 
     // Workaround for dealing with RefPtr/Reffable Adopt() semantics.  Let the
     // RefPtr go out of scope immediately; the Buffer will be added to
diff --git a/public/lib/escher/impl/vulkan_utils.cc b/public/lib/escher/impl/vulkan_utils.cc
index 8ef6f31..1eef9d5 100644
--- a/public/lib/escher/impl/vulkan_utils.cc
+++ b/public/lib/escher/impl/vulkan_utils.cc
@@ -55,7 +55,8 @@
     }
     type_bits >>= 1;
   }
-  FXL_CHECK(false);
+  FXL_CHECK(false) << "Could not find memory with properties "
+                   << (VkMemoryPropertyFlags)required_properties;
   return 0;
 }
 
diff --git a/public/lib/escher/impl/wobble_modifier_absorber.cc b/public/lib/escher/impl/wobble_modifier_absorber.cc
index a96f167..d2d234f 100644
--- a/public/lib/escher/impl/wobble_modifier_absorber.cc
+++ b/public/lib/escher/impl/wobble_modifier_absorber.cc
@@ -6,6 +6,7 @@
 #include "lib/escher/escher.h"
 #include "lib/escher/impl/command_buffer_pool.h"
 #include "lib/escher/resources/resource_recycler.h"
+#include "lib/escher/vk/gpu_allocator.h"
 
 namespace escher {
 namespace impl {
@@ -131,11 +132,11 @@
 
     auto& vertex_buffer = object.shape().mesh()->attribute_buffer(0).buffer;
     auto compute_buffer =
-        Buffer::New(recycler_, allocator_, vertex_buffer->size(),
-                    vk::BufferUsageFlagBits::eVertexBuffer |
-                        vk::BufferUsageFlagBits::eStorageBuffer |
-                        vk::BufferUsageFlagBits::eTransferDst,
-                    vk::MemoryPropertyFlagBits::eDeviceLocal);
+        allocator_->AllocateBuffer(recycler_, vertex_buffer->size(),
+                                   vk::BufferUsageFlagBits::eVertexBuffer |
+                                       vk::BufferUsageFlagBits::eStorageBuffer |
+                                       vk::BufferUsageFlagBits::eTransferDst,
+                                   vk::MemoryPropertyFlagBits::eDeviceLocal);
     // TODO(longqic): Do not allocate a new uniform buffer for each new object.
     // See ModelDisplayListBuilder::PrepareUniformBufferForWriteOfSize().
     auto per_object_uniform_buffer =
@@ -212,9 +213,9 @@
 }
 
 BufferPtr WobbleModifierAbsorber::NewUniformBuffer(vk::DeviceSize size) {
-  return Buffer::New(recycler_, allocator_, size,
-                     vk::BufferUsageFlagBits::eUniformBuffer,
-                     vk::MemoryPropertyFlagBits::eHostVisible);
+  return allocator_->AllocateBuffer(recycler_, size,
+                                    vk::BufferUsageFlagBits::eUniformBuffer,
+                                    vk::MemoryPropertyFlagBits::eHostVisible);
 }
 
 void WobbleModifierAbsorber::ApplyBarrierForUniformBuffer(
diff --git a/public/lib/escher/renderer/buffer_cache.cc b/public/lib/escher/renderer/buffer_cache.cc
index dfe3a0f..025fbe3 100644
--- a/public/lib/escher/renderer/buffer_cache.cc
+++ b/public/lib/escher/renderer/buffer_cache.cc
@@ -66,8 +66,8 @@
     free_buffers_by_id_.erase(info_itr);
   } else {
     // Construct a new buffer of the requested size.
-    buffer = Buffer::New(this, gpu_allocator_.get(), vk_size, kUsageFlags,
-                         kMemoryPropertyFlags);
+    buffer = gpu_allocator_->AllocateBuffer(this, vk_size, kUsageFlags,
+                                            kMemoryPropertyFlags);
   }
 
   return buffer;
diff --git a/public/lib/escher/shape/rounded_rect_factory.cc b/public/lib/escher/shape/rounded_rect_factory.cc
index 028f0f9..f1385e3 100644
--- a/public/lib/escher/shape/rounded_rect_factory.cc
+++ b/public/lib/escher/shape/rounded_rect_factory.cc
@@ -14,7 +14,8 @@
 
 RoundedRectFactory::RoundedRectFactory(EscherWeakPtr weak_escher)
     : ResourceRecycler(std::move(weak_escher)),
-      buffer_factory_(std::make_unique<BufferFactory>(GetEscherWeakPtr())) {}
+      buffer_factory_(GetEscherWeakPtr()->gpu_allocator(),
+                      GetEscherWeakPtr()->resource_recycler()) {}
 
 RoundedRectFactory::~RoundedRectFactory() {}
 
@@ -35,10 +36,10 @@
       vertex_count * (primary_buffer_stride + secondary_buffer_stride);
 
   auto vertex_buffer =
-      buffer_factory_->NewBuffer(vertex_buffer_size,
-                                 vk::BufferUsageFlagBits::eVertexBuffer |
-                                     vk::BufferUsageFlagBits::eTransferDst,
-                                 vk::MemoryPropertyFlagBits::eDeviceLocal);
+      buffer_factory_.NewBuffer(vertex_buffer_size,
+                                vk::BufferUsageFlagBits::eVertexBuffer |
+                                    vk::BufferUsageFlagBits::eTransferDst,
+                                vk::MemoryPropertyFlagBits::eDeviceLocal);
 
   const auto bounding_box =
       BoundingBox::NewChecked(-0.5f * vec3(spec.width, spec.height, 0),
@@ -92,10 +93,10 @@
     size_t index_buffer_size = index_count * sizeof(MeshSpec::IndexType);
 
     index_buffer_ =
-        buffer_factory_->NewBuffer(index_buffer_size,
-                                   vk::BufferUsageFlagBits::eIndexBuffer |
-                                       vk::BufferUsageFlagBits::eTransferDst,
-                                   vk::MemoryPropertyFlagBits::eDeviceLocal);
+        buffer_factory_.NewBuffer(index_buffer_size,
+                                  vk::BufferUsageFlagBits::eIndexBuffer |
+                                      vk::BufferUsageFlagBits::eTransferDst,
+                                  vk::MemoryPropertyFlagBits::eDeviceLocal);
 
     auto writer = batch_gpu_uploader->AcquireWriter(index_buffer_size);
     GenerateRoundedRectIndices(spec, mesh_spec, writer->host_ptr(),
diff --git a/public/lib/escher/shape/rounded_rect_factory.h b/public/lib/escher/shape/rounded_rect_factory.h
index 751ce7b..f80f4f2 100644
--- a/public/lib/escher/shape/rounded_rect_factory.h
+++ b/public/lib/escher/shape/rounded_rect_factory.h
@@ -7,11 +7,11 @@
 
 #include "lib/escher/resources/resource_recycler.h"
 #include "lib/escher/shape/rounded_rect.h"
+#include "lib/escher/vk/buffer_factory.h"
 
 namespace escher {
 
 class BatchGpuUploader;
-class BufferFactory;
 
 class RoundedRectFactory : private ResourceRecycler {
  public:
@@ -26,8 +26,7 @@
                            const MeshSpec& mesh_spec,
                            BatchGpuUploader* gpu_uploader);
 
-  std::unique_ptr<BufferFactory> buffer_factory_;
-
+  BufferFactoryAdapter buffer_factory_;
   BufferPtr index_buffer_;
 };
 
diff --git a/public/lib/escher/test/gpu_mem_unittest.cc b/public/lib/escher/test/gpu_mem_unittest.cc
index 44ff107..3907a42 100644
--- a/public/lib/escher/test/gpu_mem_unittest.cc
+++ b/public/lib/escher/test/gpu_mem_unittest.cc
@@ -3,11 +3,13 @@
 // found in the LICENSE file.
 
 #include "lib/escher/vk/gpu_mem.h"
+#include "gtest/gtest.h"
 #include "lib/escher/impl/gpu_mem_slab.h"
+#include "lib/escher/test/gtest_vulkan.h"
 #include "lib/escher/vk/gpu_allocator.h"
 #include "lib/escher/vk/naive_gpu_allocator.h"
-
-#include "gtest/gtest.h"
+#include "lib/escher/vk/vulkan_context.h"
+#include "lib/escher/vk/vulkan_device_queues.h"
 
 #include <iostream>
 
@@ -18,10 +20,10 @@
 // different ways.
 const vk::DeviceSize kDeviceSize = 1000;
 void TestSubAllocation(GpuMemPtr mem) {
-  auto sub_alloc1 = mem->Allocate(kDeviceSize, 0);
-  auto sub_alloc2 = mem->Allocate(kDeviceSize + 1, 0);
-  auto sub_alloc3 = mem->Allocate(kDeviceSize, 1);
-  auto sub_alloc4 = mem->Allocate(kDeviceSize, 0);
+  auto sub_alloc1 = mem->Suballocate(kDeviceSize, 0);
+  auto sub_alloc2 = mem->Suballocate(kDeviceSize + 1, 0);
+  auto sub_alloc3 = mem->Suballocate(kDeviceSize, 1);
+  auto sub_alloc4 = mem->Suballocate(kDeviceSize, 0);
   // Valid sub-allocation.
   EXPECT_NE(nullptr, sub_alloc1.get());
   // Invalid sub-allocation due to increased size.
@@ -32,14 +34,14 @@
   EXPECT_NE(nullptr, sub_alloc4.get());
 
   // Can sub-allocate from a sub-allocation...
-  auto sub_alloc5 = sub_alloc1->Allocate(kDeviceSize / 2, kDeviceSize / 2);
+  auto sub_alloc5 = sub_alloc1->Suballocate(kDeviceSize / 2, kDeviceSize / 2);
   EXPECT_NE(nullptr, sub_alloc5.get());
   // ... and sub-allocate again from that sub-allocation.  As before, the size
   // and offset of the sub-allocation must fit within the parent.
-  auto sub_alloc6 = sub_alloc5->Allocate(kDeviceSize / 2, 0);
-  auto sub_alloc7 = sub_alloc5->Allocate(kDeviceSize / 2 + 1, 0);
-  auto sub_alloc8 = sub_alloc5->Allocate(kDeviceSize / 2, 1);
-  auto sub_alloc9 = sub_alloc5->Allocate(kDeviceSize / 2, 0);
+  auto sub_alloc6 = sub_alloc5->Suballocate(kDeviceSize / 2, 0);
+  auto sub_alloc7 = sub_alloc5->Suballocate(kDeviceSize / 2 + 1, 0);
+  auto sub_alloc8 = sub_alloc5->Suballocate(kDeviceSize / 2, 1);
+  auto sub_alloc9 = sub_alloc5->Suballocate(kDeviceSize / 2, 0);
   // Valid sub-allocation.
   EXPECT_NE(nullptr, sub_alloc6.get());
   // Invalid sub-allocation due to increased size.
@@ -52,126 +54,56 @@
 
 // This test should be updated to include all hashed types used by Escher.
 TEST(GpuMem, WrapExisting) {
-  const uint32_t kMemoryTypeIndex = 0;
-  auto mem = GpuMem::New(vk::Device(), vk::DeviceMemory(), kDeviceSize,
-                         false /* needs_mapped_ptr */, kMemoryTypeIndex);
+  auto mem = GpuMem::AdoptVkMemory(vk::Device(), vk::DeviceMemory(),
+                                   kDeviceSize, false /* needs_mapped_ptr */);
   TestSubAllocation(mem);
 }
 
-TEST(GpuMem, NaiveAllocator) {
-  VulkanContext context(vk::Instance(), vk::PhysicalDevice(), vk::Device(),
-                        vk::Queue(), 0, vk::Queue(), 0);
-  NaiveGpuAllocator allocator(context);
+VK_TEST(GpuMem, NaiveAllocator) {
+  escher::VulkanInstance::Params instance_params(
+      {{"VK_LAYER_LUNARG_standard_validation"},
+       {VK_EXT_DEBUG_REPORT_EXTENSION_NAME},
+       false});
+
+  auto vulkan_instance =
+      escher::VulkanInstance::New(std::move(instance_params));
+  auto vulkan_device = escher::VulkanDeviceQueues::New(vulkan_instance, {});
+  NaiveGpuAllocator allocator(vulkan_device->GetVulkanContext());
 
   // Standard sub-allocation tests.
-  auto alloc =
-      allocator.Allocate({kDeviceSize, 0, 0}, vk::MemoryPropertyFlags());
-  EXPECT_EQ(kDeviceSize, allocator.total_slab_bytes());
+  auto alloc = allocator.AllocateMemory(
+      {kDeviceSize, 0, 0xffffffff}, vk::MemoryPropertyFlagBits::eDeviceLocal);
+  EXPECT_EQ(kDeviceSize, allocator.GetTotalBytesAllocated());
   TestSubAllocation(alloc);
 
   // Adding sub-allocations doesn't increase slab-count.
-  EXPECT_EQ(1U, allocator.slab_count());
-  auto sub_alloc1 = alloc->Allocate(kDeviceSize, 0);
-  auto sub_alloc1a = sub_alloc1->Allocate(kDeviceSize, 0);
-  auto sub_alloc1b = sub_alloc1->Allocate(kDeviceSize, 0);
-  auto sub_alloc2 = alloc->Allocate(kDeviceSize, 0);
-  auto sub_alloc2a = sub_alloc2->Allocate(kDeviceSize, 0);
-  auto sub_alloc2b = sub_alloc2->Allocate(kDeviceSize, 0);
-  EXPECT_EQ(1U, allocator.slab_count());
+  EXPECT_EQ(kDeviceSize, allocator.GetTotalBytesAllocated());
+  auto sub_alloc1 = alloc->Suballocate(kDeviceSize, 0);
+  auto sub_alloc1a = sub_alloc1->Suballocate(kDeviceSize, 0);
+  auto sub_alloc1b = sub_alloc1->Suballocate(kDeviceSize, 0);
+  auto sub_alloc2 = alloc->Suballocate(kDeviceSize, 0);
+  auto sub_alloc2a = sub_alloc2->Suballocate(kDeviceSize, 0);
+  auto sub_alloc2b = sub_alloc2->Suballocate(kDeviceSize, 0);
+  EXPECT_EQ(kDeviceSize, allocator.GetTotalBytesAllocated());
 
   // Allocating then freeing increases/decreases the slab-count.
-  auto alloc2 =
-      allocator.Allocate({kDeviceSize, 0, 0}, vk::MemoryPropertyFlags());
-  EXPECT_EQ(2U * kDeviceSize, allocator.total_slab_bytes());
-  EXPECT_EQ(2U, allocator.slab_count());
+  auto alloc2 = allocator.AllocateMemory(
+      {kDeviceSize, 0, 0xffffffff}, vk::MemoryPropertyFlagBits::eHostVisible);
+  EXPECT_EQ(2U * kDeviceSize, allocator.GetTotalBytesAllocated());
   alloc2 = nullptr;
-  EXPECT_EQ(1U, allocator.slab_count());
+  EXPECT_EQ(kDeviceSize, allocator.GetTotalBytesAllocated());
 
   // Sub-allocations keep parent allocations alive.
   alloc = nullptr;
-  EXPECT_EQ(1U, allocator.slab_count());
+  EXPECT_EQ(kDeviceSize, allocator.GetTotalBytesAllocated());
   sub_alloc1 = nullptr;
   sub_alloc1a = nullptr;
   sub_alloc1b = nullptr;
   sub_alloc2 = nullptr;
   sub_alloc2a = nullptr;
-  EXPECT_EQ(1U, allocator.slab_count());
-  EXPECT_EQ(kDeviceSize, allocator.total_slab_bytes());
+  EXPECT_EQ(kDeviceSize, allocator.GetTotalBytesAllocated());
   sub_alloc2b = nullptr;
-  EXPECT_EQ(0U, allocator.slab_count());
-  EXPECT_EQ(0U, allocator.total_slab_bytes());
-}
-
-// Used to test GpuAllocator sub-allocation callbacks.
-class NaiveGpuAllocatorForCallbackTest : public NaiveGpuAllocator {
- public:
-  explicit NaiveGpuAllocatorForCallbackTest(GpuMem** last_slab,
-                                            vk::DeviceSize* last_size,
-                                            vk::DeviceSize* last_offset)
-      : NaiveGpuAllocator(VulkanContext()),
-        last_slab_(last_slab),
-        last_size_(last_size),
-        last_offset_(last_offset) {}
-
-  void OnSuballocationDestroyed(GpuMem* slab, vk::DeviceSize size,
-                                vk::DeviceSize offset) override {
-    *last_slab_ = slab;
-    *last_size_ = size;
-    *last_offset_ = offset;
-  }
-
- private:
-  GpuMem** const last_slab_ = nullptr;
-  vk::DeviceSize* const last_size_ = nullptr;
-  vk::DeviceSize* const last_offset_ = nullptr;
-};
-
-// Note: this test relies on an allocator that returns raw GpuMemSlabs, not
-// sub-allocations.
-TEST(GpuMem, AllocatorCallbacks) {
-  GpuMem* last_slab = nullptr;
-  vk::DeviceSize last_size = 0;
-  vk::DeviceSize last_offset = 0;
-  NaiveGpuAllocatorForCallbackTest allocator(&last_slab, &last_size,
-                                             &last_offset);
-
-  auto alloc1 =
-      allocator.Allocate({kDeviceSize, 0, 0}, vk::MemoryPropertyFlags());
-  auto alloc2 =
-      allocator.Allocate({kDeviceSize, 0, 0}, vk::MemoryPropertyFlags());
-  EXPECT_EQ(nullptr, last_slab);
-
-  auto sub_alloc1a = alloc1->Allocate(10, 11);
-  auto sub_alloc1b = alloc1->Allocate(20, 12);
-  auto sub_alloc2a = alloc2->Allocate(30, 13);
-  auto sub_alloc2b = alloc2->Allocate(40, 14);
-  // Still no sub-allocations destroyed.
-  EXPECT_EQ(nullptr, last_slab);
-
-  auto sub_sub_alloc = sub_alloc1a->Allocate(1, 1);
-  sub_alloc1a = nullptr;
-  // sub_alloc1a is kept alive by sub_sub_alloc.
-  EXPECT_EQ(nullptr, last_slab);
-
-  // Trigger the first notification.
-  sub_sub_alloc = nullptr;
-  EXPECT_EQ(alloc1.get(), last_slab);
-  EXPECT_EQ(10U, last_size);
-  EXPECT_EQ(11U, last_offset);
-
-  // Finish the rest in different order than initial allocations.
-  sub_alloc2b = nullptr;
-  EXPECT_EQ(alloc2.get(), last_slab);
-  EXPECT_EQ(40U, last_size);
-  EXPECT_EQ(14U, last_offset);
-  sub_alloc2a = nullptr;
-  EXPECT_EQ(alloc2.get(), last_slab);
-  EXPECT_EQ(30U, last_size);
-  EXPECT_EQ(13U, last_offset);
-  sub_alloc1b = nullptr;
-  EXPECT_EQ(alloc1.get(), last_slab);
-  EXPECT_EQ(20U, last_size);
-  EXPECT_EQ(12U, last_offset);
+  EXPECT_EQ(0U, allocator.GetTotalBytesAllocated());
 }
 
 namespace {
@@ -197,9 +129,9 @@
 
   GpuMemPtr mem =
       fxl::MakeRefCounted<FakeGpuMem>(kVkMem, kSize0, kOffset0, nullptr);
-  auto sub = mem->Allocate(kSize1, kOffset1);
-  auto subsub = sub->Allocate(kSize2, kOffset2);
-  auto subsubsub = subsub->Allocate(kSize3, kOffset3);
+  auto sub = mem->Suballocate(kSize1, kOffset1);
+  auto subsub = sub->Suballocate(kSize2, kOffset2);
+  auto subsubsub = subsub->Suballocate(kSize3, kOffset3);
 
   EXPECT_NE(vk::DeviceMemory(), mem->base());
   EXPECT_EQ(mem->base(), sub->base());
@@ -225,16 +157,16 @@
 
   GpuMemPtr mem = fxl::MakeRefCounted<FakeGpuMem>(vk::DeviceMemory(), kSize1,
                                                   kOffset1, kNullPtr);
-  GpuMemPtr sub = mem->Allocate(kSize2, kOffset2);
-  GpuMemPtr subsub = sub->Allocate(kSize3, kOffset3);
+  GpuMemPtr sub = mem->Suballocate(kSize2, kOffset2);
+  GpuMemPtr subsub = sub->Suballocate(kSize3, kOffset3);
   EXPECT_EQ(nullptr, mem->mapped_ptr());
   EXPECT_EQ(nullptr, sub->mapped_ptr());
   EXPECT_EQ(nullptr, subsub->mapped_ptr());
 
   mem = fxl::MakeRefCounted<FakeGpuMem>(vk::DeviceMemory(), kSize1, kOffset1,
                                         kFakePtr);
-  sub = mem->Allocate(kSize2, kOffset2);
-  subsub = sub->Allocate(kSize3, kOffset3);
+  sub = mem->Suballocate(kSize2, kOffset2);
+  subsub = sub->Suballocate(kSize3, kOffset3);
   EXPECT_EQ(kFakePtr, mem->mapped_ptr());
   EXPECT_EQ(static_cast<ptrdiff_t>(kOffset2),
             sub->mapped_ptr() - mem->mapped_ptr());
diff --git a/public/lib/escher/test/pose_buffer_latching_test.cc b/public/lib/escher/test/pose_buffer_latching_test.cc
index 13b2265..bd9271a 100644
--- a/public/lib/escher/test/pose_buffer_latching_test.cc
+++ b/public/lib/escher/test/pose_buffer_latching_test.cc
@@ -3,15 +3,16 @@
 // found in the LICENSE file.
 
 #include "garnet/public/lib/escher/escher.h"
+#include "garnet/public/lib/escher/hmd/pose_buffer.h"
 #include "garnet/public/lib/escher/hmd/pose_buffer_latching_shader.h"
 #include "garnet/public/lib/escher/renderer/frame.h"
 #include "garnet/public/lib/escher/resources/resource_recycler.h"
 #include "garnet/public/lib/escher/scene/camera.h"
 #include "garnet/public/lib/escher/test/gtest_vulkan.h"
+#include "garnet/public/lib/escher/util/epsilon_compare.h"
 #include "garnet/public/lib/escher/vk/buffer.h"
+#include "garnet/public/lib/escher/vk/gpu_allocator.h"
 #include "gtest/gtest.h"
-#include "lib/escher/hmd/pose_buffer.h"
-#include "lib/escher/util/epsilon_compare.h"
 
 #include <glm/gtc/type_ptr.hpp>
 
@@ -79,11 +80,10 @@
       vk::BufferUsageFlagBits::eStorageBuffer;
 
   // Create the shader.
-  hmd::PoseBuffer pose_buffer(
-      escher::Buffer::New(escher->resource_recycler(), frame->gpu_allocator(),
-                          pose_buffer_size, buffer_usage_flags,
-                          memory_property_flags),
-      num_entries, base_time, time_interval);
+  hmd::PoseBuffer pose_buffer(escher->gpu_allocator()->AllocateBuffer(
+                                  escher->resource_recycler(), pose_buffer_size,
+                                  buffer_usage_flags, memory_property_flags),
+                              num_entries, base_time, time_interval);
 
   hmd::PoseBufferLatchingShader test_shader(escher->GetWeakPtr());
 
diff --git a/public/lib/escher/test/renderer/batch_gpu_uploader_unittest.cc b/public/lib/escher/test/renderer/batch_gpu_uploader_unittest.cc
index 08c58bc..bf9e744 100644
--- a/public/lib/escher/test/renderer/batch_gpu_uploader_unittest.cc
+++ b/public/lib/escher/test/renderer/batch_gpu_uploader_unittest.cc
@@ -122,7 +122,8 @@
   const size_t buffer_size = 3 * sizeof(vec3);
   auto writer = uploader->AcquireWriter(buffer_size);
   // Create buffer to write to.
-  BufferFactory buffer_factory(escher);
+  BufferFactoryAdapter buffer_factory(escher->gpu_allocator(),
+                                      escher->resource_recycler());
   BufferPtr vertex_buffer =
       buffer_factory.NewBuffer(buffer_size,
                                vk::BufferUsageFlagBits::eVertexBuffer |
@@ -189,7 +190,7 @@
   region.imageExtent.width = image->width();
   region.imageExtent.height = image->height();
   region.imageExtent.depth = 1;
-  region.bufferOffset = image->memory_offset();
+  region.bufferOffset = image->memory()->offset();
 
   BatchGpuUploaderPtr uploader = BatchGpuUploader::New(escher, 0);
   auto reader = uploader->AcquireReader(image->memory()->size());
@@ -210,7 +211,8 @@
   auto escher = test::GetEscher()->GetWeakPtr();
   // Create buffer to read from.
   const size_t buffer_size = 3 * sizeof(vec3);
-  BufferFactory buffer_factory(escher);
+  BufferFactoryAdapter buffer_factory(escher->gpu_allocator(),
+                                      escher->resource_recycler());
   BufferPtr vertex_buffer =
       buffer_factory.NewBuffer(buffer_size,
                                vk::BufferUsageFlagBits::eVertexBuffer |
diff --git a/public/lib/escher/test/vk/buffer_unittest.cc b/public/lib/escher/test/vk/buffer_unittest.cc
index c1386a6..f24440a 100644
--- a/public/lib/escher/test/vk/buffer_unittest.cc
+++ b/public/lib/escher/test/vk/buffer_unittest.cc
@@ -23,19 +23,19 @@
 
   // This is silly, but without creating a buffer, I don't understand how to
   // populate vk::MemoryRequirements::memoryTypeBits.
-  auto dummy_buffer = Buffer::New(recycler, allocator, kDummyBufferSize,
-                                  kBufferUsageFlags, kMemoryPropertyFlags);
+  auto dummy_buffer = allocator->AllocateBuffer(
+      recycler, kDummyBufferSize, kBufferUsageFlags, kMemoryPropertyFlags);
   vk::MemoryRequirements reqs =
       escher->vk_device().getBufferMemoryRequirements(dummy_buffer->vk());
 
   // Now that we have the memory requirements, we can allocate some memory, so
   // that we can test creating a buffer with pre-existing memory.
-  auto mem1 = allocator->Allocate(reqs, kMemoryPropertyFlags);
+  auto mem1 = allocator->AllocateMemory(reqs, kMemoryPropertyFlags);
 
   // Suballocate some memory.
   constexpr vk::DeviceSize kBufferSize = 1000;
   constexpr vk::DeviceSize kOffset = 512;
-  auto mem2 = mem1->Allocate(kBufferSize, kOffset);
+  auto mem2 = mem1->Suballocate(kBufferSize, kOffset);
   EXPECT_EQ(mem1->mapped_ptr() + kOffset, mem2->mapped_ptr());
 
   // Allocate 2 buffers, one from the original allocation, and one from the
diff --git a/public/lib/escher/vk/buffer.cc b/public/lib/escher/vk/buffer.cc
index 4fb2461..3dceae8 100644
--- a/public/lib/escher/vk/buffer.cc
+++ b/public/lib/escher/vk/buffer.cc
@@ -15,50 +15,9 @@
                                          ResourceType::kWaitableResource,
                                          ResourceType::kBuffer);
 
-BufferPtr Buffer::New(ResourceManager* manager, GpuAllocator* allocator,
-                      vk::DeviceSize size, vk::BufferUsageFlags usage_flags,
-                      vk::MemoryPropertyFlags memory_property_flags,
-                      GpuMemPtr* out_ptr) {
-  TRACE_DURATION("gfx", "escher::Buffer::New[allocator]");
-  FXL_DCHECK(manager);
-
-  auto device = manager->vulkan_context().device;
-  FXL_DCHECK(device);
-
-  // Create buffer.
-  vk::BufferCreateInfo buffer_create_info;
-  buffer_create_info.size = size;
-  buffer_create_info.usage = usage_flags;
-  buffer_create_info.sharingMode = vk::SharingMode::eExclusive;
-  auto vk_buffer =
-      ESCHER_CHECKED_VK_RESULT(device.createBuffer(buffer_create_info));
-
-  auto memory_requirements = device.getBufferMemoryRequirements(vk_buffer);
-
-  // Allocate memory for the buffer.
-  //
-  // Note that while code could use the other factory function to construct an
-  // escher::Buffer object while holding onto the GpuMemPtr, it would result in
-  // duplicate work, as the memory requirements are only known after parsing the
-  // vkBufferCreateInfo struct and producing a valid vkHandle.
-  GpuMemPtr mem;
-  if (allocator) {
-    mem = allocator->Allocate(memory_requirements, memory_property_flags);
-  } else {
-    mem = GpuMem::New(device, manager->vulkan_context().physical_device,
-                      memory_requirements, memory_property_flags);
-  }
-
-  if (out_ptr) {
-    *out_ptr = mem;
-  }
-
-  return fxl::MakeRefCounted<Buffer>(manager, std::move(mem), vk_buffer);
-}
-
 BufferPtr Buffer::New(ResourceManager* manager, GpuMemPtr mem,
                       vk::BufferUsageFlags usage_flags) {
-  TRACE_DURATION("gfx", "escher::Buffer::New[mem]");
+  TRACE_DURATION("gfx", "escher::Buffer::New");
   auto device = manager->vulkan_context().device;
 
   // Create buffer.
diff --git a/public/lib/escher/vk/buffer.h b/public/lib/escher/vk/buffer.h
index 916503a..d8ded06 100644
--- a/public/lib/escher/vk/buffer.h
+++ b/public/lib/escher/vk/buffer.h
@@ -20,11 +20,6 @@
   static const ResourceTypeInfo kTypeInfo;
   const ResourceTypeInfo& type_info() const override { return kTypeInfo; }
 
-  static BufferPtr New(ResourceManager* manager, GpuAllocator* allocator,
-                       vk::DeviceSize size, vk::BufferUsageFlags usage_flags,
-                       vk::MemoryPropertyFlags memory_property_flags,
-                       GpuMemPtr* out_ptr = nullptr);
-
   static BufferPtr New(ResourceManager* manager, GpuMemPtr mem,
                        vk::BufferUsageFlags usage_flags);
 
diff --git a/public/lib/escher/vk/buffer_factory.cc b/public/lib/escher/vk/buffer_factory.cc
deleted file mode 100644
index e71de81..0000000
--- a/public/lib/escher/vk/buffer_factory.cc
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2017 The Fuchsia 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 "lib/escher/vk/buffer_factory.h"
-#include "lib/escher/vk/gpu_mem.h"
-
-#include "lib/escher/escher.h"
-
-namespace escher {
-
-BufferFactory::BufferFactory(EscherWeakPtr escher)
-    : ResourceRecycler(std::move(escher)) {}
-
-BufferPtr BufferFactory::NewBuffer(
-    vk::DeviceSize size, vk::BufferUsageFlags usage_flags,
-    vk::MemoryPropertyFlags memory_property_flags) {
-  return Buffer::New(this, escher()->gpu_allocator(), size, usage_flags,
-                     memory_property_flags);
-}
-
-BufferPtr BufferFactory::NewBuffer(GpuMemPtr mem,
-                                   vk::BufferUsageFlags usage_flags) {
-  return Buffer::New(this, mem, usage_flags);
-}
-
-}  // namespace escher
diff --git a/public/lib/escher/vk/buffer_factory.h b/public/lib/escher/vk/buffer_factory.h
index e87d303..30f2de8 100644
--- a/public/lib/escher/vk/buffer_factory.h
+++ b/public/lib/escher/vk/buffer_factory.h
@@ -5,30 +5,53 @@
 #ifndef LIB_ESCHER_VK_BUFFER_FACTORY_H_
 #define LIB_ESCHER_VK_BUFFER_FACTORY_H_
 
-#include "lib/escher/resources/resource_recycler.h"
+#include "lib/escher/resources/resource_manager.h"
 #include "lib/escher/vk/buffer.h"
+#include "lib/escher/vk/gpu_allocator.h"
 
 namespace escher {
 
-// BufferFactory allows clients to obtain unused Buffers with the desired
-// properties.  The default implementation allocates memory and creates a new
-// Buffer, but subclasses may override this behavior, e.g. to support efficient
-// recycling of fixed-size Buffers.
-class BufferFactory : private ResourceRecycler {
+// BufferFactory allows clients to obtain new Buffers with the desired
+// properties. Subclasses are free to implement custom caching/recycling
+// behaviors. All buffers obtained from a BufferFactory must be released before
+// the BufferFactory is destroyed.
+class BufferFactory {
  public:
-  explicit BufferFactory(EscherWeakPtr escher);
+  virtual ~BufferFactory() = default;
 
-  // Creates a buffer, along with a new memory.
+  // Creates a buffer, backed by a block of memory. If |out_ptr| is non-null,
+  // the buffer will be bound to a dedicated piece of memory (i.e.,
+  // VkMemoryDedicatedRequirements.requiresDedicatedAllocation
+  // == true). That memory must be accessable through the GpuMem returned in
+  // |out_ptr|.
   virtual BufferPtr NewBuffer(vk::DeviceSize size,
                               vk::BufferUsageFlags usage_flags,
-                              vk::MemoryPropertyFlags memory_property_flags);
+                              vk::MemoryPropertyFlags memory_property_flags,
+                              GpuMemPtr* out_ptr = nullptr) = 0;
+};
 
-  // Creates a buffer for the given memory.
-  virtual BufferPtr NewBuffer(GpuMemPtr mem, vk::BufferUsageFlags usage_flags);
+// This default implementation allocates memory and creates a new
+// Buffer using the provided allocator and manager. The intent is for this class
+// to adapt existing GpuAllocators to the BufferFactory interface (i.e.
+// equivalent to a partial bind). Classes that wish to implement their own
+// caching logic should subclass BufferFactory directly, instead of injecting
+// tricky subclasses of GpuAllocator and ResourceManager into this object.
+class BufferFactoryAdapter final : public BufferFactory {
+ public:
+  BufferFactoryAdapter(GpuAllocator* allocator, ResourceManager* manager)
+      : allocator_(allocator->GetWeakPtr()), manager_(manager) {}
 
-  // Expose escher()... this is one aspect of ResourceRecycler that we want to
-  // inherit publicly.
-  using ResourceRecycler::escher;
+  BufferPtr NewBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage_flags,
+                      vk::MemoryPropertyFlags memory_property_flags,
+                      GpuMemPtr* out_ptr = nullptr) final {
+    FXL_DCHECK(allocator_);
+    return allocator_->AllocateBuffer(manager_, size, usage_flags,
+                                      memory_property_flags, out_ptr);
+  }
+
+ private:
+  const GpuAllocatorWeakPtr allocator_;
+  ResourceManager* const manager_;
 };
 
 }  // namespace escher
diff --git a/public/lib/escher/vk/gpu_allocator.cc b/public/lib/escher/vk/gpu_allocator.cc
deleted file mode 100644
index b10580b..0000000
--- a/public/lib/escher/vk/gpu_allocator.cc
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2016 The Fuchsia 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 "lib/escher/vk/gpu_allocator.h"
-
-#include "lib/escher/impl/vulkan_utils.h"
-
-namespace escher {
-
-GpuAllocator::GpuAllocator(const VulkanContext& context)
-    : physical_device_(context.physical_device),
-      device_(context.device),
-      weak_factory_(this) {}
-
-GpuAllocator::~GpuAllocator() {
-  FXL_CHECK(total_slab_bytes_ == 0);
-  FXL_CHECK(slab_count_ == 0);
-}
-
-GpuMemPtr GpuAllocator::AllocateSlab(vk::MemoryRequirements reqs,
-                                     vk::MemoryPropertyFlags flags) {
-  return impl::GpuMemSlab::New(device(), physical_device(), reqs, flags, this);
-}
-
-void GpuAllocator::OnSlabCreated(vk::DeviceSize slab_size) {
-  ++slab_count_;
-  total_slab_bytes_ += slab_size;
-}
-
-void GpuAllocator::OnSlabDestroyed(vk::DeviceSize slab_size) {
-  --slab_count_;
-  total_slab_bytes_ -= slab_size;
-}
-
-}  // namespace escher
diff --git a/public/lib/escher/vk/gpu_allocator.h b/public/lib/escher/vk/gpu_allocator.h
index 6db931c..d5c976d 100644
--- a/public/lib/escher/vk/gpu_allocator.h
+++ b/public/lib/escher/vk/gpu_allocator.h
@@ -7,88 +7,57 @@
 
 #include <vulkan/vulkan.hpp>
 
-#include "lib/escher/impl/gpu_mem_slab.h"
-#include "lib/escher/vk/vulkan_context.h"
+#include "lib/escher/vk/buffer.h"
+#include "lib/escher/vk/gpu_mem.h"
+#include "lib/escher/vk/image.h"
 #include "lib/fxl/memory/weak_ptr.h"
 
 namespace escher {
 
-// GpuAllocator provides a framework for malloc()-like sub-allocation of Vulkan
-// memory.  Vulkan does not require implementations to support large numbers of
+// GpuAllocator is an interface for allocating vulkan-aware blocks of memory,
+// and objects that are backed by said memory (i.e., buffers and images).
+//
+// Vulkan implementations are not required to support large numbers of raw
 // memory allocations.  Instead, applications are expected to allocate larger
 // chunks of memory, and sub-allocate from within these chunks.
 //
-// GpuAllocator defines the interface that clients use to obtain sub-allocated
-// memory; the specific sub-allocation strategy employed is the responsibility
-// of concrete subclasses of GpuAllocator.
-//
-// Implementation notes:
-// The following is only of interest to implementors of GpuAllocator subclasses.
-//
-// Unbeknownst to clients, GpuMem::Allocate() and GpuAllocator::Allocate() do
-// not return an instance of GpuMem, but rather an instance of a concrete
-// subclass.  There are two concrete subclasses of GpuMem (with no plans to
-// create more):
-//   - impl::GpuMemSuballocation
-//   - impl::GpuMemSlab
-//
-// GpuMemSuballocation represents a subset of a parent GpuMem; the object
-// returned from GpuMem::Allocate() is always a GpuMemSuballocation.  This is
-// also the type returned by GpuAllocator::Allocate() when a subclass performs
-// a sub-allocation of an existing GpuMem (in fact, the subclass implementation
-// uses GpuMem::Allocate() to perform this sub-allocation).
-//
-// However, sometimes a GpuAllocator has insufficient free space to sub-allocate
-// from, and must allocate more memory directly from Vulkan.  This is done by
-// calling the protected method GpuAllocator::AllocateSlab().  The returned
-// GpuMem object is actually a GpuMemSlab, but the subclass neither knows nor
-// cares (except indirectly: GpuMemSlab overrides OnAllocationDestroyed() to
-// call GpuAllocator::OnSuballocationDestroyed()).
+// GpuAllocator defines the interface that clients use to obtain
+// sub-allocated memory; the specific sub-allocation strategy employed is
+// the responsibility of concrete subclasses of GpuAllocator.
 class GpuAllocator {
  public:
-  GpuAllocator(const VulkanContext& context);
-  virtual ~GpuAllocator();
+  GpuAllocator() : weak_factory_(this) {}
+  virtual ~GpuAllocator() = default;
 
   fxl::WeakPtr<GpuAllocator> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
 
-  virtual GpuMemPtr Allocate(vk::MemoryRequirements reqs,
-                             vk::MemoryPropertyFlags flags) = 0;
+  virtual GpuMemPtr AllocateMemory(
+      vk::MemoryRequirements reqs,
+      vk::MemoryPropertyFlags memory_property_flags) = 0;
 
-  vk::PhysicalDevice physical_device() const { return physical_device_; }
-  vk::Device device() const { return device_; }
+  // If |out_ptr| is non-null, this buffer must be backed by a dedicated
+  // piece of memory (i.e.,
+  // VkMemoryDedicatedRequirements.requiresDedicatedAllocation
+  // == true). That memory must be accessible through the GpuMem returned in
+  // |out_ptr|.
+  virtual BufferPtr AllocateBuffer(
+      ResourceManager* manager, vk::DeviceSize size,
+      vk::BufferUsageFlags usage_flags,
+      vk::MemoryPropertyFlags memory_property_flags,
+      GpuMemPtr* out_ptr = nullptr) = 0;
 
-  // Current number of bytes allocated (i.e. unfreed) by this allocator.
-  // This is the sum of all slabs allocated by AllocateSlab(), even if the
-  // no sub-allocations have been made from these slabs.
-  uint64_t total_slab_bytes() { return total_slab_bytes_; }
-  uint32_t slab_count() const { return slab_count_; }
+  virtual ImagePtr AllocateImage(ResourceManager* manager,
+                                 const escher::ImageInfo& info) = 0;
 
- protected:
-  // Concrete subclasses use this to allocate a slab of memory directly from
-  // Vulkan.  Sub-allocation can then be performed via GpuMem::Allocate().
-  GpuMemPtr AllocateSlab(vk::MemoryRequirements reqs,
-                         vk::MemoryPropertyFlags flags);
-
- private:
-  // Callbacks to allow a GpuMemSlab to notify its GpuAllocator of changes.
-  friend class impl::GpuMemSlab;
-  void OnSlabCreated(vk::DeviceSize slab_size);
-  void OnSlabDestroyed(vk::DeviceSize slab_size);
-  // Notify the GpuAllocator that a sub-allocated range of memory is no longer
-  // used within the specified slab.
-  virtual void OnSuballocationDestroyed(GpuMem* slab, vk::DeviceSize size,
-                                        vk::DeviceSize offset) = 0;
-
-  vk::PhysicalDevice physical_device_;
-  vk::Device device_;
-  vk::DeviceSize total_slab_bytes_ = 0;
-  size_t slab_count_ = 0;
+  virtual uint32_t GetTotalBytesAllocated() const = 0;
 
   fxl::WeakPtrFactory<GpuAllocator> weak_factory_;  // must be last
 
   FXL_DISALLOW_COPY_AND_ASSIGN(GpuAllocator);
 };
 
+typedef fxl::WeakPtr<GpuAllocator> GpuAllocatorWeakPtr;
+
 }  // namespace escher
 
 #endif  // LIB_ESCHER_VK_GPU_ALLOCATOR_H_
diff --git a/public/lib/escher/vk/gpu_mem.cc b/public/lib/escher/vk/gpu_mem.cc
index 722f031..7521787 100644
--- a/public/lib/escher/vk/gpu_mem.cc
+++ b/public/lib/escher/vk/gpu_mem.cc
@@ -15,20 +15,13 @@
 
 GpuMem::~GpuMem() {}
 
-GpuMemPtr GpuMem::New(vk::Device device, vk::PhysicalDevice physical_device,
-                      vk::MemoryRequirements reqs,
-                      vk::MemoryPropertyFlags flags) {
-  return impl::GpuMemSlab::New(device, physical_device, reqs, flags, nullptr);
+GpuMemPtr GpuMem::AdoptVkMemory(vk::Device device, vk::DeviceMemory mem,
+                                vk::DeviceSize size, bool needs_mapped_ptr) {
+  return fxl::AdoptRef(
+      new impl::GpuMemSlab(device, mem, size, needs_mapped_ptr, nullptr));
 }
 
-GpuMemPtr GpuMem::New(vk::Device device, vk::DeviceMemory mem,
-                      vk::DeviceSize size, bool needs_mapped_ptr,
-                      uint32_t memory_type_index) {
-  return fxl::AdoptRef(new impl::GpuMemSlab(device, mem, size, needs_mapped_ptr,
-                                            memory_type_index, nullptr));
-}
-
-GpuMemPtr GpuMem::Allocate(vk::DeviceSize size, vk::DeviceSize offset) {
+GpuMemPtr GpuMem::Suballocate(vk::DeviceSize size, vk::DeviceSize offset) {
   if (offset + size > size_) {
     return GpuMemPtr();
   }
diff --git a/public/lib/escher/vk/gpu_mem.h b/public/lib/escher/vk/gpu_mem.h
index 42df467..883b228 100644
--- a/public/lib/escher/vk/gpu_mem.h
+++ b/public/lib/escher/vk/gpu_mem.h
@@ -21,17 +21,10 @@
 // Ref-counted wrapper around a vk::DeviceMemory.  Supports sub-allocation.
 class GpuMem : public fxl::RefCountedThreadSafe<GpuMem> {
  public:
-  // Create a GpuMem that wraps a newly-allocated vk::DeviceMemory, which will
-  // be destroyed when the GpuMem dies.
-  static GpuMemPtr New(vk::Device device, vk::PhysicalDevice physical_device,
-                       vk::MemoryRequirements reqs,
-                       vk::MemoryPropertyFlags flags);
-
-  // Create a GpuMem that takes ownership of |mem|, which will be destroyed hen
-  // the GpuMem dies. Guaranteed to return non-null result.
-  static GpuMemPtr New(vk::Device device, vk::DeviceMemory mem,
-                       vk::DeviceSize size, bool needs_mapped_ptr,
-                       uint32_t memory_type_index);
+  // Create a GpuMem that takes ownership of |mem|, which will be destroyed when
+  // the GpuMem dies. Guaranteed to return a non-null result.
+  static GpuMemPtr AdoptVkMemory(vk::Device device, vk::DeviceMemory mem,
+                                 vk::DeviceSize size, bool needs_mapped_ptr);
 
   // Sub-allocate a GpuMem that represents a sub-range of the memory in this
   // GpuMem.  Since these sub-allocations reference the parent GpuMem, the
@@ -39,7 +32,7 @@
   // Returns nullptr if the requested offset/size do not fit within the current
   // GpuMem.
   // Note: no bookkeeping ensures that sub-allocations do not overlap!
-  GpuMemPtr Allocate(vk::DeviceSize size, vk::DeviceSize offset);
+  GpuMemPtr Suballocate(vk::DeviceSize size, vk::DeviceSize offset);
 
   vk::DeviceMemory base() const { return base_; }
   vk::DeviceSize size() const { return size_; }
@@ -47,8 +40,8 @@
   uint8_t* mapped_ptr() const { return mapped_ptr_; }
 
  protected:
-  // |offset| + |size| must be <= the size of |base|.  Takes ownership of
-  // |base|.
+  // |offset| + |size| must be <= the size of |base|. This class does not
+  // takes ownership of |base| by default.
   GpuMem(vk::DeviceMemory base, vk::DeviceSize size, vk::DeviceSize offset,
          uint8_t* mapped_ptr);
 
@@ -56,13 +49,6 @@
   virtual ~GpuMem();
 
  private:
-  // Allow subclasses to take action when a sub-allocation is destroyed.  For
-  // example, this can be used by subclasses of GpuAllocator for bookkeeping of
-  // available memory withing a GpuMemSlab.
-  friend class impl::GpuMemSuballocation;
-  virtual void OnAllocationDestroyed(vk::DeviceSize size,
-                                     vk::DeviceSize offset) {}
-
   vk::DeviceMemory base_;
   vk::DeviceSize size_;
   vk::DeviceSize offset_;
diff --git a/public/lib/escher/vk/image.cc b/public/lib/escher/vk/image.cc
index 9c30304..e171869 100644
--- a/public/lib/escher/vk/image.cc
+++ b/public/lib/escher/vk/image.cc
@@ -17,40 +17,33 @@
                                         ResourceType::kWaitableResource,
                                         ResourceType::kImage);
 
-ImagePtr Image::New(ResourceManager* image_owner, ImageInfo info,
-                    vk::Image vk_image, GpuMemPtr mem,
-                    vk::DeviceSize mem_offset, bool bind_image_memory) {
-  TRACE_DURATION("gfx", "escher::Image::New (from VkImage)");
-  if (mem && bind_image_memory) {
-    auto bind_result = image_owner->vk_device().bindImageMemory(
-        vk_image, mem->base(), mem->offset() + mem_offset);
-    if (bind_result != vk::Result::eSuccess) {
-      FXL_DLOG(ERROR) << "vkBindImageMemory failed: "
-                      << vk::to_string(bind_result);
-      return nullptr;
-    }
+ImagePtr Image::AdoptVkImage(ResourceManager* image_owner, ImageInfo info,
+                             vk::Image vk_image, GpuMemPtr mem) {
+  TRACE_DURATION("gfx", "escher::Image::AdoptImage (from VkImage)");
+  FXL_CHECK(vk_image);
+  FXL_CHECK(mem);
+  auto bind_result = image_owner->vk_device().bindImageMemory(
+      vk_image, mem->base(), mem->offset());
+  if (bind_result != vk::Result::eSuccess) {
+    FXL_DLOG(ERROR) << "vkBindImageMemory failed: "
+                    << vk::to_string(bind_result);
+    return nullptr;
   }
-  return fxl::AdoptRef(new Image(image_owner, info, vk_image, mem, mem_offset));
+
+  return fxl::AdoptRef(new Image(image_owner, info, vk_image, mem));
 }
 
-ImagePtr Image::New(ResourceManager* image_owner, const ImageInfo& info,
-                    GpuAllocator* allocator) {
-  TRACE_DURATION("gfx", "escher::Image::New (from ImageInfo)");
-
-  auto vk_device = image_owner->vk_device();
-  vk::Image image = image_utils::CreateVkImage(vk_device, info);
-  vk::MemoryRequirements reqs = vk_device.getImageMemoryRequirements(image);
-  return Image::New(image_owner, info, image,
-                    allocator->Allocate(reqs, info.memory_flags));
+ImagePtr Image::WrapVkImage(ResourceManager* image_owner, ImageInfo info,
+                            vk::Image vk_image) {
+  return fxl::AdoptRef(new Image(image_owner, info, vk_image, nullptr));
 }
 
-Image::Image(ResourceManager* image_owner, ImageInfo info, vk::Image vk_image,
-             GpuMemPtr mem, vk::DeviceSize mem_offset)
+Image::Image(ResourceManager* image_owner, ImageInfo info, vk::Image image,
+             GpuMemPtr mem)
     : WaitableResource(image_owner),
       info_(info),
-      image_(vk_image),
-      mem_(std::move(mem)),
-      mem_offset_(mem_offset) {
+      image_(image),
+      mem_(std::move(mem)) {
   auto is_depth_stencil = image_utils::IsDepthStencilFormat(info.format);
   has_depth_ = is_depth_stencil.first;
   has_stencil_ = is_depth_stencil.second;
diff --git a/public/lib/escher/vk/image.h b/public/lib/escher/vk/image.h
index 40094be..a330237 100644
--- a/public/lib/escher/vk/image.h
+++ b/public/lib/escher/vk/image.h
@@ -48,19 +48,16 @@
   static const ResourceTypeInfo kTypeInfo;
   const ResourceTypeInfo& type_info() const override { return kTypeInfo; }
 
-  // Constructor.  In some cases it is necessary to wrap an un-owned vk::Image,
-  // which should not be destroyed when this Image is destroyed (e.g. when
-  // working with images associated with a vk::SwapchainKHR); this is done by
-  // passing nullptr as the |mem| argument.
-  // If |mem| is passed and |bind_image_memory| is true, this method also binds
-  // the memory to the image. |mem_offset| is the offset of the image's memory
-  // within |mem|.
-  static ImagePtr New(ResourceManager* image_owner, ImageInfo info, vk::Image,
-                      GpuMemPtr mem, vk::DeviceSize mem_offset = 0,
-                      bool bind_image_memory = true);
+  // Constructor. Claims ownership of the vk::Image, and binds it to the
+  // provided GpuMemPtr.
+  static ImagePtr AdoptVkImage(ResourceManager* image_owner, ImageInfo info,
+                               vk::Image image, GpuMemPtr mem);
 
-  static ImagePtr New(ResourceManager* image_owner, const ImageInfo& info,
-                      GpuAllocator* allocator);
+  // Constructor. Wraps an existing image without claiming ownership. Useful
+  // when the image is owned/maintained by another system (e.g.,
+  // vk::SwapchainKHR).
+  static ImagePtr WrapVkImage(ResourceManager* image_owner, ImageInfo info,
+                              vk::Image image);
 
   // Returns image_ and mem_ to the owner.
   ~Image() override;
@@ -76,8 +73,6 @@
   bool has_stencil() const { return has_stencil_; }
   bool is_transient() const { return info_.is_transient(); }
   const GpuMemPtr& memory() const { return mem_; }
-  // Offset of the Image within its GpuMem.
-  vk::DeviceSize memory_offset() const { return mem_offset_; }
 
   // TODO(ES-83): how does this interact with swapchain_layout_?
   // Should this be automatically set when various transitions are made, e.g.
@@ -101,15 +96,13 @@
   // which should not be destroyed when this Image is destroyed (e.g. when
   // working with images associated with a vk::SwapchainKHR); this is done by
   // passing nullptr as the |mem| argument.
-  // |mem_offset| is the offset of the image's memory within |mem|.
-  Image(ResourceManager* image_owner, ImageInfo info, vk::Image, GpuMemPtr mem,
-        vk::DeviceSize mem_offset);
+  Image(ResourceManager* image_owner, ImageInfo info, vk::Image image,
+        GpuMemPtr mem);
 
  private:
   const ImageInfo info_;
   const vk::Image image_;
   GpuMemPtr mem_;
-  const vk::DeviceSize mem_offset_ = 0;
   bool has_depth_;
   bool has_stencil_;
 
diff --git a/public/lib/escher/vk/image_factory.h b/public/lib/escher/vk/image_factory.h
index f726c03..ac31378 100644
--- a/public/lib/escher/vk/image_factory.h
+++ b/public/lib/escher/vk/image_factory.h
@@ -5,20 +5,43 @@
 #ifndef LIB_ESCHER_VK_IMAGE_FACTORY_H_
 #define LIB_ESCHER_VK_IMAGE_FACTORY_H_
 
+#include "lib/escher/resources/resource_manager.h"
+#include "lib/escher/vk/gpu_allocator.h"
 #include "lib/escher/vk/image.h"
-#include "lib/fxl/memory/ref_counted.h"
 
 namespace escher {
 
-// ImageFactory creates Images, which may or may not have been recycled.  All
-// Images obtained from an ImageFactory must be destroyed before the
-// ImageFactory is destroyed.
+// ImageFactory allows clients to obtain new Images with the desired
+// properties. Subclasses are free to implement custom caching/recycling
+// behaviors. All images obtained from an ImageFactory must be released before
+// the ImageFactory is destroyed.
 class ImageFactory {
  public:
-  virtual ~ImageFactory() {}
+  virtual ~ImageFactory() = default;
   virtual ImagePtr NewImage(const ImageInfo& info) = 0;
 };
 
+// This default implementation allocates memory and creates a new
+// Image using the provided allocator and manager. The intent is for this class
+// to adapt existing GpuAllocators to the ImageFactory interface (i.e.
+// equivalent to a partial bind). Classes that wish to implement their own
+// caching logic should subclass ImageFactory directly, instead of injecting
+// tricky subclasses of GpuAllocator and ResourceManager into this object.
+class ImageFactoryAdapter final : public ImageFactory {
+ public:
+  ImageFactoryAdapter(GpuAllocator* allocator, ResourceManager* manager)
+      : allocator_(allocator->GetWeakPtr()), manager_(manager) {}
+
+  ImagePtr NewImage(const ImageInfo& info) final {
+    FXL_DCHECK(allocator_);
+    return allocator_->AllocateImage(manager_, info);
+  }
+
+ private:
+  const GpuAllocatorWeakPtr allocator_;
+  ResourceManager* const manager_;
+};
+
 }  // namespace escher
 
 #endif  // LIB_ESCHER_VK_IMAGE_FACTORY_H_
diff --git a/public/lib/escher/vk/naive_gpu_allocator.cc b/public/lib/escher/vk/naive_gpu_allocator.cc
index 16c8690..b1a98a9 100644
--- a/public/lib/escher/vk/naive_gpu_allocator.cc
+++ b/public/lib/escher/vk/naive_gpu_allocator.cc
@@ -3,28 +3,110 @@
 // found in the LICENSE file.
 
 #include "lib/escher/vk/naive_gpu_allocator.h"
+#include "lib/escher/impl/gpu_mem_slab.h"
+#include "lib/escher/impl/vulkan_utils.h"
+#include "lib/escher/util/image_utils.h"
+#include "lib/escher/util/trace_macros.h"
+#include "lib/escher/vk/image.h"
 
 namespace escher {
 
 NaiveGpuAllocator::NaiveGpuAllocator(const VulkanContext& context)
-    : GpuAllocator(context) {}
-
-GpuMemPtr NaiveGpuAllocator::Allocate(vk::MemoryRequirements reqs,
-                                      vk::MemoryPropertyFlags flags) {
-  // TODO: need to manually overallocate and adjust offset to ensure alignment,
-  // based on the content of reqs.alignment?  Probably not, but should verify.
-
-  // More sophisticated subclasses of GpuAllocator will perform multiple
-  // suballocations within a single GpuMemSlab; these will retain the unique_ptr
-  // to the slab, along with additional metadata to track which regions within
-  // it are available.  However, since we know that there is a 1-1 mapping, we
-  // release our unique_ptr to the slab, since it is guaranteed to be returned
-  // to us by FreeMem.
-  return AllocateSlab(reqs, flags);
+    : physical_device_(context.physical_device), device_(context.device) {
+  FXL_DCHECK(device_);
 }
 
-void NaiveGpuAllocator::OnSuballocationDestroyed(GpuMem* slab,
-                                                 vk::DeviceSize size,
-                                                 vk::DeviceSize offset) {}
+NaiveGpuAllocator::~NaiveGpuAllocator() {
+  FXL_CHECK(total_slab_bytes_ == 0);
+  FXL_CHECK(slab_count_ == 0);
+}
+
+GpuMemPtr NaiveGpuAllocator::AllocateMemory(vk::MemoryRequirements reqs,
+                                            vk::MemoryPropertyFlags flags) {
+  TRACE_DURATION("gfx", "escher::NaiveGpuAllocator::AllocateMemory");
+
+  // TODO (SCN-730): need to manually overallocate and adjust offset to ensure
+  // alignment, based on the content of reqs.alignment?  Probably not, but
+  // should verify.
+  vk::DeviceMemory vk_mem;
+  uint32_t memory_type_index = 0;
+  bool needs_mapped_ptr = false;
+  // Determine whether we will need to map the memory.
+  if (flags & vk::MemoryPropertyFlagBits::eHostVisible) {
+    // We don't currently provide an interface for flushing mapped data, so
+    // ensure that the allocated memory is cache-coherent.  This is more
+    // convenient anyway.
+    flags |= vk::MemoryPropertyFlagBits::eHostCoherent;
+    needs_mapped_ptr = true;
+  }
+
+  // TODO (SCN-1161): cache flags for efficiency? Or perhaps change signature of
+  // this method to directly take the memory-type index.
+  memory_type_index =
+      impl::GetMemoryTypeIndex(physical_device_, reqs.memoryTypeBits, flags);
+
+  {
+    TRACE_DURATION("gfx", "vk::Device::allocateMemory");
+    vk::MemoryAllocateInfo info;
+    info.allocationSize = reqs.size;
+    info.memoryTypeIndex = memory_type_index;
+    vk_mem = ESCHER_CHECKED_VK_RESULT(device_.allocateMemory(info));
+  }
+
+  return fxl::AdoptRef(
+      new impl::GpuMemSlab(device_, vk_mem, reqs.size, needs_mapped_ptr, this));
+}
+
+BufferPtr NaiveGpuAllocator::AllocateBuffer(
+    ResourceManager* manager, vk::DeviceSize size,
+    vk::BufferUsageFlags usage_flags,
+    vk::MemoryPropertyFlags memory_property_flags, GpuMemPtr* out_ptr) {
+  TRACE_DURATION("gfx", "escher::NaiveGpuAllocator::AllocateBuffer");
+  FXL_DCHECK(manager);
+
+  // Create buffer.
+  vk::BufferCreateInfo buffer_create_info;
+  buffer_create_info.size = size;
+  buffer_create_info.usage = usage_flags;
+  buffer_create_info.sharingMode = vk::SharingMode::eExclusive;
+  auto vk_buffer =
+      ESCHER_CHECKED_VK_RESULT(device_.createBuffer(buffer_create_info));
+
+  auto memory_requirements = device_.getBufferMemoryRequirements(vk_buffer);
+
+  // Allocate memory for the buffer.
+  GpuMemPtr mem = AllocateMemory(memory_requirements, memory_property_flags);
+  if (out_ptr) {
+    *out_ptr = mem;
+  }
+  return fxl::AdoptRef(new Buffer(manager, std::move(mem), vk_buffer));
+}
+
+ImagePtr NaiveGpuAllocator::AllocateImage(ResourceManager* manager,
+                                          const ImageInfo& info) {
+  vk::Image image = image_utils::CreateVkImage(device_, info);
+
+  // Allocate memory and bind it to the image.
+  vk::MemoryRequirements reqs = device_.getImageMemoryRequirements(image);
+  escher::GpuMemPtr memory = AllocateMemory(reqs, info.memory_flags);
+  ImagePtr escher_image =
+      Image::AdoptVkImage(manager, info, image, std::move(memory));
+  FXL_CHECK(escher_image);
+  return escher_image;
+}
+
+uint32_t NaiveGpuAllocator::GetTotalBytesAllocated() const {
+  return total_slab_bytes_;
+}
+
+void NaiveGpuAllocator::OnSlabCreated(vk::DeviceSize slab_size) {
+  ++slab_count_;
+  total_slab_bytes_ += slab_size;
+}
+
+void NaiveGpuAllocator::OnSlabDestroyed(vk::DeviceSize slab_size) {
+  --slab_count_;
+  total_slab_bytes_ -= slab_size;
+}
 
 }  // namespace escher
diff --git a/public/lib/escher/vk/naive_gpu_allocator.h b/public/lib/escher/vk/naive_gpu_allocator.h
index d7463fc..40f3fdb 100644
--- a/public/lib/escher/vk/naive_gpu_allocator.h
+++ b/public/lib/escher/vk/naive_gpu_allocator.h
@@ -7,8 +7,8 @@
 
 #include <vulkan/vulkan.hpp>
 
+#include "lib/escher/impl/gpu_mem_slab.h"
 #include "lib/escher/vk/gpu_allocator.h"
-#include "lib/escher/vk/gpu_mem.h"
 #include "lib/escher/vk/vulkan_context.h"
 
 namespace escher {
@@ -19,15 +19,35 @@
 class NaiveGpuAllocator : public GpuAllocator {
  public:
   NaiveGpuAllocator(const VulkanContext& context);
+  ~NaiveGpuAllocator();
 
-  GpuMemPtr Allocate(vk::MemoryRequirements reqs,
-                     vk::MemoryPropertyFlags flags) override;
+  // |GpuAllocator|
+  GpuMemPtr AllocateMemory(vk::MemoryRequirements reqs,
+                           vk::MemoryPropertyFlags flags) override;
+
+  // |GpuAllocator|
+  BufferPtr AllocateBuffer(ResourceManager* manager, vk::DeviceSize size,
+                           vk::BufferUsageFlags usage_flags,
+                           vk::MemoryPropertyFlags memory_property_flags,
+                           GpuMemPtr* out_ptr) override;
+
+  // |GpuAllocator|
+  ImagePtr AllocateImage(ResourceManager* manager,
+                         const escher::ImageInfo& info) override;
+
+  // |GpuAllocator|
+  uint32_t GetTotalBytesAllocated() const override;
 
  private:
-  // No-op, because NaiveGpuAllocator does not perform sub-allocation.  This
-  // can only be called if a client manually sub-allocates from the allocation.
-  void OnSuballocationDestroyed(GpuMem* slab, vk::DeviceSize size,
-                                vk::DeviceSize offset) override;
+  // Callbacks to allow a GpuMemSlab to notify its GpuAllocator of changes.
+  friend class impl::GpuMemSlab;
+  void OnSlabCreated(vk::DeviceSize slab_size);
+  void OnSlabDestroyed(vk::DeviceSize slab_size);
+
+  vk::PhysicalDevice physical_device_;
+  vk::Device device_;
+  vk::DeviceSize total_slab_bytes_ = 0;
+  size_t slab_count_ = 0;
 };
 
 }  // namespace escher
diff --git a/public/lib/escher/vk/simple_image_factory.cc b/public/lib/escher/vk/simple_image_factory.cc
deleted file mode 100644
index 043931c..0000000
--- a/public/lib/escher/vk/simple_image_factory.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2017 The Fuchsia 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 "lib/escher/vk/simple_image_factory.h"
-
-#include "lib/escher/resources/resource_manager.h"
-#include "lib/escher/util/image_utils.h"
-#include "lib/escher/vk/gpu_allocator.h"
-
-namespace escher {
-
-SimpleImageFactory::SimpleImageFactory(ResourceManager* resource_manager,
-                                       escher::GpuAllocator* allocator)
-    : resource_manager_(resource_manager), allocator_(allocator) {}
-
-SimpleImageFactory::~SimpleImageFactory() {}
-
-ImagePtr SimpleImageFactory::NewImage(const ImageInfo& info) {
-  vk::Image image =
-      image_utils::CreateVkImage(resource_manager_->vk_device(), info);
-
-  // Allocate memory and bind it to the image.
-  vk::MemoryRequirements reqs =
-      resource_manager_->vk_device().getImageMemoryRequirements(image);
-  escher::GpuMemPtr memory = allocator_->Allocate(reqs, info.memory_flags);
-  ImagePtr escher_image =
-      Image::New(resource_manager_, info, image, std::move(memory));
-  FXL_CHECK(escher_image);
-  return escher_image;
-}
-
-}  // namespace escher
diff --git a/public/lib/escher/vk/simple_image_factory.h b/public/lib/escher/vk/simple_image_factory.h
deleted file mode 100644
index f37eedc..0000000
--- a/public/lib/escher/vk/simple_image_factory.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2016 The Fuchsia Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef LIB_ESCHER_VK_SIMPLE_IMAGE_FACTORY_H_
-#define LIB_ESCHER_VK_SIMPLE_IMAGE_FACTORY_H_
-
-#include <vulkan/vulkan.hpp>
-
-#include "lib/escher/impl/gpu_uploader.h"
-#include "lib/escher/vk/image.h"
-#include "lib/escher/vk/image_factory.h"
-#include "lib/fxl/memory/ref_counted.h"
-
-namespace escher {
-
-// Creates images by allocating a new chunk of memory directly from the
-// passed allocator. Does not cache images.
-class SimpleImageFactory : public escher::ImageFactory {
- public:
-  SimpleImageFactory(ResourceManager* resource_manager,
-                     escher::GpuAllocator* allocator);
-  ~SimpleImageFactory() override;
-
-  ImagePtr NewImage(const escher::ImageInfo& info) override;
-
- private:
-  ResourceManager* resource_manager_;
-  escher::GpuAllocator* allocator_;
-};
-
-}  // namespace escher
-
-#endif  // LIB_ESCHER_VK_SIMPLE_IMAGE_FACTORY_H_