Topic : Singleton Texture Manager
Author : Chris Smith
Page : 1

A Singleton Texture Manager for OpenGL
by Chris Smith


Abstract

This article will discuss the need for, and creation of, a Singleton Texture Manager in OpenGL. By using a Singleton, we ensure that the Texture Manager class has only one instance, which we can use to handle all texture loading/deleting operations. If implemented properly, this tool could be a great benefit to game programmers, as it gives a viable and simple means for managing a large number of textures at once. For some beginning programmers, this may be their first glimpse of the Singleton pattern.

Introduction

The graphics of today's games have often defined the difference between success and failure. Developers are often questioning themselves how much, how fast, how detailed their graphics should be. Oftentimes, the textures of a game can determine how realistic the game looks and feels. Luckily, programmers have access to API's like DirectX and OpenGL that aid in optimizing texture rendering in complex scenes. The problem that we run into, however, is that we need a way to keep track of all of the textures in the game scenes. In a simple game with a wall, a floor, and a player skin, you shouldn't have many problems, but when changing levels, loading a WAD file, or applying skins to every new model that enters the scene, you need some way to keep track of the textures that are being used or you run into problems. In this article, we will discuss how we can combat these problems through a Singleton Texture Manager with the OpenGL API.

OpenGL and Textures Overview

Every texture in OpenGL is referenced through a unique ID through the glBindTexture() function. Now, you can either specify this unique ID by yourself and have the responsibility of handling all new texture ID's, or you can let OpenGL find a currently unassigned ID with a call to the glGenTextures() function. With the latter approach, you only need to keep track of every ID the function returns.

Whichever method of texture ID generation you choose, you're still left with a seemingly random list of numbers coming and going as quickly as you can say 'jibs'. Even worse, when you're finished using a texture with a given map/object/effect, you need to find the texture's ID again and call the glDeleteTextures() function, which will recover the video memory that was allocated by the texture for re-use.

Although keeping track of a list of numbers may not seem like a real problem, if you have ever had code like this:

glDeleteTextures (1, &m_Player->GetProjectiles ().rocket [nDeadRocket].GetTexture ().nID);

then you might change your mind about how to handle a list of numbers.

A Statement

What we need is a class or object, which will sit in memory and manage every texture going in or out of our program. It will keep a list of all texture ID's, provide the functionality to delete textures, and also the functionality to give us a list of the number of textures loaded and their unique ID's. If the application sticks to a strict texture ID naming convention, such as:

Map/World Textures > 256
Skin Textures < 256
Effect Textures < 64

then we could easily look up and delete every texture of a specific type automatically.

This seems simple enough, but what if we load a texture in some remote function in the nether-regions of our code? We simply cannot create an instance of this class as a member variable for one of the main systems of our game and hope we put it in a place where it will have the most effectiveness. Even worse would be a series of texture managers keeping their own list of texture ID's.

A Possible Solution

Enter the Singleton. A Singleton is an object that has at most one instance of itself in memory at any given time. Voodoo witchcraft?! Arcane magic's!? Not really. In code, a Singleton looks like this:

// In the header file
class CSingleton
{
  static class CSingleton *m_Singleton;
  static Csingleton *GetSingleton (void);
};

// In the implementation file
CSingleton *CSingleton::m_Singleton = 0;

CSingleton *CSingleton::GetSingleton (void)
{
  if (m_Singleton == 0)
    m_Singleton = new CSingleton;
  return m_Singleton;
}


By keeping a pointer itself as a static variable, the Singleton object isn't 're-created' with every instance of the class. So with the first call to GetSingleton, a Csingleton object pointer is allocated in memory, or the static variable m_Singleton. Does that make any sense? If not, don't worry, these links might help explain things a little better...

http://www.codeproject.com/tips/singleton.asp
http://www.inquiry.com/techtips/cpp_pro/10min/10min0200.asp

Well now that you are a complete expert on the concept of a Singleton, let's start working on our OpenGL texture manager, shall we?

The Code

class CTextureManager {
public :
  CTextureManager (void);
  ~CTextureManager (void);
  static CTextureManager &GetSingleton (void);

private : // This is called automaticaly! Don't do it yourself!
  static void Initialize (void);
public :
  static void Destroy (void);

public : // Usage / Implumentation
  int LoadTexture (const char *szFilename, int nTextureID = -1);
  int LoadTextureFromMemory (UBYTE *pData, int nWidth, int nHeight, int nBPP, int nTextureID = -1);

  void FreeTexture (int nID);
  void FreeAll (void);

public : // Debug / Utilitarian
  char *GetErrorMessage (void);
 
  int GetNumTextures (void);
  int GetAvailableSpace (void);
  int GetTexID (int nIndex);

private :
  static CTextureManager *m_Singleton;

  UBYTE *LoadBitmapFile (const char *filename, int &nWidth, int &nHeight, int &nBPP);
  UBYTE *LoadTargaFile (const char *filename, int &nWidth, int &nHeight, int &nBPP);

  int GetNewTextureID (int nPossibleTextureID);  // get a new one if one isn't provided
  bool CheckSize (int nDimension);

private :
  char szErrorMessage [80];  // they arn't bugs, their features!
  int nNumTextures;
  int nAvailable;            // available space in the nTexID array
  int *nTexIDs;
};


Get the full class header and implementation here.

As you can see, the code itself is very self-explanatory. In fact, the only crucial functions are LoadTexture(), LoadTextureFromMemory(), FreeTexture(), and FreeAll(). The first two (as you would guess) load and register new textures into OpenGL, while the last two free textures from memory.

Within those four methods lies the code to handle the variables nNumTextures, nAvailable and nTexIDs. These variables respectively hold the number of loaded textures, amount of available slots in the texture ID list, and the lift itself.

The class itself has the functionality to load Targa (TGA) files and 24-bit color Bitmaps (BMP), however, it can be easily expanded to incorporate other file types via new methods for loading the graphics files internally or via the LoadTextureFromMemory() function. Either way, this Singleton class will all but "hold your hand" when dealing with textures in OpenGL.

The most crucial part of the code is that all of the 'real' data is in the CTextureManager::m_Singleton object. In other words, if you want to access nNumTextures, you actually want m_Singleton->nNumTextures! This is all because the class is still a Singleton, and to be such, all of the data must be stored in one place: the Singleton pointer.

In Conclusion

The topics covered in this article may seem overwhelming at first, but keep in mind that having a Singleton texture manager is a good idea in the long run. As the number of graphics and textures used in your game begins to grow exponentially, a solid ID convention and an object to oversee texture operations will save MANY hours of bug tracking.

Questions? Comments? Flames?
Christopher.Smith@Trinity.edu
www.cs.trinity.edu/~csmith8
Chris Smith


Page : 1