[Impeller] made sure to scale the blur radius by the effect transform (#49645)

fixes https://github.com/flutter/flutter/issues/141204

## screenshot after pr

<img width="1023" alt="Screenshot 2024-01-09 at 2 31 10 PM"
src="https://github.com/flutter/engine/assets/30870216/46fc2a63-67dc-4723-b394-2b2a6958f7ba">


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide] and the [C++,
Objective-C, Java style guides].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I added new tests to check the change I am making or feature I am
adding, or the PR is [test-exempt]. See [testing the engine] for
instructions on writing and running engine tests.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I signed the [CLA].
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#overview
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
[test-exempt]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
[C++, Objective-C, Java style guides]:
https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
[testing the engine]:
https://github.com/flutter/flutter/wiki/Testing-the-engine
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes
[Discord]: https://github.com/flutter/flutter/wiki/Chat
diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
index e23f95c..0710dc6 100644
--- a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
+++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
@@ -235,14 +235,14 @@
     return {};
   }
 
-  Vector2 scaled_sigma = {ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)};
-  Vector2 blur_radius = {CalculateBlurRadius(scaled_sigma.x),
-                         CalculateBlurRadius(scaled_sigma.y)};
-  Vector3 blur_radii =
-      (inputs[0]->GetTransform(entity).Basis() * effect_transform.Basis() *
-       Vector3{blur_radius.x, blur_radius.y, 0.0})
-          .Abs();
-  return input_coverage.value().Expand(Point(blur_radii.x, blur_radii.y));
+  Vector2 scaled_sigma = (effect_transform.Basis() *
+                          Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
+                             .Abs();
+  Vector2 blur_radius = Vector2(CalculateBlurRadius(scaled_sigma.x),
+                                CalculateBlurRadius(scaled_sigma.y));
+  Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
+  Vector2 local_padding = (entity.GetTransform().Basis() * padding).Abs();
+  return input_coverage.value().Expand(Point(local_padding.x, local_padding.y));
 }
 
 std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
@@ -256,13 +256,13 @@
     return std::nullopt;
   }
 
-  Vector2 scaled_sigma = {ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)};
-  Vector2 blur_radius = {CalculateBlurRadius(scaled_sigma.x),
-                         CalculateBlurRadius(scaled_sigma.y)};
+  Vector2 scaled_sigma = (effect_transform.Basis() *
+                          Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
+                             .Abs();
+  Vector2 blur_radius = Vector2(CalculateBlurRadius(scaled_sigma.x),
+                                CalculateBlurRadius(scaled_sigma.y));
   Vector2 padding(ceil(blur_radius.x), ceil(blur_radius.y));
-  Vector2 local_padding =
-      (entity.GetTransform().Basis() * effect_transform.Basis() * padding)
-          .Abs();
+  Vector2 local_padding = (entity.GetTransform().Basis() * padding).Abs();
 
   // Apply as much of the desired padding as possible from the source. This may
   // be ignored so must be accounted for in the downsample pass by adding a
@@ -437,7 +437,8 @@
   KernelPipeline::FragmentShader::KernelSamples result;
   result.sample_count =
       ((2 * parameters.blur_radius) / parameters.step_size) + 1;
-  FML_CHECK(result.sample_count < 24);
+  // 32 comes from kernel.glsl.
+  FML_CHECK(result.sample_count < 32);
 
   // Chop off the last samples if the radius >= 3 where they account for < 1.56%
   // of the result.
diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc
index 38d52d3..ba30534 100644
--- a/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc
+++ b/impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc
@@ -19,29 +19,43 @@
 // Use newtonian method to give the closest answer to target where
 // f(x) is less than the target. We do this because the value is `ceil`'d to
 // grab fractional pixels.
-float LowerBoundNewtonianMethod(const std::function<float(float)>& func,
-                                float target,
-                                float guess,
-                                float tolerance) {
-  const float delta = 1e-6;
-  float x = guess;
-  float fx;
+fml::StatusOr<float> LowerBoundNewtonianMethod(
+    const std::function<float(float)>& func,
+    float target,
+    float guess,
+    float tolerance) {
+  const double delta = 1e-6;
+  double x = guess;
+  double fx;
+  static const int kMaxIterations = 1000;
+  int count = 0;
 
   do {
     fx = func(x) - target;
-    float derivative = (func(x + delta) - func(x)) / delta;
+    double derivative = (func(x + delta) - func(x)) / delta;
     x = x - fx / derivative;
-
+    if (++count > kMaxIterations) {
+      return fml::Status(fml::StatusCode::kDeadlineExceeded,
+                         "Did not converge on answer.");
+    }
   } while (std::abs(fx) > tolerance ||
            fx < 0.0);  // fx < 0.0 makes this lower bound.
 
   return x;
 }
 
-Scalar CalculateSigmaForBlurRadius(Scalar radius) {
-  auto f = [](Scalar x) -> Scalar {
-    return GaussianBlurFilterContents::CalculateBlurRadius(
-        GaussianBlurFilterContents::ScaleSigma(x));
+fml::StatusOr<Scalar> CalculateSigmaForBlurRadius(
+    Scalar radius,
+    const Matrix& effect_transform) {
+  auto f = [effect_transform](Scalar x) -> Scalar {
+    Vector2 scaled_sigma = (effect_transform.Basis() *
+                            Vector2(GaussianBlurFilterContents::ScaleSigma(x),
+                                    GaussianBlurFilterContents::ScaleSigma(x)))
+                               .Abs();
+    Vector2 blur_radius = Vector2(
+        GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.x),
+        GaussianBlurFilterContents::CalculateBlurRadius(scaled_sigma.y));
+    return std::max(blur_radius.x, blur_radius.y);
   };
   // The newtonian method is used here since inverting the function is
   // non-trivial because of conditional logic and would be fragile to changes.
@@ -90,9 +104,11 @@
 }
 
 TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) {
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
-  GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1,
-                                      /*sigma_y=*/sigma_radius_1,
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
+  ASSERT_TRUE(sigma_radius_1.ok());
+  GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1.value(),
+                                      /*sigma_y=*/sigma_radius_1.value(),
                                       Entity::TileMode::kDecal);
   FilterInput::Vector inputs = {
       FilterInput::Make(Rect::MakeLTRB(100, 100, 200, 200))};
@@ -111,9 +127,11 @@
       .format = PixelFormat::kB8G8R8A8UNormInt,
       .size = ISize(100, 100),
   };
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
-  GaussianBlurFilterContents contents(/*sigma_X=*/sigma_radius_1,
-                                      /*sigma_y=*/sigma_radius_1,
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
+  ASSERT_TRUE(sigma_radius_1.ok());
+  GaussianBlurFilterContents contents(/*sigma_X=*/sigma_radius_1.value(),
+                                      /*sigma_y=*/sigma_radius_1.value(),
                                       Entity::TileMode::kDecal);
   std::shared_ptr<Texture> texture =
       GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture(
@@ -135,9 +153,12 @@
       .format = PixelFormat::kB8G8R8A8UNormInt,
       .size = ISize(100, 100),
   };
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
-  GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1,
-                                      /*sigma_y=*/sigma_radius_1,
+  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, effect_transform);
+  ASSERT_TRUE(sigma_radius_1.ok());
+  GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1.value(),
+                                      /*sigma_y=*/sigma_radius_1.value(),
                                       Entity::TileMode::kDecal);
   std::shared_ptr<Texture> texture =
       GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture(
@@ -145,8 +166,24 @@
   FilterInput::Vector inputs = {FilterInput::Make(texture)};
   Entity entity;
   entity.SetTransform(Matrix::MakeTranslation({100, 100, 0}));
-  std::optional<Rect> coverage = contents.GetFilterCoverage(
-      inputs, entity, /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}));
+  std::optional<Rect> coverage =
+      contents.GetFilterCoverage(inputs, entity, effect_transform);
+  EXPECT_TRUE(coverage.has_value());
+  if (coverage.has_value()) {
+    EXPECT_RECT_NEAR(coverage.value(),
+                     Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1));
+  }
+}
+
+TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
+  ASSERT_TRUE(sigma_radius_1.ok());
+  auto contents = std::make_unique<GaussianBlurFilterContents>(
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
+  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
+      /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}),
+      /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
   EXPECT_TRUE(coverage.has_value());
   if (coverage.has_value()) {
     EXPECT_RECT_NEAR(coverage.value(),
@@ -154,16 +191,6 @@
   }
 }
 
-TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) {
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
-  auto contents = std::make_unique<GaussianBlurFilterContents>(
-      sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
-  std::optional<Rect> coverage = contents->GetFilterSourceCoverage(
-      /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0}),
-      /*output_limit=*/Rect::MakeLTRB(100, 100, 200, 200));
-  ASSERT_EQ(coverage, Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2));
-}
-
 TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) {
   EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(1.0f), 1);
   EXPECT_EQ(GaussianBlurFilterContents::CalculateScale(2.0f), 1);
@@ -180,9 +207,11 @@
       .size = ISize(100, 100),
   };
   std::shared_ptr<Texture> texture = MakeTexture(desc);
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
+  ASSERT_TRUE(sigma_radius_1.ok());
   auto contents = std::make_unique<GaussianBlurFilterContents>(
-      sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
   contents->SetInputs({FilterInput::Make(texture)});
   std::shared_ptr<ContentContext> renderer = GetContentContext();
 
@@ -213,9 +242,11 @@
       .size = ISize(100, 100),
   };
   std::shared_ptr<Texture> texture = MakeTexture(desc);
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
+  ASSERT_TRUE(sigma_radius_1.ok());
   auto contents = std::make_unique<GaussianBlurFilterContents>(
-      sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
   contents->SetInputs({FilterInput::Make(texture)});
   std::shared_ptr<ContentContext> renderer = GetContentContext();
 
@@ -248,9 +279,10 @@
       .size = ISize(400, 300),
   };
   std::shared_ptr<Texture> texture = MakeTexture(desc);
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
   auto contents = std::make_unique<GaussianBlurFilterContents>(
-      sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
   contents->SetInputs({FilterInput::Make(texture)});
   std::shared_ptr<ContentContext> renderer = GetContentContext();
 
@@ -308,9 +340,10 @@
   texture_contents->SetDestinationRect(Rect::MakeXYWH(
       50, 40, texture->GetSize().width, texture->GetSize().height));
 
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
   auto contents = std::make_unique<GaussianBlurFilterContents>(
-      sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
   contents->SetInputs({FilterInput::Make(texture_contents)});
   std::shared_ptr<ContentContext> renderer = GetContentContext();
 
@@ -347,9 +380,10 @@
   texture_contents->SetDestinationRect(Rect::MakeXYWH(
       50, 40, texture->GetSize().width, texture->GetSize().height));
 
-  Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, Matrix());
   auto contents = std::make_unique<GaussianBlurFilterContents>(
-      sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
   contents->SetInputs({FilterInput::Make(texture_contents)});
   std::shared_ptr<ContentContext> renderer = GetContentContext();
 
@@ -372,13 +406,56 @@
   }
 }
 
+TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithEffectTransform) {
+  TextureDescriptor desc = {
+      .storage_mode = StorageMode::kDevicePrivate,
+      .format = PixelFormat::kB8G8R8A8UNormInt,
+      .size = ISize(100, 100),
+  };
+
+  Matrix effect_transform = Matrix::MakeScale({2.0, 2.0, 1.0});
+  std::shared_ptr<Texture> texture = MakeTexture(desc);
+  auto texture_contents = std::make_shared<TextureContents>();
+  texture_contents->SetSourceRect(Rect::MakeSize(texture->GetSize()));
+  texture_contents->SetTexture(texture);
+  texture_contents->SetDestinationRect(Rect::MakeXYWH(
+      50, 40, texture->GetSize().width, texture->GetSize().height));
+
+  fml::StatusOr<Scalar> sigma_radius_1 =
+      CalculateSigmaForBlurRadius(1.0, effect_transform);
+  ASSERT_TRUE(sigma_radius_1.ok());
+  auto contents = std::make_unique<GaussianBlurFilterContents>(
+      sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal);
+  contents->SetInputs({FilterInput::Make(texture_contents)});
+  contents->SetEffectTransform(effect_transform);
+  std::shared_ptr<ContentContext> renderer = GetContentContext();
+
+  Entity entity;
+  std::optional<Entity> result =
+      contents->GetEntity(*renderer, entity, /*coverage_hint=*/{});
+  EXPECT_TRUE(result.has_value());
+  if (result.has_value()) {
+    EXPECT_EQ(result.value().GetBlendMode(), BlendMode::kSourceOver);
+    std::optional<Rect> result_coverage = result.value().GetCoverage();
+    std::optional<Rect> contents_coverage = contents->GetCoverage(entity);
+    EXPECT_TRUE(result_coverage.has_value());
+    EXPECT_TRUE(contents_coverage.has_value());
+    if (result_coverage.has_value() && contents_coverage.has_value()) {
+      EXPECT_TRUE(RectNear(result_coverage.value(), contents_coverage.value()));
+      EXPECT_TRUE(RectNear(contents_coverage.value(),
+                           Rect::MakeXYWH(49.f, 39.f, 102.f, 102.f)));
+    }
+  }
+}
+
 TEST(GaussianBlurFilterContentsTest, CalculateSigmaForBlurRadius) {
   Scalar sigma = 1.0;
   Scalar radius = GaussianBlurFilterContents::CalculateBlurRadius(
       GaussianBlurFilterContents::ScaleSigma(sigma));
-  Scalar derived_sigma = CalculateSigmaForBlurRadius(radius);
-
-  EXPECT_NEAR(sigma, derived_sigma, 0.01f);
+  fml::StatusOr<Scalar> derived_sigma =
+      CalculateSigmaForBlurRadius(radius, Matrix());
+  ASSERT_TRUE(derived_sigma.ok());
+  EXPECT_NEAR(sigma, derived_sigma.value(), 0.01f);
 }
 
 TEST(GaussianBlurFilterContentsTest, Coefficients) {
diff --git a/impeller/entity/shaders/gaussian_blur/kernel.glsl b/impeller/entity/shaders/gaussian_blur/kernel.glsl
index 57f0de0..d8a544b 100644
--- a/impeller/entity/shaders/gaussian_blur/kernel.glsl
+++ b/impeller/entity/shaders/gaussian_blur/kernel.glsl
@@ -16,7 +16,7 @@
 
 uniform KernelSamples {
   int sample_count;
-  KernelSample samples[24];
+  KernelSample samples[32];
 }
 blur_info;