[Impeller] Refactor KHR swapchains to make it easy to reuse backend agnostic components. (#52002)

Just a refactor to make room for the AHB swapchains implementation while also ensuring that the MSAA and depth-stencil transients memoization as well as the existing surface implementation can be reused by that swapchain backend.

This does a few major things:

* Make an abstract implementation of swapchains, SwapchainVK. This currently has KHRSwapchainVK as its sole subclass but will soon have AHBSwapchainVK.
* There is no more per swapchain backend memoization of the MSAA and depth-stencil textures. This is now moved to SwapchainTransientsVK and can be shared by both backend. This leads into the next change. This also avoids the round trip of the textures first being set on each swapchain image and then accessed to create the onscreen renderpass. Now the transients can access the textures directly. 
* KHRSurfaceVK no longer wraps a KHRSwapchainImageVK. Instead, it deals with TextureSourceVKs (which used to be the base class of KHRSwapchainImageVK). This surface can now magically work with AHBTextureSourceVK since they have a common base class. Since the surface is now backend agnostic, it has been renamed to SurfaceVK.

There is one minor functional change over the previous implementation thought. Earlier, the transients would be created and cached when the swapchain was resized. Now, the same will happen when the first surface frame is attempted to be acquired at the new size. This effectively means that swapchain resized should be faster and do less work if no frames are rendered at the new size (continuous window resized maybe).
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 5ee8602..450c487 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -40292,14 +40292,18 @@
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/shared_object_vk.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/surface_context_vk.h + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.cc + ../../../flutter/LICENSE
-ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/surface_vk.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/surface_vk.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/texture_source_vk.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/texture_source_vk.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/impeller/renderer/backend/vulkan/texture_vk.cc + ../../../flutter/LICENSE
@@ -43169,14 +43173,18 @@
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/shared_object_vk.h
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/surface_context_vk.cc
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/surface_context_vk.h
-FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.cc
-FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h
+FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/surface_vk.cc
+FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/surface_vk.h
+FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.cc
+FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h
+FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.cc
+FILE: ../../../flutter/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/texture_source_vk.cc
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/texture_source_vk.h
 FILE: ../../../flutter/impeller/renderer/backend/vulkan/texture_vk.cc
diff --git a/impeller/playground/backend/vulkan/playground_impl_vk.cc b/impeller/playground/backend/vulkan/playground_impl_vk.cc
index 4755937..07a7661 100644
--- a/impeller/playground/backend/vulkan/playground_impl_vk.cc
+++ b/impeller/playground/backend/vulkan/playground_impl_vk.cc
@@ -20,7 +20,7 @@
 #include "impeller/renderer/backend/vulkan/context_vk.h"
 #include "impeller/renderer/backend/vulkan/formats_vk.h"
 #include "impeller/renderer/backend/vulkan/surface_context_vk.h"
-#include "impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h"
+#include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h"
 #include "impeller/renderer/backend/vulkan/texture_vk.h"
 #include "impeller/renderer/vk/compute_shaders_vk.h"
 #include "impeller/scene/shaders/vk/scene_shaders_vk.h"
diff --git a/impeller/renderer/backend/vulkan/BUILD.gn b/impeller/renderer/backend/vulkan/BUILD.gn
index 8a00a9c..36cc15c 100644
--- a/impeller/renderer/backend/vulkan/BUILD.gn
+++ b/impeller/renderer/backend/vulkan/BUILD.gn
@@ -100,14 +100,18 @@
     "shared_object_vk.h",
     "surface_context_vk.cc",
     "surface_context_vk.h",
-    "swapchain/khr/khr_surface_vk.cc",
-    "swapchain/khr/khr_surface_vk.h",
     "swapchain/khr/khr_swapchain_image_vk.cc",
     "swapchain/khr/khr_swapchain_image_vk.h",
     "swapchain/khr/khr_swapchain_impl_vk.cc",
     "swapchain/khr/khr_swapchain_impl_vk.h",
     "swapchain/khr/khr_swapchain_vk.cc",
     "swapchain/khr/khr_swapchain_vk.h",
+    "swapchain/surface_vk.cc",
+    "swapchain/surface_vk.h",
+    "swapchain/swapchain_transients_vk.cc",
+    "swapchain/swapchain_transients_vk.h",
+    "swapchain/swapchain_vk.cc",
+    "swapchain/swapchain_vk.h",
     "texture_source_vk.cc",
     "texture_source_vk.h",
     "texture_vk.cc",
diff --git a/impeller/renderer/backend/vulkan/surface_context_vk.cc b/impeller/renderer/backend/vulkan/surface_context_vk.cc
index 1b732b5..39eab3c 100644
--- a/impeller/renderer/backend/vulkan/surface_context_vk.cc
+++ b/impeller/renderer/backend/vulkan/surface_context_vk.cc
@@ -64,7 +64,7 @@
 
 bool SurfaceContextVK::SetWindowSurface(vk::UniqueSurfaceKHR surface,
                                         const ISize& size) {
-  auto swapchain = KHRSwapchainVK::Create(parent_, std::move(surface), size);
+  auto swapchain = SwapchainVK::Create(parent_, std::move(surface), size);
   if (!swapchain) {
     VALIDATION_LOG << "Could not create swapchain.";
     return false;
diff --git a/impeller/renderer/backend/vulkan/surface_context_vk.h b/impeller/renderer/backend/vulkan/surface_context_vk.h
index 799edcb..37de3eb 100644
--- a/impeller/renderer/backend/vulkan/surface_context_vk.h
+++ b/impeller/renderer/backend/vulkan/surface_context_vk.h
@@ -16,7 +16,7 @@
 
 class ContextVK;
 class Surface;
-class KHRSwapchainVK;
+class SwapchainVK;
 
 /// For Vulkan, there is both a ContextVK that implements Context and a
 /// SurfaceContextVK that also implements Context and takes a ContextVK as its
@@ -90,7 +90,7 @@
 
  private:
   std::shared_ptr<ContextVK> parent_;
-  std::shared_ptr<KHRSwapchainVK> swapchain_;
+  std::shared_ptr<SwapchainVK> swapchain_;
 };
 
 }  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.cc b/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.cc
deleted file mode 100644
index 2fa35f6..0000000
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.cc
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h"
-
-#include "impeller/core/formats.h"
-#include "impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h"
-#include "impeller/renderer/backend/vulkan/texture_vk.h"
-#include "impeller/renderer/surface.h"
-
-namespace impeller {
-
-std::unique_ptr<KHRSurfaceVK> KHRSurfaceVK::WrapSwapchainImage(
-    const std::shared_ptr<Context>& context,
-    std::shared_ptr<KHRSwapchainImageVK>& swapchain_image,
-    SwapCallback swap_callback,
-    bool enable_msaa) {
-  if (!context || !swapchain_image || !swap_callback) {
-    return nullptr;
-  }
-
-  std::shared_ptr<Texture> msaa_tex;
-  if (enable_msaa) {
-    TextureDescriptor msaa_tex_desc;
-    msaa_tex_desc.storage_mode = StorageMode::kDeviceTransient;
-    msaa_tex_desc.type = TextureType::kTexture2DMultisample;
-    msaa_tex_desc.sample_count = SampleCount::kCount4;
-    msaa_tex_desc.format = swapchain_image->GetPixelFormat();
-    msaa_tex_desc.size = swapchain_image->GetSize();
-    msaa_tex_desc.usage = TextureUsage::kRenderTarget;
-
-    if (!swapchain_image->GetMSAATexture()) {
-      msaa_tex = context->GetResourceAllocator()->CreateTexture(msaa_tex_desc);
-      msaa_tex->SetLabel("ImpellerOnscreenColorMSAA");
-      if (!msaa_tex) {
-        VALIDATION_LOG << "Could not allocate MSAA color texture.";
-        return nullptr;
-      }
-    } else {
-      msaa_tex = swapchain_image->GetMSAATexture();
-    }
-  }
-
-  TextureDescriptor resolve_tex_desc;
-  resolve_tex_desc.type = TextureType::kTexture2D;
-  resolve_tex_desc.format = swapchain_image->GetPixelFormat();
-  resolve_tex_desc.size = swapchain_image->GetSize();
-  resolve_tex_desc.usage = TextureUsage::kRenderTarget;
-  resolve_tex_desc.sample_count = SampleCount::kCount1;
-  resolve_tex_desc.storage_mode = StorageMode::kDevicePrivate;
-
-  std::shared_ptr<Texture> resolve_tex =
-      std::make_shared<TextureVK>(context,         //
-                                  swapchain_image  //
-      );
-
-  if (!resolve_tex) {
-    VALIDATION_LOG << "Could not wrap resolve texture.";
-    return nullptr;
-  }
-  resolve_tex->SetLabel("ImpellerOnscreenResolve");
-
-  ColorAttachment color0;
-  color0.clear_color = Color::DarkSlateGray();
-  color0.load_action = LoadAction::kClear;
-  if (enable_msaa) {
-    color0.texture = msaa_tex;
-    color0.store_action = StoreAction::kMultisampleResolve;
-    color0.resolve_texture = resolve_tex;
-  } else {
-    color0.texture = resolve_tex;
-    color0.store_action = StoreAction::kStore;
-  }
-
-  RenderTarget render_target_desc;
-  render_target_desc.SetColorAttachment(color0, 0u);
-  render_target_desc.SetupDepthStencilAttachments(
-      /*context=*/*context,                            //
-      /*allocator=*/*context->GetResourceAllocator(),  //
-      /*size=*/swapchain_image->GetSize(),             //
-      /*msaa=*/enable_msaa,                            //
-      /*label=*/"Onscreen",                            //
-      /*stencil_attachment_config=*/
-      RenderTarget::kDefaultStencilAttachmentConfig,                       //
-      /*depth_stencil_texture=*/swapchain_image->GetDepthStencilTexture()  //
-  );
-
-  // The constructor is private. So make_unique may not be used.
-  return std::unique_ptr<KHRSurfaceVK>(
-      new KHRSurfaceVK(render_target_desc, std::move(swap_callback)));
-}
-
-KHRSurfaceVK::KHRSurfaceVK(const RenderTarget& target,
-                           SwapCallback swap_callback)
-    : Surface(target), swap_callback_(std::move(swap_callback)) {}
-
-KHRSurfaceVK::~KHRSurfaceVK() = default;
-
-bool KHRSurfaceVK::Present() const {
-  return swap_callback_ ? swap_callback_() : false;
-}
-
-}  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h b/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h
deleted file mode 100644
index e504c53..0000000
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_KHR_KHR_SURFACE_VK_H_
-#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_KHR_KHR_SURFACE_VK_H_
-
-#include <memory>
-
-#include "impeller/renderer/backend/vulkan/context_vk.h"
-#include "impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h"
-#include "impeller/renderer/surface.h"
-
-namespace impeller {
-
-class KHRSurfaceVK final : public Surface {
- public:
-  using SwapCallback = std::function<bool(void)>;
-
-  /// @brief Wrap the swapchain image in a Surface, which provides the
-  ///        additional configuration required for usage as on onscreen render
-  ///        target by Impeller.
-  ///
-  ///        This creates the associated MSAA and depth+stencil texture.
-  static std::unique_ptr<KHRSurfaceVK> WrapSwapchainImage(
-      const std::shared_ptr<Context>& context,
-      std::shared_ptr<KHRSwapchainImageVK>& swapchain_image,
-      SwapCallback swap_callback,
-      bool enable_msaa = true);
-
-  // |Surface|
-  ~KHRSurfaceVK() override;
-
- private:
-  SwapCallback swap_callback_;
-
-  KHRSurfaceVK(const RenderTarget& target, SwapCallback swap_callback);
-
-  // |Surface|
-  bool Present() const override;
-
-  KHRSurfaceVK(const KHRSurfaceVK&) = delete;
-
-  KHRSurfaceVK& operator=(const KHRSurfaceVK&) = delete;
-};
-
-}  // namespace impeller
-
-#endif  // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_KHR_KHR_SURFACE_VK_H_
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc
index d340116..af71a1b 100644
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.cc
@@ -35,31 +35,6 @@
   return is_valid_;
 }
 
-std::shared_ptr<Texture> KHRSwapchainImageVK::GetMSAATexture() const {
-  return msaa_texture_;
-}
-
-std::shared_ptr<Texture> KHRSwapchainImageVK::GetDepthStencilTexture() const {
-  return depth_stencil_texture_;
-}
-
-void KHRSwapchainImageVK::SetMSAATexture(std::shared_ptr<Texture> texture) {
-  msaa_texture_ = std::move(texture);
-}
-
-void KHRSwapchainImageVK::SetDepthStencilTexture(
-    std::shared_ptr<Texture> texture) {
-  depth_stencil_texture_ = std::move(texture);
-}
-
-PixelFormat KHRSwapchainImageVK::GetPixelFormat() const {
-  return desc_.format;
-}
-
-ISize KHRSwapchainImageVK::GetSize() const {
-  return desc_.size;
-}
-
 // |TextureSourceVK|
 vk::Image KHRSwapchainImageVK::GetImage() const {
   return image_;
@@ -75,4 +50,9 @@
   return image_view_.get();
 }
 
+// |TextureSourceVK|
+bool KHRSwapchainImageVK::IsSwapchainImage() const {
+  return true;
+}
+
 }  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h
index a4810a0..92afa32 100644
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h
+++ b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h
@@ -9,7 +9,6 @@
 #include "impeller/renderer/backend/vulkan/formats_vk.h"
 #include "impeller/renderer/backend/vulkan/texture_source_vk.h"
 #include "impeller/renderer/backend/vulkan/vk.h"
-#include "vulkan/vulkan_handles.hpp"
 
 namespace impeller {
 
@@ -24,33 +23,21 @@
 
   bool IsValid() const;
 
-  PixelFormat GetPixelFormat() const;
-
-  ISize GetSize() const;
-
   // |TextureSourceVK|
   vk::Image GetImage() const override;
 
-  std::shared_ptr<Texture> GetMSAATexture() const;
-
-  std::shared_ptr<Texture> GetDepthStencilTexture() const;
-
   // |TextureSourceVK|
   vk::ImageView GetImageView() const override;
 
+  // |TextureSourceVK|
   vk::ImageView GetRenderTargetView() const override;
 
-  void SetMSAATexture(std::shared_ptr<Texture> texture);
-
-  void SetDepthStencilTexture(std::shared_ptr<Texture> texture);
-
-  bool IsSwapchainImage() const override { return true; }
+  // |TextureSourceVK|
+  bool IsSwapchainImage() const override;
 
  private:
   vk::Image image_ = VK_NULL_HANDLE;
   vk::UniqueImageView image_view_ = {};
-  std::shared_ptr<Texture> msaa_texture_;
-  std::shared_ptr<Texture> depth_stencil_texture_;
   bool is_valid_ = false;
 
   KHRSwapchainImageVK(const KHRSwapchainImageVK&) = delete;
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc
index 375c536..8be8883 100644
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc
@@ -12,10 +12,9 @@
 #include "impeller/renderer/backend/vulkan/context_vk.h"
 #include "impeller/renderer/backend/vulkan/formats_vk.h"
 #include "impeller/renderer/backend/vulkan/gpu_tracer_vk.h"
-#include "impeller/renderer/backend/vulkan/swapchain/khr/khr_surface_vk.h"
 #include "impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_image_vk.h"
+#include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h"
 #include "impeller/renderer/context.h"
-#include "vulkan/vulkan_structs.hpp"
 
 namespace impeller {
 
@@ -228,40 +227,6 @@
   texture_desc.size = ISize::MakeWH(swapchain_info.imageExtent.width,
                                     swapchain_info.imageExtent.height);
 
-  // Allocate a single onscreen MSAA texture and Depth+Stencil Texture to
-  // be shared by all swapchain images.
-  TextureDescriptor msaa_desc;
-  msaa_desc.storage_mode = StorageMode::kDeviceTransient;
-  msaa_desc.type = TextureType::kTexture2DMultisample;
-  msaa_desc.sample_count = SampleCount::kCount4;
-  msaa_desc.format = texture_desc.format;
-  msaa_desc.size = texture_desc.size;
-  msaa_desc.usage = TextureUsage::kRenderTarget;
-
-  // The depth+stencil configuration matches the configuration used by
-  // RenderTarget::SetupDepthStencilAttachments and matching the swapchain
-  // image dimensions and sample count.
-  TextureDescriptor depth_stencil_desc;
-  depth_stencil_desc.storage_mode = StorageMode::kDeviceTransient;
-  if (enable_msaa) {
-    depth_stencil_desc.type = TextureType::kTexture2DMultisample;
-    depth_stencil_desc.sample_count = SampleCount::kCount4;
-  } else {
-    depth_stencil_desc.type = TextureType::kTexture2D;
-    depth_stencil_desc.sample_count = SampleCount::kCount1;
-  }
-  depth_stencil_desc.format =
-      context->GetCapabilities()->GetDefaultDepthStencilFormat();
-  depth_stencil_desc.size = texture_desc.size;
-  depth_stencil_desc.usage = TextureUsage::kRenderTarget;
-
-  std::shared_ptr<Texture> msaa_texture;
-  if (enable_msaa) {
-    msaa_texture = context->GetResourceAllocator()->CreateTexture(msaa_desc);
-  }
-  std::shared_ptr<Texture> depth_stencil_texture =
-      context->GetResourceAllocator()->CreateTexture(depth_stencil_desc);
-
   std::vector<std::shared_ptr<KHRSwapchainImageVK>> swapchain_images;
   for (const auto& image : images) {
     auto swapchain_image = std::make_shared<KHRSwapchainImageVK>(
@@ -273,9 +238,6 @@
       VALIDATION_LOG << "Could not create swapchain image.";
       return;
     }
-    swapchain_image->SetMSAATexture(msaa_texture);
-    swapchain_image->SetDepthStencilTexture(depth_stencil_texture);
-
     ContextVK::SetDebugName(
         vk_context.GetDevice(), swapchain_image->GetImage(),
         "SwapchainImage" + std::to_string(swapchain_images.size()));
@@ -302,6 +264,8 @@
   surface_ = std::move(surface);
   surface_format_ = swapchain_info.imageFormat;
   swapchain_ = std::move(swapchain);
+  transients_ = std::make_shared<SwapchainTransientsVK>(context, texture_desc,
+                                                        enable_msaa);
   images_ = std::move(swapchain_images);
   synchronizers_ = std::move(synchronizers);
   current_frame_ = synchronizers_.size() - 1u;
@@ -403,17 +367,16 @@
 
   auto image = images_[index % images_.size()];
   uint32_t image_index = index;
-  return AcquireResult{KHRSurfaceVK::WrapSwapchainImage(
-      context_strong,  // context
-      image,           // swapchain image
+  return AcquireResult{SurfaceVK::WrapSwapchainImage(
+      transients_,  // transients
+      image,        // swapchain image
       [weak_swapchain = weak_from_this(), image, image_index]() -> bool {
         auto swapchain = weak_swapchain.lock();
         if (!swapchain) {
           return false;
         }
         return swapchain->Present(image, image_index);
-      },            // swap callback
-      enable_msaa_  //
+      }  // swap callback
       )};
 }
 
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h
index 84d4f35..049ab11 100644
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h
+++ b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h
@@ -10,8 +10,8 @@
 #include <variant>
 
 #include "impeller/geometry/size.h"
+#include "impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h"
 #include "impeller/renderer/backend/vulkan/vk.h"
-#include "vulkan/vulkan_enums.hpp"
 
 namespace impeller {
 
@@ -68,6 +68,7 @@
   vk::UniqueSurfaceKHR surface_;
   vk::Format surface_format_ = vk::Format::eUndefined;
   vk::UniqueSwapchainKHR swapchain_;
+  std::shared_ptr<SwapchainTransientsVK> transients_;
   std::vector<std::shared_ptr<KHRSwapchainImageVK>> images_;
   std::vector<std::unique_ptr<KHRFrameSynchronizerVK>> synchronizers_;
   size_t current_frame_ = 0u;
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc
index a126bc6..0877e55 100644
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc
+++ b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.cc
@@ -10,25 +10,22 @@
 
 namespace impeller {
 
-std::shared_ptr<KHRSwapchainVK> KHRSwapchainVK::Create(
-    const std::shared_ptr<Context>& context,
-    vk::UniqueSurfaceKHR surface,
-    const ISize& size,
-    bool enable_msaa) {
-  auto impl = KHRSwapchainImplVK::Create(context, std::move(surface), size,
-                                         enable_msaa);
-  if (!impl || !impl->IsValid()) {
-    VALIDATION_LOG << "Failed to create SwapchainVK implementation.";
-    return nullptr;
-  }
-  return std::shared_ptr<KHRSwapchainVK>(
-      new KHRSwapchainVK(std::move(impl), size, enable_msaa));
-}
-
-KHRSwapchainVK::KHRSwapchainVK(std::shared_ptr<KHRSwapchainImplVK> impl,
+KHRSwapchainVK::KHRSwapchainVK(const std::shared_ptr<Context>& context,
+                               vk::UniqueSurfaceKHR surface,
                                const ISize& size,
                                bool enable_msaa)
-    : impl_(std::move(impl)), size_(size), enable_msaa_(enable_msaa) {}
+    : size_(size), enable_msaa_(enable_msaa) {
+  auto impl = KHRSwapchainImplVK::Create(context,             //
+                                         std::move(surface),  //
+                                         size_,               //
+                                         enable_msaa_         //
+  );
+  if (!impl || !impl->IsValid()) {
+    VALIDATION_LOG << "Failed to create SwapchainVK implementation.";
+    return;
+  }
+  impl_ = std::move(impl);
+}
 
 KHRSwapchainVK::~KHRSwapchainVK() = default;
 
diff --git a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h
index da90fe6..21acdfd 100644
--- a/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h
+++ b/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "impeller/geometry/size.h"
+#include "impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h"
 #include "impeller/renderer/backend/vulkan/vk.h"
 #include "impeller/renderer/context.h"
 #include "impeller/renderer/surface.h"
@@ -17,37 +18,34 @@
 class KHRSwapchainImplVK;
 
 //------------------------------------------------------------------------------
-/// @brief      A swapchain that adapts to the underlying surface going out of
-///             date. If the caller cannot acquire the next drawable, it is due
-///             to an unrecoverable error and the swapchain must be recreated
-///             with a new surface.
+/// @brief      A swapchain implemented backed by VK_KHR_swapchain and
+///             VK_KHR_surface.
 ///
-class KHRSwapchainVK {
+class KHRSwapchainVK final : public SwapchainVK {
  public:
-  static std::shared_ptr<KHRSwapchainVK> Create(
-      const std::shared_ptr<Context>& context,
-      vk::UniqueSurfaceKHR surface,
-      const ISize& size,
-      bool enable_msaa = true);
-
   ~KHRSwapchainVK();
 
-  bool IsValid() const;
+  // |SwapchainVK|
+  bool IsValid() const override;
 
-  std::unique_ptr<Surface> AcquireNextDrawable();
+  // |SwapchainVK|
+  std::unique_ptr<Surface> AcquireNextDrawable() override;
 
-  vk::Format GetSurfaceFormat() const;
+  // |SwapchainVK|
+  vk::Format GetSurfaceFormat() const override;
 
-  /// @brief Mark the current swapchain configuration as dirty, forcing it to be
-  ///        recreated on the next frame.
-  void UpdateSurfaceSize(const ISize& size);
+  // |SwapchainVK|
+  void UpdateSurfaceSize(const ISize& size) override;
 
  private:
+  friend class SwapchainVK;
+
   std::shared_ptr<KHRSwapchainImplVK> impl_;
   ISize size_;
   const bool enable_msaa_;
 
-  KHRSwapchainVK(std::shared_ptr<KHRSwapchainImplVK> impl,
+  KHRSwapchainVK(const std::shared_ptr<Context>& context,
+                 vk::UniqueSurfaceKHR surface,
                  const ISize& size,
                  bool enable_msaa);
 
diff --git a/impeller/renderer/backend/vulkan/swapchain/surface_vk.cc b/impeller/renderer/backend/vulkan/swapchain/surface_vk.cc
new file mode 100644
index 0000000..0128579
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/swapchain/surface_vk.cc
@@ -0,0 +1,87 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "impeller/renderer/backend/vulkan/swapchain/surface_vk.h"
+
+#include "impeller/core/formats.h"
+#include "impeller/renderer/backend/vulkan/texture_vk.h"
+#include "impeller/renderer/surface.h"
+
+namespace impeller {
+
+std::unique_ptr<SurfaceVK> SurfaceVK::WrapSwapchainImage(
+    const std::shared_ptr<SwapchainTransientsVK>& transients,
+    const std::shared_ptr<TextureSourceVK>& swapchain_image,
+    SwapCallback swap_callback) {
+  if (!transients || !swapchain_image || !swap_callback) {
+    return nullptr;
+  }
+
+  auto context = transients->GetContext().lock();
+  if (!context) {
+    return nullptr;
+  }
+
+  const auto enable_msaa = transients->IsMSAAEnabled();
+
+  const auto swapchain_tex_desc = swapchain_image->GetTextureDescriptor();
+
+  TextureDescriptor resolve_tex_desc;
+  resolve_tex_desc.type = TextureType::kTexture2D;
+  resolve_tex_desc.format = swapchain_tex_desc.format;
+  resolve_tex_desc.size = swapchain_tex_desc.size;
+  resolve_tex_desc.usage = TextureUsage::kRenderTarget;
+  resolve_tex_desc.sample_count = SampleCount::kCount1;
+  resolve_tex_desc.storage_mode = StorageMode::kDevicePrivate;
+
+  std::shared_ptr<Texture> resolve_tex =
+      std::make_shared<TextureVK>(context,         //
+                                  swapchain_image  //
+      );
+
+  if (!resolve_tex) {
+    return nullptr;
+  }
+  resolve_tex->SetLabel("ImpellerOnscreenResolve");
+
+  ColorAttachment color0;
+  color0.clear_color = Color::DarkSlateGray();
+  color0.load_action = LoadAction::kClear;
+  if (enable_msaa) {
+    color0.texture = transients->GetMSAATexture();
+    color0.store_action = StoreAction::kMultisampleResolve;
+    color0.resolve_texture = resolve_tex;
+  } else {
+    color0.texture = resolve_tex;
+    color0.store_action = StoreAction::kStore;
+  }
+
+  RenderTarget render_target_desc;
+  render_target_desc.SetColorAttachment(color0, 0u);
+  render_target_desc.SetupDepthStencilAttachments(
+      /*context=*/*context,                            //
+      /*allocator=*/*context->GetResourceAllocator(),  //
+      /*size=*/swapchain_tex_desc.size,                //
+      /*msaa=*/enable_msaa,                            //
+      /*label=*/"Onscreen",                            //
+      /*stencil_attachment_config=*/
+      RenderTarget::kDefaultStencilAttachmentConfig,                  //
+      /*depth_stencil_texture=*/transients->GetDepthStencilTexture()  //
+  );
+
+  // The constructor is private. So make_unique may not be used.
+  return std::unique_ptr<SurfaceVK>(
+      new SurfaceVK(render_target_desc, std::move(swap_callback)));
+}
+
+SurfaceVK::SurfaceVK(const RenderTarget& target, SwapCallback swap_callback)
+    : Surface(target), swap_callback_(std::move(swap_callback)) {}
+
+SurfaceVK::~SurfaceVK() = default;
+
+bool SurfaceVK::Present() const {
+  return swap_callback_ ? swap_callback_() : false;
+}
+
+}  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/surface_vk.h b/impeller/renderer/backend/vulkan/swapchain/surface_vk.h
new file mode 100644
index 0000000..44b069a
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/swapchain/surface_vk.h
@@ -0,0 +1,49 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SURFACE_VK_H_
+#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SURFACE_VK_H_
+
+#include <memory>
+
+#include "impeller/renderer/backend/vulkan/context_vk.h"
+#include "impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h"
+#include "impeller/renderer/backend/vulkan/texture_source_vk.h"
+#include "impeller/renderer/surface.h"
+
+namespace impeller {
+
+class SurfaceVK final : public Surface {
+ public:
+  using SwapCallback = std::function<bool(void)>;
+
+  /// @brief Wrap the swapchain image in a Surface, which provides the
+  ///        additional configuration required for usage as on onscreen render
+  ///        target by Impeller.
+  ///
+  ///        This creates the associated MSAA and depth+stencil texture.
+  static std::unique_ptr<SurfaceVK> WrapSwapchainImage(
+      const std::shared_ptr<SwapchainTransientsVK>& transients,
+      const std::shared_ptr<TextureSourceVK>& swapchain_image,
+      SwapCallback swap_callback);
+
+  // |Surface|
+  ~SurfaceVK() override;
+
+ private:
+  SwapCallback swap_callback_;
+
+  SurfaceVK(const RenderTarget& target, SwapCallback swap_callback);
+
+  // |Surface|
+  bool Present() const override;
+
+  SurfaceVK(const SurfaceVK&) = delete;
+
+  SurfaceVK& operator=(const SurfaceVK&) = delete;
+};
+
+}  // namespace impeller
+
+#endif  // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SURFACE_VK_H_
diff --git a/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.cc b/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.cc
new file mode 100644
index 0000000..d097b11
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.cc
@@ -0,0 +1,98 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h"
+
+#include "flutter/fml/trace_event.h"
+
+namespace impeller {
+
+SwapchainTransientsVK::SwapchainTransientsVK(std::weak_ptr<Context> context,
+                                             TextureDescriptor desc,
+                                             bool enable_msaa)
+    : context_(std::move(context)), desc_(desc), enable_msaa_(enable_msaa) {}
+
+SwapchainTransientsVK::~SwapchainTransientsVK() = default;
+
+const std::shared_ptr<Texture>& SwapchainTransientsVK::GetMSAATexture() {
+  if (cached_msaa_texture_) {
+    return cached_msaa_texture_;
+  }
+  cached_msaa_texture_ = CreateMSAATexture();
+  return cached_msaa_texture_;
+}
+
+const std::shared_ptr<Texture>&
+SwapchainTransientsVK::GetDepthStencilTexture() {
+  if (cached_depth_stencil_) {
+    return cached_depth_stencil_;
+  }
+  cached_depth_stencil_ = CreateDepthStencilTexture();
+  return cached_depth_stencil_;
+}
+
+std::shared_ptr<Texture> SwapchainTransientsVK::CreateMSAATexture() const {
+  TRACE_EVENT0("impeller", __FUNCTION__);
+  if (!enable_msaa_) {
+    return nullptr;
+  }
+  TextureDescriptor msaa_desc;
+  msaa_desc.storage_mode = StorageMode::kDeviceTransient;
+  msaa_desc.type = TextureType::kTexture2DMultisample;
+  msaa_desc.sample_count = SampleCount::kCount4;
+  msaa_desc.format = desc_.format;
+  msaa_desc.size = desc_.size;
+  msaa_desc.usage = TextureUsage::kRenderTarget;
+
+  auto context = context_.lock();
+  if (!context) {
+    return nullptr;
+  }
+  auto texture = context->GetResourceAllocator()->CreateTexture(msaa_desc);
+  if (!texture) {
+    return nullptr;
+  }
+  texture->SetLabel("SwapchainMSAA");
+  return texture;
+}
+
+std::shared_ptr<Texture> SwapchainTransientsVK::CreateDepthStencilTexture()
+    const {
+  TRACE_EVENT0("impeller", __FUNCTION__);
+  auto context = context_.lock();
+  if (!context) {
+    return nullptr;
+  }
+  TextureDescriptor depth_stencil_desc;
+  depth_stencil_desc.storage_mode = StorageMode::kDeviceTransient;
+  if (enable_msaa_) {
+    depth_stencil_desc.type = TextureType::kTexture2DMultisample;
+    depth_stencil_desc.sample_count = SampleCount::kCount4;
+  } else {
+    depth_stencil_desc.type = TextureType::kTexture2D;
+    depth_stencil_desc.sample_count = SampleCount::kCount1;
+  }
+  depth_stencil_desc.format =
+      context->GetCapabilities()->GetDefaultDepthStencilFormat();
+  depth_stencil_desc.size = desc_.size;
+  depth_stencil_desc.usage = TextureUsage::kRenderTarget;
+
+  auto texture =
+      context->GetResourceAllocator()->CreateTexture(depth_stencil_desc);
+  if (!texture) {
+    return nullptr;
+  }
+  texture->SetLabel("SwapchainDepthStencil");
+  return texture;
+}
+
+bool SwapchainTransientsVK::IsMSAAEnabled() const {
+  return enable_msaa_;
+}
+
+const std::weak_ptr<Context>& SwapchainTransientsVK::GetContext() const {
+  return context_;
+}
+
+}  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h b/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h
new file mode 100644
index 0000000..1ea6f7b
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/swapchain/swapchain_transients_vk.h
@@ -0,0 +1,57 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SWAPCHAIN_TRANSIENTS_VK_H_
+#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SWAPCHAIN_TRANSIENTS_VK_H_
+
+#include "impeller/core/texture.h"
+#include "impeller/core/texture_descriptor.h"
+#include "impeller/renderer/context.h"
+
+namespace impeller {
+
+//------------------------------------------------------------------------------
+/// @brief      Resources, meant to be memoized by the texture descriptor of the
+///             wrapped swapchain images, that are intuitively cheap to create
+///             but have been observed to be time consuming to construct on some
+///             Vulkan drivers. This includes the device-transient MSAA and
+///             depth-stencil textures.
+///
+///             The same textures are used for all swapchain images.
+///
+class SwapchainTransientsVK {
+ public:
+  explicit SwapchainTransientsVK(std::weak_ptr<Context> context,
+                                 TextureDescriptor desc,
+                                 bool enable_msaa);
+
+  ~SwapchainTransientsVK();
+
+  SwapchainTransientsVK(const SwapchainTransientsVK&) = delete;
+
+  SwapchainTransientsVK& operator=(const SwapchainTransientsVK&) = delete;
+
+  const std::weak_ptr<Context>& GetContext() const;
+
+  bool IsMSAAEnabled() const;
+
+  const std::shared_ptr<Texture>& GetMSAATexture();
+
+  const std::shared_ptr<Texture>& GetDepthStencilTexture();
+
+ private:
+  std::weak_ptr<Context> context_;
+  const TextureDescriptor desc_;
+  const bool enable_msaa_;
+  std::shared_ptr<Texture> cached_msaa_texture_;
+  std::shared_ptr<Texture> cached_depth_stencil_;
+
+  std::shared_ptr<Texture> CreateMSAATexture() const;
+
+  std::shared_ptr<Texture> CreateDepthStencilTexture() const;
+};
+
+}  // namespace impeller
+
+#endif  // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SWAPCHAIN_TRANSIENTS_VK_H_
diff --git a/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.cc b/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.cc
new file mode 100644
index 0000000..66dc1e6
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.cc
@@ -0,0 +1,24 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h"
+
+#include "impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_vk.h"
+
+namespace impeller {
+
+std::shared_ptr<SwapchainVK> SwapchainVK::Create(
+    const std::shared_ptr<Context>& context,
+    vk::UniqueSurfaceKHR surface,
+    const ISize& size,
+    bool enable_msaa) {
+  return std::shared_ptr<KHRSwapchainVK>(
+      new KHRSwapchainVK(context, std::move(surface), size, enable_msaa));
+}
+
+SwapchainVK::SwapchainVK() = default;
+
+SwapchainVK::~SwapchainVK() = default;
+
+}  // namespace impeller
diff --git a/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h b/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h
new file mode 100644
index 0000000..624b99d
--- /dev/null
+++ b/impeller/renderer/backend/vulkan/swapchain/swapchain_vk.h
@@ -0,0 +1,53 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SWAPCHAIN_VK_H_
+#define FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SWAPCHAIN_VK_H_
+
+#include <memory>
+
+#include "impeller/geometry/size.h"
+#include "impeller/renderer/backend/vulkan/vk.h"
+#include "impeller/renderer/context.h"
+#include "impeller/renderer/surface.h"
+
+namespace impeller {
+
+//------------------------------------------------------------------------------
+/// @brief      A swapchain that adapts to the underlying surface going out of
+///             date. If the caller cannot acquire the next drawable, it is due
+///             to an unrecoverable error and the swapchain must be recreated
+///             with a new surface.
+///
+class SwapchainVK {
+ public:
+  static std::shared_ptr<SwapchainVK> Create(
+      const std::shared_ptr<Context>& context,
+      vk::UniqueSurfaceKHR surface,
+      const ISize& size,
+      bool enable_msaa = true);
+
+  virtual ~SwapchainVK();
+
+  SwapchainVK(const SwapchainVK&) = delete;
+
+  SwapchainVK& operator=(const SwapchainVK&) = delete;
+
+  virtual bool IsValid() const = 0;
+
+  virtual std::unique_ptr<Surface> AcquireNextDrawable() = 0;
+
+  virtual vk::Format GetSurfaceFormat() const = 0;
+
+  /// @brief Mark the current swapchain configuration as dirty, forcing it to be
+  ///        recreated on the next frame.
+  virtual void UpdateSurfaceSize(const ISize& size) = 0;
+
+ protected:
+  SwapchainVK();
+};
+
+}  // namespace impeller
+
+#endif  // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_SWAPCHAIN_SWAPCHAIN_VK_H_