Vsync: the correct way to refresh the screen using SDL2 | Blog | Superflous Returnz

Vsync: the correct way to refresh the screen using SDL2 | Blog | Superflous Returnz Vsync: the correct way to refresh the screen using SDL2 | Blog | Superflous Returnz
  • Steam
  • Nintendo Switch
  • Itch.io
  • Google Play
Superfluous and his assistant Sophie

Blog

Vsync: the correct way to refresh the screen using SDL2

2023-03-14

If you read a tutorial on how to handle display with the SDL library, chances are it sums it up as a loop: perform the computations for the “logic” of the game (moving the characters, etc.), invoke the display functions, refresh the screen, then put the program to “sleep” for a while.

Except that this method is obsolete and we can do much better ... let's see that.

Historically, the SDL

The library Simple Direct Media Layer (or SDL) has been around since 1998, and because it is easy to used and has a permissive license (zlib), it is still widely used and there exists many tutorials about it.

The problem is that a lot of these tutorials relate to the version 1.2 of the library, an old version that has become deprecated since the release of 2.0, in August 2013. SDL2 broke a lot of things in the API to be able to make substantial improvements, the main one being the handling of 2D hardware graphics acceleration.

In order to keep some backwards compatibility, a lot of functions of SDL1 have been kept. But there are a number of mechanics that you shouldn't use anymore.

The general idea

As I said in the intro, when you're developing a game in SDL, your program usually boils down to one big loop:

  • managing the “logic” of the game (collision tests, level loading, image displacements, etc.);
  • calling SDL display functions: with SDL1, calling SDL_BlitSurface() on SDL_Surface; in SDL2, we will rather use SDL_RenderCopy() on SDL_Texture (see the migration guide from 1.2 to 2.0);
  • “updating the screen”: telling the SDL that we have finished drawing what we wanted, and that it can now display the contents of the screen;
  • putting the game to “sleep” for a while, for several reasons: first, because it is useless to refresh the screen hundreds of times per second (monitors are generally calibrated at 60 Hz, so 60 refreshes per second are sufficient); second, because it saves processor time (and thus battery on mobile device).

About this last point, many tutorials still indicate to use SDL_Delay(), by computing how long the program must “sleep” so that the next screen refresh falls at the right time with respect the desired frequency (60 Hz For example).

But this method, while working rather well at first glance, creates a substantial issue ...

The problem with SDL Delay

The big problem is that the 60 Hz calculated at the CPU level is not synchronized with the 60 Hz of the GPU. What happens then? When I ask the GPU to display my image, it can be in the middle of a render and therefore only update part of the screen ... Which gives a rather ugly effect on the rendering of the game .

It's called tearing: it looks like the whole image isn't refreshing at the same time and some sections are “lagging” behind others.

In practice, it looks like this:

The solution: vsync

Of course, since SDL2 was designed around better support for hardware acceleration, it provides a nice way to solve this problem: vertical synchronization, or vsync.

In practice, this simplifies things. This is what your loop will look like:

  • managing the game logic;
  • calling the SDL display functions;
  • updating the screen;

... that's all! Yes, indeed, by using vsync, there is no longer any need to manage this “sleep” time: the SDL directly takes care of it, by sleeping exactly the time it takes to ... be synchronized to the refresh rate of the screen.

Setting this up is very simple, just pass an additional flag (SDL_RENDERER_PRESENTVSYNC) to the renderer :


SDL_Windows* window = /* create window (...) */;
SDL_Renderer* renderer = SDL_CreateRenderer (window, -1, 
                                             SDL_RENDERER_ACCELERATED
                                             | SDL_RENDERER_PRESENTVSYNC);

This method has several advantages:

  • well, first, of course, it solves the graphical problem shown above, which is the main point;

  • it directly adjusts the refresh rate of the game to the screen, so you don't have to worry about the frequency anymore: if your screen refreshes at 50Hz, then your game will run at 50Hz, etc.;

  • if your game loop is ever too slow and you “miss” a refresh, no big deal, the SDL will just wait for the next refresh for you;
  • SDL_Delay() was doing a system call internally, which is notoriously slow: that's always one less thing to do.

A little question mark

Some renderers may potentially not support vsync ... and there does not seem to exist a way to tell at runtime: the problem is that in that case, there won't be any pause and the program will run consuming 100 % of the CPU, which is not good.

I'm not sure what to make of this: on all the platforms I tested, vsync was supported, which seems encouraging. For my own tests, I sometimes run the game “GUI-less”, just to test the logic part, via this little hack:


#ifdef SOSAGE_GUILESS
  putenv("SDL_VIDEODRIVER=dummy");
#endif

In this case, of course, I don't call any display or rendering function, and I therefore replace the rendering function (that is blocking at the GPU level) by the usual SDL_Delay() which I'd got rid of for the main loop:


#ifndef SOSAGE_GUILESS
  SDL_RenderPresent (renderer);
#else
  SDL_Delay(17); // If no GUI, simulate GPU delay
#endif

Ideally, I'd like to be able to do this by detecting at runtime if vsync is available, but that doesn't seem possible at the moment. There remains the possibility of putting a short SDL_Delay() just to make sure that there is always a pause, but that seems less clean to me.

Maybe in SDL3? :)

i