Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
101 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

6. The CTexture class

Okay, we can load textures and we can draw textures. What more could a programmer want?

A programmer could want a way to keep track of the textures. The CTexture class features a built-in resource manager so a single texture is not loaded more than once. It also keeps track of information about the texture for quick and easy access at runtime.

First off, the class has a struct inside its private scope: LOADEDTEXTURE. This struct contains an IDirect3DTexture9* pointer as well as information about the texture, such as its filename and dimensions. It's declaration is:

//Loaded texture struct
struct LOADEDTEXTURE
{
  int referenceCount;         //# of CTexture instances containing this texture
  IDirect3DTexture9* texture; //The texture
  string sFilename;           //The filename of the texture
  int width;                  //Width of the texture
  int height;                 //Height of the texture
};

You'll notice that the struct keeps a reference count of CTexture instances that use the texture it contains. This is helpful as we can periodically run through the list of loaded textures and delete any unreferenced ones, or even delete the texture as soon as it becomes unreferenced. By default, the CTexture class does not release textures as soon as they become unreferenced, but it's not a particularly taxing job to make it do so. However, I do not recommend it. It would be best to wait until you have some free processing time (such as the loading time between levels) before you start deleting textures. This way you can clear all the unreferenced ones at once without causing any hiccups in the game.

Next up, also in the private scope, we have the list of loaded textures:

//Linked list of all loaded textures
static list <LOADEDTEXTURE*> loadedTextures;

We want it to be static so there is only ever one list, instead of a list in every instance of CTexture. Don't forget to initialize the list outside of the class declaration, as it is static.

Each instance of CTexture needs to know if it has already loaded a texture or not, and which texture that is. This is easily taken care of by adding a flag and LOADEDTEXTURE pointer in the private scope, as well as a constructor in the public scope.

public:

  //Set default member values
  CTexture()
  {
    bLoaded = FALSE;
    texture = NULL;
  }

private:

  BOOL bLoaded;           //Texture loaded flag
  LOADEDTEXTURE* texture; //The texture

Alright, it's time once again to load a texture. The following function looks through the loadedTextures list to see if the requested texture has been loaded. If so, it assigns that to the CTexture. If not, it loads the texture using the LoadTexture() function provided above and adds it to the list before assigning it to the CTexture. This function goes in the public scope of CTexture:

//Load texture from file
int CTexture::Init (string sFilename)
{
  D3DSURFACE_DESC surfaceDesc;
  LOADEDTEXTURE* newTexture;
  list<LOADEDTEXTURE*>::iterator itTextures;

  //Make sure texture is not already loaded
  if (bLoaded)
    return FALSE;

  //Convert filename to lowercase letters
  sFilename = strlwr((char *)sFilename.c_str ());

  //Check if texture is in the loaded list
  for (itTextures = loadedTextures.begin ();
       itTextures != loadedTextures.end ();
       itTextures++)
    if ((*itTextures)->sFilename == sFilename)
    {   
      //Get LOADEDTEXTURE object
      texture = *itTextures;  

      //Increment reference counter
      (*itTextures)->referenceCount++;

      //Set loaded flag
      bLoaded = TRUE;

      //Successfully found texture
      return TRUE;
    }

  //Texture was not in the list, make a new texture
  newTexture = new LOADEDTEXTURE;

  //Load texture from file
  newTexture->texture = LoadTexture ((char*)sFilename.c_str());
    
  //Make sure texture was loaded
  if (!newTexture->texture)
    return FALSE;

  //Get texture dimensions
  newTexture->texture->GetLevelDesc(0, &surfaceDesc);

  //Set new texture parameters
  newTexture->referenceCount = 1;
  newTexture->sFilename = sFilename;
  newTexture->width = surfaceDesc.Width;
  newTexture->height = surfaceDesc.Height;

  //Push new texture onto list
  loadedTextures.push_back (newTexture);

  //Setup current texture instance
  texture = loadedTextures.back();
  bLoaded = TRUE;
 
  //Successfully loaded texture
  return TRUE;
}

You'll notice that the filename is converted to lowercase at the beginning of the function. This is because filenames are not case-sensitive in Windows. We do not want the program to load the same texture twice if sFilename is "hello.jpg" one time and "Hello.JPG" the next.

Next up, a function to call when we're done with a particular instance of the texture. This function doesn't do much besides decrease the reference count of the LOADEDTEXTURE and clear the loaded flag of the CTexture instance. Here it is:

//Unload a texture
int CTexture::Close()
{
  //Make sure texture is loaded
  if (!bLoaded)
    return FALSE;
    
  //Decrement reference counter and nullify pointer
  texture->referenceCount--;
  texture = NULL;

  //Clear loaded flag
  bLoaded = FALSE;

  //Successfully unloaded texture
  return TRUE;
}

So now we can load and clear our texture instances. We still have no way of getting them out of memory. Thats where the following two functions come in handy. The first goes through the list and releases all unreferenced textures. This can safely be called at any time, as none of the textures it releases are in use. The next releases all of the texture in the list. It should only be called at program termination to catch any textures other parts of the program failed to release.

Here is the function that releases all unreferenced textures:

Declaration:

//Release all unreferenced textures
static int GarbageCollect();

Function:

//Release all unreferenced textures
int CTexture::GarbageCollect()
{
  list<LOADEDTEXTURE*>::iterator it;
  list<LOADEDTEXTURE*>::iterator itNext;

  //Go through loaded texture list
  for (it = loadedTextures.begin(); it != loadedTextures.end ();)   
    if ((*it)->referenceCount <= 0)
    {
      //Get next iterator
      itNext = it;
      itNext++;

      //Release texture
      if ((*it)->texture)
        (*it)->texture->Release();
      (*it)->texture = NULL;

      //Delete LOADEDTEXTURE object
      delete (*it);
      loadedTextures.erase (it);

      //Move to next element
      it = itNext;
    } else it++; //Increment iterator

  //Successfully released unreferenced textures
  return TRUE;
}

And this function will release all loaded textures:

Declaration:

//Release all unreferenced textures
static int CleanupTextures();

Function:

//Release all textures
int CTexture::CleanupTextures()
{
  list<LOADEDTEXTURE*>::iterator it;
 
  //Go through loaded texture list
  for (it = loadedTextures.begin(); it != loadedTextures.end (); it++)
  {
    //Release texture
    if ((*it)->texture)
      (*it)->texture->Release();
    (*it)->texture = NULL;
        
    //Delete LOADEDTEXTURE object
    delete (*it);
  }

  //Clear list
  loadedTextures.clear ();

  //Successfully released all textures
  return TRUE;
}

Make sure both of the above functions are declared static in the class declaration.

Alright, we're well equipped to manage our graphical resources. The only thing left to do is put them on the screen. Here are the functions for the CTexture class that use the two drawing functions provided above:

//Draw texture with limited colour modulation
void CTexture::Blit (int X, int Y, D3DCOLOR vertexColour, float rotate)
{
  RECT rDest;

  //Setup destination rectangle
  rDest.left = X;
  rDest.right = X + texture->width;
  rDest.top = Y;
  rDest.bottom = Y + texture->height;

  //Draw texture
  BlitD3D (texture->texture, &rDest, vertexColour, rotate);
}


//Draw texture with full colour modulation
void CTexture::BlitEx (int X, int Y, D3DCOLOR* vertexColours, float rotate)
{
  RECT rDest;

  //Setup destination rectangle
  rDest.left = X;
  rDest.right = X + texture->width;
  rDest.top = Y;
  rDest.bottom = Y + texture->height;

  //Draw texture
  BlitExD3D (texture->texture, &rDest, vertexColours, rotate);
}

Like the BlitExD3D() provided earlier, CTexture::BlitEx() takes a pointer to a D3DCOLOR [4] array for its vertexColours argument.

You may notice that these functions have no support for scaling. This helps them account for texture sizes being not what they seem (e.g. a 100x100 image being loaded onto a 128x128 texture). If you want to add scaling, make sure you account for this. It shouldn't be too much trouble.

This concludes my article. You can check out the included sample code for a demonstration of the methods presented here in action.

Please send any bugs, suggestions, feedback, or better rotation code to SiberianKiss@gdnMail.net.



Appendix

Contents
  The concept
  Initialization code
  Drawing a texture
  The CTexture class
  Appendix

  Source code
  Printable version
  Discuss this article