首页 > 解决方案 > 为 Unity 编写 FPS 限制器

问题描述

首先,我知道它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); });
        }
    }
}

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

它的工作方式是跟踪上次允许渲染帧的精确时间(它从非常精确的低级计时器获取当前时间,这里有更多详细信息)。然后在每次调用它的Update()函数时,它会增加1.0 / FPSLimit几秒钟以获取应该渲染下一帧的时间,然后如果当前时间小于这个时间,它会阻止执行,直到达到该时间戳。

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

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

任何帮助,将不胜感激!

标签: c#unity3dframe-rate

解决方案


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

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

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

这也是帧时间图的样子。我在地图上四处寻找不同的东西,试图稍微锻炼一下帧限制器的一致性。可以肯定地说,我做了一个比 Unity 自己的更好的帧限制器:)

FPS限制器


推荐阅读