Skip to content

Commit

Permalink
[impeller] switches gaussian blur to a "source space" calculation (#5…
Browse files Browse the repository at this point in the history
…3261)

fixes flutter/flutter#149781
fixes flutter/flutter#149458
fixes flutter/flutter#140890

This works by performing the blur in the axis aligned "source space" (as opposed to "global space").  The rotation and scaling then is applied to the result of the gaussian blur.  Previously the differences between rrect_blur and gaussian blur were "fixed" in #53130 which worked for blurring content that had no signal.  This addresses that same problem but in a more correct way that is less prone to artifacts when translating a blur since the blur happens in "source space".

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
  • Loading branch information
gaaclarke authored Jun 8, 2024
1 parent 45cf05f commit 81dca13
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 32 deletions.
39 changes: 39 additions & 0 deletions impeller/aiks/aiks_blur_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,45 @@ TEST_P(AiksTest, GaussianBlurRotatedAndClippedInteractive) {
ASSERT_TRUE(OpenPlaygroundHere(callback));
}

TEST_P(AiksTest, GaussianBlurRotatedNonUniform) {
auto callback = [&](AiksContext& renderer) -> std::optional<Picture> {
const char* tile_mode_names[] = {"Clamp", "Repeat", "Mirror", "Decal"};
const Entity::TileMode tile_modes[] = {
Entity::TileMode::kClamp, Entity::TileMode::kRepeat,
Entity::TileMode::kMirror, Entity::TileMode::kDecal};

static float rotation = 45;
static float scale = 0.6;
static int selected_tile_mode = 3;

if (AiksTest::ImGuiBegin("Controls", nullptr,
ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::SliderFloat("Rotation (degrees)", &rotation, -180, 180);
ImGui::SliderFloat("Scale", &scale, 0, 2.0);
ImGui::Combo("Tile mode", &selected_tile_mode, tile_mode_names,
sizeof(tile_mode_names) / sizeof(char*));
ImGui::End();
}

Canvas canvas;
Paint paint = {.color = Color::Green(),
.image_filter =
ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
FilterContents::BlurStyle::kNormal,
tile_modes[selected_tile_mode])};
Vector2 center = Vector2(1024, 768) / 2;
canvas.Scale(GetContentScale());
canvas.Translate({center.x, center.y, 0});
canvas.Scale({scale, scale, 1});
canvas.Rotate(Degrees(rotation));

canvas.DrawRRect(Rect::MakeXYWH(-100, -100, 200, 200), Size(10, 10), paint);
return canvas.EndRecordingAsPicture();
};

ASSERT_TRUE(OpenPlaygroundHere(callback));
}

// This addresses a bug where tiny blurs could result in mip maps that beyond
// the limits for the textures used for blurring.
// See also: b/323402168
Expand Down
63 changes: 33 additions & 30 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ Entity ApplyBlurStyle(FilterContents::BlurStyle blur_style,
Entity::FromSnapshot(input_snapshot, entity.GetBlendMode());
Entity result;
Matrix blurred_transform = blur_entity.GetTransform();
Matrix snapshot_transform = snapshot_entity.GetTransform();
Matrix snapshot_transform =
entity.GetTransform() * snapshot_entity.GetTransform();
result.SetContents(Contents::MakeAnonymous(
fml::MakeCopyable([blur_entity = blur_entity.Clone(),
blurred_transform, snapshot_transform,
Expand Down Expand Up @@ -359,24 +360,23 @@ std::optional<Rect> GaussianBlurFilterContents::GetFilterCoverage(
return {};
}

std::optional<Rect> input_coverage = inputs[0]->GetCoverage(entity);
if (!input_coverage.has_value()) {
Entity snapshot_entity = entity.Clone();
snapshot_entity.SetTransform(Matrix());
std::optional<Rect> source_coverage = inputs[0]->GetCoverage(snapshot_entity);
if (!source_coverage.has_value()) {
return {};
}

Vector2 entity_scale_x = entity.GetTransform().Basis() * Vector2(1.0, 0.0);
Vector2 entity_scale_y = entity.GetTransform().Basis() * Vector2(0.0, 1.0);
Vector2 scaled_sigma = (Matrix::MakeScale({entity_scale_x.GetLength(),
entity_scale_y.GetLength(), 1.0}) *
Vector2 scaled_sigma = (effect_transform.Basis() *
Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
.Abs();
scaled_sigma.x = std::min(scaled_sigma.x, kMaxSigma);
scaled_sigma.y = std::min(scaled_sigma.y, kMaxSigma);
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));
Rect expanded_source_coverage = source_coverage->Expand(padding);
return expanded_source_coverage.TransformBounds(entity.GetTransform());
}

std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
Expand All @@ -390,11 +390,7 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
return std::nullopt;
}

Vector2 entity_scale_x = entity.GetTransform().Basis() * Vector2(1.0, 0.0);
Vector2 entity_scale_y = entity.GetTransform().Basis() * Vector2(0.0, 1.0);
Vector2 scaled_sigma = (effect_transform.Basis() *
Matrix::MakeScale({entity_scale_x.GetLength(),
entity_scale_y.GetLength(), 1.0}) *
Vector2(ScaleSigma(sigma_x_), ScaleSigma(sigma_y_)))
.Abs();
scaled_sigma.x = std::min(scaled_sigma.x, kMaxSigma);
Expand All @@ -420,17 +416,28 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
mip_count = 1;
}

Entity snapshot_entity = entity.Clone();
snapshot_entity.SetTransform(Matrix());
std::optional<Rect> source_expanded_coverage_hint;
if (expanded_coverage_hint.has_value()) {
source_expanded_coverage_hint =
expanded_coverage_hint->TransformBounds(entity.GetTransform().Invert());
}

std::optional<Snapshot> input_snapshot =
inputs[0]->GetSnapshot("GaussianBlur", renderer, entity,
/*coverage_limit=*/expanded_coverage_hint,
inputs[0]->GetSnapshot("GaussianBlur", renderer, snapshot_entity,
/*coverage_limit=*/source_expanded_coverage_hint,
/*mip_count=*/mip_count);
if (!input_snapshot.has_value()) {
return std::nullopt;
}

if (scaled_sigma.x < kEhCloseEnough && scaled_sigma.y < kEhCloseEnough) {
return Entity::FromSnapshot(input_snapshot.value(),
entity.GetBlendMode()); // No blur to render.
Entity result =
Entity::FromSnapshot(input_snapshot.value(),
entity.GetBlendMode()); // No blur to render.
result.SetTransform(entity.GetTransform() * input_snapshot->transform);
return result;
}

// In order to avoid shimmering in downsampling step, we should have mips.
Expand Down Expand Up @@ -462,7 +469,7 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
Vector2 effective_scalar =
Vector2(subpass_size) / source_rect_padded.GetSize();

Quad uvs = CalculateUVs(inputs[0], entity, source_rect_padded,
Quad uvs = CalculateUVs(inputs[0], snapshot_entity, source_rect_padded,
input_snapshot->texture->GetSize());

std::shared_ptr<CommandBuffer> command_buffer =
Expand All @@ -484,18 +491,14 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(

std::optional<Rect> input_snapshot_coverage = input_snapshot->GetCoverage();
Quad blur_uvs = {Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)};
if (expanded_coverage_hint.has_value() &&
input_snapshot_coverage.has_value() &&
// TODO(https://github.com/flutter/flutter/issues/140890): Remove this
// condition. There is some flaw in coverage stopping us from using this
// today. I attempted to use source coordinates to calculate the uvs,
// but that didn't work either.
input_snapshot.has_value() &&
input_snapshot.value().transform.IsTranslationScaleOnly()) {
FML_DCHECK(input_snapshot.value().transform.IsTranslationScaleOnly());
if (source_expanded_coverage_hint.has_value() &&
input_snapshot_coverage.has_value()) {
// Only process the uvs where the blur is happening, not the whole texture.
std::optional<Rect> uvs = MakeReferenceUVs(input_snapshot_coverage.value(),
expanded_coverage_hint.value())
.Intersection(Rect::MakeSize(Size(1, 1)));
std::optional<Rect> uvs =
MakeReferenceUVs(input_snapshot_coverage.value(),
source_expanded_coverage_hint.value())
.Intersection(Rect::MakeSize(Size(1, 1)));
FML_DCHECK(uvs.has_value());
if (uvs.has_value()) {
blur_uvs[0] = uvs->GetLeftTop();
Expand Down Expand Up @@ -560,7 +563,7 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(

Entity blur_output_entity = Entity::FromSnapshot(
Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
.transform = input_snapshot->transform *
.transform = entity.GetTransform() * input_snapshot->transform *
padding_snapshot_adjustment *
Matrix::MakeScale(1 / effective_scalar),
.sampler_descriptor = sampler_desc,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,8 @@ TEST_P(GaussianBlurFilterContentsTest,
CalculateSigmaForBlurRadius(1.0, Matrix());
auto contents = std::make_unique<GaussianBlurFilterContents>(
sigma_radius_1.value(), sigma_radius_1.value(), Entity::TileMode::kDecal,
FilterContents::BlurStyle::kNormal, /*mask_geometry=*/nullptr);
FilterContents::BlurStyle::kNormal,
/*mask_geometry=*/nullptr);
contents->SetInputs({FilterInput::Make(texture_contents)});
std::shared_ptr<ContentContext> renderer = GetContentContext();

Expand All @@ -400,7 +401,7 @@ TEST_P(GaussianBlurFilterContentsTest,
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(94.f, 74.f, 212.f, 212.f)));
Rect::MakeXYWH(98.f, 78.f, 204.0f, 204.f)));
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions testing/impeller_golden_tests_output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ impeller_Play_AiksTest_GaussianBlurRotatedAndClippedInteractive_Vulkan.png
impeller_Play_AiksTest_GaussianBlurRotatedAndClipped_Metal.png
impeller_Play_AiksTest_GaussianBlurRotatedAndClipped_OpenGLES.png
impeller_Play_AiksTest_GaussianBlurRotatedAndClipped_Vulkan.png
impeller_Play_AiksTest_GaussianBlurRotatedNonUniform_Metal.png
impeller_Play_AiksTest_GaussianBlurRotatedNonUniform_OpenGLES.png
impeller_Play_AiksTest_GaussianBlurRotatedNonUniform_Vulkan.png
impeller_Play_AiksTest_GaussianBlurScaledAndClipped_Metal.png
impeller_Play_AiksTest_GaussianBlurScaledAndClipped_OpenGLES.png
impeller_Play_AiksTest_GaussianBlurScaledAndClipped_Vulkan.png
Expand Down

0 comments on commit 81dca13

Please sign in to comment.