New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Scrolling is not true 120hz and feels laggy on ProMotion iPhone 13 #90675
Comments
This looks potentially related: https://twitter.com/ChristianSelig/status/1441419862417494018
|
Flutter uses Metal, so it should be already capable of reaching 120hz. I think that the current issue is that the vsync waiter currently checks for the reported frame rate from the CADisplayLink that is still 60. The reason why this happens could be related to this: I think it should be fairly simple to support the right refresh rate once it’s properly reported by the iOS SDK. On the other hand I’m worried that supporting a completely adaptive scenario (dynamically switching between 10 and 120hz), like the one that will be natively supported in the upcoming future also by third-party apps, could be way less trivial to achieve |
Thanks @sroddy I just tried adding |
According to apple: The answer came from Apple today. The company told AppleInsider that the 120Hz ProMotion screen will work on all third-party apps although developers need to add a plist entry in order for their apps to work with the faster refresh rate. |
This is the official Apple documentation |
It's not just the plist entry, it is also necessary to set |
For the time being, I showcased a sample implementation of how to enable the ProMotion feature: https://twitter.com/Kounexx/status/1442529256706396164 You can do this until there is an official implementation! |
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 15.0, *) {
let displayLink = CADisplayLink(target: self, selector: #selector(step))
displayLink.preferredFrameRateRange = CAFrameRateRange(minimum:80, maximum:120, preferred:120)
displayLink.add(to: .current, forMode: .default)
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@objc func step(displaylink: CADisplayLink) {
// Will be called once a frame has been built while matching desired frame rate
} But I guess that also means the app always runs at 120hz, which could be pretty bad on battery right? Do we need to wrap this into a method call and remember to turn the framerate up/down as we need it, or will the flutter engine do this for us in the future? edit |
cc @jmagman |
/cc @iskakaushik this may be of interest to you. A good excuse to pick up a iPhone 13 Pro test device. |
The behaviour seems to be strange indeed. Looking at the documentation, it even suggests variations like this:
Ultimately the best solution will probably be that Flutter applies different frame rates for different use cases (list scroll, expanding, progress bar etc.) instead of applying it in general. Testing the battery performance would be interesting as well! |
I think this needs to set in the engine, as per @sroddy:
Bigger issue might be we need an api exposed to change it at runtime. |
@chinmaygarde closed a similar issue a few years ago for the iPad Pro. From #27259 (comment)
There's an open issue to add an iPad Pro to the devicelab for benchmarking. #31450.
And
|
I think that the approach you are testing “kind-of” works because the screen refreshes at the max frame rate declared by any of the currently active animations. if you add a CADisplayLink on the current run loop that refreshes at 120fps, all the CADisplayLink, including the one registered by the Flutter Engine, will start having their callback invoked at 120fps (unless they have a min value specified-which the one used by Flutter Engine is not having) I think that there is no way to slow everything down below 60fps because the Flutter’s one is currently using the default settings and is set to run at 60fps min by default. |
@jmagman could we possibly bump the priority above p4? All native iOS apps support 120hz out of the box for things like scrolling, and it's only a potential issue when they have some kind of custom animation to show. For flutter apps, everything including scrolling is being rendered at 60hz which makes flutter apps feel laggy by comparison and feels like a big regression in app performance for anybody who simply upgrades their phone. This affects everybody with a flutter app deployed in production right now. |
@sroddy it only sets to 60fps if it's not at least iOS 10.3. I used some print statements to see what a default CADisplayLink reports without setting The output:
Looking at how the flutter engine sets up the CADisplayLink, It appears that a CADisplayLink by default returns 0 for everything, which means it would simply return This means flutter is already receiving the display refresh rate as 120. Note: the app is still rendering everything at 60hz even with this in place (with the appropriate Info.plist entry too). edit Another test, if I set |
Depending on how the MetalKit view has been implemented inside the Engine (I didn't have the time to investigate) it may also be needed to play with the preferredFramesPerSecond property of the MTKView itself. This WWCD session is very informative about how to potentially deal with this "madness" But in order to really benefit from the ProMotion display features, I think devs would need a way to specify the desired frame rate of a given animation, so that the frames can be provided at the desired pace and the ProMotion display would automatically adapt its frame rate to optimise rendering and battery, given the various conditions. |
It's still showing 60hz after implementing the changes above. Testing a list view on my iPhone 13 Pro Max and it's still laggy compared to native apps and the App Store. You can definitely tell the difference. |
Not sure wether this helps, but there seems to be a strange behaviour that could potentially be from Apple's side. This is the content of @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
var displayLink : CADisplayLink?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller = self.window.rootViewController as! FlutterViewController
displayLink = CADisplayLink(target: self, selector: #selector(displayLinkCallback))
displayLink!.add(to: .current, forMode: .default)
if #available(iOS 15.0, *) {
displayLink!.preferredFrameRateRange = CAFrameRateRange(minimum:120, maximum:120, preferred:120)
} else {
// Fallback on earlier versions
}
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
@objc func displayLinkCallback() {
// let workingTime = displayLink!.targetTimestamp - CACurrentMediaTime()
// At this moment in time, there are `workingTime` seconds available to
// generate content for the next frame, so
// Core Animation can render it to the display.
}
} with Starting the app for the first time on an iPhone 13 Pro Max, the app runs at Going to control centre and turning on I tried the same behaviour with |
Is there any alternative to modifying the engine |
Excited to see #96710 and flutter/engine#30900! |
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as duplicate.
This will be done with flutter/engine#29797 as described in https://flutter.dev/go/variable-refresh-rate which is blocked by #85555 which is blocked by #94078 which should be done in the next few weeks. Please refrain from commenting "me too"s on this issue, just give the issue a 👍 and you'll get updates. We know it's a problem and we're working on it. 🙂 |
The iOS 15 SDK is now available in the engine #85555. |
The engine fixed has been rolled into flutter/master.
To enable 120hz. Also make sure you have |
We're particularly interested in:
Please let us know if you have any feedback! |
|
Thanks @eric8810
Can you file a new issue for this? Likely unrelated to the app refresh rate. |
Does it resolve the laggy feel?
How does this impact battery life?
You shouldn't have to manually update Info.plist, did the flutter tool add the key for you?
|
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
Even though the performance overlay says the frame timing should easily produce 120fps, it feels different than native iOS apps on the iPhone 13- a bit slower and janky. this is not sksl jank
I took some slow motion videos to demonstrate the scrolling performance issue. When you play it back, scrub frame by frame and you will see there is a lot more jank between screen updates on the flutter app vs the native messages one.
You'll see the performance profiler reporting 2.7ms/frame which in theory should mean it's capable of 370fps+ but as you can see there is a big difference between the frames on the native messages app and the flutter sample app.
Flutter app video (iPhone 13 Pro):
https://drive.google.com/file/d/1Qwqty1dCoGUyIfB5t5WeBf1s6czQhzFZ/view
Native iOS messages app (iPhone 13 Pro):
https://drive.google.com/file/d/1w5cw_PaIiMPTlyLuq2dKo86-KsWr-FEN/view
In case Google Drive applies some processing to the videos, here's a zip file with them untouched:
https://drive.google.com/file/d/1kqDEEI50Bs1uctgePvHKM-hJEE0QUhrA/view
Steps to Reproduce
Example app available here:
https://github.com/acoutts/flutter_120hz_bug
Expected results:
Flutter apps should run as smooth as native apps.
Actual results:
Flutter performance does not match the native experience on 120hz devices.
Logs
The text was updated successfully, but these errors were encountered: