Sign in to follow this  

Texture management systems

This topic is 4212 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'd like you guys to take a look at my design for a simple texture management system for my game. If you could tell me wether it is fairly efficient and simple to use. Before I start, I'm using C++. I will have a TextureManager class. This class has an std::map<std::string, Texture>. This map will hold the file name of textures and the actual texture. So one value (can't remeber the correct term) could have "data/images/player.png" as the string, and the Texture that is loaded in from that file location. When a Sprite wants to use a texture, it asks a TextureManager for a texture that is located at a file. The TextureManager then searches through its std::map to see whether it already has texture loaded from that same file. If it doesn't, it loads in the texture, places it and it's file location into the map, and returns a pointer to the Texture. If it already has a texture loaded from that file, it returns a pointer to the currently existing Texture. The destructor for the TextureManager class would free all the Textures. That's the basic idea of the system. What do you think of it? I see a couple problems with it already. First is that if I procedurally generate a texture, I won't be able to use this. I'd have to have whatever generates the texture store it itself. Another is that if I edit textures (I do render the screen to textures sometimes), it would change it for every other file that uses that texture. Also, loading another level may prove difficult with this system. I either load in all textures, even those that I have already loaded in for the previous level (by creating a new TextureManager instance), or I could simply leave all previously used Textures in the TextureManager and load in new ones (by using the prevoius TextureManager). Any solutions to the above problems, additional problems it could create, or other comments would be appreciated.

Share this post


Link to post
Share on other sites
This texture manager looks OK, I've seen this kind of approach used in a commercial game. The only difference is that the hash table was set up like std::map<int, Texture*>, where int is the hash value of the string. This hash value was pre-calculated in a crunch application and stored in the binary mesh format.

You are right, it's kind of awkward to use this system with procedurally generated textures. You could, however invent your own naming convention for these textures, but I think the texture manager should only be used for files that are to be loaded from disk.

Loading another level is a problem, not doubt about it. You should use the same TextureManager instance everywhere, think of it as a singleton class. If you are worried about freeing existing textures and loading new ones, change the std::map approach to hold boost::shared_ptr instead of raw pointers. Also, have all your other classes hold boost::shared_ptr pointer to textures. That way you could just unload all the models and go through the std::map to delete all textures with use_count==1 (only referenced in texture manager, nowhere else) If you want to be very sneaky, when you load a new level, chances are that it will reuse existing textures, so load the level first and free unused textures after that.

EDIT: Render to texture problem could be solved by cloning the texture and then using the clone as a render target. You don't have to keep this cloned texture in the texture manager, if you are using boost::shared_ptr, it will go away as soon as the model that's using it is unloaded.

Share this post


Link to post
Share on other sites
Quote:
Original post by deathkrush
Loading another level is a problem, not doubt about it. You should use the same TextureManager instance everywhere, think of it as a singleton class. If you are worried about freeing existing textures and loading new ones, change the std::map approach to hold boost::shared_ptr instead of raw pointers. Also, have all your other classes hold boost::shared_ptr pointer to textures. That way you could just unload all the models and go through the std::map to delete all textures with use_count==1 (only referenced in texture manager, nowhere else) If you want to be very sneaky, when you load a new level, chances are that it will reuse existing textures, so load the level first and free unused textures after that.


A better method than this is to store boost::weak_ptr's in your resourcemanager and let the shared_ptr deal with deleting the textures naturally when they are no longer used.

Share this post


Link to post
Share on other sites
Seems like the standard faire TextureManager, and is a nice fairly complete description.

Quote:

First is that if I procedurally generate a texture, I won't be able to use this. I'd have to have whatever generates the texture store it itself.


Or have whatever generates the texture register it with a string ID, and tell the manager to not try and reload it. Though yes, not very clean or usable.

Quote:

Another is that if I edit textures (I do render the screen to textures sometimes), it would change it for every other file that uses that texture.


Yup, but generally such things (minimap, screen effects) are one use only anyways.

Quote:

Also, loading another level may prove difficult with this system. I either load in all textures, even those that I have already loaded in for the previous level (by creating a new TextureManager instance), or I could simply leave all previously used Textures in the TextureManager and load in new ones (by using the prevoius TextureManager).


Whichever. In practice, this isn't such a big problem. (at least in my experience).


Quote:

change the std::map approach to hold boost::shared_ptr instead of raw pointers. Also, have all your other classes hold boost::shared_ptr pointer to textures. That way you could just unload all the models and go through the std::map to delete all textures with use_count==1 (only referenced in texture manager, nowhere else)


Or you can use the tools available, and store boost::weak_ptr's in the map, which automagically release the texture/resource/memory when the manager is the only reference left.


Quote:

Any solutions to the above problems, additional problems it could create, or other comments would be appreciated.


Another thing to consider is the use of filenames for keys. Not all resources will be individual files. Having partial textures (sprite sheets), packed resources, networked resources, and things like your generated textures don't lend themselves to auto-reload from file. Personally, I use two maps. One for name->loading_instructions, one for name->loaded_resource. It's a bit more complex though, and of limited benefit for most applications.

Also, a hash_map would likely yield better performance for string keys.

Share this post


Link to post
Share on other sites
Quote:
Original post by jamessharpe

A better method than this is to store boost::weak_ptr's in your resourcemanager and let the shared_ptr deal with deleting the textures naturally when they are no longer used.


I agree, boost::weak_ptr is a more elegant solution, but I think it has a potential problem of unloading and loading the same texture over and over, especially if there is some kind of stream loading system in place. That's why I would rather keep boost::shared_ptr in my texture manager and manage texture unloading myself. To me it seems that texture manager should own the texture objects, not just weakly reference them.

[Edited by - deathkrush on July 3, 2006 4:33:03 PM]

Share this post


Link to post
Share on other sites
Thanks!

I think I will use this system, then. I will stick with just strings for now. If it turns out that loading levels takes too long when I actually make complex scenes, I will test out hashes.

I don't have any experience with boost:shared_pty or weak_prt but I will check them out, too.

Share this post


Link to post
Share on other sites
"so load the level first and free unused textures after that"

This may be a bad idea as you will run out of texture memory if each level is using close the max spec. It will also spike memory by loading 2 datasets at once. Instead generate a list of all textures in the level to be loaded, then when unloading the current level don't unload the texture or model if it is in the list.

"the hash table was set up like std::map<int, Texture*>"

Don't worry about this until you are optimizing after the game is basically done. Unless you have 100,000 textures I can't see this gaining you anything.

Share this post


Link to post
Share on other sites
at the risk of self pimping, I've been working on this idea in my journal, you might want to go take a read (and for the record, I advocate the weak_ptr in the texture manager idea for the current design).

Share this post


Link to post
Share on other sites
Quote:
Original post by Rasterman
"so load the level first and free unused textures after that"

This may be a bad idea as you will run out of texture memory if each level is using close the max spec. It will also spike memory by loading 2 datasets at once. Instead generate a list of all textures in the level to be loaded, then when unloading the current level don't unload the texture or model if it is in the list.

OK, you win. :-)

Share this post


Link to post
Share on other sites
It's a good idea to keep tabs on how much memory you're actually using, and only keep the most relevant textures in memory, so once you hit your limit you free up some resources before consuming more.
Obviously it's not always that simple to determine which textures are "most relevant", so a lot of people just use a LRU ( least recently used ) caching scheme. Managed resources are actually handled this way in Direct3D, unless the runtime detects you're using more textures per frame than fit in memory ( in which case it switches to a MRU scheme to avoid thrashing ). In addition to the time stamp of when it was last used, you can give certain textures a higher priority ( HUD, GUI textures etc ) so they are weighted when determining which textures to free up. When changing levels, you can also boost the priority of the shared textures between levels, so when you go to precache some of the new textures, the shared ones don't get dumped.

Daniel

Share this post


Link to post
Share on other sites
Quote:

I agree, boost::weak_ptr is a more elegant solution, but I think it has a potential problem of unloading and loading the same texture over and over, especially if there is some kind of stream loading system in place. That's why I would rather keep boost::shared_ptr in my texture manager and manage texture unloading myself. To me it seems that texture manager should own the texture objects, not just weakly reference them.


I'm working on the problem of texture ( and more generally, ressources ) manager, and i came excalty to those kind of things .

The main thing is taht the deleting part is hard to manage .
You can either, flag texture as "temporary" and run a delete check on every frame inside the texture manager . IT's fine, but kinda time-consuming . Or you can call a delete function fom each "item" taht use a temporary texture ( this delete function will kill the texture if shared_ptr.use_count() == 1 )

I can't find a better solution, if you guys got ideas ... tell us :)

Share this post


Link to post
Share on other sites
Well, there are a few solutions to your problem, the one that is most obvious is to use shared pointers, as you and other people has suggested. Another one is to have a 'null' resource or an empty resource handle. Those are greatly explained in one of the Game Programming Gems books, and you may also find it here.
I, however, use the way similar to this with additions of filename, name and an id within that internal resource structure. Here is how my resource manager looks like:

#ifndef RESOURCEMANAGER_H
#define RESOURCEMANAGER_H

#if _MSC_VER > 1000
#pragma once
#endif

#include "Pool.h"

//! Class Name
/*!
Description
*/

template<class T> class CResourceManager
{
CREATOR(CAppWindow);

protected:
CResourceManager(unsigned int uiPoolCapacity = INFINITE) { m_Resources.Expand(uiPoolCapacity); };
virtual ~CResourceManager()
{ Destroy(); };

public:

struct tResource
{
T *m_pResource;
std::string m_strName;
std::string m_strFilename;

tResource() : m_pResource(NULL), m_strName(""), m_strFilename("") {};
virtual ~tResource()
{ };

protected:
/*!
Method details:
@param Archive - template parameter for archive type
@param tArchive - Archive reference
@param uiVersion - Archive version
\return (void)
*/

template<class Archive> void serialize(Archive& tArchive, const unsigned int uiVersion)
{
tArchive & boost::serialization::make_nvp("Name", m_strName);
tArchive & boost::serialization::make_nvp("Filename", m_strFilename);
tArchive & boost::serialization::make_nvp("Resource", m_pResource);
};
};

protected:
/*!
Method details:
@param Archive - template parameter for archive type
@param tArchive - Archive reference
@param uiVersion - Archive version
\return (void)
*/

template<class Archive> void serialize(Archive& tArchive, unsigned int uiVersion)
{
tArchive & BOOST_SERIALIZATION_NVP(m_Resources);
};

public:
/*!
Method details:
@param strFilename - String with filename to load the resource from
@param strName - Name of the resource (must be unique)
\return Pointer to resource that has been loaded, NULL if not
*/

virtual T *AddResource(std::string strFilename, std::string strName)
{
for (unsigned int i = 0; i < m_Resources.Size(); i++)
{
tResource *pResource = m_Resources.Get(i);
if(pResource->m_strName == strName)
return pResource->m_pResource;
}

return NULL;
};

/*!
Method details:
@param strName - Name of the resource to fetch (must be unique)
@param strFilename - Filename of the resource to fetch
\return Pointer to the resource if found, NULL if not
*/

virtual T* const GetResource(std::string strName, std::string strFilename = "")
{
if(strName == "")
{
for(unsigned int i = 0; i < m_Resources.Size(); i++)
{
tResource *pResource = m_Resources.Get(i);
if(pResource->m_strFilename == strFilename)
return pResource->m_pResource;
}
}
else
{
for(unsigned int i = 0; i < m_Resources.Size(); i++)
{
tResource *pResource = m_Resources.Get(i);
if(pResource->m_strName == strName)
return pResource->m_pResource;
}
}

return NULL;
};

/*!
Method details:
@param strName - Name of the resource to remove (must be unique)
@param strFilename - Filename of the resource to remove
*/

virtual void RemoveResource(std::string strName, std::string strFilename)
{
T *pResourcePtr = GetResource(strName, strFilename);

for(unsigned int i = 0; i < m_Resources.Size(); i++)
{
tResource *pResource = m_Resources.Get(i);
if(pResource->m_pResource == pResourcePtr)
{
m_Resources.Remove(pResource);
return;
}
}
};

/*!
Method details:
@param pResourcePtr - Pointer to resource
\return String with resource' name
*/

std::string& GetResourceName(T* const pResourcePtr)
{
for(unsigned int i = 0; i < m_Resources.Size(); i++)
{
tResource *pResource = m_Resources.Get(i);
if(pResource->m_pResource == pResourcePtr)
return pResource->m_strName;
}

return std::string("");
};

virtual void Destroy()
{
m_Resources.Destroy();
};

protected:
CPool<tResource> m_Resources;
};

#endif



This is a base functionality that other managers (materials, textures, sprites) re-use and take advantage of.

Another alternative is to have a default resource that is created no matter what, and is used to replace 'dying' resources.

Share this post


Link to post
Share on other sites

This topic is 4212 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this