7

In a 2D OpenGL engine I implemented I have a fixed timestep as described in the famous fix your timestep article, along with blending.

I have a test object that moves vertically (y axis). There is stuttering in the movement (preprogrammed movement, not from user input). This means the object does not move smoothly across the screen.

Please see the uncompressed video I am linking: LINK

The game framerate stays at 60fps (Vsync turned on from Nvidia driver)

The game logic updates at a fixed 20 updates/ticks per second, set by me. This is normal. The object moves 50 pixels per update.

However the movement on the screen is severely stuttering.

EDIT: I noticed by stepping in the recorded video above frame by frame that the stuttering is caused by a frame being shown twice.

EDIT2: Setting the application priority to Realtime in the task manager completely eliminates the stutter! However this obviously isn't a solution.

Below is the object y movement delta at different times, with VSync turned off First column is the elapsed time since last frame, in microseconds (ex 4403 ) Second column is movement on the y axis of an object since last frame. Effectively, the object moves 1000 pixels per second, and the log below confirms it.

time since last frame: 4403    ypos delta since last frame: 4.403015
time since last frame: 3807    ypos delta since last frame: 3.806976
time since last frame: 3716    ypos delta since last frame: 3.716003
time since last frame: 3859    ypos delta since last frame: 3.859009
time since last frame: 4398    ypos delta since last frame: 4.398010
time since last frame: 8961    ypos delta since last frame: 8.960999
time since last frame: 7871    ypos delta since last frame: 7.871002
time since last frame: 3985    ypos delta since last frame: 3.984985
time since last frame: 3684    ypos delta since last frame: 3.684021

Now with VSync turned on

time since last frame: 17629     ypos delta since last frame: 17.628906
time since last frame: 15688     ypos delta since last frame: 15.687988
time since last frame: 16641     ypos delta since last frame: 16.641113
time since last frame: 16657     ypos delta since last frame: 16.656738
time since last frame: 16715     ypos delta since last frame: 16.715332
time since last frame: 16663     ypos delta since last frame: 16.663086
time since last frame: 16666     ypos delta since last frame: 16.665771
time since last frame: 16704     ypos delta since last frame: 16.704102
time since last frame: 16626     ypos delta since last frame: 16.625732

I would say they look ok.

This has been driving me bonkers for days, what am I missing?

Below is my Frame function which is called in a loop:

void Frame()
{
static sf::Time t;
static const double ticksPerSecond = 20;
static uint64_t stepSizeMicro = 1000000 / ticksPerSecond; // microseconds
static sf::Time accumulator = sf::seconds(0);

gElapsedTotal = gClock.getElapsedTime();

sf::Time elapsedSinceLastFrame = gElapsedTotal - gLastFrameTime;
gLastFrameTime = gElapsedTotal;


if (elapsedSinceLastFrame.asMicroseconds() > 250000 )
    elapsedSinceLastFrame = sf::microseconds(250000);

accumulator += elapsedSinceLastFrame;

while (accumulator.asMicroseconds() >= stepSizeMicro)
{
    Update(stepSizeMicro / 1000000.f);
    gGameTime += sf::microseconds(stepSizeMicro);
    accumulator -= sf::microseconds(stepSizeMicro);
}
uint64_t blendMicro = accumulator.asMicroseconds() / stepSizeMicro;
float blend = accumulator.asMicroseconds() / (float) stepSizeMicro;
if (rand() % 200 == 0) Trace("blend: %f", blend);
CWorld::GetInstance()->Draw(blend);
}

More info as requested in the comments:

  • stuttering occurs both while in fullscreen 1920x1080 and in window mode 1600x900

  • the setup is a simple SFML project. I'm not aware if it uses VBO/VAO internally when rendering textured rectangles

  • not doing anything else on my computer. Keep in mind this issue occurs on other computers as well, it's not just my rig

  • am running on primary display. The display doesn't really make a difference. The issue occurs both in fullscreen and window mode.

Ed Rowlett-Barbu
  • 1,611
  • 10
  • 27
  • you are missing to analyze what the source of the performance problem is, this could be a lot of things - a background task, resource loading, too complex code, ... – CodeSmile Oct 06 '14 at 16:37
  • There is no performance issue. The game runs 200+ fps. The stuttering is the issue. Stuttering means the object is not moving smoothly across the screen. You can see the position at different times on the screen in the log I posted. The positions are correct, and linear with the time. Let me know if the question is not clear enough. I have no idea why people are downvoting it! – Ed Rowlett-Barbu Oct 06 '14 at 16:38
  • With VSync on, it stays at 60fps. The stuttering still occurs. – Ed Rowlett-Barbu Oct 06 '14 at 16:58
  • I don't see how you can be seeing stuttering in the 60fps case as your figures show you pretty identical delta. If you ignore the timestep logic and just simply add 16.6 to the ypos each frame do you still see stuttering? –  Oct 06 '14 at 17:19
  • I do, yes. That's the weird part. – Ed Rowlett-Barbu Oct 06 '14 at 17:29
  • I have posted my frame function for further info, in case it helps. – Ed Rowlett-Barbu Oct 06 '14 at 17:43
  • Is there rounding between your "ideal" ypos delta printed above, and the actual position used in the rendering code? – Ben Voigt Oct 06 '14 at 17:46
  • That is the one used directly in the rendering. It is obtained by blending the current and previous position states using the blend factor. – Ed Rowlett-Barbu Oct 06 '14 at 17:48
  • It coincides perfectly with the time elapsed between frames, as proof I would say that the blending between states is implemented correctly. This is killing me... – Ed Rowlett-Barbu Oct 06 '14 at 17:52
  • Are you running this test on an emulator or vm or something else which could be skewing the results? –  Oct 06 '14 at 22:26
  • I have attached a screencapture of the issue. Please observe the microstuttering. This is at my home machine. On other machines I observed it even more severe. – Ed Rowlett-Barbu Oct 07 '14 at 06:43
  • You are double-buffering aren't you? – Nathan Wride Oct 07 '14 at 06:56
  • Yup. From other research I gather it might be the context switching issue in windows. Other threads/processes are scheduled and my own process doesn't switch the buffers in time, so the same frame gets displayed twice. But not sure. My issue is I haven't observed any noticeable stutter in other games so it's not like I can ignore this in mine. How do the rest of the people handle this? – Ed Rowlett-Barbu Oct 07 '14 at 06:59
  • In theory, a frame lasts 4 milliseconds, well below the 16ms forced by VSync. So the loop should switch the buffer in time without causing a frame to be dropped (rendered twice). Yet the dropping still happens. – Ed Rowlett-Barbu Oct 08 '14 at 12:35
  • We need more info: What resolution, double-buffer and multi-sampling settings are you using? Because if you have stuttering with vsynch on, it means a) that your timestep is more or less stable around 59.x fps, and b) that somehow the video card is having trouble swapping the buffers. This could be because the internal resolution is more than the flip can handle. Or it may be a driver issue for that particular setup. Are you using the card for PhysX or other parallel activity, such as stream out shaders? Are you running on primary display? Are you using VBO's or VAO's?.. – StarShine Oct 10 '14 at 08:40
  • @StarShine I have appended answers to your questions – Ed Rowlett-Barbu Oct 10 '14 at 09:57
  • I'm not familiar with SFML, but looking at the avi it might by related to the number of drawcalls you are requesting, or maybe SFML clears and re-requests buffers too often. From the SFML website, you can "group" draw calls by grouping sprite objects, for example by image or by size. It supposedly offers the opportunity to render instances, or at-least use a VBO/VAO to render the sprites faster and cut down on the state-changes with the opengl driver. If you make your scene simpler (less tiles, or just one type of tile), does the stutter still occur? – StarShine Oct 10 '14 at 10:27
  • Why would the number of draw calls affect the stutter if the frame duration is well below 16ms? It's 4ms with VSync off. I am aware of grouping of sprites, will try it. But it shouldn't affect it. – Ed Rowlett-Barbu Oct 10 '14 at 14:16
  • It looks to me like you will render twice when `accumulator < stepSizeMicro`. This will happen when your `elapsedSinceLastFrame` is small. As a test, just try looping until `accumulator` is at least `stepSizeMicro` – megadan Oct 12 '14 at 18:32
  • @megadan Rendering twice is ok. It doesn't render the same thing even without an Update. That's what the blend parameter is for. See the link I posted. – Ed Rowlett-Barbu Oct 12 '14 at 18:52

3 Answers3

2

I have profiled my own code. The issue was there was an area of my code that occasionally had performance spikes due to cache misses. This caused my loop to take longer than 16.6666 milliseconds, the max time it should take to display smoothly at 60Hz. This was only one frame, once in a while. That frame caused the stuttering. The code logic itself was correct, this proved to be a performance issue.

For future reference in hopes that this will help other people, how I debugged this was I put an

if ( timeSinceLastFrame > 16000 ) // microseconds
{
    Trace("Slow frame detected");
    DisplayProfilingInformation();
}

in my frame code. When the if is triggered, it displays profiling stats for the functions in the last frame, to see which function took the longest in the previous frame. I was thus able to pinpoint the performance bug to a structure that was not suitable for its usage. A big, nasty map of maps that generated a lot of cache misses and occasionally spiked in performance.

I hope this helps future unfortunate souls.

Ed Rowlett-Barbu
  • 1,611
  • 10
  • 27
  • I haven't, I'm looking for a replacement for a map of maps. For my needs that would be a sparse 3d matrix container of sorts that can still access elements by index, is contiguous in memory and doesn't allocate space for empty elements/positions. But I can't find one. – Ed Rowlett-Barbu Oct 14 '14 at 16:01
  • An overview at http://en.wikipedia.org/wiki/Volume_rendering, octree or try looking a JUDY structure. – Surt Oct 14 '14 at 22:24
0

It seems like you're not synchronizing your 60Hz frame loop with the GPU's 60Hz VSync. Yes, you have enabled Vsync in Nvidia but that only causes Nvidia to use a back-buffer which is swapped on the Vsync.

You need to set the swap interval to 1 and perform a glFinish() to wait for the Vsync.

anorm
  • 2,255
  • 1
  • 19
  • 38
-1

A tricky one, but from the above it seems to me this is not a 'frame rate' problem, but rather somewhere in your 'animate' code. Another observation is the line "Update(stepSizeMicro / 1000000.f);". the divide by 1000000.f could mean you are losing resolution due to the limitations of floating point numbers bit resolution, so rounding could be your killer?

Gavin Simpson
  • 2,766
  • 3
  • 30
  • 39
  • I highly doubt this. I have displayed the positions with which the object is rendered in my question. As you can see, the positions are very smooth with the time. – Ed Rowlett-Barbu Oct 11 '14 at 09:51
  • Also, as I said above, the issue does not occur when setting the process priority to realtime. – Ed Rowlett-Barbu Oct 11 '14 at 09:52
  • Yeah I saw that. How about trying this : create a global variable called "animation_complete". at the end of every draw() function dump it to a log file (with counters for frame number and animation step number?) . At the beginning of the 'update' function set it to false, and at the end set it to true. Perhaps that will assist in finding out if at least the correct animation step is complete or not. Or something like that.... cos I am sure it's case of your animation(update) not completing before the draw function get's called, i.e. a sync problem. – Gavin Simpson Oct 11 '14 at 10:10
  • further more... I would guess you animation progress is not actually time based, but frame based. (within the update function). That would mean if time does pass two steps the animation will still only update by 1 step, hence the repeated frame. I hope that makes sense. – Gavin Simpson Oct 11 '14 at 10:12
  • the lines "if (elapsedSinceLastFrame.asMicroseconds() > 250000 ) elapsedSinceLastFrame = sf::microseconds(250000);" don;t make sense to me. You are saying if it takes too long just ignore it taking too long? – Gavin Simpson Oct 11 '14 at 10:17
  • That is a frame skip implementation to avoid the well known spiral of death, yes There can't be a sync problem because update and animate are on the same thread It doesn't matter what update does, you can ignore that function entirely. The position I wrote are from animation, that's where the object is displayed. There will be indeed more animation steps than updates, of course. This is normal, the positions between updates are interpolated. Please read the link I posted, it seems to me you are not familiar with fixed timesteps. Fixed timesteps are excellent for deterministic simulations. – Ed Rowlett-Barbu Oct 11 '14 at 10:22
  • Hmm, I am familier, and did read the link you posted. Just trying to eliminate the obvious. I understand your frustration. So another silly question :) in "float blend = accumulator.asMicroseconds() / (float) stepSizeMicro;" should you not also cast "accumulator.asMicroseconds()" as a float before doing the divide? – Gavin Simpson Oct 11 '14 at 10:31
  • stepSizeMicro is already cast to float. Expressions in C++ are cast to the bigger type of the two operands :P – Ed Rowlett-Barbu Oct 11 '14 at 11:17
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/62877/discussion-between-gavin-simpson-and-zadirion). – Gavin Simpson Oct 11 '14 at 12:04