From 03dbc940c530347e755568f0eba87820ba764ade Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Fri, 28 Apr 2023 18:39:17 -0700 Subject: [PATCH 1/4] take advantage of DisplayList culling in Impeller --- display_list/display_list.cc | 5 +++ display_list/display_list.h | 1 + display_list/display_list_unittests.cc | 58 ++++++++++++------------- impeller/aiks/canvas.cc | 46 ++++++++++++++++---- impeller/aiks/canvas.h | 12 ++++- impeller/display_list/dl_dispatcher.cc | 14 +++++- impeller/display_list/dl_dispatcher.h | 4 ++ impeller/entity/entity_pass.h | 1 + shell/gpu/gpu_surface_metal_impeller.mm | 13 ++++-- 9 files changed, 109 insertions(+), 45 deletions(-) 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/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 6c69fe02cded5..37cbef5e506e3 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -23,15 +23,25 @@ namespace impeller { Canvas::Canvas() { - Initialize(); + Initialize(Rect::MakeLTRB(-1e9, -1e9, 1e9, 1e9)); +} + +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(Rect 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{.clip_bounds = 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.clip_bounds = xformation_stack_.back().clip_bounds; entry.stencil_depth = xformation_stack_.back().stencil_depth; if (create_subpass) { entry.is_subpass = true; @@ -109,6 +120,11 @@ const Matrix& Canvas::GetCurrentTransformation() const { return xformation_stack_.back().xformation; } +const Rect Canvas::GetCurrentLocalClipBounds() const { + Matrix inverse = xformation_stack_.back().xformation.Invert(); + return xformation_stack_.back().clip_bounds.TransformBounds(inverse); +} + void Canvas::Translate(const Vector3& offset) { Concat(Matrix::MakeTranslation(offset)); } @@ -257,21 +273,22 @@ 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); + ClipGeometry(Geometry::MakeFillPath(path), clip_op, path.GetBoundingBox()); } void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) { - ClipGeometry(Geometry::MakeRect(rect), clip_op); + ClipGeometry(Geometry::MakeRect(rect), clip_op, rect); } void Canvas::ClipRRect(const Rect& rect, Scalar corner_radius, Entity::ClipOperation clip_op) { - ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op); + ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op, rect); } void Canvas::ClipGeometry(std::unique_ptr geometry, - Entity::ClipOperation clip_op) { + Entity::ClipOperation clip_op, + std::optional geometry_bounds) { auto contents = std::make_shared(); contents->SetGeometry(std::move(geometry)); contents->SetClipOperation(clip_op); @@ -283,6 +300,19 @@ void Canvas::ClipGeometry(std::unique_ptr geometry, GetCurrentPass().AddEntity(entity); + if (geometry_bounds.has_value()) { + if (clip_op == Entity::ClipOperation::kIntersect) { + Rect transformed = + geometry_bounds.value().TransformBounds(GetCurrentTransformation()); + auto new_bounds = + xformation_stack_.back().clip_bounds.Intersection(transformed); + xformation_stack_.back().clip_bounds = new_bounds.value_or(Rect{}); + } + // else if kDifference we could chop off a side of the clip_bounds if + // the subtracted geometry was a rectangle and it spans the height or + // width of the current bounds. + } + ++xformation_stack_.back().stencil_depth; xformation_stack_.back().contains_clips = true; } @@ -364,7 +394,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..ed005ea644503 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -40,6 +40,10 @@ class Canvas { Canvas(); + explicit Canvas(Rect cull_rect); + + explicit Canvas(IRect cull_rect); + ~Canvas(); void Save(); @@ -57,6 +61,8 @@ class Canvas { const Matrix& GetCurrentTransformation() const; + const Rect GetCurrentLocalClipBounds() const; + void ResetTransform(); void Transform(const Matrix& xformation); @@ -135,8 +141,9 @@ class Canvas { EntityPass* current_pass_ = nullptr; std::deque xformation_stack_; std::shared_ptr lazy_glyph_atlas_; + Rect initial_cull_rect_; - void Initialize(); + void Initialize(Rect cull_rect); void Reset(); @@ -145,7 +152,8 @@ class Canvas { size_t GetStencilDepth() const; void ClipGeometry(std::unique_ptr geometry, - Entity::ClipOperation clip_op); + Entity::ClipOperation clip_op, + std::optional geometry_bounds); void Save(bool create_subpass, BlendMode = BlendMode::kSourceOver, diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index d3eb8f54ac257..650b2d839c67b 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,15 @@ void DlDispatcher::drawDisplayList( canvas_.SaveLayer(save_paint); } - display_list->Dispatch(*this); + if (display_list->rtree()) { + Rect clip_bounds = canvas_.GetCurrentLocalClipBounds(); + display_list->Dispatch( + *this, + SkRect::MakeLTRB(clip_bounds.GetLeft(), clip_bounds.GetTop(), + clip_bounds.GetRight(), clip_bounds.GetBottom())); + } 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..045b5ba2047a7 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -236,6 +236,7 @@ class EntityPass { struct CanvasStackEntry { Matrix xformation; + Rect clip_bounds; size_t stencil_depth = 0u; bool is_subpass = false; bool contains_clips = false; diff --git a/shell/gpu/gpu_surface_metal_impeller.mm b/shell/gpu/gpu_surface_metal_impeller.mm index ee503202c6de1..278aa5b40cdbe 100644 --- a/shell/gpu/gpu_surface_metal_impeller.mm +++ b/shell/gpu/gpu_surface_metal_impeller.mm @@ -104,9 +104,9 @@ damage_[texture] = SkIRect::MakeEmpty(); } + std::optional buffer_damage = surface_frame.submit_info().buffer_damage; std::optional clip_rect; - if (surface_frame.submit_info().buffer_damage.has_value()) { - auto buffer_damage = surface_frame.submit_info().buffer_damage; + if (buffer_damage.has_value()) { clip_rect = impeller::IRect::MakeXYWH(buffer_damage->x(), buffer_damage->y(), buffer_damage->width(), buffer_damage->height()); } @@ -118,8 +118,13 @@ return surface->Present(); } - impeller::DlDispatcher impeller_dispatcher; - display_list->Dispatch(impeller_dispatcher); + if (!buffer_damage.has_value()) { + auto size = surface->GetSize(); + buffer_damage = SkIRect::MakeWH(size.width, size.height); + clip_rect = impeller::IRect::MakeXYWH(0, 0, size.width, size.height); + } + impeller::DlDispatcher impeller_dispatcher(clip_rect.value()); + display_list->Dispatch(impeller_dispatcher, buffer_damage.value()); auto picture = impeller_dispatcher.EndRecordingAsPicture(); return renderer->Render( From 81af5eb8725dec8534829fcd8375f4ff0d48ad14 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Sun, 30 Apr 2023 03:07:35 -0700 Subject: [PATCH 2/4] make cull_rect std::optional and add unit tests of Canvas clip bounds methods --- ci/licenses_golden/excluded_files | 1 + impeller/aiks/BUILD.gn | 5 +- impeller/aiks/canvas.cc | 105 ++++++-- impeller/aiks/canvas.h | 21 +- impeller/aiks/canvas_unittests.cc | 337 +++++++++++++++++++++++++ impeller/display_list/dl_dispatcher.cc | 21 +- impeller/entity/entity_pass.h | 8 - impeller/geometry/rect.h | 8 +- 8 files changed, 457 insertions(+), 49 deletions(-) create mode 100644 impeller/aiks/canvas_unittests.cc 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/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 37cbef5e506e3..9cbe4e82eaf9e 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -23,7 +23,7 @@ namespace impeller { Canvas::Canvas() { - Initialize(Rect::MakeLTRB(-1e9, -1e9, 1e9, 1e9)); + Initialize(std::nullopt); } Canvas::Canvas(Rect cull_rect) { @@ -37,11 +37,11 @@ Canvas::Canvas(IRect cull_rect) { Canvas::~Canvas() = default; -void Canvas::Initialize(Rect cull_rect) { +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{.clip_bounds = cull_rect}); + 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); @@ -64,7 +64,7 @@ void Canvas::Save( std::optional backdrop_filter) { auto entry = CanvasStackEntry{}; entry.xformation = xformation_stack_.back().xformation; - entry.clip_bounds = xformation_stack_.back().clip_bounds; + entry.cull_rect = xformation_stack_.back().cull_rect; entry.stencil_depth = xformation_stack_.back().stencil_depth; if (create_subpass) { entry.is_subpass = true; @@ -120,9 +120,13 @@ const Matrix& Canvas::GetCurrentTransformation() const { return xformation_stack_.back().xformation; } -const Rect Canvas::GetCurrentLocalClipBounds() const { - Matrix inverse = xformation_stack_.back().xformation.Invert(); - return xformation_stack_.back().clip_bounds.TransformBounds(inverse); +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) { @@ -273,22 +277,61 @@ 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, path.GetBoundingBox()); + 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, rect); + 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, rect); + 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, - Entity::ClipOperation clip_op, - std::optional geometry_bounds) { + Entity::ClipOperation clip_op) { auto contents = std::make_shared(); contents->SetGeometry(std::move(geometry)); contents->SetClipOperation(clip_op); @@ -300,23 +343,35 @@ void Canvas::ClipGeometry(std::unique_ptr geometry, GetCurrentPass().AddEntity(entity); - if (geometry_bounds.has_value()) { - if (clip_op == Entity::ClipOperation::kIntersect) { - Rect transformed = - geometry_bounds.value().TransformBounds(GetCurrentTransformation()); - auto new_bounds = - xformation_stack_.back().clip_bounds.Intersection(transformed); - xformation_stack_.back().clip_bounds = new_bounds.value_or(Rect{}); - } - // else if kDifference we could chop off a side of the clip_bounds if - // the subtracted geometry was a rectangle and it spans the height or - // width of the current bounds. - } - ++xformation_stack_.back().stencil_depth; 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()); diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index ed005ea644503..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 { @@ -61,7 +70,7 @@ class Canvas { const Matrix& GetCurrentTransformation() const; - const Rect GetCurrentLocalClipBounds() const; + const std::optional GetCurrentLocalCullingBounds() const; void ResetTransform(); @@ -141,9 +150,9 @@ class Canvas { EntityPass* current_pass_ = nullptr; std::deque xformation_stack_; std::shared_ptr lazy_glyph_atlas_; - Rect initial_cull_rect_; + std::optional initial_cull_rect_; - void Initialize(Rect cull_rect); + void Initialize(std::optional cull_rect); void Reset(); @@ -152,8 +161,10 @@ class Canvas { size_t GetStencilDepth() const; void ClipGeometry(std::unique_ptr geometry, - Entity::ClipOperation clip_op, - std::optional geometry_bounds); + Entity::ClipOperation clip_op); + + void IntersectCulling(Rect clip_bounds); + void SubtractCulling(Rect clip_bounds); void Save(bool create_subpass, BlendMode = BlendMode::kSourceOver, 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 650b2d839c67b..157efbaee69a7 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -1057,12 +1057,21 @@ void DlDispatcher::drawDisplayList( canvas_.SaveLayer(save_paint); } - if (display_list->rtree()) { - Rect clip_bounds = canvas_.GetCurrentLocalClipBounds(); - display_list->Dispatch( - *this, - SkRect::MakeLTRB(clip_bounds.GetLeft(), clip_bounds.GetTop(), - clip_bounds.GetRight(), clip_bounds.GetBottom())); + 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); } diff --git a/impeller/entity/entity_pass.h b/impeller/entity/entity_pass.h index 045b5ba2047a7..649151de0a14b 100644 --- a/impeller/entity/entity_pass.h +++ b/impeller/entity/entity_pass.h @@ -234,12 +234,4 @@ class EntityPass { FML_DISALLOW_COPY_AND_ASSIGN(EntityPass); }; -struct CanvasStackEntry { - Matrix xformation; - Rect clip_bounds; - 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); } From 77ad0e706c370d0ad1d5d39c646d0be81ff8e496 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 1 May 2023 13:34:17 -0700 Subject: [PATCH 3/4] fix warnings about DL with no RTree while running gallery --- flow/surface_frame.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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(); } } From fa1cfa8bb52445e30057308436306a1140775319 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Mon, 1 May 2023 14:54:57 -0700 Subject: [PATCH 4/4] fix cull_rect handling in Impeller submit callback --- impeller/renderer/backend/metal/surface_mtl.h | 3 +++ impeller/renderer/backend/metal/surface_mtl.mm | 5 +++++ shell/gpu/gpu_surface_metal_impeller.mm | 15 ++++++--------- 3 files changed, 14 insertions(+), 9 deletions(-) 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 278aa5b40cdbe..8d5a54952682e 100644 --- a/shell/gpu/gpu_surface_metal_impeller.mm +++ b/shell/gpu/gpu_surface_metal_impeller.mm @@ -104,9 +104,9 @@ damage_[texture] = SkIRect::MakeEmpty(); } - std::optional buffer_damage = surface_frame.submit_info().buffer_damage; std::optional clip_rect; - if (buffer_damage.has_value()) { + if (surface_frame.submit_info().buffer_damage.has_value()) { + auto buffer_damage = surface_frame.submit_info().buffer_damage; clip_rect = impeller::IRect::MakeXYWH(buffer_damage->x(), buffer_damage->y(), buffer_damage->width(), buffer_damage->height()); } @@ -118,13 +118,10 @@ return surface->Present(); } - if (!buffer_damage.has_value()) { - auto size = surface->GetSize(); - buffer_damage = SkIRect::MakeWH(size.width, size.height); - clip_rect = impeller::IRect::MakeXYWH(0, 0, size.width, size.height); - } - impeller::DlDispatcher impeller_dispatcher(clip_rect.value()); - display_list->Dispatch(impeller_dispatcher, buffer_damage.value()); + 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(