Blog
Vsync: the correct way to refresh the screen using SDL2
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()
onSDL_Surface
; in SDL2, we will rather useSDL_RenderCopy()
onSDL_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? :)