Game Programming with SDL: Getting Started with OpenGL

SDL helps to provide a framework on which to build games. OpenGL is the graphic library most commonly used with this framework. This article shows you how these two technologies work together.

SDL is the foundation on which a game can be built without much ado. However, SDL is not complete in itself. It just provides certain services that allow the interaction between various components of a game/simulation, as well as the games interaction with the OS, to become seamless. If there are no components to utilize these services, then these services become just proof of concept.

In a gaming engine, most of the time, these services are required by the rendering and AI components. From this part onwards I will be concentrating on the rendering component and its interaction with SDL. I will be covering the AI component in the future.

Though SDL supports other graphics libraries, its usage with OpenGL is more common. The reason for this is that SDL and OpenGL fit like parts of a puzzle. So most of the time, the rendering component, or the rendering sub-system (I will be using this term from now onwards) of a gaming engine is built upon OpenGL. Hence understanding OpenGL is a must to build a good rendering sub-system.

This part and the articles coming in the near future will detail the different aspects of OpenGL along with how SDL helps in creating a good framework for future purposes. The next section will detail the steps necessary in creating a basic application, while the third section will cover the development of a framework using SDL that can be used in the future. In the last section, I will use simple OpenGL routines to test the framework. That is the agenda for this discussion.

{mospagebreak title=OpenGL: Basic Steps}

Now that I’ve discussed the theory behind OpenGL, let’s see how to put it to use. To draw any shape onto the screen, there are three main steps. They are:

  1. Clearing the screen
  2. Resetting the view
  3. Drawing the scene

Of these the third step consists of multiple sub-steps. I’ll cover the details next. 

Clearing the Screen

To set the stage for drawing, clearing the screen is a must. This can be done by using the glClear() command. This command clears the screen by setting the values of the bit plane area of the view port. glClear() takes a single argument that is the bitwise OR of several values indicating which buffer is to be cleared. The values of the parameter can be:

  • GL_COLOR_BUFFER_BIT, which indicates the buffers currently enabled for color writing have to be cleared.
  • GL_DEPTH_BUFFER_BIT, which is used to clear the depth buffer.
  • GL_ACCUM_BUFFER_BIT, which is used if the accumulation buffer has to be cleared. 
  • GL_STENCIL_BUFFER_BIT, which is passed as parameter when the stencil buffer has to be cleared.

Next, the color to be used as the erasing color is specified. This can be done using glClearColor(). This command clears the color buffers specified. That means when the specified color buffers are cleared the screen is recreated accordingly. So to clear the depth buffer and set the clearing color to blue the statements would be:

glClear(GL_DEPTH_BUFFER_BIT);
glClearColor(0.0f,0.0f,1.0f,0.0f);

 Resetting the View

The background and the required buffers have been cleared. But the actual model of the image is based on the view. The view can be considered the matrix representation of the image. In order to draw this matrix, it has to be configured as an identity. This is done using glLoadIdentity(). The statement would be:

glLoadIdentity();

Drawing the Scene

To draw the scene we tell OpenGL to do two things: start and stop the drawing, and issue the drawing commands.

Commands to start and stop the drawing are issued through the calls to glBegin() and glEnd(). The glBegin() command takes one parameter, namely the type of shape to be drawn. To draw using three points use GL_TRIANGLES. Use  GL_QUADS for points and GL_POLYGON for multiple points. The glEnd() command tells OpenGL to stop the drawing. For example, to draw a triangle the statements would be:

glBegin(GL_TRIANGLES);
:
:
glEnd();

The drawing commands come between these commands.

Within the drawing commands, vertex data is specified. These commands are of the type glVertex*f() where * corresponds to the number of parameters, either two or three. Each call creates a point and then connects it with the point created with the earlier call. So to create a triangle with the coordinates (0.0, 1.0, 0.0), (-1.0,-1.0, 0.0) and (1.0,-1.0, 0.0) the commands would be:

glBegin(GL_TRIANGLES);
   glVertex3f( 0.0f, 1.0f, 0.0f);
   glVertex3f(-1.0f,-1.0f, 0.0f);
   glVertex3f( 1.0f,-1.0f, 0.0f);

glEnd();

That’s all we need to cover about drawing objects with OpenGL. In the next section, these commands will be used to put the SDL-based framework to the test.

{mospagebreak title=SDL-Based Framework: Creating and Testing}

Up to now I have discussed SDL’s various APIs. Now it’s time to put them together so that working with OpenGL and SDL becomes easy. So here we go. The framework is based upon NeHe’s excellent framework for SDL.net — the .Net port of SDL.

First the includes:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/gl.h>  // The OpenGL
#include <GL/glu.h> // and OpenGL utility
#include <SDL.h>    // and SDL headers

Here are the global variables which save the state of the surface and the program:

bool isProgramLooping;//This one is being used to know if the program
                     
//needs to be continued or exited
SDL_Surface *Screen;

Now the common functionalities: initialization, termination and full-screen toggling.

bool Initialize(void)// Any Application & User Initialization Code would be here
{
  
AppStatus.Visible= true; // When program begins, the
                            // application window is
                            // visible
  
AppStatus.MouseFocus= true;// and have both mouse
  
AppStatus.KeyboardFocus = true;// and input focus

   // start of user initialization. These are just examples
  
angle = 0.0f;// Set the starting angle to zero
  
cnt1= 0.0f;// Set the cos(for the x axis) counter to zero
  
cnt2= 0.0f;// Set the sin(for the y axis) counter to zero

   {
    
printf("Cannot load graphic: %sn", SDL_GetError() );
    
return false;
  
}

   return true;
          // return true if initialization is successful
}

void Deinitialize(void) // Any User Deinitialization such as releasing file handles etc has to be performed here
{
  
return;
}

void TerminateApplication(void)// terminate the application
{
  
static SDL_Event Q;// This function sends aSDL_QUIT event
  
Q.type = SDL_QUIT;// to the sdl event queue
  
if(SDL_PushEvent(&Q) == -1) // Sending the event

   {
    
printf("SDL_QUIT event can’t be pushed: %sn", SDL_GetError() ); exit(1);
       // And Exit
  
}

}

void ToggleFullscreen(void)// Toggle Fullscreen/Windowed
                           //(Works On
Linux/BeOS Only)
{
  
SDL_Surface *S; // a surface to point the screen
  
S = SDL_GetVideoSurface(); // gets the video surface
  
if(!S || (SDL_WM_ToggleFullScreen(S)!=1))
      // If SDL_GetVideoSurface Fails, Or if cant toggle to fullscreen
   
{
    
printf("Unable to toggle fullscreen: %sn", SDL_GetError() );
     // only reporting the error, not exiting
  
}

}

{mospagebreak title=Adding OpenGL}

Next come the OpenGL parts — creating an OpenGL window. In other words, this section deals with initializing OpenGL. But the initialization needs updating as it is created. Hence the reshape function :

void ReshapeGL(int width, int height) // reshape the window when it’s moved or resized
{
  
glViewport(0,0,(GLsizei)(width),(GLsizei)(height));
     // reset the current viewport
  
glMatrixMode(GL_PROJECTION);
     // select the projection matrix
  
glLoadIdentity();
     // reset the projection matrix
  
gluPerspective(45.0f,(GLfloat)(width)/(GLfloat)(height),1.0f,100.0f);
     // calculate the aspect ratio of the window
  
glMatrixMode(GL_MODELVIEW);
     // select the modelview matrix
   
glLoadIdentity();
     // reset the modelview matrix
  
return;
}

bool CreateWindowGL(int W, int H, int B, Uint32 F) 
  // This Code Creates Our OpenGL Window
{
  
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );
     // In order to use SDL_OPENGLBLIT we have to
  
SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, 5 );
     // set GL attributes first
  
SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, 5 );
  
SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE, 16 );
  
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
     // colors and double buffering

   if(!(Screen = SDL_SetVideoMode(W, H, B, F)))
     // SDL_SetVideoMode to create the window
  
{
    
return false; // if it fails, return false
  
}

   SDL_FillRect(Screen, NULL, SDL_MapRGBA(Screen->format,0,0,0,0));
  
ReshapeGL(SCREEN_W, SCREEN_H);
     // calling reshape as the window is created
  
return true;
     // return true (initialization successful)
}

I will be discussing the APIs used in the resize function in the next article. Next is the draw function. It also contains the test code:

void Draw3D(SDL_Surface *S) // OpenGL drawing code here
{
  
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// clear screen and depth buffer. Screen color has been cleared at init
  
glLoadIdentity(); // reset the modelview matrix
  
glBegin(GL_TRIANGLES);
    
glVertex3f( 0.0f, 1.0f, 0.0f);
    
glVertex3f(-1.0f,-1.0f, 0.0f);
     glVertex3f( 1.0f,-1.0f, 0.0f);
  
glEnd();

   glFlush(); // flush the gl rendering pipelines

   return;
}

Now we move on to the main(). It contains the keyboard handling code. It checks for each key press and processes it accordingly.

int main(int argc, char **argv)
{
  
SDL_Event E; // and event used in the polling process
  
Uint8 *Keys; // a pointer to an array that will
                // contain the keyboard
snapshot
  
Uint32 Vflags; // video flags

   Screen = NULL;
  
Keys = NULL;
  
Vflags = SDL_HWSURFACE|SDL_OPENGLBLIT;//a hardware 
   // surface
and special openglblit mode
   // so we can even blit 2d graphics in our opengl scene

   if(SDL_Init(SDL_INIT_VIDEO)<0)// initializing the sdl
                         // library, t
he video subsystem

   {
    
printf("Unable to open SDL: %sn", SDL_GetError() );// if sdl can’t //be initialized
    
exit(1);
  
}

   atexit(SDL_Quit);// sdl’s been inited, now making
                    // sure that
sdl_quit will be
                   
// called in case of exit()

   if(!CreateWindowGL(SCREEN_W, SCREEN_H, SCREEN_BPP, Vflags)) // video flags are set, creating the window
  
{
    
printf("Unable to open screen surface: %sn", SDL_GetError() );
    
exit(1);
  
}

   if(!InitGL(Screen))// calling the OpenGL init function
  
{
    
printf("Can’t init GL: %sn", SDL_GetError() );
    
exit(1);
   }

   if(!Initialize())
   {
    
printf("App init failed: %sn", SDL_GetError() ); exit(1);
   }

   isProgramLooping = true;

   while(isProgramLooping)// and while it’s looping
  
{
    
if(SDL_PollEvent(&E))
    
{
      
switch(E.type)// and processing it
      
{

         case SDL_QUIT://check whether it’s a quit event?
        
{
          
isProgramLooping = false;
          
break;
         }

         case SDL_VIDEORESIZE:// or it’s a resize event?
        
{
          
ReshapeGL(E.resize.w, E.resize.h);
          
break; 
         }

         case SDL_KEYDOWN:// check which key has been pressed
        
{
          
Keys = SDL_GetKeyState(NULL); break;
         }
       
}

     }

     Draw3D(Screen);
     SDL_GL_SwapBuffers();
       // and swap the buffers (since double buffering is being used)
 

   }

   Deinitialize();
  
exit(0); // And finally exit() so calling call sdl_quit

   return 0;
}

That brings us to the end of this discussion. This time it was a bit lengthy. But the framework that has been developed will work as the foundation for developing functionalities like lighting, texture mapping, animation and so on. The next topic will be using timers in animating the triangle just drawn. Till next time.

Google+ Comments

Google+ Comments