1

I am trying to use CADisplayLink to achieve a constant frame rate for a game loop. When testing it in a simulator I am not able achieve stable time frames. Should I consider a different approach?

Or is that just because of the simulator not being able to emulate the hardware appropriately?

According to Apple this is how to calculate FPS:

let actualFramesPerSecond = 1 / (displaylink.targetTimestamp - displaylink.timestamp)

In my plain iOS app that I've set up for testing this prints a constant frame rate:

 FPS: 59.99999875668439
 FPS: 59.99999875668439
 FPS: 59.99999875668439
 FPS: 59.99999875668439
 FPS: 59.99999875668439

However, when I calculate the FPS using the actual elapsed time, it gives something else:

 FPS: 64.35942918520792
 FPS: 58.30848150362142
 FPS: 57.640194044003465
 FPS: 64.47022656706324
 FPS: 59.392580005664115
 FPS: 60.282043174566674

Plus, when using the code for rendering some sprites it looks unstable in simulator. How is it possible that the above code snippet diverges so much from reality?

My main concern is: Why is there a constant frame rate when calculating the FPS the Apple way? Is it possible that their documentation just contains a little glitch? Besides, the 'actual' values are kind of expected when there is some load, but for an empty loop that is doing nothing?

This is the code I am using to execute my game loop:

private var previousTimeInSeconds: Double = 0

private lazy var displayLink: CADisplayLink = {
    let displayLink = CADisplayLink(target: self,
                                    selector: #selector(displayLoop))
    return displayLink;
}()

private func startLoop() {
    previousTimeInSeconds = Date().timeIntervalSince1970
    displayLink.add(to: .current, forMode: .common)
}

@objc private func displayLoop() {
    let currentTimeInSeconds = Date().timeIntervalSince1970
    let elapsedTimeInSeconds = currentTimeInSeconds - previousTimeInSeconds
    previousTimeInSeconds = currentTimeInSeconds

    //let actualFramesPerSecond = 1 / (displayLink.targetTimestamp - displayLink.timestamp) // is showing constant 59.xxx FPS
    let actualFramesPerSecond = 1 / elapsedTimeInSeconds

    print("FPS: \(actualFramesPerSecond)") // varies from 50.xxx to 70.xxx FPS
    /*
     FPS: 64.35942918520792
     FPS: 58.30848150362142
     FPS: 57.640194044003465
     FPS: 64.47022656706324
     FPS: 59.392580005664115
     FPS: 60.282043174566674
     */
}
Nusatad
  • 3,231
  • 3
  • 11
  • 17
  • I wouldn’t worry about frame rates on simulator. The question is what sort of frame rates you achieve on reasonably configured devices. And make sure to test with release build, not a debug build. – Rob Sep 14 '19 at 17:59
  • CADisplayLink does not promise a constant frame rate. It promises a display-linked frame rate. Even on iOS, while it may be quite consistent, this isn't promised and you certainly cannot rely on it. You have to do animations based on actual time, not assuming that the rate is always consistent. – Rob Napier Sep 14 '19 at 19:34
  • **it's not supposed to be constant!!!!** that is a profound misunderstanding of how computers/displays work. – Fattie Dec 22 '22 at 21:00

1 Answers1

4

I think I found the answer. Don't use Date().timeIntervalSince1970 to calculate the individual frame rates per second. It seems to be just not precise enough.

Instead, use displayLink.timestamp, which is the time value associated with the last frame that was displayed. Replace the Date() usages in my code above with timestamp and you get the expected FPS values:

FPS: 59.99999875668439
FPS: 59.99999896623196
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 29.999999430729087
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 59.99999896623196
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 59.99999875668439
FPS: 59.99999896623196

As you can see, it's mostly constant at 59 FPS, with a few drops here and there which is totally expected as there is no guarantee that CADisplayLink is called at a constant rate. But under 'labor situations' with (almost) no code to execute this is the frame rate you'd could expect.

The formular I took from Apple's documentation ("let actualFramesPerSecond = 1 / (displayLink.targetTimestamp - displayLink.timestamp)") is kind of useless if want to calculate the real frame rate times. Instead, it shows that targetTimestamp can be used to achieve a stable game loop (as the resulting value is indeed a constant). I wish the documetation could've been more clear in that.

Nusatad
  • 3,231
  • 3
  • 11
  • 17
  • For anyone googling this old question. For better or worse, A basic understsnding (indeed, advanced understanding!) of game loops is taken as a given, unfortunately, in Apple's run loop systems. I suggested, starting wit their page which indeed shows the run loop as a sort of circular diagram (it's pointless giving the URL as they change the page constantly, simply google). – Fattie Dec 22 '22 at 21:07
  • note that the "formula" they give, which is utterly stupid, would be the "frame rate" of *that one frame*. frame rate (such as shown on your gaming PC) means the average frame rate over some amount of time. "frame rate" is not used in any way when animating. – Fattie Dec 22 '22 at 21:10