CADisplayLink 无法在模拟器中实现恒定帧速率

Posted

技术标签:

【中文标题】CADisplayLink 无法在模拟器中实现恒定帧速率【英文标题】:CADisplayLink is unable to achieve a constant frame rate in simulator 【发布时间】:2019-09-14 16:52:53 【问题描述】:

我正在尝试使用CADisplayLink 来实现游戏循环的恒定帧速率。在模拟器中测试时,我无法达到稳定的时间范围。我应该考虑不同的方法吗?

还是只是因为模拟器无法正确模拟硬件?

根据Apple这是如何计算FPS的:

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

在我为测试而设置的普通 ios 应用程序中,会打印一个恒定的帧速率:

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

但是,当我使用实际经过的时间计算 FPS 时,它会给出其他信息:

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

另外,当使用代码渲染一些精灵时,它在模拟器中看起来很不稳定。上面的代码sn-p怎么可能和现实相差这么大?

我主要担心的是:为什么在以 Apple 方式计算 FPS 时帧速率是恒定的?他们的文档是否可能只是包含一些小故障?此外,当有一些负载时,“实际”值是预期的,但是对于一个什么都不做的空循环?

这是我用来执行游戏循环的代码:

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
     */

【问题讨论】:

我不会担心模拟器上的帧速率。问题是您在合理配置的设备上实现了什么样的帧速率。并确保使用发布版本进行测试,而不是调试版本。 CADisplayLink 不承诺恒定的帧速率。它承诺显示链接的帧速率。即使在 iOS 上,虽然它可能非常一致,但这不是承诺的,你当然不能依赖它。您必须根据实际时间制作动画,而不是假设速率始终一致。 【参考方案1】:

我想我找到了答案。不要使用Date().timeIntervalSince1970 来计算每秒的单个帧速率。它似乎不够精确。

改为使用displayLink.timestamp,这是与显示的最后一帧相关联的时间值。将我上面代码中的 Date() 用法替换为 timestamp,您将获得预期的 FPS 值:

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

如您所见,它几乎保持在 59 FPS 的速度,偶尔会出现几滴,这是完全可以预料的,因为无法保证 CADisplayLink 以恒定的速率被调用。但是在(几乎)没有代码执行的“劳动情况”下,这是您可以预期的帧速率。

如果要计算实际帧速率时间,我从 Apple 文档中获取的公式(“let actualFramesPerSecond = 1 / (displayLink.targetTimestamp - displayLink.timestamp)”)有点没用。相反,它表明targetTimestamp 可用于实现稳定的游戏循环(因为结果值确实是一个常数)。我希望文档可以更清楚。

【讨论】:

以上是关于CADisplayLink 无法在模拟器中实现恒定帧速率的主要内容,如果未能解决你的问题,请参考以下文章

在 iPhone 上使用 CADisplayLink 在 OpenGL ES 视图中进行等速旋转

通过消除恒定运动进行运动检测

在恒定时间内“拆分”矩阵

Anylogic - 如何在模拟实验中实现 CRN (Common Random Numbers) 方法?

安装app到ios模拟器中实现控制

在 PHPUnit 中实现给定接口的模拟对象上的未定义方法?