[Impeller] Enable support for Apple Silicon Macs and tvOS devices. (#35287)

diff --git a/impeller/renderer/allocator.cc b/impeller/renderer/allocator.cc
index 8151eec..49e6948 100644
--- a/impeller/renderer/allocator.cc
+++ b/impeller/renderer/allocator.cc
@@ -13,20 +13,6 @@
 
 Allocator::~Allocator() = default;
 
-bool Allocator::RequiresExplicitHostSynchronization(StorageMode mode) {
-  if (mode != StorageMode::kHostVisible) {
-    return false;
-  }
-
-#if FML_OS_IOS
-  // StorageMode::kHostVisible is MTLStorageModeShared already.
-  return false;
-#else   // FML_OS_IOS
-  // StorageMode::kHostVisible is MTLResourceStorageModeManaged.
-  return true;
-#endif  // FML_OS_IOS
-}
-
 std::shared_ptr<DeviceBuffer> Allocator::CreateBufferWithCopy(
     const uint8_t* buffer,
     size_t length) {
diff --git a/impeller/renderer/allocator.h b/impeller/renderer/allocator.h
index 10e1eb3..eb88a62 100644
--- a/impeller/renderer/allocator.h
+++ b/impeller/renderer/allocator.h
@@ -69,8 +69,6 @@
   std::shared_ptr<DeviceBuffer> CreateBufferWithCopy(
       const fml::Mapping& mapping);
 
-  static bool RequiresExplicitHostSynchronization(StorageMode mode);
-
  protected:
   Allocator();
 
diff --git a/impeller/renderer/backend/metal/allocator_mtl.h b/impeller/renderer/backend/metal/allocator_mtl.h
index 256ed3a..64f4c59 100644
--- a/impeller/renderer/backend/metal/allocator_mtl.h
+++ b/impeller/renderer/backend/metal/allocator_mtl.h
@@ -21,11 +21,10 @@
  private:
   friend class ContextMTL;
 
-  // In the prototype, we are going to be allocating resources directly with the
-  // MTLDevice APIs. But, in the future, this could be backed by named heaps
-  // with specific limits.
   id<MTLDevice> device_;
   std::string allocator_label_;
+  bool supports_memoryless_targets_ = false;
+  bool supports_uma_ = false;
   bool is_valid_ = false;
 
   AllocatorMTL(id<MTLDevice> device, std::string label);
diff --git a/impeller/renderer/backend/metal/allocator_mtl.mm b/impeller/renderer/backend/metal/allocator_mtl.mm
index 7b01205..4a5dafa 100644
--- a/impeller/renderer/backend/metal/allocator_mtl.mm
+++ b/impeller/renderer/backend/metal/allocator_mtl.mm
@@ -14,12 +14,51 @@
 
 namespace impeller {
 
+static bool DeviceSupportsMemorylessTargets(id<MTLDevice> device) {
+  // Refer to the "Memoryless render targets" feature in the table below:
+  // https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf
+  if (@available(ios 13.0, tvos 13.0, macos 10.15, *)) {
+    return [device supportsFamily:MTLGPUFamilyApple2];
+  } else {
+#if FML_OS_IOS
+    // This is perhaps redundant. But, just in case we somehow get into a case
+    // where Impeller runs on iOS versions less than 8.0 and/or without A8
+    // GPUs, we explicitly check feature set support.
+    return [device supportsFeatureSet:MTLFeatureSet_iOS_GPUFamily2_v1];
+#else
+    // MacOS devices with Apple GPUs are only available with macos 10.15 and
+    // above. So, if we are here, it is safe to assume that memory-less targets
+    // are not supported.
+    return false;
+#endif
+  }
+  FML_UNREACHABLE();
+}
+
+static bool DeviceHasUnifiedMemoryArchitecture(id<MTLDevice> device) {
+  if (@available(ios 13.0, tvos 13.0, macOS 10.15, *)) {
+    return [device hasUnifiedMemory];
+  } else {
+#if FML_OS_IOS
+    // iOS devices where the availability check can fail always have had UMA.
+    return true;
+#else
+    // Mac devices where the availability check can fail have never had UMA.
+    return false;
+#endif
+  }
+  FML_UNREACHABLE();
+}
+
 AllocatorMTL::AllocatorMTL(id<MTLDevice> device, std::string label)
     : device_(device), allocator_label_(std::move(label)) {
   if (!device_) {
     return;
   }
 
+  supports_memoryless_targets_ = DeviceSupportsMemorylessTargets(device_);
+  supports_uma_ = DeviceHasUnifiedMemoryArchitecture(device_);
+
   is_valid_ = true;
 }
 
@@ -29,56 +68,86 @@
   return is_valid_;
 }
 
-static MTLResourceOptions ToMTLResourceOptions(StorageMode type) {
+static MTLResourceOptions ToMTLResourceOptions(StorageMode type,
+                                               bool supports_memoryless_targets,
+                                               bool supports_uma) {
   switch (type) {
     case StorageMode::kHostVisible:
 #if FML_OS_IOS
       return MTLResourceStorageModeShared;
 #else
-      return MTLResourceStorageModeManaged;
+      if (supports_uma) {
+        return MTLResourceStorageModeShared;
+      } else {
+        return MTLResourceStorageModeManaged;
+      }
 #endif
     case StorageMode::kDevicePrivate:
       return MTLResourceStorageModePrivate;
     case StorageMode::kDeviceTransient:
-#if FML_OS_IOS
-      return MTLResourceStorageModeMemoryless;
-#else
-      return MTLResourceStorageModePrivate;
-#endif
+      if (supports_memoryless_targets) {
+        // Device may support but the OS has not been updated.
+        if (@available(macOS 11.0, *)) {
+          return MTLResourceStorageModeMemoryless;
+        } else {
+          return MTLResourceStorageModePrivate;
+        }
+      } else {
+        return MTLResourceStorageModePrivate;
+      }
+      FML_UNREACHABLE();
   }
-
-  return MTLResourceStorageModePrivate;
+  FML_UNREACHABLE();
 }
 
-static MTLStorageMode ToMTLStorageMode(StorageMode mode) {
+static MTLStorageMode ToMTLStorageMode(StorageMode mode,
+                                       bool supports_memoryless_targets,
+                                       bool supports_uma) {
   switch (mode) {
     case StorageMode::kHostVisible:
 #if FML_OS_IOS
       return MTLStorageModeShared;
 #else
-      return MTLStorageModeManaged;
+      if (supports_uma) {
+        return MTLStorageModeShared;
+      } else {
+        return MTLStorageModeManaged;
+      }
 #endif
     case StorageMode::kDevicePrivate:
       return MTLStorageModePrivate;
     case StorageMode::kDeviceTransient:
-#if FML_OS_IOS
-      return MTLStorageModeMemoryless;
-#else
-      return MTLStorageModePrivate;
-#endif
+      if (supports_memoryless_targets) {
+        // Device may support but the OS has not been updated.
+        if (@available(macOS 11.0, *)) {
+          return MTLStorageModeMemoryless;
+        } else {
+          return MTLStorageModePrivate;
+        }
+      } else {
+        return MTLStorageModePrivate;
+      }
+      FML_UNREACHABLE();
   }
-  return MTLStorageModeShared;
+  FML_UNREACHABLE();
 }
 
 std::shared_ptr<DeviceBuffer> AllocatorMTL::CreateBuffer(StorageMode mode,
                                                          size_t length) {
-  auto buffer = [device_ newBufferWithLength:length
-                                     options:ToMTLResourceOptions(mode)];
+  const auto resource_options =
+      ToMTLResourceOptions(mode, supports_memoryless_targets_, supports_uma_);
+  const auto storage_mode =
+      ToMTLStorageMode(mode, supports_memoryless_targets_, supports_uma_);
+
+  auto buffer = [device_ newBufferWithLength:length options:resource_options];
   if (!buffer) {
     return nullptr;
   }
-  return std::shared_ptr<DeviceBufferMTL>(
-      new DeviceBufferMTL(buffer, length, mode));
+  return std::shared_ptr<DeviceBufferMTL>(new DeviceBufferMTL(buffer,       //
+                                                              length,       //
+                                                              mode,         //
+                                                              storage_mode  //
+                                                              ));
 }
 
 std::shared_ptr<Texture> AllocatorMTL::CreateTexture(
@@ -95,7 +164,8 @@
     return nullptr;
   }
 
-  mtl_texture_desc.storageMode = ToMTLStorageMode(mode);
+  mtl_texture_desc.storageMode =
+      ToMTLStorageMode(mode, supports_memoryless_targets_, supports_uma_);
   auto texture = [device_ newTextureWithDescriptor:mtl_texture_desc];
   if (!texture) {
     return nullptr;
diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.h b/impeller/renderer/backend/metal/device_buffer_mtl.h
index e167dd8..1120779 100644
--- a/impeller/renderer/backend/metal/device_buffer_mtl.h
+++ b/impeller/renderer/backend/metal/device_buffer_mtl.h
@@ -27,8 +27,12 @@
   friend class AllocatorMTL;
 
   const id<MTLBuffer> buffer_;
+  const MTLStorageMode storage_mode_;
 
-  DeviceBufferMTL(id<MTLBuffer> buffer, size_t size, StorageMode mode);
+  DeviceBufferMTL(id<MTLBuffer> buffer,
+                  size_t size,
+                  StorageMode mode,
+                  MTLStorageMode storage_mode);
 
   // |DeviceBuffer|
   bool CopyHostBuffer(const uint8_t* source,
diff --git a/impeller/renderer/backend/metal/device_buffer_mtl.mm b/impeller/renderer/backend/metal/device_buffer_mtl.mm
index 32e7c55..e784bcf 100644
--- a/impeller/renderer/backend/metal/device_buffer_mtl.mm
+++ b/impeller/renderer/backend/metal/device_buffer_mtl.mm
@@ -13,8 +13,9 @@
 
 DeviceBufferMTL::DeviceBufferMTL(id<MTLBuffer> buffer,
                                  size_t size,
-                                 StorageMode mode)
-    : DeviceBuffer(size, mode), buffer_(buffer) {}
+                                 StorageMode mode,
+                                 MTLStorageMode storage_mode)
+    : DeviceBuffer(size, mode), buffer_(buffer), storage_mode_(storage_mode) {}
 
 DeviceBufferMTL::~DeviceBufferMTL() = default;
 
@@ -45,15 +46,11 @@
     ::memmove(dest + offset, source + source_range.offset, source_range.length);
   }
 
-// |RequiresExplicitHostSynchronization| always returns false on iOS. But the
-// compiler is mad that `didModifyRange:` appears in a TU meant for iOS. So,
+// MTLStorageModeManaged is never present on always returns false on iOS. But
+// the compiler is mad that `didModifyRange:` appears in a TU meant for iOS. So,
 // just compile it away.
-//
-// Making this call is never necessary on iOS because there is no
-// MTLResourceStorageModeManaged mode. Only the MTLStorageModeShared mode is
-// available.
 #if !FML_OS_IOS
-  if (Allocator::RequiresExplicitHostSynchronization(mode_)) {
+  if (storage_mode_ == MTLStorageModeManaged) {
     [buffer_ didModifyRange:NSMakeRange(offset, source_range.length)];
   }
 #endif
@@ -76,7 +73,6 @@
   [buffer_ addDebugMarker:@(label.c_str())
                     range:NSMakeRange(range.offset, range.length)];
   return true;
-  FML_UNREACHABLE();
 }
 
 }  // namespace impeller