Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
81 changes: 35 additions & 46 deletions impeller/entity/contents/filters/gaussian_blur_filter_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,6 @@ void BindVertices(Command& cmd,
cmd.BindVertices(vtx_builder.CreateVertexBuffer(host_buffer));
}

Matrix MakeAnchorScale(const Point& anchor, Vector2 scale) {
return Matrix::MakeTranslation({anchor.x, anchor.y, 0}) *
Matrix::MakeScale(scale) *
Matrix::MakeTranslation({-anchor.x, -anchor.y, 0});
}

void SetTileMode(SamplerDescriptor* descriptor,
const ContentContext& renderer,
Entity::TileMode tile_mode) {
Expand Down Expand Up @@ -87,7 +81,6 @@ std::shared_ptr<Texture> MakeDownsampleSubpass(
const SamplerDescriptor& sampler_descriptor,
const Quad& uvs,
const ISize& subpass_size,
const Vector2 padding,
Entity::TileMode tile_mode) {
ContentContext::SubpassCallback subpass_callback =
[&](const ContentContext& renderer, RenderPass& pass) {
Expand All @@ -104,23 +97,13 @@ std::shared_ptr<Texture> MakeDownsampleSubpass(
frame_info.texture_sampler_y_coord_scale = 1.0;
frame_info.alpha = 1.0;

// Insert transparent gutter around the downsampled image so the blur
// creates a halo effect. This compensates for when the expanded clip
// region can't give us the full gutter we want.
Vector2 texture_size = Vector2(input_texture->GetSize());
Quad guttered_uvs =
MakeAnchorScale({0.5, 0.5},
(texture_size + padding * 2) / texture_size)
.Transform(uvs);

BindVertices<TextureFillVertexShader>(
cmd, host_buffer,
{
{Point(0, 0), guttered_uvs[0]},
{Point(1, 0), guttered_uvs[1]},
{Point(0, 1), guttered_uvs[2]},
{Point(1, 1), guttered_uvs[3]},
});
BindVertices<TextureFillVertexShader>(cmd, host_buffer,
{
{Point(0, 0), uvs[0]},
{Point(1, 0), uvs[1]},
{Point(0, 1), uvs[2]},
{Point(1, 1), uvs[3]},
});

SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
SetTileMode(&linear_sampler_descriptor, renderer, tile_mode);
Expand Down Expand Up @@ -276,18 +259,14 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
// transparent gutter.
std::optional<Rect> expanded_coverage_hint = ExpandCoverageHint(
coverage_hint, entity.GetTransform() * effect_transform, padding);
// TODO(gaaclarke): How much of the gutter is thrown away can be used to
// adjust the padding that is added in the downsample pass.
// For example, if we get all the padding we requested from
// the expanded_coverage_hint, there is no need to add a
// transparent gutter.

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

if (scaled_sigma.x < kEhCloseEnough && scaled_sigma.y < kEhCloseEnough) {
return Entity::FromSnapshot(input_snapshot.value(), entity.GetBlendMode(),
Expand All @@ -300,21 +279,33 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
// gutter from the expanded_coverage_hint, we can skip the downsample pass.
// pass.
Vector2 downsample_scalar(desired_scalar, desired_scalar);
Vector2 padded_size =
Vector2(input_snapshot->texture->GetSize()) + 2.0 * padding;
Vector2 downsampled_size = padded_size * downsample_scalar;
Rect source_rect = Rect::MakeSize(input_snapshot->texture->GetSize());
Rect source_rect_padded = source_rect;
Matrix padding_snapshot_adjustment;
if (!coverage_hint.has_value() ||
(input_snapshot_coverage.has_value() &&
!input_snapshot_coverage->Contains(coverage_hint.value()))) {
Copy link
Member

Choose a reason for hiding this comment

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

A couple of things come to mind while thinking about this:

  1. Did you mean to compare against the expanded coverage hint instead? At the end of the downsample pass, we need to have enough content in the downsample pass to cover sampling from the entire expanded coverage area area for the full area of the expanded coverage hint.
  2. Should this check be flipped? Like if the expanded_coverage_hint is equal to or a subset of the input_snapshot_coverage, then I suppose that would indicate that the snapshot rendered content into a big enough square that we don't need to worry about creating more with the proper tiling mode.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think so, the input_snapshot_coverage is incorporating the expanded coverage hint.

This is basically saying: I requested an expanded_coverage_hint and the snapshot you gave me for that doesn't contain all of the area of the coverage_hint. Therefore, some part of the halo will be visible and we'll add padding.

Copy link
Member Author

Choose a reason for hiding this comment

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

Check out the test, that should make this clear. The coverage_hint is saying we are going to render a small portion of the image, so there is no need to add the halo gutter.

Copy link
Member

@bdero bdero Dec 20, 2023

Choose a reason for hiding this comment

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

Ah, I actually missed the not operator in front of the Contains check here.

I still think this isn't quite working for all cases. I made an interactive playground (will push a PR for it) to demonstrate an issue with Decal tiling:

Screen.Recording.2023-12-20.at.5.08.59.AM.mov

The color jumping is explained by flutter/flutter#140193 (comment) and isn't the focus of the video. The main problem is that the edge of the blurred image turns opaque when the clip becomes contained by the input snapshot coverage.

Copy link
Member

Choose a reason for hiding this comment

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

(Made a PR for the interactive toy here: #49283)

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahh right, the coverage_hint has to be inside of the snapshot image coverage while accounting for the blur radius. I'll fix that although I wonder if that will make this optimization hardly applicable.

Copy link
Member Author

Choose a reason for hiding this comment

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

I fixed the problem, but there is a jump in the rendering because of the downsampling issue. It also catches way less cases now because it isn't cutting off the padding when hitting the edge of the input. I'm going to keep all the refactors and land it as a refactor since I have PRs that depend on this. I can circle back.

I also ran into a fun Rect floating point bug =( flutter/flutter#140464

// This means that the snapshot does not contain all the data it needs to
// render, so we add extra padding for the blur halo to render.
// TODO(gaaclarke): This adds a gutter for the blur halo that is uniform,
// if we could only add padding where necessary that would
// be more efficient.
source_rect_padded = source_rect_padded.Expand(padding);
padding_snapshot_adjustment = Matrix::MakeTranslation(-padding);
}
Vector2 downsampled_size = source_rect_padded.size * downsample_scalar;
// TODO(gaaclarke): I don't think we are correctly handling this fractional
// amount we are throwing away.
ISize subpass_size =
ISize(round(downsampled_size.x), round(downsampled_size.y));
Vector2 effective_scalar = subpass_size / padded_size;
Vector2 effective_scalar = Vector2(subpass_size) / source_rect_padded.size;

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

std::shared_ptr<Texture> pass1_out_texture = MakeDownsampleSubpass(
renderer, input_snapshot->texture, input_snapshot->sampler_descriptor,
uvs, subpass_size, padding, tile_mode_);
uvs, subpass_size, tile_mode_);

Vector2 pass1_pixel_size = 1.0 / Vector2(pass1_out_texture->GetSize());

Expand Down Expand Up @@ -343,13 +334,12 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge);

return Entity::FromSnapshot(
Snapshot{
.texture = pass3_out_texture,
.transform = input_snapshot->transform *
Matrix::MakeTranslation({-padding.x, -padding.y, 0}) *
Matrix::MakeScale(1 / effective_scalar),
.sampler_descriptor = sampler_desc,
.opacity = input_snapshot->opacity},
Snapshot{.texture = pass3_out_texture,
.transform = input_snapshot->transform *
padding_snapshot_adjustment *
Matrix::MakeScale(1 / effective_scalar),
.sampler_descriptor = sampler_desc,
.opacity = input_snapshot->opacity},
entity.GetBlendMode(), entity.GetClipDepth());
}

Expand All @@ -360,11 +350,10 @@ Scalar GaussianBlurFilterContents::CalculateBlurRadius(Scalar sigma) {
Quad GaussianBlurFilterContents::CalculateUVs(
const std::shared_ptr<FilterInput>& filter_input,
const Entity& entity,
const Rect& source_rect,
const ISize& texture_size) {
Matrix input_transform = filter_input->GetLocalTransform(entity);
Rect snapshot_rect =
Rect::MakeXYWH(0, 0, texture_size.width, texture_size.height);
Quad coverage_quad = snapshot_rect.GetTransformedPoints(input_transform);
Quad coverage_quad = source_rect.GetTransformedPoints(input_transform);

Matrix uv_transform = Matrix::MakeScale(
{1.0f / texture_size.width, 1.0f / texture_size.height, 1.0f});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,12 @@ class GaussianBlurFilterContents final : public FilterContents {
/// Calculate the UV coordinates for rendering the filter_input.
/// @param filter_input The FilterInput that should be rendered.
/// @param entity The associated entity for the filter_input.
/// @param texture_size The size of the texture_size the uvs will be used for.
/// @param source_rect The rect in source coordinates to convert to uvs.
/// @param texture_size The rect to convert in source coordinates.
static Quad CalculateUVs(const std::shared_ptr<FilterInput>& filter_input,
const Entity& entity,
const ISize& pass_size);
const Rect& source_rect,
const ISize& texture_size);

/// Calculate the scale factor for the downsample pass given a sigma value.
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,40 @@ TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
}
}

TEST_P(GaussianBlurFilterContentsTest, RenderCoverageNoBlurHalo) {
TextureDescriptor desc = {
.storage_mode = StorageMode::kDevicePrivate,
.format = PixelFormat::kB8G8R8A8UNormInt,
.size = ISize(100, 100),
};
std::shared_ptr<Texture> texture = MakeTexture(desc);
Scalar sigma_radius_1 = CalculateSigmaForBlurRadius(1.0);
auto contents = std::make_unique<GaussianBlurFilterContents>(
sigma_radius_1, sigma_radius_1, Entity::TileMode::kDecal);
contents->SetInputs({FilterInput::Make(texture)});
std::shared_ptr<ContentContext> renderer = GetContentContext();

Entity entity;
std::optional<Entity> result = contents->GetEntity(
*renderer, entity, /*coverage_hint=*/Rect::MakeLTRB(25, 25, 75, 75));
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(contents_coverage.value(),
Rect::MakeLTRB(-1, -1, 101, 101)));
// The result coverage is inside the coverage as a result of an
// optimization that avoids adding a halo gutter.
EXPECT_TRUE(
RectNear(result_coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)));
}
}
}

TEST_P(GaussianBlurFilterContentsTest,
RenderCoverageMatchesGetCoverageTranslate) {
TextureDescriptor desc = {
Expand Down Expand Up @@ -285,8 +319,8 @@ TEST_P(GaussianBlurFilterContentsTest, CalculateUVsSimple) {
std::shared_ptr<Texture> texture = MakeTexture(desc);
auto filter_input = FilterInput::Make(texture);
Entity entity;
Quad uvs = GaussianBlurFilterContents::CalculateUVs(filter_input, entity,
ISize(100, 100));
Quad uvs = GaussianBlurFilterContents::CalculateUVs(
filter_input, entity, Rect::MakeSize(ISize(100, 100)), ISize(100, 100));
std::optional<Rect> uvs_bounds = Rect::MakePointBounds(uvs);
EXPECT_TRUE(uvs_bounds.has_value());
if (uvs_bounds.has_value()) {
Expand Down