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

Commit 3bba8a4

Browse files
author
Jonah Williams
authored
share platform view slicing logic across iOS and Android. (#54010)
This removes support for "unobstructed platform views" on iOS - instead prefering to use the Android strategy of minimizing overlay layers, as this is generally more performant.
1 parent 86f4938 commit 3bba8a4

File tree

10 files changed

+329
-178
lines changed

10 files changed

+329
-178
lines changed

ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
../../../flutter/flow/surface_frame_unittests.cc
8181
../../../flutter/flow/testing
8282
../../../flutter/flow/texture_unittests.cc
83+
../../../flutter/flow/view_slicer_unittests.cc
8384
../../../flutter/flutter_frontend_server
8485
../../../flutter/fml/ascii_trie_unittests.cc
8586
../../../flutter/fml/backtrace_unittests.cc

ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41734,6 +41734,8 @@ ORIGIN: ../../../flutter/flow/surface.cc + ../../../flutter/LICENSE
4173441734
ORIGIN: ../../../flutter/flow/surface.h + ../../../flutter/LICENSE
4173541735
ORIGIN: ../../../flutter/flow/surface_frame.cc + ../../../flutter/LICENSE
4173641736
ORIGIN: ../../../flutter/flow/surface_frame.h + ../../../flutter/LICENSE
41737+
ORIGIN: ../../../flutter/flow/view_slicer.cc + ../../../flutter/LICENSE
41738+
ORIGIN: ../../../flutter/flow/view_slicer.h + ../../../flutter/LICENSE
4173741739
ORIGIN: ../../../flutter/flutter_vma/flutter_skia_vma.cc + ../../../flutter/LICENSE
4173841740
ORIGIN: ../../../flutter/flutter_vma/flutter_skia_vma.h + ../../../flutter/LICENSE
4173941741
ORIGIN: ../../../flutter/flutter_vma/flutter_vma.cc + ../../../flutter/LICENSE
@@ -44616,6 +44618,8 @@ FILE: ../../../flutter/flow/surface.cc
4461644618
FILE: ../../../flutter/flow/surface.h
4461744619
FILE: ../../../flutter/flow/surface_frame.cc
4461844620
FILE: ../../../flutter/flow/surface_frame.h
44621+
FILE: ../../../flutter/flow/view_slicer.cc
44622+
FILE: ../../../flutter/flow/view_slicer.h
4461944623
FILE: ../../../flutter/flutter_vma/flutter_skia_vma.cc
4462044624
FILE: ../../../flutter/flutter_vma/flutter_skia_vma.h
4462144625
FILE: ../../../flutter/flutter_vma/flutter_vma.cc

flow/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ source_set("flow") {
8888
"surface.h",
8989
"surface_frame.cc",
9090
"surface_frame.h",
91+
"view_slicer.cc",
92+
"view_slicer.h",
9193
]
9294

9395
public_configs = [ "//flutter:config" ]
@@ -183,6 +185,7 @@ if (enable_unittests) {
183185
"testing/mock_layer_unittests.cc",
184186
"testing/mock_texture_unittests.cc",
185187
"texture_unittests.cc",
188+
"view_slicer_unittests.cc",
186189
]
187190

188191
deps = [

flow/view_slicer.cc

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/flow/view_slicer.h"
6+
7+
#include <unordered_map>
8+
#include "flow/embedded_views.h"
9+
#include "fml/logging.h"
10+
11+
namespace flutter {
12+
13+
std::unordered_map<int64_t, SkRect> SliceViews(
14+
DlCanvas* background_canvas,
15+
const std::vector<int64_t>& composition_order,
16+
const std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>&
17+
slices,
18+
const std::unordered_map<int64_t, SkRect>& view_rects) {
19+
std::unordered_map<int64_t, SkRect> overlay_layers;
20+
21+
auto current_frame_view_count = composition_order.size();
22+
23+
// Restore the clip context after exiting this method since it's changed
24+
// below.
25+
DlAutoCanvasRestore save(background_canvas, /*do_save=*/true);
26+
27+
for (size_t i = 0; i < current_frame_view_count; i++) {
28+
int64_t view_id = composition_order[i];
29+
EmbedderViewSlice* slice = slices.at(view_id).get();
30+
if (slice->canvas() == nullptr) {
31+
continue;
32+
}
33+
34+
slice->end_recording();
35+
36+
SkRect full_joined_rect = SkRect::MakeEmpty();
37+
38+
// Determinate if Flutter UI intersects with any of the previous
39+
// platform views stacked by z position.
40+
//
41+
// This is done by querying the r-tree that holds the records for the
42+
// picture recorder corresponding to the flow layers added after a platform
43+
// view layer.
44+
for (int j = i; j >= 0; j--) {
45+
int64_t current_view_id = composition_order[j];
46+
auto maybe_rect = view_rects.find(current_view_id);
47+
FML_DCHECK(maybe_rect != view_rects.end());
48+
if (maybe_rect == view_rects.end()) {
49+
continue;
50+
}
51+
52+
SkRect current_view_rect = maybe_rect->second;
53+
const SkIRect rounded_in_platform_view_rect = current_view_rect.roundIn();
54+
55+
// Each rect corresponds to a native view that renders Flutter UI.
56+
std::vector<SkIRect> intersection_rects =
57+
slice->region(current_view_rect).getRects();
58+
59+
// Ignore intersections of single width/height on the edge of the platform
60+
// view.
61+
// This is to address the following performance issue when interleaving
62+
// adjacent platform views and layers: Since we `roundOut` both platform
63+
// view rects and the layer rects, as long as the coordinate is
64+
// fractional, there will be an intersection of a single pixel width (or
65+
// height) after rounding out, even if they do not intersect before
66+
// rounding out. We have to round out both platform view rect and the
67+
// layer rect. Rounding in platform view rect will result in missing pixel
68+
// on the intersection edge. Rounding in layer rect will result in missing
69+
// pixel on the edge of the layer on top of the platform view.
70+
for (auto it = intersection_rects.begin(); it != intersection_rects.end();
71+
/*no-op*/) {
72+
// If intersection_rect does not intersect with the *rounded in*
73+
// platform view rect, then the intersection must be a single pixel
74+
// width (or height) on edge.
75+
if (!SkIRect::Intersects(*it, rounded_in_platform_view_rect)) {
76+
it = intersection_rects.erase(it);
77+
} else {
78+
++it;
79+
}
80+
}
81+
82+
// Limit the number of native views, so it doesn't grow forever.
83+
//
84+
// In this case, the rects are merged into a single one that is the union
85+
// of all the rects.
86+
SkRect partial_joined_rect = SkRect::MakeEmpty();
87+
for (const SkIRect& rect : intersection_rects) {
88+
partial_joined_rect.join(SkRect::Make(rect));
89+
}
90+
91+
// Get the intersection rect with the `current_view_rect`,
92+
partial_joined_rect.intersect(SkRect::Make(current_view_rect.roundOut()));
93+
94+
// Join the `partial_joined_rect` into `full_joined_rect` to get the rect
95+
// above the current `slice`
96+
full_joined_rect.join(partial_joined_rect);
97+
}
98+
99+
if (!full_joined_rect.isEmpty()) {
100+
overlay_layers.insert({view_id, full_joined_rect});
101+
102+
// Clip the background canvas, so it doesn't contain any of the pixels
103+
// drawn on the overlay layer.
104+
background_canvas->ClipRect(full_joined_rect,
105+
DlCanvas::ClipOp::kDifference);
106+
}
107+
slice->render_into(background_canvas);
108+
}
109+
110+
// Manually trigger the DlAutoCanvasRestore before we submit the frame
111+
save.Restore();
112+
113+
return overlay_layers;
114+
}
115+
116+
} // namespace flutter

flow/view_slicer.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_FLOW_VIEW_SLICER_H_
6+
#define FLUTTER_FLOW_VIEW_SLICER_H_
7+
8+
#include <unordered_map>
9+
#include "display_list/dl_canvas.h"
10+
#include "flow/embedded_views.h"
11+
12+
namespace flutter {
13+
14+
/// @brief Compute the required overlay layers and clip the view slices
15+
/// according to the size and position of the platform views.
16+
std::unordered_map<int64_t, SkRect> SliceViews(
17+
DlCanvas* background_canvas,
18+
const std::vector<int64_t>& composition_order,
19+
const std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>&
20+
slices,
21+
const std::unordered_map<int64_t, SkRect>& view_rects);
22+
23+
} // namespace flutter
24+
25+
#endif // FLUTTER_FLOW_VIEW_SLICER_H_

flow/view_slicer_unittests.cc

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include <unordered_map>
6+
#include "display_list/dl_builder.h"
7+
#include "flow/embedded_views.h"
8+
#include "flutter/flow/view_slicer.h"
9+
#include "gtest/gtest.h"
10+
11+
namespace flutter {
12+
namespace testing {
13+
14+
namespace {
15+
void AddSliceOfSize(
16+
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>>& slices,
17+
int64_t id,
18+
SkRect rect) {
19+
slices[id] = std::make_unique<DisplayListEmbedderViewSlice>(rect);
20+
DlPaint paint;
21+
paint.setColor(DlColor::kBlack());
22+
slices[id]->canvas()->DrawRect(rect, paint);
23+
}
24+
} // namespace
25+
26+
TEST(ViewSlicerTest, CanSlicerNonOverlappingViews) {
27+
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
28+
29+
std::vector<int64_t> composition_order = {1};
30+
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
31+
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(99, 99, 100, 100));
32+
33+
std::unordered_map<int64_t, SkRect> view_rects = {
34+
{1, SkRect::MakeLTRB(50, 50, 60, 60)}};
35+
36+
auto computed_overlays =
37+
SliceViews(&builder, composition_order, slices, view_rects);
38+
39+
EXPECT_TRUE(computed_overlays.empty());
40+
}
41+
42+
TEST(ViewSlicerTest, IgnoresFractionalOverlaps) {
43+
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
44+
45+
std::vector<int64_t> composition_order = {1};
46+
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
47+
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 50.49, 50.49));
48+
49+
std::unordered_map<int64_t, SkRect> view_rects = {
50+
{1, SkRect::MakeLTRB(50.5, 50.5, 100, 100)}};
51+
52+
auto computed_overlays =
53+
SliceViews(&builder, composition_order, slices, view_rects);
54+
55+
EXPECT_TRUE(computed_overlays.empty());
56+
}
57+
58+
TEST(ViewSlicerTest, ComputesOverlapWith1PV) {
59+
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
60+
61+
std::vector<int64_t> composition_order = {1};
62+
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
63+
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 50, 50));
64+
65+
std::unordered_map<int64_t, SkRect> view_rects = {
66+
{1, SkRect::MakeLTRB(0, 0, 100, 100)}};
67+
68+
auto computed_overlays =
69+
SliceViews(&builder, composition_order, slices, view_rects);
70+
71+
EXPECT_EQ(computed_overlays.size(), 1u);
72+
auto overlay = computed_overlays.find(1);
73+
ASSERT_NE(overlay, computed_overlays.end());
74+
75+
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(0, 0, 50, 50));
76+
}
77+
78+
TEST(ViewSlicerTest, ComputesOverlapWith2PV) {
79+
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
80+
81+
std::vector<int64_t> composition_order = {1, 2};
82+
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
83+
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 50, 50));
84+
AddSliceOfSize(slices, 2, SkRect::MakeLTRB(50, 50, 100, 100));
85+
86+
std::unordered_map<int64_t, SkRect> view_rects = {
87+
{1, SkRect::MakeLTRB(0, 0, 50, 50)}, //
88+
{2, SkRect::MakeLTRB(50, 50, 100, 100)}, //
89+
};
90+
91+
auto computed_overlays =
92+
SliceViews(&builder, composition_order, slices, view_rects);
93+
94+
EXPECT_EQ(computed_overlays.size(), 2u);
95+
96+
auto overlay = computed_overlays.find(1);
97+
ASSERT_NE(overlay, computed_overlays.end());
98+
99+
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(0, 0, 50, 50));
100+
101+
overlay = computed_overlays.find(2);
102+
ASSERT_NE(overlay, computed_overlays.end());
103+
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(50, 50, 100, 100));
104+
}
105+
106+
TEST(ViewSlicerTest, OverlappingTwoPVs) {
107+
DisplayListBuilder builder(SkRect::MakeLTRB(0, 0, 100, 100));
108+
109+
std::vector<int64_t> composition_order = {1, 2};
110+
std::unordered_map<int64_t, std::unique_ptr<EmbedderViewSlice>> slices;
111+
// This embeded view overlaps both platform views:
112+
//
113+
// [ A [ ]]
114+
// [_____[ C ]]
115+
// [ B [ ]]
116+
// [ ]
117+
AddSliceOfSize(slices, 1, SkRect::MakeLTRB(0, 0, 0, 0));
118+
AddSliceOfSize(slices, 2, SkRect::MakeLTRB(0, 0, 100, 100));
119+
120+
std::unordered_map<int64_t, SkRect> view_rects = {
121+
{1, SkRect::MakeLTRB(0, 0, 50, 50)}, //
122+
{2, SkRect::MakeLTRB(50, 50, 100, 100)}, //
123+
};
124+
125+
auto computed_overlays =
126+
SliceViews(&builder, composition_order, slices, view_rects);
127+
128+
EXPECT_EQ(computed_overlays.size(), 1u);
129+
130+
auto overlay = computed_overlays.find(2);
131+
ASSERT_NE(overlay, computed_overlays.end());
132+
133+
// We create a single overlay for both overlapping sections.
134+
EXPECT_EQ(overlay->second, SkRect::MakeLTRB(0, 0, 100, 100));
135+
}
136+
137+
} // namespace testing
138+
} // namespace flutter

0 commit comments

Comments
 (0)