开启垂直同步后,固定时间步长出现了卡顿现象

7
在我实现的2D OpenGL引擎中,我采用了著名的fix your timestep文章所描述的固定时间步长和混合技术。我有一个测试对象在垂直方向(y轴)移动。在移动过程中存在抖动(预编程移动,非用户输入),这意味着物体不能在屏幕上平滑移动。请查看我提供的未压缩视频链接:LINK。游戏帧率保持在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

现在已经开启了垂直同步(VSync)。
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

  • 没有在电脑上做其他事情。请记住,此问题也会在其他计算机上发生,不仅仅是我的电脑

  • 正在主显示器上运行。显示器并不真正起到作用。无论是全屏还是窗口模式都会出现此问题。


我不明白你是如何在 60fps 的情况下看到卡顿的,因为你的数据显示 Δ 值非常相似。如果忽略时间步长逻辑,仅仅每一帧将 ypos 加上 16.6,你还能看到卡顿吗? - user146043
我是的,没错。这很奇怪。 - Ed Rowlett-Barbu
我已经发布了我的框架函数以供参考,如果有帮助的话。 - Ed Rowlett-Barbu
在渲染代码中使用的实际位置和上面打印的“理想”ypos delta之间是否存在四舍五入? - Ben Voigt
这是直接用于渲染的那一个。它是通过使用混合因子,混合当前和前一个位置状态而获得的。 - Ed Rowlett-Barbu
显示剩余15条评论
3个回答

2

我已经为自己的代码做了性能分析。问题是我的代码中有个地方由于缓存丢失,偶尔会出现性能峰值。这导致循环的时间超过了16.6666毫秒,即它应该流畅显示在60赫兹的最大时间。这只发生在偶尔的一帧上。那一帧导致了卡顿。代码逻辑本身是正确的,这证明这是一个性能问题。

为了日后参考,希望这可以帮助其他人。我是通过添加

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

在我的框架代码中,当if触发时,它会显示上一个帧中函数的分析统计数据,以查看哪个函数在前一个帧中花费了最长时间。因此,我能够将性能错误准确地定位到一种不适合其使用的结构上。这是一个巨大而丑陋的映射图,生成了大量的缓存未命中,并偶尔在性能方面出现波动。
我希望这能帮助未来的不幸者。

我还没有找到,我正在寻找一个替代的映射映射。对于我的需求来说,这将是一种稀疏的三维矩阵容器,可以通过索引访问元素,在内存中是连续的,并且不为空元素/位置分配空间。但我找不到这样的容器。 - Ed Rowlett-Barbu
请查看http://en.wikipedia.org/wiki/Volume_rendering,了解概述,八叉树或尝试查找JUDY结构。 - Surt

0

看起来你没有将你的60Hz帧循环与GPU的60Hz垂直同步进行同步。是的,你已经在Nvidia中启用了Vsync,但这只会导致Nvidia使用一个在Vsync上交换的后备缓冲区。

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


-1
这是一个棘手的问题,但从上面来看,我觉得这不是一个“帧率”问题,而是在你的“animate”代码中的某个地方。另一个观察是“Update(stepSizeMicro / 1000000.f);”这一行。除以1000000.f可能意味着由于浮点数位分辨率的限制,你正在失去精度,因此四舍五入可能是你的杀手?

我非常怀疑这一点。我在我的问题中展示了对象呈现的位置。正如您所看到的,这些位置随时间变化非常平滑。 - Ed Rowlett-Barbu
另外,正如我上面所说的,当将进程优先级设置为实时时,问题不会发生。 - Ed Rowlett-Barbu
这是一种帧跳过实现,以避免众所周知的死循环问题。由于更新和动画在同一个线程上,因此不可能存在同步问题。无论更新函数做什么,您都可以完全忽略该函数。我写的位置是来自动画,那是对象显示的位置。当然会有比更新更多的动画步骤。这是正常的,更新之间的位置是插值的。请阅读我发布的链接,我觉得您对固定时间步长不熟悉。固定时间步长非常适用于确定性模拟。 - Ed Rowlett-Barbu
嗯,我很熟悉,并且已经阅读了您发布的链接。只是试图消除显而易见的问题。我理解您的挫败感。所以另一个愚蠢的问题 :) 在“float blend = accumulator.asMicroseconds() / (float) stepSizeMicro;”中,您在执行除法之前是否应该将“accumulator.asMicroseconds()”也转换为浮点数? - Gavin Simpson
stepSizeMicro已经转换为浮点型。在C++中,表达式会被转换为两个操作数中较大的类型:P - Ed Rowlett-Barbu
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接