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

Round SkRects in ClipRectContainsPlatformViewBoundingRect #50387

Closed
wants to merge 2 commits into from

Conversation

jmagman
Copy link
Member

@jmagman jmagman commented Feb 6, 2024

Platform view clip mutators are being applied when the platform view scrolls off screen. This was due to floating point errors comparing the bounds of the platform view with the bounds of the clip view.

static bool ClipRectContainsPlatformViewBoundingRect(const SkRect& clip_rect,
const SkRect& platformview_boundingrect,
const SkMatrix& transform_matrix) {
SkRect transformed_rect = transform_matrix.mapRect(clip_rect);
return transformed_rect.contains(platformview_boundingrect);

Example:

Clip rect:

(fLeft = 135, fTop = 17.2308426, fRight = 1305, fBottom = 2240.23096)

Platform view rect:

(fLeft = 135, fTop = 17.230835, fRight = 1035, fBottom = 767.230835)

The clip rect should be larger than the platform view rect and therefore no clipping should happen. The left and top should be equal, and the right and bottom clip is greater than the platform view. However, because the top 17.2308426 > 17.230835 the rect is not larger (for the clip rect to contain the platform view, left and top must be <= and right and bottom must be >=), then clipping happens, causing an expensive -[FlutterClippingMaskView drawRect:] call. Handle this by rounding the rect bounds during the fast-path ClipRectContainsPlatformViewBoundingRect check.

Fixes flutter/flutter#142828
See also #37434 and #20050

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or the PR is test-exempt. See testing the engine for instructions on writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@jmagman jmagman self-assigned this Feb 6, 2024
@jmagman jmagman force-pushed the round-mutator-rects branch from 634135f to a76625b Compare February 7, 2024 05:53
@@ -1529,6 +1538,8 @@ - (void)testChildClippingViewShouldBeTheBoundingRectOfPlatformView {
XCTAssertLessThan(
fabs(platformViewRectInFlutterView.size.height - childClippingView.frame.size.height),
kFloatCompareEpsilon);

XCTAssertNil(childClippingView.maskView);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check fails on master but passes on this PR.

@jmagman jmagman force-pushed the round-mutator-rects branch from a76625b to a2cc737 Compare February 7, 2024 06:04
@jmagman jmagman marked this pull request as ready for review February 7, 2024 06:07
@jmagman
Copy link
Member Author

jmagman commented Feb 7, 2024

(@stuartmorgan I added you since you reviewed #37434 and maybe at one point were familiar with this code)

Copy link
Contributor

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

I think if you plan to work on platform view performance then it would be beneficial to have a benchmark running a non-trivial platform view. Eyeballing the cumulative effect of many small perf improvements will be difficult, and I've known many an engineer get discouraged when it felt like they weren't making progress.

return transformed_rect.contains(platformview_boundingrect);

// Tolerate floating point errors when comparing flow clipping view bounds
// and quartz platform view bounds. Round to integers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these logical pixels or physical pixels? Integers seems pretty coarse.

Should we maybe outset the clip rect by a small delta instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Physical pixels since it's running right before this logic:

// The UIKit frame is set based on the logical resolution (points) instead of physical.
// (https://developer.apple.com/library/archive/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/Displays/Displays.html).
// However, flow is based on the physical resolution. For example, 1000 pixels in flow equals
// 500 points in UIKit for devices that has screenScale of 2. We need to scale the transformMatrix
// down to the logical resoltion before applying it to the layer of PlatformView.
transformMatrix.postScale(1 / screenScale, 1 / screenScale);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if its physical pixels and the error is at the scale of 0.00001, then even rounding to the nearest 0.01 should resolve the problem without having any meaningful effect on the rendering while still being way smaller than the space between two sample points (at 4x MSAA there are 4 per physical pixel).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be fine to copy the logic here and round the outer rect out:

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

    /** Sets SkRect by discarding the fractional portion of fLeft and fTop; and rounding
        up fRight and fBottom, using
        (sk_float_floor(fLeft), sk_float_floor(fTop),
         sk_float_ceil(fRight), sk_float_ceil(fBottom)).

        @param dst  storage for SkRect
    */
    void roundOut(SkRect* dst) const {
        dst->setLTRB(sk_float_floor(fLeft), sk_float_floor(fTop),
                     sk_float_ceil(fRight), sk_float_ceil(fBottom));
    }

@hellohuanlin
Copy link
Contributor

hellohuanlin commented Feb 8, 2024

Platform view clip mutators are being applied when the platform view scrolls off screen.

For me to better understand - this rounding trick works because the platform view is less than 1 pixel outside the screen bound, and we mistakenly considered it as still within the screen bound (due to rounding errors)?

@chinmaygarde
Copy link
Member

@jmagman: @hellohuanlin has an open question. But otherwise this PR seems good to go. Can we land it?

@jmagman
Copy link
Member Author

jmagman commented Feb 15, 2024

But otherwise this PR seems good to go. Can we land it?

There's a request to make it round to 0.01 instead of integer. I need to update it.

@jmagman jmagman marked this pull request as draft February 15, 2024 21:22
@hellohuanlin
Copy link
Contributor

do we want to hold this till we have a project to measure the improvement?

@@ -72,7 +71,6 @@
3DD7D38C27D2B81000DA365C /* FlutterUndoManagerPluginTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterUndoManagerPluginTest.mm; sourceTree = "<group>"; };
689EC1E2281B30D3008FEB58 /* FlutterSpellCheckPluginTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterSpellCheckPluginTest.mm; sourceTree = "<group>"; };
68B6091227F62F990036AC78 /* VsyncWaiterIosTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = VsyncWaiterIosTest.mm; sourceTree = "<group>"; };
73F12C22288F92FF00AFC3A6 /* FlutterViewControllerTest_mrc.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterViewControllerTest_mrc.mm; sourceTree = "<group>"; };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in #48162

@@ -50,7 +50,6 @@
0AC232F724BA71D300A85907 /* FlutterEngineTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterEngineTest.mm; sourceTree = "<group>"; };
0AC2330324BA71D300A85907 /* accessibility_bridge_test.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = accessibility_bridge_test.mm; sourceTree = "<group>"; };
0AC2330B24BA71D300A85907 /* FlutterTextInputPluginTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterTextInputPluginTest.mm; sourceTree = "<group>"; };
0AC2330F24BA71D300A85907 /* FlutterBinaryMessengerRelayTest.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterBinaryMessengerRelayTest.mm; sourceTree = "<group>"; };
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlutterBinaryMessengerRelayTest is no longer in the iOS directory and is tested in common #44395

@jmagman jmagman marked this pull request as ready for review February 16, 2024 21:42
@chinmaygarde
Copy link
Member

Triage: The last update was that there was still some work to do to setup a benchmark for this. Is that still the case?

@jmagman
Copy link
Member Author

jmagman commented Feb 29, 2024

This doesn't work except on top of #50386, which makes me think I don't have the whole picture here. Closing for now.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[iOS] Mutator clips should not be applied to clipped elements that are scrolled "offscreen"
5 participants