VSYNC + SDL_Delay

Without VSYNC, I have FPS = 150. I set SDL_SetHint (SDL_HINT_RENDER_VSYNC, “1”) - FPS drops to 75, which is understandable.
But if after SDL_RenderPresent I insert SDL_Delay (13) the FPS is 73 and it should drop to 37. SDL_Delay (<= 13) has little effect on FPS and SDL_Delay (> 13) works like there would be no Vsync.

Can someone explain this to me? How it’s working ?

It depends on which platform you’re on but generally speaking, SDL_Delay does not have enough precision for frame pacing. For example, it’s just a Sleep on Win32 and it might be 10ms or so accuracy.

In addition, even if VSYNC disabled, FPS = 150 does not immediately mean your processing time is 1/150 seconds because of overheads of renderer.

You can check actual wallclock time with SDL_GetTicks (64) and it’d be better to check actual behaviour of your platform.

I weakly guess your processing time is much more shorter than 13ms that you might have expected since SDL_Sleep(13) + your_processing_time still gives around 75fps – If you’re on modern(>2008) Linux, SDL_Sleep will give you about 1ms precision and you still have 0.33ms to draw frames in 75fps. In case some % of your frame missed 0.33ms deadline you’ll see fewer FPS as an average.

Short version: your graphics driver is really smart.

Think of it like this:

Your CPU and your GPU are two separate machines which talk to each other over a network inside your computer. The CPU sends rendering commands to the GPU (via the graphics driver), and the GPU interprets those commands at its own pace, but your CPU doesn’t slow down to wait for the GPU to finish; it just keeps going, tacking more and more commands onto the end of the list that the GPU is working its way down, and the graphics driver can occasionally delay the CPU to try to maintain some sort of stable frame rate as determined by VSync and other driver settings.

Manually delaying the CPU with SDL_Delay() doesn’t stop your GPU from continuing to work on its accumulated rendering commands.

Graphics drivers have gotten pretty smart about this sort of stuff, and will do their best to schedule GPU work to minimise the amount of time that your CPU is waiting for your GPU (as sometimes happens with vsync) and the amount of time that your GPU is waiting for the CPU (as can happen if the GPU finished all of its work and the CPU isn’t providing more work yet); the driver will try to line up and rearrange those periods of idleness and blockage to minimise it, to maintain as high a frame rate as possible.

In your specific case, the driver likely will choose not to vsync-block the CPU during the call to RenderPresent, but will instead do it at some other point later on during the frame so that your SDL_Delay() will overlap with the normal vsync delay. As a concrete example… if the driver was going to vsync delay you by 20ms and you do a SDL_Delay that delays by 10ms, the driver will quickly figure out “well gosh, I only actually have to vsync delay you by 10ms now because you’ve done the other 10ms on your own.” That sleep effectively becomes “free”.

(obviously this will vary based on almost everything; your CPU speed, GPU speed, how bottlenecked your GPU is, monitor refresh rate, and all sorts of other stuff. But modern graphic drivers are kind of absurdly smart at this kind of scheduling and will do a lot to try to wring absolutely as much performance out of a program as they possibly can)

1 Like

Do you know how it works or are you just making it up?
This would mean that the developer has no control over the speed of the game.

As others have explained, it’s much more complicated ‘under the hood’ than you imagine, and in any case the behavior will differ depending on the platform and on GPU settings etc. For example double or even triple-buffering may be enabled.

So just enable VSYNC (you don’t need to use a hint, you can specify SDL_RENDERER_PRESENTVSYNC when you create the renderer) and update your rendered graphics by one frame period for each call to SDL_RenderPresent().

So long as there are no ‘dropped frames’ (because your processing time is too long) everything will ‘just work’.

But then it will work depending on the refresh, and I always want 60 FPS. I will probably have to combine without vsync …

You can’t always have 60 fps. Suppose your display device was running natively at 75 fps or 100 fps, imagine the temporal aliasing that would result. For smooth animation you must render at whatever the display’s refresh rate is (or at least a sub-multiple of it).

This either cuts out vsync or skips some frames. There are problems with this omission …

I spent more than two hours of my spare time writing and re-writing a five-paragraph technical explanation to explain how this complicated system works in the simplest and clearest way I could figure out because I was trying to be helpful.

No, I was not “making it up”, as you say.

I guess my question is this: why do you always want 60 FPS, specifically?

The normal thing for games to do is to run their rendering at whatever the monitor’s native refresh rate is, or as close to that as possible if they can’t render as fast as the monitor (which is getting much more common these days with gaming-focused monitors often going up to 300hz or higher) This is what the graphics driver is designed to help you do, and the driver is going to be much less helpful if you’re trying to do anything other than that.

If you can explain why you want to run specifically and exactly at 60fps on everything and not at the monitor’s native refresh rate if it’s something other than 60hz, then maybe we can give better advice about how to achieve what you’re actually trying to do.

2 Likes

It’s probably obvious, all speed and everything in the game is adjusted to 60 Hz …
How can a 300Hz game run if the computer is not that fast?

Don’t hard-code your game to be based on 60hz. Use time deltas, based on the actual time between frames.

1 Like

Ah, there we go; that’s the core misunderstanding going on. :slight_smile:

Modern games generally get written using one of two broad styles to cope with exactly that problem of displays running at different rates. (caveat: all of the code below is being written directly into this post and has never been compiled or tested; I’ve probably made some typos in it! But it’s just supposed to get the broad ideas across)

Type 1: “Variable Timestep”

In this approach, a game measures the time between frames using a high precision clock, and then you have your game simulation use that time to determine how far ahead to run the simulation. With SDL code, your game loop would look something like this:

while ( !quitting )
{
    // note that "ticks" is an unknown duration of time which will vary across different hardware.  All we
    // really know about it for certain is that it starts at 0 when the game is launched and goes up from
    // there over time.  SDL_GetPerformanceFrequency() tells us how to convert it into seconds.
    uint64_t ticks_now = SDL_GetPerformanceCount();

    // how long has it been since the last frame was rendered?  This is how much time our simulation needs to run!
    float timestep = (float)(ticks_now - ticks_last_frame) / SDL_GetPerformanceFrequency(); 
    ticks_last_frame = ticks_now;

    // this object moves according to its velocity, which is expressed as a distance *per second* 
    // that it travels, regardless of frame rate.  Acceleration would affect velocity in the same way.
    object.position += object.velocity * timestep;

    Game_Render();
}

This approach has the chief benefit of being easy to implement and relatively straightforward. The only real downside to it is that intensive physics simulations can sometimes go a little quirky if you don’t give them the same size timestep every frame. (it’s quite common for physics engines to run their own little “fixed timestep” system inside a mostly-variable timestep game to deal with that problem)

Type 2: “Fixed Timestep”

Under this approach, you declare that your game simulation is only going to run at a particular frame rate, and you instead run your simulation a variable number of times per rendered frame. It looks something like this:

while( !quitting ) {
    uint64_t ticks_now = SDL_GetPerformanceCount();
    const uint64_t ticks_per_frame = SDL_GetPerformanceFrequency() / 60; // lets run our simulation at a fixed 60fps
    const float c_fixedTimestep = 1.0f / 60.0f;

    if ( ticks_now < ticks_last_frame + ticks_per_frame ) {
        // consider a small SDL_Delay() here to let the CPU sleep a bit?  We seem to be running fast.
        // regardless, no need to update anything or render a frame right now;  let's go around the loop
        // again until it's time to do an update!
    } else {
        // enough time has passed for us to update the simulation state and render a frame.
        while ( ticks_now > ticks_last_frame + ticks_per_frame ) {
            // here we always update our object's position according to c_fixedTimestep, 
            // and if we're rendering slowly we'll update our simulation as often as we need to 
            // in order to catch up.
            object.position += object.velocity * c_fixedTimestep;
            ticks_last_frame += ticks_per_frame;
        }
        Game_Render();
    }
}

The chief benefit of using a fixed timestep is that it makes physics systems really happy and it makes everything determinate and repeatable; if you ignore user inputs then your simulations will run the same way every time, because the simulation gets the same inputs every time; there’s no messy “how long has it been since the last frame” in the middle of your simulation here, the way there is when using a variable timestep.

The downside is that you can wind up in what people call a “death spiral” if your simulation takes more time to run than the time it simulated. That is, if it takes you 0.033 seconds to simulate 0.016 of time, then you’ll never catch up and your game will eventually appear to freeze. (lots of games institute a maximum number of times around that simulation loop they’ll go per frame, so the game just appears to slow down instead of appearing to freeze in that situation)

The other major downside is kind of what we were talking about earlier; if you’re only displaying 60 new frames per second on a screen that updates its image at 75 frames per second, then those 60 frames won’t all appear for the same length of time on the monitor; it’ll look strange and stuttery, as if you were dropping frames. (which you kind of are, albeit intentionally, if you think about it)

The usual approach to address this is to decouple your rendering from the simulation entirely; set things up so your simulation runs at one rate, but then your rendering can run at a different rate, rendering your game stuff in between the positions specified by the simulation so that your rendering remains smooth when updated at 75fps or 300fps even when the simulation is only running at 60hz or even lower. All of that is a big and complicated topic, and folks usually point to resources like Fix Your Timestep! | Gaffer On Games for the gory details.

Conclusion

Hopefully that all makes sense! For the vast majority of folks the recommendation is to go with a variable timestep. It’s simple and easy and will give you good results the majority of the time for the majority of games. In twenty years in the mainstream industry, I have only once worked on a game which used fixed timesteps for everything (and it was a pain to work with, but I understand why that specific project needed them).

1 Like

I would want to add that it should measure the average frame period over several frames (typically a ‘running average’) because the measured period between one SDL_RenderPresent() returning and the next can be quite variable and jittery. This is for the very reason that you explained earlier, that the graphics drivers and GPU try to be “smart” and may buffer frames before they are actually displayed.

Except for a few genuinely ‘variable frame rate’ monitors, you can normally assume that the actual times at which the frames are displayed will be absolutely regular (i.e. at precise 1/60 second intervals or whatever) so you will introduce motion artefacts into your animations if you don’t average out (smooth) the variations in the measured period.

Averaging over a number of frames also helps with the (very real) problem you identified of physics engines disliking highly variable frame periods.

1 Like

Live timestep measurement causes additional problems and averaging does not solve them, and the latter method is overcomplicated. I turned off vsync and added at the end of the loop:
dc = SDLTime-dc;
If (dc <17)
SDL_Delay (16-dc)
EndIf
dc = SDLTime;

And please do not use // for multi-line comments, because the automatic translator has problems with that.

Be aware that the result will be severe temporal aliasing; it will be like watching NTSC video on a PAL TV, by dropping frames rather than standards-conversion. Terrible!

Looks good.Looks good.
Looks good.

How do you know it’s how it works?