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

Commit 9d89f87

Browse files
authored
[Impeller] new blur: implemented ping ponging (#49252)
This will reuse the downsample texture for the blur pass. issue: flutter/flutter#140189 Testing: This has golden image coverage for the refactoring. There is a slight performance difference which will show up in benchmarks. Ideally there would also be a memory test for the blur. I don't think there is one yet. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide] and the [C++, Objective-C, Java style guides]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I added new tests to check the change I am making or feature I am adding, or the PR is [test-exempt]. See [testing the engine] for instructions on writing and running engine tests. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I signed the [CLA]. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style [testing the engine]: https://github.com/flutter/flutter/wiki/Testing-the-engine [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat
1 parent a06926d commit 9d89f87

File tree

7 files changed

+102
-52
lines changed

7 files changed

+102
-52
lines changed

impeller/entity/contents/content_context.cc

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,12 @@ bool ContentContext::IsValid() const {
405405
return is_valid_;
406406
}
407407

408-
std::shared_ptr<Texture> ContentContext::MakeSubpass(
408+
fml::StatusOr<RenderTarget> ContentContext::MakeSubpass(
409409
const std::string& label,
410410
ISize texture_size,
411411
const SubpassCallback& subpass_callback,
412412
bool msaa_enabled) const {
413-
auto context = GetContext();
414-
413+
std::shared_ptr<Context> context = GetContext();
415414
RenderTarget subpass_target;
416415
if (context->GetCapabilities()->SupportsOffscreenMSAA() && msaa_enabled) {
417416
subpass_target = RenderTarget::CreateOffscreenMSAA(
@@ -428,32 +427,41 @@ std::shared_ptr<Texture> ContentContext::MakeSubpass(
428427
std::nullopt // stencil_attachment_config
429428
);
430429
}
430+
return MakeSubpass(label, subpass_target, subpass_callback);
431+
}
432+
433+
fml::StatusOr<RenderTarget> ContentContext::MakeSubpass(
434+
const std::string& label,
435+
const RenderTarget& subpass_target,
436+
const SubpassCallback& subpass_callback) const {
437+
std::shared_ptr<Context> context = GetContext();
438+
431439
auto subpass_texture = subpass_target.GetRenderTargetTexture();
432440
if (!subpass_texture) {
433-
return nullptr;
441+
return fml::Status(fml::StatusCode::kUnknown, "");
434442
}
435443

436444
auto sub_command_buffer = context->CreateCommandBuffer();
437445
sub_command_buffer->SetLabel(SPrintF("%s CommandBuffer", label.c_str()));
438446
if (!sub_command_buffer) {
439-
return nullptr;
447+
return fml::Status(fml::StatusCode::kUnknown, "");
440448
}
441449

442450
auto sub_renderpass = sub_command_buffer->CreateRenderPass(subpass_target);
443451
if (!sub_renderpass) {
444-
return nullptr;
452+
return fml::Status(fml::StatusCode::kUnknown, "");
445453
}
446454
sub_renderpass->SetLabel(SPrintF("%s RenderPass", label.c_str()));
447455

448456
if (!subpass_callback(*this, *sub_renderpass)) {
449-
return nullptr;
457+
return fml::Status(fml::StatusCode::kUnknown, "");
450458
}
451459

452460
if (!sub_command_buffer->EncodeAndSubmit(sub_renderpass)) {
453-
return nullptr;
461+
return fml::Status(fml::StatusCode::kUnknown, "");
454462
}
455463

456-
return subpass_texture;
464+
return subpass_target;
457465
}
458466

459467
#if IMPELLER_ENABLE_3D

impeller/entity/contents/content_context.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include "flutter/fml/build_config.h"
1414
#include "flutter/fml/logging.h"
15+
#include "flutter/fml/status_or.h"
1516
#include "impeller/base/validation.h"
1617
#include "impeller/core/formats.h"
1718
#include "impeller/entity/entity.h"
@@ -692,10 +693,17 @@ class ContentContext {
692693

693694
/// @brief Creates a new texture of size `texture_size` and calls
694695
/// `subpass_callback` with a `RenderPass` for drawing to the texture.
695-
std::shared_ptr<Texture> MakeSubpass(const std::string& label,
696-
ISize texture_size,
697-
const SubpassCallback& subpass_callback,
698-
bool msaa_enabled = true) const;
696+
fml::StatusOr<RenderTarget> MakeSubpass(
697+
const std::string& label,
698+
ISize texture_size,
699+
const SubpassCallback& subpass_callback,
700+
bool msaa_enabled = true) const;
701+
702+
/// Makes a subpass that will render to `subpass_target`.
703+
fml::StatusOr<RenderTarget> MakeSubpass(
704+
const std::string& label,
705+
const RenderTarget& subpass_target,
706+
const SubpassCallback& subpass_callback) const;
699707

700708
std::shared_ptr<LazyGlyphAtlas> GetLazyGlyphAtlas() const {
701709
return lazy_glyph_atlas_;

impeller/entity/contents/contents.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ std::optional<Snapshot> Contents::RenderToSnapshot(
7979
}
8080
}
8181

82-
auto texture = renderer.MakeSubpass(
82+
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
8383
label, ISize::Ceil(coverage->GetSize()),
8484
[&contents = *this, &entity, &coverage](const ContentContext& renderer,
8585
RenderPass& pass) -> bool {
@@ -92,12 +92,12 @@ std::optional<Snapshot> Contents::RenderToSnapshot(
9292
},
9393
msaa_enabled);
9494

95-
if (!texture) {
95+
if (!render_target.ok()) {
9696
return std::nullopt;
9797
}
9898

9999
auto snapshot = Snapshot{
100-
.texture = texture,
100+
.texture = render_target.value().GetRenderTargetTexture(),
101101
.transform = Matrix::MakeTranslation(coverage->GetOrigin()),
102102
};
103103
if (sampler_descriptor.has_value()) {

impeller/entity/contents/filters/blend_filter_contents.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,15 @@ static std::optional<Entity> AdvancedBlend(
223223
return true;
224224
};
225225

226-
auto out_texture = renderer.MakeSubpass(
226+
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
227227
"Advanced Blend Filter", ISize(subpass_coverage.GetSize()), callback);
228-
if (!out_texture) {
228+
if (!render_target.ok()) {
229229
return std::nullopt;
230230
}
231231

232232
return Entity::FromSnapshot(
233233
Snapshot{
234-
.texture = out_texture,
234+
.texture = render_target.value().GetRenderTargetTexture(),
235235
.transform = Matrix::MakeTranslation(subpass_coverage.GetOrigin()),
236236
// Since we absorbed the transform of the inputs and used the
237237
// respective snapshot sampling modes when blending, pass on
@@ -646,16 +646,16 @@ static std::optional<Entity> PipelineBlend(
646646
return true;
647647
};
648648

649-
auto out_texture = renderer.MakeSubpass(
649+
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
650650
"Pipeline Blend Filter", ISize(subpass_coverage.GetSize()), callback);
651651

652-
if (!out_texture) {
652+
if (!render_target.ok()) {
653653
return std::nullopt;
654654
}
655655

656656
return Entity::FromSnapshot(
657657
Snapshot{
658-
.texture = out_texture,
658+
.texture = render_target.value().GetRenderTargetTexture(),
659659
.transform = Matrix::MakeTranslation(subpass_coverage.GetOrigin()),
660660
// Since we absorbed the transform of the inputs and used the
661661
// respective snapshot sampling modes when blending, pass on

impeller/entity/contents/filters/directional_gaussian_blur_filter_contents.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ Sigma ScaleSigma(Sigma sigma) {
3737
DirectionalGaussianBlurFilterContents::DirectionalGaussianBlurFilterContents() =
3838
default;
3939

40-
DirectionalGaussianBlurFilterContents::~
41-
DirectionalGaussianBlurFilterContents() = default;
40+
DirectionalGaussianBlurFilterContents::
41+
~DirectionalGaussianBlurFilterContents() = default;
4242

4343
void DirectionalGaussianBlurFilterContents::SetSigma(Sigma sigma) {
4444
blur_sigma_ = sigma;
@@ -256,10 +256,10 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
256256
Vector2 scaled_size = pass_texture_rect.GetSize() * scale;
257257
ISize floored_size = ISize(scaled_size.x, scaled_size.y);
258258

259-
auto out_texture = renderer.MakeSubpass("Directional Gaussian Blur Filter",
260-
floored_size, subpass_callback);
259+
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
260+
"Directional Gaussian Blur Filter", floored_size, subpass_callback);
261261

262-
if (!out_texture) {
262+
if (!render_target.ok()) {
263263
return std::nullopt;
264264
}
265265

@@ -271,7 +271,7 @@ std::optional<Entity> DirectionalGaussianBlurFilterContents::RenderFilter(
271271

272272
return Entity::FromSnapshot(
273273
Snapshot{
274-
.texture = out_texture,
274+
.texture = render_target.value().GetRenderTargetTexture(),
275275
.transform =
276276
texture_rotate.Invert() *
277277
Matrix::MakeTranslation(pass_texture_rect.GetOrigin()) *

impeller/entity/contents/filters/gaussian_blur_filter_contents.cc

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ void SetTileMode(SamplerDescriptor* descriptor,
6565

6666
/// Makes a subpass that will render the scaled down input and add the
6767
/// transparent gutter required for the blur halo.
68-
std::shared_ptr<Texture> MakeDownsampleSubpass(
68+
fml::StatusOr<RenderTarget> MakeDownsampleSubpass(
6969
const ContentContext& renderer,
7070
std::shared_ptr<Texture> input_texture,
7171
const SamplerDescriptor& sampler_descriptor,
@@ -110,21 +110,24 @@ std::shared_ptr<Texture> MakeDownsampleSubpass(
110110

111111
return true;
112112
};
113-
std::shared_ptr<Texture> out_texture = renderer.MakeSubpass(
113+
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
114114
"Gaussian Blur Filter", subpass_size, subpass_callback);
115-
return out_texture;
115+
return render_target;
116116
}
117117

118-
std::shared_ptr<Texture> MakeBlurSubpass(
118+
fml::StatusOr<RenderTarget> MakeBlurSubpass(
119119
const ContentContext& renderer,
120-
std::shared_ptr<Texture> input_texture,
120+
const RenderTarget& input_pass,
121121
const SamplerDescriptor& sampler_descriptor,
122122
Entity::TileMode tile_mode,
123-
const GaussianBlurFragmentShader::BlurInfo& blur_info) {
123+
const GaussianBlurFragmentShader::BlurInfo& blur_info,
124+
std::optional<RenderTarget> destination_target) {
124125
if (blur_info.blur_sigma < kEhCloseEnough) {
125-
return input_texture;
126+
return input_pass;
126127
}
127128

129+
std::shared_ptr<Texture> input_texture = input_pass.GetRenderTargetTexture();
130+
128131
// TODO(gaaclarke): This blurs the whole image, but because we know the clip
129132
// region we could focus on just blurring that.
130133
ISize subpass_size = input_texture->GetSize();
@@ -171,9 +174,13 @@ std::shared_ptr<Texture> MakeBlurSubpass(
171174

172175
return true;
173176
};
174-
std::shared_ptr<Texture> out_texture = renderer.MakeSubpass(
175-
"Gaussian Blur Filter", subpass_size, subpass_callback);
176-
return out_texture;
177+
if (destination_target.has_value()) {
178+
return renderer.MakeSubpass("Gaussian Blur Filter",
179+
destination_target.value(), subpass_callback);
180+
} else {
181+
return renderer.MakeSubpass("Gaussian Blur Filter", subpass_size,
182+
subpass_callback);
183+
}
177184
}
178185

179186
} // namespace
@@ -293,38 +300,65 @@ std::optional<Entity> GaussianBlurFilterContents::RenderFilter(
293300
Quad uvs = CalculateUVs(inputs[0], entity, source_rect_padded,
294301
input_snapshot->texture->GetSize());
295302

296-
std::shared_ptr<Texture> pass1_out_texture = MakeDownsampleSubpass(
303+
fml::StatusOr<RenderTarget> pass1_out = MakeDownsampleSubpass(
297304
renderer, input_snapshot->texture, input_snapshot->sampler_descriptor,
298305
uvs, subpass_size, tile_mode_);
299306

300-
Vector2 pass1_pixel_size = 1.0 / Vector2(pass1_out_texture->GetSize());
307+
if (!pass1_out.ok()) {
308+
return std::nullopt;
309+
}
301310

302-
std::shared_ptr<Texture> pass2_out_texture =
303-
MakeBlurSubpass(renderer, pass1_out_texture,
311+
Vector2 pass1_pixel_size =
312+
1.0 / Vector2(pass1_out.value().GetRenderTargetTexture()->GetSize());
313+
314+
fml::StatusOr<RenderTarget> pass2_out =
315+
MakeBlurSubpass(renderer, /*input_pass=*/pass1_out.value(),
304316
input_snapshot->sampler_descriptor, tile_mode_,
305317
GaussianBlurFragmentShader::BlurInfo{
306318
.blur_uv_offset = Point(0.0, pass1_pixel_size.y),
307319
.blur_sigma = scaled_sigma.y * effective_scalar.y,
308320
.blur_radius = blur_radius.y * effective_scalar.y,
309321
.step_size = 1.0,
310-
});
322+
},
323+
/*destination_target=*/std::nullopt);
311324

312-
// TODO(gaaclarke): Make this pass reuse the texture from pass1.
313-
std::shared_ptr<Texture> pass3_out_texture =
314-
MakeBlurSubpass(renderer, pass2_out_texture,
325+
if (!pass2_out.ok()) {
326+
return std::nullopt;
327+
}
328+
329+
// Only ping pong if the first pass actually created a render target.
330+
auto pass3_destination = pass2_out.value().GetRenderTargetTexture() !=
331+
pass1_out.value().GetRenderTargetTexture()
332+
? std::optional<RenderTarget>(pass1_out.value())
333+
: std::optional<RenderTarget>(std::nullopt);
334+
335+
fml::StatusOr<RenderTarget> pass3_out =
336+
MakeBlurSubpass(renderer, /*input_pass=*/pass2_out.value(),
315337
input_snapshot->sampler_descriptor, tile_mode_,
316338
GaussianBlurFragmentShader::BlurInfo{
317339
.blur_uv_offset = Point(pass1_pixel_size.x, 0.0),
318340
.blur_sigma = scaled_sigma.x * effective_scalar.x,
319341
.blur_radius = blur_radius.x * effective_scalar.x,
320342
.step_size = 1.0,
321-
});
343+
},
344+
pass3_destination);
345+
346+
if (!pass3_out.ok()) {
347+
return std::nullopt;
348+
}
349+
350+
// The ping-pong approach requires that each render pass output has the same
351+
// size.
352+
FML_DCHECK((pass1_out.value().GetRenderTargetSize() ==
353+
pass2_out.value().GetRenderTargetSize()) &&
354+
(pass2_out.value().GetRenderTargetSize() ==
355+
pass3_out.value().GetRenderTargetSize()));
322356

323357
SamplerDescriptor sampler_desc = MakeSamplerDescriptor(
324358
MinMagFilter::kLinear, SamplerAddressMode::kClampToEdge);
325359

326360
return Entity::FromSnapshot(
327-
Snapshot{.texture = pass3_out_texture,
361+
Snapshot{.texture = pass3_out.value().GetRenderTargetTexture(),
328362
.transform = input_snapshot->transform *
329363
padding_snapshot_adjustment *
330364
Matrix::MakeScale(1 / effective_scalar),

impeller/entity/contents/filters/morphology_filter_contents.cc

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,9 @@ std::optional<Entity> DirectionalMorphologyFilterContents::RenderFilter(
137137
return pass.AddCommand(std::move(cmd));
138138
};
139139

140-
auto out_texture = renderer.MakeSubpass("Directional Morphology Filter",
141-
ISize(coverage.GetSize()), callback);
142-
if (!out_texture) {
140+
fml::StatusOr<RenderTarget> render_target = renderer.MakeSubpass(
141+
"Directional Morphology Filter", ISize(coverage.GetSize()), callback);
142+
if (!render_target.ok()) {
143143
return std::nullopt;
144144
}
145145

@@ -148,7 +148,7 @@ std::optional<Entity> DirectionalMorphologyFilterContents::RenderFilter(
148148
sampler_desc.mag_filter = MinMagFilter::kLinear;
149149

150150
return Entity::FromSnapshot(
151-
Snapshot{.texture = out_texture,
151+
Snapshot{.texture = render_target.value().GetRenderTargetTexture(),
152152
.transform = Matrix::MakeTranslation(coverage.GetOrigin()),
153153
.sampler_descriptor = sampler_desc,
154154
.opacity = input_snapshot->opacity},

0 commit comments

Comments
 (0)