[Impeller] take advantage of DisplayList culling (flutter/engine#41606)

Switching the calls to dispatch into an Impeller Dispatcher to use a cull rect to enable pre-culling of the out-of-bounds ops.

This change showed an improvement of around 2x on the rendering performance of the non-intersecting platform view benchmark, but that was measured without the recent changes to the destructive blend modes in Impeller renderer.
This commit is contained in:
Jim Graham 2023-05-01 16:27:52 -07:00 committed by GitHub
parent 3b8e1ec80f
commit 6ad6014776
16 changed files with 529 additions and 51 deletions

View File

@ -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

View File

@ -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()) {

View File

@ -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

View File

@ -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<DisplayList>& 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};
SkIRect cull_rect = {9, 9, 21, 21};
DisplayListBuilder culling_builder(cull_rect);
main->Dispatch(ToReceiver(culling_builder), cull_rect);
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), main));
test(cull_rect, main);
}
}

View File

@ -31,7 +31,8 @@ SurfaceFrame::SurfaceFrame(sk_sp<SkSurface> surface,
canvas_ = &adapter_;
} else if (display_list_fallback) {
FML_DCHECK(!frame_size.isEmpty());
dl_builder_ = sk_make_sp<DisplayListBuilder>(SkRect::Make(frame_size));
dl_builder_ =
sk_make_sp<DisplayListBuilder>(SkRect::Make(frame_size), true);
canvas_ = dl_builder_.get();
}
}

View File

@ -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",

View File

@ -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<Rect> cull_rect) {
initial_cull_rect_ = cull_rect;
base_pass_ = std::make_unique<EntityPass>();
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<LazyGlyphAtlas>();
FML_DCHECK(GetSaveCount() == 1u);
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
@ -54,6 +64,7 @@ void Canvas::Save(
std::optional<EntityPass::BackdropFilterProc> 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<Rect> 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> geometry,
@ -287,6 +347,31 @@ void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
xformation_stack_.back().contains_clips = true;
}
void Canvas::IntersectCulling(Rect clip_rect) {
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
std::optional<Rect>& 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<Rect>& 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;
}

View File

@ -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<Rect> 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<Rect> GetCurrentLocalCullingBounds() const;
void ResetTransform();
void Transform(const Matrix& xformation);
@ -135,8 +150,9 @@ class Canvas {
EntityPass* current_pass_ = nullptr;
std::deque<CanvasStackEntry> xformation_stack_;
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
std::optional<Rect> initial_cull_rect_;
void Initialize();
void Initialize(std::optional<Rect> cull_rect);
void Reset();
@ -147,6 +163,9 @@ class Canvas {
void ClipGeometry(std::unique_ptr<Geometry> 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<EntityPass::BackdropFilterProc> backdrop_filter =

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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

View File

@ -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);
}

View File

@ -48,6 +48,9 @@ class SurfaceMTL final : public Surface {
id<MTLDrawable> drawable() const { return drawable_; }
// Returns a Rect defining the area of the surface in device pixels
IRect coverage() const;
// |Surface|
bool Present() const override;

View File

@ -148,6 +148,11 @@ bool SurfaceMTL::ShouldPerformPartialRepaint(std::optional<IRect> damage_rect) {
return true;
}
// |Surface|
IRect SurfaceMTL::coverage() const {
return IRect::MakeSize(resolve_texture_->GetSize());
}
// |Surface|
bool SurfaceMTL::Present() const {
if (drawable_ == nil) {

View File

@ -118,8 +118,10 @@ std::unique_ptr<SurfaceFrame> GPUSurfaceMetalImpeller::AcquireFrame(const SkISiz
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(