[Impeller] Incorporate filters in subpass coverage. (#45778)

I adjusted the size of the matrix filtered circle in both SaveLayer
Matrix filter test variants (post-filter and backdrop filter) to move
the transformed circle outside the coverage area that would be computed
if subpass filters aren't being accounted for. Further, we need to avoid
intersecting with the pass coverage hint when the transformation being
applied by the paint's filter graph causes the Entity's space basis to
deviate from the parent pass basis. We're still able to apply it for
simple translations, though.
diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc
index 6af0cbc..06f551b 100644
--- a/impeller/aiks/aiks_unittests.cc
+++ b/impeller/aiks/aiks_unittests.cc
@@ -3220,7 +3220,7 @@
     canvas.SaveLayer({.image_filter = ImageFilter::MakeMatrix(
                           Matrix::MakeTranslation(Vector2(1, 1) *
                                                   (200 + 100 * k1OverSqrt2)) *
-                              Matrix::MakeScale(Vector2(1, 1) * 0.2) *
+                              Matrix::MakeScale(Vector2(1, 1) * 0.5) *
                               Matrix::MakeTranslation(Vector2(-200, -200)),
                           SamplerDescriptor{})},
                      std::nullopt);
@@ -3248,7 +3248,7 @@
         {}, std::nullopt,
         ImageFilter::MakeMatrix(
             Matrix::MakeTranslation(Vector2(1, 1) * (100 + 100 * k1OverSqrt2)) *
-                Matrix::MakeScale(Vector2(1, 1) * 0.2) *
+                Matrix::MakeScale(Vector2(1, 1) * 0.5) *
                 Matrix::MakeTranslation(Vector2(-100, -100)),
             SamplerDescriptor{}));
     canvas.Restore();
diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc
index a7bb6d0..5c2b808 100644
--- a/impeller/aiks/paint.cc
+++ b/impeller/aiks/paint.cc
@@ -60,14 +60,21 @@
     std::shared_ptr<Contents> input) const {
   input = WithColorFilter(input, /*absorb_opacity=*/true);
   input = WithInvertFilter(input);
-  input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);
+  auto image_filter = WithImageFilter(input, Matrix(), /*is_subpass=*/false);
+  if (image_filter) {
+    input = image_filter;
+  }
   return input;
 }
 
 std::shared_ptr<Contents> Paint::WithFiltersForSubpassTarget(
     std::shared_ptr<Contents> input,
     const Matrix& effect_transform) const {
-  input = WithImageFilter(input, effect_transform, /*is_subpass=*/true);
+  auto image_filter =
+      WithImageFilter(input, effect_transform, /*is_subpass=*/true);
+  if (image_filter) {
+    input = image_filter;
+  }
   input = WithColorFilter(input, /*absorb_opacity=*/true);
   return input;
 }
@@ -81,12 +88,12 @@
   return input;
 }
 
-std::shared_ptr<Contents> Paint::WithImageFilter(
-    std::shared_ptr<Contents> input,
+std::shared_ptr<FilterContents> Paint::WithImageFilter(
+    const FilterInput::Variant& input,
     const Matrix& effect_transform,
     bool is_subpass) const {
   if (!image_filter) {
-    return input;
+    return nullptr;
   }
   auto filter = image_filter->WrapInput(FilterInput::Make(input));
   filter->SetIsForSubpass(is_subpass);
diff --git a/impeller/aiks/paint.h b/impeller/aiks/paint.h
index d77a298..384b4bd 100644
--- a/impeller/aiks/paint.h
+++ b/impeller/aiks/paint.h
@@ -98,11 +98,12 @@
   std::shared_ptr<Contents> WithMaskBlur(std::shared_ptr<Contents> input,
                                          bool is_solid_color) const;
 
- private:
-  std::shared_ptr<Contents> WithImageFilter(std::shared_ptr<Contents> input,
-                                            const Matrix& effect_transform,
-                                            bool is_subpass) const;
+  std::shared_ptr<FilterContents> WithImageFilter(
+      const FilterInput::Variant& input,
+      const Matrix& effect_transform,
+      bool is_subpass) const;
 
+ private:
   std::shared_ptr<Contents> WithColorFilter(std::shared_ptr<Contents> input,
                                             bool absorb_opacity = false) const;
 
diff --git a/impeller/aiks/paint_pass_delegate.cc b/impeller/aiks/paint_pass_delegate.cc
index 382ca34..6a72338 100644
--- a/impeller/aiks/paint_pass_delegate.cc
+++ b/impeller/aiks/paint_pass_delegate.cc
@@ -47,6 +47,13 @@
                                             effect_transform);
 }
 
+// |EntityPassDelgate|
+std::shared_ptr<FilterContents> PaintPassDelegate::WithImageFilter(
+    const FilterInput::Variant& input,
+    const Matrix& effect_transform) const {
+  return paint_.WithImageFilter(input, effect_transform, true);
+}
+
 /// OpacityPeepholePassDelegate
 /// ----------------------------------------------
 
@@ -140,4 +147,11 @@
                                             effect_transform);
 }
 
+// |EntityPassDelgate|
+std::shared_ptr<FilterContents> OpacityPeepholePassDelegate::WithImageFilter(
+    const FilterInput::Variant& input,
+    const Matrix& effect_transform) const {
+  return paint_.WithImageFilter(input, effect_transform, true);
+}
+
 }  // namespace impeller
diff --git a/impeller/aiks/paint_pass_delegate.h b/impeller/aiks/paint_pass_delegate.h
index 967b59e..17e87b2 100644
--- a/impeller/aiks/paint_pass_delegate.h
+++ b/impeller/aiks/paint_pass_delegate.h
@@ -32,6 +32,11 @@
       std::shared_ptr<Texture> target,
       const Matrix& effect_transform) override;
 
+  // |EntityPassDelgate|
+  std::shared_ptr<FilterContents> WithImageFilter(
+      const FilterInput::Variant& input,
+      const Matrix& effect_transform) const override;
+
  private:
   const Paint paint_;
 
@@ -61,6 +66,11 @@
       std::shared_ptr<Texture> target,
       const Matrix& effect_transform) override;
 
+  // |EntityPassDelgate|
+  std::shared_ptr<FilterContents> WithImageFilter(
+      const FilterInput::Variant& input,
+      const Matrix& effect_transform) const override;
+
  private:
   const Paint paint_;
 
diff --git a/impeller/entity/contents/contents.cc b/impeller/entity/contents/contents.cc
index 85e70e5..a181e22 100644
--- a/impeller/entity/contents/contents.cc
+++ b/impeller/entity/contents/contents.cc
@@ -123,6 +123,10 @@
   return {};
 }
 
+const FilterContents* Contents::AsFilter() const {
+  return nullptr;
+}
+
 bool Contents::ApplyColorFilter(
     const Contents::ColorFilterProc& color_filter_proc) {
   return false;
diff --git a/impeller/entity/contents/contents.h b/impeller/entity/contents/contents.h
index 245b8f2..b9dec5d 100644
--- a/impeller/entity/contents/contents.h
+++ b/impeller/entity/contents/contents.h
@@ -23,6 +23,7 @@
 class Entity;
 class Surface;
 class RenderPass;
+class FilterContents;
 
 ContentContextOptions OptionsFromPass(const RenderPass& pass);
 
@@ -156,6 +157,12 @@
                                                  ISize target_size) const;
 
   //----------------------------------------------------------------------------
+  /// @brief Cast to a filter. Returns `nullptr` if this Contents is not a
+  ///        filter.
+  ///
+  virtual const FilterContents* AsFilter() const;
+
+  //----------------------------------------------------------------------------
   /// @brief      If possible, applies a color filter to this contents inputs on
   ///             the CPU.
   ///
diff --git a/impeller/entity/contents/filters/filter_contents.cc b/impeller/entity/contents/filters/filter_contents.cc
index ae48cec..3d4fa96 100644
--- a/impeller/entity/contents/filters/filter_contents.cc
+++ b/impeller/entity/contents/filters/filter_contents.cc
@@ -259,6 +259,10 @@
   return std::nullopt;
 }
 
+const FilterContents* FilterContents::AsFilter() const {
+  return this;
+}
+
 Matrix FilterContents::GetLocalTransform(const Matrix& parent_transform) const {
   return Matrix();
 }
@@ -266,6 +270,14 @@
 Matrix FilterContents::GetTransform(const Matrix& parent_transform) const {
   return parent_transform * GetLocalTransform(parent_transform);
 }
+bool FilterContents::IsTranslationOnly() const {
+  for (auto& input : inputs_) {
+    if (!input->IsTranslationOnly()) {
+      return false;
+    }
+  }
+  return true;
+}
 
 bool FilterContents::IsLeaf() const {
   for (auto& input : inputs_) {
diff --git a/impeller/entity/contents/filters/filter_contents.h b/impeller/entity/contents/filters/filter_contents.h
index e5e7604..c143934 100644
--- a/impeller/entity/contents/filters/filter_contents.h
+++ b/impeller/entity/contents/filters/filter_contents.h
@@ -125,10 +125,23 @@
       bool msaa_enabled = true,
       const std::string& label = "Filter Snapshot") const override;
 
+  // |Contents|
+  const FilterContents* AsFilter() const override;
+
   virtual Matrix GetLocalTransform(const Matrix& parent_transform) const;
 
   Matrix GetTransform(const Matrix& parent_transform) const;
 
+  /// @brief  Returns true if this filter graph doesn't perform any basis
+  ///         transformations to the filtered content. For example: Rotating,
+  ///         scaling, and skewing are all basis transformations, but
+  ///         translating is not.
+  ///
+  ///         This is useful for determining whether a filtered object's space
+  ///         is compatible enough with the parent pass space to perform certain
+  ///         subpass clipping optimizations.
+  virtual bool IsTranslationOnly() const;
+
   /// @brief  Returns `true` if this filter does not have any `FilterInput`
   ///         children.
   bool IsLeaf() const;
diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc
index e1af31f..78a1cec 100644
--- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc
+++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.cc
@@ -58,6 +58,10 @@
   filter_->PopulateGlyphAtlas(lazy_glyph_atlas, scale);
 }
 
+bool FilterContentsFilterInput::IsTranslationOnly() const {
+  return filter_->IsTranslationOnly();
+}
+
 bool FilterContentsFilterInput::IsLeaf() const {
   return false;
 }
diff --git a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
index 7aaaa3a..45d4499 100644
--- a/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
+++ b/impeller/entity/contents/filters/inputs/filter_contents_filter_input.h
@@ -37,6 +37,9 @@
       Scalar scale) override;
 
   // |FilterInput|
+  bool IsTranslationOnly() const override;
+
+  // |FilterInput|
   bool IsLeaf() const override;
 
   // |FilterInput|
diff --git a/impeller/entity/contents/filters/inputs/filter_input.cc b/impeller/entity/contents/filters/inputs/filter_input.cc
index 1cb4744..e438212 100644
--- a/impeller/entity/contents/filters/inputs/filter_input.cc
+++ b/impeller/entity/contents/filters/inputs/filter_input.cc
@@ -76,6 +76,10 @@
 
 FilterInput::~FilterInput() = default;
 
+bool FilterInput::IsTranslationOnly() const {
+  return true;
+}
+
 bool FilterInput::IsLeaf() const {
   return true;
 }
diff --git a/impeller/entity/contents/filters/inputs/filter_input.h b/impeller/entity/contents/filters/inputs/filter_input.h
index 598ca1b..43db41e 100644
--- a/impeller/entity/contents/filters/inputs/filter_input.h
+++ b/impeller/entity/contents/filters/inputs/filter_input.h
@@ -69,6 +69,9 @@
       const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
       Scalar scale);
 
+  /// @see  `FilterContents::HasBasisTransformations`
+  virtual bool IsTranslationOnly() const;
+
   /// @brief  Returns `true` unless this input is a `FilterInput`, which may
   ///         take other inputs.
   virtual bool IsLeaf() const;
diff --git a/impeller/entity/contents/filters/matrix_filter_contents.cc b/impeller/entity/contents/filters/matrix_filter_contents.cc
index 5483b4d..c767ffe 100644
--- a/impeller/entity/contents/filters/matrix_filter_contents.cc
+++ b/impeller/entity/contents/filters/matrix_filter_contents.cc
@@ -19,6 +19,10 @@
   FilterContents::SetIsForSubpass(is_subpass);
 }
 
+bool MatrixFilterContents::IsTranslationOnly() const {
+  return matrix_.Basis().IsIdentity() && FilterContents::IsTranslationOnly();
+}
+
 void MatrixFilterContents::SetSamplerDescriptor(SamplerDescriptor desc) {
   sampler_descriptor_ = std::move(desc);
 }
diff --git a/impeller/entity/contents/filters/matrix_filter_contents.h b/impeller/entity/contents/filters/matrix_filter_contents.h
index a2e4c5f..ea22130 100644
--- a/impeller/entity/contents/filters/matrix_filter_contents.h
+++ b/impeller/entity/contents/filters/matrix_filter_contents.h
@@ -20,6 +20,9 @@
   // |FilterContents|
   void SetIsForSubpass(bool is_for_subpass) override;
 
+  // |FilterContents|
+  bool IsTranslationOnly() const override;
+
   void SetSamplerDescriptor(SamplerDescriptor desc);
 
   // |FilterContents|
diff --git a/impeller/entity/entity_pass.cc b/impeller/entity/entity_pass.cc
index 29a737e..b93d997 100644
--- a/impeller/entity/entity_pass.cc
+++ b/impeller/entity/entity_pass.cc
@@ -110,12 +110,39 @@
     if (auto entity = std::get_if<Entity>(&element)) {
       coverage = entity->GetCoverage();
 
+      // When the coverage limit is std::nullopt, that means there is no limit,
+      // as opposed to empty coverage.
       if (coverage.has_value() && coverage_limit.has_value()) {
+        const auto* filter = entity->GetContents()->AsFilter();
+        if (!filter || filter->IsTranslationOnly()) {
+          coverage = coverage->Intersection(coverage_limit.value());
+        }
+      }
+    } else if (auto subpass_ptr =
+                   std::get_if<std::unique_ptr<EntityPass>>(&element)) {
+      auto& subpass = *subpass_ptr->get();
+
+      std::optional<Rect> unfiltered_coverage =
+          GetSubpassCoverage(subpass, std::nullopt);
+      if (!unfiltered_coverage.has_value()) {
+        continue;
+      }
+
+      std::shared_ptr<FilterContents> image_filter =
+          subpass.delegate_->WithImageFilter(*unfiltered_coverage,
+                                             subpass.xformation_);
+      if (image_filter) {
+        Entity subpass_entity;
+        subpass_entity.SetTransformation(subpass.xformation_);
+        coverage = image_filter->GetCoverage(subpass_entity);
+      } else {
+        coverage = unfiltered_coverage;
+      }
+
+      if (coverage.has_value() && coverage_limit.has_value() &&
+          (!image_filter || image_filter->IsTranslationOnly())) {
         coverage = coverage->Intersection(coverage_limit.value());
       }
-    } else if (auto subpass =
-                   std::get_if<std::unique_ptr<EntityPass>>(&element)) {
-      coverage = GetSubpassCoverage(*subpass->get(), coverage_limit);
     } else {
       FML_UNREACHABLE();
     }
@@ -135,6 +162,16 @@
 std::optional<Rect> EntityPass::GetSubpassCoverage(
     const EntityPass& subpass,
     std::optional<Rect> coverage_limit) const {
+  std::shared_ptr<FilterContents> image_filter =
+      subpass.delegate_->WithImageFilter(Rect(), subpass.xformation_);
+
+  // If the filter graph transforms the basis of the subpass, then its space
+  // has deviated too much from the parent pass to safely intersect with the
+  // pass coverage limit.
+  coverage_limit =
+      (image_filter && image_filter->IsTranslationOnly() ? std::nullopt
+                                                         : coverage_limit);
+
   auto entities_coverage = subpass.GetElementsCoverage(coverage_limit);
   // The entities don't cover anything. There is nothing to do.
   if (!entities_coverage.has_value()) {
diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h
index f9b11d1..f00c9d3 100644
--- a/impeller/entity/entity_pass.h
+++ b/impeller/entity/entity_pass.h
@@ -141,6 +141,9 @@
 
   void SetEnableOffscreenCheckerboard(bool enabled);
 
+  //----------------------------------------------------------------------------
+  /// @brief  Get the coverage of an unfiltered subpass.
+  ///
   std::optional<Rect> GetSubpassCoverage(
       const EntityPass& subpass,
       std::optional<Rect> coverage_limit) const;
diff --git a/impeller/entity/entity_pass_delegate.cc b/impeller/entity/entity_pass_delegate.cc
index b996995..e6a1021 100644
--- a/impeller/entity/entity_pass_delegate.cc
+++ b/impeller/entity/entity_pass_delegate.cc
@@ -34,6 +34,13 @@
     FML_UNREACHABLE();
   }
 
+  // |EntityPassDelgate|
+  std::shared_ptr<FilterContents> WithImageFilter(
+      const FilterInput::Variant& input,
+      const Matrix& effect_transform) const override {
+    return nullptr;
+  }
+
  private:
   FML_DISALLOW_COPY_AND_ASSIGN(DefaultEntityPassDelegate);
 };
diff --git a/impeller/entity/entity_pass_delegate.h b/impeller/entity/entity_pass_delegate.h
index fbc7f97..4675236 100644
--- a/impeller/entity/entity_pass_delegate.h
+++ b/impeller/entity/entity_pass_delegate.h
@@ -9,6 +9,8 @@
 #include "flutter/fml/macros.h"
 #include "impeller/core/texture.h"
 #include "impeller/entity/contents/contents.h"
+#include "impeller/entity/contents/filters/filter_contents.h"
+#include "impeller/entity/contents/filters/inputs/filter_input.h"
 
 namespace impeller {
 
@@ -32,6 +34,10 @@
       std::shared_ptr<Texture> target,
       const Matrix& effect_transform) = 0;
 
+  virtual std::shared_ptr<FilterContents> WithImageFilter(
+      const FilterInput::Variant& input,
+      const Matrix& effect_transform) const = 0;
+
  private:
   FML_DISALLOW_COPY_AND_ASSIGN(EntityPassDelegate);
 };
diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc
index b5d36ca..3ff5548 100644
--- a/impeller/entity/entity_unittests.cc
+++ b/impeller/entity/entity_unittests.cc
@@ -91,6 +91,13 @@
     return nullptr;
   }
 
+  // |EntityPassDelegate|
+  std::shared_ptr<FilterContents> WithImageFilter(
+      const FilterInput::Variant& input,
+      const Matrix& effect_transform) const override {
+    return nullptr;
+  }
+
  private:
   const std::optional<Rect> coverage_;
   const bool collapse_;