diff --git a/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc b/impeller/entity/contents/filters/gaussian_blur_filter_contents.cc index e23f95c4cc192..0710dc6275cb7 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 @@ std::optional GaussianBlurFilterContents::GetFilterCoverage( 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 GaussianBlurFilterContents::RenderFilter( @@ -256,13 +256,13 @@ std::optional GaussianBlurFilterContents::RenderFilter( 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 GenerateBlurInfo( 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 38d52d36309be..ba3053483181f 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 @@ namespace { // 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& func, - float target, - float guess, - float tolerance) { - const float delta = 1e-6; - float x = guess; - float fx; +fml::StatusOr LowerBoundNewtonianMethod( + const std::function& 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 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, CoverageSimple) { } TEST(GaussianBlurFilterContentsTest, CoverageWithSigma) { - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); - GaussianBlurFilterContents contents(/*sigma_x=*/sigma_radius_1, - /*sigma_y=*/sigma_radius_1, + fml::StatusOr 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 @@ TEST_P(GaussianBlurFilterContentsTest, CoverageWithTexture) { .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 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 = GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture( @@ -135,9 +153,12 @@ TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { .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 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 = GetContentContext()->GetContext()->GetResourceAllocator()->CreateTexture( @@ -145,23 +166,29 @@ TEST_P(GaussianBlurFilterContentsTest, CoverageWithEffectTransform) { FilterInput::Vector inputs = {FilterInput::Make(texture)}; Entity entity; entity.SetTransform(Matrix::MakeTranslation({100, 100, 0})); - std::optional coverage = contents.GetFilterCoverage( - inputs, entity, /*effect_transform=*/Matrix::MakeScale({2.0, 2.0, 1.0})); + std::optional coverage = + contents.GetFilterCoverage(inputs, entity, effect_transform); EXPECT_TRUE(coverage.has_value()); if (coverage.has_value()) { EXPECT_RECT_NEAR(coverage.value(), - Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2)); + Rect::MakeLTRB(100 - 1, 100 - 1, 200 + 1, 200 + 1)); } } TEST(GaussianBlurFilterContentsTest, FilterSourceCoverage) { - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal); + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); std::optional 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)); + EXPECT_TRUE(coverage.has_value()); + if (coverage.has_value()) { + EXPECT_RECT_NEAR(coverage.value(), + Rect::MakeLTRB(100 - 2, 100 - 2, 200 + 2, 200 + 2)); + } } TEST(GaussianBlurFilterContentsTest, CalculateSigmaValues) { @@ -180,9 +207,11 @@ TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) { .size = ISize(100, 100), }; std::shared_ptr texture = MakeTexture(desc); - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - 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 renderer = GetContentContext(); @@ -213,9 +242,11 @@ TEST_P(GaussianBlurFilterContentsTest, .size = ISize(100, 100), }; std::shared_ptr texture = MakeTexture(desc); - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); + ASSERT_TRUE(sigma_radius_1.ok()); auto contents = std::make_unique( - 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 renderer = GetContentContext(); @@ -248,9 +279,10 @@ TEST_P(GaussianBlurFilterContentsTest, .size = ISize(400, 300), }; std::shared_ptr texture = MakeTexture(desc); - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); auto contents = std::make_unique( - 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 renderer = GetContentContext(); @@ -308,9 +340,10 @@ TEST_P(GaussianBlurFilterContentsTest, TextureContentsWithDestinationRect) { texture_contents->SetDestinationRect(Rect::MakeXYWH( 50, 40, texture->GetSize().width, texture->GetSize().height)); - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); auto contents = std::make_unique( - 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 renderer = GetContentContext(); @@ -347,9 +380,10 @@ TEST_P(GaussianBlurFilterContentsTest, texture_contents->SetDestinationRect(Rect::MakeXYWH( 50, 40, texture->GetSize().width, texture->GetSize().height)); - Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0); + fml::StatusOr sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, Matrix()); auto contents = std::make_unique( - 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 renderer = GetContentContext(); @@ -372,13 +406,56 @@ TEST_P(GaussianBlurFilterContentsTest, } } +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 = MakeTexture(desc); + auto texture_contents = std::make_shared(); + 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 sigma_radius_1 = + CalculateSigmaForBlurRadius(1.0, effect_transform); + ASSERT_TRUE(sigma_radius_1.ok()); + auto contents = std::make_unique( + sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal); + contents->SetInputs({FilterInput::Make(texture_contents)}); + contents->SetEffectTransform(effect_transform); + std::shared_ptr renderer = GetContentContext(); + + Entity entity; + std::optional 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 result_coverage = result.value().GetCoverage(); + std::optional 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 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 57f0de0a61183..d8a544bf070f6 100644 --- a/impeller/entity/shaders/gaussian_blur/kernel.glsl +++ b/impeller/entity/shaders/gaussian_blur/kernel.glsl @@ -16,7 +16,7 @@ struct KernelSample { uniform KernelSamples { int sample_count; - KernelSample samples[24]; + KernelSample samples[32]; } blur_info;