Linear movement stutter
Asked Answered
A

2

8

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.

Anthrax answered 25/1, 2014 at 21:11 Comment(3)
Try comparing different GPU driver versions. If it also happens with opengl it wont be some dx quirk.Cockboat
I have heard of this behaviour in other sources too, with OpenGL.Dusen
Seems like exact same problem with OpenGL though - #18182516Anthrax
T
8

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
}
Tisbe answered 26/1, 2014 at 19:5 Comment(8)
No, the solution is to subtract the two results from QueryPerformanceCounter using integer arithmetic first, and cast the difference.Buddhology
I'm running Windows 7 with Aero. What happens when you exchange your position calculation with this line: position.x += 1;Tisbe
Doesn't solve stuttering with aero enabled. But totally worth fixing, thanks very much. I hope I implemented it correct. Added code to first post. Also I checked deltaTime never is zero (except beginning).Anthrax
@Eduard Wirch I used position.x += 4; becuase its hard to notice stuttering on slow speed and it seems to work a little bit better (might be subjective), but even though I still can notice few spikes from time to time in windowed mode and when window loses focus it is stuttering awfully as usual. So problem is not solved.Anthrax
@EduardWirch I upvoted it, it was quite a good answer, though I would recommend using long double if at all possible. That's what extended precision was made for, after all.Unblessed
I know this question is old, but why does full screen help this problem?Halfbaked
@shell: If you run in windowed mode, then your application shares the GPU with other processes. You are not in full controll of the vsync cycle. The main problem of the OP was the loss in precision in calculation. Stutter in windowed mode have to be analysed per case to see which applications interfere with vsync cycle. If you face this problem, read my linked blog post to find out how to use GPUView to analyze the problem.Tisbe
Thanks Eduard for the explanation.Halfbaked
S
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?

Selfcontradiction answered 26/1, 2014 at 17:20 Comment(4)
It doesnt help much. Stuttering even occured when message queue loop and render loop were in two seperate thread. Although it works much better (havent seen noticable stuttering) in fullscreen and borderless windowed fullscreen.Anthrax
D3DCREATE_HARDWARE_VERTEXPROCESSING doesnt make any difference. Unfortunatelly many users cant reproduce it, but other have this problem as I`ve seen similar questions and forum topics with exact same issue without solution.Anthrax
With regards to the if/while dilemma, your approach doesn't make a difference if the code is set up like this: while (gamerunning){if PeekMessage(...){/*handle message*/}else{/*update game*/}} - which it is. In fact, the if approach is preferred because it checks if the game should quit after each new message is received, and the game will never attempt to update when the window is closing. With regards to hardware/vertex processing, I suggest having an if-else tree like "try creating hardware device, if failed try mixed, if failed try software, if failed then your computer sucks".Iambus
@Iambus Thanks for the feedback. His code didn't use if/else, just if and then render regardless. So I suggested a minimal solution to nail down the underlying problem. Same for the hardware/software vertexprocessing.Selfcontradiction

© 2022 - 2024 — McMap. All rights reserved.