diff --git a/engine/src/flutter/ci/licenses_golden/excluded_files b/engine/src/flutter/ci/licenses_golden/excluded_files index c33ed59bac3..3b23a057c82 100644 --- a/engine/src/flutter/ci/licenses_golden/excluded_files +++ b/engine/src/flutter/ci/licenses_golden/excluded_files @@ -140,6 +140,7 @@ ../../../flutter/impeller/compiler/switches_unittests.cc ../../../flutter/impeller/core/allocator_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_atlas_unittests.cc +../../../flutter/impeller/display_list/aiks_dl_basic_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_clip_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_gradient_unittests.cc ../../../flutter/impeller/display_list/aiks_dl_opacity_unittests.cc diff --git a/engine/src/flutter/display_list/display_list.h b/engine/src/flutter/display_list/display_list.h index 35c19ef9d4a..bb67c435cc6 100644 --- a/engine/src/flutter/display_list/display_list.h +++ b/engine/src/flutter/display_list/display_list.h @@ -98,9 +98,11 @@ namespace flutter { V(TransformReset) \ \ V(ClipIntersectRect) \ + V(ClipIntersectOval) \ V(ClipIntersectRRect) \ V(ClipIntersectPath) \ V(ClipDifferenceRect) \ + V(ClipDifferenceOval) \ V(ClipDifferenceRRect) \ V(ClipDifferencePath) \ \ diff --git a/engine/src/flutter/display_list/display_list_unittests.cc b/engine/src/flutter/display_list/display_list_unittests.cc index 635d38af7c8..d25f729ac3c 100644 --- a/engine/src/flutter/display_list/display_list_unittests.cc +++ b/engine/src/flutter/display_list/display_list_unittests.cc @@ -4332,36 +4332,66 @@ TEST_F(DisplayListTest, DrawDisplayListForwardsBackdropFlag) { #define CLIP_EXPECTOR(name) ClipExpector name(__FILE__, __LINE__) +struct ClipExpectation { + std::variant shape; + bool is_oval; + ClipOp clip_op; + bool is_aa; + + std::string shape_name() { + switch (shape.index()) { + case 0: + return is_oval ? "SkOval" : "SkRect"; + case 1: + return "SkRRect"; + case 2: + return "SkPath"; + default: + return "Unknown"; + } + } +}; + +::std::ostream& operator<<(::std::ostream& os, const ClipExpectation& expect) { + os << "Expectation("; + switch (expect.shape.index()) { + case 0: + os << std::get(expect.shape); + if (expect.is_oval) { + os << " (oval)"; + } + break; + case 1: + os << std::get(expect.shape); + break; + case 2: + os << std::get(expect.shape); + break; + case 3: + os << "Unknown"; + } + os << ", " << expect.clip_op; + os << ", " << expect.is_aa; + os << ")"; + return os; +} + class ClipExpector : public virtual DlOpReceiver, virtual IgnoreAttributeDispatchHelper, virtual IgnoreTransformDispatchHelper, virtual IgnoreDrawDispatchHelper { public: - struct Expectation { - std::variant 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(); + while (index_ < clip_expectations_.size()) { + auto expect = clip_expectations_[index_]; + FML_LOG(ERROR) << "leftover clip shape[" << index_ << "] = " << expect; + index_++; + } } ClipExpector& addExpectation(const SkRect& rect, @@ -4369,6 +4399,19 @@ class ClipExpector : public virtual DlOpReceiver, bool is_aa = false) { clip_expectations_.push_back({ .shape = rect, + .is_oval = false, + .clip_op = clip_op, + .is_aa = is_aa, + }); + return *this; + } + + ClipExpector& addOvalExpectation(const SkRect& rect, + ClipOp clip_op = ClipOp::kIntersect, + bool is_aa = false) { + clip_expectations_.push_back({ + .shape = rect, + .is_oval = true, .clip_op = clip_op, .is_aa = is_aa, }); @@ -4380,6 +4423,7 @@ class ClipExpector : public virtual DlOpReceiver, bool is_aa = false) { clip_expectations_.push_back({ .shape = rrect, + .is_oval = false, .clip_op = clip_op, .is_aa = is_aa, }); @@ -4391,6 +4435,7 @@ class ClipExpector : public virtual DlOpReceiver, bool is_aa = false) { clip_expectations_.push_back({ .shape = path, + .is_oval = false, .clip_op = clip_op, .is_aa = is_aa, }); @@ -4402,6 +4447,11 @@ class ClipExpector : public virtual DlOpReceiver, bool is_aa) override { check(rect, clip_op, is_aa); } + void clipOval(const SkRect& bounds, + DlCanvas::ClipOp clip_op, + bool is_aa) override { + check(bounds, clip_op, is_aa, true); + } void clipRRect(const SkRRect& rrect, DlCanvas::ClipOp clip_op, bool is_aa) override { @@ -4415,22 +4465,23 @@ class ClipExpector : public virtual DlOpReceiver, private: size_t index_ = 0; - std::vector clip_expectations_; + std::vector clip_expectations_; template - void check(T shape, ClipOp clip_op, bool is_aa) { + void check(T shape, ClipOp clip_op, bool is_aa, bool is_oval = false) { ASSERT_LT(index_, clip_expectations_.size()) << label() << std::endl - << "extra clip shape = " << shape; + << "extra clip shape = " << shape << (is_oval ? " (oval)" : ""); 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(expected.shape)) { EXPECT_TRUE(std::holds_alternative(expected.shape)) << label() << ", expected type: " << expected.shape_name(); } else { EXPECT_EQ(std::get(expected.shape), shape) << label(); } + EXPECT_EQ(expected.is_oval, is_oval) << label(); + EXPECT_EQ(expected.clip_op, clip_op) << label(); + EXPECT_EQ(expected.is_aa, is_aa) << label(); index_++; } @@ -4570,9 +4621,47 @@ TEST_F(DisplayListTest, ClipRectNestedNonCullingComplex) { cull_dl->Dispatch(expector); } +TEST_F(DisplayListTest, ClipOvalCulling) { + auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + // A 10x10 rectangle extends 5x5 from the center to each corner. To have + // an oval that encompasses that rectangle, the radius must be at least + // length(5, 5), or 7.071+ so we expand the radius 5 square clip by 2.072 + // on each side to barely contain the corners of the square. + auto encompassing_oval = clip.makeOutset(2.072f, 2.072f); + + DisplayListBuilder cull_builder; + cull_builder.ClipRect(clip, ClipOp::kIntersect, false); + cull_builder.ClipOval(encompassing_oval, 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, ClipOvalNonCulling) { + auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + // A 10x10 rectangle extends 5x5 from the center to each corner. To have + // an oval that encompasses that rectangle, the radius must be at least + // length(5, 5), or 7.071+ so we expand the radius 5 square clip by 2.072 + // on each side to barely exclude the corners of the square. + auto non_encompassing_oval = clip.makeOutset(2.071f, 2.071f); + + DisplayListBuilder cull_builder; + cull_builder.ClipRect(clip, ClipOp::kIntersect, false); + cull_builder.ClipOval(non_encompassing_oval, ClipOp::kIntersect, false); + auto cull_dl = cull_builder.Build(); + + CLIP_EXPECTOR(expector); + expector.addExpectation(clip, ClipOp::kIntersect, false); + expector.addOvalExpectation(non_encompassing_oval, 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); + ASSERT_FALSE(rrect.isOval()); DisplayListBuilder cull_builder; cull_builder.ClipRect(clip, ClipOp::kIntersect, false); @@ -4586,7 +4675,8 @@ TEST_F(DisplayListTest, ClipRRectCulling) { 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); + auto rrect = SkRRect::MakeRectXY(clip.makeOutset(1.0f, 1.0f), 4.0f, 4.0f); + ASSERT_FALSE(rrect.isOval()); DisplayListBuilder cull_builder; cull_builder.ClipRect(clip, ClipOp::kIntersect, false); @@ -4661,9 +4751,55 @@ TEST_F(DisplayListTest, ClipPathRectNonCulling) { cull_dl->Dispatch(expector); } +TEST_F(DisplayListTest, ClipPathOvalCulling) { + auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + // A 10x10 rectangle extends 5x5 from the center to each corner. To have + // an oval that encompasses that rectangle, the radius must be at least + // length(5, 5), or 7.071+ so we expand the radius 5 square clip by 2.072 + // on each side to barely contain the corners of the square. + auto encompassing_oval = clip.makeOutset(2.072f, 2.072f); + SkPath path; + path.addOval(encompassing_oval); + + 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, ClipPathOvalNonCulling) { + auto clip = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + // A 10x10 rectangle extends 5x5 from the center to each corner. To have + // an oval that encompasses that rectangle, the radius must be at least + // length(5, 5), or 7.071+ so we expand the radius 5 square clip by 2.072 + // on each side to barely exclude the corners of the square. + auto non_encompassing_oval = clip.makeOutset(2.071f, 2.071f); + SkPath path; + path.addOval(non_encompassing_oval); + SkRRect rrect; + rrect.setOval(non_encompassing_oval); + + 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 + // Eventually it should turn it into a ClipOval + expector.addExpectation(rrect, 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); + ASSERT_FALSE(rrect.isOval()); SkPath path; path.addRRect(rrect); @@ -4679,7 +4815,8 @@ TEST_F(DisplayListTest, ClipPathRRectCulling) { 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); + auto rrect = SkRRect::MakeRectXY(clip.makeOutset(1.0f, 1.0f), 4.0f, 4.0f); + ASSERT_FALSE(rrect.isOval()); SkPath path; path.addRRect(rrect); @@ -4724,5 +4861,323 @@ TEST_F(DisplayListTest, RecordLargeVertices) { } } +TEST_F(DisplayListTest, DrawRectRRectPromoteToDrawRect) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + + DisplayListBuilder builder; + builder.DrawRRect(SkRRect::MakeRect(rect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawRect(rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, DrawOvalRRectPromoteToDrawOval) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + + DisplayListBuilder builder; + builder.DrawRRect(SkRRect::MakeOval(rect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawOval(rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, DrawRectPathPromoteToDrawRect) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + + DisplayListBuilder builder; + builder.DrawPath(SkPath::Rect(rect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawRect(rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, DrawOvalPathPromoteToDrawOval) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + + DisplayListBuilder builder; + builder.DrawPath(SkPath::Oval(rect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawOval(rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, DrawRRectPathPromoteToDrawRRect) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect rrect = SkRRect::MakeRectXY(rect, 2.0f, 2.0f); + + DisplayListBuilder builder; + builder.DrawPath(SkPath::RRect(rrect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawRRect(rrect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, DrawRectRRectPathPromoteToDrawRect) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect rrect = SkRRect::MakeRect(rect); + + DisplayListBuilder builder; + builder.DrawPath(SkPath::RRect(rrect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawRect(rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, DrawOvalRRectPathPromoteToDrawOval) { + SkRect rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect rrect = SkRRect::MakeOval(rect); + + DisplayListBuilder builder; + builder.DrawPath(SkPath::RRect(rrect), DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.DrawOval(rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipRectRRectPromoteToClipRect) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + + DisplayListBuilder builder; + builder.ClipRRect(SkRRect::MakeRect(clip_rect), ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipRect(clip_rect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipOvalRRectPromoteToClipOval) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + + DisplayListBuilder builder; + builder.ClipRRect(SkRRect::MakeOval(clip_rect), ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipOval(clip_rect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipRectPathPromoteToClipRect) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::Rect(clip_rect); + ASSERT_TRUE(clip_path.isRect(nullptr)); + ASSERT_FALSE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipRect(clip_rect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipOvalPathPromoteToClipOval) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::Oval(clip_rect); + ASSERT_TRUE(clip_path.isOval(nullptr)); + ASSERT_FALSE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipOval(clip_rect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipRRectPathPromoteToClipRRect) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 2.0f, 2.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::RRect(clip_rrect); + ASSERT_TRUE(clip_path.isRRect(nullptr)); + ASSERT_FALSE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipRRect(clip_rrect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipRectInversePathNoPromoteToClipRect) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::Rect(clip_rect); + clip_path.toggleInverseFillType(); + ASSERT_TRUE(clip_path.isRect(nullptr)); + ASSERT_TRUE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + // Non-promoting tests can't use DL comparisons to verify that the + // promotion isn't happening because the test and expectation builders + // would both apply or not apply the same optimization. For this case + // we use the CLIP_EXPECTOR instead to see exactly which type of + // clip operation was recorded. + CLIP_EXPECTOR(expector); + expector.addExpectation(clip_path, ClipOp::kIntersect, false); + dl->Dispatch(expector); +} + +TEST_F(DisplayListTest, ClipOvalInversePathNoPromoteToClipOval) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::Oval(clip_rect); + clip_path.toggleInverseFillType(); + ASSERT_TRUE(clip_path.isOval(nullptr)); + ASSERT_TRUE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + // Non-promoting tests can't use DL comparisons to verify that the + // promotion isn't happening because the test and expectation builders + // would both apply or not apply the same optimization. For this case + // we use the CLIP_EXPECTOR instead to see exactly which type of + // clip operation was recorded. + CLIP_EXPECTOR(expector); + expector.addExpectation(clip_path, ClipOp::kIntersect, false); + dl->Dispatch(expector); +} + +TEST_F(DisplayListTest, ClipRRectInversePathNoPromoteToClipRRect) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect clip_rrect = SkRRect::MakeRectXY(clip_rect, 2.0f, 2.0f); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::RRect(clip_rrect); + clip_path.toggleInverseFillType(); + ASSERT_TRUE(clip_path.isRRect(nullptr)); + ASSERT_TRUE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + // Non-promoting tests can't use DL comparisons to verify that the + // promotion isn't happening because the test and expectation builders + // would both apply or not apply the same optimization. For this case + // we use the CLIP_EXPECTOR instead to see exactly which type of + // clip operation was recorded. + CLIP_EXPECTOR(expector); + expector.addExpectation(clip_path, ClipOp::kIntersect, false); + dl->Dispatch(expector); +} + +TEST_F(DisplayListTest, ClipRectRRectPathPromoteToClipRect) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect clip_rrect = SkRRect::MakeRect(clip_rect); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::RRect(clip_rrect); + ASSERT_TRUE(clip_path.isRRect(nullptr)); + ASSERT_FALSE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipRect(clip_rect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + +TEST_F(DisplayListTest, ClipOvalRRectPathPromoteToClipOval) { + SkRect clip_rect = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f); + SkRRect clip_rrect = SkRRect::MakeOval(clip_rect); + SkRect draw_rect = clip_rect.makeOutset(2.0f, 2.0f); + SkPath clip_path = SkPath::RRect(clip_rrect); + ASSERT_TRUE(clip_path.isRRect(nullptr)); + ASSERT_FALSE(clip_path.isInverseFillType()); + + DisplayListBuilder builder; + builder.ClipPath(clip_path, ClipOp::kIntersect, false); + // Include a rendering op in case DlBuilder ever removes unneeded clips + builder.DrawRect(draw_rect, DlPaint()); + auto dl = builder.Build(); + + DisplayListBuilder expected; + expected.ClipOval(clip_rect, ClipOp::kIntersect, false); + expected.DrawRect(draw_rect, DlPaint()); + auto expect_dl = expected.Build(); + + DisplayListsEQ_Verbose(dl, expect_dl); +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/display_list/dl_builder.cc b/engine/src/flutter/display_list/dl_builder.cc index 98772c0d7a3..748d07ebfe0 100644 --- a/engine/src/flutter/display_list/dl_builder.cc +++ b/engine/src/flutter/display_list/dl_builder.cc @@ -963,6 +963,38 @@ void DisplayListBuilder::ClipRect(const SkRect& rect, break; } } +void DisplayListBuilder::ClipOval(const SkRect& bounds, + ClipOp clip_op, + bool is_aa) { + if (!bounds.isFinite()) { + return; + } + if (current_info().is_nop) { + return; + } + if (current_info().has_valid_clip && + clip_op == DlCanvas::ClipOp::kIntersect && + layer_local_state().oval_covers_cull(bounds)) { + return; + } + global_state().clipOval(bounds, clip_op, is_aa); + layer_local_state().clipOval(bounds, clip_op, is_aa); + if (global_state().is_cull_rect_empty() || + layer_local_state().is_cull_rect_empty()) { + current_info().is_nop = true; + return; + } + current_info().has_valid_clip = true; + checkForDeferredSave(); + switch (clip_op) { + case ClipOp::kIntersect: + Push(0, bounds, is_aa); + break; + case ClipOp::kDifference: + Push(0, bounds, is_aa); + break; + } +} void DisplayListBuilder::ClipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) { diff --git a/engine/src/flutter/display_list/dl_builder.h b/engine/src/flutter/display_list/dl_builder.h index b1a458f68ee..dc75e2d37f6 100644 --- a/engine/src/flutter/display_list/dl_builder.h +++ b/engine/src/flutter/display_list/dl_builder.h @@ -117,6 +117,10 @@ class DisplayListBuilder final : public virtual DlCanvas, ClipOp clip_op = ClipOp::kIntersect, bool is_aa = false) override; // |DlCanvas| + void ClipOval(const SkRect& bounds, + ClipOp clip_op = ClipOp::kIntersect, + bool is_aa = false) override; + // |DlCanvas| void ClipRRect(const SkRRect& rrect, ClipOp clip_op = ClipOp::kIntersect, bool is_aa = false) override; @@ -400,6 +404,10 @@ class DisplayListBuilder final : public virtual DlCanvas, ClipRect(rect, clip_op, is_aa); } // |DlOpReceiver| + void clipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) override { + ClipOval(bounds, clip_op, is_aa); + } + // |DlOpReceiver| void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override { ClipRRect(rrect, clip_op, is_aa); } diff --git a/engine/src/flutter/display_list/dl_canvas.h b/engine/src/flutter/display_list/dl_canvas.h index ab505777aea..8fa4ad923f3 100644 --- a/engine/src/flutter/display_list/dl_canvas.h +++ b/engine/src/flutter/display_list/dl_canvas.h @@ -105,6 +105,9 @@ class DlCanvas { virtual void ClipRect(const SkRect& rect, ClipOp clip_op = ClipOp::kIntersect, bool is_aa = false) = 0; + virtual void ClipOval(const SkRect& bounds, + ClipOp clip_op = ClipOp::kIntersect, + bool is_aa = false) = 0; virtual void ClipRRect(const SkRRect& rrect, ClipOp clip_op = ClipOp::kIntersect, bool is_aa = false) = 0; diff --git a/engine/src/flutter/display_list/dl_color.h b/engine/src/flutter/display_list/dl_color.h index 0338169077a..9a823f81389 100644 --- a/engine/src/flutter/display_list/dl_color.h +++ b/engine/src/flutter/display_list/dl_color.h @@ -5,7 +5,7 @@ #ifndef FLUTTER_DISPLAY_LIST_DL_COLOR_H_ #define FLUTTER_DISPLAY_LIST_DL_COLOR_H_ -#include "third_party/skia/include/core/SkScalar.h" +#include "flutter/display_list/geometry/dl_geometry_types.h" namespace flutter { @@ -14,17 +14,29 @@ struct DlColor { constexpr DlColor() : argb_(0xFF000000) {} constexpr explicit DlColor(uint32_t argb) : argb_(argb) {} - /// @brief Construct a 32 bit color from a floating point A, R, G, and B color - /// channel. - constexpr explicit DlColor(SkScalar a, SkScalar r, SkScalar g, SkScalar b) - : argb_(static_cast(std::round(a * 255)) << 24 | // - static_cast(std::round(r * 255)) << 16 | // - static_cast(std::round(g * 255)) << 8 | // - static_cast(std::round(b * 255)) << 0 // - ) {} + /// @brief Construct a 32 bit color from floating point R, G, B, and A color + /// channels. + static constexpr DlColor RGBA(DlScalar r, + DlScalar g, + DlScalar b, + DlScalar a) { + return ARGB(a, r, g, b); + } - static constexpr uint8_t toAlpha(SkScalar opacity) { return toC(opacity); } - static constexpr SkScalar toOpacity(uint8_t alpha) { return toF(alpha); } + /// @brief Construct a 32 bit color from floating point A, R, G, and B color + /// channels. + static constexpr DlColor ARGB(DlScalar a, + DlScalar r, + DlScalar g, + DlScalar b) { + return DlColor(toC(a) << 24 | // + toC(r) << 16 | // + toC(g) << 8 | // + toC(b)); + } + + static constexpr uint8_t toAlpha(DlScalar opacity) { return toC(opacity); } + static constexpr DlScalar toOpacity(uint8_t alpha) { return toF(alpha); } // clang-format off static constexpr DlColor kTransparent() {return DlColor(0x00000000);}; @@ -54,16 +66,16 @@ struct DlColor { constexpr int getGreen() const { return (argb_ >> 8) & 0xFF; } constexpr int getBlue() const { return argb_ & 0xFF; } - constexpr float getAlphaF() const { return toF(getAlpha()); } - constexpr float getRedF() const { return toF(getRed()); } - constexpr float getGreenF() const { return toF(getGreen()); } - constexpr float getBlueF() const { return toF(getBlue()); } + constexpr DlScalar getAlphaF() const { return toF(getAlpha()); } + constexpr DlScalar getRedF() const { return toF(getRed()); } + constexpr DlScalar getGreenF() const { return toF(getGreen()); } + constexpr DlScalar getBlueF() const { return toF(getBlue()); } constexpr uint32_t premultipliedArgb() const { if (isOpaque()) { return argb_; } - float f = getAlphaF(); + DlScalar f = getAlphaF(); return (argb_ & 0xFF000000) | // toC(getRedF() * f) << 16 | // toC(getGreenF() * f) << 8 | // @@ -83,7 +95,7 @@ struct DlColor { return DlColor((argb_ & 0xFFFFFF00) | (blue << 0)); } - constexpr DlColor modulateOpacity(float opacity) const { + constexpr DlColor modulateOpacity(DlScalar opacity) const { return opacity <= 0 ? withAlpha(0) : opacity >= 1 ? *this : withAlpha(round(getAlpha() * opacity)); @@ -99,8 +111,8 @@ struct DlColor { private: uint32_t argb_; - static float toF(uint8_t comp) { return comp * (1.0f / 255); } - static uint8_t toC(float fComp) { return round(fComp * 255); } + static constexpr DlScalar toF(uint8_t comp) { return comp * (1.0f / 255); } + static constexpr uint8_t toC(DlScalar fComp) { return round(fComp * 255); } }; } // namespace flutter diff --git a/engine/src/flutter/display_list/dl_color_unittests.cc b/engine/src/flutter/display_list/dl_color_unittests.cc index 7a6c6a15ed8..31790143213 100644 --- a/engine/src/flutter/display_list/dl_color_unittests.cc +++ b/engine/src/flutter/display_list/dl_color_unittests.cc @@ -45,5 +45,190 @@ TEST(DisplayListColor, DlColorDirectlyComparesToSkColor) { EXPECT_EQ(DlColor::kBlue(), SK_ColorBLUE); } +TEST(DisplayListColor, DlColorFloatConstructor) { + EXPECT_EQ(DlColor::ARGB(1.0f, 1.0f, 1.0f, 1.0f), DlColor(0xFFFFFFFF)); + EXPECT_EQ(DlColor::ARGB(0.0f, 0.0f, 0.0f, 0.0f), DlColor(0x00000000)); + EXPECT_EQ(DlColor::ARGB(0.5f, 0.5f, 0.5f, 0.5f), DlColor(0x80808080)); + EXPECT_EQ(DlColor::ARGB(1.0f, 0.0f, 0.5f, 1.0f), DlColor(0xFF0080FF)); + + EXPECT_EQ(DlColor::RGBA(1.0f, 1.0f, 1.0f, 1.0f), DlColor(0xFFFFFFFF)); + EXPECT_EQ(DlColor::RGBA(0.0f, 0.0f, 0.0f, 0.0f), DlColor(0x00000000)); + EXPECT_EQ(DlColor::RGBA(0.5f, 0.5f, 0.5f, 0.5f), DlColor(0x80808080)); + EXPECT_EQ(DlColor::RGBA(1.0f, 0.0f, 0.5f, 1.0f), DlColor(0xFFFF0080)); +} + +TEST(DisplayListColor, DlColorComponentGetters) { + { + DlColor test(0xFFFFFFFF); + + EXPECT_EQ(test.getAlpha(), 0xFF); + EXPECT_EQ(test.getRed(), 0xFF); + EXPECT_EQ(test.getGreen(), 0xFF); + EXPECT_EQ(test.getBlue(), 0xFF); + + EXPECT_EQ(test.getAlphaF(), 1.0f); + EXPECT_EQ(test.getRedF(), 1.0f); + EXPECT_EQ(test.getGreenF(), 1.0f); + EXPECT_EQ(test.getBlueF(), 1.0f); + } + + { + DlColor test = DlColor::ARGB(1.0f, 1.0f, 1.0f, 1.0f); + + EXPECT_EQ(test.getAlpha(), 0xFF); + EXPECT_EQ(test.getRed(), 0xFF); + EXPECT_EQ(test.getGreen(), 0xFF); + EXPECT_EQ(test.getBlue(), 0xFF); + + EXPECT_EQ(test.getAlphaF(), 1.0f); + EXPECT_EQ(test.getRedF(), 1.0f); + EXPECT_EQ(test.getGreenF(), 1.0f); + EXPECT_EQ(test.getBlueF(), 1.0f); + } + + { + DlColor test(0x00000000); + + EXPECT_EQ(test.getAlpha(), 0x00); + EXPECT_EQ(test.getRed(), 0x00); + EXPECT_EQ(test.getGreen(), 0x00); + EXPECT_EQ(test.getBlue(), 0x00); + + EXPECT_EQ(test.getAlphaF(), 0.0f); + EXPECT_EQ(test.getRedF(), 0.0f); + EXPECT_EQ(test.getGreenF(), 0.0f); + EXPECT_EQ(test.getBlueF(), 0.0f); + } + + { + DlColor test = DlColor::ARGB(0.0f, 0.0f, 0.0f, 0.0f); + + EXPECT_EQ(test.getAlpha(), 0x00); + EXPECT_EQ(test.getRed(), 0x00); + EXPECT_EQ(test.getGreen(), 0x00); + EXPECT_EQ(test.getBlue(), 0x00); + + EXPECT_EQ(test.getAlphaF(), 0.0f); + EXPECT_EQ(test.getRedF(), 0.0f); + EXPECT_EQ(test.getGreenF(), 0.0f); + EXPECT_EQ(test.getBlueF(), 0.0f); + } + + { + DlColor test(0x7F7F7F7F); + + EXPECT_EQ(test.getAlpha(), 0x7F); + EXPECT_EQ(test.getRed(), 0x7F); + EXPECT_EQ(test.getGreen(), 0x7F); + EXPECT_EQ(test.getBlue(), 0x7F); + + const DlScalar half = 127.0f * (1.0f / 255.0f); + + EXPECT_EQ(test.getAlphaF(), half); + EXPECT_EQ(test.getRedF(), half); + EXPECT_EQ(test.getGreenF(), half); + EXPECT_EQ(test.getBlueF(), half); + } + + { + DlColor test = DlColor::ARGB(0.5f, 0.5f, 0.5f, 0.5f); + + EXPECT_EQ(test.getAlpha(), 0x80); + EXPECT_EQ(test.getRed(), 0x80); + EXPECT_EQ(test.getGreen(), 0x80); + EXPECT_EQ(test.getBlue(), 0x80); + + const DlScalar half = 128.0f * (1.0f / 255.0f); + + EXPECT_EQ(test.getAlphaF(), half); + EXPECT_EQ(test.getRedF(), half); + EXPECT_EQ(test.getGreenF(), half); + EXPECT_EQ(test.getBlueF(), half); + } + + { + DlColor test(0x1F2F3F4F); + + EXPECT_EQ(test.getAlpha(), 0x1F); + EXPECT_EQ(test.getRed(), 0x2F); + EXPECT_EQ(test.getGreen(), 0x3F); + EXPECT_EQ(test.getBlue(), 0x4F); + + EXPECT_EQ(test.getAlphaF(), 0x1f * (1.0f / 255.0f)); + EXPECT_EQ(test.getRedF(), 0x2f * (1.0f / 255.0f)); + EXPECT_EQ(test.getGreenF(), 0x3f * (1.0f / 255.0f)); + EXPECT_EQ(test.getBlueF(), 0x4f * (1.0f / 255.0f)); + } + + { + DlColor test = DlColor::ARGB(0.1f, 0.2f, 0.3f, 0.4f); + + EXPECT_EQ(test.getAlpha(), round(0.1f * 255)); + EXPECT_EQ(test.getRed(), round(0.2f * 255)); + EXPECT_EQ(test.getGreen(), round(0.3f * 255)); + EXPECT_EQ(test.getBlue(), round(0.4f * 255)); + + // Unfortunately conversion from float to 8-bit back to float is lossy + EXPECT_EQ(test.getAlphaF(), round(0.1f * 255) * (1.0f / 255.0f)); + EXPECT_EQ(test.getRedF(), round(0.2f * 255) * (1.0f / 255.0f)); + EXPECT_EQ(test.getGreenF(), round(0.3f * 255) * (1.0f / 255.0f)); + EXPECT_EQ(test.getBlueF(), round(0.4f * 255) * (1.0f / 255.0f)); + } + + { + DlColor test = DlColor::RGBA(0.2f, 0.3f, 0.4f, 0.1f); + + EXPECT_EQ(test.getAlpha(), round(0.1f * 255)); + EXPECT_EQ(test.getRed(), round(0.2f * 255)); + EXPECT_EQ(test.getGreen(), round(0.3f * 255)); + EXPECT_EQ(test.getBlue(), round(0.4f * 255)); + + // Unfortunately conversion from float to 8-bit back to float is lossy + EXPECT_EQ(test.getAlphaF(), round(0.1f * 255) * (1.0f / 255.0f)); + EXPECT_EQ(test.getRedF(), round(0.2f * 255) * (1.0f / 255.0f)); + EXPECT_EQ(test.getGreenF(), round(0.3f * 255) * (1.0f / 255.0f)); + EXPECT_EQ(test.getBlueF(), round(0.4f * 255) * (1.0f / 255.0f)); + } +} + +TEST(DisplayListColor, DlColorOpaqueTransparent) { + auto test_argb = [](int a, int r, int g, int b) { + ASSERT_TRUE(a >= 0 && a <= 255); + ASSERT_TRUE(r >= 0 && r <= 255); + ASSERT_TRUE(g >= 0 && g <= 255); + ASSERT_TRUE(b >= 0 && b <= 255); + + int argb = ((a << 24) | (r << 16) | (g << 8) | b); + bool is_opaque = (a == 255); + bool is_transprent = (a == 0); + + EXPECT_EQ(DlColor(argb).isOpaque(), is_opaque); + EXPECT_EQ(DlColor(argb).isTransparent(), is_transprent); + + DlScalar aF = a * (1.0f / 255.0f); + DlScalar rF = r * (1.0f / 255.0f); + DlScalar gF = g * (1.0f / 255.0f); + DlScalar bF = b * (1.0f / 255.0f); + + EXPECT_EQ(DlColor::ARGB(aF, rF, gF, bF).isOpaque(), is_opaque); + EXPECT_EQ(DlColor::ARGB(aF, rF, gF, bF).isTransparent(), is_transprent); + + EXPECT_EQ(DlColor::RGBA(rF, gF, bF, aF).isOpaque(), is_opaque); + EXPECT_EQ(DlColor::RGBA(rF, gF, bF, aF).isTransparent(), is_transprent); + }; + + for (int r = 0; r <= 255; r += 15) { + for (int g = 0; g <= 255; g += 15) { + for (int b = 0; b <= 255; b += 15) { + test_argb(0, r, g, b); + for (int a = 15; a < 255; a += 15) { + test_argb(a, r, g, b); + } + test_argb(255, r, g, b); + } + } + } +} + } // namespace testing } // namespace flutter diff --git a/engine/src/flutter/display_list/dl_op_receiver.h b/engine/src/flutter/display_list/dl_op_receiver.h index 8de5dae62c4..dc85e379e41 100644 --- a/engine/src/flutter/display_list/dl_op_receiver.h +++ b/engine/src/flutter/display_list/dl_op_receiver.h @@ -328,6 +328,7 @@ class DlOpReceiver { virtual void transformReset() = 0; virtual void clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) = 0; + virtual void clipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) = 0; virtual void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) = 0; virtual void clipPath(const SkPath& path, ClipOp clip_op, bool is_aa) = 0; diff --git a/engine/src/flutter/display_list/dl_op_records.h b/engine/src/flutter/display_list/dl_op_records.h index 3ad6eebe8fc..aeb0379e237 100644 --- a/engine/src/flutter/display_list/dl_op_records.h +++ b/engine/src/flutter/display_list/dl_op_records.h @@ -566,11 +566,11 @@ struct TransformResetOp final : TransformClipOpBase { // the header, but the Windows compiler keeps wanting to expand that // packing into more bytes than needed (even when they are declared as // packed bit fields!) -#define DEFINE_CLIP_SHAPE_OP(shapetype, clipop) \ - struct Clip##clipop##shapetype##Op final : TransformClipOpBase { \ - static constexpr auto kType = DisplayListOpType::kClip##clipop##shapetype; \ +#define DEFINE_CLIP_SHAPE_OP(shapename, shapetype, clipop) \ + struct Clip##clipop##shapename##Op final : TransformClipOpBase { \ + static constexpr auto kType = DisplayListOpType::kClip##clipop##shapename; \ \ - Clip##clipop##shapetype##Op(Sk##shapetype shape, bool is_aa) \ + Clip##clipop##shapename##Op(Sk##shapetype shape, bool is_aa) \ : is_aa(is_aa), shape(shape) {} \ \ const bool is_aa; \ @@ -578,15 +578,17 @@ struct TransformResetOp final : TransformClipOpBase { \ void dispatch(DispatchContext& ctx) const { \ if (op_needed(ctx)) { \ - ctx.receiver.clip##shapetype(shape, DlCanvas::ClipOp::k##clipop, \ + ctx.receiver.clip##shapename(shape, DlCanvas::ClipOp::k##clipop, \ is_aa); \ } \ } \ }; -DEFINE_CLIP_SHAPE_OP(Rect, Intersect) -DEFINE_CLIP_SHAPE_OP(RRect, Intersect) -DEFINE_CLIP_SHAPE_OP(Rect, Difference) -DEFINE_CLIP_SHAPE_OP(RRect, Difference) +DEFINE_CLIP_SHAPE_OP(Rect, Rect, Intersect) +DEFINE_CLIP_SHAPE_OP(Oval, Rect, Intersect) +DEFINE_CLIP_SHAPE_OP(RRect, RRect, Intersect) +DEFINE_CLIP_SHAPE_OP(Rect, Rect, Difference) +DEFINE_CLIP_SHAPE_OP(Oval, Rect, Difference) +DEFINE_CLIP_SHAPE_OP(RRect, RRect, Difference) #undef DEFINE_CLIP_SHAPE_OP #define DEFINE_CLIP_PATH_OP(clipop) \ diff --git a/engine/src/flutter/display_list/skia/dl_sk_canvas.cc b/engine/src/flutter/display_list/skia/dl_sk_canvas.cc index a7334f303ec..235b654f41b 100644 --- a/engine/src/flutter/display_list/skia/dl_sk_canvas.cc +++ b/engine/src/flutter/display_list/skia/dl_sk_canvas.cc @@ -153,6 +153,12 @@ void DlSkCanvasAdapter::ClipRect(const SkRect& rect, delegate_->clipRect(rect, ToSk(clip_op), is_aa); } +void DlSkCanvasAdapter::ClipOval(const SkRect& bounds, + ClipOp clip_op, + bool is_aa) { + delegate_->clipRRect(SkRRect::MakeOval(bounds), ToSk(clip_op), is_aa); +} + void DlSkCanvasAdapter::ClipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) { diff --git a/engine/src/flutter/display_list/skia/dl_sk_canvas.h b/engine/src/flutter/display_list/skia/dl_sk_canvas.h index c61f3f46545..5ceebe561ef 100644 --- a/engine/src/flutter/display_list/skia/dl_sk_canvas.h +++ b/engine/src/flutter/display_list/skia/dl_sk_canvas.h @@ -72,6 +72,7 @@ class DlSkCanvasAdapter final : public virtual DlCanvas { SkMatrix GetTransform() const override; void ClipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override; + void ClipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) override; void ClipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override; void ClipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override; diff --git a/engine/src/flutter/display_list/skia/dl_sk_dispatcher.cc b/engine/src/flutter/display_list/skia/dl_sk_dispatcher.cc index 84c7115fb5f..819860d3fb1 100644 --- a/engine/src/flutter/display_list/skia/dl_sk_dispatcher.cc +++ b/engine/src/flutter/display_list/skia/dl_sk_dispatcher.cc @@ -122,6 +122,11 @@ void DlSkCanvasDispatcher::clipRect(const SkRect& rect, bool is_aa) { canvas_->clipRect(rect, ToSk(clip_op), is_aa); } +void DlSkCanvasDispatcher::clipOval(const SkRect& bounds, + ClipOp clip_op, + bool is_aa) { + canvas_->clipRRect(SkRRect::MakeOval(bounds), ToSk(clip_op), is_aa); +} void DlSkCanvasDispatcher::clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) { diff --git a/engine/src/flutter/display_list/skia/dl_sk_dispatcher.h b/engine/src/flutter/display_list/skia/dl_sk_dispatcher.h index ef15f8152e7..384f7226336 100644 --- a/engine/src/flutter/display_list/skia/dl_sk_dispatcher.h +++ b/engine/src/flutter/display_list/skia/dl_sk_dispatcher.h @@ -51,6 +51,7 @@ class DlSkCanvasDispatcher : public virtual DlOpReceiver, void transformReset() override; void clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override; + void clipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) override; void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override; void clipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override; diff --git a/engine/src/flutter/display_list/testing/dl_rendering_unittests.cc b/engine/src/flutter/display_list/testing/dl_rendering_unittests.cc index 89102029e17..1368f3e921f 100644 --- a/engine/src/flutter/display_list/testing/dl_rendering_unittests.cc +++ b/engine/src/flutter/display_list/testing/dl_rendering_unittests.cc @@ -2073,6 +2073,39 @@ class CanvasCompareTester { ctx.canvas->ClipRect(r_clip, ClipOp::kDifference, false); }) .with_diff_clip()); + // Skia lacks clipOval and requires us to make an oval SkRRect + SkRRect rr_oval_clip = SkRRect::MakeOval(r_clip); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "Hard ClipOval", + [=](const SkSetupContext& ctx) { + ctx.canvas->clipRRect(rr_oval_clip, SkClipOp::kIntersect, + false); + }, + [=](const DlSetupContext& ctx) { + ctx.canvas->ClipOval(r_clip, ClipOp::kIntersect, false); + })); + RenderWith(testP, env, intersect_tolerance, + CaseParameters( + "AntiAlias ClipOval", + [=](const SkSetupContext& ctx) { + ctx.canvas->clipRRect(rr_oval_clip, SkClipOp::kIntersect, + true); + }, + [=](const DlSetupContext& ctx) { + ctx.canvas->ClipOval(r_clip, ClipOp::kIntersect, true); + })); + RenderWith(testP, env, diff_tolerance, + CaseParameters( + "Hard ClipOval Diff", + [=](const SkSetupContext& ctx) { + ctx.canvas->clipRRect(rr_oval_clip, SkClipOp::kDifference, + false); + }, + [=](const DlSetupContext& ctx) { + ctx.canvas->ClipOval(r_clip, ClipOp::kDifference, false); + }) + .with_diff_clip()); // This test RR clip used to use very small radii, but due to // optimizations in the HW rrect rasterization, this caused small // bulges in the corners of the RRect which were interpreted as @@ -2850,12 +2883,14 @@ TEST_F(DisplayListRendering, DrawDiagonalLines) { SkPoint p2 = SkPoint::Make(kRenderRight, kRenderBottom); SkPoint p3 = SkPoint::Make(kRenderLeft, kRenderBottom); SkPoint p4 = SkPoint::Make(kRenderRight, kRenderTop); + // Adding some edge to edge diagonals that run through the points about + // 16 units in from the center of that edge. // Adding some edge center to edge center diagonals to better fill // out the RRect Clip so bounds checking sees less empty bounds space. - SkPoint p5 = SkPoint::Make(kRenderCenterX, kRenderTop); - SkPoint p6 = SkPoint::Make(kRenderRight, kRenderCenterY); - SkPoint p7 = SkPoint::Make(kRenderLeft, kRenderCenterY); - SkPoint p8 = SkPoint::Make(kRenderCenterX, kRenderBottom); + SkPoint p5 = SkPoint::Make(kRenderCenterX, kRenderTop + 15); + SkPoint p6 = SkPoint::Make(kRenderRight - 15, kRenderCenterY); + SkPoint p7 = SkPoint::Make(kRenderCenterX, kRenderBottom - 15); + SkPoint p8 = SkPoint::Make(kRenderLeft + 15, kRenderCenterY); CanvasCompareTester::RenderAll( // TestParameters( @@ -2880,9 +2915,13 @@ TEST_F(DisplayListRendering, DrawDiagonalLines) { .set_draw_line()); } -TEST_F(DisplayListRendering, DrawHorizontalLine) { - SkPoint p1 = SkPoint::Make(kRenderLeft, kRenderCenterY); - SkPoint p2 = SkPoint::Make(kRenderRight, kRenderCenterY); +TEST_F(DisplayListRendering, DrawHorizontalLines) { + SkPoint p1 = SkPoint::Make(kRenderLeft, kRenderTop + 16); + SkPoint p2 = SkPoint::Make(kRenderRight, kRenderTop + 16); + SkPoint p3 = SkPoint::Make(kRenderLeft, kRenderCenterY); + SkPoint p4 = SkPoint::Make(kRenderRight, kRenderCenterY); + SkPoint p5 = SkPoint::Make(kRenderLeft, kRenderBottom - 16); + SkPoint p6 = SkPoint::Make(kRenderRight, kRenderBottom - 16); CanvasCompareTester::RenderAll( // TestParameters( @@ -2893,18 +2932,26 @@ TEST_F(DisplayListRendering, DrawHorizontalLine) { SkPaint p = ctx.paint; p.setStyle(SkPaint::kStroke_Style); ctx.canvas->drawLine(p1, p2, p); + ctx.canvas->drawLine(p3, p4, p); + ctx.canvas->drawLine(p5, p6, p); }, [=](const DlRenderContext& ctx) { // ctx.canvas->DrawLine(p1, p2, ctx.paint); + ctx.canvas->DrawLine(p3, p4, ctx.paint); + ctx.canvas->DrawLine(p5, p6, ctx.paint); }, kDrawHVLineFlags) .set_draw_line() .set_horizontal_line()); } -TEST_F(DisplayListRendering, DrawVerticalLine) { - SkPoint p1 = SkPoint::Make(kRenderCenterX, kRenderTop); - SkPoint p2 = SkPoint::Make(kRenderCenterY, kRenderBottom); +TEST_F(DisplayListRendering, DrawVerticalLines) { + SkPoint p1 = SkPoint::Make(kRenderLeft + 16, kRenderTop); + SkPoint p2 = SkPoint::Make(kRenderLeft + 16, kRenderBottom); + SkPoint p3 = SkPoint::Make(kRenderCenterX, kRenderTop); + SkPoint p4 = SkPoint::Make(kRenderCenterX, kRenderBottom); + SkPoint p5 = SkPoint::Make(kRenderRight - 16, kRenderTop); + SkPoint p6 = SkPoint::Make(kRenderRight - 16, kRenderBottom); CanvasCompareTester::RenderAll( // TestParameters( @@ -2915,9 +2962,13 @@ TEST_F(DisplayListRendering, DrawVerticalLine) { SkPaint p = ctx.paint; p.setStyle(SkPaint::kStroke_Style); ctx.canvas->drawLine(p1, p2, p); + ctx.canvas->drawLine(p3, p4, p); + ctx.canvas->drawLine(p5, p6, p); }, [=](const DlRenderContext& ctx) { // ctx.canvas->DrawLine(p1, p2, ctx.paint); + ctx.canvas->DrawLine(p3, p4, ctx.paint); + ctx.canvas->DrawLine(p5, p6, ctx.paint); }, kDrawHVLineFlags) .set_draw_line() @@ -2929,12 +2980,14 @@ TEST_F(DisplayListRendering, DrawDiagonalDashedLines) { SkPoint p2 = SkPoint::Make(kRenderRight, kRenderBottom); SkPoint p3 = SkPoint::Make(kRenderLeft, kRenderBottom); SkPoint p4 = SkPoint::Make(kRenderRight, kRenderTop); + // Adding some edge to edge diagonals that run through the points about + // 16 units in from the center of that edge. // Adding some edge center to edge center diagonals to better fill // out the RRect Clip so bounds checking sees less empty bounds space. - SkPoint p5 = SkPoint::Make(kRenderCenterX, kRenderTop); - SkPoint p6 = SkPoint::Make(kRenderRight, kRenderCenterY); - SkPoint p7 = SkPoint::Make(kRenderLeft, kRenderCenterY); - SkPoint p8 = SkPoint::Make(kRenderCenterX, kRenderBottom); + SkPoint p5 = SkPoint::Make(kRenderCenterX, kRenderTop + 15); + SkPoint p6 = SkPoint::Make(kRenderRight - 15, kRenderCenterY); + SkPoint p7 = SkPoint::Make(kRenderCenterX, kRenderBottom - 15); + SkPoint p8 = SkPoint::Make(kRenderLeft + 15, kRenderCenterY); // Full diagonals are 100x100 which are 140 in length // Dashing them with 25 on, 5 off means that the last @@ -2955,7 +3008,7 @@ TEST_F(DisplayListRendering, DrawDiagonalDashedLines) { SkPaint p = ctx.paint; p.setStyle(SkPaint::kStroke_Style); SkScalar intervals[2] = {25.0f, 5.0f}; - p.setPathEffect(SkDashPathEffect::Make(intervals, 2.0f, 0.0f)); + p.setPathEffect(SkDashPathEffect::Make(intervals, 2, 0.0f)); ctx.canvas->drawLine(p1, p2, p); ctx.canvas->drawLine(p3, p4, p); ctx.canvas->drawLine(p5, p6, p); @@ -3125,7 +3178,7 @@ TEST_F(DisplayListRendering, DrawPointsAsPoints) { const SkScalar x3 = kRenderCenterX + 0.1; const SkScalar x4 = (kRenderRight + kRenderCenterX) * 0.5; const SkScalar x5 = kRenderRight - 16; - const SkScalar x6 = kRenderRight; + const SkScalar x6 = kRenderRight - 1; const SkScalar y0 = kRenderTop; const SkScalar y1 = kRenderTop + 16; @@ -3133,7 +3186,7 @@ TEST_F(DisplayListRendering, DrawPointsAsPoints) { const SkScalar y3 = kRenderCenterY + 0.1; const SkScalar y4 = (kRenderBottom + kRenderCenterY) * 0.5; const SkScalar y5 = kRenderBottom - 16; - const SkScalar y6 = kRenderBottom; + const SkScalar y6 = kRenderBottom - 1; // clang-format off const SkPoint points[] = { @@ -3177,7 +3230,7 @@ TEST_F(DisplayListRendering, DrawPointsAsLines) { const SkScalar y0 = kRenderTop; const SkScalar y1 = kRenderTop + 16; const SkScalar y2 = kRenderBottom - 16; - const SkScalar y3 = kRenderBottom; + const SkScalar y3 = kRenderBottom - 1; // clang-format off const SkPoint points[] = { @@ -3226,10 +3279,12 @@ TEST_F(DisplayListRendering, DrawPointsAsPolygon) { SkPoint::Make(kRenderRight, kRenderBottom), SkPoint::Make(kRenderLeft, kRenderBottom), SkPoint::Make(kRenderLeft, kRenderTop), - SkPoint::Make(kRenderCenterX, kRenderTop), - SkPoint::Make(kRenderRight, kRenderCenterY), - SkPoint::Make(kRenderCenterX, kRenderBottom), - SkPoint::Make(kRenderLeft, kRenderCenterY), + + SkPoint::Make(kRenderCenterX, kRenderTop + 15), + SkPoint::Make(kRenderRight - 15, kRenderCenterY), + SkPoint::Make(kRenderCenterX, kRenderBottom - 15), + SkPoint::Make(kRenderLeft + 15, kRenderCenterY), + SkPoint::Make(kRenderCenterX, kRenderTop + 15), }; const int count1 = sizeof(points1) / sizeof(points1[0]); @@ -3730,7 +3785,7 @@ TEST_F(DisplayListRendering, DrawShadow) { }, kRenderCornerRadius, kRenderCornerRadius); const DlColor color = DlColor::kDarkGrey(); - const SkScalar elevation = 5; + const SkScalar elevation = 7; CanvasCompareTester::RenderAll( // TestParameters( @@ -3756,7 +3811,7 @@ TEST_F(DisplayListRendering, DrawShadowTransparentOccluder) { }, kRenderCornerRadius, kRenderCornerRadius); const DlColor color = DlColor::kDarkGrey(); - const SkScalar elevation = 5; + const SkScalar elevation = 7; CanvasCompareTester::RenderAll( // TestParameters( @@ -3782,7 +3837,7 @@ TEST_F(DisplayListRendering, DrawShadowDpr) { }, kRenderCornerRadius, kRenderCornerRadius); const DlColor color = DlColor::kDarkGrey(); - const SkScalar elevation = 5; + const SkScalar elevation = 7; CanvasCompareTester::RenderAll( // TestParameters( diff --git a/engine/src/flutter/display_list/testing/dl_test_snippets.cc b/engine/src/flutter/display_list/testing/dl_test_snippets.cc index e5ad4b2f365..9ce87ae6c24 100644 --- a/engine/src/flutter/display_list/testing/dl_test_snippets.cc +++ b/engine/src/flutter/display_list/testing/dl_test_snippets.cc @@ -424,6 +424,30 @@ std::vector CreateAllClipOps() { r.clipRect(kTestBounds, DlCanvas::ClipOp::kDifference, false); }}, }}, + {"ClipOval", + { + {1, 24, 0, + [](DlOpReceiver& r) { + r.clipOval(kTestBounds, DlCanvas::ClipOp::kIntersect, true); + }}, + {1, 24, 0, + [](DlOpReceiver& r) { + r.clipOval(kTestBounds.makeOffset(1, 1), + DlCanvas::ClipOp::kIntersect, true); + }}, + {1, 24, 0, + [](DlOpReceiver& r) { + r.clipOval(kTestBounds, DlCanvas::ClipOp::kIntersect, false); + }}, + {1, 24, 0, + [](DlOpReceiver& r) { + r.clipOval(kTestBounds, DlCanvas::ClipOp::kDifference, true); + }}, + {1, 24, 0, + [](DlOpReceiver& r) { + r.clipOval(kTestBounds, DlCanvas::ClipOp::kDifference, false); + }}, + }}, {"ClipRRect", { {1, 64, 0, @@ -484,6 +508,11 @@ std::vector CreateAllClipOps() { [](DlOpReceiver& r) { r.clipPath(kTestPathOval, DlCanvas::ClipOp::kIntersect, true); }}, + // clipPath(rrect) becomes clipRRect + {1, 64, 0, + [](DlOpReceiver& r) { + r.clipPath(kTestPathRRect, DlCanvas::ClipOp::kIntersect, true); + }}, }}, }; } @@ -637,8 +666,10 @@ std::vector CreateAllRenderingOps() { {1, 40, 1, [](DlOpReceiver& r) { r.drawPath(kTestPath1); }}, {1, 40, 1, [](DlOpReceiver& r) { r.drawPath(kTestPath2); }}, {1, 40, 1, [](DlOpReceiver& r) { r.drawPath(kTestPath3); }}, + // oval, rect and rrect paths are left as drawPath {1, 40, 1, [](DlOpReceiver& r) { r.drawPath(kTestPathRect); }}, {1, 40, 1, [](DlOpReceiver& r) { r.drawPath(kTestPathOval); }}, + {1, 40, 1, [](DlOpReceiver& r) { r.drawPath(kTestPathRRect); }}, }}, {"DrawArc", { diff --git a/engine/src/flutter/display_list/testing/dl_test_snippets.h b/engine/src/flutter/display_list/testing/dl_test_snippets.h index 16d740c2fb6..733ce673640 100644 --- a/engine/src/flutter/display_list/testing/dl_test_snippets.h +++ b/engine/src/flutter/display_list/testing/dl_test_snippets.h @@ -183,6 +183,7 @@ static const SkRRect kTestInnerRRect = SkRRect::MakeRectXY(kTestBounds.makeInset(5, 5), 2, 2); static const SkPath kTestPathRect = SkPath::Rect(kTestBounds); static const SkPath kTestPathOval = SkPath::Oval(kTestBounds); +static const SkPath kTestPathRRect = SkPath::RRect(kTestRRect); static const SkPath kTestPath1 = SkPath::Polygon({{0, 0}, {10, 10}, {10, 0}, {0, 10}}, true); static const SkPath kTestPath2 = diff --git a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc index 748cf07a838..86f3cff46a6 100644 --- a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc +++ b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.cc @@ -74,6 +74,24 @@ void DisplayListMatrixClipState::clipRect(const DlRect& rect, } } +void DisplayListMatrixClipState::clipOval(const DlRect& bounds, + ClipOp op, + bool is_aa) { + if (!bounds.IsFinite()) { + return; + } + switch (op) { + case DlCanvas::ClipOp::kIntersect: + adjustCullRect(bounds, op, is_aa); + break; + case DlCanvas::ClipOp::kDifference: + if (oval_covers_cull(bounds)) { + cull_rect_ = DlRect(); + } + break; + } +} + void DisplayListMatrixClipState::clipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) { diff --git a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h index 59c9880bd3b..4a6198d75a3 100644 --- a/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h +++ b/engine/src/flutter/display_list/utils/dl_matrix_clip_tracker.h @@ -149,6 +149,10 @@ class DisplayListMatrixClipState { void clipRect(const SkRect& rect, ClipOp op, bool is_aa) { clipRect(ToDlRect(rect), op, is_aa); } + void clipOval(const DlRect& bounds, ClipOp op, bool is_aa); + void clipOval(const SkRect& bounds, ClipOp op, bool is_aa) { + clipRect(ToDlRect(bounds), op, is_aa); + } void clipRRect(const SkRRect& rrect, ClipOp op, bool is_aa); void clipPath(const SkPath& path, ClipOp op, bool is_aa); diff --git a/engine/src/flutter/display_list/utils/dl_receiver_utils.h b/engine/src/flutter/display_list/utils/dl_receiver_utils.h index d50cb00cba0..bb6ce6539f6 100644 --- a/engine/src/flutter/display_list/utils/dl_receiver_utils.h +++ b/engine/src/flutter/display_list/utils/dl_receiver_utils.h @@ -44,6 +44,9 @@ class IgnoreClipDispatchHelper : public virtual DlOpReceiver { void clipRect(const SkRect& rect, DlCanvas::ClipOp clip_op, bool is_aa) override {} + void clipOval(const SkRect& bounds, + DlCanvas::ClipOp clip_op, + bool is_aa) override {} void clipRRect(const SkRRect& rrect, DlCanvas::ClipOp clip_op, bool is_aa) override {} diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index 9d6a7301616..91a1e44923d 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -70,292 +70,6 @@ TEST_P(AiksTest, CanvasCanPushPopCTM) { Matrix::MakeTranslation({100.0, 100.0, 0.0})); } -TEST_P(AiksTest, CanRenderColoredRect) { - Canvas canvas; - Paint paint; - paint.color = Color::Blue(); - canvas.DrawPath(PathBuilder{} - .AddRect(Rect::MakeXYWH(100.0, 100.0, 100.0, 100.0)) - .TakePath(), - paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderImage) { - Canvas canvas; - Paint paint; - auto image = std::make_shared(CreateTextureForFixture("kalimba.jpg")); - paint.color = Color::Red(); - canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderInvertedImageWithColorFilter) { - Canvas canvas; - Paint paint; - auto image = std::make_shared(CreateTextureForFixture("kalimba.jpg")); - paint.color = Color::Red(); - paint.color_filter = - ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Yellow()); - paint.invert_colors = true; - - canvas.DrawImage(image, Point::MakeXY(100.0, 100.0), paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderColorFilterWithInvertColors) { - Canvas canvas; - Paint paint; - paint.color = Color::Red(); - paint.color_filter = - ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Yellow()); - paint.invert_colors = true; - - canvas.DrawRect(Rect::MakeLTRB(0, 0, 100, 100), paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) { - Canvas canvas; - Paint paint; - paint.color = Color::Red(); - paint.color_filter = - ColorFilter::MakeBlend(BlendMode::kSourceOver, Color::Yellow()); - paint.invert_colors = true; - - canvas.DrawPaint(paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -namespace { -bool GenerateMipmap(const std::shared_ptr& context, - std::shared_ptr texture, - std::string label) { - auto buffer = context->CreateCommandBuffer(); - if (!buffer) { - return false; - } - auto pass = buffer->CreateBlitPass(); - if (!pass) { - return false; - } - pass->GenerateMipmap(std::move(texture), std::move(label)); - - pass->EncodeCommands(context->GetResourceAllocator()); - return context->GetCommandQueue()->Submit({buffer}).ok(); -} - -void CanRenderTiledTexture(AiksTest* aiks_test, - Entity::TileMode tile_mode, - Matrix local_matrix = {}) { - auto context = aiks_test->GetContext(); - ASSERT_TRUE(context); - auto texture = aiks_test->CreateTextureForFixture("table_mountain_nx.png", - /*enable_mipmapping=*/true); - GenerateMipmap(context, texture, "table_mountain_nx"); - Canvas canvas; - canvas.Scale(aiks_test->GetContentScale()); - canvas.Translate({100.0f, 100.0f, 0}); - Paint paint; - paint.color_source = - ColorSource::MakeImage(texture, tile_mode, tile_mode, {}, local_matrix); - paint.color = Color(1, 1, 1, 1); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint); - - // Should not change the image. - constexpr auto stroke_width = 64; - paint.style = Paint::Style::kStroke; - paint.stroke_width = stroke_width; - if (tile_mode == Entity::TileMode::kDecal) { - canvas.DrawRect(Rect::MakeXYWH(stroke_width, stroke_width, 600, 600), - paint); - } else { - canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint); - } - - { - // Should not change the image. - PathBuilder path_builder; - path_builder.AddCircle({150, 150}, 150); - path_builder.AddRoundedRect(Rect::MakeLTRB(300, 300, 600, 600), 10); - paint.style = Paint::Style::kFill; - canvas.DrawPath(path_builder.TakePath(), paint); - } - - { - // Should not change the image. Tests the Convex short-cut code. - PathBuilder path_builder; - path_builder.AddCircle({150, 450}, 150); - path_builder.SetConvexity(Convexity::kConvex); - paint.style = Paint::Style::kFill; - canvas.DrawPath(path_builder.TakePath(), paint); - } - - ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} -} // namespace - -TEST_P(AiksTest, CanRenderTiledTextureClamp) { - CanRenderTiledTexture(this, Entity::TileMode::kClamp); -} - -TEST_P(AiksTest, CanRenderTiledTextureRepeat) { - CanRenderTiledTexture(this, Entity::TileMode::kRepeat); -} - -TEST_P(AiksTest, CanRenderTiledTextureMirror) { - CanRenderTiledTexture(this, Entity::TileMode::kMirror); -} - -TEST_P(AiksTest, CanRenderTiledTextureDecal) { - CanRenderTiledTexture(this, Entity::TileMode::kDecal); -} - -TEST_P(AiksTest, CanRenderTiledTextureClampWithTranslate) { - CanRenderTiledTexture(this, Entity::TileMode::kClamp, - Matrix::MakeTranslation({172.f, 172.f, 0.f})); -} - -TEST_P(AiksTest, CanRenderImageRect) { - Canvas canvas; - Paint paint; - auto image = std::make_shared(CreateTextureForFixture("kalimba.jpg")); - Size image_half_size = Size(image->GetSize()) * 0.5; - - // Render the bottom right quarter of the source image in a stretched rect. - auto source_rect = Rect::MakeSize(image_half_size); - source_rect = source_rect.Shift(Point(image_half_size)); - - canvas.DrawImageRect(image, source_rect, Rect::MakeXYWH(100, 100, 600, 600), - paint); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderSimpleClips) { - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - - paint.color = Color::White(); - canvas.DrawPaint(paint); - - auto draw = [&canvas](const Paint& paint, Scalar x, Scalar y) { - canvas.Save(); - canvas.Translate({x, y}); - { - canvas.Save(); - canvas.ClipRect(Rect::MakeLTRB(50, 50, 150, 150)); - canvas.DrawPaint(paint); - canvas.Restore(); - } - { - canvas.Save(); - canvas.ClipOval(Rect::MakeLTRB(200, 50, 300, 150)); - canvas.DrawPaint(paint); - canvas.Restore(); - } - { - canvas.Save(); - canvas.ClipRRect(Rect::MakeLTRB(50, 200, 150, 300), {20, 20}); - canvas.DrawPaint(paint); - canvas.Restore(); - } - { - canvas.Save(); - canvas.ClipRRect(Rect::MakeLTRB(200, 230, 300, 270), {20, 20}); - canvas.DrawPaint(paint); - canvas.Restore(); - } - { - canvas.Save(); - canvas.ClipRRect(Rect::MakeLTRB(230, 200, 270, 300), {20, 20}); - canvas.DrawPaint(paint); - canvas.Restore(); - } - canvas.Restore(); - }; - - paint.color = Color::Blue(); - draw(paint, 0, 0); - - std::vector gradient_colors = { - Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, - Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, - Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, - Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, - Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; - std::vector stops = { - 0.0, - (1.0 / 6.0) * 1, - (1.0 / 6.0) * 2, - (1.0 / 6.0) * 3, - (1.0 / 6.0) * 4, - (1.0 / 6.0) * 5, - 1.0, - }; - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - paint.color_source = ColorSource::MakeRadialGradient( - {500, 600}, 75, std::move(gradient_colors), std::move(stops), - Entity::TileMode::kMirror, {}); - draw(paint, 0, 300); - - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({0, 0})); - draw(paint, 300, 0); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanSaveLayerStandalone) { - Canvas canvas; - - Paint red; - red.color = Color::Red(); - - Paint alpha; - alpha.color = Color::Red().WithAlpha(0.5); - - canvas.SaveLayer(alpha); - - canvas.DrawCircle({125, 125}, 125, red); - - canvas.Restore(); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) { - Canvas canvas; - Paint paint; - - std::vector colors = {Color{0.9568, 0.2627, 0.2118, 1.0}, - Color{0.1294, 0.5882, 0.9529, 1.0}}; - std::vector stops = { - 0.0, - 1.0, - }; - - paint.color_source = ColorSource::MakeLinearGradient( - {0, 0}, {100, 100}, std::move(colors), std::move(stops), - Entity::TileMode::kRepeat, {}); - - canvas.Save(); - canvas.Translate({100, 100, 0}); - canvas.DrawRect(Rect::MakeXYWH(0, 0, 200, 200), paint); - canvas.Restore(); - - canvas.Save(); - canvas.Translate({100, 400, 0}); - canvas.DrawCircle({100, 100}, 100, paint); - canvas.Restore(); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, CanPictureConvertToImage) { Canvas recorder_canvas; Paint paint; @@ -518,27 +232,6 @@ TEST_P(AiksTest, ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) { - Canvas canvas; - - Paint paint; - paint.color = Color::Red(); - - PathBuilder::RoundingRadii radii; - radii.top_left = {50, 25}; - radii.top_right = {25, 50}; - radii.bottom_right = {50, 25}; - radii.bottom_left = {25, 50}; - - auto path = PathBuilder{} - .AddRoundedRect(Rect::MakeXYWH(100, 100, 500, 500), radii) - .TakePath(); - - canvas.DrawPath(path, paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - struct TextRenderOptions { bool stroke = false; Scalar font_size = 50; @@ -852,21 +545,6 @@ TEST_P(AiksTest, TextRotated) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } -TEST_P(AiksTest, CanDrawPaint) { - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = Color::MediumTurquoise()}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, CanDrawPaintMultipleTimes) { - Canvas canvas; - canvas.Scale(Vector2(0.2, 0.2)); - canvas.DrawPaint({.color = Color::MediumTurquoise()}); - canvas.DrawPaint({.color = Color::Color::OrangeRed().WithAlpha(0.5)}); - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - // This makes sure the WideGamut named tests use 16bit float pixel format. TEST_P(AiksTest, FormatWideGamut) { EXPECT_EQ(GetContext()->GetCapabilities()->GetDefaultColorFormat(), @@ -920,344 +598,6 @@ TEST_P(AiksTest, TransformMultipliesCorrectly) { // clang-format on } -TEST_P(AiksTest, FilledCirclesRenderCorrectly) { - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - const int color_count = 3; - Color colors[color_count] = { - Color::Blue(), - Color::Green(), - Color::Crimson(), - }; - - paint.color = Color::White(); - canvas.DrawPaint(paint); - - int c_index = 0; - int radius = 600; - while (radius > 0) { - paint.color = colors[(c_index++) % color_count]; - canvas.DrawCircle({10, 10}, radius, paint); - if (radius > 30) { - radius -= 10; - } else { - radius -= 2; - } - } - - std::vector gradient_colors = { - Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, - Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, - Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, - Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, - Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; - std::vector stops = { - 0.0, - (1.0 / 6.0) * 1, - (1.0 / 6.0) * 2, - (1.0 / 6.0) * 3, - (1.0 / 6.0) * 4, - (1.0 / 6.0) * 5, - 1.0, - }; - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - paint.color_source = ColorSource::MakeRadialGradient( - {500, 600}, 75, std::move(gradient_colors), std::move(stops), - Entity::TileMode::kMirror, {}); - canvas.DrawCircle({500, 600}, 100, paint); - - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({700, 200})); - canvas.DrawCircle({800, 300}, 100, paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, StrokedCirclesRenderCorrectly) { - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - const int color_count = 3; - Color colors[color_count] = { - Color::Blue(), - Color::Green(), - Color::Crimson(), - }; - - paint.color = Color::White(); - canvas.DrawPaint(paint); - - int c_index = 0; - - auto draw = [&paint, &colors, &c_index](Canvas& canvas, Point center, - Scalar r, Scalar dr, int n) { - for (int i = 0; i < n; i++) { - paint.color = colors[(c_index++) % color_count]; - canvas.DrawCircle(center, r, paint); - r += dr; - } - }; - - paint.style = Paint::Style::kStroke; - paint.stroke_width = 1; - draw(canvas, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29] - paint.stroke_width = 5; - draw(canvas, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590] - - std::vector gradient_colors = { - Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, - Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, - Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, - Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, - Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; - std::vector stops = { - 0.0, - (1.0 / 6.0) * 1, - (1.0 / 6.0) * 2, - (1.0 / 6.0) * 3, - (1.0 / 6.0) * 4, - (1.0 / 6.0) * 5, - 1.0, - }; - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - paint.color_source = ColorSource::MakeRadialGradient( - {500, 600}, 75, std::move(gradient_colors), std::move(stops), - Entity::TileMode::kMirror, {}); - draw(canvas, {500, 600}, 5, 10, 10); - - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({700, 200})); - draw(canvas, {800, 300}, 5, 10, 10); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, FilledEllipsesRenderCorrectly) { - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - const int color_count = 3; - Color colors[color_count] = { - Color::Blue(), - Color::Green(), - Color::Crimson(), - }; - - paint.color = Color::White(); - canvas.DrawPaint(paint); - - int c_index = 0; - int long_radius = 600; - int short_radius = 600; - while (long_radius > 0 && short_radius > 0) { - paint.color = colors[(c_index++) % color_count]; - canvas.DrawOval(Rect::MakeXYWH(10 - long_radius, 10 - short_radius, - long_radius * 2, short_radius * 2), - paint); - canvas.DrawOval(Rect::MakeXYWH(1000 - short_radius, 750 - long_radius, - short_radius * 2, long_radius * 2), - paint); - if (short_radius > 30) { - short_radius -= 10; - long_radius -= 5; - } else { - short_radius -= 2; - long_radius -= 1; - } - } - - std::vector gradient_colors = { - Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, - Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, - Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, - Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, - Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; - std::vector stops = { - 0.0, - (1.0 / 6.0) * 1, - (1.0 / 6.0) * 2, - (1.0 / 6.0) * 3, - (1.0 / 6.0) * 4, - (1.0 / 6.0) * 5, - 1.0, - }; - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - paint.color = Color::White().WithAlpha(0.5); - - paint.color_source = ColorSource::MakeRadialGradient( - {300, 650}, 75, std::move(gradient_colors), std::move(stops), - Entity::TileMode::kMirror, {}); - canvas.DrawOval(Rect::MakeXYWH(200, 625, 200, 50), paint); - canvas.DrawOval(Rect::MakeXYWH(275, 550, 50, 200), paint); - - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({610, 15})); - canvas.DrawOval(Rect::MakeXYWH(610, 90, 200, 50), paint); - canvas.DrawOval(Rect::MakeXYWH(685, 15, 50, 200), paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) { - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - const int color_count = 3; - Color colors[color_count] = { - Color::Blue(), - Color::Green(), - Color::Crimson(), - }; - - paint.color = Color::White(); - canvas.DrawPaint(paint); - - int c_index = 0; - for (int i = 0; i < 4; i++) { - for (int j = 0; j < 4; j++) { - paint.color = colors[(c_index++) % color_count]; - canvas.DrawRRect(Rect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80), - Size(i * 5 + 10, j * 5 + 10), paint); - } - } - paint.color = colors[(c_index++) % color_count]; - canvas.DrawRRect(Rect::MakeXYWH(10, 420, 380, 80), Size(40, 40), paint); - paint.color = colors[(c_index++) % color_count]; - canvas.DrawRRect(Rect::MakeXYWH(410, 20, 80, 380), Size(40, 40), paint); - - std::vector gradient_colors = { - Color{0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0}, - Color{0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0}, - Color{0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0}, - Color{0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0}, - Color{0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0}, - Color{0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0}}; - std::vector stops = { - 0.0, - (1.0 / 6.0) * 1, - (1.0 / 6.0) * 2, - (1.0 / 6.0) * 3, - (1.0 / 6.0) * 4, - (1.0 / 6.0) * 5, - 1.0, - }; - auto texture = CreateTextureForFixture("airplane.jpg", - /*enable_mipmapping=*/true); - - paint.color = Color::White().WithAlpha(0.1); - paint.color_source = ColorSource::MakeRadialGradient( - {550, 550}, 75, gradient_colors, stops, Entity::TileMode::kMirror, {}); - for (int i = 1; i <= 10; i++) { - int j = 11 - i; - canvas.DrawRRect(Rect::MakeLTRB(550 - i * 20, 550 - j * 20, // - 550 + i * 20, 550 + j * 20), - Size(i * 10, j * 10), paint); - } - paint.color = Color::White().WithAlpha(0.5); - paint.color_source = ColorSource::MakeRadialGradient( - {200, 650}, 75, std::move(gradient_colors), std::move(stops), - Entity::TileMode::kMirror, {}); - canvas.DrawRRect(Rect::MakeLTRB(100, 610, 300, 690), Size(40, 40), paint); - canvas.DrawRRect(Rect::MakeLTRB(160, 550, 240, 750), Size(40, 40), paint); - - paint.color = Color::White().WithAlpha(0.1); - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({520, 20})); - for (int i = 1; i <= 10; i++) { - int j = 11 - i; - canvas.DrawRRect(Rect::MakeLTRB(720 - i * 20, 220 - j * 20, // - 720 + i * 20, 220 + j * 20), - Size(i * 10, j * 10), paint); - } - paint.color = Color::White().WithAlpha(0.5); - paint.color_source = ColorSource::MakeImage( - texture, Entity::TileMode::kRepeat, Entity::TileMode::kRepeat, {}, - Matrix::MakeTranslation({800, 300})); - canvas.DrawRRect(Rect::MakeLTRB(800, 410, 1000, 490), Size(40, 40), paint); - canvas.DrawRRect(Rect::MakeLTRB(860, 350, 940, 550), Size(40, 40), paint); - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - -TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) { - Canvas canvas; - canvas.Scale(GetContentScale()); - Paint paint; - paint.mask_blur_descriptor = Paint::MaskBlurDescriptor{ - .style = FilterContents::BlurStyle::kNormal, - .sigma = Sigma{1}, - }; - - canvas.DrawPaint({.color = Color::White()}); - - paint.color = Color::Crimson(); - Scalar y = 100.0f; - for (int i = 0; i < 5; i++) { - Scalar x = (i + 1) * 100; - Scalar radius = x / 10.0f; - canvas.DrawRect(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // - radius, 60.0f - radius), - paint); - } - - paint.color = Color::Blue(); - y += 100.0f; - for (int i = 0; i < 5; i++) { - Scalar x = (i + 1) * 100; - Scalar radius = x / 10.0f; - canvas.DrawCircle({x + 25, y + 25}, radius, paint); - } - - paint.color = Color::Green(); - y += 100.0f; - for (int i = 0; i < 5; i++) { - Scalar x = (i + 1) * 100; - Scalar radius = x / 10.0f; - canvas.DrawOval(Rect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // - radius, 60.0f - radius), - paint); - } - - paint.color = Color::Purple(); - y += 100.0f; - for (int i = 0; i < 5; i++) { - Scalar x = (i + 1) * 100; - Scalar radius = x / 20.0f; - canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), // - {radius, radius}, // - paint); - } - - paint.color = Color::Orange(); - y += 100.0f; - for (int i = 0; i < 5; i++) { - Scalar x = (i + 1) * 100; - Scalar radius = x / 20.0f; - canvas.DrawRRect(Rect::MakeXYWH(x, y, 60.0f, 60.0f), // - {radius, 5.0f}, paint); - } - - ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); -} - TEST_P(AiksTest, FastEllipticalRRectMaskBlursRenderCorrectly) { Canvas canvas; canvas.Scale(GetContentScale()); diff --git a/engine/src/flutter/impeller/display_list/BUILD.gn b/engine/src/flutter/impeller/display_list/BUILD.gn index 57e4c271b72..67c0620302d 100644 --- a/engine/src/flutter/impeller/display_list/BUILD.gn +++ b/engine/src/flutter/impeller/display_list/BUILD.gn @@ -53,6 +53,7 @@ template("display_list_unittests_component") { target_name = invoker.target_name predefined_sources = [ "aiks_dl_atlas_unittests.cc", + "aiks_dl_basic_unittests.cc", "aiks_dl_clip_unittests.cc", "aiks_dl_gradient_unittests.cc", "aiks_dl_opacity_unittests.cc", diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc new file mode 100644 index 00000000000..02224c075c9 --- /dev/null +++ b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc @@ -0,0 +1,766 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/impeller/aiks/aiks_unittests.h" + +#include "flutter/display_list/dl_blend_mode.h" +#include "flutter/display_list/dl_builder.h" +#include "flutter/display_list/dl_color.h" +#include "flutter/display_list/dl_paint.h" +#include "flutter/impeller/display_list/dl_image_impeller.h" +#include "flutter/impeller/geometry/scalar.h" +#include "flutter/testing/display_list_testing.h" +#include "flutter/testing/testing.h" + +namespace impeller { +namespace testing { + +using namespace flutter; + +TEST_P(AiksTest, CanRenderColoredRect) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kBlue()); + SkPath path = SkPath(); + path.addRect(SkRect::MakeXYWH(100.0, 100.0, 100.0, 100.0)); + builder.DrawPath(path, paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderImage) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kRed()); + auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg")); + builder.DrawImage(image, SkPoint::Make(100.0, 100.0), + DlImageSampling::kNearestNeighbor, &paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderInvertedImageWithColorFilter) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kRed()); + paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::kYellow(), DlBlendMode::kSrcOver)); + paint.setInvertColors(true); + auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg")); + + builder.DrawImage(image, SkPoint::Make(100.0, 100.0), + DlImageSampling::kNearestNeighbor, &paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderColorFilterWithInvertColors) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kRed()); + paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::kYellow(), DlBlendMode::kSrcOver)); + paint.setInvertColors(true); + + builder.DrawRect(SkRect::MakeLTRB(0, 0, 100, 100), paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderColorFilterWithInvertColorsDrawPaint) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kRed()); + paint.setColorFilter( + DlBlendColorFilter::Make(DlColor::kYellow(), DlBlendMode::kSrcOver)); + paint.setInvertColors(true); + + builder.DrawPaint(paint); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +namespace { +bool GenerateMipmap(const std::shared_ptr& context, + std::shared_ptr texture, + std::string label) { + auto buffer = context->CreateCommandBuffer(); + if (!buffer) { + return false; + } + auto pass = buffer->CreateBlitPass(); + if (!pass) { + return false; + } + pass->GenerateMipmap(std::move(texture), std::move(label)); + + pass->EncodeCommands(context->GetResourceAllocator()); + return context->GetCommandQueue()->Submit({buffer}).ok(); +} + +void CanRenderTiledTexture(AiksTest* aiks_test, + DlTileMode tile_mode, + Matrix local_matrix = {}) { + auto context = aiks_test->GetContext(); + ASSERT_TRUE(context); + auto texture = aiks_test->CreateTextureForFixture("table_mountain_nx.png", + /*enable_mipmapping=*/true); + GenerateMipmap(context, texture, "table_mountain_nx"); + auto image = DlImageImpeller::Make(texture); + SkMatrix sk_local_matrix = ToSkMatrix(local_matrix); + DlImageColorSource color_source(image, tile_mode, tile_mode, + DlImageSampling::kNearestNeighbor, + &sk_local_matrix); + + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kWhite()); + paint.setColorSource(&color_source); + + builder.Scale(aiks_test->GetContentScale().x, aiks_test->GetContentScale().y); + builder.Translate(100.0f, 100.0f); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 600, 600), paint); + + // Should not change the image. + constexpr auto stroke_width = 64; + paint.setDrawStyle(DlDrawStyle::kStroke); + paint.setStrokeWidth(stroke_width); + if (tile_mode == DlTileMode::kDecal) { + builder.DrawRect(SkRect::MakeXYWH(stroke_width, stroke_width, 600, 600), + paint); + } else { + builder.DrawRect(SkRect::MakeXYWH(0, 0, 600, 600), paint); + } + + { + // Should not change the image. + SkPath path; + path.addCircle(150, 150, 150); + path.addRoundRect(SkRect::MakeLTRB(300, 300, 600, 600), 10, 10); + + // Make sure path cannot be simplified... + EXPECT_FALSE(path.isRect(nullptr)); + EXPECT_FALSE(path.isOval(nullptr)); + EXPECT_FALSE(path.isRRect(nullptr)); + + // Make sure path will not trigger the optimal convex code + EXPECT_FALSE(path.isConvex()); + + paint.setDrawStyle(DlDrawStyle::kFill); + builder.DrawPath(path, paint); + } + + { + // Should not change the image. Tests the Convex short-cut code. + SkPath circle; + circle.addCircle(150, 450, 150); + + // Unfortunately, the circle path can be simplified... + EXPECT_TRUE(circle.isOval(nullptr)); + // At least it's convex, though... + EXPECT_TRUE(circle.isConvex()); + + // Let's make a copy that doesn't remember that it's just a circle... + SkPath path; + // This moveTo confuses addPath into appending rather than replacing, + // which prevents it from noticing that it's just a circle... + path.moveTo(10, 10); + path.addPath(circle); + + // Make sure path cannot be simplified... + EXPECT_FALSE(path.isRect(nullptr)); + EXPECT_FALSE(path.isOval(nullptr)); + EXPECT_FALSE(path.isRRect(nullptr)); + + // But check that we will trigger the optimal convex code + EXPECT_TRUE(path.isConvex()); + + paint.setDrawStyle(DlDrawStyle::kFill); + builder.DrawPath(path, paint); + } + + ASSERT_TRUE(aiks_test->OpenPlaygroundHere(builder.Build())); +} +} // namespace + +TEST_P(AiksTest, CanRenderTiledTextureClamp) { + CanRenderTiledTexture(this, DlTileMode::kClamp); +} + +TEST_P(AiksTest, CanRenderTiledTextureRepeat) { + CanRenderTiledTexture(this, DlTileMode::kRepeat); +} + +TEST_P(AiksTest, CanRenderTiledTextureMirror) { + CanRenderTiledTexture(this, DlTileMode::kMirror); +} + +TEST_P(AiksTest, CanRenderTiledTextureDecal) { + CanRenderTiledTexture(this, DlTileMode::kDecal); +} + +TEST_P(AiksTest, CanRenderTiledTextureClampWithTranslate) { + CanRenderTiledTexture(this, DlTileMode::kClamp, + Matrix::MakeTranslation({172.f, 172.f, 0.f})); +} + +TEST_P(AiksTest, CanRenderImageRect) { + DisplayListBuilder builder; + auto image = DlImageImpeller::Make(CreateTextureForFixture("kalimba.jpg")); + + SkSize image_half_size = SkSize::Make(image->dimensions().fWidth * 0.5f, + image->dimensions().fHeight * 0.5f); + + // Render the bottom right quarter of the source image in a stretched rect. + auto source_rect = SkRect::MakeSize(image_half_size); + source_rect = + source_rect.makeOffset(image_half_size.fWidth, image_half_size.fHeight); + + builder.DrawImageRect(image, source_rect, + SkRect::MakeXYWH(100, 100, 600, 600), + DlImageSampling::kNearestNeighbor); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderSimpleClips) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + + auto draw = [&builder](const DlPaint& paint, Scalar x, Scalar y) { + builder.Save(); + builder.Translate(x, y); + { + builder.Save(); + builder.ClipRect(SkRect::MakeLTRB(50, 50, 150, 150)); + builder.DrawPaint(paint); + builder.Restore(); + } + { + builder.Save(); + builder.ClipOval(SkRect::MakeLTRB(200, 50, 300, 150)); + builder.DrawPaint(paint); + builder.Restore(); + } + { + builder.Save(); + builder.ClipRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(50, 200, 150, 300), 20, 20)); + builder.DrawPaint(paint); + builder.Restore(); + } + { + builder.Save(); + builder.ClipRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(200, 230, 300, 270), 20, 20)); + builder.DrawPaint(paint); + builder.Restore(); + } + { + builder.Save(); + builder.ClipRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(230, 200, 270, 300), 20, 20)); + builder.DrawPaint(paint); + builder.Restore(); + } + builder.Restore(); + }; + + paint.setColor(DlColor::kBlue()); + draw(paint, 0, 0); + + DlColor gradient_colors[7] = { + DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0), + DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0), + }; + Scalar stops[7] = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + auto image = DlImageImpeller::Make(texture); + + paint.setColorSource(DlColorSource::MakeRadial( + {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); + draw(paint, 0, 300); + + DlImageColorSource image_source(image, DlTileMode::kRepeat, + DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor); + paint.setColorSource(&image_source); + draw(paint, 300, 0); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanSaveLayerStandalone) { + DisplayListBuilder builder; + + DlPaint red; + red.setColor(DlColor::kRed()); + + DlPaint alpha; + alpha.setColor(DlColor::kRed().modulateOpacity(0.5)); + + builder.SaveLayer(nullptr, &alpha); + + builder.DrawCircle({125, 125}, 125, red); + + builder.Restore(); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderDifferentShapesWithSameColorSource) { + DisplayListBuilder builder; + DlPaint paint; + + DlColor colors[2] = { + DlColor::RGBA(0.9568, 0.2627, 0.2118, 1.0), + DlColor::RGBA(0.1294, 0.5882, 0.9529, 1.0), + }; + DlScalar stops[2] = { + 0.0, + 1.0, + }; + + paint.setColorSource(DlColorSource::MakeLinear({0, 0}, {100, 100}, 2, colors, + stops, DlTileMode::kRepeat)); + + builder.Save(); + builder.Translate(100, 100); + builder.DrawRect(SkRect::MakeXYWH(0, 0, 200, 200), paint); + builder.Restore(); + + builder.Save(); + builder.Translate(100, 400); + builder.DrawCircle({100, 100}, 100, paint); + builder.Restore(); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanRenderRoundedRectWithNonUniformRadii) { + DisplayListBuilder builder; + DlPaint paint; + paint.setColor(DlColor::kRed()); + + SkRRect rrect; + SkVector radii[4] = { + SkVector{50, 25}, + SkVector{25, 50}, + SkVector{50, 25}, + SkVector{25, 50}, + }; + rrect.setRectRadii(SkRect::MakeXYWH(100, 100, 500, 500), radii); + + builder.DrawRRect(rrect, paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanDrawPaint) { + auto medium_turquoise = + DlColor::RGBA(72.0f / 255.0f, 209.0f / 255.0f, 204.0f / 255.0f, 1.0f); + + DisplayListBuilder builder; + builder.Scale(0.2, 0.2); + builder.DrawPaint(DlPaint().setColor(medium_turquoise)); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, CanDrawPaintMultipleTimes) { + auto medium_turquoise = + DlColor::RGBA(72.0f / 255.0f, 209.0f / 255.0f, 204.0f / 255.0f, 1.0f); + auto orange_red = + DlColor::RGBA(255.0f / 255.0f, 69.0f / 255.0f, 0.0f / 255.0f, 1.0f); + + DisplayListBuilder builder; + builder.Scale(0.2, 0.2); + builder.DrawPaint(DlPaint().setColor(medium_turquoise)); + builder.DrawPaint(DlPaint().setColor(orange_red.modulateOpacity(0.5f))); + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, FilledCirclesRenderCorrectly) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + const int color_count = 3; + DlColor colors[color_count] = { + DlColor::kBlue(), + DlColor::kGreen(), + DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f), + }; + + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + + int c_index = 0; + int radius = 600; + while (radius > 0) { + paint.setColor(colors[(c_index++) % color_count]); + builder.DrawCircle({10, 10}, radius, paint); + if (radius > 30) { + radius -= 10; + } else { + radius -= 2; + } + } + + DlColor gradient_colors[7] = { + DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0), + DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0), + }; + DlScalar stops[7] = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + auto image = DlImageImpeller::Make(texture); + + paint.setColorSource(DlColorSource::MakeRadial( + {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); + builder.DrawCircle({500, 600}, 100, paint); + + SkMatrix local_matrix = SkMatrix::Translate(700, 200); + DlImageColorSource image_source( + image, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &local_matrix); + paint.setColorSource(&image_source); + builder.DrawCircle({800, 300}, 100, paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, StrokedCirclesRenderCorrectly) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + const int color_count = 3; + DlColor colors[color_count] = { + DlColor::kBlue(), + DlColor::kGreen(), + DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f), + }; + + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + + int c_index = 0; + + auto draw = [&paint, &colors, &c_index](DlCanvas& canvas, SkPoint center, + Scalar r, Scalar dr, int n) { + for (int i = 0; i < n; i++) { + paint.setColor(colors[(c_index++) % color_count]); + canvas.DrawCircle(center, r, paint); + r += dr; + } + }; + + paint.setDrawStyle(DlDrawStyle::kStroke); + paint.setStrokeWidth(1); + draw(builder, {10, 10}, 2, 2, 14); // r = [2, 28], covers [1,29] + paint.setStrokeWidth(5); + draw(builder, {10, 10}, 35, 10, 56); // r = [35, 585], covers [30,590] + + DlColor gradient_colors[7] = { + DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0), + DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0), + }; + DlScalar stops[7] = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + auto image = DlImageImpeller::Make(texture); + + paint.setColorSource(DlColorSource::MakeRadial( + {500, 600}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); + draw(builder, {500, 600}, 5, 10, 10); + + SkMatrix local_matrix = SkMatrix::Translate(700, 200); + DlImageColorSource image_source( + image, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &local_matrix); + paint.setColorSource(&image_source); + draw(builder, {800, 300}, 5, 10, 10); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, FilledEllipsesRenderCorrectly) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + const int color_count = 3; + DlColor colors[color_count] = { + DlColor::kBlue(), + DlColor::kGreen(), + DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f), + }; + + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + + int c_index = 0; + int long_radius = 600; + int short_radius = 600; + while (long_radius > 0 && short_radius > 0) { + paint.setColor(colors[(c_index++) % color_count]); + builder.DrawOval(SkRect::MakeXYWH(10 - long_radius, 10 - short_radius, + long_radius * 2, short_radius * 2), + paint); + builder.DrawOval(SkRect::MakeXYWH(1000 - short_radius, 750 - long_radius, + short_radius * 2, long_radius * 2), + paint); + if (short_radius > 30) { + short_radius -= 10; + long_radius -= 5; + } else { + short_radius -= 2; + long_radius -= 1; + } + } + + DlColor gradient_colors[7] = { + DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0), + DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0), + }; + DlScalar stops[7] = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + auto image = DlImageImpeller::Make(texture); + + paint.setColor(DlColor::kWhite().modulateOpacity(0.5)); + + paint.setColorSource(DlColorSource::MakeRadial( + {300, 650}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); + builder.DrawOval(SkRect::MakeXYWH(200, 625, 200, 50), paint); + builder.DrawOval(SkRect::MakeXYWH(275, 550, 50, 200), paint); + + SkMatrix local_matrix = SkMatrix::Translate(610, 15); + DlImageColorSource image_source( + image, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &local_matrix); + paint.setColorSource(&image_source); + builder.DrawOval(SkRect::MakeXYWH(610, 90, 200, 50), paint); + builder.DrawOval(SkRect::MakeXYWH(685, 15, 50, 200), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, FilledRoundRectsRenderCorrectly) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + const int color_count = 3; + DlColor colors[color_count] = { + DlColor::kBlue(), + DlColor::kGreen(), + DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f), + }; + + paint.setColor(DlColor::kWhite()); + builder.DrawPaint(paint); + + int c_index = 0; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + paint.setColor(colors[(c_index++) % color_count]); + builder.DrawRRect( + SkRRect::MakeRectXY( + SkRect::MakeXYWH(i * 100 + 10, j * 100 + 20, 80, 80), // + i * 5 + 10, j * 5 + 10), + paint); + } + } + paint.setColor(colors[(c_index++) % color_count]); + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeXYWH(10, 420, 380, 80), 40, 40), paint); + paint.setColor(colors[(c_index++) % color_count]); + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeXYWH(410, 20, 80, 380), 40, 40), paint); + + DlColor gradient_colors[7] = { + DlColor::RGBA(0x1f / 255.0, 0.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0x5b / 255.0, 0.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0, 1.0), + DlColor::RGBA(0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0, 1.0), + DlColor::RGBA(0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0, 1.0), + DlColor::RGBA(0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0, 1.0), + }; + DlScalar stops[7] = { + 0.0, + (1.0 / 6.0) * 1, + (1.0 / 6.0) * 2, + (1.0 / 6.0) * 3, + (1.0 / 6.0) * 4, + (1.0 / 6.0) * 5, + 1.0, + }; + auto texture = CreateTextureForFixture("airplane.jpg", + /*enable_mipmapping=*/true); + auto image = DlImageImpeller::Make(texture); + + paint.setColor(DlColor::kWhite().modulateOpacity(0.1)); + paint.setColorSource(DlColorSource::MakeRadial( + {550, 550}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); + for (int i = 1; i <= 10; i++) { + int j = 11 - i; + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(550 - i * 20, 550 - j * 20, // + 550 + i * 20, 550 + j * 20), + i * 10, j * 10), + paint); + } + + paint.setColor(DlColor::kWhite().modulateOpacity(0.5)); + paint.setColorSource(DlColorSource::MakeRadial( + {200, 650}, 75, 7, gradient_colors, stops, DlTileMode::kMirror)); + paint.setColor(DlColor::kWhite().modulateOpacity(0.5)); + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(100, 610, 300, 690), 40, 40), paint); + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(160, 550, 240, 750), 40, 40), paint); + + paint.setColor(DlColor::kWhite().modulateOpacity(0.1)); + SkMatrix local_matrix = SkMatrix::Translate(520, 20); + DlImageColorSource image_source( + image, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &local_matrix); + paint.setColorSource(&image_source); + for (int i = 1; i <= 10; i++) { + int j = 11 - i; + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(720 - i * 20, 220 - j * 20, // + 720 + i * 20, 220 + j * 20), + i * 10, j * 10), + paint); + } + + paint.setColor(DlColor::kWhite().modulateOpacity(0.5)); + local_matrix = SkMatrix::Translate(800, 300); + DlImageColorSource image_source2( + image, DlTileMode::kRepeat, DlTileMode::kRepeat, + DlImageSampling::kNearestNeighbor, &local_matrix); + paint.setColorSource(&image_source2); + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(800, 410, 1000, 490), 40, 40), + paint); + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeLTRB(860, 350, 940, 550), 40, 40), paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + +TEST_P(AiksTest, SolidColorCirclesOvalsRRectsMaskBlurCorrectly) { + DisplayListBuilder builder; + builder.Scale(GetContentScale().x, GetContentScale().y); + DlPaint paint; + paint.setMaskFilter(DlBlurMaskFilter::Make(DlBlurStyle::kNormal, 1.0f)); + + builder.DrawPaint(DlPaint().setColor(DlColor::kWhite())); + + paint.setColor( + DlColor::RGBA(220.0f / 255.0f, 20.0f / 255.0f, 60.0f / 255.0f, 1.0f)); + Scalar y = 100.0f; + for (int i = 0; i < 5; i++) { + Scalar x = (i + 1) * 100; + Scalar radius = x / 10.0f; + builder.DrawRect(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // + radius, 60.0f - radius), + paint); + } + + paint.setColor(DlColor::kBlue()); + y += 100.0f; + for (int i = 0; i < 5; i++) { + Scalar x = (i + 1) * 100; + Scalar radius = x / 10.0f; + builder.DrawCircle({x + 25, y + 25}, radius, paint); + } + + paint.setColor(DlColor::kGreen()); + y += 100.0f; + for (int i = 0; i < 5; i++) { + Scalar x = (i + 1) * 100; + Scalar radius = x / 10.0f; + builder.DrawOval(SkRect::MakeXYWH(x + 25 - radius / 2, y + radius / 2, // + radius, 60.0f - radius), + paint); + } + + paint.setColor( + DlColor::RGBA(128.0f / 255.0f, 0.0f / 255.0f, 128.0f / 255.0f, 1.0f)); + y += 100.0f; + for (int i = 0; i < 5; i++) { + Scalar x = (i + 1) * 100; + Scalar radius = x / 20.0f; + builder.DrawRRect(SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), + radius, radius), + paint); + } + + paint.setColor( + DlColor::RGBA(255.0f / 255.0f, 165.0f / 255.0f, 0.0f / 255.0f, 1.0f)); + y += 100.0f; + for (int i = 0; i < 5; i++) { + Scalar x = (i + 1) * 100; + Scalar radius = x / 20.0f; + builder.DrawRRect( + SkRRect::MakeRectXY(SkRect::MakeXYWH(x, y, 60.0f, 60.0f), radius, 5.0f), + paint); + } + + auto dl = builder.Build(); + ASSERT_TRUE(OpenPlaygroundHere(dl)); +} + +} // namespace testing +} // namespace impeller diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_path_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_path_unittests.cc index c481a2d59f6..4723e76b963 100644 --- a/engine/src/flutter/impeller/display_list/aiks_dl_path_unittests.cc +++ b/engine/src/flutter/impeller/display_list/aiks_dl_path_unittests.cc @@ -246,7 +246,8 @@ TEST_P(AiksTest, SolidStrokesRenderCorrectly) { paint.setColor(DlColor::kWhite()); builder.DrawPaint(paint); - paint.setColor(DlColor(color.alpha, color.red, color.green, color.blue)); + paint.setColor( + DlColor::ARGB(color.alpha, color.red, color.green, color.blue)); paint.setDrawStyle(DlDrawStyle::kStroke); paint.setStrokeWidth(10); @@ -339,13 +340,13 @@ TEST_P(AiksTest, DrawLinesRenderCorrectly) { }; std::vector colors = { - DlColor{1, 0x1f / 255.0, 0.0, 0x5c / 255.0}, - DlColor{1, 0x5b / 255.0, 0.0, 0x60 / 255.0}, - DlColor{1, 0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0}, - DlColor{1, 0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0}, - DlColor{1, 0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0}, - DlColor{1, 0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0}, - DlColor{1, 0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0}}; + DlColor::ARGB(1, 0x1f / 255.0, 0.0, 0x5c / 255.0), + DlColor::ARGB(1, 0x5b / 255.0, 0.0, 0x60 / 255.0), + DlColor::ARGB(1, 0x87 / 255.0, 0x01 / 255.0, 0x60 / 255.0), + DlColor::ARGB(1, 0xac / 255.0, 0x25 / 255.0, 0x53 / 255.0), + DlColor::ARGB(1, 0xe1 / 255.0, 0x6b / 255.0, 0x5c / 255.0), + DlColor::ARGB(1, 0xf3 / 255.0, 0x90 / 255.0, 0x60 / 255.0), + DlColor::ARGB(1, 0xff / 255.0, 0xb5 / 255.0, 0x6b / 250.0)}; std::vector stops = { 0.0, (1.0 / 6.0) * 1, @@ -432,8 +433,8 @@ TEST_P(AiksTest, ArcWithZeroSweepAndBlur) { DlPaint paint; paint.setColor(DlColor::kRed()); - std::vector colors = {DlColor{1.0, 0.0, 0.0, 1.0}, - DlColor{0.0, 0.0, 0.0, 1.0}}; + std::vector colors = {DlColor::RGBA(1.0, 0.0, 0.0, 1.0), + DlColor::RGBA(0.0, 0.0, 0.0, 1.0)}; std::vector stops = {0.0, 1.0}; paint.setColorSource( diff --git a/engine/src/flutter/impeller/display_list/dl_dispatcher.cc b/engine/src/flutter/impeller/display_list/dl_dispatcher.cc index f1b6552b34c..2a2bfb2f786 100644 --- a/engine/src/flutter/impeller/display_list/dl_dispatcher.cc +++ b/engine/src/flutter/impeller/display_list/dl_dispatcher.cc @@ -723,6 +723,14 @@ void DlDispatcherBase::clipRect(const SkRect& rect, ToClipOperation(clip_op)); } +// |flutter::DlOpReceiver| +void DlDispatcherBase::clipOval(const SkRect& bounds, + ClipOp clip_op, + bool is_aa) { + GetCanvas().ClipOval(skia_conversions::ToRect(bounds), + ToClipOperation(clip_op)); +} + // |flutter::DlOpReceiver| void DlDispatcherBase::clipRRect(const SkRRect& rrect, ClipOp sk_op, diff --git a/engine/src/flutter/impeller/display_list/dl_dispatcher.h b/engine/src/flutter/impeller/display_list/dl_dispatcher.h index 7349ca7e4fd..294616b7487 100644 --- a/engine/src/flutter/impeller/display_list/dl_dispatcher.h +++ b/engine/src/flutter/impeller/display_list/dl_dispatcher.h @@ -123,6 +123,9 @@ class DlDispatcherBase : public flutter::DlOpReceiver { // |flutter::DlOpReceiver| void clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override; + // |flutter::DlOpReceiver| + void clipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) override; + // |flutter::DlOpReceiver| void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override; diff --git a/engine/src/flutter/testing/display_list_testing.cc b/engine/src/flutter/testing/display_list_testing.cc index 8cf84bc2702..ce831ca2478 100644 --- a/engine/src/flutter/testing/display_list_testing.cc +++ b/engine/src/flutter/testing/display_list_testing.cc @@ -742,6 +742,14 @@ void DisplayListStreamDispatcher::clipRect(const SkRect& rect, ClipOp clip_op, << "isaa: " << is_aa << ");" << std::endl; } +void DisplayListStreamDispatcher::clipOval(const SkRect& bounds, ClipOp clip_op, + bool is_aa) { + startl() << "clipOval(" + << bounds << ", " + << clip_op << ", " + << "isaa: " << is_aa + << ");" << std::endl; +} void DisplayListStreamDispatcher::clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) { diff --git a/engine/src/flutter/testing/display_list_testing.h b/engine/src/flutter/testing/display_list_testing.h index b710e1e22af..2ccb011dedc 100644 --- a/engine/src/flutter/testing/display_list_testing.h +++ b/engine/src/flutter/testing/display_list_testing.h @@ -118,6 +118,7 @@ class DisplayListStreamDispatcher final : public DlOpReceiver { void transformReset() override; void clipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override; + void clipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) override; void clipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override; void clipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override; diff --git a/engine/src/flutter/testing/mock_canvas.cc b/engine/src/flutter/testing/mock_canvas.cc index 695678e23a0..03627f722fb 100644 --- a/engine/src/flutter/testing/mock_canvas.cc +++ b/engine/src/flutter/testing/mock_canvas.cc @@ -214,6 +214,13 @@ void MockCanvas::ClipRect(const SkRect& rect, ClipOp op, bool is_aa) { state_stack_.back().clipRect(rect, op, is_aa); } +void MockCanvas::ClipOval(const SkRect& bounds, ClipOp op, bool is_aa) { + ClipEdgeStyle style = is_aa ? kSoftClipEdgeStyle : kHardClipEdgeStyle; + draw_calls_.emplace_back( + DrawCall{current_layer_, ClipOvalData{bounds, op, style}}); + state_stack_.back().clipOval(bounds, op, is_aa); +} + void MockCanvas::ClipRRect(const SkRRect& rrect, ClipOp op, bool is_aa) { ClipEdgeStyle style = is_aa ? kSoftClipEdgeStyle : kHardClipEdgeStyle; draw_calls_.emplace_back( @@ -520,6 +527,16 @@ std::ostream& operator<<(std::ostream& os, return os << data.rect << " " << data.clip_op << " " << data.style; } +bool operator==(const MockCanvas::ClipOvalData& a, + const MockCanvas::ClipOvalData& b) { + return a.bounds == b.bounds && a.clip_op == b.clip_op && a.style == b.style; +} + +std::ostream& operator<<(std::ostream& os, + const MockCanvas::ClipOvalData& data) { + return os << data.bounds << " " << data.clip_op << " " << data.style; +} + bool operator==(const MockCanvas::ClipRRectData& a, const MockCanvas::ClipRRectData& b) { return a.rrect == b.rrect && a.clip_op == b.clip_op && a.style == b.style; diff --git a/engine/src/flutter/testing/mock_canvas.h b/engine/src/flutter/testing/mock_canvas.h index f15e0ec54ad..7384d5ecf57 100644 --- a/engine/src/flutter/testing/mock_canvas.h +++ b/engine/src/flutter/testing/mock_canvas.h @@ -114,6 +114,12 @@ class MockCanvas final : public DlCanvas { ClipEdgeStyle style; }; + struct ClipOvalData { + SkRect bounds; + ClipOp clip_op; + ClipEdgeStyle style; + }; + struct ClipRRectData { SkRRect rrect; ClipOp clip_op; @@ -145,6 +151,7 @@ class MockCanvas final : public DlCanvas { DrawDisplayListData, DrawShadowData, ClipRectData, + ClipOvalData, ClipRRectData, ClipPathData, DrawPaintData>; @@ -206,6 +213,7 @@ class MockCanvas final : public DlCanvas { SkMatrix GetTransform() const override; void ClipRect(const SkRect& rect, ClipOp clip_op, bool is_aa) override; + void ClipOval(const SkRect& bounds, ClipOp clip_op, bool is_aa) override; void ClipRRect(const SkRRect& rrect, ClipOp clip_op, bool is_aa) override; void ClipPath(const SkPath& path, ClipOp clip_op, bool is_aa) override;