Skip to content

Commit

Permalink
Reland: simplify the logic around computing subtree opacity inheritan…
Browse files Browse the repository at this point in the history
  • Loading branch information
flar authored Apr 22, 2022
1 parent e881225 commit 8ab4124
Show file tree
Hide file tree
Showing 38 changed files with 1,809 additions and 93 deletions.
6 changes: 6 additions & 0 deletions display_list/display_list_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@ void DisplayListBuilder::restore() {
}
}
}
void DisplayListBuilder::restoreToCount(int restore_count) {
FML_DCHECK(restore_count <= getSaveCount());
while (restore_count < getSaveCount()) {
restore();
}
}
void DisplayListBuilder::saveLayer(const SkRect* bounds,
const SaveLayerOptions in_options) {
SaveLayerOptions options = in_options.without_optimizations();
Expand Down
1 change: 1 addition & 0 deletions display_list/display_list_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ class DisplayListBuilder final : public virtual Dispatcher,
void saveLayer(const SkRect* bounds, const DlPaint* paint);
void restore() override;
int getSaveCount() { return layer_stack_.size(); }
void restoreToCount(int restore_count);

void translate(SkScalar tx, SkScalar ty) override;
void scale(SkScalar sx, SkScalar sy) override;
Expand Down
11 changes: 10 additions & 1 deletion flow/layers/clip_path_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ namespace flutter {
ClipPathLayer::ClipPathLayer(const SkPath& clip_path, Clip clip_behavior)
: clip_path_(clip_path), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
set_layer_can_inherit_opacity(true);
}

void ClipPathLayer::Diff(DiffContext* context, const Layer* old_layer) {
Expand Down Expand Up @@ -41,12 +40,22 @@ void ClipPathLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipPath(clip_path_);

// Collect inheritance information on our children in Preroll so that
// we can pass it along by default.
context->subtree_can_inherit_opacity = true;

SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
if (child_paint_bounds.intersect(clip_path_bounds)) {
set_paint_bounds(child_paint_bounds);
}

// If we use a SaveLayer then we can accept opacity on behalf
// of our children and apply it in the saveLayer.
if (UsesSaveLayer()) {
context->subtree_can_inherit_opacity = true;
}

context->mutators_stack.Pop();
context->cull_rect = previous_cull_rect;
}
Expand Down
232 changes: 232 additions & 0 deletions flow/layers/clip_path_layer_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "flutter/flow/layers/clip_path_layer.h"

#include "flutter/flow/layers/opacity_layer.h"
#include "flutter/flow/testing/layer_test.h"
#include "flutter/flow/testing/mock_layer.h"
#include "flutter/fml/macros.h"
Expand Down Expand Up @@ -262,5 +263,236 @@ TEST_F(ClipPathLayerTest, Readback) {
EXPECT_TRUE(ReadbackResult(context, save_layer, reader, true));
}

TEST_F(ClipPathLayerTest, OpacityInheritance) {
auto path1 = SkPath().addRect({10, 10, 30, 30});
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
auto layer_clip = SkPath()
.addRect(SkRect::MakeLTRB(5, 5, 25, 25))
.addOval(SkRect::MakeLTRB(20, 20, 40, 50));
auto clip_path_layer =
std::make_shared<ClipPathLayer>(layer_clip, Clip::hardEdge);
clip_path_layer->Add(mock1);

// ClipRectLayer will pass through compatibility from a compatible child
PrerollContext* context = preroll_context();
context->subtree_can_inherit_opacity = false;
clip_path_layer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

auto path2 = SkPath().addRect({40, 40, 50, 50});
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
clip_path_layer->Add(mock2);

// ClipRectLayer will pass through compatibility from multiple
// non-overlapping compatible children
context->subtree_can_inherit_opacity = false;
clip_path_layer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

auto path3 = SkPath().addRect({20, 20, 40, 40});
auto mock3 = MockLayer::MakeOpacityCompatible(path3);
clip_path_layer->Add(mock3);

// ClipRectLayer will not pass through compatibility from multiple
// overlapping children even if they are individually compatible
context->subtree_can_inherit_opacity = false;
clip_path_layer->Preroll(context, SkMatrix::I());
EXPECT_FALSE(context->subtree_can_inherit_opacity);

{
// ClipRectLayer(aa with saveLayer) will always be compatible
auto clip_path_saveLayer = std::make_shared<ClipPathLayer>(
layer_clip, Clip::antiAliasWithSaveLayer);
clip_path_saveLayer->Add(mock1);
clip_path_saveLayer->Add(mock2);

// Double check first two children are compatible and non-overlapping
context->subtree_can_inherit_opacity = false;
clip_path_saveLayer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

// Now add the overlapping child and test again, should still be compatible
clip_path_saveLayer->Add(mock3);
context->subtree_can_inherit_opacity = false;
clip_path_saveLayer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);
}

// An incompatible, but non-overlapping child for the following tests
auto path4 = SkPath().addRect({60, 60, 70, 70});
auto mock4 = MockLayer::Make(path4);

{
// ClipRectLayer with incompatible child will not be compatible
auto clip_path_bad_child =
std::make_shared<ClipPathLayer>(layer_clip, Clip::hardEdge);
clip_path_bad_child->Add(mock1);
clip_path_bad_child->Add(mock2);

// Double check first two children are compatible and non-overlapping
context->subtree_can_inherit_opacity = false;
clip_path_bad_child->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

clip_path_bad_child->Add(mock4);

// The third child is non-overlapping, but not compatible so the
// TransformLayer should end up incompatible
context->subtree_can_inherit_opacity = false;
clip_path_bad_child->Preroll(context, SkMatrix::I());
EXPECT_FALSE(context->subtree_can_inherit_opacity);
}

{
// ClipRectLayer(aa with saveLayer) will always be compatible
auto clip_path_saveLayer_bad_child = std::make_shared<ClipPathLayer>(
layer_clip, Clip::antiAliasWithSaveLayer);
clip_path_saveLayer_bad_child->Add(mock1);
clip_path_saveLayer_bad_child->Add(mock2);

// Double check first two children are compatible and non-overlapping
context->subtree_can_inherit_opacity = false;
clip_path_saveLayer_bad_child->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

// Now add the incompatible child and test again, should still be compatible
clip_path_saveLayer_bad_child->Add(mock4);
context->subtree_can_inherit_opacity = false;
clip_path_saveLayer_bad_child->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);
}
}

TEST_F(ClipPathLayerTest, OpacityInheritancePainting) {
auto path1 = SkPath().addRect({10, 10, 30, 30});
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
auto path2 = SkPath().addRect({40, 40, 50, 50});
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
auto layer_clip = SkPath()
.addRect(SkRect::MakeLTRB(5, 5, 25, 25))
.addOval(SkRect::MakeLTRB(20, 20, 40, 50));
auto clip_path_layer =
std::make_shared<ClipPathLayer>(layer_clip, Clip::antiAlias);
clip_path_layer->Add(mock1);
clip_path_layer->Add(mock2);

// ClipRectLayer will pass through compatibility from multiple
// non-overlapping compatible children
PrerollContext* context = preroll_context();
context->subtree_can_inherit_opacity = false;
clip_path_layer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

int opacity_alpha = 0x7F;
SkPoint offset = SkPoint::Make(10, 10);
auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);
opacity_layer->Add(clip_path_layer);
context->subtree_can_inherit_opacity = false;
opacity_layer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(opacity_layer->children_can_accept_opacity());

auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY);
DisplayListBuilder expected_builder;
/* OpacityLayer::Paint() */ {
expected_builder.save();
{
expected_builder.translate(offset.fX, offset.fY);
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
expected_builder.transformReset();
expected_builder.transform(opacity_integer_transform);
#endif
/* ClipRectLayer::Paint() */ {
expected_builder.save();
expected_builder.clipPath(layer_clip, SkClipOp::kIntersect, true);
/* child layer1 paint */ {
expected_builder.setColor(opacity_alpha << 24);
expected_builder.saveLayer(&path1.getBounds(), true);
{
expected_builder.setColor(0xFF000000);
expected_builder.drawPath(path1);
}
expected_builder.restore();
}
/* child layer2 paint */ {
expected_builder.setColor(opacity_alpha << 24);
expected_builder.saveLayer(&path2.getBounds(), true);
{
expected_builder.setColor(0xFF000000);
expected_builder.drawPath(path2);
}
expected_builder.restore();
}
expected_builder.restore();
}
}
expected_builder.restore();
}

opacity_layer->Paint(display_list_paint_context());
EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list()));
}

TEST_F(ClipPathLayerTest, OpacityInheritanceSaveLayerPainting) {
auto path1 = SkPath().addRect({10, 10, 30, 30});
auto mock1 = MockLayer::MakeOpacityCompatible(path1);
auto path2 = SkPath().addRect({20, 20, 40, 40});
auto mock2 = MockLayer::MakeOpacityCompatible(path2);
auto children_bounds = path1.getBounds();
children_bounds.join(path2.getBounds());
auto layer_clip = SkPath()
.addRect(SkRect::MakeLTRB(5, 5, 25, 25))
.addOval(SkRect::MakeLTRB(20, 20, 40, 50));
auto clip_path_layer =
std::make_shared<ClipPathLayer>(layer_clip, Clip::antiAliasWithSaveLayer);
clip_path_layer->Add(mock1);
clip_path_layer->Add(mock2);

// ClipRectLayer will pass through compatibility from multiple
// non-overlapping compatible children
PrerollContext* context = preroll_context();
context->subtree_can_inherit_opacity = false;
clip_path_layer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(context->subtree_can_inherit_opacity);

int opacity_alpha = 0x7F;
SkPoint offset = SkPoint::Make(10, 10);
auto opacity_layer = std::make_shared<OpacityLayer>(opacity_alpha, offset);
opacity_layer->Add(clip_path_layer);
context->subtree_can_inherit_opacity = false;
opacity_layer->Preroll(context, SkMatrix::I());
EXPECT_TRUE(opacity_layer->children_can_accept_opacity());

auto opacity_integer_transform = SkM44::Translate(offset.fX, offset.fY);
DisplayListBuilder expected_builder;
/* OpacityLayer::Paint() */ {
expected_builder.save();
{
expected_builder.translate(offset.fX, offset.fY);
#ifndef SUPPORT_FRACTIONAL_TRANSLATION
expected_builder.transformReset();
expected_builder.transform(opacity_integer_transform);
#endif
/* ClipRectLayer::Paint() */ {
expected_builder.save();
expected_builder.clipPath(layer_clip, SkClipOp::kIntersect, true);
expected_builder.setColor(opacity_alpha << 24);
expected_builder.saveLayer(&children_bounds, true);
/* child layer1 paint */ {
expected_builder.setColor(0xFF000000);
expected_builder.drawPath(path1);
}
/* child layer2 paint */ { //
expected_builder.drawPath(path2);
}
expected_builder.restore();
}
}
expected_builder.restore();
}

opacity_layer->Paint(display_list_paint_context());
EXPECT_TRUE(DisplayListsEQ_Verbose(expected_builder.Build(), display_list()));
}

} // namespace testing
} // namespace flutter
11 changes: 10 additions & 1 deletion flow/layers/clip_rect_layer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ namespace flutter {
ClipRectLayer::ClipRectLayer(const SkRect& clip_rect, Clip clip_behavior)
: clip_rect_(clip_rect), clip_behavior_(clip_behavior) {
FML_DCHECK(clip_behavior != Clip::none);
set_layer_can_inherit_opacity(true);
}

void ClipRectLayer::Diff(DiffContext* context, const Layer* old_layer) {
Expand Down Expand Up @@ -40,12 +39,22 @@ void ClipRectLayer::Preroll(PrerollContext* context, const SkMatrix& matrix) {
Layer::AutoPrerollSaveLayerState::Create(context, UsesSaveLayer());
context->mutators_stack.PushClipRect(clip_rect_);

// Collect inheritance information on our children in Preroll so that
// we can pass it along by default.
context->subtree_can_inherit_opacity = true;

SkRect child_paint_bounds = SkRect::MakeEmpty();
PrerollChildren(context, matrix, &child_paint_bounds);
if (child_paint_bounds.intersect(clip_rect_)) {
set_paint_bounds(child_paint_bounds);
}

// If we use a SaveLayer then we can accept opacity on behalf
// of our children and apply it in the saveLayer.
if (UsesSaveLayer()) {
context->subtree_can_inherit_opacity = true;
}

context->mutators_stack.Pop();
context->cull_rect = previous_cull_rect;
}
Expand Down
Loading

0 comments on commit 8ab4124

Please sign in to comment.