线性运动口吃

时间:2014-01-25 21:11:22

标签: c++ game-physics directx-9

我使用Direct3D9ID3DXSprite中创建了简单的,与帧无关的,可变时间步长,线性移动。大多数用户都无法注意到它,但在某些(包括我的)计算机上,它经常发生,有时它会很多口吃。

  • VSync启用和禁用时发生口吃。

  • 我发现在OpenGL渲染器中也是如此。

  • 它不是浮点问题。

  • 似乎问题仅存在于AERO Transparent Glass窗口模式中(在全屏,无边框全屏窗口或禁用航空器时很好或至少不太明显),更糟糕的是当窗口失去焦点时。

修改

即使发生口吃,帧增量时间也不会离开16 .. 17 ms。

好像我的帧增量时间测量日志代码被窃听了。我现在修好了。

  • 正常情况下启用VSync的帧会渲染17ms,但有时(可能在发生sutttering时)会跳到25-30ms。

(我只在应用程序退出时转储一次日志,而不是在运行时转储,渲染,因此它不会影响性能)

    device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 0);

    device->BeginScene();

    sprite->Begin(D3DXSPRITE_ALPHABLEND);

    QueryPerformanceCounter(&counter);

    float time = counter.QuadPart / (float) frequency.QuadPart;

    float deltaTime = time - currentTime;

    currentTime = time;

    position.x += velocity * deltaTime;

    if (position.x > 640)
        velocity = -250;
    else if (position.x < 0)
        velocity = 250;

    position.x = (int) position.x;

    sprite->Draw(texture, 0, 0, &position, D3DCOLOR_ARGB(255, 255, 255, 255));

    sprite->End();

    device->EndScene();

    device->Present(0, 0, 0, 0);

由于Eduard Wirch和Ben Voigt的固定计时器(尽管它没有解决初始问题)

float time()
{
    static LARGE_INTEGER start = {0};
    static LARGE_INTEGER frequency;

    if (start.QuadPart == 0)
    {
        QueryPerformanceFrequency(&frequency);
        QueryPerformanceCounter(&start);
    }

    LARGE_INTEGER counter;

    QueryPerformanceCounter(&counter);

    return (float) ((counter.QuadPart - start.QuadPart) / (double) frequency.QuadPart);
}

编辑#2:

到目前为止,我尝试了三种更新方法:

1)可变时间步

    x += velocity * deltaTime;

2)固定时间步

    x += 4;

3)固定时间步长+插值

    accumulator += deltaTime;

    float updateTime = 0.001f;

    while (accumulator > updateTime)
    {
        previousX = x;

        x += velocity * updateTime;

        accumulator -= updateTime;
    }

    float alpha = accumulator / updateTime;

    float interpolatedX = x * alpha + previousX * (1 - alpha);

所有方法都工作得非常相似,固定时间步长看起来更好,但它不是一个完全依赖帧速率的选项,它不能完全解决问题(仍然很少跳跃(断断续续))。

到目前为止,禁用AERO Transparent Glass或全屏显示只是重大的积极变化。

我正在使用NVIDIA最新驱动程序GeForce 332.21 DriverWindows 7 x64 Ultimate

2 个答案:

答案 0 :(得分:7)

部分解决方案是简单的精确数据类型问题。用常数交换速度计算,你会看到一个非常平滑的运动。分析计算表明,您将结果从QueryPerformanceCounter()存储在浮点内。 QueryPerformanceCounter()会在我的计算机上返回一个如下所示的数字:724032629776。此数字至少需要存储5个字节。然而,float使用4个字节(实际数字只有24位)来存储该值。因此,将QueryPerformanceCounter()的结果转换为float时,精度会丢失。有时,这导致deltaTime零导致口吃。

这部分解释了为什么有些用户没有遇到这个问题。这完全取决于QueryPerformanceCounter()的结果是否适合float

问题的这一部分的解决方案是:使用double(或者作为Ben Voigt建议:存储初始性能计数器,并在转换为float之前从新值中减去此值。这将给出当你的应用程序运行很长时间(取决于性能计数器的增长速度)时,你至少会有更多的头部空间,但最终可能再次达到float分辨率限制。)

解决这个问题后,口吃的情况要少得多,但并没有完全消失。分析运行时行为表明偶尔会跳过一个帧。应用程序GPU命令缓冲区由Present刷新,但是当前命令保留在应用程序上下文队列中,直到下一个vsync(即使在vsync(14ms)之前很久就调用了Present)。进一步的分析表明,背景过程(f.lux)告诉系统偶尔设置伽马斜坡。此命令要求完整的GPU队列在执行之前运行。可能是为了避免副作用。这个GPU刷新是在'present'命令移动到GPU队列之前启动的。系统阻止视频调度直到GPU干涸。这直到下一个vsync。因此,在下一帧之前,当前数据包不会移动到GPU队列。这可见效果:口吃。

您也不太可能在计算机上运行f.lux。但是你可能正在经历类似的背景介入。您需要自己在系统上查找问题的根源。我写了一篇关于如何诊断框架跳过的博客文章:Diagnose frame skips and stutter in DirectX applications。你也会发现将f.lux诊断为罪魁祸首的整个故事。

但即使您发现帧的来源跳过,我怀疑在启用dwm窗口组合时您将获得稳定的60fps。原因是,你没有直接画到屏幕上。但相反,你绘制到dwm的共享表面。由于它是共享资源,因此可以被其他人锁定一段任意时间,使您无法保持帧速率对您的应用程序稳定。如果您确实需要稳定的帧速率,请全屏显示,或禁用窗口组合(在Windows 7上.Windows 8不允许禁用窗口组合):

#include <dwmapi.h>
...
HRESULT hr = DwmEnableComposition(DWM_EC_DISABLECOMPOSITION);
if (!SUCCEEDED(hr)) {
   // log message or react in a different way
}

答案 1 :(得分:2)

我查看了您的源代码,发现每帧只处理一个窗口消息。对我而言,这在过去引起了口吃。

我建议循环PeekMessage,直到它返回零表示消息队列已用尽。之后渲染一个框架。

所以改变:

if (PeekMessageW(&message, 0, 0, 0, PM_REMOVE))

while (PeekMessageW(&message, 0, 0, 0, PM_REMOVE))

编辑:

我编译并运行了代码(使用另一个纹理),它为我顺利显示了运动。我没有aero(Windows 8)。

我注意到的一件事:你设置了D3DCREATE_SOFTWARE_VERTEXPROCESSING。您是否尝试将其设置为D3DCREATE_HARDWARE_VERTEXPROCESSING