-
Notifications
You must be signed in to change notification settings - Fork 6k
[rotation_distortion]send interpolated viewport metrics to address rotation distortion #40412
[rotation_distortion]send interpolated viewport metrics to address rotation distortion #40412
Conversation
b7b91d2
to
2a51b57
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM.
I think an alternative we might consider is to update the framework/Dart API so that this could be done in Dart code instead - but if iOS is really the only platform where this happens that may be overkill.
It would be worth filing a bug to improve the curve used on this. Historically iOS users care about this kind of thing more than I would have expected, and with this patch it should be relatively easy to fix for someone who wants to take the time to figure that out.
UIEdgeInsets fromPadding, | ||
CGSize toSize, | ||
UIEdgeInsets toPadding) { | ||
CGFloat scale = [UIScreen mainScreen].scale; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest checking if the view is loaded before doing this:
CGFloat scale = [UIScreen mainScreen].scale; | |
UIScreen* mainScreen = self.mainScreenIfViewLoaded; | |
if (mainScreen == nil) { | |
return; | |
} | |
CGFloat scale = mainScreen.scale; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just did some research - it turned out to be more involved and likely requires non-trivial refactoring to make it work in unit test, as explained here.
/** | ||
* Interpolate the viewport metrics for smoother rotation transition. | ||
*/ | ||
void FLTInterpolateViewportMetrics(flutter::ViewportMetrics& viewportMetrics, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of passing in a viewportMetrics reference, can this instead be a method on the view controller that directly accesses _viewportMetrics
? Or was that too difficult to test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I had issue with testing - OCMock/NSInvocation
only supports populating NSObject references, and not structs. And I do not want to expose viewportMetrics
property as it's anti-pattern. So I made this pure function for easier testing.
|
||
// End of rotation. Invalidate the timer. | ||
if (rotationProgress == 1) { | ||
_isDuringRotationTransition = NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this potentially racy, can -viewWillTransitionToSize
be called before the timer is invalidated/the rotation is complete? Can the rotation be reversed halfway through? Maybe this should instead be tracked with a NSConditionLock
? Or the timer object be cached on the view controller and invalidated if -viewWillTransitionToSize
is called a second time before it's complete?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch. I did some experiment - the viewWillTransitionToSize
itself behaves nicely, it gets called only after the previous rotation is done. However, race can still happen due to a few reasons (added in the comment).
…izes during rotation to fix a problem with aspect ratio distortion
shell/platform/darwin/ios/framework/Source/FlutterViewController.mm
Outdated
Show resolved
Hide resolved
__block double rotationProgress = 0; | ||
// Timer is retained by the run loop, and will be released after invalidated. | ||
[NSTimer | ||
scheduledTimerWithTimeInterval:kRotationViewportMetricsUpdateInterval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe replace kRotationViewportMetricsUpdateInterval with 1/[DisplayLinkManager displayRefreshRate]
for pro-motion devices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not necessary during rotation since the device is moving. I've even tried 30fps - not too bad either (And i don't have a pro motion device)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i can test with promotion device
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feel free to pull the branch, or can wait for me to fix my laptop's engine build (hopefully this week).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, let me see if I can expense one.
3a45ba4
to
580b9d2
Compare
Update: @cyanglaz and I found a performance issue for complex projects like flutter gallery. Let me try a few things. Worst case scenario we may have to rely on the snapshot approach. |
CGSize oldSize = self.view.bounds.size; | ||
UIEdgeInsets oldPadding = self.view.safeAreaInsets; | ||
|
||
__block double rotationProgress = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively use NSProgress
, this is fine though.
@@ -1299,7 +1419,7 @@ - (void)viewDidLayoutSubviews { | |||
_viewportMetrics.physical_height = viewBounds.size.height * scale; | |||
|
|||
[self updateViewportPadding]; | |||
[self updateViewportMetrics]; | |||
[self updateViewportMetricsIfNeeded:NO]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the call sites it's not clear what the boolean does (missing named parameters in Objective-C 😞). How about -updateViewportMetricsIfNeeded
and -updateViewportMetricsIfNeededForRotation
?
To share an update: I tried fine-tuning the interpolation count to address the perf issue on complex UI, and found There is also a good news - Fun story, when i was playing basketball yesterday, a new solution came to my mind, and I call it delayed swap: In the originally behavior, we swap the width/height immediately before the rotation, resulting in roughly ~4x distortion in the beginning. With "delayed swap" solution, we swap the width/height right in the middle of the rotation (i.e. delay the swap for half of the transition duration). If I am imagining it right, this new "delayed swap" solution should give us the same result as the "snapshot" solution (reducing ~4x distortion to ~2x), but without the drawback of "snapshot" solution (i.e. it should work well with dynamic content like animation or video). This "delayed swap" solution is much easier than both "size interpolation" and "snapshot" solution. I am quickly trying it out and hope to get some good result🤞 |
…distortion (#40730) The "size interpolation" solution didn't go well (more context [here](#40412 (comment))). Then a new solution came to my mind, and I call it **"delayed swap"**: In the originally behavior, we swap the width/height immediately before the rotation, resulting in roughly ~4x distortion in the beginning. With "delayed swap" solution, we **swap the width/height right in the middle of the rotation** (i.e. delay the swap for half of the transition duration). This new "delayed swap" solution gives us the same benefit as the "snapshot" solution: - reducing ~4x distortion to ~2x - most distorted frames occur in the middle of rotation when it's moving the fastest, making it hard to notice And it fixes the drawback of "snapshot" solution: - it works well with dynamic content like animation or video - it doesn't have a ~0.5 second penalty when taking the snapshot Looks pretty good on flutter gallery: https://user-images.githubusercontent.com/41930132/228383137-7cd09982-89a9-4c83-bf55-9431de708278.mp4 *List which issues are fixed by this PR. You must list at least one issue.* Fixes flutter/flutter#16322 *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
Send interpolated viewport metrics to make the rotation smoother. Result is pretty good, as show below.
There are still some minor amount of distortion. There are 2 main reasons:
Currently we are calling framework from engine per frame. A potential performance improvement can be just sending the rotation duration to the framework, and have the framework to do the interpolation by itself. Though this will require a whole lot more refactoring on both engine and framework.
3.size.interpolation.mp4
List which issues are fixed by this PR. You must list at least one issue.
Fixes flutter/flutter#16322
If you had to change anything in the flutter/tests repo, include a link to the migration guide as per the breaking change policy.
Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.