5
\$\begingroup\$

enter image description here

For some time now i have no been able to pinpoint the nature of the rendering stutter shown above(by not clearing the screen, not using SwapBuffers and drawing only to the front buffer). See the big spaces between some balls?


UPDATE 2

By putting the FixedUpdate on another thread(which seems like a good idea on it's own) and using Sleep(1) the loop no longer have the random stutters(which means it not longer stutters when using vsync for the loop timing). Now only the batch of stutters that come on a timing are present. They come in roughly 9 second periods and last for something between 2 and 3 seconds.

So the last culprit must be the sleep timing portion of the code. Right now it looks like this:

    if(!isVSyncEnabled)
    {
        QueryPerformanceCounter(&sleepStart);

        sleepAccumulator += (updateStep - ((sleepStart.QuadPart - loopNewTime.QuadPart) * 1000.0f
                           / counterFrequency.QuadPart));

        while(sleepAccumulator > 0)
        {
            QueryPerformanceCounter(&sleepStart);

            Sleep(1);

            QueryPerformanceCounter(&sleepEnd);

            sleepAccumulator -= ((sleepEnd.QuadPart - sleepStart.QuadPart) * 1000.0f
                                            / counterFrequency.QuadPart);
        }
    }

I tried removing the Sleep, but then instead of 60 the game would lock on 22 fps. Due to the fact that startTime - endTime will give 0 as result since there is no padding in between those. What else can i use as padding for a busy loop?


UPDATE

After some more tests including free movement with a joystick with varying speeds. I managed to gather a more appropriate description of the problem:

SOLVED > This is a monitor thing - It seems like i can see the "ghost" of the last frame in the current frame. This becomes more prominent the faster the object moves. Also it does not get captured in screenshots. So this makes me suspect that the problem must be related to the way i'm using (or not using)double buffers/swapping/something of the sort. Maybe even a monitor problem?

Some big stutters still happen, but i believe that part is related to the game loop.




Here is the game loop:

u32 loopOldTime = timeGetTime() * 1000;
u32 loopNewTime = loopOldTime;

u32 loopDuration;

// fixedUpdateFrequency and displayDevices[currentDisplayDevice].frequency are 60 in my tests
u32 loopFixedUpdateStep = (u32)((1000.0f / fixedUpdateFrequency) * 1000);
u32 renderStep = (u32)((1000.0f / displayDevices[currentDisplayDevice].frequency) * 1000);

u32 loopAccumulator = 0;

s32 sleepAccumulator = 0;
u32 sleepStart = 0;

u32 framerateTimeThreshhold = 0;
u8  framerateCount = 0;

deltaTimeFixed = loopFixedUpdateStep  / 1000000.0f;

while(isRunning)
{
    loopOldTime = loopNewTime;
    loopNewTime = timeGetTime() * 1000;

    loopDuration = Min(loopNewTime - loopOldTime, 250000);
    deltaTime = loopDuration / 1000000.0f;

    loopAccumulator += loopDuration;

    if((acceptInputWithoutFocus && canRunWithoutFocus) || isFocused)
    {
        -- Input Stuff --
    }

    while(loopAccumulator >= loopFixedUpdateStep)
    {
        if(canRunWithoutFocus || isFocused)
        {
            -- FixedUpdate Stuff --
        }

        loopAccumulator -= loopFixedUpdateStep;
    }

    timeInterpolation = (r32)loopAccumulator / (r32)loopFixedUpdateStep;

    if(canRunWithoutFocus || isFocused)
    {
        -- Update Stuff --


        -- Render Stuff(calculating vertexes and putting those in place) --


        glClear(OPENGL_CLEAR_BITS);

        glBufferSubData(GL_ARRAY_BUFFER, 0, spriteFactory.verticesSize, spriteFactory.vertices);
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, spriteFactory.indicesSize, spriteFactory.indices);

        glDrawElements(GL_TRIANGLES, spriteFactory.spriteLimit * 6, GL_UNSIGNED_SHORT, (void*)0);

        SwapBuffers(deviceContext);


        framerateTimeThreshhold += loopDuration;
        ++framerateCount;

        if(framerateTimeThreshhold >= 1000000)
        {
            framerate = framerateCount;
            framerateCount = 0;
            framerateTimeThreshhold -= 1000000;
        }
    }

    sleepStart = timeGetTime() * 1000;

    sleepAccumulator += (s32)(renderStep - (u32)(sleepStart - loopNewTime));

    if(sleepAccumulator > 0)
    {
        Sleep((u32)(sleepAccumulator / 1000));

        sleepAccumulator -= (timeGetTime() * 1000) - sleepStart;
    }
}

Here is the movement code:

r32 speed = 500 * deltaTime;

player->sprite->position.x += speed * input->gamepads[0].axisMap[GAMEPAD_LEFT_STICK_X]->normalized;
player->sprite->position.y -= speed * input->gamepads[0].axisMap[GAMEPAD_LEFT_STICK_Y]->normalized;

player->sprite->position.x = Clamp(0, player->sprite->position.x, screenWidth - player->sprite->width);
player->sprite->position.y = Clamp(0, player->sprite->position.y, screenHeight - player->sprite->height);

Here is the code for the spinlock test that will be mentioned bellow:

while(isRunning)
{
    loopOldTime = loopNewTime;

    if((acceptInputWithoutFocus && canRunWithoutFocus) || isFocused)
    {
        -- Input Stuff --
    }

    if(canRunWithoutFocus || isFocused)
    {
        -- Update Stuff --


        -- Render Stuff(calculating vertexes and putting those in place) --


        glClear(OPENGL_CLEAR_BITS);

        glBufferSubData(GL_ARRAY_BUFFER, 0, spriteFactory.verticesSize, spriteFactory.vertices);
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, spriteFactory.indicesSize, spriteFactory.indices);

        glDrawElements(GL_TRIANGLES, spriteFactory.spriteLimit * 6, GL_UNSIGNED_SHORT, (void*)0);

        SwapBuffers(deviceContext);


        framerateTimeThreshhold += loopDuration;
        ++framerateCount;

        if(framerateTimeThreshhold >= 1000000)
        {
            framerate = framerateCount;
            framerateCount = 0;
            framerateTimeThreshhold -= 1000000;
        }
    }

    sleep = true;

    while(sleep)
    {
        loopNewTime = timeGetTime();

        if(loopNewTime - loopOldTime > 15)
        {
            sleep = false;
        }
    }
}

Some notes:

  • timeBeginPeriod(1) has been invoked beforehand to make the precision of timeGetTime and Sleep(need confirmation) 1ms.
  • I'm handling timings in milliseconds.
  • Variables types:
    • u > unsigned int || s > signed int || r > float/doable
    • number > size

I made the following tests:

  1. Put the code that is moving the blocks on Update and scaled with deltaTime.
  2. Put the code that is moving the blocks on FixedUpdate and use timeInterpolation to the vertexes.
  3. Remove the game loop structure and use a simple spinlock as seen above. The fps is of course not locked at 60(63 in the example above).
  4. Switched from timeGetTime to QueryPerformanceFrequency/Counter.
  5. Switched between glBufferSubData and glMapBufferRange.
  6. Tried explicit OpenGL synch with glFinish.
  7. Tried no timing at all.
  8. Tried VSync on and off along all the previous tests.

Here if what i managed to notice:

  • Vsync makes the way stutter less common. But it still happens in all cases except the one that uses no timing at all.
  • The stutters are more frequent when running movement code on FixedUpdate with interpolation.
  • The stutters happen in periods. After some time of them happening constantly, there will be some that they will not happen at all.
  • The stutters seem to coincide with peaks in timeInterpolation. Some of the time, when interpolation reaches 0.9 >, a stutter happens. And when a period of frequent stutters happen, timeInporlation also peaks frequently.
  • While using the spinlock, the stutters happens at a fixed frequency.
  • While recording it i noticed that the stutters happen less in the recording(after, not during) but still do happen.

And this is what i have. At every step i find something new that bounces me between the problem being in my OpenGL code or in my loop code, but never conclusive or clear enough for me to figure out what really is the problem. Frustrating, to say the least.

Thanks in advance for any tips or ways of finally finding out the problem.

\$\endgroup\$
7
  • \$\begingroup\$ How do you use "timeInterpolation"? Do you interpolate between the last two states you computed in the fixed-update loop? \$\endgroup\$
    – Heckel
    Sep 5, 2018 at 2:41
  • \$\begingroup\$ current * interpolation + last * (1 - interpolation) \$\endgroup\$
    – user69809
    Sep 5, 2018 at 13:42
  • \$\begingroup\$ I've seen in your post that you have uploaded some image, why is not in the question? This would seem very useful. Also, think about future visitors: the link to your project will most likely rot, so you should remove it and not rely on it. \$\endgroup\$
    – Vaillancourt
    Sep 6, 2018 at 14:19
  • \$\begingroup\$ Haven't watched the video but why doesn't SwapBuffers already wait for the next frame? \$\endgroup\$
    – user253751
    Sep 7, 2018 at 2:27
  • 1
    \$\begingroup\$ @JoséCarlosCandido we may benefit from a reformulation of your solution as a new answer to your question. \$\endgroup\$
    – Krupip
    Jan 2, 2019 at 17:44

1 Answer 1

1
\$\begingroup\$

Calls to timeBeginPeriod/timeEndPeriod won’t result in a Sleep() of 1ms. You will get anything but 1ms and possible values as high as 10-15ms. Also, no need to adjust scheduler timeslice for timeGetTime either, the default will give you accurate enough timing.

Your monitor is fine, the ghosting you are seeing is known as temporal aliasing. If you clear the buffer between moves and correctly implement interpolation, this effect can be mitigated.

\$\endgroup\$

You must log in to answer this question.