6. The CTexture classOkay, 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.
|
|