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

Commit 97df97a

Browse files
jonahwilliamszanderso
authored andcommitted
[Impeller] distinguish between no clear color and transparent black clear color. (#49038)
If we clear to transparent black, we're not forcing the pass to be constructed. Change the entity pass API so that we can tell the difference between clearing transparent black and not having a clear color. In flutter/flutter#139571 , the app is creating a layer that is clearing to a transparent color, which is getting skipped. That invalid texture is fed into a blend which produces either black or magenta error texture. Fixes flutter/flutter#139571
1 parent bc2e856 commit 97df97a

File tree

4 files changed

+212
-17
lines changed

4 files changed

+212
-17
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 168 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ TEST_P(AiksTest, CanRenderLinearGradientWithDitheringDisabled) {
448448

449449
TEST_P(AiksTest, CanRenderLinearGradientWithDitheringEnabled) {
450450
CanRenderLinearGradientWithDithering(this, true);
451-
} // namespace
451+
}
452452

453453
static void CanRenderRadialGradientWithDithering(AiksTest* aiks_test,
454454
bool use_dithering) {
@@ -2416,19 +2416,18 @@ TEST_P(AiksTest, ClearColorOptimizationDoesNotApplyForBackdropFilters) {
24162416
Picture picture = canvas.EndRecordingAsPicture();
24172417

24182418
std::optional<Color> actual_color;
2419+
bool found_subpass = false;
24192420
picture.pass->IterateAllElements([&](EntityPass::Element& element) -> bool {
24202421
if (auto subpass = std::get_if<std::unique_ptr<EntityPass>>(&element)) {
24212422
actual_color = subpass->get()->GetClearColor();
2423+
found_subpass = true;
24222424
}
24232425
// Fail if the first element isn't a subpass.
24242426
return true;
24252427
});
24262428

2427-
ASSERT_TRUE(actual_color.has_value());
2428-
if (!actual_color) {
2429-
return;
2430-
}
2431-
ASSERT_EQ(actual_color.value(), Color::BlackTransparent());
2429+
EXPECT_TRUE(found_subpass);
2430+
EXPECT_FALSE(actual_color.has_value());
24322431
}
24332432

24342433
TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
@@ -3645,5 +3644,168 @@ TEST_P(AiksTest, MaskBlurWithZeroSigmaIsSkipped) {
36453644
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
36463645
}
36473646

3647+
TEST_P(AiksTest, AdvancedBlendWithClearColorOptimization) {
3648+
Canvas canvas;
3649+
canvas.DrawPaint(
3650+
{.color = {1.0, 1.0, 0.0, 1.0}, .blend_mode = BlendMode::kSource});
3651+
3652+
canvas.DrawRect(
3653+
Rect::MakeXYWH(0, 0, 200, 300),
3654+
{.color = {1.0, 0.0, 1.0, 1.0}, .blend_mode = BlendMode::kMultiply});
3655+
}
3656+
3657+
TEST_P(AiksTest, GaussianBlurAtPeripheryVertical) {
3658+
Canvas canvas;
3659+
3660+
canvas.Scale(GetContentScale());
3661+
canvas.DrawRRect(Rect::MakeLTRB(0, 0, GetWindowSize().width, 100),
3662+
Size(10, 10), Paint{.color = Color::LimeGreen()});
3663+
canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
3664+
Size(10, 10), Paint{.color = Color::Magenta()});
3665+
canvas.ClipRect(Rect::MakeLTRB(100, 0, 200, GetWindowSize().height));
3666+
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
3667+
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
3668+
FilterContents::BlurStyle::kNormal,
3669+
Entity::TileMode::kClamp));
3670+
canvas.Restore();
3671+
3672+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3673+
}
3674+
3675+
TEST_P(AiksTest, GaussianBlurAtPeripheryHorizontal) {
3676+
Canvas canvas;
3677+
3678+
canvas.Scale(GetContentScale());
3679+
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
3680+
canvas.DrawImageRect(
3681+
std::make_shared<Image>(boston),
3682+
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height),
3683+
Rect::MakeLTRB(0, 0, GetWindowSize().width, 100), Paint{});
3684+
canvas.DrawRRect(Rect::MakeLTRB(0, 110, GetWindowSize().width, 210),
3685+
Size(10, 10), Paint{.color = Color::Magenta()});
3686+
canvas.ClipRect(Rect::MakeLTRB(0, 50, GetWindowSize().width, 150));
3687+
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
3688+
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
3689+
FilterContents::BlurStyle::kNormal,
3690+
Entity::TileMode::kClamp));
3691+
canvas.Restore();
3692+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3693+
}
3694+
3695+
#define FLT_FORWARD(mock, real, method) \
3696+
EXPECT_CALL(*mock, method()) \
3697+
.WillRepeatedly(::testing::Return(real->method()));
3698+
3699+
TEST_P(AiksTest, GaussianBlurWithoutDecalSupport) {
3700+
if (GetParam() != PlaygroundBackend::kMetal) {
3701+
GTEST_SKIP_(
3702+
"This backend doesn't yet support setting device capabilities.");
3703+
}
3704+
if (!WillRenderSomething()) {
3705+
// Sometimes these tests are run without playgrounds enabled which is
3706+
// pointless for this test since we are asserting that
3707+
// `SupportsDecalSamplerAddressMode` is called.
3708+
GTEST_SKIP_("This test requires playgrounds.");
3709+
}
3710+
3711+
std::shared_ptr<const Capabilities> old_capabilities =
3712+
GetContext()->GetCapabilities();
3713+
auto mock_capabilities = std::make_shared<MockCapabilities>();
3714+
EXPECT_CALL(*mock_capabilities, SupportsDecalSamplerAddressMode())
3715+
.Times(::testing::AtLeast(1))
3716+
.WillRepeatedly(::testing::Return(false));
3717+
FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultColorFormat);
3718+
FLT_FORWARD(mock_capabilities, old_capabilities, GetDefaultStencilFormat);
3719+
FLT_FORWARD(mock_capabilities, old_capabilities, SupportsOffscreenMSAA);
3720+
FLT_FORWARD(mock_capabilities, old_capabilities,
3721+
SupportsImplicitResolvingMSAA);
3722+
FLT_FORWARD(mock_capabilities, old_capabilities, SupportsReadFromResolve);
3723+
FLT_FORWARD(mock_capabilities, old_capabilities, SupportsFramebufferFetch);
3724+
FLT_FORWARD(mock_capabilities, old_capabilities, SupportsSSBO);
3725+
FLT_FORWARD(mock_capabilities, old_capabilities, SupportsCompute);
3726+
FLT_FORWARD(mock_capabilities, old_capabilities,
3727+
SupportsTextureToTextureBlits);
3728+
ASSERT_TRUE(SetCapabilities(mock_capabilities).ok());
3729+
3730+
auto texture = std::make_shared<Image>(CreateTextureForFixture("boston.jpg"));
3731+
Canvas canvas;
3732+
canvas.Scale(GetContentScale() * 0.5);
3733+
canvas.DrawPaint({.color = Color::Black()});
3734+
canvas.DrawImage(
3735+
texture, Point(200, 200),
3736+
{
3737+
.image_filter = ImageFilter::MakeBlur(
3738+
Sigma(20.0), Sigma(20.0), FilterContents::BlurStyle::kNormal,
3739+
Entity::TileMode::kDecal),
3740+
});
3741+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3742+
}
3743+
3744+
TEST_P(AiksTest, GaussianBlurOneDimension) {
3745+
Canvas canvas;
3746+
3747+
canvas.Scale(GetContentScale());
3748+
canvas.Scale({0.5, 0.5, 1.0});
3749+
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
3750+
canvas.DrawImage(std::make_shared<Image>(boston), Point(100, 100), Paint{});
3751+
canvas.SaveLayer({.blend_mode = BlendMode::kSource}, std::nullopt,
3752+
ImageFilter::MakeBlur(Sigma(50.0), Sigma(0.0),
3753+
FilterContents::BlurStyle::kNormal,
3754+
Entity::TileMode::kClamp));
3755+
canvas.Restore();
3756+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3757+
}
3758+
3759+
// Smoketest to catch issues with the coverage hint.
3760+
// Draws a rotated blurred image within a rectangle clip. The center of the clip
3761+
// rectangle is the center of the rotated image. The entire area of the clip
3762+
// rectangle should be filled with opaque colors output by the blur.
3763+
TEST_P(AiksTest, GaussianBlurRotatedAndClipped) {
3764+
Canvas canvas;
3765+
std::shared_ptr<Texture> boston = CreateTextureForFixture("boston.jpg");
3766+
Rect bounds =
3767+
Rect::MakeXYWH(0, 0, boston->GetSize().width, boston->GetSize().height);
3768+
Vector2 image_center = Vector2(bounds.GetSize() / 2);
3769+
Paint paint = {.image_filter =
3770+
ImageFilter::MakeBlur(Sigma(20.0), Sigma(20.0),
3771+
FilterContents::BlurStyle::kNormal,
3772+
Entity::TileMode::kDecal)};
3773+
Vector2 clip_size = {150, 75};
3774+
Vector2 center = Vector2(1024, 768) / 2;
3775+
canvas.Scale(GetContentScale());
3776+
canvas.ClipRect(
3777+
Rect::MakeLTRB(center.x, center.y, center.x, center.y).Expand(clip_size));
3778+
canvas.Translate({center.x, center.y, 0});
3779+
canvas.Scale({0.6, 0.6, 1});
3780+
canvas.Rotate(Degrees(25));
3781+
3782+
canvas.DrawImageRect(std::make_shared<Image>(boston), /*source=*/bounds,
3783+
/*dest=*/bounds.Shift(-image_center), paint);
3784+
3785+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3786+
}
3787+
3788+
TEST_P(AiksTest, SubpassWithClearColorOptimization) {
3789+
Canvas canvas;
3790+
3791+
// Use a non-srcOver blend mode to ensure that we don't detect this as an
3792+
// opacity peephole optimization.
3793+
canvas.SaveLayer(
3794+
{.color = Color::Blue().WithAlpha(0.5), .blend_mode = BlendMode::kSource},
3795+
Rect::MakeLTRB(0, 0, 200, 200));
3796+
canvas.DrawPaint(
3797+
{.color = Color::BlackTransparent(), .blend_mode = BlendMode::kSource});
3798+
canvas.Restore();
3799+
3800+
canvas.SaveLayer(
3801+
{.color = Color::Blue(), .blend_mode = BlendMode::kDestinationOver});
3802+
canvas.Restore();
3803+
3804+
// This playground should appear blank on CI since we are only drawing
3805+
// transparent black. If the clear color optimization is broken, the texture
3806+
// will be filled with NaNs and may produce a magenta texture on macOS or iOS.
3807+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3808+
}
3809+
36483810
} // namespace testing
36493811
} // namespace impeller

impeller/aiks/paint_pass_delegate.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
#include "impeller/entity/contents/texture_contents.h"
1111
#include "impeller/entity/entity_pass.h"
1212
#include "impeller/geometry/color.h"
13-
#include "impeller/geometry/path_builder.h"
1413

1514
namespace impeller {
1615

impeller/entity/entity_pass.cc

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ bool EntityPass::Render(ContentContext& renderer,
368368
if (!supports_onscreen_backdrop_reads && reads_from_onscreen_backdrop) {
369369
auto offscreen_target = CreateRenderTarget(
370370
renderer, root_render_target.GetRenderTargetSize(), true,
371-
GetClearColor(render_target.GetRenderTargetSize()));
371+
GetClearColorOrDefault(render_target.GetRenderTargetSize()));
372372

373373
if (!OnRender(renderer, // renderer
374374
capture, // capture
@@ -475,7 +475,8 @@ bool EntityPass::Render(ContentContext& renderer,
475475
}
476476

477477
// Set up the clear color of the root pass.
478-
color0.clear_color = GetClearColor(render_target.GetRenderTargetSize());
478+
color0.clear_color =
479+
GetClearColorOrDefault(render_target.GetRenderTargetSize());
479480
root_render_target.SetColorAttachment(color0, 0);
480481

481482
EntityPassTarget pass_target(
@@ -628,10 +629,16 @@ EntityPass::EntityResult EntityPass::GetEntityForElement(
628629
}
629630

630631
auto subpass_target = CreateRenderTarget(
632+
<<<<<<< HEAD
631633
renderer, // renderer
632634
subpass_size, // size
633635
subpass->GetTotalPassReads(renderer) > 0, // readable
634636
subpass->GetClearColor(subpass_size)); // clear_color
637+
=======
638+
renderer, // renderer
639+
subpass_size, // size
640+
subpass->GetClearColorOrDefault(subpass_size)); // clear_color
641+
>>>>>>> 0b0fab8215 ([Impeller] distinguish between no clear color and transparent black clear color. (#49038))
635642

636643
if (!subpass_target.IsValid()) {
637644
VALIDATION_LOG << "Subpass render target is invalid.";
@@ -722,8 +729,7 @@ bool EntityPass::OnRender(
722729
}
723730
auto clear_color_size = pass_target.GetRenderTarget().GetRenderTargetSize();
724731

725-
if (!collapsed_parent_pass &&
726-
!GetClearColor(clear_color_size).IsTransparent()) {
732+
if (!collapsed_parent_pass && GetClearColor(clear_color_size).has_value()) {
727733
// Force the pass context to create at least one new pass if the clear color
728734
// is present.
729735
pass_context.GetRenderPass(pass_depth);
@@ -965,6 +971,20 @@ bool EntityPass::OnRender(
965971
FilterInput::Make(texture,
966972
result.entity.GetTransformation().Invert()),
967973
FilterInput::Make(result.entity.GetContents())};
974+
<<<<<<< HEAD
975+
=======
976+
std::optional<Color> foreground_color;
977+
std::optional<Rect> coverage;
978+
if (was_collapsing_clear_colors) {
979+
// If all previous elements were skipped due to clear color
980+
// optimization, then provide the clear color as the foreground of the
981+
// advanced blend.
982+
foreground_color = GetClearColorOrDefault(clear_color_size);
983+
coverage = Rect::MakeSize(clear_color_size);
984+
} else {
985+
coverage = result.entity.GetCoverage();
986+
}
987+
>>>>>>> 0b0fab8215 ([Impeller] distinguish between no clear color and transparent black clear color. (#49038))
968988
auto contents = ColorFilterContents::MakeBlend(
969989
result.entity.GetBlendMode(), inputs);
970990
contents->SetCoverageHint(result.entity.GetCoverage());
@@ -1140,21 +1160,29 @@ void EntityPass::SetBlendMode(BlendMode blend_mode) {
11401160
flood_clip_ = Entity::IsBlendModeDestructive(blend_mode);
11411161
}
11421162

1143-
Color EntityPass::GetClearColor(ISize target_size) const {
1144-
Color result = Color::BlackTransparent();
1163+
Color EntityPass::GetClearColorOrDefault(ISize size) const {
1164+
return GetClearColor(size).value_or(Color::BlackTransparent());
1165+
}
1166+
1167+
std::optional<Color> EntityPass::GetClearColor(ISize target_size) const {
11451168
if (backdrop_filter_proc_) {
1146-
return result;
1169+
return std::nullopt;
11471170
}
11481171

1172+
std::optional<Color> result = std::nullopt;
11491173
for (const Element& element : elements_) {
11501174
auto [entity_color, blend_mode] =
11511175
ElementAsBackgroundColor(element, target_size);
11521176
if (!entity_color.has_value()) {
11531177
break;
11541178
}
1155-
result = result.Blend(entity_color.value(), blend_mode);
1179+
result = result.value_or(Color::BlackTransparent())
1180+
.Blend(entity_color.value(), blend_mode);
1181+
}
1182+
if (result.has_value()) {
1183+
return result->Premultiply();
11561184
}
1157-
return result.Premultiply();
1185+
return result;
11581186
}
11591187

11601188
void EntityPass::SetBackdropFilter(BackdropFilterProc proc) {

impeller/entity/entity_pass.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,13 @@ class EntityPass {
135135

136136
void SetBlendMode(BlendMode blend_mode);
137137

138-
Color GetClearColor(ISize size = ISize::Infinite()) const;
138+
/// @brief Return the premultiplied clear color of the pass entities, if any.
139+
std::optional<Color> GetClearColor(ISize size = ISize::Infinite()) const;
140+
141+
/// @brief Return the premultiplied clear color of the pass entities.
142+
///
143+
/// If the entity pass has no clear color, this will return transparent black.
144+
Color GetClearColorOrDefault(ISize size = ISize::Infinite()) const;
139145

140146
void SetBackdropFilter(BackdropFilterProc proc);
141147

0 commit comments

Comments
 (0)