Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading