OpenGL

From LD Smith Games Workshop
Jump to navigation Jump to search

I am attempting to integrate OpenGL into the code, so that the game uses hardware graphics acceleration. Hopefully, this will improve some of the framerate issues.

This will also allow me to use Fraps to make videos of the game to post to YouTube.

Below, I have documented the process I went through to enable OpenGL, including all of the issues I came across.

Get code to compile with a single line of OpenGL

I can compile a program using #include "SDL_opengl.h", but I am getting errors about missing functions when an OpenGL method is called.


I installed the OpenGL libraries in cygwin, so I'm not sure if those were necessary:

Opengl cygwin.jpg


Installing these files may help

http://www.libsdl.org/extras/win32/cygwin/


Found out that I was using the wrong parameters to compile. I should have been using this:

gcc test.c `sdl-config --libs --cflags` -lglut32 -lglu32 -lopengl32

Display a bitmap to the screen

I got a simple square BMP to display in OpenGL:

Opengl square.jpg

Added Tux BMP:

Opengl tux.jpg

Tux on an image background:

One of the vertices was off, which made the background skewed.

Opengl tuxbkg.jpg

Moving Block

Next Step is to get the player block moving

Opengl tux02.jpg

Pseudo Code:

 INTEGER playerX
 INTEGER playerY
 INTEGER playerVelocityX
 INTEGER playerVelocityY
 
 Setup_SDL
 Setup_OpenGL
 Load_Bitmaps
 
 GameLoop {
 
   While (User hasn't Quit) {
     IF (User presses up) THEN
       playerVelocityY = - PLAYER_SPEED
 
     IF (User presses down) THEN
       playerVelocityY = PLAYER_SPEED
 
     IF (User presses left) THEN
       playerVelocityX = - PLAYER_SPEED
 
     IF (User presses right) THEN
       playerVelocityX =  PLAYER_SPEED
 
     playerX = playerX + playerVelocityX
     playerY = playerY + playerVelocityY
 
     Render_Screen
   }
 }


Change BMP to PNG

Next step is to replace the BMP with a PNG

Some "gotchas"

  • Add "IMG_Init(IMG_INIT_PNG);" to the program's startup code
  • Replace "SDL_LoadBMP" with "IMG_Load"
  • Add -lSDL_image on the compile line (Makefile or go script)
  • Add required DLLs to the working directory (libpng12-0.dll, zlib1.dll, SDL_image.dll, etc)
  • If the PNG will not display, try passing a BMP to IMG_Load to see if it works

Got SDL and OpenGL to accept the PNG image with transparency, but there are display issues:

Opengl tux03.jpg

PNG with transparency disabled, and background color saved. Basic image is correct, but some discoloration.

Opengl tux04.jpg

Got the non-transparent PNG to display correctly. The problem was with the glTeximage2D line. The "format" parameter was set to "GL_BGR" when it should have been "GL_RBG".

Apparently, changing the format parameter to GL_RBG (from GL_BGR) will make BMPs appear discolored (cyan for yellow).

Opengl tux05.jpg

Another interesting note I found was that all textures MUST be a power of 2 (2, 4, 8, 16, 32, 64, 128, 256, ...) in order to work with OpenGL.

After testing on two Windows Vista machines, I've found that the "texture size must be a power of 2" is HARDWARE dependent. On my desktop system, it will handles texture not a power of 2, but my laptop (running the SAME operating system) will only display those images that are a power of 2 (everything else is black).


Got the transparent PNG image to display correctly. I changed the "format" parameter in the glTeximage2D (noted above) to "GL_RBGA". However, the transparent pixels show up as white instead of the background color.

Opengl tux06.jpg

The image now displays correctly with transparency! I had to change the "internalformat" parameter for glTeximage2D from "3" to "GL_RBGA".

The methods glEnable(GL_ALPHA_TEST); and glAlphaFun(GL_GREATER, 0.5); must also be called to enable transparency.

Opengl tux07.jpg

Now I just need to write a standard method for generating textures, and I think I will be ready to integrate the code into the main game code.

Below is my test code:

 #include "SDL.h"
 #include "SDL_image.h"
 #include "SDL_opengl.h"
 #include <stdio.h>
 void gameLoop();
 void drawScene();
 
 int iPlayerX = 0;
 int iPlayerY = 0;
 int iPlayerVelX = 0;
 int iPlayerVelY = 0;
 GLuint texture;
 GLuint texture_bkg;
 
 int main(int argc, char *argv[]) {
     SDL_Surface *screen;
     if ( SDL_Init(SDL_INIT_VIDEO) != 0 ) {
         printf("Unable to initialize SDL: %s\n", SDL_GetError());
         return 1;
     }
     SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
     screen = SDL_SetVideoMode( 1024, 768, 16, SDL_OPENGL );
     IMG_Init(IMG_INIT_JPG|IMG_INIT_PNG);
     if ( !screen ) {
                 printf("Unable to set video mode: %s\n", SDL_GetError());
                 return 1;
         }
         glClearColor( 0, 0, 0, 0 );
         glEnable( GL_TEXTURE_2D ); // Need this to display a texture
     glViewport( 0, 0, 1024, 768 );
     glMatrixMode( GL_PROJECTION );
     glLoadIdentity();
     glOrtho( 0, 1024, 768, 0, -1, 1 );
     glMatrixMode( GL_MODELVIEW );
     glLoadIdentity();
     glEnable(GL_ALPHA_TEST);
     glAlphaFunc(GL_GREATER, 0.5);
 
     SDL_Surface *surface;
     SDL_Surface *surface_bkg;
 //    if ( (surface = SDL_LoadBMP("image.bmp")) ) {
 //    if ( (surface = SDL_LoadBMP("tux.bmp")) ) {
 //    if ( (surface = IMG_Load("tux.png")) ) {
 //    if ( (surface = SDL_LoadBMP("tux1a.bmp")) ) {
 //    if ( (surface = IMG_Load("tux1a.bmp")) ) {
     if ( (surface = IMG_Load("tux.png")) ) {
         glGenTextures( 1, &texture );
         glBindTexture( GL_TEXTURE_2D, texture );
         glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
         glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
         glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0,
                       GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels );
     } else {
         printf("SDL could not load image: %s\n", SDL_GetError());
         SDL_Quit();
         return 1;
     }
     if ( surface ) {
         SDL_FreeSurface( surface );
     }
     surface_bkg = SDL_LoadBMP("bkg.bmp");
     glGenTextures(1, &texture_bkg);
     glBindTexture(GL_TEXTURE_2D, texture_bkg);
     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
     glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
     glTexImage2D( GL_TEXTURE_2D, 0, 3, surface_bkg->w, surface_bkg->h, 0,
                       GL_BGR, GL_UNSIGNED_BYTE, surface_bkg->pixels );
     SDL_FreeSurface(surface_bkg);
     gameLoop();
     glDeleteTextures( 1, &texture );
     IMG_Quit();
     SDL_Quit();
         return 0;
 } 
 
 void drawScene() {
     glClear( GL_COLOR_BUFFER_BIT );
     glBindTexture( GL_TEXTURE_2D, texture_bkg );
     glBegin( GL_QUADS );
         glTexCoord2i( 0, 0 );
         glVertex3f( 0, 0, 0 );
         glTexCoord2i( 1, 0 );
         glVertex3f( 1024, 0, 0 );
         glTexCoord2i( 1, 1 );
         glVertex3f( 1024, 768, 0 );
         glTexCoord2i( 0, 1 );
         glVertex3f( 0, 768, 0 );
     glEnd();
     glBindTexture( GL_TEXTURE_2D, texture );
     glBegin( GL_QUADS );
         glTexCoord2i( 0, 0 );
         glVertex3f( iPlayerX, iPlayerY, 0 );
         glTexCoord2i( 1, 0 );
         glVertex3f( iPlayerX + 256, iPlayerY, 0 );
         glTexCoord2i( 1, 1 );
         glVertex3f( iPlayerX + 256, iPlayerY + 256, 0 );
         glTexCoord2i( 0, 1 );
         glVertex3f( iPlayerX, iPlayerY + 256, 0 );
     glEnd();
     SDL_GL_SwapBuffers();
 }  
   
 void gameLoop() {
   int iKeepLooping;
   SDL_Event event;
   iKeepLooping = 1;
   while (iKeepLooping) {
     while (SDL_PollEvent( &event) ) {
       switch(event.type) {
         case SDL_KEYDOWN:
           if (event.key.keysym.sym == SDLK_LEFT) {
             iPlayerVelX = -2;
           }
           if (event.key.keysym.sym == SDLK_RIGHT) {
             iPlayerVelX = 2;
           }
           if (event.key.keysym.sym == SDLK_UP) {
             iPlayerVelY = -2;
           }
           if (event.key.keysym.sym == SDLK_DOWN) {
             iPlayerVelY = 2;
           }
           if (event.key.keysym.sym == SDLK_q) {
             iKeepLooping = 0;
           }
           break;
         case SDL_KEYUP:
           iPlayerVelX = 0;
           iPlayerVelY = 0;
           break;
       }  
     }
     iPlayerX += iPlayerVelX;
     iPlayerY += iPlayerVelY;
     drawScene();
     }
   }

Adding Other Objects

I added a snowman that simply moves in a horizontal motion and bounces off the window border.

Now I have three OpenGL textures.

Opengl tux09.jpg

Modularizing Code

With the three textures, I had texture initialization code that was repeated three times.

I pulled all of that code out into its own function, so now I just simply call loadPNGTexture("filename.png") to load the texture. The function had to be written specific for PNGs, since the RGB/BGR format issue with BMPs.

When I tried using my new method, the entire program crashed. I found that the reason was because I converted the background from a BMP to a PNG, but I didn't add a transparent layer. Apparently it is REALLY important to make sure you have a transparent layer if you specify RGBA as a parameter to glTexImage2D.

 GLuint loadPNGTexture(char *strFile) {
   SDL_Surface *surface;
   GLuint giTexture;
   if ( (surface = IMG_Load(strFile)) ) {
       glGenTextures( 1, &giTexture );
       glBindTexture( GL_TEXTURE_2D, giTexture );
       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
       glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
       glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, surface->w, surface->h, 0,
                     GL_RGBA, GL_UNSIGNED_BYTE, surface->pixels );
   }
   if ( surface ) {
       SDL_FreeSurface( surface );
   }
   return giTexture;
 }


Opengl tux08.jpg


Wrote a blit texture method to replace all the repeated OpenGL glVertex calls.

I could have had all the vertices start at (0, 0) and use glTranslate to (x, y). Not sure which way is better, but I know that to rotate an object, the pivot point is always at (0, 0)

 void blitTexture(GLuint img, int x, int y, int w, int h) {
   glLoadIdentity();
   glBindTexture( GL_TEXTURE_2D, img );
   glBegin( GL_QUADS );
     glTexCoord2i( 0, 0 );
     glVertex3f( x, y, 0 );
     glTexCoord2i( 1, 0 );
     glVertex3f( x + w, y, 0 );
     glTexCoord2i( 1, 1 );
     glVertex3f( x + w, y + h, 0 );
     glTexCoord2i( 0, 1 );
     glVertex3f( x, y + h, 0 );
   glEnd();
 }


Wrote an OpenGL init function. I think I'm about ready to plug this into the main game code. I'm planning on just adding the file to the project directory (name it draw.c or something), then replace all the SDL_Surfaces in the game with GLuint, and replace the SDL_BlitSurface calls with calls to the blitTexture method I wrote.

 void Opengl_Init(SDL_Surface *screen) {
   SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
   screen = SDL_SetVideoMode( 1024, 768, 16, SDL_OPENGL );
   glClearColor( 0, 0, 0, 0 );
   glEnable( GL_TEXTURE_2D ); // Need this to display a texture
   glViewport( 0, 0, 1024, 768 );
   glMatrixMode( GL_PROJECTION );
   glLoadIdentity();
   glOrtho( 0, 1024, 768, 0, -1, 1 );
   glMatrixMode( GL_MODELVIEW );
   glLoadIdentity();
   glEnable(GL_ALPHA_TEST);
   glAlphaFunc(GL_GREATER, 0.5);
 }