修复了VSync打开时出现的时间步长停顿问题

Fixed timestep stuttering with VSync on

本文关键字:时间 问题 VSync      更新时间:2023-10-16

在我实现的2D OpenGL引擎中,我有一个固定的时间步长,如著名的修复时间步长文章中所述,以及混合。

我有一个垂直移动的测试对象(y轴)。动作中有口吃(预先编程的动作,而不是来自用户输入)。这意味着对象不能在屏幕上平滑移动。

请参阅我正在链接的未压缩视频:链接

游戏帧速率保持在60fps(从Nvidia驱动程序打开Vsync)

游戏逻辑以我设定的每秒20次更新/滴答的固定速度更新。这很正常。对象每次更新移动50个像素。

然而,屏幕上的动作却非常断断续续。

编辑:我在上面一帧一帧的录制视频中注意到,口吃是由一帧显示两次引起的。

编辑2:在任务管理器中将应用程序优先级设置为"实时",完全消除了卡顿!然而,这显然不是一个解决方案。

下面是在VSync关闭的情况下,对象y在不同时间的移动增量第一列是自上一帧以来经过的时间,单位为微秒(不包括4403)第二列是自上一帧以来对象在y轴上的移动。实际上,物体每秒移动1000个像素,下面的日志证实了这一点

time since last frame: 4403    ypos delta since last frame: 4.403015
time since last frame: 3807    ypos delta since last frame: 3.806976
time since last frame: 3716    ypos delta since last frame: 3.716003
time since last frame: 3859    ypos delta since last frame: 3.859009
time since last frame: 4398    ypos delta since last frame: 4.398010
time since last frame: 8961    ypos delta since last frame: 8.960999
time since last frame: 7871    ypos delta since last frame: 7.871002
time since last frame: 3985    ypos delta since last frame: 3.984985
time since last frame: 3684    ypos delta since last frame: 3.684021

现在打开了垂直同步

time since last frame: 17629     ypos delta since last frame: 17.628906
time since last frame: 15688     ypos delta since last frame: 15.687988
time since last frame: 16641     ypos delta since last frame: 16.641113
time since last frame: 16657     ypos delta since last frame: 16.656738
time since last frame: 16715     ypos delta since last frame: 16.715332
time since last frame: 16663     ypos delta since last frame: 16.663086
time since last frame: 16666     ypos delta since last frame: 16.665771
time since last frame: 16704     ypos delta since last frame: 16.704102
time since last frame: 16626     ypos delta since last frame: 16.625732

我会说他们看起来不错。

这几天来一直让我抓狂,我错过了什么?

下面是我在循环中调用的Frame函数:

void Frame()
{
static sf::Time t;
static const double ticksPerSecond = 20;
static uint64_t stepSizeMicro = 1000000 / ticksPerSecond; // microseconds
static sf::Time accumulator = sf::seconds(0);
gElapsedTotal = gClock.getElapsedTime();
sf::Time elapsedSinceLastFrame = gElapsedTotal - gLastFrameTime;
gLastFrameTime = gElapsedTotal;

if (elapsedSinceLastFrame.asMicroseconds() > 250000 )
    elapsedSinceLastFrame = sf::microseconds(250000);
accumulator += elapsedSinceLastFrame;
while (accumulator.asMicroseconds() >= stepSizeMicro)
{
    Update(stepSizeMicro / 1000000.f);
    gGameTime += sf::microseconds(stepSizeMicro);
    accumulator -= sf::microseconds(stepSizeMicro);
}
uint64_t blendMicro = accumulator.asMicroseconds() / stepSizeMicro;
float blend = accumulator.asMicroseconds() / (float) stepSizeMicro;
if (rand() % 200 == 0) Trace("blend: %f", blend);
CWorld::GetInstance()->Draw(blend);
}

评论中要求的更多信息:

  • 在全屏1920x1080和窗口模式1600x900 中都会出现卡顿

  • 设置是一个简单的SFML项目。我不知道它在渲染纹理矩形时是否在内部使用VBO/VAO

  • 没有在我的电脑上做任何其他事情。请记住,这个问题也发生在其他计算机上,这不仅仅是我的装备

  • 正在主显示器上运行。显示器并没有什么区别。该问题在全屏和窗口模式下都会发生。

我已经介绍了自己的代码。问题是我的代码中有一个区域由于缓存未命中而偶尔出现性能峰值。这导致我的循环耗时超过16.6666毫秒,这是在60Hz下平滑显示所需的最长时间。这只是一帧,偶尔一帧。那帧导致了口吃。代码逻辑本身是正确的,这被证明是一个性能问题。

为了将来的参考,希望这能帮助其他人,我是如何调试的,我放了一个

if ( timeSinceLastFrame > 16000 ) // microseconds
{
    Trace("Slow frame detected");
    DisplayProfilingInformation();
}

在我的帧代码中。当if被触发时,它会显示上一帧中函数的评测统计信息,以查看上一帧使用时间最长的函数。因此,我能够将性能缺陷精确定位为不适合其使用的结构。一个大而讨厌的映射映射,它产生了大量缓存未命中,偶尔性能会飙升。

我希望这能帮助未来不幸的灵魂。

您似乎没有将60Hz帧循环与GPU的60Hz VSync同步。是的,您已经在Nvidia中启用了Vsync,但这只会导致Nvidia使用在Vsync上交换的后缓冲区。

您需要将交换间隔设置为1,并执行glFinish()以等待Vsync。

这是一个棘手的问题,但从上面来看,这似乎不是"帧速率"问题,而是"动画"代码中的某个地方。另一个观察结果是行"Update(stepSizeMicro/1000000.f);"。除以100000000.f可能意味着由于浮点数字位分辨率的限制,您正在失去分辨率,所以四舍五入可能是您的杀手?