CAMetalLayer nextDrawable delay with Control Center

So I have a Metal App up and running at a nice solid 60 fps.....


Sometimes when I swipe up the control center and then slide it down the fps dips to 30-40fps. It hangs there for awhile and then goes back up to 60 fps. Sometimes it fails to go back to 60 fps and just sits at 40 fps.


I have tried several of the Metal Examples and the same behavior is observed.


I tracked it down and the delay is being caused by the nextDrawable command. I call this as late in the frame as possible and I am running metal from the same thread via a Display link just like the examples. However, even the examples suffer from stutter if the Control Center is brought up during execution.


Minimizing\switching the app and then bringing it back up to focus magically fixes the issue. This is also happening in the Metal examples.


Does anyone have any ideas what could be causing this? Many thanks for anyones help.

Replies

Apple ***** at making forums, so I can't post links here as they'll go into moderation purgatory across the entire weekend and probably most of the early part of next week.


YOU ARE NOT ALONE!


This particular issue is within Scene Kit and Sprite Kit, too. And no, it doesn't matter if the user chooses Metal or OpenGL ES as the rendering technology.


Near as I can determine this is something to do with how a combination of iOS and the CADisplayLink operate in consideration of potential device performance. I'm assuming the game engines of iOS use CADisplayLink under-the-hood to sync/control their game loops.


My guess is that there's something causing a throttling, a very deliberate throttling, by the OS as a result of some kind of research/analysis of app performance that it's doing.


When you do the jump out to the home screen or switch apps you're (sometimes) re-triggering that analysis, at which point it may or may not find that performance of your app is more capable, so it releases the throttling.


I say "sometimes" because if you do it very fast you can do it without re-triggering the analysis of your app's performance. There are other activities that can cause this re-triggering of the sampling, too. Taking a call, an incoming barrage of messages/notifications, connecting and using the Quicktime screen record facility and a few others. Basically anything that sends a big interrupt.


Conversely, any of these interrupts can trigger a throttling from 60fps to 40fps.


There are other throttling rates, too, but 40fps is certainly the most commonly observed and annoying of them.

Thanks,


While I have seen this in the Metal demos, I did manage to solve it on my end even though the solution seems unrelated. Essentially I was getting sloppy because I wanted to get something quickly up on the screen, so I was sending and repeatedly drawing a large triangle mesh without first having it get partitioned in my scene graph.


Once I organized my vertex\index buffers more sanely this problem is not showing up. However, like I said earlier the Metal Demos still have this issue and there is no guarantee that I won't see it again as I develop my game.


In any case many thanks for pointing the issue out for me.

I encountered the same issue when using Metal framework (Xcode 13.3) to render a scrollable view on iPad Pro 11 with M1 chip (iPadOS 15.4).

MTKView works well with 120fps when I open the app and interact with it. However, opening the control center (or the notification center) and then closing it, the frame rate often drops to 80Hz.

the demo project - https://github.com/Yang-Xijie/ScrollablePencilMTKView

Does anyone has some great solutions?

Stoping the MTKView rendering and opening later by setting isPaused in SceneDelegate might be one workaround (Genshin Impact uses this).

func sceneWillResignActive(_ scene: UIScene) {
    mainVC.documentVC?.renderView.isPaused = true
}

func sceneDidBecomeActive(_ scene: UIScene) {
    mainVC.documentVC?.renderView.isPaused = false
}

Or some tricky methods that never stop rendering? Notability will still render its pages when user interacts with control center. I do wonder how this is achieved...

It seems that https://github.com/flutter/flutter/issues/90675 has discussed about this problem.

I found that it seems that set presentsWithTransaction to true can solve this problem..... But this needs that rendering thread (or present drawable thread must be main thread.)