Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit e64aee0

Browse files
authored
[Impeller] take advantage of DisplayList culling (#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.
1 parent 46d0142 commit e64aee0

File tree

16 files changed

+529
-51
lines changed

16 files changed

+529
-51
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
../../../flutter/impeller/.gitignore
121121
../../../flutter/impeller/README.md
122122
../../../flutter/impeller/aiks/aiks_unittests.cc
123+
../../../flutter/impeller/aiks/canvas_unittests.cc
123124
../../../flutter/impeller/archivist/archivist_unittests.cc
124125
../../../flutter/impeller/base/README.md
125126
../../../flutter/impeller/base/base_unittests.cc

display_list/display_list.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ void DisplayList::Dispatch(DlOpReceiver& receiver) const {
140140
Dispatch(receiver, ptr, ptr + byte_count_, NopCuller::instance);
141141
}
142142

143+
void DisplayList::Dispatch(DlOpReceiver& receiver,
144+
const SkIRect& cull_rect) const {
145+
Dispatch(receiver, SkRect::Make(cull_rect));
146+
}
147+
143148
void DisplayList::Dispatch(DlOpReceiver& receiver,
144149
const SkRect& cull_rect) const {
145150
if (cull_rect.isEmpty()) {

display_list/display_list.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ class DisplayList : public SkRefCnt {
235235

236236
void Dispatch(DlOpReceiver& ctx) const;
237237
void Dispatch(DlOpReceiver& ctx, const SkRect& cull_rect) const;
238+
void Dispatch(DlOpReceiver& ctx, const SkIRect& cull_rect) const;
238239

239240
// From historical behavior, SkPicture always included nested bytes,
240241
// but nested ops are only included if requested. The defaults used

display_list/display_list_unittests.cc

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2532,81 +2532,79 @@ TEST_F(DisplayListTest, RTreeRenderCulling) {
25322532
main_receiver.drawRect({20, 20, 30, 30});
25332533
auto main = main_builder.Build();
25342534

2535+
auto test = [main](SkIRect cull_rect, const sk_sp<DisplayList>& expected) {
2536+
{ // Test SkIRect culling
2537+
DisplayListBuilder culling_builder;
2538+
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2539+
2540+
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2541+
}
2542+
2543+
{ // Test SkRect culling
2544+
DisplayListBuilder culling_builder;
2545+
main->Dispatch(ToReceiver(culling_builder), SkRect::Make(cull_rect));
2546+
2547+
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2548+
}
2549+
};
2550+
25352551
{ // No rects
2536-
SkRect cull_rect = {11, 11, 19, 19};
2552+
SkIRect cull_rect = {11, 11, 19, 19};
25372553

25382554
DisplayListBuilder expected_builder;
25392555
auto expected = expected_builder.Build();
25402556

2541-
DisplayListBuilder culling_builder(cull_rect);
2542-
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2543-
2544-
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2557+
test(cull_rect, expected);
25452558
}
25462559

25472560
{ // Rect 1
2548-
SkRect cull_rect = {9, 9, 19, 19};
2561+
SkIRect cull_rect = {9, 9, 19, 19};
25492562

25502563
DisplayListBuilder expected_builder;
25512564
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
25522565
expected_receiver.drawRect({0, 0, 10, 10});
25532566
auto expected = expected_builder.Build();
25542567

2555-
DisplayListBuilder culling_builder(cull_rect);
2556-
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2557-
2558-
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2568+
test(cull_rect, expected);
25592569
}
25602570

25612571
{ // Rect 2
2562-
SkRect cull_rect = {11, 9, 21, 19};
2572+
SkIRect cull_rect = {11, 9, 21, 19};
25632573

25642574
DisplayListBuilder expected_builder;
25652575
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
25662576
expected_receiver.drawRect({20, 0, 30, 10});
25672577
auto expected = expected_builder.Build();
25682578

2569-
DisplayListBuilder culling_builder(cull_rect);
2570-
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2571-
2572-
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2579+
test(cull_rect, expected);
25732580
}
25742581

25752582
{ // Rect 3
2576-
SkRect cull_rect = {9, 11, 19, 21};
2583+
SkIRect cull_rect = {9, 11, 19, 21};
25772584

25782585
DisplayListBuilder expected_builder;
25792586
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
25802587
expected_receiver.drawRect({0, 20, 10, 30});
25812588
auto expected = expected_builder.Build();
25822589

2583-
DisplayListBuilder culling_builder(cull_rect);
2584-
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2585-
2586-
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2590+
test(cull_rect, expected);
25872591
}
25882592

25892593
{ // Rect 4
2590-
SkRect cull_rect = {11, 11, 21, 21};
2594+
SkIRect cull_rect = {11, 11, 21, 21};
25912595

25922596
DisplayListBuilder expected_builder;
25932597
DlOpReceiver& expected_receiver = ToReceiver(expected_builder);
25942598
expected_receiver.drawRect({20, 20, 30, 30});
25952599
auto expected = expected_builder.Build();
25962600

2597-
DisplayListBuilder culling_builder(cull_rect);
2598-
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2599-
2600-
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), expected));
2601+
test(cull_rect, expected);
26012602
}
26022603

26032604
{ // All 4 rects
2604-
SkRect cull_rect = {9, 9, 21, 21};
2605-
2606-
DisplayListBuilder culling_builder(cull_rect);
2607-
main->Dispatch(ToReceiver(culling_builder), cull_rect);
2605+
SkIRect cull_rect = {9, 9, 21, 21};
26082606

2609-
EXPECT_TRUE(DisplayListsEQ_Verbose(culling_builder.Build(), main));
2607+
test(cull_rect, main);
26102608
}
26112609
}
26122610

flow/surface_frame.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ SurfaceFrame::SurfaceFrame(sk_sp<SkSurface> surface,
3131
canvas_ = &adapter_;
3232
} else if (display_list_fallback) {
3333
FML_DCHECK(!frame_size.isEmpty());
34-
dl_builder_ = sk_make_sp<DisplayListBuilder>(SkRect::Make(frame_size));
34+
dl_builder_ =
35+
sk_make_sp<DisplayListBuilder>(SkRect::Make(frame_size), true);
3536
canvas_ = dl_builder_.get();
3637
}
3738
}

impeller/aiks/BUILD.gn

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ impeller_component("aiks_playground") {
4747

4848
impeller_component("aiks_unittests") {
4949
testonly = true
50-
sources = [ "aiks_unittests.cc" ]
50+
sources = [
51+
"aiks_unittests.cc",
52+
"canvas_unittests.cc",
53+
]
5154
deps = [
5255
":aiks",
5356
":aiks_playground",

impeller/aiks/canvas.cc

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,25 @@
2323
namespace impeller {
2424

2525
Canvas::Canvas() {
26-
Initialize();
26+
Initialize(std::nullopt);
27+
}
28+
29+
Canvas::Canvas(Rect cull_rect) {
30+
Initialize(cull_rect);
31+
}
32+
33+
Canvas::Canvas(IRect cull_rect) {
34+
Initialize(Rect::MakeLTRB(cull_rect.GetLeft(), cull_rect.GetTop(),
35+
cull_rect.GetRight(), cull_rect.GetBottom()));
2736
}
2837

2938
Canvas::~Canvas() = default;
3039

31-
void Canvas::Initialize() {
40+
void Canvas::Initialize(std::optional<Rect> cull_rect) {
41+
initial_cull_rect_ = cull_rect;
3242
base_pass_ = std::make_unique<EntityPass>();
3343
current_pass_ = base_pass_.get();
34-
xformation_stack_.emplace_back(CanvasStackEntry{});
44+
xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect});
3545
lazy_glyph_atlas_ = std::make_shared<LazyGlyphAtlas>();
3646
FML_DCHECK(GetSaveCount() == 1u);
3747
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
@@ -54,6 +64,7 @@ void Canvas::Save(
5464
std::optional<EntityPass::BackdropFilterProc> backdrop_filter) {
5565
auto entry = CanvasStackEntry{};
5666
entry.xformation = xformation_stack_.back().xformation;
67+
entry.cull_rect = xformation_stack_.back().cull_rect;
5768
entry.stencil_depth = xformation_stack_.back().stencil_depth;
5869
if (create_subpass) {
5970
entry.is_subpass = true;
@@ -109,6 +120,15 @@ const Matrix& Canvas::GetCurrentTransformation() const {
109120
return xformation_stack_.back().xformation;
110121
}
111122

123+
const std::optional<Rect> Canvas::GetCurrentLocalCullingBounds() const {
124+
auto cull_rect = xformation_stack_.back().cull_rect;
125+
if (cull_rect.has_value()) {
126+
Matrix inverse = xformation_stack_.back().xformation.Invert();
127+
cull_rect = cull_rect.value().TransformBounds(inverse);
128+
}
129+
return cull_rect;
130+
}
131+
112132
void Canvas::Translate(const Vector3& offset) {
113133
Concat(Matrix::MakeTranslation(offset));
114134
}
@@ -258,16 +278,56 @@ void Canvas::DrawCircle(Point center, Scalar radius, const Paint& paint) {
258278

259279
void Canvas::ClipPath(const Path& path, Entity::ClipOperation clip_op) {
260280
ClipGeometry(Geometry::MakeFillPath(path), clip_op);
281+
if (clip_op == Entity::ClipOperation::kIntersect) {
282+
auto bounds = path.GetBoundingBox();
283+
if (bounds.has_value()) {
284+
IntersectCulling(bounds.value());
285+
}
286+
}
261287
}
262288

263289
void Canvas::ClipRect(const Rect& rect, Entity::ClipOperation clip_op) {
264290
ClipGeometry(Geometry::MakeRect(rect), clip_op);
291+
switch (clip_op) {
292+
case Entity::ClipOperation::kIntersect:
293+
IntersectCulling(rect);
294+
break;
295+
case Entity::ClipOperation::kDifference:
296+
SubtractCulling(rect);
297+
break;
298+
}
265299
}
266300

267301
void Canvas::ClipRRect(const Rect& rect,
268302
Scalar corner_radius,
269303
Entity::ClipOperation clip_op) {
270304
ClipGeometry(Geometry::MakeRRect(rect, corner_radius), clip_op);
305+
switch (clip_op) {
306+
case Entity::ClipOperation::kIntersect:
307+
IntersectCulling(rect);
308+
break;
309+
case Entity::ClipOperation::kDifference:
310+
if (corner_radius <= 0) {
311+
SubtractCulling(rect);
312+
} else {
313+
// We subtract the inner "tall" and "wide" rectangle pieces
314+
// that fit inside the corners which cover the greatest area
315+
// without involving the curved corners
316+
// Since this is a subtract operation, we can subtract each
317+
// rectangle piece individually without fear of interference.
318+
if (corner_radius * 2 < rect.size.width) {
319+
SubtractCulling(Rect::MakeLTRB(
320+
rect.GetLeft() + corner_radius, rect.GetTop(),
321+
rect.GetRight() - corner_radius, rect.GetBottom()));
322+
}
323+
if (corner_radius * 2 < rect.size.height) {
324+
SubtractCulling(Rect::MakeLTRB(
325+
rect.GetLeft(), rect.GetTop() + corner_radius, //
326+
rect.GetRight(), rect.GetBottom() - corner_radius));
327+
}
328+
}
329+
break;
330+
}
271331
}
272332

273333
void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
@@ -287,6 +347,31 @@ void Canvas::ClipGeometry(std::unique_ptr<Geometry> geometry,
287347
xformation_stack_.back().contains_clips = true;
288348
}
289349

350+
void Canvas::IntersectCulling(Rect clip_rect) {
351+
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
352+
std::optional<Rect>& cull_rect = xformation_stack_.back().cull_rect;
353+
if (cull_rect.has_value()) {
354+
cull_rect = cull_rect
355+
.value() //
356+
.Intersection(clip_rect) //
357+
.value_or(Rect{});
358+
} else {
359+
cull_rect = clip_rect;
360+
}
361+
}
362+
363+
void Canvas::SubtractCulling(Rect clip_rect) {
364+
std::optional<Rect>& cull_rect = xformation_stack_.back().cull_rect;
365+
if (cull_rect.has_value()) {
366+
clip_rect = clip_rect.TransformBounds(GetCurrentTransformation());
367+
cull_rect = cull_rect
368+
.value() //
369+
.Cutout(clip_rect) //
370+
.value_or(Rect{});
371+
}
372+
// else (no cull) diff (any clip) is non-rectangular
373+
}
374+
290375
void Canvas::RestoreClip() {
291376
Entity entity;
292377
entity.SetTransformation(GetCurrentTransformation());
@@ -364,7 +449,7 @@ Picture Canvas::EndRecordingAsPicture() {
364449
picture.pass = std::move(base_pass_);
365450

366451
Reset();
367-
Initialize();
452+
Initialize(initial_cull_rect_);
368453

369454
return picture;
370455
}

impeller/aiks/canvas.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ namespace impeller {
2828

2929
class Entity;
3030

31+
struct CanvasStackEntry {
32+
Matrix xformation;
33+
// |cull_rect| is conservative screen-space bounds of the clipped output area
34+
std::optional<Rect> cull_rect;
35+
size_t stencil_depth = 0u;
36+
bool is_subpass = false;
37+
bool contains_clips = false;
38+
};
39+
3140
class Canvas {
3241
public:
3342
struct DebugOptions {
@@ -40,6 +49,10 @@ class Canvas {
4049

4150
Canvas();
4251

52+
explicit Canvas(Rect cull_rect);
53+
54+
explicit Canvas(IRect cull_rect);
55+
4356
~Canvas();
4457

4558
void Save();
@@ -57,6 +70,8 @@ class Canvas {
5770

5871
const Matrix& GetCurrentTransformation() const;
5972

73+
const std::optional<Rect> GetCurrentLocalCullingBounds() const;
74+
6075
void ResetTransform();
6176

6277
void Transform(const Matrix& xformation);
@@ -135,8 +150,9 @@ class Canvas {
135150
EntityPass* current_pass_ = nullptr;
136151
std::deque<CanvasStackEntry> xformation_stack_;
137152
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
153+
std::optional<Rect> initial_cull_rect_;
138154

139-
void Initialize();
155+
void Initialize(std::optional<Rect> cull_rect);
140156

141157
void Reset();
142158

@@ -147,6 +163,9 @@ class Canvas {
147163
void ClipGeometry(std::unique_ptr<Geometry> geometry,
148164
Entity::ClipOperation clip_op);
149165

166+
void IntersectCulling(Rect clip_bounds);
167+
void SubtractCulling(Rect clip_bounds);
168+
150169
void Save(bool create_subpass,
151170
BlendMode = BlendMode::kSourceOver,
152171
std::optional<EntityPass::BackdropFilterProc> backdrop_filter =

0 commit comments

Comments
 (0)