Back to Articles

Programming with SDL

Tutorial 1 - An Introduction to SDL


Introduction

SDL is a multimedia library similar to DirectX, but it is cross platform, and arguably much easier to use. This series of tutorials will cover a variety of topics related to SDL.

There are several different sources of information on SDL. The best place to get up to date documentation is from the SDL website itself. Please note that some of the code samples used in this series are based from functions and code distributed with the SDL library.

I haven't found a tutorial that covered all the areas of programming and trouble you might have in Linux with SDL. With that in mind, this tutorial focuses specifically on Linux and Unix-like operating systems. The code samples are still relatively cross platform. They should compile under Windows or other SDL platforms with only minor modifications.

Before reading this series, you should understand the following concepts: file I/O, bit manipulation, and general graphics terminology. It would also be useful to have a familiarity with basic trig and some physics, as later tutorials will rely heavily on those subjects. I've tried to keep beginner and advanced sections seperated. If a section applies to both, it is not labled. Other sections are either labled with (beginner) or (advanced). Important elements that might lead astray people new to SDL, or interesting optimization techniques will be made clear using hint boxes like the one bellow.

Hint Boxes

Hint boxes like the this one will appear whenever there is a need to clarify a point. If you see one of these, it most likely contains something you need to know!

In order to get started, you must first have SDL installed. RPM's can be downloaded from the SDL website. Debian users will find SDL in the venerable collection of packages that come with debian and can install via "apt-get". For development with SDL, you must install the development packages in addition to the runtime packagese.

This series is written very informally - I don't intend this to be a reference manual or anything. It is largely from my own notes that I took while learning SDL, and I'm sure it contains spelling and grammatical errors. Please don't bug me about it. If there are glaring factual mistakes, I would like to hear from you though.

Creating the Makefile (Beginner)

People using operating systems other than Linux or similar should consult the SDL documentation for information on compiling SDL programs.

I am not going to go indepth on Makefile creation here. There are multiple methods of setting up Makefiles, and getting Makefiles to work on several different platforms. If you do not understand how to use Makefiles, there are already several good tutorials written covering the subject. Here is one of them.

First, setup your basic Makefile. (This is from the Example1 source code).

	all: example1

	example1: example1.o
		gcc example1.o -o example1

	example1.o: example1.c
		gcc -c example1.c 

Now, use the sdl-config program to get the required libraries and parameters from the compiler. Insert this into the makefile so that it appears as follows:

	all: example1

	example1: example1.o
		gcc example1.o `sdl-config --libs` -o example1

	example1.o: example1.c
		gcc -c `sdl-config --cflags` example1.c

Creating a Simple SDL Program

Every SDL program must include SDL.h. Including SDL.h gives you access to all the functions needed for SDL programming.

The first SDL function that any program calls must be SDL_Init. SDL_Init allows you to tell the library which parts of SDL you will be using. Currently, you may initialize and use timer, audio, video functions, cdrom functions, and joystick functions. If you wish to use all of these, SDL_INIT_EVERYTHING initializes all subsystems. There are also two special purpose SDL_Init paramaters that are not covered in this particular tutorial. These are SDL_INIT_NOPARACHUTE and SDL_INIT_EVENTTHREAD.

SDL_Init will return a value less than zero if an error occurs. It is important to check that the function worked properly before making any SDL calls.

if( SDL_Init(SDL_INIT_VIDEO) < 0 ) {
	fprintf(stderr, "Could not initialize SDL: %s\n", 
			SDL_GetError());
  		return -1;
}

atexit(SDL_Quit);
This code initializes SDL video functions and returns -1 if there was an error.

When finished making SDL function calls anywhere in your program, it is important to call SDL_Quit to free the resources that SDL uses. This is easily done for most SDL programs by adding an atexit statement immediately after calling SDL_Init, as seen above.

Graphics Setup

Now that SDL has been initialized for drawing graphics to the screen, the program must setup a Window (or get a display buffer) to draw in. In SDL display buffers are referred to as surfaces. Surfaces have several different uses - many of which will be covered in later tutorials. For this tutorial, however, the SDL "surface" will refer to a window.

SDL Surfaces are declared as a pointer to type SDL_Surface. In order to draw to the screen, the program must first get a valid SDL_Surface using SDL_SetVideoMode. SDL_SetVideoMode takes four parameters: the width of the requested mode, the height, the bitdepth, and a set of flags(or options) about the mode.

The following are options that may be passed to SDL_SetVideoMode:

  • SDL_SWSURFACE: Requests a software surface. Software surfaces are in system memory, and are not generally as fast as hardware surfaces.
  • SDL_HWSURFACE: If possible, this surface will be stored video memory. Operations to hardware surfaces can be much faster than software surfaces.
  • SDL_ANYFORMAT: This allows the creation of a surface in any depth format. The bitdepth parameter is the then the preferred format.
  • SDL_HWPALETTE: Most of the time, you will be using true color visuals and will not need to worry about the hardware palette. However, when using palettes, this parameter requests that SDL have full control over the current hardware palette.
  • SDL_DOUBLEBUF: Create a doublbuffered surface. This option will be covered in a later tutorial.
  • SDL_FULLSCREEN: Create a full screen visual. This will set the video mode to the one request by the width and height parameters - and give your application complete control over what the user sees. Most game software will use this option.
  • SDL_OPENGL: Creates an OpenGL surface. To be covered in a later tutorail.
  • SDL_RESIZABLE: Creates a resizable window. To be covered in a later tutorial.
  • SDL_NOFRAME: Create a window with no frame. To be covered in a later tutorial.
  • Today, I am going to demonstrate using a simple Software surface that will support any bit depth. This is created using the following code:

    SDL_Surface* screen;
    ...
    screen = SDL_SetVideoMode(640, 480, 16, SDL_SWSURFACE|SDL_ANYFORMAT);
    if( !screen ) {
    	fprintf(stderr, "Couldn't create a surface: %s\n",
    			SDL_GetError());
    	return -1;
    }	
    
    Creates an SDL_Surface(a window), and checks for errors

    This code will create a software SDL surface of width 640, height 480, and a preferred bit depth of 16 bits per pixel. Note: It is generally a good idea to check that SDL_SetVideoMode does indeed return a proper surface.

    Drawing a Pixel and Getting Buffer Information (Advanced)

    In addition to containing a video buffer, the SDL Surfaces contain information about the current bitdepth, width, and height of the surface. The important data for us is the structure inside the SDL_Surface type named format. This structure contains information on bit depth, as well as information usefull for programming on multiple platforms. (Specifically moving from big indian to little indian platforms.)

    I am not going to cover the whole structure in depth today, as that later tutorials will cover some fields in more depth. The fields that we are currently concerned with are BytesPerPixel and R/G/B/AShift. These will be explained in depth later.

    The following is from setpixel functions in the source code for the example program:

    Uint8 *ubuff8;
    Uint16 *ubuff16; 
    Uint32 *ubuff32;
    Uint32 color;
    
    Different bit depths are easiest accessed with different types of buffers. Here, I declare every type of buffer that might be used.
    	
    color = SDL_MapRGB( screen->format, r, g, b );
    	
    
    The SDL_MapRGB function will return a 32 bit integer from a red, green, and blue value. This integer may then be used to set the pixel value in the Surface video buffer.
      /* How we draw the pixel depends on the bitdepth */
      switch(screen->format->BytesPerPixel) 
        {
        case 1: 
          ubuff8 = (Uint8*) screen->pixels;
          ubuff8 += (y * screen->pitch) + x; 
          *ubuff8 = (Uint8) color;
          break;
    
        case 2:
          ubuff8 = (Uint8*) screen->pixels;
          ubuff8 += (y * screen->pitch) + (x*2);
          ubuff16 = (Uint16*) ubuff8;
          *ubuff16 = (Uint16) color; 
          break;  
    
        case 3:
          ubuff8 = (Uint8*) screen->pixels;
          ubuff8 += (y * screen->pitch) + (x*3);
          
    
          if(SDL_BYTEORDER == SDL_LIL_ENDIAN) {
    	c1 = (color & 0xFF0000) >> 16;
    	c2 = (color & 0x00FF00) >> 8;
    	c3 = (color & 0x0000FF);
          } else {
    	c3 = (color & 0xFF0000) >> 16;
    	c2 = (color & 0x00FF00) >> 8;
    	c1 = (color & 0x0000FF);	
          }
    
          ubuff8[0] = c3;
          ubuff8[1] = c2;
          ubuff8[2] = c1;
          break;
          
        case 4:
          ubuff8 = (Uint8*) screen->pixels;
          ubuff8 = (y*screen->pitch) + (x*4);
          ubuff32 = (Uint32*)ubuff8;
          *ubuff32 = color;
          break;
          
        default:
          fprintf(stderr, "Error: Unknown bitdepth!\n");
        }
      
    
    Don't worry if you can't fully understand this particular portion of code! This function is slightly lower level that may of the other functions that you will be using in the future.

    Advanced: The following fields of the SDL_Surface structure are important here:

  • pixels: A pointer to the video data for the surface.
  • pitch: Pitch refers to the width of the surface in bytes.
  • format: A structure including information about the current surface.
  • The following fields of the format structure are important here:

  • BytesPerPixel: This field contains the number of bytes that a single pixel uses.
  • Rshift, Gshift, Bshift, Ashift: The number of bits to shift the value returned by SDL_MapRGB in order to return the proper offset for each individual color channel.
  • Locking, Unlocking, and Flipping - Oh my!

    Before writing or reading to a surface's pixels member, it should be "locked" if required. This is especially important for using hardware surfaces.

    In order to see if a surface requires locking before use, call the function SDL_MustLock. This function takes a single parameter: the SDL surface you wish to access. It will return a non zero value if the surface must be locked.

    A surface may be locked with the function SDL_LockSurface. This function only takes a single parameter, the SDL surface. This function will return a value less than zero if there is an error. After locking a surface, it is important to also unlock it. This is done with SDL_UnlockSurface, again passed the surface to unlock. Here is the SDL lock and unlock code from the example program:

    	if(SDL_MUSTLOCK(screen)) {
    		if(SDL_LockSurface(screen) < 0) 
    			return;
    	}
    	
    ...
    
    	if(SDL_MUSTLOCK(screen)) {
    		SDL_UnlockSurface(screen);
    	}
    
    
    Note: In general it is a good idea to keep the surface locked for as short a period as possible.

    When drawing to a surface, the data is usually not displayed as you draw it. In order to synchronize the data in an SDL surface, with the data on the screen, you must call the SDL_Flip function. This functions again takes only the SDL surface as a parameter.

    Locking Surfaces

    Remember that surfaces only need to be locked when you want to access the pixel data of a surface(the pixels member of the SDL_Surface structure). Surfaces should remained locked only as long as needed. When a surface is locked, avoid making any operating system or library calls.

    Finishing Up

    The example code covers a few thinks not discussed in this tutorial. These will be discussed in the next tutorial which will cover window management functions, events, and a little on timers. For the most part, the example code is generic enough that there is no issue using it in your own code.

    Functions Covered in this Tutorial: SDL_Init, SDL_Exit, SDL_SetVideoMode, SDL_MapRGB, SDL_MUSTLOCK, SDL_LockSurface, SDL_UnlockSurface, SDL_Flip

    Code: tutorial1.tgz

    ----- Article by Andrew M. (andrew@textux.com)

    Back to Articles