diff --git a/ci/licenses_golden/excluded_files b/ci/licenses_golden/excluded_files index 2917f5ca964ab..00385d59eef87 100644 --- a/ci/licenses_golden/excluded_files +++ b/ci/licenses_golden/excluded_files @@ -120,6 +120,7 @@ ../../../flutter/impeller/.gitignore ../../../flutter/impeller/README.md ../../../flutter/impeller/aiks/aiks_unittests.cc +../../../flutter/impeller/aiks/canvas_unittests.cc ../../../flutter/impeller/archivist/archivist_unittests.cc ../../../flutter/impeller/base/README.md ../../../flutter/impeller/base/base_unittests.cc diff --git a/display_list/display_list.cc b/display_list/display_list.cc index 2425f699a16c0..378f86804f260 100644 --- a/display_list/display_list.cc +++ b/display_list/display_list.cc @@ -140,6 +140,11 @@ void DisplayList::Dispatch(DlOpReceiver& receiver) const { Dispatch(receiver, ptr, ptr + byte_count_, NopCuller::instance); } +void DisplayList::Dispatch(DlOpReceiver& receiver, + const SkIRect& cull_rect) const { + Dispatch(receiver, SkRect::Make(cull_rect)); +} + void DisplayList::Dispatch(DlOpReceiver& receiver, const SkRect& cull_rect) const { if (cull_rect.isEmpty()) { diff --git a/display_list/display_list.h b/display_list/display_list.h index 6820434418b2d..4a6f339fdea0d 100644 --- a/display_list/display_list.h +++ b/display_list/display_list.h @@ -235,6 +235,7 @@ class DisplayList : public SkRefCnt { void Dispatch(DlOpReceiver& ctx) const; void Dispatch(DlOpReceiver& ctx, const SkRect& cull_rect) const; + void Dispatch(DlOpReceiver& ctx, const SkIRect& cull_rect) const; // From historical behavior, SkPicture always included nested bytes, // but nested ops are only included if requested. The defaults used diff --git a/display_list/display_list_unittests.cc b/display_list/display_list_unittests.cc index 35bf68b32c7bc..f2e12f74d8d20 100644 --- a/display_list/display_list_unittests.cc +++ b/display_list/display_list_unittests.cc @@ -2532,81 +2532,79 @@ TEST_F(DisplayListTest, RTreeRenderCulling) { main_receiver.drawRect({20, 20, 30, 30}); auto main = main_builder.Build(); + auto test = [main](SkIRect cull_rect, const sk_sp& expected) { + { // Test SkIRect culling + DisplayListBuilder culling_builder; + main->Dispatch(ToReceiver(culling_builder), cull_rect); + + EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + } + + { // Test SkRect culling + DisplayListBuilder culling_builder; + main->Dispatch(ToReceiver(culling_builder), SkRect::Make(cull_rect)); + + EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + } + }; + { // No rects - SkRect cull_rect = {11, 11, 19, 19}; + SkIRect cull_rect = {11, 11, 19, 19}; DisplayListBuilder expected_builder; auto expected = expected_builder.Build(); - DisplayListBuilder culling_builder(cull_rect); - main->Dispatch(ToReceiver(culling_builder), cull_rect); - - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + test(cull_rect, expected); } { // Rect 1 - SkRect cull_rect = {9, 9, 19, 19}; + SkIRect cull_rect = {9, 9, 19, 19}; DisplayListBuilder expected_builder; DlOpReceiver& expected_receiver = ToReceiver(expected_builder); expected_receiver.drawRect({0, 0, 10, 10}); auto expected = expected_builder.Build(); - DisplayListBuilder culling_builder(cull_rect); - main->Dispatch(ToReceiver(culling_builder), cull_rect); - - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + test(cull_rect, expected); } { // Rect 2 - SkRect cull_rect = {11, 9, 21, 19}; + SkIRect cull_rect = {11, 9, 21, 19}; DisplayListBuilder expected_builder; DlOpReceiver& expected_receiver = ToReceiver(expected_builder); expected_receiver.drawRect({20, 0, 30, 10}); auto expected = expected_builder.Build(); - DisplayListBuilder culling_builder(cull_rect); - main->Dispatch(ToReceiver(culling_builder), cull_rect); - - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + test(cull_rect, expected); } { // Rect 3 - SkRect cull_rect = {9, 11, 19, 21}; + SkIRect cull_rect = {9, 11, 19, 21}; DisplayListBuilder expected_builder; DlOpReceiver& expected_receiver = ToReceiver(expected_builder); expected_receiver.drawRect({0, 20, 10, 30}); auto expected = expected_builder.Build(); - DisplayListBuilder culling_builder(cull_rect); - main->Dispatch(ToReceiver(culling_builder), cull_rect); - - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + test(cull_rect, expected); } { // Rect 4 - SkRect cull_rect = {11, 11, 21, 21}; + SkIRect cull_rect = {11, 11, 21, 21}; DisplayListBuilder expected_builder; DlOpReceiver& expected_receiver = ToReceiver(expected_builder); expected_receiver.drawRect({20, 20, 30, 30}); auto expected = expected_builder.Build(); - DisplayListBuilder culling_builder(cull_rect); - main->Dispatch(ToReceiver(culling_builder), cull_rect); - - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected)); + test(cull_rect, expected); } { // All 4 rects - SkRect cull_rect = {9, 9, 21, 21}; - - DisplayListBuilder culling_builder(cull_rect); - main->Dispatch(ToReceiver(culling_builder), cull_rect); + SkIRect cull_rect = {9, 9, 21, 21}; - EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), main)); + test(cull_rect, main); } } diff --git a/flow/surface_frame.cc b/flow/surface_frame.cc index d11c92ef17440..b59f47be454de 100644 --- a/flow/surface_frame.cc +++ b/flow/surface_frame.cc @@ -31,7 +31,8 @@ SurfaceFrame::SurfaceFrame(sk_sp surface, canvas_ = &adapter_; } else if (display_list_fallback) { FML_DCHECK(!frame_size.isEmpty()); - dl_builder_ = sk_make_sp(SkRect::Make(frame_size)); + dl_builder_ = + sk_make_sp(SkRect::Make(frame_size), true); canvas_ = dl_builder_.get(); } } diff --git a/impeller/aiks/BUILD.gn b/impeller/aiks/BUILD.gn index dc90d4344728c..b0fe875cfef7d 100644 --- a/impeller/aiks/BUILD.gn +++ b/impeller/aiks/BUILD.gn @@ -47,7 +47,10 @@ impeller_component("aiks_playground") { impeller_component("aiks_unittests") { testonly = true - sources = [ "aiks_unittests.cc" ] + sources = [ + "aiks_unittests.cc", + "canvas_unittests.cc", + ] deps = [ ":aiks", ":aiks_playground", diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 6c69fe02cded5..9cbe4e82eaf9e 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -23,15 +23,25 @@ namespace impeller { Canvas::Canvas() { - Initialize(); + Initialize(std::nullopt); +} + +Canvas::Canvas(Rect cull_rect) { + Initialize(cull_rect); +} + +Canvas::Canvas(IRect cull_rect) { + Initialize(Rect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(), + cull_rect.GetRight(), cull_rect.GetBottom())); } Canvas::~Canvas() = default; -void Canvas::Initialize() { +void Canvas::Initialize(std::optional cull_rect) { + initial_cull_rect_ = cull_rect; base_pass_ = std::make_unique(); current_pass_ = base_pass_.get(); - xformation_stack_.emplace_back(CanvasStackEntry{}); + xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect}); lazy_glyph_atlas_ = std::make_shared(); FML_DCHECK(GetSaveCount() == 1u); FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u); @@ -54,6 +64,7 @@ void Canvas::Save( std::optional backdrop_filter) { auto entry = CanvasStackEntry{}; entry.xformation = xformation_stack_.back().xformation; + entry.cull_rect = xformation_stack_.back().cull_rect; entry.stencil_depth = xformation_stack_.back().stencil_depth; if (create_subpass) { entry.is_subpass = true; @@ -109,6 +120,15 @@ const Matrix& Canvas::GetCurrentTransformation() const { return xformation_stack_.back().xformation; } +const std::optional Canvas::GetCurrentLocalCullingBounds() const { + auto cull_rect = xformation_stack_.back().cull_rect; + if (cull_rect.has_value()) { + Matrix inverse = xformation_stack_.back().xformation.Invert(); + cull_rect = cull_rect.value().TransformBounds(inverse); + } + return cull_rect; +} + void Canvas::Translate(const Vector3& offset) { Concat(Matrix::MakeTranslation(offset)); } @@ -258,16 +278,56 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) { void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) { ClipGeometry(Geometry::MakeFillPath(path), clip_op); + if (clip_op == Entity::ClipOperation::kIntersect) { + auto bounds = path.GetBoundingBox(); + if (bounds.has_value()) { + IntersectCulling(bounds.value()); + } + } } void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) { ClipGeometry(Geometry::MakeRect(rect), clip_op); + switch (clip_op) { + case Entity::ClipOperation::kIntersect: + IntersectCulling(rect); + break; + case Entity::ClipOperation::kDifference: + SubtractCulling(rect); + break; + } } void Canvas::ClipRRect(const Rect& rect, Scalar corner_radius, Entity::ClipOperation clip_op) { ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op); + switch (clip_op) { + case Entity::ClipOperation::kIntersect: + IntersectCulling(rect); + break; + case Entity::ClipOperation::kDifference: + if (corner_radius <= 0) { + SubtractCulling(rect); + } else { + // We subtract the inner "tall" and "wide" rectangle pieces + // that fit inside the corners which cover the greatest area + // without involving the curved corners + // Since this is a subtract operation, we can subtract each + // rectangle piece individually without fear of interference. + if (corner_radius * 2 < rect.size.width) { + SubtractCulling(Rect::MakeLTRB( + rect.GetLeft() + corner_radius, rect.GetTop(), + rect.GetRight() - corner_radius, rect.GetBottom())); + } + if (corner_radius * 2 < rect.size.height) { + SubtractCulling(Rect::MakeLTRB( + rect.GetLeft(), rect.GetTop() + corner_radius, // + rect.GetRight(), rect.GetBottom() - corner_radius)); + } + } + break; + } } void Canvas::ClipGeometry(std::unique_ptr geometry, @@ -287,6 +347,31 @@ void Canvas::ClipGeometry(std::unique_ptr geometry, xformation_stack_.back().contains_clips = true; } +void Canvas::IntersectCulling(Rect clip_rect) { + clip_rect = clip_rect.TransformBounds(GetCurrentTransformation()); + std::optional& cull_rect = xformation_stack_.back().cull_rect; + if (cull_rect.has_value()) { + cull_rect = cull_rect + .value() // + .Intersection(clip_rect) // + .value_or(Rect{}); + } else { + cull_rect = clip_rect; + } +} + +void Canvas::SubtractCulling(Rect clip_rect) { + std::optional& cull_rect = xformation_stack_.back().cull_rect; + if (cull_rect.has_value()) { + clip_rect = clip_rect.TransformBounds(GetCurrentTransformation()); + cull_rect = cull_rect + .value() // + .Cutout(clip_rect) // + .value_or(Rect{}); + } + // else (no cull) diff (any clip) is non-rectangular +} + void Canvas::RestoreClip() { Entity entity; entity.SetTransformation(GetCurrentTransformation()); @@ -364,7 +449,7 @@ Picture Canvas::EndRecordingAsPicture() { picture.pass = std::move(base_pass_); Reset(); - Initialize(); + Initialize(initial_cull_rect_); return picture; } diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 7a6fc349b2248..54be12491b09d 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -28,6 +28,15 @@ namespace impeller { class Entity; +struct CanvasStackEntry { + Matrix xformation; + // |cull_rect| is conservative screen-space bounds of the clipped output area + std::optional cull_rect; + size_t stencil_depth = 0u; + bool is_subpass = false; + bool contains_clips = false; +}; + class Canvas { public: struct DebugOptions { @@ -40,6 +49,10 @@ class Canvas { Canvas(); + explicit Canvas(Rect cull_rect); + + explicit Canvas(IRect cull_rect); + ~Canvas(); void Save(); @@ -57,6 +70,8 @@ class Canvas { const Matrix& GetCurrentTransformation() const; + const std::optional GetCurrentLocalCullingBounds() const; + void ResetTransform(); void Transform(const Matrix& xformation); @@ -135,8 +150,9 @@ class Canvas { EntityPass* current_pass_ = nullptr; std::deque xformation_stack_; std::shared_ptr lazy_glyph_atlas_; + std::optional initial_cull_rect_; - void Initialize(); + void Initialize(std::optional cull_rect); void Reset(); @@ -147,6 +163,9 @@ class Canvas { void ClipGeometry(std::unique_ptr geometry, Entity::ClipOperation clip_op); + void IntersectCulling(Rect clip_bounds); + void SubtractCulling(Rect clip_bounds); + void Save(bool create_subpass, BlendMode = BlendMode::kSourceOver, std::optional backdrop_filter = diff --git a/impeller/aiks/canvas_unittests.cc b/impeller/aiks/canvas_unittests.cc new file mode 100644 index 0000000000000..9c0f07acd406a --- /dev/null +++ b/impeller/aiks/canvas_unittests.cc @@ -0,0 +1,337 @@ +// 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/testing/testing.h" +#include "impeller/aiks/canvas.h" +#include "impeller/geometry/path_builder.h" + +namespace impeller { +namespace testing { + +using AiksCanvasTest = ::testing::Test; + +TEST(AiksCanvasTest, EmptyCullRect) { + Canvas canvas; + + ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value()); +} + +TEST(AiksCanvasTest, InitialCullRect) { + Rect initial_cull(0, 0, 10, 10); + + Canvas canvas(initial_cull); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), initial_cull); +} + +TEST(AiksCanvasTest, TranslatedCullRect) { + Rect initial_cull(5, 5, 10, 10); + Rect translated_cull(0, 0, 10, 10); + + Canvas canvas(initial_cull); + canvas.Translate(Vector3(5, 5, 0)); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), translated_cull); +} + +TEST(AiksCanvasTest, ScaledCullRect) { + Rect initial_cull(5, 5, 10, 10); + Rect scaled_cull(10, 10, 20, 20); + + Canvas canvas(initial_cull); + canvas.Scale(Vector2(0.5, 0.5)); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), scaled_cull); +} + +TEST(AiksCanvasTest, RectClipIntersectAgainstEmptyCullRect) { + Rect rect_clip(5, 5, 10, 10); + + Canvas canvas; + canvas.ClipRect(rect_clip, Entity::ClipOperation::kIntersect); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), rect_clip); +} + +TEST(AiksCanvasTest, RectClipDiffAgainstEmptyCullRect) { + Rect rect_clip(5, 5, 10, 10); + + Canvas canvas; + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value()); +} + +TEST(AiksCanvasTest, RectClipIntersectAgainstCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, 5, 10, 10); + Rect result_cull(5, 5, 5, 5); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kIntersect); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffAgainstNonCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, 5, 10, 10); + Rect result_cull(0, 0, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffAboveCullRect) { + Rect initial_cull(5, 5, 10, 10); + Rect rect_clip(0, 0, 20, 4); + Rect result_cull(5, 5, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffBelowCullRect) { + Rect initial_cull(5, 5, 10, 10); + Rect rect_clip(0, 16, 20, 4); + Rect result_cull(5, 5, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffLeftOfCullRect) { + Rect initial_cull(5, 5, 10, 10); + Rect rect_clip(0, 0, 4, 20); + Rect result_cull(5, 5, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffRightOfCullRect) { + Rect initial_cull(5, 5, 10, 10); + Rect rect_clip(16, 0, 4, 20); + Rect result_cull(5, 5, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffAgainstVCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, 0, 10, 10); + Rect result_cull(0, 0, 5, 10); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RectClipDiffAgainstHCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(0, 5, 10, 10); + Rect result_cull(0, 0, 10, 5); + + Canvas canvas(initial_cull); + canvas.ClipRect(rect_clip, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RRectClipIntersectAgainstEmptyCullRect) { + Rect rect_clip(5, 5, 10, 10); + + Canvas canvas; + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kIntersect); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), rect_clip); +} + +TEST(AiksCanvasTest, RRectClipDiffAgainstEmptyCullRect) { + Rect rect_clip(5, 5, 10, 10); + + Canvas canvas; + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference); + + ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value()); +} + +TEST(AiksCanvasTest, RRectClipIntersectAgainstCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, 5, 10, 10); + Rect result_cull(5, 5, 5, 5); + + Canvas canvas(initial_cull); + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kIntersect); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RRectClipDiffAgainstNonCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, 5, 10, 10); + Rect result_cull(0, 0, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RRectClipDiffAgainstVPartiallyCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, 0, 10, 10); + Rect result_cull(0, 0, 6, 10); + + Canvas canvas(initial_cull); + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RRectClipDiffAgainstVFullyCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(5, -2, 10, 14); + Rect result_cull(0, 0, 5, 10); + + Canvas canvas(initial_cull); + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RRectClipDiffAgainstHPartiallyCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(0, 5, 10, 10); + Rect result_cull(0, 0, 10, 6); + + Canvas canvas(initial_cull); + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, RRectClipDiffAgainstHFullyCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + Rect rect_clip(-2, 5, 14, 10); + Rect result_cull(0, 0, 10, 5); + + Canvas canvas(initial_cull); + canvas.ClipRRect(rect_clip, 1, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, PathClipIntersectAgainstEmptyCullRect) { + PathBuilder builder; + builder.AddRect({5, 5, 1, 1}); + builder.AddRect({5, 14, 1, 1}); + builder.AddRect({14, 5, 1, 1}); + builder.AddRect({14, 14, 1, 1}); + Path path = builder.TakePath(); + Rect rect_clip(5, 5, 10, 10); + + Canvas canvas; + canvas.ClipPath(path, Entity::ClipOperation::kIntersect); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), rect_clip); +} + +TEST(AiksCanvasTest, PathClipDiffAgainstEmptyCullRect) { + PathBuilder builder; + builder.AddRect({5, 5, 1, 1}); + builder.AddRect({5, 14, 1, 1}); + builder.AddRect({14, 5, 1, 1}); + builder.AddRect({14, 14, 1, 1}); + Path path = builder.TakePath(); + + Canvas canvas; + canvas.ClipPath(path, Entity::ClipOperation::kDifference); + + ASSERT_FALSE(canvas.GetCurrentLocalCullingBounds().has_value()); +} + +TEST(AiksCanvasTest, PathClipIntersectAgainstCullRect) { + Rect initial_cull(0, 0, 10, 10); + PathBuilder builder; + builder.AddRect({5, 5, 1, 1}); + builder.AddRect({5, 14, 1, 1}); + builder.AddRect({14, 5, 1, 1}); + builder.AddRect({14, 14, 1, 1}); + Path path = builder.TakePath(); + Rect result_cull(5, 5, 5, 5); + + Canvas canvas(initial_cull); + canvas.ClipPath(path, Entity::ClipOperation::kIntersect); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, PathClipDiffAgainstNonCoveredCullRect) { + Rect initial_cull(0, 0, 10, 10); + PathBuilder builder; + builder.AddRect({5, 5, 1, 1}); + builder.AddRect({5, 14, 1, 1}); + builder.AddRect({14, 5, 1, 1}); + builder.AddRect({14, 14, 1, 1}); + Path path = builder.TakePath(); + Rect result_cull(0, 0, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipPath(path, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +TEST(AiksCanvasTest, PathClipDiffAgainstFullyCoveredCullRect) { + Rect initial_cull(5, 5, 10, 10); + PathBuilder builder; + builder.AddRect({0, 0, 100, 100}); + Path path = builder.TakePath(); + // Diff clip of Paths is ignored due to complexity + Rect result_cull(5, 5, 10, 10); + + Canvas canvas(initial_cull); + canvas.ClipPath(path, Entity::ClipOperation::kDifference); + + ASSERT_TRUE(canvas.GetCurrentLocalCullingBounds().has_value()); + ASSERT_EQ(canvas.GetCurrentLocalCullingBounds().value(), result_cull); +} + +} // namespace testing +} // namespace impeller diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index d3eb8f54ac257..157efbaee69a7 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -43,6 +43,10 @@ namespace impeller { DlDispatcher::DlDispatcher() = default; +DlDispatcher::DlDispatcher(Rect cull_rect) : canvas_(cull_rect) {} + +DlDispatcher::DlDispatcher(IRect cull_rect) : canvas_(cull_rect) {} + DlDispatcher::~DlDispatcher() = default; static BlendMode ToBlendMode(flutter::DlBlendMode mode) { @@ -1053,7 +1057,24 @@ void DlDispatcher::drawDisplayList( canvas_.SaveLayer(save_paint); } - display_list->Dispatch(*this); + if (display_list->has_rtree()) { + // The canvas remembers the screen-space culling bounds clipped by + // the surface and the history of clip calls. DisplayList can cull + // the ops based on a rectangle expressed in its "destination bounds" + // so we need the canvas to transform those into the current local + // coordinate space into which the DisplayList will be rendered. + auto cull_bounds = canvas_.GetCurrentLocalCullingBounds(); + if (cull_bounds.has_value()) { + Rect cull_rect = cull_bounds.value(); + display_list->Dispatch( + *this, SkRect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(), + cull_rect.GetRight(), cull_rect.GetBottom())); + } else { + display_list->Dispatch(*this); + } + } else { + display_list->Dispatch(*this); + } // Restore all saved state back to what it was before we interpreted // the display_list diff --git a/impeller/display_list/dl_dispatcher.h b/impeller/display_list/dl_dispatcher.h index 0fb0d54180dc9..28db030885a6d 100644 --- a/impeller/display_list/dl_dispatcher.h +++ b/impeller/display_list/dl_dispatcher.h @@ -15,6 +15,10 @@ class DlDispatcher final : public flutter::DlOpReceiver { public: DlDispatcher(); + explicit DlDispatcher(Rect cull_rect); + + explicit DlDispatcher(IRect cull_rect); + ~DlDispatcher(); Picture EndRecordingAsPicture(); diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 4868128a680cf..649151de0a14b 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -234,11 +234,4 @@ class EntityPass { FML_DISALLOW_COPY_AND_ASSIGN(EntityPass); }; -struct CanvasStackEntry { - Matrix xformation; - size_t stencil_depth = 0u; - bool is_subpass = false; - bool contains_clips = false; -}; - } // namespace impeller diff --git a/impeller/geometry/rect.h b/impeller/geometry/rect.h index c3468b7a0de3b..8b74e5edb40f9 100644 --- a/impeller/geometry/rect.h +++ b/impeller/geometry/rect.h @@ -224,21 +224,21 @@ struct TRect { // Full cutout. return std::nullopt; } - if (b_top <= a_top) { + if (b_top <= a_top && b_bottom > a_top) { // Cuts off the top. return TRect::MakeLTRB(a_left, b_bottom, a_right, a_bottom); } - if (b_bottom >= b_bottom) { + if (b_bottom >= a_bottom && b_top < a_bottom) { // Cuts out the bottom. return TRect::MakeLTRB(a_left, a_top, a_right, b_top); } } if (b_top <= a_top && b_bottom >= a_bottom) { - if (b_left <= a_left) { + if (b_left <= a_left && b_right > a_left) { // Cuts out the left. return TRect::MakeLTRB(b_right, a_top, a_right, a_bottom); } - if (b_right >= a_right) { + if (b_right >= a_right && b_left < a_right) { // Cuts out the right. return TRect::MakeLTRB(a_left, a_top, b_left, a_bottom); } diff --git a/impeller/renderer/backend/metal/surface_mtl.h b/impeller/renderer/backend/metal/surface_mtl.h index 72bdd3e70e3c1..cebc43e340451 100644 --- a/impeller/renderer/backend/metal/surface_mtl.h +++ b/impeller/renderer/backend/metal/surface_mtl.h @@ -48,6 +48,9 @@ class SurfaceMTL final : public Surface { id drawable() const { return drawable_; } + // Returns a Rect defining the area of the surface in device pixels + IRect coverage() const; + // |Surface| bool Present() const override; diff --git a/impeller/renderer/backend/metal/surface_mtl.mm b/impeller/renderer/backend/metal/surface_mtl.mm index 92c075176fca2..4bf510cdfe304 100644 --- a/impeller/renderer/backend/metal/surface_mtl.mm +++ b/impeller/renderer/backend/metal/surface_mtl.mm @@ -148,6 +148,11 @@ new SurfaceMTL(context->weak_from_this(), render_target_desc, resolve_tex, return true; } +// |Surface| +IRect SurfaceMTL::coverage() const { + return IRect::MakeSize(resolve_texture_->GetSize()); +} + // |Surface| bool SurfaceMTL::Present() const { if (drawable_ == nil) { diff --git a/shell/gpu/gpu_surface_metal_impeller.mm b/shell/gpu/gpu_surface_metal_impeller.mm index ee503202c6de1..8d5a54952682e 100644 --- a/shell/gpu/gpu_surface_metal_impeller.mm +++ b/shell/gpu/gpu_surface_metal_impeller.mm @@ -118,8 +118,10 @@ return surface->Present(); } - impeller::DlDispatcher impeller_dispatcher; - display_list->Dispatch(impeller_dispatcher); + impeller::IRect cull_rect = surface->coverage(); + SkIRect sk_cull_rect = SkIRect::MakeWH(cull_rect.size.width, cull_rect.size.height); + impeller::DlDispatcher impeller_dispatcher(cull_rect); + display_list->Dispatch(impeller_dispatcher, sk_cull_rect); auto picture = impeller_dispatcher.EndRecordingAsPicture(); return renderer->Render(