Showing posts with label SDL. Show all posts
Showing posts with label SDL. Show all posts

Thursday, 29 November 2012

Using NGL with SDL

SDL is a very good library for games development and very useful for cross platform development. In this post I will explain how to install and configure SDL 2.0 (HG) for use with OpenGL and my NGL:: library. The source code can be downloaded using bzr branch http://nccastaff.bournemouth.ac.uk/jmacey/Code/SDLNGL from here

SDL installation

The latest version of SDL handles creating "core profile" OpenGL contexts on mac so this will be required. Earlier version of SDL will not work as they do not support the creation of the correct context for OpenGL under the mac. I decided to do a local install of SDL and if you wish to use this in the Labs at the University you will have to do the same thing as you don't have root permission to install the libs. The process of installation is similar to the one outlined here and I'm going to install the libraries in a directory called $(HOME)/SDL2.0 this is important as the makefile will also use this location to find the sdl2-config script at a later date.

The following commands will download and install the libraries and build it into the correct directory.
mkdir SDL2.0
tar vfxz SDL-2.0.tar.gz 
cd SDL-2.0.0-6673/
./configure --prefix=/home/jmacey/SDL2.0 (change to your home dir)
make -j 8
make install
This will install everything into the SDL2 directory and you will have a structure like this
bin include lib share
To test this is working do the following
cd ~/SDL2.0/bin
./sdl2-config --cflags --libs
-I/Volumes/home/jmacey/SDL2.0/include/SDL2 -D_THREAD_SAFE
-L/Volumes/home/jmacey/SDL2.0/lib -lSDL2

SDL NGL Demo

The demo is split into two main modules. The main.cpp file will create the SDL and OpenGL context, and handle the processing of events. The NGLDraw class will contain all OpenGL setup and drawing routines.

Setup and basic SDL

To use SDL we need to include the <SDL.h> header, this will be placed in the path by the following command in the Qt .pro file.
QMAKE_CXXFLAGS+=$$system($$(HOME)/SDL2.0/bin/sdl2-config  --cflags)
message(output from sdl2-config --cflags added to CXXFLAGS= $$QMAKE_CXXFLAGS)

LIBS+=$$system($$(HOME)/SDL2.0/bin/sdl2-config  --libs)
message(output from sdl2-config --libs added to LIB=$$LIBS)
For more info see this post

First we need to initialise the SDL video subsystem using the following command

// Initialize SDL's Video subsystem
if (SDL_Init(SDL_INIT_VIDEO) < 0 )
{
  // Or die on error
  SDLErrorExit("Unable to initialize SDL");
}
There is also a helper function to exit SDL gracefully
void SDLErrorExit(const std::string &_msg)
{
  std::cerr<<_msg<<"\n";
  std::cerr<<SDL_GetError()<<"\n";
  SDL_Quit();
  exit(EXIT_FAILURE);
}
Next we create the basic window, in this case I get the size of the screen and configure the screen to be centred and half max screen width and height
// now get the size of the display and create a window we need to init the video
SDL_Rect rect;
SDL_GetDisplayBounds(0,&rect);
// now create our window
SDL_Window *window=SDL_CreateWindow("SDLNGL",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,
                         rect.w/2,rect.h/2,
                         SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
// check to see if that worked or exit
if (!window)
{
 SDLErrorExit("Unable to create window"); 
}

Creating an OpenGL context

SDL 2.0 uses a SDL_GLContext to hold the information about the current GL context. There are many flags we need to setup our context and these are handled using the SDL_GL_SetAttribute function. I've also discovered on my linux build that some of these flags don't work and cause crashes (particularly creating a core profile context). To overcome this conditional compilation is used as shown in the following function.

SDL_GLContext createOpenGLContext(SDL_Window *window)
{
  // Request an opengl 3.2 context first we setup our attributes, if you need any
  // more just add them here before the call to create the context
  // SDL doesn't have the ability to choose which profile at this time of writing,
  // but it should default to the core profile
  // for some reason we need this for mac but linux crashes on the latest nvidia drivers
  // under centos
  #ifdef DARWIN
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  #endif
  // set multi sampling else we get really bad graphics that alias
  SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
  SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,4);
  // Turn on double buffering with a 24bit Z buffer.
  // You may need to change this to 16 or 32 for your system
  // on mac up to 32 will work but under linux centos build only 16
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
  // enable double buffering (should be on by default)
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  //
  return SDL_GL_CreateContext(window);

}
Care must be taken with setting the depth size, under mac osx it works with 32 bit, under linux I set to 16 and on some machines 24 will work. The following code configures the GL context and clears the screen.
SDL_GLContext glContext=createOpenGLContext(window);
if(!glContext)
{
 SDLErrorExit("Problem creating OpenGL context");
}
// make this our current GL context (we can have more than one window but in this case not)
SDL_GL_MakeCurrent(window, glContext);
/* This makes our buffer swap syncronized with the monitor's vertical refresh */
SDL_GL_SetSwapInterval(1);
// now clear the screen and swap whilst NGL inits (which may take time)
glClear(GL_COLOR_BUFFER_BIT);
SDL_GL_SwapWindow(window);
Now this has been done we can use NGL and create our graphics. In this case the NGLDraw class is a re-working of the SimpleNGL demo, it initialises GLEW if required. The following code shows the creation of the NGLDraw class and the key and mouse processing.
NGLDraw ngl;
// resize the ngl to set the screen size and camera stuff
ngl.resize(rect.w,rect.h);
while(!quit)
{

 while ( SDL_PollEvent(&event) )
 {
  switch (event.type)
  {
   // this is the window x being clicked.
   case SDL_QUIT : quit = true; break;
   // process the mouse data by passing it to ngl class
   case SDL_MOUSEMOTION : ngl.mouseMoveEvent(event.motion); break;
   case SDL_MOUSEBUTTONDOWN : ngl.mousePressEvent(event.button); break;
   case SDL_MOUSEBUTTONUP : ngl.mouseReleaseEvent(event.button); break;
   case SDL_MOUSEWHEEL : ngl.wheelEvent(event.wheel);
   // if the window is re-sized pass it to the ngl class to change gl viewport
   // note this is slow as the context is re-create by SDL each time
   case SDL_WINDOWEVENT :
    int w,h;
    // get the new window size
    SDL_GetWindowSize(window,&w,&h);
    ngl.resize(w,h);
   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;
     case SDLK_w : glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); break;
     case SDLK_s : glPolygonMode(GL_FRONT_AND_BACK,GL_FILL); break;
     case SDLK_f :
     SDL_SetWindowFullscreen(window,SDL_TRUE);
     glViewport(0,0,rect.w,rect.h);
     break;

     case SDLK_g : SDL_SetWindowFullscreen(window,SDL_FALSE); break;
     default : break;
    } // end of key process
   } // end of keydown

   default : break;
  } // end of event switch
 } // end of poll events

 // now we draw ngl
 ngl.draw();
 // swap the buffers
 SDL_GL_SwapWindow(window);

}
The most important call here is the SDL_GL_SwapWindow call which tells SDL to swap the buffers and re-draw.

NGLDraw class 

Most of the NGLDraw class is basic ngl code,  the constructor is used to initialise ngl and create the camera, light and materials.  The draw method grabs and instance of the primitives class and draws the teapot, both of which are similar to the Qt NGL demos. The main difference is the processing of the mouse input. I still use the same flags and attributes to store the rotations and position data, however the SDL mouse data is used to grab x,y and button values. This is shown in the following code.
void NGLDraw::mouseMoveEvent (const SDL_MouseMotionEvent &_event)
{
  if(m_rotate && _event.state &SDL_BUTTON_LMASK)
  {
    int diffx=_event.x-m_origX;
    int diffy=_event.y-m_origY;
    m_spinXFace += (float) 0.5f * diffy;
    m_spinYFace += (float) 0.5f * diffx;
    m_origX = _event.x;
    m_origY = _event.y;
    this->draw();

  }
  // right mouse translate code
  else if(m_translate && _event.state &SDL_BUTTON_RMASK)
  {
    int diffX = (int)(_event.x - m_origXPos);
    int diffY = (int)(_event.y - m_origYPos);
    m_origXPos=_event.x;
    m_origYPos=_event.y;
    m_modelPos.m_x += INCREMENT * diffX;
    m_modelPos.m_y -= INCREMENT * diffY;
    this->draw();
  }
}


void NGLDraw::mousePressEvent (const SDL_MouseButtonEvent &_event)
{
  // this method is called when the mouse button is pressed in this case we
  // store the value where the maouse was clicked (x,y) and set the Rotate flag to true
  if(_event.button == SDL_BUTTON_LEFT)
  {
    m_origX = _event.x;
    m_origY = _event.y;
    m_rotate =true;
  }
  // right mouse translate mode
  else if(_event.button == SDL_BUTTON_RIGHT)
  {
    m_origXPos = _event.x;
    m_origYPos = _event.y;
    m_translate=true;
  }
}

void NGLDraw::mouseReleaseEvent (const SDL_MouseButtonEvent &_event)
{
  // this event is called when the mouse button is released
  // we then set Rotate to false
  if (_event.button == SDL_BUTTON_LEFT)
  {
    m_rotate=false;
  }
  // right mouse translate mode
  if (_event.button == SDL_BUTTON_RIGHT)
  {
    m_translate=false;
  }
}

void NGLDraw::wheelEvent(const SDL_MouseWheelEvent &_event)
{

  // check the diff of the wheel position (0 means no change)
  if(_event.y > 0)
  {
    m_modelPos.m_z+=ZOOM;
    this->draw();
  }
  else if(_event.y <0 )
  {
    m_modelPos.m_z-=ZOOM;
    this->draw();
  }

  // check the diff of the wheel position (0 means no change)
  if(_event.x > 0)
  {
    m_modelPos.m_x-=ZOOM;
    this->draw();
  }
  else if(_event.x <0 )
  {
    m_modelPos.m_x+=ZOOM;
    this->draw();
  }
}
The rest of the code is fairly self explanatory if you've use NGL before.

Thursday, 4 October 2012

How to run sdl-config in a Qt Project

I did a quick google search for this and didn't find anything so I thought I would figure it out myself. It is actually quite easy, you need to use the $$system() function in qmake to run the sdl-config script (which should be in your path) and then assign it to the correct qmake variables. The following code snippet shows what to add

LIBS+=$$system(sdl-config  --libs)
message(output from sdl-config --libs added to LIBS =$$LIBS)
CXX_FLAGS+=$$system(sdl-config  --cflags)
message(output from sdl-config --cflags added to CXX_FLAGS= $$CXX_FLAGS)
The message calls are there to display the output for debug and can be removed. This will output the following (on my mac)
Project MESSAGE: output from sdl-config --libs added to LIBS =-L/usr/local/lib -lSDLmain -lSDL -Wl,-framework,Cocoa
Project MESSAGE: output from sdl-config --cflags added to CXX_FLAGS= -I/usr/local/include/SDL -D_GNU_SOURCE=1 -D_THREAD_SAFE

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;
}

Getting Started with SDL (Part 1 installation)

SDL is an ideal cross platform API for basic games development and other non GUI graphics systems. To quote the website above

 "Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer. It is used by MPEG playback software, emulators, and many popular games, including the award winning Linux port of "Civilization: Call To Power."

In this series of blog posts I will look at the basic use of SDL with a focus on using it on the Raspberry Pi, however all the code should work under all linux distributions as well as Mac OSX ( Windows should also just work, however I don't have a windows machine to test against).

Installing SDL using apt-get

The easiest way to install SDL on the rpi is to use apt-get and install the pre-build development packages. To do this use the following commands

sudo apt-get install libsdl1.2-dev

To check that this has been successful we can now execute the sdl-config script as follows

sdl-config 
Usage: sdl-config [--prefix[=DIR]] [--exec-prefix[=DIR]] [--version] [--cflags] [--libs] [--static-libs]

Installing from Source

The source code for SDL is available from this link http://www.libsdl.org/download-1.2.php. This is the easiest way to get SDL working on Linux system without apt as well as for Mac OSX.  To build from the .tgz version do the following

tar vfxz SDL-1.2.15.tar.gz
cd SDL-1.2.15
./configure
make
sudo make install

Once this is done we can again check to ensure that things are working by testing the sdl-config program above.

Raspberry Pi user config for the console

If you intend to use SDL on the raspberry pi without using X windows the SDL library will attempt to access the framebuffer directly. By default only the root user has access to the framebuffer device so we need to add the current user (i.e. what you logged in as) to this group.  To do this we need to add the user to the group video, input and audio groups (audio if we use it later) for a minimum you must have video and input.
sudo usermod -a -G video,input,audio [your username]

Once this is done logout and the user will be added to the group on the next login.
part 2