【问题标题】:Fixed timestep stuttering with VSync on修复了打开 VSync 时的时间步卡顿
【发布时间】:2014-10-06 14:46:35
【问题描述】:

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

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

请看我链接的未压缩视频:LINK

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

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

但是屏幕上的动作严重卡顿。

编辑:我注意到通过逐帧进入上面录制的视频,卡顿是由一帧显示两次引起的。

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

下面是对象在不同时间的 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);
}

在 cmets 中要求的更多信息:

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

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

  • 没有在我的计算机上执行任何其他操作。请记住,此问题也发生在其他计算机上,不仅仅是我的设备

  • 正在主显示器上运行。显示器并没有真正的区别。在全屏和窗口模式下都会出现此问题。

【问题讨论】:

  • 你没有分析性能问题的根源是什么,这可能是很多事情——后台任务、资源加载、过于复杂的代码……
  • 没有性能问题。游戏运行 200+ fps。口吃是问题。口吃意味着对象在屏幕上没有平滑移动。您可以在我发布的日志中的屏幕上看到不同时间的位置。位置是正确的,并且与时间成线性关系。如果问题不够清楚,请告诉我。我不知道为什么人们不赞成它!
  • 开启垂直同步后,它保持在 60fps。口吃仍然存在。
  • 我看不出在 60fps 的情况下你怎么会看到卡顿,因为你的数据显示你的增量完全相同。如果你忽略时间步长逻辑,只是简单地将 16.6 添加到每帧的 ypos 上,你还会看到卡顿吗?
  • 我愿意,是的。这就是奇怪的部分。

标签: c++ opengl visual-c++ c++11 game-engine


【解决方案1】:

我已经分析了我自己的代码。问题是我的代码的某个区域偶尔会由于缓存未命中而出现性能峰值。这导致我的循环花费的时间超过 16.6666 毫秒,这是在 60Hz 下流畅显示所需的最长时间。这只是一帧,偶尔。那个框架引起了口吃。代码逻辑本身是正确的,这被证明是一个性能问题。

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

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

在我的框架代码中。当if 被触发时,它会显示最后一帧中函数的分析统计信息,以查看哪个函数在前一帧中花费的时间最长。因此,我能够将性能错误定位到不适合其使用的结构。一张大而讨厌的地图地图,会产生大量缓存未命中,并且偶尔会出现性能飙升。

我希望这对未来不幸的人有所帮助。

【讨论】:

  • 我没有,我正在寻找地图地图的替代品。对于我的需求,这将是一个稀疏的 3d 矩阵容器,它仍然可以按索引访问元素,在内存中是连续的,并且不会为空元素/位置分配空间。但我找不到。
  • en.wikipedia.org/wiki/Volume_rendering 的概述,八叉树或尝试查看 JUDY 结构。
【解决方案2】:

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

您需要将swap interval 设置为1 并执行glFinish() 以等待Vsync。

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 我非常怀疑这一点。我已经显示了在我的问题中呈现对象的位置。如您所见,位置随着时间的推移非常平滑。
    • 另外,正如我上面所说,将进程优先级设置为实时时不会出现此问题。
    • 是的,我看到了。试试这个怎么样:创建一个名为“animation_complete”的全局变量。在每个 draw() 函数结束时将其转储到日志文件中(带有帧号和动画步号的计数器?)。在“更新”函数的开头将其设置为 false,最后将其设置为 true。也许这将有助于找出至少正确的动画步骤是否完成。或类似的东西....因为我确定在调用绘制函数之前您的动画(更新)没有完成,即同步问题。
    • 进一步...我猜你的动画进度实际上不是基于时间的,而是基于帧的。 (在更新功能内)。这意味着如果时间确实经过了两步,动画仍然只会更新 1 步,因此会重复帧。我希望这是有道理的。
    • 行“if (elapsedSinceLastFrame.asMicroseconds() > 250000) elapsedSinceLastFrame = sf::microseconds(250000);”不要;对我没有意义。你是说如果需要太长时间就忽略它需要太长时间?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-31
    • 2011-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-13
    • 1970-01-01
    相关资源
    最近更新 更多