Linear movement stutter

只谈情不闲聊 提交于 2019-12-22 04:57:30

问题


I have created simple, frame independent, variable time step, linear movement in Direct3D9 using ID3DXSprite. Most users cant notice it, but on some (including mine) computers it happens often and sometimes it stutters a lot.

  • Stuttering occurs with VSync enabled and disabled.

  • I figured out that same happens in OpenGL renderer.

  • Its not floating point problem.

  • Seems like problem only exist in AERO Transparent Glass windowed mode (fine or at least much less noticeable in fullscreen, borderless full screen window or with aero disabled), even worse when window lost focus.

EDIT:

Frame delta time doesnt leave bounds 16 .. 17 ms even when stuttering occurs.

Seems like my frame delta time measurement log code was bugged. I fixed it now.

  • Normally with VSync enabled frame renders 17ms, but sometimes (probably when sutttering happens) it jumps to 25-30ms.

(I dump log only once at application exit, not while running, rendering, so its does not affect performance)

    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);

Fixed timer thanks to Eduard Wirch and Ben Voigt (although it doesnt fix initial problem)

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);
}

EDIT #2:

So far I have tried three update methods:

1) Variable time step

    x += velocity * deltaTime;

2) Fixed time step

    x += 4;

3) Fixed time step + Interpolation

    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);

All methods work pretty much same, fixed time step looks better, but it's not quite an option to depend on frame rate and it doesn't solve problem completely (still jumps (stutters) from time to time rarely).

So far disabling AERO Transparent Glass or going full screen is only significant positive change.

I am using NVIDIA latest drivers GeForce 332.21 Driver and Windows 7 x64 Ultimate.


回答1:


Part of the solution was a simple precision data type problem. Exchange the speed calculation by a constant, and you'll see a extremely smooth movement. Analysing the calculation showed that you're storing the result from QueryPerformanceCounter() inside a float. QueryPerformanceCounter() returns a number which looks like this on my computer: 724032629776. This number requires at least 5 bytes to be stored. How ever a float uses 4 bytes (and only 24 bits for actual number) to store the value. So precision is lost when you convert the result of QueryPerformanceCounter() to float. And sometimes this leads to a deltaTime of zero causing stuttering.

This explains partly why some users do not experience this problem. It all depends on if the result of QueryPerformanceCounter() does fit into a float.

The solution for this part of the problem is: use double (or as Ben Voigt suggested: store the initial performance counter, and subtract this from new values before converting to float. This would give you at least more head room, but might eventually hit the float resolution limit again, when the application runs for a long time (depends on the growth speed of the performance counter).)

After fixing this, the stuttering was much less but did not disappear completely. Analyzing the runtime behaviour showed that a frame is skipped now and then. The application GPU command buffer is flushed by Present but the present command remains in the application context queue until the next vsync (even though Present was invoked long before vsync (14ms)). Further analysis showed that a back ground process (f.lux) told the system to set the gamma ramp once in a while. This command required the complete GPU queue to run dry before it was executed. Probably to avoid side effects. This GPU flush was started just before the 'present' command was moved to the GPU queue. The system blocked the video scheduling until the GPU ran dry. This took until the next vsync. So the present packet was not moved to GPU queue until the next frame. The visible effect of this: stutter.

It's unlikely that you're running f.lux on your computer too. But you're probably experiencing a similar background intervention. You'll need to look for the source of the problem on your system yourself. I've written a blog post about how to diagnose frame skips: Diagnose frame skips and stutter in DirectX applications. You'll also find the whole story of diagnosing f.lux as the culprit there.

But even if you find the source of your frame skip, I doubt that you'll achieve stable 60fps while dwm window composition is enabled. The reason is, you're not drawing to the screen directly. But instead you draw to a shared surface of dwm. Since it's a shared resource it can be locked by others for an arbitrary amount of time making it impossible for you to keep the frame rate stable for your application. If you really need a stable frame rate, go full screen, or disable window composition (on Windows 7. Windows 8 does not allow disabling window composition):

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



回答2:


I took a look at your source code and noticed that you only process one window message every frame. For me this caused stuttering in the past.

I would recommend to loop on PeekMessage until it returns zero to indicate that the message queue is exhausted. After that render a frame.

So change:

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

to

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

Edit:

I compiled and ran you code (with another texture) and it displayed the movement smoothly for me. I don't have aero though (Windows 8).

One thing I noticed: You set D3DCREATE_SOFTWARE_VERTEXPROCESSING. Have you tried to set this to D3DCREATE_HARDWARE_VERTEXPROCESSING?



来源:https://stackoverflow.com/questions/21356302/linear-movement-stutter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!