为 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 自己的要好得多:)
首先,我知道 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 自己的要好得多:)