[Impeller] Remove blend mode restrictions on clear color optimization (flutter/engine#43348)

Now that all of the CPU blend modes work, we can allow back-to-back
fullscreen solid color draws to get blended together regardless of their
blend mode/color opacity.
This commit is contained in:
Brandon DeRosier 2023-06-30 21:48:45 -07:00 committed by GitHub
parent a0dc7abbe9
commit a3745e962c
9 changed files with 56 additions and 42 deletions

View File

@ -1989,11 +1989,13 @@ TEST_P(AiksTest, OpacityPeepHoleApplicationTest) {
TEST_P(AiksTest, DrawPaintAbsorbsClears) {
Canvas canvas;
canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
canvas.DrawPaint(
{.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSource});
canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
.blend_mode = BlendMode::kSourceOver});
Picture picture = canvas.EndRecordingAsPicture();
ASSERT_EQ(picture.pass->GetClearColor(), Color::CornflowerBlue());
auto expected = Color::Red().Blend(Color::CornflowerBlue().WithAlpha(0.75),
BlendMode::kSourceOver);
ASSERT_EQ(picture.pass->GetClearColor(), expected);
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
std::shared_ptr<Context> real_context = GetContext();
@ -2010,8 +2012,9 @@ TEST_P(AiksTest, DrawRectAbsorbsClears) {
Canvas canvas;
canvas.DrawRect({0, 0, 300, 300},
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
canvas.DrawRect({0, 0, 300, 300}, {.color = Color::CornflowerBlue(),
.blend_mode = BlendMode::kSource});
canvas.DrawRect({0, 0, 300, 300},
{.color = Color::CornflowerBlue().WithAlpha(0.75),
.blend_mode = BlendMode::kSourceOver});
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
Picture picture = canvas.EndRecordingAsPicture();
@ -2029,9 +2032,9 @@ TEST_P(AiksTest, DrawRectAbsorbsClearsNegativeRRect) {
Canvas canvas;
canvas.DrawRRect({0, 0, 300, 300}, 5.0,
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
canvas.DrawRRect(
{0, 0, 300, 300}, 5.0,
{.color = Color::CornflowerBlue(), .blend_mode = BlendMode::kSource});
canvas.DrawRRect({0, 0, 300, 300}, 5.0,
{.color = Color::CornflowerBlue().WithAlpha(0.75),
.blend_mode = BlendMode::kSourceOver});
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
Picture picture = canvas.EndRecordingAsPicture();
@ -2069,8 +2072,9 @@ TEST_P(AiksTest, DrawRectAbsorbsClearsNegative) {
Canvas canvas;
canvas.DrawRect({0, 0, 300, 300},
{.color = Color::Red(), .blend_mode = BlendMode::kSource});
canvas.DrawRect({0, 0, 300, 300}, {.color = Color::CornflowerBlue(),
.blend_mode = BlendMode::kSource});
canvas.DrawRect({0, 0, 300, 300},
{.color = Color::CornflowerBlue().WithAlpha(0.75),
.blend_mode = BlendMode::kSourceOver});
std::shared_ptr<ContextSpy> spy = ContextSpy::Make();
Picture picture = canvas.EndRecordingAsPicture();
@ -2089,7 +2093,8 @@ TEST_P(AiksTest, CollapsedDrawPaintInSubpass) {
canvas.DrawPaint(
{.color = Color::Yellow(), .blend_mode = BlendMode::kSource});
canvas.SaveLayer({.blend_mode = BlendMode::kMultiply});
canvas.DrawPaint({.color = Color::Red(), .blend_mode = BlendMode::kSource});
canvas.DrawPaint({.color = Color::CornflowerBlue().WithAlpha(0.75),
.blend_mode = BlendMode::kSourceOver});
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
}
@ -2171,7 +2176,7 @@ static Picture BlendModeTest(BlendMode blend_mode,
canvas.DrawPaint({.color = Color::Black()});
//----------------------------------------------------------------------------
/// 1. Save layer blending (top left).
/// 1. Save layer blending (top squares).
///
canvas.Save();
@ -2201,7 +2206,7 @@ static Picture BlendModeTest(BlendMode blend_mode,
canvas.RestoreToCount(0);
//----------------------------------------------------------------------------
/// 2. CPU blend modes (top left).
/// 2. CPU blend modes (bottom squares).
///
canvas.Save();

View File

@ -118,6 +118,11 @@ void Contents::SetInheritedOpacity(Scalar opacity) {
"Contents::CanAcceptOpacity returns false.";
}
std::optional<Color> Contents::AsBackgroundColor(const Entity& entity,
ISize target_size) const {
return {};
}
bool Contents::ShouldRender(const Entity& entity,
const std::optional<Rect>& stencil_coverage) const {
if (!stencil_coverage.has_value()) {

View File

@ -117,10 +117,14 @@ class Contents {
/// Use of this method is invalid if CanAcceptOpacity returns false.
virtual void SetInheritedOpacity(Scalar opacity);
/// @brief Returns a color if this Contents will flood the given `target_size`
/// with a color. This output color is the "Source" color that will be
/// used for the Entity's blend operation.
///
/// This is useful for absorbing full screen solid color draws into
/// subpass clear colors.
virtual std::optional<Color> AsBackgroundColor(const Entity& entity,
ISize target_size) const {
return {};
}
ISize target_size) const;
private:
std::optional<Rect> coverage_hint_;

View File

@ -104,12 +104,6 @@ std::unique_ptr<SolidColorContents> SolidColorContents::Make(const Path& path,
std::optional<Color> SolidColorContents::AsBackgroundColor(
const Entity& entity,
ISize target_size) const {
if (!(GetColor().IsOpaque() &&
(entity.GetBlendMode() == BlendMode::kSource ||
entity.GetBlendMode() == BlendMode::kSourceOver))) {
return {};
}
Rect target_rect = Rect::MakeSize(target_size);
return GetGeometry()->CoversArea(entity.GetTransformation(), target_rect)
? GetColor()

View File

@ -124,6 +124,10 @@ bool Entity::SetInheritedOpacity(Scalar alpha) {
return true;
}
std::optional<Color> Entity::AsBackgroundColor(ISize target_size) const {
return contents_->AsBackgroundColor(*this, target_size);
}
/// @brief Returns true if the blend mode is "destructive", meaning that even
/// fully transparent source colors would result in the destination
/// getting changed.

View File

@ -92,6 +92,8 @@ class Entity {
bool SetInheritedOpacity(Scalar alpha);
std::optional<Color> AsBackgroundColor(ISize target_size) const;
private:
Matrix transformation_;
std::shared_ptr<Contents> contents_;

View File

@ -37,13 +37,13 @@
namespace impeller {
namespace {
std::optional<Color> AsBackgroundColor(const EntityPass::Element& element,
ISize target_size) {
std::tuple<std::optional<Color>, BlendMode> ElementAsBackgroundColor(
const EntityPass::Element& element,
ISize target_size) {
if (const Entity* entity = std::get_if<Entity>(&element)) {
std::optional<Color> entity_color =
entity->GetContents()->AsBackgroundColor(*entity, target_size);
std::optional<Color> entity_color = entity->AsBackgroundColor(target_size);
if (entity_color.has_value()) {
return entity_color.value();
return {entity_color.value(), entity->GetBlendMode()};
}
}
return {};
@ -251,7 +251,7 @@ bool EntityPass::Render(ContentContext& renderer,
if (!supports_onscreen_backdrop_reads && reads_from_onscreen_backdrop) {
auto offscreen_target = CreateRenderTarget(
renderer, root_render_target.GetRenderTargetSize(), true,
GetClearColor(render_target.GetRenderTargetSize()).Premultiply());
GetClearColor(render_target.GetRenderTargetSize()));
if (!OnRender(renderer, // renderer
offscreen_target.GetRenderTarget()
@ -356,8 +356,7 @@ bool EntityPass::Render(ContentContext& renderer,
}
// Set up the clear color of the root pass.
color0.clear_color =
GetClearColor(render_target.GetRenderTargetSize()).Premultiply();
color0.clear_color = GetClearColor(render_target.GetRenderTargetSize());
root_render_target.SetColorAttachment(color0, 0);
EntityPassTarget pass_target(
@ -723,13 +722,12 @@ bool EntityPass::OnRender(
for (const auto& element : elements_) {
// Skip elements that are incorporated into the clear color.
if (is_collapsing_clear_colors) {
std::optional<Color> entity_color =
AsBackgroundColor(element, root_pass_size);
auto [entity_color, _] =
ElementAsBackgroundColor(element, root_pass_size);
if (entity_color.has_value()) {
continue;
} else {
is_collapsing_clear_colors = false;
}
is_collapsing_clear_colors = false;
}
EntityResult result =
@ -922,14 +920,14 @@ void EntityPass::SetBlendMode(BlendMode blend_mode) {
Color EntityPass::GetClearColor(ISize target_size) const {
Color result = Color::BlackTransparent();
for (const Element& element : elements_) {
std::optional<Color> entity_color = AsBackgroundColor(element, target_size);
if (entity_color.has_value()) {
result = entity_color.value();
} else {
auto [entity_color, blend_mode] =
ElementAsBackgroundColor(element, target_size);
if (!entity_color.has_value()) {
break;
}
result = result.Blend(entity_color.value(), blend_mode);
}
return result;
return result.Premultiply();
}
void EntityPass::SetBackdropFilter(BackdropFilterProc proc) {

View File

@ -138,4 +138,8 @@ std::unique_ptr<Geometry> Geometry::MakeRect(Rect rect) {
return std::make_unique<RectGeometry>(rect);
}
bool Geometry::CoversArea(const Matrix& transform, const Rect& rect) const {
return false;
}
} // namespace impeller

View File

@ -90,9 +90,7 @@ class Geometry {
/// @return `true` if this geometry will completely cover all fragments in
/// `rect` when the `transform` is applied to it.
virtual bool CoversArea(const Matrix& transform, const Rect& rect) const {
return false;
}
virtual bool CoversArea(const Matrix& transform, const Rect& rect) const;
};
} // namespace impeller