Skip to content

Commit

Permalink
[DisplayList] cull clip operations that can be trivially and safely i…
Browse files Browse the repository at this point in the history
…gnored (#53367)

This mechanism pulls some clip-reduction logic from Impeller Entities up into DisplayListBuilder to simplify the work that Impeller has to do and also amortize the work if the DisplayList is reused from frame to frame. Since the DisplayList does not have access to information such as surface sizes, there will still be cases that Impeller can catch that must be conservatively included by the recording process in the Builder, so this mechanism does not replace the same code in Impeller, it merely catches some cases earlier, while recording widget output, to avoid the same work on every frame down in Impeller.

In this first pass only the most conservative and straightforward cases are handled - a clip on a layer which has a previous clip that the new clip fully contains (and which was not already restored).

This limited approach is already enough to eliminate a couple of clip operations in the layout of many of the pages in the `new_gallery` benchmarks.
  • Loading branch information
flar committed Jun 13, 2024
1 parent ad30b91 commit 337f2fd
Show file tree
Hide file tree
Showing 10 changed files with 903 additions and 40 deletions.
346 changes: 345 additions & 1 deletion display_list/display_list_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ TEST_F(DisplayListTest, SingleOpDisplayListsRecapturedAreEqual) {
sk_sp<DisplayList> copy = copy_builder.Build();
auto desc =
group.op_name + "(variant " + std::to_string(i + 1) + " == copy)";
DisplayListsEQ_Verbose(dl, copy);
ASSERT_EQ(copy->op_count(false), dl->op_count(false)) << desc;
ASSERT_EQ(copy->bytes(false), dl->bytes(false)) << desc;
ASSERT_EQ(copy->op_count(true), dl->op_count(true)) << desc;
Expand Down Expand Up @@ -3383,7 +3384,7 @@ TEST_F(DisplayListTest, NopOperationsOmittedFromRecords) {
}

TEST_F(DisplayListTest, ImpellerPathPreferenceIsHonored) {
class Tester : virtual public DlOpReceiver,
class Tester : public virtual DlOpReceiver,
public IgnoreClipDispatchHelper,
public IgnoreDrawDispatchHelper,
public IgnoreAttributeDispatchHelper,
Expand Down Expand Up @@ -3962,6 +3963,10 @@ class DepthExpector : public virtual DlOpReceiver,
index_++;
}

bool all_depths_checked() const {
return index_ == depth_expectations_.size();
}

private:
size_t index_ = 0;
std::vector<uint32_t> depth_expectations_;
Expand Down Expand Up @@ -4331,5 +4336,344 @@ TEST_F(DisplayListTest, DrawDisplayListForwardsBackdropFlag) {
EXPECT_TRUE(parent_dl->root_has_backdrop_filter());
}

#define CLIP_EXPECTOR(name) ClipExpector name(__FILE__, __LINE__)

class ClipExpector : public virtual DlOpReceiver,
virtual IgnoreAttributeDispatchHelper,
virtual IgnoreTransformDispatchHelper,
virtual IgnoreDrawDispatchHelper {
public:
struct Expectation {
std::variant<SkRect, SkRRect, SkPath> shape;
ClipOp clip_op;
bool is_aa;

std::string shape_name() {
switch (shape.index()) {
case 0:
return "SkRect";
case 1:
return "SkRRect";
case 2:
return "SkPath";
default:
return "Unknown";
}
}
};

// file and line supplied automatically from CLIP_EXPECTOR macro
explicit ClipExpector(const std::string& file, int line)
: file_(file), line_(line) {}

~ClipExpector() { //
EXPECT_EQ(index_, clip_expectations_.size()) << label();
}

ClipExpector& addExpectation(const SkRect& rect,
ClipOp clip_op = ClipOp::kIntersect,
bool is_aa = false) {
clip_expectations_.push_back({
.shape = rect,
.clip_op = clip_op,
.is_aa = is_aa,
});
return *this;
}

ClipExpector& addExpectation(const SkRRect& rrect,
ClipOp clip_op = ClipOp::kIntersect,
bool is_aa = false) {
clip_expectations_.push_back({
.shape = rrect,
.clip_op = clip_op,
.is_aa = is_aa,
});
return *this;
}

ClipExpector& addExpectation(const SkPath& path,
ClipOp clip_op = ClipOp::kIntersect,
bool is_aa = false) {
clip_expectations_.push_back({
.shape = path,
.clip_op = clip_op,
.is_aa = is_aa,
});
return *this;
}

void clipRect(const SkRect& rect,
DlCanvas::ClipOp clip_op,
bool is_aa) override {
check(rect, clip_op, is_aa);
}
void clipRRect(const SkRRect& rrect,
DlCanvas::ClipOp clip_op,
bool is_aa) override {
check(rrect, clip_op, is_aa);
}
void clipPath(const SkPath& path,
DlCanvas::ClipOp clip_op,
bool is_aa) override {
check(path, clip_op, is_aa);
}

private:
size_t index_ = 0;
std::vector<Expectation> clip_expectations_;

template <typename T>
void check(T shape, ClipOp clip_op, bool is_aa) {
ASSERT_LT(index_, clip_expectations_.size()) << label();
auto expected = clip_expectations_[index_];
EXPECT_EQ(expected.clip_op, clip_op) << label();
EXPECT_EQ(expected.is_aa, is_aa) << label();
if (!std::holds_alternative<T>(expected.shape)) {
EXPECT_TRUE(std::holds_alternative<T>(expected.shape))
<< label() << ", expected type: " << expected.shape_name();
} else {
EXPECT_EQ(std::get<T>(expected.shape), shape) << label();
}
index_++;
}

const std::string file_;
const int line_;

std::string label() {
return "at index " + std::to_string(index_) + //
", from " + file_ + //
":" + std::to_string(line_);
}
};

TEST_F(DisplayListTest, ClipRectCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipRect(clip.makeOutset(1.0f, 1.0f), ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRectNonCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto smaller_clip = clip.makeInset(1.0f, 1.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipRect(smaller_clip, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
expector.addExpectation(smaller_clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRectNestedCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto larger_clip = clip.makeOutset(1.0f, 1.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.Save();
cull_builder.ClipRect(larger_clip, ClipOp::kIntersect, false);
cull_builder.Restore();
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRectNestedNonCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto larger_clip = clip.makeOutset(1.0f, 1.0f);

DisplayListBuilder cull_builder;
cull_builder.Save();
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.Restore();
// Should not be culled because we have restored the prior clip
cull_builder.ClipRect(larger_clip, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
expector.addExpectation(larger_clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRectNestedCullingComplex) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto smaller_clip = clip.makeInset(1.0f, 1.0f);
auto smallest_clip = clip.makeInset(2.0f, 2.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.Save();
cull_builder.ClipRect(smallest_clip, ClipOp::kIntersect, false);
cull_builder.ClipRect(smaller_clip, ClipOp::kIntersect, false);
cull_builder.Restore();
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
expector.addExpectation(smallest_clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRectNestedNonCullingComplex) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto smaller_clip = clip.makeInset(1.0f, 1.0f);
auto smallest_clip = clip.makeInset(2.0f, 2.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.Save();
cull_builder.ClipRect(smallest_clip, ClipOp::kIntersect, false);
cull_builder.Restore();
// Would not be culled if it was inside the clip
cull_builder.ClipRect(smaller_clip, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
expector.addExpectation(smallest_clip, ClipOp::kIntersect, false);
expector.addExpectation(smaller_clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRRectCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto rrect = SkRRect::MakeRectXY(clip.makeOutset(2.0f, 2.0f), 2.0f, 2.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipRRect(rrect, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipRRectNonCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto rrect = SkRRect::MakeRectXY(clip.makeOutset(2.0f, 2.0f), 12.0f, 12.0f);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipRRect(rrect, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
expector.addExpectation(rrect, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipPathNonCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
SkPath path;
path.moveTo(0.0f, 0.0f);
path.lineTo(1000.0f, 0.0f);
path.lineTo(0.0f, 1000.0f);
path.close();

// Double checking that the path does indeed contain the clip. But,
// sadly, the Builder will not check paths for coverage to this level
// of detail. (In particular, path containment of the corners is not
// authoritative of true containment, but we know in this case that
// a triangle contains a rect if it contains all 4 corners...)
ASSERT_TRUE(path.contains(clip.fLeft, clip.fTop));
ASSERT_TRUE(path.contains(clip.fRight, clip.fTop));
ASSERT_TRUE(path.contains(clip.fRight, clip.fBottom));
ASSERT_TRUE(path.contains(clip.fLeft, clip.fBottom));

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipPath(path, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
expector.addExpectation(path, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipPathRectCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
SkPath path;
path.addRect(clip.makeOutset(1.0f, 1.0f));

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipPath(path, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipPathRectNonCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto smaller_clip = clip.makeInset(1.0f, 1.0f);
SkPath path;
path.addRect(smaller_clip);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipPath(path, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
// Builder will not cull this clip, but it will turn it into a ClipRect
expector.addExpectation(smaller_clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipPathRRectCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto rrect = SkRRect::MakeRectXY(clip.makeOutset(2.0f, 2.0f), 2.0f, 2.0f);
SkPath path;
path.addRRect(rrect);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipPath(path, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

TEST_F(DisplayListTest, ClipPathRRectNonCulling) {
auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
auto rrect = SkRRect::MakeRectXY(clip.makeOutset(2.0f, 2.0f), 12.0f, 12.0f);
SkPath path;
path.addRRect(rrect);

DisplayListBuilder cull_builder;
cull_builder.ClipRect(clip, ClipOp::kIntersect, false);
cull_builder.ClipPath(path, ClipOp::kIntersect, false);
auto cull_dl = cull_builder.Build();

CLIP_EXPECTOR(expector);
expector.addExpectation(clip, ClipOp::kIntersect, false);
// Builder will not cull this clip, but it will turn it into a ClipRRect
expector.addExpectation(rrect, ClipOp::kIntersect, false);
cull_dl->Dispatch(expector);
}

} // namespace testing
} // namespace flutter

0 comments on commit 337f2fd

Please sign in to comment.