SDL Programming in Linux: Getting Started with OpenGL
Posted On June 2, 2007 by Ramdas S filed under
SDL is the foundation on which a game could be built without much ado. However, SDL is not complete in itself. It just provides services using which interaction between various components of a game/simulation as well as the games interaction with OS becomes seamless. If there are no components to utilize these services, then the services become just a 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 would be concentrating on the rendering component and its interaction with SDL. I shall cover AI component in the future.
Though SDL supports other graphics libraries, its usage with OpenGL is more common. The reason is that SDL and OpenGL fit together like different parts of a puzzle. So, most of the time, the rendering component or the rendering sub-system (I would 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 chapter as well as those in the near future will delve into different aspects of OpenGL along with how SDL helps in creating a good framework for future purposes. In this part, I will be providing the whys and wherefores of OpenGL. The first section focuses on the whys and wherefores while the second section details the steps in creating a basic application. The second section also relates to creating a framework using SDL that can be used in the future. Simple OpenGL routines to test the framework would also be covered. That is the agenda for this discussion.
OpenGL: What is it?
If this question is asked, then the most common answer one would get is that OpenGL is a graphics library in C. However, this is a misconception. In fact, OpenGL is a low-level graphics library specification. Just like J2EE, OpenGL is nothing but a set of platform neutral, language independent and vendor neutral APIs. These APIs are procedural in nature. In simple terms, this means that a programmer does not describe the object and appearances; instead he/she details the steps through which an effect or an appearance can be achieved. These steps comprise many OpenGL commands that include commands to draw graphic primitives such as a point, line, polygon, etc. in three dimensions.
OpenGL also provides commands and procedures to work with lighting, textures, animations, etc. One important aspect to bear in mind is that OpenGL is meant for rendering. Hence, it does not provide any APIs for working with I/O management, window management, etc. That is where SDL comes into the picture. To understand how OpenGL renders, it is important to understand how it interfaces between the graphics application and graphics card. So here we go…
The interfacing works at three levels. They are:
1. Generic Implementation;
2. Hardware Implementation; and
3. OpenGL pipeline.
While Generics deal with providing a rendering layer that sits on top of the OS specific rendering system, hardware implementation provides direct hardware interfacing and pipeline works at taking the command and giving it to the hardware after processing. Let us look into the details.
Generic Implementation:
The other term for generic implementation is software rendering. If the system can display a generated graphics, then, technically speaking, generic implementation can run anywhere. The place occupied by generic implementation is between the program and software rasterizer. Pictorially, it should be as in figure 1.
Fig. 1

It is clear from the diagram that generic implementation takes the help of OS specific APIs to draw the generated graphics. For example, on Windows it is GDI whereas on *nix systems it is XLib. The generic implementation on Windows is known as WOGL and that on Linux as MESA 3D.
Hardware Implementation:
The problem with generic implementation is that it depends on the OS for rendering and hence the rendering speed and quality differs from OS to OS. This is where hardware implementation steps in. In this case, calls to the OpenGL APIs are passed directly to the device driver (typically, the AGP card’s driver). The driver directly interfaces the graphics device instead of routing it through the OS specific graphics system (refer figure 2).
Fig. 2

The functioning of hardware interfacing is totally different from that of generic implementation, which is evident from the diagram. Interfacing with the device driver directly enhances both the quality as well as speed of the rendered graphics.
OpenGL Pipeline:
In essence, the term pipeline is a process, i.e. the finer steps of a conversion or transformation. In other words, a process such as conversion can be broken down into finer steps. These steps together form the pipeline.
In a graphics pipeline, each stage or step refines the scene. In case of OpenGL, it is vertex data. Whenever an application makes an API call, it is placed at command buffer along with commands, texture, vertex data, etc. On flushing of this buffer (either programmatically or by the driver), the contained data is passed on to the next step where calculation intensive lighting and transformations are applied. Once this is completed, the next step creates colored images from the geometric, color and texture data. The created image is placed in the frame buffer, which is the memory of the graphic device a.k.a the screen. Diagrammatically, this would be as shown in figure 3.
Fig. 3

Though this is a simplified version of the actual process, the above detailed process provides an insight into the working of OpenGL. That brings this section to conclusion. However, one question still remains - what are the basic steps in creating an OpenGL application? The answer is provided in the next section.
OpenGL: Basic steps towards application
Till now, the theory of OpenGL was discussed. Let us now see how to put it into use. To draw any shape on to the screen, there are three main steps, namely:
1. Clearing the screen;
2. Resetting the view; and
3. Drawing the scene.
Of these, the third step consists of multiple sub-steps.
Clearing the Screen:
To set the stage for drawing, clearing the screen is a must. This can be done by using the glClear() command, which 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:
a. GL_COLOR_BUFFER_BIT
Indicates the buffers currently enabled for color writing have to be cleared.
b. GL_DEPTH_BUFFER_BIT
This is used to clear the depth buffer.
c. GL_ACCUM_BUFFER_BIT
If the accumulation buffer has to be cleared, use this.
d. GL_STENCIL_BUFFER_BIT
This is passed as a 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(), which 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 required buffers have been cleared. However, the actual model of the image is based on the view. View can be considered as the matrix representation of the image. So, to draw, this matrix has to be set to identity matrix. This is done using glLoadIdentity(). The statement would be:
glLoadIdentity();
Drawing the Scene:
To draw the scene, we need to tell OpenGL two things:
a. Start and stop the drawing:
These commands are issued through calls to glBegin() and glEnd(). The glBegin() takes one parameter - the type of shape to be drawn. To draw using three points use GL_TRIANGLES, GL_QUADS to use four points and GL_POLYGON to use multiple points. The glEnd() 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.
b. Issue the drawing commands:
In the drawing commands, vertex data is specified. These commands are of the type glVertex*f() where * corresponds to the number of parameters - 2 or 3. 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 there is to about drawing objects with OpenGL. In the next section, these commands would be used to put the SDL-based framework to test.
SDL-based framework: Creation and Testing
Till now I have discussed various APIs of SDL. Now it is time to put them together so that we can start working with OpenGL.
First the includes:
#include <stdio.h>// Include the Standard IO Header
#include <stdlib.h>// and the standard lib header
#include <string.h>// and the string lib header
#include <GL/gl.h>// we're including the openGL header
#include <GL/glu.h>// and the glu header
#include <SDL.h>//and the SDL header
The global variables:
bool isProgramLooping; //we're using this one to know if the program
//must go on in the main Loop
SDL_Surface *Screen;
Now for the common functionalities - initialization, termination and full screen toggling. See code 1.
Code 1
bool Initialize(void)// Any Application & User Initialization Code Goes Here
{
AppStatus.Visible= true; // At The Beginning, Our App 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: %s\n", SDL_GetError() );
return false;
}
return true; // Return TRUE (Initialization Successful)
}
void Deinitialize(void) // Any User Deinitialization Goes Here
{
return; // We Have Nothing To Deinit Now
}
void TerminateApplication(void)// Terminate The Application
{
static SDL_Event Q;// We're Sending A SDL_QUIT Event
Q.type = SDL_QUIT;// To The SDL Event Queue
if(SDL_PushEvent(&Q) == -1) // Try Send The Event
{
printf("SDL_QUIT event can't be pushed: %s\n", SDL_GetError() ); exit(1); // And Exit
}
return; // We're Always Making Our Functions Return
}
void ToggleFullscreen(void) // Toggle Fullscreen/Windowed (Works On Linux/BeOS Only)
{
SDL_Surface *S; // A Surface To Point The Screen
S = SDL_GetVideoSurface(); // Get The Video Surface
if(!S || (SDL_WM_ToggleFullScreen(S)!=1)) // If SDL_GetVideoSurface Failed, Or We Can't Toggle To Fullscreen
{
printf("Unable to toggle fullscreen: %s\n", SDL_GetError() ); // We're Reporting The Error, But We're Not Exiting
}
return; // Always Return
}
Next come the OpenGL parts - Creating an OpenGL window. In other words, initializing OpenGL. But, initializing needs updating as it is created. Hence, the reshape function. Refer code 2.
Code 2
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 doublebuffering
if(!(Screen = SDL_SetVideoMode(W, H, B, F))) // We're Using SDL_SetVideoMode To Create The Window
{
return false; // If It Fails, We're Returning False
}
SDL_FillRect(Screen, NULL, SDL_MapRGBA(Screen->format,0,0,0,0));
ReshapeGL(SCREEN_W, SCREEN_H); // we're calling reshape as the window is created
return true; // Return TRUE (Initialization Successful)
}
I will be discussing the APIs used in resize function in the forthcoming issue. Next is the draw function. It also contains the test code. Check out code 3.
Code 3
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 for the main(). Refer code 4. It also lists the keyboard handling code.
Code 4
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; // our 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)// init the sdl library, the video subsystem
{
printf("Unable to open SDL: %s\n", SDL_GetError() );// if sdl can't be initialized
exit(1);
}
atexit(SDL_Quit);// sdl's been init, now we're 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: %s\n", SDL_GetError() );
exit(1);
}
if(!InitGL(Screen))// we're calling the opengl init function
{
printf("Can't init GL: %s\n", SDL_GetError() );
exit(1); }
if(!Initialize()) {
printf("App init failed: %s\n", 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:// it's a quit event?
{
isProgramLooping = false;
break; }
case SDL_VIDEORESIZE:// It's a RESIZE Event?
{
ReshapeGL(E.resize.w, E.resize.h);
break; // And Break
}
case SDL_KEYDOWN:// Someone Has Pressed A Key?
{
Keys = SDL_GetKeyState(NULL); break; }
}
}
Draw3D(Screen); // Do The Drawings!
SDL_GL_SwapBuffers(); // and swap the buffers (we're double-buffering, remember?)
}
}
}
Deinitialize();
exit(0); // And finally we're out, exit() will call sdl_quit
return 0;// we're standard: the main() must return a value
}
That brings us to the end of this discussion. The framework that has been developed will just work as the foundation for developing functionalities like lighting, texture mapping, animation and so on. The next topic will focus on using timers in animating the triangle just drawn. Till next time…
The author can be reached at: a_p_rajshekhar@yahoo.co.in.
