Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

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

Merged
merged 1 commit into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -235,14 +235,14 @@ std::optional<Rect> 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<Entity> GaussianBlurFilterContents::RenderFilter(
Expand All @@ -256,13 +256,13 @@ std::optional<Entity> 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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<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.
Expand Down Expand Up @@ -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<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))};
Expand All @@ -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<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(
Expand All @@ -135,33 +153,42 @@ 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<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(
desc);
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 - 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<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);
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));
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) {
Expand All @@ -180,9 +207,11 @@ TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
.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();

Expand Down Expand Up @@ -213,9 +242,11 @@ TEST_P(GaussianBlurFilterContentsTest,
.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();

Expand Down Expand Up @@ -248,9 +279,10 @@ TEST_P(GaussianBlurFilterContentsTest,
.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();

Expand Down Expand Up @@ -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<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();

Expand Down Expand Up @@ -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<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();

Expand All @@ -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> 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) {
Expand Down
2 changes: 1 addition & 1 deletion impeller/entity/shaders/gaussian_blur/kernel.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct KernelSample {

uniform KernelSamples {
int sample_count;
KernelSample samples[24];
KernelSample samples[32];
}
blur_info;

Expand Down