Monday, 24 September 2012

Getting Started with SDL (Part 2 A Simple Window)

In the last post we talked about how to install SDL in this post we will create a simple program to create initialise SDL and create a simple window.

All the code for this post can be found here and downloaded via version control using bzr

SDL.h

The first thing we need to do when using SDL is to include the SDL.h header file. This is done using the following line
#include <SDL/SDL.h>
Note that the directory prefix SDL/ is part of this path, as we shall see later the sdl-config script will give us the correct include paths when we compile the program relative to this directory.

SDL_main

Depending upon the operating system, SDL uses different native code to generate the window / interactions with the operating system. Under linux this is done be default, however under Mac OSX and Windows we need to include a different version of main. To allow this and make the code portable we can use the C/C++ conditional compilation pre-processor. To do this we use the following code
/// note that under mac osx (and windows) there is a different
/// way to build SDL so we need to use SDL_main under linux
/// normal main is fine so we use this conditional compilation
/// to incude the correct version (DARWIN being mac os x)
#if defined (DARWIN) || defined (WIN32)
  int SDL_main(int argc, char **argv)
#else
  int main(int argc, char **argv)
#endif

SDL_Init

The first thing we need to do when using SDL is to initialise the library, to do this we use the SDL_Init function, this is passed one parameter which is a flag to indicate which sub-systems should be initialised. These values are combined together using a logical or ( | ). The subsystems available are as follows
SDL_INIT_TIMER Initializes the timer sub system.
SDL_INIT_AUDIO Initializes the audio sub system.
SDL_INIT_VIDEO Initializes the video sub system.
SDL_INIT_CDROM Initializes the cdrom sub system.
SDL_INIT_JOYSTICK Initializes the joystick sub system.
SDL_INIT_EVERYTHING Initialize all of the above.
SDL_INIT_NOPARACHUTE Prevents SDL from catching fatal signals.
SDL_INIT_EVENTTHREAD
For example if we wish to initialise both the video and joystick sub sytems we would use the following code 
SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK);
In the following examples we will use just the video subsystem but we will also check to see if the initialisation actually worked by checking the return value from SDL_init and making sure it's a zero
if (SDL_Init( SDL_INIT_VIDEO ) !=0)
{
    std::cerr <<"error initialising SDL exiting\n";
    exit(EXIT_FAILURE);
}

Setting Video mode

To give us a video surface we use the SDL_SetVideoMode function, it has 4 parameters width and height, bits per pixel (bpp) and flags.

If the bpp value is set to 0 it will attempt to use the system value for the current display, the flags parameter can be a logical or combination of the values below, however some of these flags will cancel each other out.

SDL_SWSURFACE Surface is stored in system memory
SDL_HWSURFACE Surface is stored in video memory
SDL_ASYNCBLIT Surface uses asynchronous blits if possible
SDL_ANYFORMAT Allows any pixel-format (Display surface)
SDL_HWPALETTE Surface has exclusive palette
SDL_DOUBLEBUF Surface is double buffered (Display surface)
SDL_FULLSCREEN Surface is full screen (Display Surface)
SDL_OPENGL Surface has an OpenGL context (Display Surface)
SDL_OPENGLBLIT Surface supports OpenGL blitting (Display Surface)
SDL_RESIZABLE Surface is resizable (Display Surface)
SDL_HWACCEL Surface blit uses hardware acceleration
SDL_SRCCOLORKEY Surface use colorkey blitting
SDL_RLEACCEL Colorkey blitting is accelerated with RLE
SDL_SRCALPHA Surface blit uses alpha blending
SDL_PREALLOC Surface uses preallocated memory

This function will return an SDL_Surface structure if successful which will be referred to in other drawing functions. If this fails NULL will be returned to we can check if there was an error.

/// @brief the width of the window
const int WINDOW_WIDTH = 1024;
/// @brief the height of the window
const int WINDOW_HEIGHT = 720;
SDL_Surface* screen = SDL_SetVideoMode( WINDOW_WIDTH, WINDOW_HEIGHT, 
                                        0,SDL_HWSURFACE | SDL_DOUBLEBUF );
if( screen == NULL)
{
  std::cerr<<"error setting SDL Video Mode\n";
  exit(EXIT_FAILURE);
}

In this example we are setting the video to be a Hardware surface (in GPU memory) and to use double buffering which should give use better graphics performance in the later examples.

Setting the window caption

To set the text in the titlebar of the window created by SDL we can use the following code
// next we set the window bar caption to the text 2nd param is for an icon
// this is a char * to a pixmap data but if we use 0 none is loaded
SDL_WM_SetCaption( "A Simple SDL Window", 0 );

Event processing

SDL uses an event structure called SDL_Event to store all the information about the various events the host system / Windows manager is passing. This structure is actually a structure or many other structures and we can process the information in a number of ways. For these first simple examples we are going to look for a key down press and the windows system passing a Quit message. The structure of this is a continuous while loop, where we check a flag to see if we should exit.
SDL_Event event;
bool quit=false;
// now we loop until the quit flag is set to true
while(!quit)
{
 // process SDL events, in this case we are looking for keys
  while ( SDL_PollEvent(&event) )  
  {
    switch (event.type)
    {
    // this is the window x being clicked.
    case SDL_QUIT : quit = true; break;

    // now we look for a keydown event
    case SDL_KEYDOWN:
    {
      switch( event.key.keysym.sym )
      {
        // if it's the escape key quit
        case SDLK_ESCAPE :  quit = true; break;
        default : break;
      }
    }

    default : break;
  }
 }
} // end processing loop

Exiting SDL

Once processing has finished it is important to shutdown SDL as it may have grabbed resources that other programs need access. To do this we use the SDL_Quit function.

Compiling the program

To compile our completed program we need to pass several flags to the c++ compiler we are using, this is what the sdl-config program is for. If we run sdl-config we get the following 
sdl-config --cflags --libs
-I/usr/include/SDL -D_GNU_SOURCE=1 -D_REENTRANT
-L/usr/lib/arm-linux-gnueabihf -lSDL
We can combine this into the call to g++ by using the single back quotes as follows
g++ InitSDL.cpp -o InitSDL `sdl-config --cflags --libs`
The full listing of the program can be downloaded from the bzr repository at the top of the page, however here is the source for the basic demo (without comments)
#include <SDL/SDL.h>
#include <cstdlib>
#include <iostream>

const int WINDOW_WIDTH = 1024;
const int WINDOW_HEIGHT = 720;

#if defined (DARWIN) || defined (WIN32)
  int SDL_main(int argc, char **argv)
#else
  int main(int argc, char **argv)
#endif
{
 if (SDL_Init( SDL_INIT_VIDEO ) !=0)
 {
  std::cerr <<"error initialising SDL exiting\n";
  exit(EXIT_FAILURE);
 }
 SDL_Surface* screen = SDL_SetVideoMode( WINDOW_WIDTH, WINDOW_HEIGHT, 0,SDL_HWSURFACE | SDL_DOUBLEBUF );
 if( screen == NULL)
 {
  std::cerr<<"error setting SDL Video Mode\n";
  exit(EXIT_FAILURE);
 }
 SDL_WM_SetCaption( "A Simple SDL Window", 0 );

 SDL_Event event;
 bool quit=false;
 while(!quit)
 {
  while ( SDL_PollEvent(&event) )
  {
   switch (event.type)
   {
    case SDL_QUIT : quit = true; break;

    case SDL_KEYDOWN:
    {
     switch( event.key.keysym.sym )
     {
      case SDLK_ESCAPE :  quit = true; break;
      default : break;
     }
    }

    default : break;
   }
  }
 } // end processing loop

 SDL_Quit();

 return EXIT_SUCCESS;
}

2 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Great article, although I do have one small correction for you - although indeed the | operator is correct to use for combining the flags to pass to SDL_Init(), | is the bitwise OR operator (the || operator is logical OR).

    ReplyDelete