Implementing a Texture Manager

Environment: OpenGL

When programming a 3D engine that's supposed to render very big extensions (that's it: lots of geometry and lots of textures) that are going to be dynamically loaded from disk, we don't want to have lower framerates as we are transferring the data from disk to memory, and, especially while talking about textures, the API-specific calls to create the resources to handle the data can considerably decrease performance and produce non-desirable framerate changes.

Specifically, with OpenGL, calls to glTexImage() and glDeleteTextures() forces the OpenGL implementation to allocate/deallocate memory, and should be avoided as much as we can because calls to glTexSubImage() are way cheaper than the two others.

One way to do so is by implementing a texture manager, which can be done quite easily and can reduce the texture memory requirements as well as framerate changes. This way, every time we need memory to load a texture, the texture manager checks whether there's any texture with the same characteristics (size and format, which must be the same to reutilize the texture) which have been freed, and in case there's any, it can be used instead of generating another one.

So, what information must we have to know whether a texture can be loaded in another one? We can define a struct that looks similar to this one:

struct texObject
{
  int numDim;              //if the texture's 1d, 2d, or 3d
  int sizeU,sizeV,sizeW;   //size in the 3 axes
  int numMipLevels;        //number of mipmap levels it has
  void* pTextura;          //pointer to texture memory
  int creationFlag;        //return value which says us if we must
                           //create a texture or substitute it
  int numChannels;         //number of channels (rgb - rgba)
  int bytesPerPixel;       //2, 3, 4...
  unsigned int texName;    //texture object name to bind if it has
                           //already been created

  texObject();             //set default values
};

The interface the texture manager exposes should be something similar to this one:

void InitTextureManager();     //allocate memory and initialize
                               //as needed - implementation
                               //dependent
void CloseTextureManager();    //free reserved memory
texObject* GetTextureMem(int numDim,int sizeU,int sizeV,int sizeW,
                         int numMipLevels,int numChannels,
                         int bytesPerPixel);
void ReleaseTextureMem(texObject* texture);

We could have two lists, one for freed textures and another one for used textures. They'll be called g_freeTextures and g_usedTextures from now on.

Basically, GetTextureMem(...) can be implemented this way:

texObject* GetTextureMem(int numDim,int sizeU,int sizeV,int sizeW,
                         int numMipLevels,int numChannels,
                         int bytesPerPixel)
{
  texObject* pTexObject;

  int totalSize= CALCULATE_BYTE_SIZE(numDim,sizeU,sizeV,sizeW,
                                     numMipLevels,numChannels,
                                     bytesPerPixel);

  pTexObject= g_freeTextures.ExtractObject(numDim,sizeU,sizeV,
                                           sizeW,numMipLevels,
                                           numChannels,
                                           bytesPerPixel);

  if (pTexObject)
  {
    //we've found a texture that fits what we need
    if ( pTexObject->texName )    //has OpenGL texture object
                                  //already been generated?
      pTexObject->creationFlag= MUST_CALL_GLSUB;
    else pTexObject->creationFlag= MUST_CALL_GLTEX;

    g_freeTextures.ExtractObject(pTexObject);
    g_usedTextures.Insert(pTexObject);
  }
  else
  {
    //we must create another texture
    pTexObject= new texObject;
    pTexObject->pTextura= new unsigned char[totalSize];
    pTexObject->creationFlag= MUST_CALL_GLTEX;
    pTexObject->numMipLevels= numMipLevels;
    pTexObject->sizeU= sizeU;
    pTexObject->sizeV= sizeV;
    pTexObject->sizeW= sizeW;
    pTexObject->bytesPerPixel= bytesPerPixel;
    pTexObject->numChannels= numChannels;

    g_usedTextures.Insert(pTexObject);
  }

  return pTexObject;
}

The implementation of ReleaseTextureMem(...) could be as trivial as:

void ReleaseTextureMem(texObject* pTexObject)
{
  //we must take the texture object from the used-textures list
  //to the freed-textures list
  g_usedTextures.ExtractObject(pTexObject);
  g_freeTextures.Insert(pTexObject);
}

That way, an object loader could ask for a texture this way:

READ_TEXTURE_FILE_HEADER(&numDim,&sizeU,&sizeV,&sizeW,
                         &numMipLevels,&numChannels,
                         &bytesPerPixel);
texObject* pTexObject= GetTextureMem(int numDim,int sizeU,
                                     int sizeV,int sizeW,
                                     int numMipLevels,
                                     int numChannels,
                                     int bytesPerPixel);
READ_TEXTURE_IN_TEX_OBJECT(pTexObject);

...


//this could have to be done in a separate thread because of the
//openGL context
switch(m_pTexObject->creationFlag)
{
case TEX_OBJECT_INITED:
  break;
case MUST_CALL_GLTEX:
  glGenTextures(1,&m_pTexObject->texName);
  SET_TEXTURE_AND_PARAMETERS(m_pTexObject->texName);

  glTexImage2D(GL_TEXTURE_2D,0,NUM_OF_CHANNELS_CODE,m_pTexObject->
               sizeU,m_pTexObject->sizeV,0,NUM_OF_CHANNELS_CODE,
               INTERNAL_FORMAT,m_pTexObject->pTextura);

  m_pTexObject->creationFlag= TEX_OBJECT_INITED;
  break;
case MUST_CALL_GLSUB:
  SET_TEXTURE_AND_PARAMETERS(m_pTexObject->texName);

  glTexSubImage2D(GL_TEXTURE_2D,0,0,0,m_pTexObject->sizeU,
                  m_pTexObject->sizeV,NUM_OF_CHANNELS_CODE,
                  INTERNAL_FORMAT,m_pTexObject->pTextura);

  m_pTexObject->creationFlag= TEX_OBJECT_INITED;
  break;
  }

Of course, as we are having textures freed, it would be very handy to provide another function which would periodically free the textures that are in the free-texture list and have not been reused for a long time.

Downloads

Download performance demo - 678 Kb

It compares performance between having textures created again and reusing them. Press 's' to switch the mode. To run the program, be sure you have DirectInput installed.



Comments

  • How about some benchmarking?

    Posted by Legacy on 06/27/2003 12:00am

    Originally posted by: Thanassis Anastassiou

    Very nice way of handling Textures...Although now you add the time to search a loaded textures database to find the apropriate texture and then replace it and so on.
    So in order to realy show, how nice this Texture manager is, you could provide some benchmarking....Framerate count in a classic rotating cube demo where textures would change rapidly and new ones would be loaded from disk.....


    Very nice work again.

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds