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

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

Merged
merged 15 commits into from
Jun 8, 2024
Merged
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());
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