线性运动口吃

Dem*_*ion 7 c++ game-physics directx-9

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

  • 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);
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

编辑#2:

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

1)可变时间步

    x += velocity * deltaTime;
Run Code Online (Sandbox Code Playgroud)

2)固定时间步

    x += 4;
Run Code Online (Sandbox Code Playgroud)

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);
Run Code Online (Sandbox Code Playgroud)

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

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

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

Edu*_*rch 7

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

这部分解释了为什么有些用户没有遇到这个问题.这一切都取决于结果QueryPerformanceCounter()是否适合a float.

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

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

您也不太可能在计算机上运行f.lux.但是你可能正在经历类似的背景介入.您需要自己在系统上查找问题的根源.我写了一篇关于如何诊断帧跳过的博客文章:在DirectX应用程序中诊断帧跳过和断断续续.你也会发现将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
}
Run Code Online (Sandbox Code Playgroud)

  • 不,解决方案是首先使用整数算法从QueryPerformanceCounter中减去两个结果,并抛出差异. (2认同)