#include "global.h" /* ----------------------------------------------------------------------------- Class: RageTextureManager Desc: See header. Copyright (c) 2001-2002 by the person(s) listed below. All rights reserved. Chris Danford ----------------------------------------------------------------------------- */ /* Texture policies: * * Default: When DelayedDelete is off, delete unused textures immediately. * When on, only delete textures when we change themes, on DoDelayedDelete(). * This is what you get if you call LoadTexture() on a texture that isn't * loaded. * * Cached: When DelayedDelete is off, delete unused textures when we change screens. * When on, treat as Default. This is used to precache textures that aren't * loaded immediately; use CacheTexture. * * Volatile: Delete unused textures once they've been used at least once. Ignore * DelayedDelete. * * This is for banners. We don't want to load all low-quality banners in * memory at once, since it might be ten megs of textures, and we don't * *have* to, since we can reload them very quickly. We don't want to keep * high quality textures in memory, either, although it's unlikely that a * player could actually view all banners long enough to transition to them * all in the course of one song select screen. * * Policy priority is in the order CACHED, VOLATILE, DEFAULT. Textures that are * loaded DEFAULT can be changed to VOLATILE and CACHED; VOLATILE textures can only * be changed to CACHED. CACHED flags are normally set explicitly on a per-texture * basis. VOLATILE flags are set for all banners, but banners which are explicitly * set to CACHED should stay CACHED. Finally, all textures, when they're finally used, * are loaded as NORMAL, and that should never override. */ #include "RageTextureManager.h" #include "RageBitmapTexture.h" #include "arch/MovieTexture/MovieTexture.h" #include "RageUtil.h" #include "RageLog.h" #include "RageException.h" #include "RageDisplay.h" RageTextureManager* TEXTUREMAN = NULL; static CString GetExtension(const CString &fn) { CString sDir, sFName, sExt; splitpath( fn, sDir, sFName, sExt ); sExt.MakeLower(); return sExt; } RageTextureManager::RageTextureManager() { m_iNoWarnAboutOddDimensions = 0; } RageTextureManager::~RageTextureManager() { for( std::map::iterator i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ++i) { RageTexture* pTexture = i->second; if( pTexture->m_iRefCount ) LOG->Trace( "TEXTUREMAN LEAK: '%s', RefCount = %d.", i->first.filename.c_str(), pTexture->m_iRefCount ); SAFE_DELETE( pTexture ); } } void RageTextureManager::Update( float fDeltaTime ) { for( std::map::iterator i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ++i) { RageTexture* pTexture = i->second; pTexture->Update( fDeltaTime ); } } void RageTextureManager::AdjustTextureID(RageTextureID &ID) const { if( ID.iColorDepth == -1 ) ID.iColorDepth = m_iTextureColorDepth; ID.iMaxSize = min( ID.iMaxSize, m_iMaxTextureResolution ); } /* If you've set up a texture yourself, register it here so it can be referenced * and deleted by an ID. This takes ownership. They're kept around until the * ne*/ bool RageTextureManager::IsTextureRegistered( RageTextureID ID ) const { AdjustTextureID(ID); return m_mapPathToTexture.find(ID) != m_mapPathToTexture.end(); } void RageTextureManager::RegisterTexture( RageTextureID ID, RageTexture *pTexture ) { AdjustTextureID(ID); /* Make sure we don't already have a texture with this ID. If we do, the * caller should have used it. */ std::map::iterator p = m_mapPathToTexture.find(ID); if(p != m_mapPathToTexture.end()) /* Oops, found the texture. */ RageException::Throw("Custom texture \"%s\" already registered!", ID.filename.c_str()); m_mapPathToTexture[ID] = pTexture; } // Load and unload textures from disk. RageTexture* RageTextureManager::LoadTextureInternal( RageTextureID ID ) { Checkpoint( ssprintf( "RageTextureManager::LoadTexture(%s).", ID.filename.c_str() ) ); AdjustTextureID(ID); /* We could have two copies of the same bitmap if there are equivalent but * different paths, e.g. "Bitmaps\me.bmp" and "..\Rage PC Edition\Bitmaps\me.bmp". */ std::map::iterator p = m_mapPathToTexture.find(ID); if(p != m_mapPathToTexture.end()) { /* Found the texture. Just increase the refcount and return it. */ RageTexture* pTexture = p->second; pTexture->m_iRefCount++; LOG->UnmapLog( "LoadTexture" ); return pTexture; } // The texture is not already loaded. Load it. const CString sExt = GetExtension(ID.filename); RageTexture* pTexture; if( sExt == ".avi" || sExt == ".mpg" || sExt == ".mpeg" ) pTexture = MakeRageMovieTexture( ID ); else pTexture = new RageBitmapTexture( ID ); m_mapPathToTexture[ID] = pTexture; LOG->UnmapLog( "LoadTexture" ); return pTexture; } /* Load a normal texture. Use this call to actually use a texture. */ RageTexture* RageTextureManager::LoadTexture( RageTextureID ID ) { RageTexture* pTexture = LoadTextureInternal( ID ); if( pTexture ) pTexture->m_bWasUsed = true; return pTexture; } void RageTextureManager::CacheTexture( RageTextureID ID ) { RageTexture* pTexture = LoadTextureInternal( ID ); pTexture->m_Policy = min( pTexture->m_Policy, RageTexture::TEX_CACHED ); UnloadTexture( pTexture ); } void RageTextureManager::VolatileTexture( RageTextureID ID ) { RageTexture* pTexture = LoadTextureInternal( ID ); pTexture->m_Policy = min( pTexture->m_Policy, RageTexture::TEX_VOLATILE ); UnloadTexture( pTexture ); } void RageTextureManager::UnloadTexture( RageTexture *t ) { t->m_iRefCount--; ASSERT( t->m_iRefCount >= 0 ); if( t->m_iRefCount ) return; /* Can't unload textures that are still referenced. */ bool bDeleteThis = false; /* Always unload movies, so we don't waste time decoding. * * Actually, multiple refs to a movie won't work; they should play independently, * but they'll actually share settings. Not worth fixing, since we don't currently * using movies for anything except BGAs (though we could). */ if( t->IsAMovie() ) bDeleteThis = true; /* Delete normal textures immediately unless m_bDelayedDelete is is on. */ if( t->m_Policy == RageTexture::TEX_DEFAULT && !m_bDelayedDelete ) bDeleteThis = true; /* Delete volatile textures after they've been used at least once. */ if( t->m_bWasUsed ) bDeleteThis = true; if( bDeleteThis ) DeleteTexture( t ); } void RageTextureManager::DeleteTexture( RageTexture *t ) { ASSERT( t->m_iRefCount==0 ); LOG->Trace( "RageTextureManager: deleting '%s'.", t->GetID().filename.c_str() ); for( std::map::iterator i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); i++ ) { if( i->second == t ) { m_mapPathToTexture.erase( i ); // remove map entry SAFE_DELETE( t ); // free the texture return; } } ASSERT(0); // we tried to delete a texture that wasn't loaded. } void RageTextureManager::GarbageCollect( GCType type ) { // Search for old textures with refcount==0 to unload LOG->Trace("Performing texture garbage collection."); for( std::map::iterator i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ) { std::map::iterator j = i; i++; CString sPath = j->first.filename; RageTexture* t = j->second; if( t->m_iRefCount ) continue; /* Can't unload textures that are still referenced. */ bool bDeleteThis = false; if( type==screen_changed ) { switch( t->m_Policy ) { case RageTexture::TEX_DEFAULT: /* If m_bDelayedDelete, wait until delayed_delete. If !m_bDelayedDelete, * it should have been deleted when it reached no references, but we * might have just changed the preference. */ if( !m_bDelayedDelete ) bDeleteThis = true; break; case RageTexture::TEX_CACHED: if( !m_bDelayedDelete ) bDeleteThis = true; break; case RageTexture::TEX_VOLATILE: bDeleteThis = true; break; default: ASSERT(0); } } /* This happens when we change themes; free all textures. */ if( type==delayed_delete ) bDeleteThis = true; if( bDeleteThis ) DeleteTexture( t ); } } void RageTextureManager::ReloadAll() { TEXTUREMAN->DisableOddDimensionWarning(); for( std::map::iterator i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ++i) { i->second->Reload(); } TEXTUREMAN->EnableOddDimensionWarning(); } /* In some cases, changing the display mode will reset the rendering context, * releasing all textures. We don't want to reload immediately if that happens, * since we might be changing texture preferences too, which also may have to reload * textures. Instead, tell all textures that their texture ID is invalid, so it * doesn't try to free it later when we really do reload (since that ID might be * associated with a different texture). Ack. */ void RageTextureManager::InvalidateTextures() { std::map::iterator i; for( i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ++i) { RageTexture* pTexture = i->second; pTexture->Invalidate(); } /* We're going to have to reload all loaded textures. Let's get rid * of all unreferenced textures, so we don't reload a ton of cached * data that we're not necessarily going to use. * * This must be done *after* we Invalidate above, to let the texture * know its OpenGL texture number is invalid. If we don't do that, * it'll try to free it. */ for( i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ) { std::map::iterator j = i; i++; CString sPath = j->first.filename; RageTexture* pTexture = j->second; if( pTexture->m_iRefCount==0 ) { SAFE_DELETE( pTexture ); // free the texture m_mapPathToTexture.erase(j); // and remove the key in the map } } } bool RageTextureManager::SetPrefs( int iTextureColorDepth, int iMovieColorDepth, bool bDelayedDelete, int iMaxTextureResolution ) { bool need_reload = false; if( m_bDelayedDelete != bDelayedDelete || m_iMovieColorDepth != iMovieColorDepth || m_iTextureColorDepth != iTextureColorDepth || m_iMaxTextureResolution != iMaxTextureResolution ) need_reload = true; m_bDelayedDelete = bDelayedDelete; m_iTextureColorDepth = iTextureColorDepth; m_iMaxTextureResolution = iMaxTextureResolution; m_iMovieColorDepth = iMovieColorDepth; ASSERT( m_iTextureColorDepth==16 || m_iTextureColorDepth==32 ); ASSERT( m_iMovieColorDepth==16 || m_iMovieColorDepth==32 ); return need_reload; } void RageTextureManager::DiagnosticOutput() const { unsigned cnt = distance(m_mapPathToTexture.begin(), m_mapPathToTexture.end()); LOG->Trace("%u textures loaded:", cnt); int total = 0; for( std::map::const_iterator i = m_mapPathToTexture.begin(); i != m_mapPathToTexture.end(); ++i ) { const RageTextureID &ID = i->first; const RageTexture *tex = i->second; CString diags = DISPLAY->GetTextureDiagnostics( tex->GetTexHandle() ); CString str = ssprintf("%3ix%3i (%2i)", tex->GetTextureHeight(), tex->GetTextureWidth(), tex->m_iRefCount); if( diags != "" ) str += " " + diags; LOG->Trace(" %-40s %s", str.c_str(), Basename(ID.filename).c_str() ); total += tex->GetTextureHeight() * tex->GetTextureWidth(); } LOG->Trace("total %3i texels", total); }