[Impeller] Add buffer-to-texture blit capability check; fix GL+VK playgrounds on macOS (#41320)

GL/VK playgrounds that load image fixtures are currently broken on
macOS.
diff --git a/impeller/playground/playground.cc b/impeller/playground/playground.cc
index ec48aa3..4f61c5c 100644
--- a/impeller/playground/playground.cc
+++ b/impeller/playground/playground.cc
@@ -395,82 +395,82 @@
   return image;
 }
 
-namespace {
-std::shared_ptr<Texture> CreateTextureForDecompressedImage(
+static std::shared_ptr<Texture> CreateTextureForDecompressedImage(
     const std::shared_ptr<Context>& context,
     DecompressedImage& decompressed_image,
     bool enable_mipmapping) {
   // TODO(https://github.com/flutter/flutter/issues/123468): copying buffers to
   // textures is not implemented for GLES/Vulkan.
-#if FML_OS_MACOSX
-  impeller::TextureDescriptor texture_descriptor;
-  texture_descriptor.storage_mode = impeller::StorageMode::kDevicePrivate;
-  texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
-  texture_descriptor.size = decompressed_image.GetSize();
-  texture_descriptor.mip_count =
-      enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
+  if (context->GetCapabilities()->SupportsBufferToTextureBlits()) {
+    impeller::TextureDescriptor texture_descriptor;
+    texture_descriptor.storage_mode = impeller::StorageMode::kDevicePrivate;
+    texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
+    texture_descriptor.size = decompressed_image.GetSize();
+    texture_descriptor.mip_count =
+        enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
 
-  auto dest_texture =
-      context->GetResourceAllocator()->CreateTexture(texture_descriptor);
-  if (!dest_texture) {
-    FML_DLOG(ERROR) << "Could not create Impeller texture.";
-    return nullptr;
+    auto dest_texture =
+        context->GetResourceAllocator()->CreateTexture(texture_descriptor);
+    if (!dest_texture) {
+      FML_DLOG(ERROR) << "Could not create Impeller texture.";
+      return nullptr;
+    }
+
+    auto buffer = context->GetResourceAllocator()->CreateBufferWithCopy(
+        *decompressed_image.GetAllocation().get());
+
+    dest_texture->SetLabel(
+        impeller::SPrintF("ui.Image(%p)", dest_texture.get()).c_str());
+
+    auto command_buffer = context->CreateCommandBuffer();
+    if (!command_buffer) {
+      FML_DLOG(ERROR)
+          << "Could not create command buffer for mipmap generation.";
+      return nullptr;
+    }
+    command_buffer->SetLabel("Mipmap Command Buffer");
+
+    auto blit_pass = command_buffer->CreateBlitPass();
+    if (!blit_pass) {
+      FML_DLOG(ERROR) << "Could not create blit pass for mipmap generation.";
+      return nullptr;
+    }
+    blit_pass->SetLabel("Mipmap Blit Pass");
+    blit_pass->AddCopy(buffer->AsBufferView(), dest_texture);
+    if (enable_mipmapping) {
+      blit_pass->GenerateMipmap(dest_texture);
+    }
+
+    blit_pass->EncodeCommands(context->GetResourceAllocator());
+    if (!command_buffer->SubmitCommands()) {
+      FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
+      return nullptr;
+    }
+    return dest_texture;
+  } else {  // Doesn't support buffer-to-texture blits.
+    auto texture_descriptor = TextureDescriptor{};
+    texture_descriptor.storage_mode = StorageMode::kHostVisible;
+    texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
+    texture_descriptor.size = decompressed_image.GetSize();
+    texture_descriptor.mip_count =
+        enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
+
+    auto texture =
+        context->GetResourceAllocator()->CreateTexture(texture_descriptor);
+    if (!texture) {
+      VALIDATION_LOG << "Could not allocate texture for fixture.";
+      return nullptr;
+    }
+
+    auto uploaded = texture->SetContents(decompressed_image.GetAllocation());
+    if (!uploaded) {
+      VALIDATION_LOG
+          << "Could not upload texture to device memory for fixture.";
+      return nullptr;
+    }
+    return texture;
   }
-
-  auto buffer = context->GetResourceAllocator()->CreateBufferWithCopy(
-      *decompressed_image.GetAllocation().get());
-
-  dest_texture->SetLabel(
-      impeller::SPrintF("ui.Image(%p)", dest_texture.get()).c_str());
-
-  auto command_buffer = context->CreateCommandBuffer();
-  if (!command_buffer) {
-    FML_DLOG(ERROR) << "Could not create command buffer for mipmap generation.";
-    return nullptr;
-  }
-  command_buffer->SetLabel("Mipmap Command Buffer");
-
-  auto blit_pass = command_buffer->CreateBlitPass();
-  if (!blit_pass) {
-    FML_DLOG(ERROR) << "Could not create blit pass for mipmap generation.";
-    return nullptr;
-  }
-  blit_pass->SetLabel("Mipmap Blit Pass");
-  blit_pass->AddCopy(buffer->AsBufferView(), dest_texture);
-  if (enable_mipmapping) {
-    blit_pass->GenerateMipmap(dest_texture);
-  }
-
-  blit_pass->EncodeCommands(context->GetResourceAllocator());
-  if (!command_buffer->SubmitCommands()) {
-    FML_DLOG(ERROR) << "Failed to submit blit pass command buffer.";
-    return nullptr;
-  }
-  return dest_texture;
-#else
-  auto texture_descriptor = TextureDescriptor{};
-  texture_descriptor.storage_mode = StorageMode::kHostVisible;
-  texture_descriptor.format = PixelFormat::kR8G8B8A8UNormInt;
-  texture_descriptor.size = decompressed_image.GetSize();
-  texture_descriptor.mip_count =
-      enable_mipmapping ? decompressed_image.GetSize().MipCount() : 1u;
-
-  auto texture =
-      context->GetResourceAllocator()->CreateTexture(texture_descriptor);
-  if (!texture) {
-    VALIDATION_LOG << "Could not allocate texture for fixture.";
-    return nullptr;
-  }
-
-  auto uploaded = texture->SetContents(decompressed_image.GetAllocation());
-  if (!uploaded) {
-    VALIDATION_LOG << "Could not upload texture to device memory for fixture.";
-    return nullptr;
-  }
-  return texture;
-#endif  // FML_OS_MACOS
 }
-}  // namespace
 
 std::shared_ptr<Texture> Playground::CreateTextureForMapping(
     const std::shared_ptr<Context>& context,
diff --git a/impeller/renderer/backend/gles/context_gles.cc b/impeller/renderer/backend/gles/context_gles.cc
index fb42495..f2bba7c 100644
--- a/impeller/renderer/backend/gles/context_gles.cc
+++ b/impeller/renderer/backend/gles/context_gles.cc
@@ -65,6 +65,7 @@
             .SetHasThreadingRestrictions(true)
             .SetSupportsOffscreenMSAA(false)
             .SetSupportsSSBO(false)
+            .SetSupportsBufferToTextureBlits(false)
             .SetSupportsTextureToTextureBlits(
                 reactor_->GetProcTable().BlitFramebuffer.IsAvailable())
             .SetSupportsFramebufferFetch(false)
diff --git a/impeller/renderer/backend/metal/context_mtl.mm b/impeller/renderer/backend/metal/context_mtl.mm
index 5976c5b..2bca0a1 100644
--- a/impeller/renderer/backend/metal/context_mtl.mm
+++ b/impeller/renderer/backend/metal/context_mtl.mm
@@ -52,6 +52,7 @@
       .SetHasThreadingRestrictions(false)
       .SetSupportsOffscreenMSAA(true)
       .SetSupportsSSBO(true)
+      .SetSupportsBufferToTextureBlits(true)
       .SetSupportsTextureToTextureBlits(true)
       .SetSupportsDecalTileMode(true)
       .SetSupportsFramebufferFetch(DeviceSupportsFramebufferFetch(device))
diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.cc b/impeller/renderer/backend/vulkan/capabilities_vk.cc
index 7346ccd..ff4410f 100644
--- a/impeller/renderer/backend/vulkan/capabilities_vk.cc
+++ b/impeller/renderer/backend/vulkan/capabilities_vk.cc
@@ -314,6 +314,11 @@
 }
 
 // |Capabilities|
+bool CapabilitiesVK::SupportsBufferToTextureBlits() const {
+  return false;
+}
+
+// |Capabilities|
 bool CapabilitiesVK::SupportsTextureToTextureBlits() const {
   return true;
 }
diff --git a/impeller/renderer/backend/vulkan/capabilities_vk.h b/impeller/renderer/backend/vulkan/capabilities_vk.h
index 78fdc62..006ed6f 100644
--- a/impeller/renderer/backend/vulkan/capabilities_vk.h
+++ b/impeller/renderer/backend/vulkan/capabilities_vk.h
@@ -56,6 +56,9 @@
   bool SupportsSSBO() const override;
 
   // |Capabilities|
+  bool SupportsBufferToTextureBlits() const override;
+
+  // |Capabilities|
   bool SupportsTextureToTextureBlits() const override;
 
   // |Capabilities|
diff --git a/impeller/renderer/capabilities.cc b/impeller/renderer/capabilities.cc
index f92f69e..5b23d4c 100644
--- a/impeller/renderer/capabilities.cc
+++ b/impeller/renderer/capabilities.cc
@@ -29,6 +29,11 @@
   bool SupportsSSBO() const override { return supports_ssbo_; }
 
   // |Capabilities|
+  bool SupportsBufferToTextureBlits() const override {
+    return supports_buffer_to_texture_blits_;
+  }
+
+  // |Capabilities|
   bool SupportsTextureToTextureBlits() const override {
     return supports_texture_to_texture_blits_;
   }
@@ -75,6 +80,7 @@
   StandardCapabilities(bool has_threading_restrictions,
                        bool supports_offscreen_msaa,
                        bool supports_ssbo,
+                       bool supports_buffer_to_texture_blits,
                        bool supports_texture_to_texture_blits,
                        bool supports_framebuffer_fetch,
                        bool supports_compute,
@@ -87,6 +93,7 @@
       : has_threading_restrictions_(has_threading_restrictions),
         supports_offscreen_msaa_(supports_offscreen_msaa),
         supports_ssbo_(supports_ssbo),
+        supports_buffer_to_texture_blits_(supports_buffer_to_texture_blits),
         supports_texture_to_texture_blits_(supports_texture_to_texture_blits),
         supports_framebuffer_fetch_(supports_framebuffer_fetch),
         supports_compute_(supports_compute),
@@ -103,6 +110,7 @@
   bool has_threading_restrictions_ = false;
   bool supports_offscreen_msaa_ = false;
   bool supports_ssbo_ = false;
+  bool supports_buffer_to_texture_blits_ = false;
   bool supports_texture_to_texture_blits_ = false;
   bool supports_framebuffer_fetch_ = false;
   bool supports_compute_ = false;
@@ -136,6 +144,12 @@
   return *this;
 }
 
+CapabilitiesBuilder& CapabilitiesBuilder::SetSupportsBufferToTextureBlits(
+    bool value) {
+  supports_buffer_to_texture_blits_ = value;
+  return *this;
+}
+
 CapabilitiesBuilder& CapabilitiesBuilder::SetSupportsTextureToTextureBlits(
     bool value) {
   supports_texture_to_texture_blits_ = value;
@@ -189,6 +203,7 @@
       has_threading_restrictions_,                                        //
       supports_offscreen_msaa_,                                           //
       supports_ssbo_,                                                     //
+      supports_buffer_to_texture_blits_,                                  //
       supports_texture_to_texture_blits_,                                 //
       supports_framebuffer_fetch_,                                        //
       supports_compute_,                                                  //
diff --git a/impeller/renderer/capabilities.h b/impeller/renderer/capabilities.h
index f2dde2c..78c0c20 100644
--- a/impeller/renderer/capabilities.h
+++ b/impeller/renderer/capabilities.h
@@ -21,6 +21,8 @@
 
   virtual bool SupportsSSBO() const = 0;
 
+  virtual bool SupportsBufferToTextureBlits() const = 0;
+
   virtual bool SupportsTextureToTextureBlits() const = 0;
 
   virtual bool SupportsFramebufferFetch() const = 0;
@@ -57,6 +59,8 @@
 
   CapabilitiesBuilder& SetSupportsSSBO(bool value);
 
+  CapabilitiesBuilder& SetSupportsBufferToTextureBlits(bool value);
+
   CapabilitiesBuilder& SetSupportsTextureToTextureBlits(bool value);
 
   CapabilitiesBuilder& SetSupportsFramebufferFetch(bool value);
@@ -80,6 +84,7 @@
   bool has_threading_restrictions_ = false;
   bool supports_offscreen_msaa_ = false;
   bool supports_ssbo_ = false;
+  bool supports_buffer_to_texture_blits_ = false;
   bool supports_texture_to_texture_blits_ = false;
   bool supports_framebuffer_fetch_ = false;
   bool supports_compute_ = false;