[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;