A Crash Course in SDL

An adaptation of a chapter from the author's upcoming book entitled Programming Linux Games.
Setting up the Display

Before we can begin blitting surfaces to the screen, we need to initialize the SDL library and switch the display into an appropriate mode. Take a look at Listing 1, the equivalent of “Hello, world!” in SDL.

Listing 1. Setting Up the Display

This program includes the SDL.h header file, which is the master header for SDL. Every SDL application should include this file. The program also includes two standard headers, for the printf and atexit functions.

We begin by calling SDL_Init to initialize SDL. This function takes an ORed list of arguments to indicate which subsystems should be initialized; we are only interested in the video subsystem, so we pass SDL_INIT_VIDEO (if we wanted audio, for instance, we would call this function with SDL_INIT_VIDEO | SDL_INIT_AUDIO). Unless a fatal error occurs, this function should return zero. We also use C's atexit facility to request that SDL_Quit be called before the program exits. This function makes sure that SDL has a chance to shut down properly (which becomes especially important if a fullscreen application crashes).

Next, we use the SDL_SetVideoMode function to inform the display of our desired resolution (in this case 640 pixels across by 480 pixels down) and color depth (16-bit packed pixel). There is a catch here: SDL will try to set up the display as requested, but it might fail. If this happens, SDL won't tell us, but it will instead emulate the requested mode internally. This is usually acceptable, since the emulation code is relatively fast, and we would usually rather not deal with multiple modes internally. SDL_SetVideoMode returns a pointer to the surface that represents the display. If something goes wrong, this function returns NULL.

Finally, we report success and exit. The C library calls SDL_Quit automatically (since we registered it with atexit), and SDL returns the video display to its original mode. (We could also call SDL_Quit explicitly if we wanted to shut the system down before exiting our application; there's no harm in calling it more than once.)

Now that we've created an SDL application, we need to compile it. SDL applications are easy to build; assuming a proper installation of SDL, they just require a few flags and libraries. The standard SDL distribution includes a program called sdl-config (similar to the gtk-config and glib-config programs that ship with the GTK+ toolkit) for supplying the appropriate commandline arguments to gcc. The command sdl-config --cflags produces a list of the options that should be passed to the compiler, and sdl-config --libs produces a list of libraries that should be linked in. We can use backtick substitution to drop this into the gcc command line. If SDL is installed on your system, you can compile this example with the following command:

$ gcc sdltest.c -o sdltest `sdl-config --cflags --libs`
Drawing Pixels Directly

Putting data into an SDL surface is simple. Each SDL_Surface structure contains a pixels member. This is a void pointer to the raw graphic image, and we can write to it directly if we know which type of pixel the surface is set up for. We must call the SDL_LockSurface function before we access this data (because some surfaces reside in special memory areas and require special handling). When we are finished with the surface, we must call SDL_UnlockSurface to release it. The width and the height of the image are given by the w and h members of the structure, and the pixel format is specified by the format member (which is of type SDL_PixelFormat). SDL often emulates nonstandard screen resolutions with higher resolutions, and the pitch member of the pixel format structure indicates the actual width of the frame buffer. You should always use pitch instead of w for calculating offsets into the pixels buffer, or else your application might not work on some display devices.

The example shown in Listing 2 will use the SDL pixel format information to draw individual pixels on the screen. We have chosen to use a 16-bit (hicolor) mode for demonstration purposes, but other modes are equally simple to program.

Listing 2. Drawing Individual Pixels on the Screen

The code's comments give the play-by-play, but a few things might not be obvious. This program employs a very general routine for constructing hicolor pixel values; this routine will work with any hicolor (16-bit) pixel format that SDL recognizes. Although we could write a separate (faster) routine for each possible hicolor data layout, this would require a lot of work and would only marginally improve performance. The hicolor 565 (5 red bits, 6 green bits, and 5 blue bits) pixel format is perhaps the most widely used and could be reasonably optimized, but 556 and 555 are not uncommon. In addition, there is no guarantee that the bit fields will be in the red-green-blue order. Our CreateHicolorPixel routine solves this problem by referring to the data in the SDL_PixelFormat structure. For instance, the routine uses the Rloss member of the structure to determine how many bits to drop from the 8-bit red component, and it then uses the Rshift member to determine where the red bits should be located within the 16-bit pixel value.

Another important issue involves the SDL_UpdateRect function. As we mentioned earlier, SDL sometimes emulates video modes if the video card is unable to provide a certain mode itself. If the video card does not support a requested 24-bit mode, for instance, SDL might select a 16-bit mode instead and return a fake frame buffer setup for 24-bit pixels. This would allow your program to continue normally, and SDL would handle the conversion from 24-bits to 16-bits on the fly (with a slight performance loss). The SDL_UpdateRect function informs SDL that a portion of the screen has been updated and that it should perform the appropriate conversions to display that area. If a program does not use this function, there is a chance that it will still work. It is better to be on the safe side and call this function whenever the frame buffer surface has been changed.

Finally, if you run the program you might notice that it runs in a window instead of taking over the entire screen. To change this, replace the zero in the SDL_SetVideoMode call with the constant SDL_FULLSCREEN. Be careful, though; fullscreen applications are harder to debug, and they tend to mess things up badly when they crash.