[ios][platform_view][performance] overlay intersection (flutter/engine#50637)

Address the performance of platform view due to an extra overlay. This overlay was added due to the following rounding problem: 

> For example, if we interleave flutter widget and platform view in a list, and let's say we have a flutter widget (top = 0, bottom = 100.1), and a platform view below that widget (top = 100.1, bottom = 200). They are NOT supposed to be overlapping. However, after rounding out, we will get flutter widget (top = 0, bottom = 101), and platform view (top = 100, bottom 200). This will result in 1 pixel overlap as long as there's a floating point in the coord.

(Quote myself from the discussion below). 

*List which issues are fixed by this PR. You must list at least one issue.*

Fixes https://github.com/flutter/flutter/issues/143420

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
hellohuanlin 2024-03-21 16:48:34 -07:00 committed by GitHub
parent 48710ab05d
commit 9c54875efc
6 changed files with 123 additions and 1 deletions

View File

@ -339,10 +339,31 @@ class EmbedderViewSlice {
virtual DlCanvas* canvas() = 0;
virtual void end_recording() = 0;
virtual const DlRegion& getRegion() const = 0;
// TODO(hellohuanlin): We should deprecate this function if we migrate
// all platforms to use `roundedInRegion`. Then we should rename
// `roundedInRegion` to just `region`.
DlRegion region(const SkRect& query) const {
return DlRegion::MakeIntersection(getRegion(), DlRegion(query.roundOut()));
}
// TODO(hellohuanlin): iOS only for now, but we should try it on other
// platforms.
DlRegion roundedInRegion(const SkRect& query) const {
// Use `roundIn` to address a performance issue when we interleave embedded
// view (the queried rect) and flutter widgets (the slice regions).
// Rounding out both the queried rect and slice regions will
// result in an intersection region of 1 px height, which is then used to
// create an overlay layer. For each overlay, we acquire a surface frame,
// paint the pixels and submit the frame. This resulted in performance
// issues since the surface frame acquisition is expensive. Since slice
// regions are already rounded out (see:
// https://github.com/flutter/engine/blob/5f40c9f49f88729bc3e71390356209dbe29ec788/display_list/geometry/dl_rtree.cc#L209),
// we can simply round in the queried rect to avoid the situation.
// After rounding in, it will ignore a single (or partial) pixel overlap,
// and give the ownership to the platform view.
return DlRegion::MakeIntersection(getRegion(), DlRegion(query.roundIn()));
}
virtual void render_into(DlCanvas* canvas) = 0;
};

View File

@ -705,7 +705,8 @@ bool FlutterPlatformViewsController::SubmitFrame(GrDirectContext* gr_context,
for (size_t j = i + 1; j > 0; j--) {
int64_t current_platform_view_id = composition_order_[j - 1];
SkRect platform_view_rect = GetPlatformViewRect(current_platform_view_id);
std::vector<SkIRect> intersection_rects = slice->region(platform_view_rect).getRects();
std::vector<SkIRect> intersection_rects =
slice->roundedInRegion(platform_view_rect).getRects();
auto allocation_size = intersection_rects.size();
// For testing purposes, the overlay id is used to find the overlay view.

View File

@ -52,6 +52,7 @@
@"platform_view_one_overlay_two_intersecting_overlays",
@"--platform-view-multiple-without-overlays" : @"platform_view_multiple_without_overlays",
@"--platform-view-max-overlays" : @"platform_view_max_overlays",
@"--platform-view-surrounding-layers" : @"platform_view_surrounding_layers",
@"--platform-view-multiple" : @"platform_view_multiple",
@"--platform-view-multiple-background-foreground" :
@"platform_view_multiple_background_foreground",

View File

@ -305,4 +305,30 @@ static const CGFloat kCompareAccuracy = 0.001;
XCTAssertFalse(overlayView1.exists);
}
// Platform view surrounded by adjacent layers on each side should not create any overlays.
// +----+
// | B |
// +---+----+---+
// | A | PV | C |
// +---+----+---+
// | D |
// +----+
- (void)testPlatformViewsWithAdjacentSurroundingLayers {
XCUIApplication* app = [[XCUIApplication alloc] init];
app.launchArguments = @[ @"--platform-view-surrounding-layers" ];
[app launch];
XCUIElement* platform_view = app.otherElements[@"platform_view[0]"];
XCTAssertTrue([platform_view waitForExistenceWithTimeout:1.0]);
CGFloat scale = [UIScreen mainScreen].scale;
XCTAssertEqual(platform_view.frame.origin.x * scale, 100.5);
XCTAssertEqual(platform_view.frame.origin.y * scale, 100.5);
XCTAssertEqual(platform_view.frame.size.width * scale, 100);
XCTAssertEqual(platform_view.frame.size.height * scale, 100);
XCUIElement* overlay = app.otherElements[@"platform_view[0].overlay[0]"];
XCTAssertFalse(overlay.exists);
}
@end

View File

@ -366,6 +366,78 @@ class PlatformViewMaxOverlaysScenario extends Scenario
}
}
/// A platform view with adjacent surrounding layers should not create overlays.
class PlatformViewSurroundingLayersScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
PlatformViewSurroundingLayersScenario(
super.view, {
required this.id,
});
/// The platform view identifier.
final int id;
@override
void onBeginFrame(Duration duration) {
final SceneBuilder builder = SceneBuilder();
// Simulate partial pixel offsets as we would see while scrolling.
// All objects in the scene below are then on sub-pixel boundaries.
builder.pushOffset(0.5, 0.5);
// a platform view from (100, 100) to (200, 200)
builder.pushOffset(100, 100);
addPlatformView(
id,
width: 100,
height: 100,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
);
builder.pop();
final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);
const Rect rect = Rect.fromLTWH(100, 100, 100, 100);
// Rect at the left of platform view
canvas.drawRect(
rect.shift(const Offset(-100, 0)),
Paint()..color = const Color(0x22FF0000),
);
// Rect at the right of platform view
canvas.drawRect(
rect.shift(const Offset(100, 0)),
Paint()..color = const Color(0x22FF0000),
);
// Rect at the top of platform view
canvas.drawRect(
rect.shift(const Offset(0, -100)),
Paint()..color = const Color(0x22FF0000),
);
// Rect at the bottom of platform view
canvas.drawRect(
rect.shift(const Offset(0, 100)),
Paint()..color = const Color(0x22FF0000),
);
final Picture picture = recorder.endRecording();
builder.addPicture(Offset.zero, picture);
// Pop the (0.5, 0.5) offset.
builder.pop();
final Scene scene = builder.build();
view.render(scene);
scene.dispose();
}
}
/// Builds a scene with 2 platform views.
class MultiPlatformViewScenario extends Scenario
with _BasePlatformViewScenarioMixin {

View File

@ -34,6 +34,7 @@ Map<String, _ScenarioFactory> _scenarios = <String, _ScenarioFactory>{
'platform_view_one_overlay_two_intersecting_overlays': (FlutterView view) => PlatformViewOneOverlayTwoIntersectingOverlaysScenario(view, id: _viewId++),
'platform_view_multiple_without_overlays': (FlutterView view) => MultiPlatformViewWithoutOverlaysScenario(view, firstId: _viewId++, secondId: _viewId++),
'platform_view_max_overlays': (FlutterView view) => PlatformViewMaxOverlaysScenario(view, id: _viewId++),
'platform_view_surrounding_layers': (FlutterView view) => PlatformViewSurroundingLayersScenario(view, id: _viewId++),
'platform_view_cliprect': (FlutterView view) => PlatformViewClipRectScenario(view, id: _viewId++),
'platform_view_cliprect_with_transform': (FlutterView view) => PlatformViewClipRectWithTransformScenario(view, id: _viewId++),
'platform_view_cliprect_after_moved': (FlutterView view) => PlatformViewClipRectAfterMovedScenario(view, id: _viewId++),