Intro:

This is a page that will discuss how to use SDL and OpenGL together, and how to access OpenGL extensions, in particular the ARB GLSL functions.

What is & why use SDL?

SDL stands for Simple Directmedia Layer.  Overall, it is a great cross-platform library written in C that gives you access to video, audio, keyboard, mouse and joysticks.  Plus, unlike GLUT, you have control over the main loop because you implement it!  And don't worry, it's not that hard to make a main loop, because it's just an infinite loop where you process input, do some other work and then show some output.  Simple.  So what we are going to do is the following:
  1. Setup SDL
  2. Setup a Makefile
  3. Write some code using SDL + OpenGL
  4. Write some code to setup functions to do some GLSL
  5. Have fun!

STEP 1: Get SDL Setup

    First you'll need to download/install SDL

For Mac Users:

    I recommend using Fink and using the apt-get capabilities that come with it.  If you choose to install from source then follow the directions for Linux users.  Once installed with the right environment variables setup, open up Terminal and type the following:

       sudo apt-get install sdl

You will be prompted for the administrative password for the system, and you will be prompted to install dependent packages.  After giving the password and accepting dependencies, you will have the SDL binary libraries, headers for development and a nice script that we will find useful later on. 

For Linux Users:

    For Linux there are a couple options.  If you have a package manager like YaST does on SuSe Linux then you can use that.  Just make sure you install the development packages as well.  If you want to install from source then make sure you go to the SDL website and download the tarball with the source code.  After unpacking it, go into the SDL directory and type the following

       ./configure
       make
       make install

Now this will install in the /usr/local directory.  If you want to install it somewhere else, such as your own local directory (e.g. /home/arun/local ), then add a --prefix /home/arun/local as an argument to the configure script with your path instead of /home/arun/local.  If you do this just make sure that /home/arun/local/bin, or whatever your local path is, has a bin directory and that your $PATH environment variable includes the directory path to that folder.  This will be important later on.

STEP 2: Setting Up the Makefile

    So now we have SDL setup, now let's make a skeleton Makefile.  Of course, you'll want to make your own project directory and go into that directory.  I like to use VIM as an editor, but you can use anything you want.  And if you haven't done a Makefile yet, then shame on you :)  So here is my Makefile, and I'll explain what's what.


############
MACHINE= $(shell uname -s)

ifeq ($(MACHINE),Darwin)
    OPENGL_INC= -FOpenGL
    OPENGL_LIB= -framework OpenGL
    SDL_INC= `sdl-config --cflags`
    SDL_LIB= `sdl-config --libs`
else
    OPENGL_INC= -I/usr/X11R6/include
    OPENGL_LIB= -I/usr/lib64 -lGL -lGLU
    SDL_INC= `sdl-config --cflags`
    SDL_LIB= `sdl-config --libs`
endif

# object files have corresponding source files
OBJS= sdlglsl.o
CXX=g++
COMPILER_FLAGS= -g
INCLUDE= $(SDL_INC) $(OPENGL_INC)
LIBS= $(SDL_LIB) $(OPENGL_LIB)

EXEC= sdlglsl

$(EXEC): $(OBJS)
    $(CXX) $(COMPILER_FLAGS) -o $(EXEC) $(OBJS) $(LIBS)

%.o:    %.cpp
    $(CXX) -c $(COMPILER_FLAGS) -o $@ $< $(INCLUDE)

clean:
    rm -f $(EXEC) $(OBJS)


So what are you looking at?  Well here we have a Makefile that will work on Linux and on Mac OS X.  The key thing here is that we take advantage of some shell scripts that will give us some clue as to what platform we are on.  That being the uname script.  The first line takes the result of the script with a -s argument  which will return either "Linux", or "Darwin" for the case of the Mac. 

    Now OS X has a slightly different way of accessing the OpenGL libraries because they use Frameworks.  There's plenty of documentation at the Apple Developer's site for how to use frameworks, make frameworks, etc.  Just remember that on the Mac, if a library is in a framework you need to use a -F<Library Name Here> approach to getting the include path setup right and a -framework <Library Name Here> approach to getting the library path setup right.

    Next you'll notice is how we setup the SDL include and library paths.  Basically in the bin directory where SDL is installed has a script called sdl-config, which will retain all the information needed about where SDL was installed (i.e. the prefix ), what the include path is, what the library path is, compiler arguements for both and more.

STEP 3: Write SDL + OpenGL code

    Now on to the good stuff.  What we'll do is take a top down approach and figure out what we need to do and then break them down into functions as necessary.  First let's make sure we have the right header files included and below is code that will take care of some slight differences between Linux and OS X:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include <SDL.h>
#ifdef __APPLE__
    #include <OpenGL/glu.h>
    #include <OpenGL/glext.h>
#else
    #include <GL/glu.h>
    #include <GL/glext.h>
    #include <GL/glx.h>
    #include <GL/glxext.h>
    #define glXGetProcAddress(x) (*glXGetProcAddressARB)((const GLubyte*)x)
#endif

#include <math.h>
#include <time.h>
#include <unistd.h>


Now one thing in there that seems strange is the glXGetProcAddress definition.  For the sake of keeping the text down I've placed that there now, but we'll get to it later.  So now let's write our main function.  In it what we'll need to do is setup our SDL video output to use OpenGL hardware acceleration, and then go into our infinite loop.  So far that's three functions.  When setting up SDL with OpenGL we need to do the following:

  1. Initialize the video system
  2. Tell the system to call a specific SDL function atexit()
  3. Get optimal video settings
  4. Set some OpenGL attributes
  5. Get the framebuffer/drawing surface
  6. Setup our OpenGL viewport and projection mode ( Orthographic or Perspective)
Here's the source code to do that:

//-----------------------------------
// some globals
#define DESIRED_FPS 60.0

SDL_Surface* gDrawSurface = NULL;
int width = 800;
int height = 600;

//-----------------------------------
// Function prototypes
void InfLoop();
void SetupSDL();

//-----------------------------------
int main(int argc, char** argv)
{
    SetupSDL();
    InfLoop();
    return 0;
}


//-----------------------------------
// Setup SDL and OpenGL
void SetupSDL(void)
{
    // init video system
    const SDL_VideoInfo* vidinfo;
    if( SDL_Init(SDL_INIT_VIDEO) < 0 )
    {
        fprintf(stderr,"Failed to initialize SDL Video!\n");
        exit(1);
    }

    // tell system which funciton to process when exit() call is made
    atexit(SDL_Quit);

    // get optimal video settings
    vidinfo = SDL_GetVideoInfo();
    if(!vidinfo)
    {
        fprintf(stderr,"Coudn't get video information!\n%s\n", SDL_GetError());
        exit(1);
    }

    // set opengl attributes
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,        5);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,      5);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,       5);
#ifdef __APPLE__
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,      32);
#else
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,      16);
#endif
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,    1);

    // get a framebuffer
    gDrawSurface = SDL_SetVideoMode(width,height,vidinfo->vfmt->BitsPerPixel,
        SDL_OPENGL);

    if( !gDrawSurface )
    {
        fprintf(stderr,"Couldn't set video mode!\n%s\n", SDL_GetError());
        exit(1);
    }

    // set opengl viewport and perspective view
    glViewport(0,0,width,height);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective( 120, 4.0f / 3.0f, .00001, 100);
    glMatrixMode(GL_MODELVIEW);

}

//-----------------------------------
// Infinite Loop
void InfLoop()
{
    //--- infinite loop with event queue processing
    SDL_Event event;
    while(1)
    {
        while( SDL_PollEvent( &event ))
        {
            switch( event.type )
            {
            case SDL_QUIT:
                exit(0);
                break;
            }

        } // -- while event in queue

    } // -- infinite loop
}


One little quirky thing that's been happening to me, but may not for you is that the SDL_GL_DEPTH_SIZE attribute doesn't work on my Mac when I set the value to 16, but works just fine when I set it to 32.  This is great, but we won't be seeing anything because nothing is being drawn!  It's up to you now to make a new function and then incorporate it into the main loop.  Assuming you make a function called DrawScene(), then you would just add it to the main loop like so:

//-----------------------------------
// Infinite Loop
void InfLoop()
{
    //--- infinite loop with event queue processing
    SDL_Event event;
    while(1)
    {
        while( SDL_PollEvent( &event ))
        {
            switch( event.type )
            {
            case SDL_QUIT:
                exit(0);
                break;
            }

        } // -- while event in queue

        // Add call to drawing function here!!!!
        DrawScene()

    } // -- infinite loop
}

Now we need to setup GLSL. 

STEP 4: Setting up GLSL

Mac users, wave your hands in the air because you can just use GLSL now.  No need to get any function pointers or anything, assuming you have OS X 10.4.3 or later.  Linux users, you have a bit more work cut out for you.  This is the step where we end up coming back to our glXGetProcAddress macro.  Our overall plan is as follows

  1. Declare global function pointers that will be used to access GLSL related functions
  2. Implement a function to acquire GLSL related functions, assuming they are there
So now how do we do this?

STEP 4-1:

Since OS X has the functions readily available we have to first setup a preprocess step to ignore our global function pointers that we are going to declare when we compile for the Mac.

#if !defined(__APPLE__)

#endif

Now we need to populate it with our globals.  But what types are they supposed to be?  In OpenGL, if you want to get a function pointer you typically have a typedef that follows this pattern:

PFNGL<INSERT FUNCTION NAME HERE>PROC

Make note that the function name has to be in all capitals.  As an example to create a function pointer to the glCreateProgramObjectARB() function you would declare the variable as such:

PFNGLCREATEPROGRAMOBJECTARBPROC    glCreateProgramObjectARB = NULL;

Below is a set of OpenGL functions you would need for GLSL (NOTE: there are more):

#if !defined(__APPLE__) && !defined(_WIN32)

PFNGLCREATEPROGRAMOBJECTARBPROC     glCreateProgramObjectARB = NULL;
PFNGLCREATESHADEROBJECTARBPROC      glCreateShaderObjectARB = NULL;
PFNGLSHADERSOURCEARBPROC            glShaderSourceARB = NULL;
PFNGLCOMPILESHADERARBPROC           glCompileShaderARB = NULL;
PFNGLGETOBJECTPARAMETERIVARBPROC    glGetObjectParameterivARB = NULL;
PFNGLATTACHOBJECTARBPROC            glAttachObjectARB = NULL;
PFNGLGETINFOLOGARBPROC              glGetInfoLogARB = NULL;
PFNGLLINKPROGRAMARBPROC             glLinkProgramARB = NULL;
PFNGLUSEPROGRAMOBJECTARBPROC        glUseProgramObjectARB = NULL;
PFNGLGETUNIFORMLOCATIONARBPROC      glGetUniformLocationARB = NULL;
PFNGLUNIFORM1FARBPROC               glUniform1f = NULL;

#endif


STEP 4-2:

Now we need to make a function to access those funcitons.  Remember the following line from above:

#define glXGetProcAddress(x) (*glXGetProcAddressARB)((const GLubyte*)x)

Now we are going to use the macro and here is our function:

#ifdef __APPLE__
void SetupGLSLProcs()
{
    //do nothing
}

#elif !defined(_WIN32)

void SetupGLSLProcs()
{
    glCreateProgramObjectARB = (PFNGLCREATEPROGRAMOBJECTARBPROC)
        glXGetProcAddress("glCreateProgramObjectARB");
    glCreateShaderObjectARB = (PFNGLCREATESHADEROBJECTARBPROC)
        glXGetProcAddress("glCreateShaderObjectARB");
    glShaderSourceARB = (PFNGLSHADERSOURCEARBPROC)
        glXGetProcAddress("glShaderSourceARB");
    glCompileShaderARB = (PFNGLCOMPILESHADERARBPROC)
        glXGetProcAddress("glCompileShaderARB");
    glGetObjectParameterivARB = (PFNGLGETOBJECTPARAMETERIVARBPROC)
        glXGetProcAddress("glGetObjectParameterivARB");
    glAttachObjectARB = (PFNGLATTACHOBJECTARBPROC)
        glXGetProcAddress("glAttachObjectARB");
    glGetInfoLogARB = (PFNGLGETINFOLOGARBPROC)
        glXGetProcAddress("glGetInfoLogARB");
    glLinkProgramARB = (PFNGLLINKPROGRAMARBPROC)
        glXGetProcAddress("glLinkProgramARB");
    glUseProgramObjectARB = (PFNGLUSEPROGRAMOBJECTARBPROC)
        glXGetProcAddress("glUseProgramObjectARB");
    glGetUniformLocationARB = (PFNGLGETUNIFORMLOCATIONARBPROC)
        glXGetProcAddress("glGetUniformLocationARB");
    glUniform1f = (PFNGLUNIFORM1FARBPROC)
        glXGetProcAddress("glUniform1fARB");
}

#endif

All you have to do is add a call to this function after we setup SDL.

//-----------------------------------
int main(int argc, char** argv)
{
    SetupSDL();
    SetupGLSLProcs();
    InfLoop();
    return 0;
}


STEP 5: Have Fun!

So now we've got everything settled and it's up to you to learn how to use GLSL.  At this point you can do some cool things, and don't worry if it looks awesome.  Just mess around with shaders as much as you can.  Follow Andy's notes on how to set the shaders, compile, link, use, etc.