为 Unity 编程 FPS 限制器

Programming an FPS limiter for Unity

首先,我知道 Application.targetFrameRate 存在,它做得很好,但我想要更准确的东西。对我来说,当设置为 60 时,它将帧速率限制在 60.3 左右,当设置为 200 时,它限制在 204 左右。顺便说一下,这些是 使用 RTSS 7.2 在构建中(而不是在编辑器中)测量的。

所以我开始使用低级计时器在 Unity 中创建我的自定义帧限制器,但由于某些原因它无法正常工作。这是我的代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System;

public class FrameLimiter : MonoBehaviour
{
    private FrameLimiter m_Instance;
    public FrameLimiter Instance { get { return m_Instance; } }

    public double FPSLimit = 300.0;

    private long lastTime = HighResolutionDateTime.UtcNow.Ticks;

    void Awake()
    {
        m_Instance = this;
    }

    void OnDestroy()
    {
        m_Instance = null;
    }

    void Update()
    {
        if (FPSLimit == 0.0) return;

        lastTime += TimeSpan.FromSeconds(1.0 / FPSLimit).Ticks;

        var now = HighResolutionDateTime.UtcNow.Ticks;

        if (now >= lastTime)
        {
            lastTime = now;
            return;
        }
        else
        {
            SpinWait.SpinUntil(() => { return (HighResolutionDateTime.UtcNow.Ticks >= lastTime); });
        }
    }
}

基本上这是一个单例,设置为在脚本执行顺序中先于任何其他脚本执行,并且它会阻止执行,直到到达正确的时间。

它的工作方式是跟踪上次允许渲染帧的精确时间(它从非常精确的低级计时器获取当前时间,here's more detail about that)。然后在每次调用它的 Update() 函数时,它都会给它加上 1.0 / FPSLimit 秒以获得下一帧应该渲染的时间,然后如果当前时间小于这个时间,它会阻塞执行直到已达到该时间戳。

SpinWait 基本上只是一种阻止执行的有效方法,而不像空 while 循环那样固定 CPU 。但只是为了记录,我也尝试了一个空的 while 循环,得到了相同的结果,除了更高的 CPU 使用率。

因此,如果您了解此代码的工作原理,您应该会发现理论上这应该非常精确地锁定帧速率,特别是考虑到此计时器的精度优于 1μs (0.001ms) according to Microsoft. 但是尽管如此,当我将其设置为锁定在 60 时,我得到了大约 58.8 FPS,我真的不明白。 我是 运行 在禁用 V-sync 的独占全屏模式下构建的。 顺便说一句,我得到了更高的帧率,没有限制,所以基本性能游戏不是问题。

如有任何帮助,我们将不胜感激!

如果您的程序运行完美并且旋转等待恰好在计算的时间完成,您将获得恰好 60fps。

没有什么是完美的,因此它会比计算的时间多一点,从而降低帧速率。

这个问题比我想象的要奇怪。 DateTime 的这种实现似乎在内部将存储时间舍入为整数毫秒,因此当设置为 60 FPS 时,我的帧限制器将帧调整为 17ms 而不是 16.6666ms。

解决方案是更改我正在使用的计时器的代码,只获取 GetSystemTimePreciseAsFileTime() 返回的原始值,而不是将其封装在 DateTime 对象中。

通过此更改,RTSS 在构建中显示完美的 60.0 FPS 锁定,如果限制器设置为 60,偶尔会下降到 59.9。

这也是帧时间图的样子。我在不同的地方环顾地图,试图稍微锻炼一下帧限制器的一致性。可以肯定地说,我制作的帧限制器比 Unity 自己的要好得多:)