Linux Game Programming for PC & Embedded Systems using SDL
Presented by
Fore June
Author of Windows Fan, Linux Fan

Games and SDL
SDL Installation
SDL for Embedded
SDL API
SDL Events
 SDL Graphics
SDL Threads
Thread Example
SDL Animation
SDL Sound
 Raw Video Player
Video Formats
Video Compression
 Game Trees
About The Author

An Introduction to Video Compression in C/C++ now available at Amazon

@Copyright by Fore June, 2006

SDL API

    The complete SDL API can be found at http://www.libsdl.org/cgi/docwiki.cgi/SDL_20API. We discuss below some of the basic SDL functions, mainly concerning graphics.

  1. Initializing the Library

    Use SDL_Init() to dynamically load and initialize the library. This function takes a set of flags corresponding to the portions you want to activate:

    SDL_INIT_AUDIO
    SDL_INIT_VIDEO
    SDL_INIT_CDROM
    SDL_INIT_TIMER
    SDL_INIT_EVERYTHING

    Use SDL_Quit() to clean up the library when you are done with it.

    Tip:
    SDL dynamically loads the SDL library from the standard system library locations. Use the SDL_SetLibraryPath() function to use an alternate location for the dynamic libraries distributed with your application.

    Synopsis     #include "SDL.h"
    int SDL_Init(Uint32 flags);
    Description     Initializes SDL. This should be called before all other SDL functions. The flags parameter specifies what part(s) of SDL to initialize. (Flags should be bitwise-ORed together, e.g. "SDL_INIT_AUDIO | SDL_INIT_VIDEO".)
    SDL_INIT_TIMER 	Initializes the timer subsystem.
    SDL_INIT_AUDIO 	Initializes the audio subsystem.
    SDL_INIT_VIDEO 	Initializes the video subsystem.
    SDL_INIT_CDROM 	Initializes the cdrom subsystem.
    SDL_INIT_JOYSTICK 	Initializes the joystick subsystem.
    SDL_INIT_EVERYTHING 	Initialize all of the above.
    SDL_INIT_NOPARACHUTE 	Prevents SDL from catching fatal signals.
    SDL_INIT_EVENTTHREAD 	Run the event manager in a separate thread.
    	
    Returns     -1 on error, 0 on success. When error occured, you can obtain more information about it by calling SDL_GetError().

    Synopsis     #include "SDL.h"
    void SDL_Quit(void);
    Description     Shuts down all SDL subsystems and frees the resources allocated to them. You can set SDL_Quit as your atexit call for simplicity, like:

      atexit(SDL_Quit);
    However, it is not advisable to use atexit for large programs or dynamically loaded code.
    Returns     none
     

    Synopsis     #include "SDL.h"
    void SDL_QuitSubSystem(Uint32 flags);
    Description     Shuts down a particular component of SDL, leaving others untouched.

    Example:
    #include <stdlib.h>
    #include "SDL.h"
    
    main(int argc, char *argv[])
    {
        if ( SDL_Init(SDL_INIT_AUDIO|SDL_INIT_VIDEO) < 0 ) {
            fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
            exit(1);
        }
        //atexit(SDL_Quit);
        SDL_Quit();
        ...
    }
    

  2. Video API

    Every personal computer has a video card to process graphical data. There are numerous brands of video cards which differ in structures and functions. To hide the low-level programming details of the video hardware, people introduce the concept of framebuffer device which is an abstraction for the graphic hardware. It represents the frame buffer of some video hardware, and allows application software to access the graphic hardware through a well-defined interface, so that the software doesn't need to know anything about the low-level interface stuff. We may regard framebuffer as an area of memory that describes the image on the computer screen, with each screen pixel corresponding to a memory location.

    SDL uses structures called surfaces to handle graphical data. A surface can be regarded as a memory block that stores a retangular region of pixels. Like a framebuffer, a surface has widths, heights, and specific pixel formats. The rectangular region of data of a surface is often referred to as bitmaps or pixmaps.

    SDL surfaces can be copied onto each other efficiently with one-to-one pixel mapping, which is referred to as block image transfer or blit. Blit operations are important in game programming as they allow a portion or complete image to be transferred from a memory buffer working in the background to the display screen effectively. Since the framebuffer can be also regarded as a surface, one can display the entire image on the screen with a single blitting operation. In practice, games rely mainly on blits to show the drawings rather than writing to individual pixels one by one. In general, a game's artwork is created by artists and saved in files. The game program assembles the images on screen from those predrawn graphics.

    Choosing and setting video modes

    Before writing to to the framebuffer, you need to tell the video card what features you need. You can choose your desired bit-depth and resolution, and set it using the SDL_SetVideoMode() function, which returns an SDL_Surface where all graphics data, including the screen, are stored. You can choose a video mode based on the following information: Full screen or windowed, Screen size, Window properties ( has border, resize, .. ), Bits per Pixel, Surface type ( software surface, hardware surface )

    The following two examples ask for a 640x480 screen software surface with a pixel format that is that same as the current display setting.
    Example:
    int options = (
        SDL_ANYFORMAT  |
        SDL_FULLSCREEN |
        SDL_SWSURFACE
    );
    
    SDL_Surface *screen = NULL;
    
    screen = SDL_SetVideoMode(640, 480, 0, options);
    if (NULL == screen)
    {
        printf("Can't set video mode");
        exit(1);
    }
      
     
    Tip #1:
    You can find the fastest video depth supported by the hardware with the function SDL_GetVideoInfo().

    Tip #2:
    You can get a list of supported video resulutions at a particular bit-depth using the function SDL_ListModes().

    Example:
    //setvideo.cpp
    //compile by: g++ -o setvideo setvideo.cpp -I/usr/include -L/usr/local/lib -lSDL
    #include <SDL/SDL.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main()
    {   
      SDL_Surface *screen;
    
      //initialize video system
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
    fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
    exit(1);
    }
    //ensure SDL_Quit is called when the program exits
    atexit(SDL_Quit);
    
    //set video mode of 640 x 480 with 16-bit pixels
    screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE);
    if ( screen == NULL ) {
    fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
    exit(1);
    }
    
    SDL_Delay ( 2000 );	//delay 2 seconds before exit
    printf("Setting video mode successful!\n");
    return 0;
    }
    

    Drawing pixels on the screen

    Drawing to the screen is done by writing directly to the graphics framebuffer, and calling the screen update function.

    Nothing you draw on a software surface is visible until it has been copied from memory to the display buffer on the video card. SDL provides two ways to do that: SDL_Flip() and SDL_UpdateRect(). SDL_Flip() copies the entire software surface to the screen. If your screen is set to 640x480 at 4 bytes per pixel, SDL_Flip() will copy 1.2 megabytes per frame and your frame rate will be limited by how fast your computer can copy images to the screen.

    SDL_UpdateRects() is designed to let you use a "dirty pixels" scheme. It lets you specify a list of rectangular areas that have been changed and only copies those areas to the screen. This technique is ideal for a game with a complex background but only a small number of moving or changing items. Tracking dirty pixels can give you a dramatic improvement in performance.

     
     
    Tip:
    If you know that you will be doing a lot of drawing, it is best to lock the screen (if necessary) once before drawing, draw while keeping a list of areas that need to be updated, and unlock the screen again before updating the display.

    Synopsis     #include "SDL.h"
    int SDL_Flip( SDL_Surface *screen );
    Description     Swaps the background and foreground buffers for systems that support double-buffering. For systems not supporting double-buffering, it is the same as SDL_UpdateRect(screen, 0, 0, 0, 0).
    Returns     none
     

    Synopsis     #include "SDL.h"
    void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h);
    Description     Makes sure the given area is updated on the given screen. The rectangle must be confined within the screen boundaries (no clipping is done).

    If 'x', 'y', 'w' and 'h' are all 0, SDL_UpdateRect will update the entire screen.

    Returns     none
     

    The following example draws a pixel on a screen with arbitrary format.

    Example:
    void DrawPixel(SDL_Surface *screen, Uint8 R, Uint8 G, Uint8 B)
    {
    
      Uint32 color = SDL_MapRGB(screen->format, R, G, B); if ( SDL_MUSTLOCK(screen) ) { if ( SDL_LockSurface(screen) < 0 ) { return; } } switch (screen->format->BytesPerPixel) { case 1: { /* Assuming 8-bpp */ Uint8 *bufp; bufp = (Uint8 *)screen->pixels + y*screen->pitch + x; *bufp = color; } break; case 2: { /* Probably 15-bpp or 16-bpp */ Uint16 *bufp; bufp = (Uint16 *)screen->pixels + y*screen->pitch/2 + x; *bufp = color; } break; case 3: { /* Slow 24-bpp mode, usually not used */ Uint8 *bufp; bufp = (Uint8 *)screen->pixels + y*screen->pitch + x; *(bufp+screen->format->Rshift/8) = R; *(bufp+screen->format->Gshift/8) = G; *(bufp+screen->format->Bshift/8) = B; } break; case 4: { /* Probably 32-bpp */ Uint32 *bufp; bufp = (Uint32 *)screen->pixels + y*screen->pitch/4 + x; *bufp = color; } break; } if ( SDL_MUSTLOCK(screen) ) { SDL_UnlockSurface(screen); } SDL_UpdateRect(screen, x, y, 1, 1);
    }

    Loading and displaying images

    SDL provides one single image loading function, SDL_LoadBMP() for you to load a Windows BMP image onto an SDL surface. The function returns a pointer to an SDL_Surface structure containing the image, or a NULL pointer for failure of loading the image.

    You can then display the loaded images by using SDL_BlitSurface() to blit them into the graphics framebuffer. SDL_BlitSurface() automatically clips the blit rectangle, which should be passed to SDL_UpdateRect() to update the portion of the screen which has changed. Bitmaps use dynamically allocated memory; if no longer needed they should be freed by using the function SDL_FreeSurface().

    Conversely, the function SDL_SaveBMP() allows you to save an SDL surface as a BMP file.

     
     
    Tip #1:
    If you are loading an image to be displayed many times, you can improve blitting speed by convert it to the format of the screen. The function SDL_DisplayFormat() does this conversion for you.

    Tip #2:
    Many sprite images have a transparent background. You can enable transparent blits (colorkey blit) with the SDL_SetColorKey() function.

    Synopsis     #include "SDL.h"
    SDL_Surface *SDL_LoadBMP(const char *file);
    Description     Loads a surface from a named Windows BMP file.
    Note: When loading a 24-bit Windows BMP file, pixel data points are loaded as blue, green, red, and NOT red, green, blue (as one might expect).
    Returns     the new surface, or NULL if there was an error.
     

    Synopsis     #include "SDL.h"
    int SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect );
    Description     Performs a fast blit from the source surface to the destination surface. The width and height in srcrect determine the size of the copied rectangle. Only the position is used in the dstrect (the width and height are ignored). Blits with negative dstrect coordinates will be clipped properly. If srcrect is NULL, the entire surface is copied. If dstrect is NULL, then the destination position (upper left corner) is (0, 0).
    Returns     0 for success, -1 for failure.
     

    Example:
    void ShowBMP(char *file, SDL_Surface *screen, int x, int y)
    {
      SDL_Surface *image;
      SDL_Rect dest;
    
    /* Load the BMP file into a surface */
    
      image = SDL_LoadBMP(file);
      if ( image == NULL ) {
        fprintf(stderr, "Couldn't load %s: %s\n", file, SDL_GetError());
        return;
      }
    
    /* Blit onto the screen surface.
    The surfaces should not be locked at this point.
    */
      dest.x = x;
      dest.y = y;
      dest.w = image->w;
      dest.h = image->h;
      SDL_BlitSurface(image, NULL, screen, &dest);
    
      /* Update the changed portion of the screen */
      SDL_UpdateRects(screen, 1, &dest);
    }

    The above image functions only handle images in BMP format. If you need to process images in other popular formats like .png, .jpg or .gif, you need to install the SDL_image- library discussed before. After you have installed it, your program needs to include SDL/SDL_image.h and link with -lSDL_image. Documentation of this library can be found at http://jcatki.no-ip.org/SDL_image/SDL_image.html The library supports BMP, PNM (PPM/PGM/PBM), XPM, LBM, PCX, GIF, JPEG, PNG, TGA, and TIFF formats.

    You can use the function IMG_Load() to load an image onto an SDL surface. It is best to call this function outside of event loops, and keep the loaded images around until you are really done with them, as the loading process could be time-consuming. When you have finished using the image, you should use SDL_FreeSurface() to free the allocated resources.

    Synopsis     #include "SDL_image.h"
    SDL_Surface *IMG_Load ( const char *ifile );
    Description     Loads ifile for use as an image onto a new SDL_Surface.
    Returns     A pointer to the new SDL_Surface where the image is loaded.
     

    Example:
    /*
    loadimage.cpp
     
    compile by: g++ -o loadimage loadimage.cpp -I/usr/include -L/usr/local/lib -lSDL -lSDL_image
    execute by: ./loadimage
    */
     
    #include <SDL/SDL.h>
    #include <SDL/SDL_image.h>
    #include <stdlib.h>
     
    bool load_image (   SDL_Surface *screen, char *image_name, int x, int y )
    {
      SDL_Surface *image;
      SDL_Rect source, offset;	//offset is the destination
                                                                                    
      image = IMG_Load(  image_name );
      if ( image == NULL ) {
        printf ( "Unable to load image\n" );
        return false;
      }
      source.x = 0;
      source.y = 0;
      source.w = image->w;	//display the whole image
      source.h = image->h;
      offset.x = x;         //position to display the image
      offset.y = y;
      offset.w = image->w;	//the width and height here actually are NOT used
      offset.h = image->h;
                                                                                    
      //Draws the image data to the screen: (image, source) is the source, (screen, offset) is the destination
      SDL_BlitSurface ( image, &source, screen, &offset );
      //free the resources allocated to image
      SDL_FreeSurface ( image );
      return true;
    }
                                                                                    
    int main()
    {
      SDL_Surface *screen;
                                                                                    
      //initialize video system
      if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
            fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
            exit(1);
      }
      //ensure SDL_Quit is called when the program exits
      atexit(SDL_Quit);
                                                                                    
      //set video mode of 640 x 480 with 16-bit pixels
      screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE);
      if ( screen == NULL ) {
            fprintf(stderr, "Unable to set 640x480 video: %s\n", SDL_GetError());
            exit(1);
      }
                                                                                    
      //put image near center of screen
      if ( !load_image ( screen, "test-image.gif", 320, 240 ) ) {
            exit ( 1 );
      }
      //update the entire screen
      SDL_UpdateRect ( screen, 0, 0, 0, 0 );
                                                                                    
      SDL_Delay ( 4000 );   //delay 4 seconds before exit
                                                                                    
      return 0;
    }