Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[impeller] switches gaussian blur to a "source space" calculation #53261

Merged
merged 15 commits into from
Jun 8, 2024
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 =
gaaclarke marked this conversation as resolved.
Show resolved Hide resolved
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());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will cause anything that's scaled up to be rendered at a resolution which is too low for accurate results.

Perhaps a good heuristic to use here would would be to extract the max basis length and apply it here.

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 @@ -617,6 +617,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