[CP] 3.14: [Impeller] Fix mask blurs and the Gaussian blur coverage hint. (#45079) (#45193)

3.14 cherry-pick for https://github.com/flutter/engine/pull/43519.
diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc
index d6ed928..ccbb87d 100644
--- a/impeller/aiks/aiks_unittests.cc
+++ b/impeller/aiks/aiks_unittests.cc
@@ -3124,5 +3124,40 @@
   ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
 }
 
+TEST_P(AiksTest, ClippedBlurFilterRendersCorrectlyInteractive) {
+  auto callback = [&](AiksContext& renderer, RenderTarget& render_target) {
+    auto point = IMPELLER_PLAYGROUND_POINT(Point(400, 400), 20, Color::Green());
+
+    Canvas canvas;
+    canvas.Translate(point - Point(400, 400));
+    Paint paint;
+    paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
+        .style = FilterContents::BlurStyle::kNormal,
+        .sigma = Radius{120 * 3},
+    };
+    paint.color = Color::Red();
+    PathBuilder builder{};
+    builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
+    canvas.DrawPath(builder.TakePath(), paint);
+    return renderer.Render(canvas.EndRecordingAsPicture(), render_target);
+  };
+  ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
+TEST_P(AiksTest, ClippedBlurFilterRendersCorrectly) {
+  Canvas canvas;
+  canvas.Translate(Point(0, -400));
+  Paint paint;
+  paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{
+      .style = FilterContents::BlurStyle::kNormal,
+      .sigma = Radius{120 * 3},
+  };
+  paint.color = Color::Red();
+  PathBuilder builder{};
+  builder.AddRect(Rect::MakeLTRB(0, 0, 800, 800));
+  canvas.DrawPath(builder.TakePath(), paint);
+  ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
+}
+
 }  // namespace testing
 }  // namespace impeller
diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc
index 84cb754..50a8f2d 100644
--- a/impeller/aiks/canvas.cc
+++ b/impeller/aiks/canvas.cc
@@ -483,7 +483,7 @@
   Entity entity;
   entity.SetBlendMode(paint.blend_mode);
   entity.SetStencilDepth(GetStencilDepth());
-  entity.SetContents(paint.WithFilters(contents, false));
+  entity.SetContents(paint.WithFilters(contents));
   entity.SetTransformation(GetCurrentTransformation());
 
   GetCurrentPass().AddEntity(entity);
@@ -552,8 +552,14 @@
     color_text_contents->SetColorSourceContents(
         paint.color_source.GetContents(paint));
 
-    entity.SetContents(
-        paint.WithFilters(std::move(color_text_contents), false));
+    // TODO(bdero): This mask blur application is a hack. It will always wind up
+    //              doing a gaussian blur that affects the color source itself
+    //              instead of just the mask. The color filter text support
+    //              needs to be reworked in order to interact correctly with
+    //              mask filters.
+    //              https://github.com/flutter/flutter/issues/133297
+    entity.SetContents(paint.WithFilters(
+        paint.WithMaskBlur(std::move(color_text_contents), true)));
 
     GetCurrentPass().AddEntity(entity);
     return;
@@ -564,7 +570,14 @@
   entity.SetTransformation(GetCurrentTransformation() *
                            Matrix::MakeTranslation(position));
 
-  entity.SetContents(paint.WithFilters(std::move(text_contents), true));
+  // TODO(bdero): This mask blur application is a hack. It will always wind up
+  //              doing a gaussian blur that affects the color source itself
+  //              instead of just the mask. The color filter text support
+  //              needs to be reworked in order to interact correctly with
+  //              mask filters.
+  //              https://github.com/flutter/flutter/issues/133297
+  entity.SetContents(
+      paint.WithFilters(paint.WithMaskBlur(std::move(text_contents), true)));
 
   GetCurrentPass().AddEntity(entity);
 }
@@ -673,7 +686,7 @@
   entity.SetTransformation(GetCurrentTransformation());
   entity.SetStencilDepth(GetStencilDepth());
   entity.SetBlendMode(paint.blend_mode);
-  entity.SetContents(paint.WithFilters(contents, false));
+  entity.SetContents(paint.WithFilters(contents));
 
   GetCurrentPass().AddEntity(entity);
 }
diff --git a/impeller/aiks/paint.cc b/impeller/aiks/paint.cc
index 5cf342e..0775162 100644
--- a/impeller/aiks/paint.cc
+++ b/impeller/aiks/paint.cc
@@ -34,21 +34,32 @@
 std::shared_ptr<Contents> Paint::CreateContentsForGeometry(
     std::shared_ptr<Geometry> geometry) const {
   auto contents = color_source.GetContents(*this);
+
+  // Attempt to apply the color filter on the CPU first.
+  // Note: This is not just an optimization; some color sources rely on
+  //       CPU-applied color filters to behave properly.
+  bool needs_color_filter = !!color_filter;
+  if (color_filter &&
+      contents->ApplyColorFilter(color_filter->GetCPUColorFilterProc())) {
+    needs_color_filter = false;
+  }
+
   contents->SetGeometry(std::move(geometry));
   if (mask_blur_descriptor.has_value()) {
-    return mask_blur_descriptor->CreateMaskBlur(contents);
+    // If there's a mask blur and we need to apply the color filter on the GPU,
+    // we need to be careful to only apply the color filter to the source
+    // colors. CreateMaskBlur is able to handle this case.
+    return mask_blur_descriptor->CreateMaskBlur(
+        contents, needs_color_filter ? color_filter : nullptr);
   }
+
   return contents;
 }
 
 std::shared_ptr<Contents> Paint::WithFilters(
-    std::shared_ptr<Contents> input,
-    std::optional<bool> is_solid_color) const {
-  bool is_solid_color_val = is_solid_color.value_or(color_source.GetType() ==
-                                                    ColorSource::Type::kColor);
+    std::shared_ptr<Contents> input) const {
   input = WithColorFilter(input, /*absorb_opacity=*/true);
   input = WithInvertFilter(input);
-  input = WithMaskBlur(input, is_solid_color_val);
   input = WithImageFilter(input, Matrix(), /*is_subpass=*/false);
   return input;
 }
@@ -128,7 +139,16 @@
 }
 
 std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
-    std::shared_ptr<ColorSourceContents> color_source_contents) const {
+    std::shared_ptr<ColorSourceContents> color_source_contents,
+    const std::shared_ptr<ColorFilter>& color_filter) const {
+  // If it's a solid color and there is no color filter, then we can just get
+  // away with doing one Gaussian blur.
+  if (color_source_contents->IsSolidColor() && !color_filter) {
+    return FilterContents::MakeGaussianBlur(
+        FilterInput::Make(color_source_contents), sigma, sigma, style,
+        Entity::TileMode::kDecal, Matrix());
+  }
+
   /// 1. Create an opaque white mask of the original geometry.
 
   auto mask = std::make_shared<SolidColorContents>();
@@ -152,11 +172,20 @@
   color_source_contents->SetGeometry(
       Geometry::MakeRect(*expanded_local_bounds));
 
-  /// 4. Composite the color source and mask together.
+  std::shared_ptr<Contents> color_contents = color_source_contents;
+
+  /// 4. Apply the user set color filter on the GPU, if applicable.
+
+  if (color_filter) {
+    color_contents = color_filter->WrapWithGPUColorFilter(
+        FilterInput::Make(color_source_contents), true);
+  }
+
+  /// 5. Composite the color source with the blurred mask.
 
   return ColorFilterContents::MakeBlend(
-      BlendMode::kSourceIn, {FilterInput::Make(blurred_mask),
-                             FilterInput::Make(color_source_contents)});
+      BlendMode::kSourceIn,
+      {FilterInput::Make(blurred_mask), FilterInput::Make(color_contents)});
 }
 
 std::shared_ptr<FilterContents> Paint::MaskBlurDescriptor::CreateMaskBlur(
diff --git a/impeller/aiks/paint.h b/impeller/aiks/paint.h
index 7f3839b..c8a3434 100644
--- a/impeller/aiks/paint.h
+++ b/impeller/aiks/paint.h
@@ -42,7 +42,8 @@
     Sigma sigma;
 
     std::shared_ptr<FilterContents> CreateMaskBlur(
-        std::shared_ptr<ColorSourceContents> color_source_contents) const;
+        std::shared_ptr<ColorSourceContents> color_source_contents,
+        const std::shared_ptr<ColorFilter>& color_filter) const;
 
     std::shared_ptr<FilterContents> CreateMaskBlur(
         const FilterInput::Ref& input,
@@ -67,18 +68,10 @@
 
   /// @brief      Wrap this paint's configured filters to the given contents.
   /// @param[in]  input           The contents to wrap with paint's filters.
-  /// @param[in]  is_solid_color  Affects mask blurring behavior. If false, use
-  ///                             the image border for mask blurring. If true,
-  ///                             do a Gaussian blur to achieve the mask
-  ///                             blurring effect for arbitrary paths. If unset,
-  ///                             use the current paint configuration to infer
-  ///                             the result.
   /// @return     The filter-wrapped contents. If there are no filters that need
   ///             to be wrapped for the current paint configuration, the
   ///             original contents is returned.
-  std::shared_ptr<Contents> WithFilters(
-      std::shared_ptr<Contents> input,
-      std::optional<bool> is_solid_color = std::nullopt) const;
+  std::shared_ptr<Contents> WithFilters(std::shared_ptr<Contents> input) const;
 
   /// @brief      Wrap this paint's configured filters to the given contents of
   ///             subpass target.
@@ -101,10 +94,10 @@
   /// @brief   Whether this paint has a color filter that can apply opacity
   bool HasColorFilter() const;
 
- private:
   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;
diff --git a/impeller/entity/contents/color_source_contents.cc b/impeller/entity/contents/color_source_contents.cc
index 24b1956..8977b6d 100644
--- a/impeller/entity/contents/color_source_contents.cc
+++ b/impeller/entity/contents/color_source_contents.cc
@@ -38,6 +38,10 @@
   return inverse_matrix_;
 }
 
+bool ColorSourceContents::IsSolidColor() const {
+  return false;
+}
+
 std::optional<Rect> ColorSourceContents::GetCoverage(
     const Entity& entity) const {
   return geometry_->GetCoverage(entity.GetTransformation());
diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h
index 5ae6f15..53b3fae 100644
--- a/impeller/entity/contents/color_source_contents.h
+++ b/impeller/entity/contents/color_source_contents.h
@@ -90,6 +90,8 @@
   ///
   Scalar GetOpacityFactor() const;
 
+  virtual bool IsSolidColor() const;
+
   // |Contents|
   std::optional<Rect> GetCoverage(const Entity& entity) const override;
 
diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
index 0226507..b0b0397 100644
--- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
+++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
@@ -104,11 +104,19 @@
   // Limit the kernel size to 1000x1000 pixels, like Skia does.
   auto radius = std::min(Radius{blur_sigma_}.radius, 500.0f);
 
+  auto transform = entity.GetTransformation() * effect_transform.Basis();
+  auto transformed_blur_radius =
+      transform.TransformDirection(blur_direction_ * radius);
+
+  auto transformed_blur_radius_length = transformed_blur_radius.GetLength();
+
   // Input 0 snapshot.
 
   std::optional<Rect> expanded_coverage_hint;
   if (coverage_hint.has_value()) {
-    auto r = Size(radius, radius).Abs();
+    auto r =
+        Size(transformed_blur_radius_length, transformed_blur_radius_length)
+            .Abs();
     expanded_coverage_hint =
         is_first_pass ? Rect(coverage_hint.value().origin - r,
                              Size(coverage_hint.value().size + r * 2))
@@ -126,12 +134,6 @@
         entity.GetStencilDepth());  // No blur to render.
   }
 
-  auto transform = entity.GetTransformation() * effect_transform.Basis();
-  auto transformed_blur_radius =
-      transform.TransformDirection(blur_direction_ * radius);
-
-  auto transformed_blur_radius_length = transformed_blur_radius.GetLength();
-
   // If the radius length is < .5, the shader will take at most 1 sample,
   // resulting in no blur.
   if (transformed_blur_radius_length < .5) {
@@ -158,16 +160,6 @@
   pass_texture_rect.origin.x -= transformed_blur_radius_length;
   pass_texture_rect.size.width += transformed_blur_radius_length * 2;
 
-  // Crop the pass texture with the rotated coverage hint if one was given.
-  if (expanded_coverage_hint.has_value()) {
-    auto maybe_pass_texture_rect = pass_texture_rect.Intersection(
-        expanded_coverage_hint->TransformBounds(texture_rotate));
-    if (!maybe_pass_texture_rect.has_value()) {
-      return std::nullopt;
-    }
-    pass_texture_rect = *maybe_pass_texture_rect;
-  }
-
   // Source override snapshot.
 
   auto source = source_override_ ? source_override_ : inputs[0];
@@ -335,6 +327,8 @@
   SamplerDescriptor sampler_desc;
   sampler_desc.min_filter = MinMagFilter::kLinear;
   sampler_desc.mag_filter = MinMagFilter::kLinear;
+  sampler_desc.width_address_mode = SamplerAddressMode::kClampToEdge;
+  sampler_desc.width_address_mode = SamplerAddressMode::kClampToEdge;
 
   return Entity::FromSnapshot(
       Snapshot{.texture = out_texture,
diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc
index b90f3ff..d72b7e0 100644
--- a/impeller/entity/contents/solid_color_contents.cc
+++ b/impeller/entity/contents/solid_color_contents.cc
@@ -24,6 +24,10 @@
   return color_.WithAlpha(color_.alpha * GetOpacityFactor());
 }
 
+bool SolidColorContents::IsSolidColor() const {
+  return true;
+}
+
 bool SolidColorContents::IsOpaque() const {
   return GetColor().IsOpaque();
 }
diff --git a/impeller/entity/contents/solid_color_contents.h b/impeller/entity/contents/solid_color_contents.h
index 3d704b4..6b31e11 100644
--- a/impeller/entity/contents/solid_color_contents.h
+++ b/impeller/entity/contents/solid_color_contents.h
@@ -34,6 +34,9 @@
 
   Color GetColor() const;
 
+  // |ColorSourceContents|
+  bool IsSolidColor() const override;
+
   // |Contents|
   bool IsOpaque() const override;
 
diff --git a/impeller/golden_tests/golden_playground_test_mac.cc b/impeller/golden_tests/golden_playground_test_mac.cc
index 55209e7..86b0fff 100644
--- a/impeller/golden_tests/golden_playground_test_mac.cc
+++ b/impeller/golden_tests/golden_playground_test_mac.cc
@@ -27,6 +27,9 @@
     "impeller_Play_AiksTest_CanRenderRadialGradientManyColors_Vulkan",
     "impeller_Play_AiksTest_CanRenderBackdropBlurInteractive_Metal",
     "impeller_Play_AiksTest_CanRenderBackdropBlurInteractive_Vulkan",
+    "impeller_Play_AiksTest_ClippedBlurFilterRendersCorrectlyInteractive_Metal",
+    "impeller_Play_AiksTest_ClippedBlurFilterRendersCorrectlyInteractive_"
+    "Vulkan",
     "impeller_Play_AiksTest_TextFrameSubpixelAlignment_Metal",
     "impeller_Play_AiksTest_TextFrameSubpixelAlignment_Vulkan",
     "impeller_Play_AiksTest_ColorWheel_Metal",