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 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 would 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 is that SDL and OpenGL fits like 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 part and the articles coming in the near future would be detailing the different aspects of OpenGL along with how SDL helps in creating a good framework for future purposes. In this part I would be providing whys and wherefores of OpenGL. The first section would detail about the whys and wherefores, second section would detail the steps in creating a basic application whereas in the second section I would be creating a framework using SDL that can be used in the future. In the same section, I would also use simple OpenGL routines to test the framework. 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 graphics library in C. However, this is a misconception. In fact, OpenGL is 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 a programmer does not describes the object and appearances instead he/she details the steps through which an effect or an appearance can be achieved. These steps comprises of many OpenGL commands that includes commands to draw graphic primitives such as point, line, polygon etc in the three dimensions. OpenGL also provides commands and procedures to work with lighting, textures, animations etc. One important aspect to keep 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’s where SDL comes into picture. To understand how OpenGL renders, it is important to understand how it interfaces between graphics application and graphics card. So here we go.
The interfacing works at three levels. They are:
1. Generic Implementation
2. Hardware Implementation
3. OpenGL pipeline
While the Generic deals with providing a rendering layer that sits on top of the OS specific rendering system whereas Hardware implementation provides direct hardware interfacing and pipeline works at taking the command and giving it to hardware after processing. Lets look at the details.
1. Generic Implementation:
The other word 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 the Generic implementation is between the program and the software rasterizer. Pictorially it would be:
It is clear from the diagram that the Generic implementation takes the help 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 is MESA 3D.
2. 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 comes. In this case, the 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 OS specific graphics system. Diagrammatically:
The functioning of Hardware Interfacing is totally different from that of Generic Implementation which is evident from the diagram. Since interfacing with the device driver directly enhances both the quality as well as speed of the rendered graphics.
3. OpenGL Pipeline:
In essence, the term pipeline is a process that is 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 alongwith commands, texture, vertex data etc. On flushing of this buffer(either programmatically or by 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 that is the screen. Pictorially this would be:
Though this a simplified version of the actual process, yet the above detailed process provides an insight into the working of OpenGL. This brings this section to conclusion. However one question still remains- what are the basic steps in creating an OpenGL application. That is what next section is about.
OpenGL- Basic Steps towards Application:
Till now theory of OpenGL was discussed. Now lets see how to put it into 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. Following are the details:
1. 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 :
a. GL_COLOR_BUFFER_BIT
It 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 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);
2. Resetting the View:
The back ground and the required buffers have been cleared. But 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();
3. Drawing the Scene:
To draw the scene we to tell OpenGL two things:
a. Start and Stop the drawing:
These commands are issued through the 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 no. of parameters-2 or 3. Each call creates a point and then connects it with the point created with 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 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 & Testing:
Till now I have discussed various APIs of SDL. Now its time to put them together so that working with OpenGL. So here we go.
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 the common functionalities- initialization, termination, full-screen toggling.
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 Funtions 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 comes the OpenGL parts- Creating an OpenGL window. In other words initializing OpenGL. But initializing 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 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 next issue. 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 the main(). It contains the keyboard handling code
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 nitialized
exit(1);
}
atexit(SDL_Quit);// sdl’s been init, now we’re making sure thet 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. This time it was a bit lengthy. But the framework that has been developed just will work as the foundation for developing functionalities like lighting, texture mapping, animation and so on. The next topic would be using timers in animating the triangle just drawn. Till next time.