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

Commit 746697c

Browse files
authored
[Impeller] implements gaussian "blur halo" (#48149)
This makes the blurred item have a halo effect that is rendered outside of the bounds of the original thing that was rendered. issue: flutter/flutter#131580 [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
1 parent 6da31e1 commit 746697c

File tree

2 files changed

+54
-63
lines changed

2 files changed

+54
-63
lines changed

impeller/entity/contents/filters/gaussian_blur_filter_contents.cc

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,19 @@ void BindVertices(Command& cmd,
3737
cmd.BindVertices(vtx_buffer);
3838
}
3939

40+
Matrix MakeAnchorScale(const Point& anchor, Vector2 scale) {
41+
return Matrix::MakeTranslation({anchor.x, anchor.y, 0}) *
42+
Matrix::MakeScale(scale) *
43+
Matrix::MakeTranslation({-anchor.x, -anchor.y, 0});
44+
}
45+
4046
std::shared_ptr<Texture> MakeDownsampleSubpass(
4147
const ContentContext& renderer,
4248
std::shared_ptr<Texture> input_texture,
4349
const SamplerDescriptor& sampler_descriptor,
4450
const Quad& uvs,
45-
const ISize& subpass_size) {
51+
const ISize& subpass_size,
52+
const Vector2 padding) {
4653
ContentContext::SubpassCallback subpass_callback =
4754
[&](const ContentContext& renderer, RenderPass& pass) {
4855
HostBuffer& host_buffer = pass.GetTransientsBuffer();
@@ -58,20 +65,32 @@ std::shared_ptr<Texture> MakeDownsampleSubpass(
5865
frame_info.texture_sampler_y_coord_scale = 1.0;
5966
frame_info.alpha = 1.0;
6067

68+
// Insert transparent gutter around the downsampled image so the blur
69+
// creates a halo effect.
70+
Vector2 texture_size = Vector2(input_texture->GetSize());
71+
Quad vertices =
72+
MakeAnchorScale({0.5, 0.5},
73+
texture_size / (texture_size + padding * 2))
74+
.Transform(
75+
{Point(0, 0), Point(1, 0), Point(0, 1), Point(1, 1)});
76+
6177
BindVertices<TextureFillVertexShader>(cmd, host_buffer,
6278
{
63-
{Point(0, 0), uvs[0]},
64-
{Point(1, 0), uvs[1]},
65-
{Point(0, 1), uvs[2]},
66-
{Point(1, 1), uvs[3]},
79+
{vertices[0], uvs[0]},
80+
{vertices[1], uvs[1]},
81+
{vertices[2], uvs[2]},
82+
{vertices[3], uvs[3]},
6783
});
6884

85+
SamplerDescriptor linear_sampler_descriptor = sampler_descriptor;
86+
linear_sampler_descriptor.mag_filter = MinMagFilter::kLinear;
87+
linear_sampler_descriptor.min_filter = MinMagFilter::kLinear;
6988
TextureFillVertexShader::BindFrameInfo(
7089
cmd, host_buffer.EmplaceUniform(frame_info));
7190
TextureFillFragmentShader::BindTextureSampler(
7291
cmd, input_texture,
7392
renderer.GetContext()->GetSamplerLibrary()->GetSampler(
74-
sampler_descriptor));
93+
linear_sampler_descriptor));
7594

7695
pass.AddCommand(std::move(cmd));
7796

@@ -125,14 +144,6 @@ std::shared_ptr<Texture> MakeBlurSubpass(
125144
return out_texture;
126145
}
127146

128-
/// Given a desired |scalar|, will return the scalar that gets close but leaves
129-
/// |size| in integer sizes.
130-
Vector2 CalculateIntegerScale(Scalar scalar, ISize size) {
131-
ISize new_size(size.width / scalar, size.height / scalar);
132-
return Vector2(size.width / static_cast<Scalar>(new_size.width),
133-
size.height / static_cast<Scalar>(new_size.height));
134-
}
135-
136147
/// Calculate how much to scale down the texture depending on the blur radius.
137148
/// This curve was taken from |DirectionalGaussianBlurFilterContents|.
138149
Scalar CalculateScale(Scalar radius) {
@@ -200,46 +211,43 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
200211
}
201212

202213
Scalar blur_radius = CalculateBlurRadius(sigma_);
203-
Scalar desired_scale = 1.0 / CalculateScale(blur_radius);
204-
Vector2 downsample =
205-
CalculateIntegerScale(desired_scale, input_snapshot->texture->GetSize());
206-
207-
// TODO(gaaclarke): This isn't taking into account the blur radius to expand
208-
// the rendered size, so blurred objects are clipped. In
209-
// order for that to be implemented correctly we'll need to
210-
// start adjusting the geometry coordinates in the downsample
211-
// step so that there is a border of transparency around it
212-
// before the blur steps.
214+
Scalar desired_scalar = CalculateScale(blur_radius);
215+
Vector2 downsample_scalar(desired_scalar, desired_scalar);
216+
Vector2 padding(ceil(blur_radius), ceil(blur_radius));
217+
218+
Vector2 padded_size =
219+
Vector2(input_snapshot->texture->GetSize()) + 2.0 * padding;
220+
Vector2 downsampled_size = padded_size * downsample_scalar;
221+
// TODO(gaaclarke): I don't think we are correctly handling this fractional
222+
// amount we are throwing away.
213223
ISize subpass_size =
214-
ISize(input_snapshot->texture->GetSize().width / downsample.x,
215-
input_snapshot->texture->GetSize().height / downsample.y);
224+
ISize(round(downsampled_size.x), round(downsampled_size.y));
216225

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

220229
std::shared_ptr<Texture> pass1_out_texture = MakeDownsampleSubpass(
221230
renderer, input_snapshot->texture, input_snapshot->sampler_descriptor,
222-
uvs, subpass_size);
231+
uvs, subpass_size, padding);
223232

224-
Size pass1_pixel_size(1.0 / pass1_out_texture->GetSize().width,
225-
1.0 / pass1_out_texture->GetSize().height);
233+
Vector2 pass1_pixel_size = 1.0 / Vector2(pass1_out_texture->GetSize());
226234

227235
std::shared_ptr<Texture> pass2_out_texture = MakeBlurSubpass(
228236
renderer, pass1_out_texture, input_snapshot->sampler_descriptor,
229237
GaussianBlurFragmentShader::BlurInfo{
230-
.blur_uv_offset = Point(0.0, pass1_pixel_size.height),
231-
.blur_sigma = sigma_ / downsample.y,
232-
.blur_radius = blur_radius / downsample.y,
238+
.blur_uv_offset = Point(0.0, pass1_pixel_size.y),
239+
.blur_sigma = sigma_ * downsample_scalar.y,
240+
.blur_radius = blur_radius * downsample_scalar.y,
233241
.step_size = 1.0,
234242
});
235243

236244
// TODO(gaaclarke): Make this pass reuse the texture from pass1.
237245
std::shared_ptr<Texture> pass3_out_texture = MakeBlurSubpass(
238246
renderer, pass2_out_texture, input_snapshot->sampler_descriptor,
239247
GaussianBlurFragmentShader::BlurInfo{
240-
.blur_uv_offset = Point(pass1_pixel_size.width, 0.0),
241-
.blur_sigma = sigma_ / downsample.x,
242-
.blur_radius = blur_radius / downsample.x,
248+
.blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
249+
.blur_sigma = sigma_ * downsample_scalar.x,
250+
.blur_radius = blur_radius * downsample_scalar.x,
243251
.step_size = 1.0,
244252
});
245253

@@ -249,14 +257,10 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
249257
return Entity::FromSnapshot(
250258
Snapshot{
251259
.texture = pass3_out_texture,
252-
.transform =
253-
entity.GetTransform() *
254-
Matrix::MakeScale(
255-
{input_snapshot->texture->GetSize().width /
256-
static_cast<Scalar>(pass1_out_texture->GetSize().width),
257-
input_snapshot->texture->GetSize().height /
258-
static_cast<Scalar>(pass1_out_texture->GetSize().height),
259-
1.0}),
260+
.transform = entity.GetTransform() *
261+
Matrix::MakeTranslation({-padding.x, -padding.y, 0}) *
262+
Matrix::MakeScale(padded_size /
263+
Vector2(pass1_out_texture->GetSize())),
260264
.sampler_descriptor = sampler_desc,
261265
.opacity = input_snapshot->opacity},
262266
entity.GetBlendMode(), entity.GetClipDepth());

impeller/entity/contents/filters/gaussian_blur_filter_contents_unittests.cc

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,10 @@ TEST_P(GaussianBlurFilterContentsTest, RenderCoverageMatchesGetCoverage) {
135135
EXPECT_TRUE(result_coverage.has_value());
136136
EXPECT_TRUE(contents_coverage.has_value());
137137
if (result_coverage.has_value() && contents_coverage.has_value()) {
138-
// TODO(gaaclarke): This test won't pass until the blur_radius is used to
139-
// expand the coverage. See note inside of
140-
// gaussian_blur_filter_contents.cc.
141-
// EXPECT_TRUE(RectNear(result_coverage.value(),
142-
// contents_coverage.value()));
138+
EXPECT_TRUE(RectNear(contents_coverage.value(),
139+
Rect::MakeLTRB(-1, -1, 101, 101)));
143140
EXPECT_TRUE(
144-
RectNear(result_coverage.value(), Rect::MakeLTRB(0, 0, 100, 100)));
141+
RectNear(result_coverage.value(), Rect::MakeLTRB(-1, -1, 101, 101)));
145142
}
146143
}
147144
}
@@ -170,15 +167,10 @@ TEST_P(GaussianBlurFilterContentsTest,
170167
EXPECT_TRUE(result_coverage.has_value());
171168
EXPECT_TRUE(contents_coverage.has_value());
172169
if (result_coverage.has_value() && contents_coverage.has_value()) {
173-
// TODO(gaaclarke): This test won't pass until the blur_radius is used to
174-
// expand the coverage. See note inside of
175-
// gaussian_blur_filter_contents.cc.
176-
// EXPECT_TRUE(RectNear(result_coverage.value(),
177-
// contents_coverage.value()));
178170
EXPECT_TRUE(RectNear(contents_coverage.value(),
179171
Rect::MakeLTRB(99, 199, 201, 301)));
180-
EXPECT_TRUE(RectNear(result_coverage.value(),
181-
Rect::MakeLTRB(100, 200, 200, 300)));
172+
EXPECT_TRUE(
173+
RectNear(result_coverage.value(), Rect::MakeLTRB(99, 199, 201, 301)));
182174
}
183175
}
184176
}
@@ -208,15 +200,10 @@ TEST_P(GaussianBlurFilterContentsTest,
208200
EXPECT_TRUE(result_coverage.has_value());
209201
EXPECT_TRUE(contents_coverage.has_value());
210202
if (result_coverage.has_value() && contents_coverage.has_value()) {
211-
// TODO(gaaclarke): This test won't pass until the blur_radius is used to
212-
// expand the coverage. See note inside of
213-
// gaussian_blur_filter_contents.cc.
214-
// EXPECT_TRUE(RectNear(result_coverage.value(),
215-
// contents_coverage.value()));
216203
EXPECT_TRUE(RectNear(contents_coverage.value(),
217204
Rect::MakeLTRB(99, 99, 401, 501)));
218-
EXPECT_TRUE(RectNear(result_coverage.value(),
219-
Rect::MakeLTRB(100, 100, 400, 500)));
205+
EXPECT_TRUE(
206+
RectNear(result_coverage.value(), Rect::MakeLTRB(99, 99, 401, 501)));
220207
}
221208
}
222209
}

0 commit comments

Comments
 (0)