Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions flow/embedded_views.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,31 +339,10 @@ 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;
};

Expand Down
38 changes: 35 additions & 3 deletions shell/platform/darwin/ios/framework/Source/FlutterPlatformViews.mm
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
return transformed_rrect.contains(platformview_boundingrect);
}

// Determines if the intersection rect of a layer and a platform view is a single pixel width
// or height, and on the edge of the platform view.
static bool IsIntersectionSinglePixelWidthOrHeightOnEdgeOfPlatformView(
const SkIRect& rounded_out_platform_view_rect,
const SkIRect& intersection_rect) {
bool onLeftOrRight = intersection_rect.left() == rounded_out_platform_view_rect.left() ||
intersection_rect.right() == rounded_out_platform_view_rect.right();
bool onTopOrBottom = intersection_rect.top() == rounded_out_platform_view_rect.top() ||
intersection_rect.bottom() == rounded_out_platform_view_rect.bottom();
return (intersection_rect.width() == 1 && onLeftOrRight) ||
(intersection_rect.height() == 1 && onTopOrBottom);
}

namespace flutter {
// Becomes NO if Apple's API changes and blurred backdrop filters cannot be applied.
BOOL canApplyBlurBackdrop = YES;
Expand Down Expand Up @@ -705,8 +718,27 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
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->roundedInRegion(platform_view_rect).getRects();
std::vector<SkIRect> intersection_rects = slice->region(platform_view_rect).getRects();
const SkIRect rounded_out_platform_view_rect = platform_view_rect.roundOut();
// Ignore intersections of single width/height on the edge of the platform view.
// This is to address the following performance issue when interleaving adjacent
// platform views and layers:
// Since we `roundOut` both platform view rects and the layer rects, as long as
// the coordinate is fractional, there will be an intersection of a single pixel width
// (or height) after rounding out, even if they do not intersect before rounding out.
// We have to round out both platform view rect and the layer rect.
// Rounding in platform view rect will result in missing pixel on the intersection edge.
// Rounding in layer rect will result in missing pixel on the edge of the layer on top
// of the platform view.
for (auto it = intersection_rects.begin(); it != intersection_rects.end(); /*no-op*/) {
if (IsIntersectionSinglePixelWidthOrHeightOnEdgeOfPlatformView(
rounded_out_platform_view_rect, *it)) {
it = intersection_rects.erase(it);
} else {
++it;
}
}

auto allocation_size = intersection_rects.size();

// For testing purposes, the overlay id is used to find the overlay view.
Expand All @@ -731,7 +763,7 @@ static bool ClipRRectContainsPlatformViewBoundingRect(const SkRRect& clip_rrect,
for (SkIRect& joined_rect : intersection_rects) {
// Get the intersection rect between the current rect
// and the platform view rect.
joined_rect.intersect(platform_view_rect.roundOut());
joined_rect.intersect(rounded_out_platform_view_rect);
// Clip the background canvas, so it doesn't contain any of the pixels drawn
// on the overlay layer.
background_canvas->ClipRect(SkRect::Make(joined_rect), DlCanvas::ClipOp::kDifference);
Expand Down
5 changes: 4 additions & 1 deletion testing/scenario_app/ios/Scenarios/Scenarios/AppDelegate.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ - (BOOL)application:(UIApplication*)application
@"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-surrounding-layers-fractional-coordinate" :
@"platform_view_surrounding_layers_fractional_coordinate",
@"--platform-view-partial-intersection-fractional-coordinate" :
@"platform_view_partial_intersection_fractional_coordinate",
@"--platform-view-multiple" : @"platform_view_multiple",
@"--platform-view-multiple-background-foreground" :
@"platform_view_multiple_background_foreground",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,9 @@ - (void)testPlatformViewsMaxOverlays {
// +---+----+---+
// | D |
// +----+
- (void)testPlatformViewsWithAdjacentSurroundingLayers {
- (void)testPlatformViewsWithAdjacentSurroundingLayersAndFractionalCoordinate {
XCUIApplication* app = [[XCUIApplication alloc] init];
app.launchArguments = @[ @"--platform-view-surrounding-layers" ];
app.launchArguments = @[ @"--platform-view-surrounding-layers-fractional-coordinate" ];
[app launch];

XCUIElement* platform_view = app.otherElements[@"platform_view[0]"];
Expand All @@ -331,4 +331,33 @@ - (void)testPlatformViewsWithAdjacentSurroundingLayers {
XCTAssertFalse(overlay.exists);
}

// Platform view partially intersect with a layer in fractional coordinate.
// +-------+
// | |
// | PV +--+--+
// | | |
// +----+ A |
// | |
// +-----+
- (void)testPlatformViewsWithPartialIntersectionAndFractionalCoordinate {
XCUIApplication* app = [[XCUIApplication alloc] init];
app.launchArguments = @[ @"--platform-view-partial-intersection-fractional-coordinate" ];
[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, 0.5);
XCTAssertEqual(platform_view.frame.origin.y * scale, 0.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]"];
XCTAssert(overlay.exists);

// We want to make sure the overlay covers the edge (which is at 100.5).
XCTAssertEqual(CGRectGetMaxX(overlay.frame) * scale, 101);
XCTAssertEqual(CGRectGetMaxY(overlay.frame) * scale, 101);
}
@end
53 changes: 51 additions & 2 deletions testing/scenario_app/lib/src/platform_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -367,10 +367,10 @@ class PlatformViewMaxOverlaysScenario extends Scenario
}

/// A platform view with adjacent surrounding layers should not create overlays.
class PlatformViewSurroundingLayersScenario extends Scenario
class PlatformViewSurroundingLayersFractionalCoordinateScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
PlatformViewSurroundingLayersScenario(
PlatformViewSurroundingLayersFractionalCoordinateScenario(
super.view, {
required this.id,
});
Expand Down Expand Up @@ -438,6 +438,55 @@ class PlatformViewSurroundingLayersScenario extends Scenario
}
}

/// A platform view partially intersect with a layer, both with fractional coordinates.
class PlatformViewPartialIntersectionFractionalCoordinateScenario extends Scenario
with _BasePlatformViewScenarioMixin {
/// Creates the PlatformView scenario.
PlatformViewPartialIntersectionFractionalCoordinateScenario(
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 (0, 0) to (100, 100)
addPlatformView(
id,
width: 100,
height: 100,
dispatcher: view.platformDispatcher,
sceneBuilder: builder,
);

final PictureRecorder recorder = PictureRecorder();
final Canvas canvas = Canvas(recorder);

canvas.drawRect(
const Rect.fromLTWH(50, 50, 100, 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 {
Expand Down
3 changes: 2 additions & 1 deletion testing/scenario_app/lib/src/scenarios.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ 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_surrounding_layers_fractional_coordinate': (FlutterView view) => PlatformViewSurroundingLayersFractionalCoordinateScenario(view, id: _viewId++),
'platform_view_partial_intersection_fractional_coordinate': (FlutterView view) => PlatformViewPartialIntersectionFractionalCoordinateScenario(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++),
Expand Down