//----------------------------------------------------------------------------- // Copyright (c) 2012 GarageGames, LLC // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #include "platform/platform.h" #include "gfx/gfxTextureManager.h" #include "gfx/gfxDevice.h" #include "gfx/gfxCardProfile.h" #include "gfx/gfxStringEnumTranslate.h" #include "gfx/bitmap/ddsUtils.h" #include "core/strings/stringFunctions.h" #include "core/util/safeDelete.h" #include "core/resourceManager.h" #include "core/volume.h" #include "core/util/dxt5nmSwizzle.h" #include "console/consoleTypes.h" #include "console/engineAPI.h" //#define DEBUG_SPEW S32 GFXTextureManager::smTextureReductionLevel = 0; GFXTextureManager::EventSignal GFXTextureManager::smEventSignal; static const String sDDSExt( "dds" ); void GFXTextureManager::init() { Con::addVariable( "$pref::Video::textureReductionLevel", TypeS32, &smTextureReductionLevel, "The number of mipmap levels to drop on loaded textures to reduce " "video memory usage. It will skip any textures that have been defined " "as not allowing down scaling.\n" "@ingroup GFX\n" ); } GFXTextureManager::GFXTextureManager() { mListHead = mListTail = NULL; mTextureManagerState = GFXTextureManager::Living; // Set up the hash table mHashCount = 1023; mHashTable = new GFXTextureObject *[mHashCount]; for(U32 i = 0; i < mHashCount; i++) mHashTable[i] = NULL; } GFXTextureManager::~GFXTextureManager() { if( mHashTable ) SAFE_DELETE_ARRAY( mHashTable ); mCubemapTable.clear(); } U32 GFXTextureManager::getTextureDownscalePower( GFXTextureProfile *profile ) { if ( !profile || profile->canDownscale() ) return smTextureReductionLevel; return 0; } bool GFXTextureManager::validateTextureQuality( GFXTextureProfile *profile, U32 &width, U32 &height ) { U32 scaleFactor = getTextureDownscalePower( profile ); if ( scaleFactor == 0 ) return true; // Otherwise apply the appropriate scale... width >>= scaleFactor; height >>= scaleFactor; return true; } void GFXTextureManager::kill() { AssertFatal( mTextureManagerState != GFXTextureManager::Dead, "Texture Manager already killed!" ); // Release everything in the cache we can // so we don't leak any textures. cleanupCache(); GFXTextureObject *curr = mListHead; GFXTextureObject *temp; // Actually delete all the textures we know about. while( curr != NULL ) { temp = curr->mNext; curr->kill(); curr = temp; } mCubemapTable.clear(); mTextureManagerState = GFXTextureManager::Dead; } void GFXTextureManager::zombify() { AssertFatal( mTextureManagerState != GFXTextureManager::Zombie, "Texture Manager already a zombie!" ); // Notify everyone that cares about the zombification! smEventSignal.trigger( GFXZombify ); // Release unused pool textures. cleanupPool(); // Release everything in the cache we can. cleanupCache(); // Free all the device copies of the textures. GFXTextureObject *temp = mListHead; while( temp != NULL ) { freeTexture( temp, true ); temp = temp->mNext; } // Finally, note our state. mTextureManagerState = GFXTextureManager::Zombie; } void GFXTextureManager::resurrect() { // Reupload all the device copies of the textures. GFXTextureObject *temp = mListHead; while( temp != NULL ) { refreshTexture( temp ); temp = temp->mNext; } // Notify callback registries. smEventSignal.trigger( GFXResurrect ); // Update our state. mTextureManagerState = GFXTextureManager::Living; } void GFXTextureManager::cleanupPool() { PROFILE_SCOPE( GFXTextureManager_CleanupPool ); TexturePoolMap::Iterator iter = mTexturePool.begin(); for ( ; iter != mTexturePool.end(); ) { if ( iter->value->getRefCount() == 1 ) { // This texture is unreferenced, so take the time // now to completely remove it from the pool. TexturePoolMap::Iterator unref = iter; iter++; unref->value = NULL; mTexturePool.erase( unref ); continue; } iter++; } } void GFXTextureManager::requestDeleteTexture( GFXTextureObject *texture ) { // If this is a non-cached texture then just really delete it. if ( texture->mTextureLookupName.isEmpty() ) { delete texture; return; } // Set the time and store it. texture->mDeleteTime = Platform::getTime(); mToDelete.push_back_unique( texture ); } void GFXTextureManager::cleanupCache( U32 secondsToLive ) { PROFILE_SCOPE( GFXTextureManager_CleanupCache ); U32 killTime = Platform::getTime() - secondsToLive; for ( U32 i=0; i < mToDelete.size(); ) { GFXTextureObject *tex = mToDelete[i]; // If the texture was picked back up by a user // then just remove it from the list. if ( tex->getRefCount() != 0 ) { mToDelete.erase_fast( i ); continue; } // If its time has expired delete it for real. if ( tex->mDeleteTime <= killTime ) { //Con::errorf( "Killed texture: %s", tex->mTextureLookupName.c_str() ); delete tex; mToDelete.erase_fast( i ); continue; } i++; } } GFXTextureObject *GFXTextureManager::_lookupTexture( const char *hashName, const GFXTextureProfile *profile ) { GFXTextureObject *ret = hashFind( hashName ); // TODO: Profile checking HERE return ret; } GFXTextureObject *GFXTextureManager::_lookupTexture( const DDSFile *ddsFile, const GFXTextureProfile *profile ) { if( ddsFile->getTextureCacheString().isNotEmpty() ) { // Call _lookupTexture() return _lookupTexture( ddsFile->getTextureCacheString(), profile ); } return NULL; } GFXTextureObject *GFXTextureManager::createTexture( GBitmap *bmp, const String &resourceName, GFXTextureProfile *profile, bool deleteBmp ) { AssertFatal(bmp, "GFXTextureManager::createTexture() - Got NULL bitmap!"); GFXTextureObject *cacheHit = _lookupTexture( resourceName, profile ); if( cacheHit != NULL) { // Con::errorf("Cached texture '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); if (deleteBmp) delete bmp; return cacheHit; } return _createTexture( bmp, resourceName, profile, deleteBmp, NULL ); } GFXTextureObject *GFXTextureManager::_createTexture( GBitmap *bmp, const String &resourceName, GFXTextureProfile *profile, bool deleteBmp, GFXTextureObject *inObj ) { PROFILE_SCOPE( GFXTextureManager_CreateTexture_Bitmap ); #ifdef DEBUG_SPEW Platform::outputDebugString( "[GFXTextureManager] _createTexture (GBitmap) '%s'", resourceName.c_str() ); #endif // Massage the bitmap based on any resize rules. U32 scalePower = getTextureDownscalePower( profile ); GBitmap *realBmp = bmp; U32 realWidth = bmp->getWidth(); U32 realHeight = bmp->getHeight(); if ( scalePower && isPow2(bmp->getWidth()) && isPow2(bmp->getHeight()) && profile->canDownscale() ) { // We only work with power of 2 textures for now, so we // don't have to worry about padding. // We downscale the bitmap on the CPU... this is the reason // you should be using DDS which already has good looking mips. GBitmap *padBmp = bmp; padBmp->extrudeMipLevels(); scalePower = getMin( scalePower, padBmp->getNumMipLevels() - 1 ); realWidth = getMax( (U32)1, padBmp->getWidth() >> scalePower ); realHeight = getMax( (U32)1, padBmp->getHeight() >> scalePower ); realBmp = new GBitmap( realWidth, realHeight, false, bmp->getFormat() ); // Copy to the new bitmap... dMemcpy( realBmp->getWritableBits(), padBmp->getBits(scalePower), padBmp->getBytesPerPixel() * realWidth * realHeight ); // This line is commented out because createPaddedBitmap is commented out. // If that line is added back in, this line should be added back in. // delete padBmp; } // Call the internal create... (use the real* variables now, as they // reflect the reality of the texture we are creating.) U32 numMips = 0; GFXFormat realFmt = realBmp->getFormat(); _validateTexParams( realWidth, realHeight, profile, numMips, realFmt ); GFXTextureObject *ret; if ( inObj ) { // If the texture has changed in dimensions // then we need to recreate it. if ( inObj->getWidth() != realWidth || inObj->getHeight() != realHeight || inObj->getFormat() != realFmt ) ret = _createTextureObject( realHeight, realWidth, 0, realFmt, profile, numMips, false, 0, inObj ); else ret = inObj; } else ret = _createTextureObject(realHeight, realWidth, 0, realFmt, profile, numMips ); if(!ret) { Con::errorf("GFXTextureManager - failed to create texture (1) for '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); return NULL; } // Extrude mip levels // Don't do this for fonts! if( ret->mMipLevels > 1 && ( realBmp->getNumMipLevels() == 1 ) && ( realBmp->getFormat() != GFXFormatA8 ) && isPow2( realBmp->getHeight() ) && isPow2( realBmp->getWidth() ) && !profile->noMip() ) { // NOTE: This should really be done by extruding mips INTO a DDS file instead // of modifying the gbitmap realBmp->extrudeMipLevels(false); } // If _validateTexParams kicked back a different format, than there needs to be // a conversion DDSFile *bmpDDS = NULL; if( realBmp->getFormat() != realFmt ) { const GFXFormat oldFmt = realBmp->getFormat(); // TODO: Set it up so that ALL format conversions use DDSFile. Rip format // switching out of GBitmap entirely. if( !realBmp->setFormat( realFmt ) ) { // This is not the ideal implementation... bmpDDS = DDSFile::createDDSFileFromGBitmap( realBmp ); bool convSuccess = false; if( bmpDDS != NULL ) { // This shouldn't live here, I don't think switch( realFmt ) { case GFXFormatDXT1: case GFXFormatDXT2: case GFXFormatDXT3: case GFXFormatDXT4: case GFXFormatDXT5: // If this is a Normal Map profile, than the data needs to be conditioned // to use the swizzle trick if( ret->mProfile->getType() == GFXTextureProfile::NormalMap ) { PROFILE_START(DXT_DXTNMSwizzle); static DXT5nmSwizzle sDXT5nmSwizzle; DDSUtil::swizzleDDS( bmpDDS, sDXT5nmSwizzle ); PROFILE_END(); } convSuccess = DDSUtil::squishDDS( bmpDDS, realFmt ); break; default: AssertFatal(false, "Attempting to convert to a non-DXT format"); break; } } if( !convSuccess ) { Con::errorf( "[GFXTextureManager]: Failed to change source format from %s to %s. Cannot create texture.", GFXStringTextureFormat[oldFmt], GFXStringTextureFormat[realFmt] ); delete bmpDDS; return NULL; } } #ifdef TORQUE_DEBUG else { //Con::warnf( "[GFXTextureManager]: Changed bitmap format from %s to %s.", // GFXStringTextureFormat[oldFmt], GFXStringTextureFormat[realFmt] ); } #endif } // Call the internal load... if( ( bmpDDS == NULL && !_loadTexture( ret, realBmp ) ) || // If we aren't doing a DDS format change, use bitmap load ( bmpDDS != NULL && !_loadTexture( ret, bmpDDS ) ) ) // If there is a DDS, than load that instead. A format change took place. { Con::errorf("GFXTextureManager - failed to load GBitmap for '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); return NULL; } // Do statistics and book-keeping... // - info for the texture... ret->mTextureLookupName = resourceName; ret->mBitmapSize.set(realWidth, realHeight,0); #ifdef TORQUE_DEBUG if (resourceName.isNotEmpty()) ret->mDebugDescription = resourceName; else ret->mDebugDescription = "Anonymous Texture Object"; #endif if(profile->doStoreBitmap()) { // NOTE: may store a downscaled copy! SAFE_DELETE( ret->mBitmap ); SAFE_DELETE( ret->mDDS ); if( bmpDDS == NULL ) ret->mBitmap = new GBitmap( *realBmp ); else ret->mDDS = bmpDDS; } else { // Delete the DDS if we made one SAFE_DELETE( bmpDDS ); } if ( !inObj ) _linkTexture( ret ); // - output debug info? // Save texture for debug purpose // static int texId = 0; // char buff[256]; // dSprintf(buff, sizeof(buff), "tex_%d", texId++); // bmp->writePNGDebug(buff); // texId++; // Before we delete the bitmap save our transparency flag ret->mHasTransparency = realBmp->getHasTransparency(); // Some final cleanup... if(realBmp != bmp) SAFE_DELETE(realBmp); if (deleteBmp) SAFE_DELETE(bmp); // Return the new texture! return ret; } GFXTextureObject *GFXTextureManager::createTexture( DDSFile *dds, GFXTextureProfile *profile, bool deleteDDS ) { AssertFatal(dds, "GFXTextureManager::createTexture() - Got NULL dds!"); // Check the cache first... GFXTextureObject *cacheHit = _lookupTexture( dds, profile ); if ( cacheHit ) { // Con::errorf("Cached texture '%s'", (fileName.isNotEmpty() ? fileName.c_str() : "unknown")); if( deleteDDS ) delete dds; return cacheHit; } return _createTexture( dds, profile, deleteDDS, NULL ); } GFXTextureObject *GFXTextureManager::_createTexture( DDSFile *dds, GFXTextureProfile *profile, bool deleteDDS, GFXTextureObject *inObj ) { PROFILE_SCOPE( GFXTextureManager_CreateTexture_DDS ); const char *fileName = dds->getTextureCacheString(); if( !fileName ) fileName = "unknown"; #ifdef DEBUG_SPEW Platform::outputDebugString( "[GFXTextureManager] _createTexture (DDS) '%s'", fileName ); #endif // Ignore padding from the profile. U32 numMips = dds->mMipMapCount; GFXFormat fmt = dds->mFormat; _validateTexParams( dds->getHeight(), dds->getWidth(), profile, numMips, fmt ); if( fmt != dds->mFormat ) { Con::errorf( "GFXTextureManager - failed to validate texture parameters for DDS file '%s'", fileName ); return NULL; } // Call the internal create... (use the real* variables now, as they // reflect the reality of the texture we are creating.) GFXTextureObject *ret; if ( inObj ) { // If the texture has changed in dimensions // then we need to recreate it. if ( inObj->getWidth() != dds->getWidth() || inObj->getHeight() != dds->getHeight() || inObj->getFormat() != fmt || inObj->getMipLevels() != numMips ) ret = _createTextureObject( dds->getHeight(), dds->getWidth(), 0, fmt, profile, numMips, true, 0, inObj ); else ret = inObj; } else ret = _createTextureObject( dds->getHeight(), dds->getWidth(), 0, fmt, profile, numMips, true ); if(!ret) { Con::errorf("GFXTextureManager - failed to create texture (1) for '%s' DDSFile.", fileName); return NULL; } // Call the internal load... if(!_loadTexture(ret, dds)) { Con::errorf("GFXTextureManager - failed to load DDS for '%s'", fileName); return NULL; } // Do statistics and book-keeping... // - info for the texture... ret->mTextureLookupName = dds->getTextureCacheString(); ret->mBitmapSize.set( dds->mWidth, dds->mHeight, 0 ); #ifdef TORQUE_DEBUG ret->mDebugDescription = fileName; #endif if(profile->doStoreBitmap()) { // NOTE: may store a downscaled copy! SAFE_DELETE( ret->mBitmap ); SAFE_DELETE( ret->mDDS ); ret->mDDS = new DDSFile( *dds ); } if ( !inObj ) _linkTexture( ret ); // - output debug info? // Save texture for debug purpose // static int texId = 0; // char buff[256]; // dSprintf(buff, sizeof(buff), "tex_%d", texId++); // bmp->writePNGDebug(buff); // texId++; // Save our transparency flag ret->mHasTransparency = dds->getHasTransparency(); if( deleteDDS ) delete dds; // Return the new texture! return ret; } GFXTextureObject *GFXTextureManager::createTexture( const Torque::Path &path, GFXTextureProfile *profile ) { PROFILE_SCOPE( GFXTextureManager_createTexture ); // Resource handles used for loading. Hold on to them // throughout this function so that change notifications // don't get added, then removed, and then re-added. Resource< DDSFile > dds; Resource< GBitmap > bitmap; // We need to handle path's that have had "incorrect" // extensions parsed out of the file name Torque::Path correctPath = path; bool textureExt = false; // Easiest case to handle is when there isn't an extension if (path.getExtension().isEmpty()) textureExt = true; // Since "dds" isn't registered with GBitmap currently we // have to test it separately if (sDDSExt.equal( path.getExtension(), String::NoCase ) ) textureExt = true; // Now loop through the rest of the GBitmap extensions // to see if we have any matches for ( U32 i = 0; i < GBitmap::sRegistrations.size(); i++ ) { // If we have gotten a match (either in this loop or before) // then we can exit if (textureExt) break; const GBitmap::Registration ® = GBitmap::sRegistrations[i]; const Vector &extensions = reg.extensions; for ( U32 j = 0; j < extensions.size(); ++j ) { if ( extensions[j].equal( path.getExtension(), String::NoCase ) ) { // Found a valid texture extension textureExt = true; break; } } } // If we didn't find a valid texture extension then assume that // the parsed out "extension" was actually intended to be part of // the texture name so add it back if (!textureExt) { correctPath.setFileName( Torque::Path::Join( path.getFileName(), '.', path.getExtension() ) ); correctPath.setExtension( String::EmptyString ); } // Check the cache first... String pathNoExt = Torque::Path::Join( correctPath.getRoot(), ':', correctPath.getPath() ); pathNoExt = Torque::Path::Join( pathNoExt, '/', correctPath.getFileName() ); GFXTextureObject *retTexObj = _lookupTexture( pathNoExt, profile ); if( retTexObj ) return retTexObj; const U32 scalePower = getTextureDownscalePower( profile ); // If this is a valid file (has an extension) than load it Path realPath; if( Torque::FS::IsFile( correctPath ) ) { // Check for DDS if( sDDSExt.equal(correctPath.getExtension(), String::NoCase ) ) { dds = DDSFile::load( correctPath, scalePower ); if( dds != NULL ) { realPath = dds.getPath(); retTexObj = createTexture( dds, profile, false ); } } else // Let GBitmap take care of it { bitmap = GBitmap::load( correctPath ); if( bitmap != NULL ) { realPath = bitmap.getPath(); retTexObj = createTexture( bitmap, pathNoExt, profile, false ); } } } else { // NOTE -- We should probably remove the code from GBitmap that tries different // extensions for things GBitmap loads, and move it here. I think it should // be a bit more involved than just a list of extensions. Some kind of // extension registration thing, maybe. // Check to see if there is a .DDS file with this name (if no extension is provided) Torque::Path tryDDSPath = pathNoExt; if( tryDDSPath.getExtension().isNotEmpty() ) tryDDSPath.setFileName( tryDDSPath.getFullFileName() ); tryDDSPath.setExtension( sDDSExt ); if( Torque::FS::IsFile( tryDDSPath ) ) { dds = DDSFile::load( tryDDSPath, scalePower ); if( dds != NULL ) { realPath = dds.getPath(); retTexObj = createTexture( dds, profile, false ); } } // Otherwise, retTexObj stays NULL, and fall through to the generic GBitmap // load. } // If we still don't have a texture object yet, feed the correctPath to GBitmap and // it will try a bunch of extensions if( retTexObj == NULL ) { // Find and load the texture. bitmap = GBitmap::load( correctPath ); if ( bitmap != NULL ) { realPath = bitmap.getPath(); retTexObj = createTexture( bitmap, pathNoExt, profile, false ); } } if ( retTexObj ) { // Store the path for later use. retTexObj->mPath = realPath; // Register the texture file for change notifications. FS::AddChangeNotification( retTexObj->getPath(), this, &GFXTextureManager::_onFileChanged ); } // Could put in a final check for 'retTexObj == NULL' here as an error message. return retTexObj; } GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, void *pixels, GFXFormat format, GFXTextureProfile *profile ) { // For now, stuff everything into a GBitmap and pass it off... This may need to be revisited -- BJG GBitmap *bmp = new GBitmap(width, height, 0, format); dMemcpy(bmp->getWritableBits(), pixels, width * height * bmp->getBytesPerPixel()); return createTexture( bmp, String::EmptyString, profile, true ); } GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, U32 numMipLevels, S32 antialiasLevel ) { // Deal with sizing issues... U32 localWidth = width; U32 localHeight = height; // TODO: Format check HERE! -patw validateTextureQuality(profile, localWidth, localHeight); U32 numMips = numMipLevels; GFXFormat checkFmt = format; _validateTexParams( localWidth, localHeight, profile, numMips, checkFmt ); AssertFatal( checkFmt == format, "Anonymous texture didn't get the format it wanted." ); GFXTextureObject *outTex = NULL; // If this is a pooled profile then look there first. if ( profile->isPooled() ) { outTex = _findPooledTexure( localWidth, localHeight, checkFmt, profile, numMips, antialiasLevel ); // If we got a pooled texture then its // already setup... just return it. if ( outTex ) return outTex; } // Create the texture if we didn't get one from the pool. if ( !outTex ) { outTex = _createTextureObject( localHeight, localWidth, 0, format, profile, numMips, false, antialiasLevel ); // Make sure we add it to the pool. if ( outTex && profile->isPooled() ) mTexturePool.insertEqual( profile, outTex ); } if ( !outTex ) { Con::errorf("GFXTextureManager - failed to create anonymous texture."); return NULL; } // And do book-keeping... // - texture info outTex->mBitmapSize.set(localWidth, localHeight, 0); outTex->mAntialiasLevel = antialiasLevel; // PWTODO: Need to assign this a lookup name before _linkTexture() is called // otherwise it won't get a hash insert call _linkTexture( outTex ); return outTex; } GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, U32 depth, void *pixels, GFXFormat format, GFXTextureProfile *profile ) { PROFILE_SCOPE( GFXTextureManager_CreateTexture_3D ); // Create texture... GFXTextureObject *ret = _createTextureObject( height, width, depth, format, profile, 1 ); if(!ret) { Con::errorf("GFXTextureManager - failed to create anonymous texture."); return NULL; } // Call the internal load... if( !_loadTexture( ret, pixels ) ) { Con::errorf("GFXTextureManager - failed to load volume texture" ); return NULL; } // And do book-keeping... // - texture info ret->mBitmapSize.set( width, height, depth ); _linkTexture( ret ); // Return the new texture! return ret; } GFXTextureObject* GFXTextureManager::_findPooledTexure( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, U32 numMipLevels, S32 antialiasLevel ) { PROFILE_SCOPE( GFXTextureManager_FindPooledTexure ); GFXTextureObject *outTex; // First see if we have a free one in the pool. TexturePoolMap::Iterator iter = mTexturePool.find( profile ); for ( ; iter != mTexturePool.end() && iter->key == profile; iter++ ) { outTex = iter->value; // If the reference count is 1 then we're the only // ones holding on to this texture and we can hand // it out if the size matches... else its in use. if ( outTex->getRefCount() != 1 ) continue; // Check for a match... if so return it. The assignment // to a GFXTexHandle will take care of incrementing the // reference count and keeping it from being handed out // to anyone else. if ( outTex->getFormat() == format && outTex->getWidth() == width && outTex->getHeight() == height && outTex->getMipLevels() == numMipLevels && outTex->mAntialiasLevel == antialiasLevel ) return outTex; } return NULL; } void GFXTextureManager::hashInsert( GFXTextureObject *object ) { if ( object->mTextureLookupName.isEmpty() ) return; U32 key = object->mTextureLookupName.getHashCaseInsensitive() % mHashCount; object->mHashNext = mHashTable[key]; mHashTable[key] = object; } void GFXTextureManager::hashRemove( GFXTextureObject *object ) { if ( object->mTextureLookupName.isEmpty() ) return; U32 key = object->mTextureLookupName.getHashCaseInsensitive() % mHashCount; GFXTextureObject **walk = &mHashTable[key]; while(*walk) { if(*walk == object) { *walk = object->mHashNext; break; } walk = &((*walk)->mHashNext); } } GFXTextureObject* GFXTextureManager::hashFind( const String &name ) { if ( name.isEmpty() ) return NULL; U32 key = name.getHashCaseInsensitive() % mHashCount; GFXTextureObject *walk = mHashTable[key]; for(; walk; walk = walk->mHashNext) { if( walk->mTextureLookupName.equal( name, String::NoCase ) ) break; } return walk; } void GFXTextureManager::freeTexture(GFXTextureObject *texture, bool zombify) { // Ok, let the backend deal with it. _freeTexture(texture, zombify); } void GFXTextureManager::refreshTexture(GFXTextureObject *texture) { _refreshTexture(texture); } void GFXTextureManager::_linkTexture( GFXTextureObject *obj ) { // info for the profile GFXTextureProfile::updateStatsForCreation(obj); // info for the cache hashInsert(obj); // info for the master list if( mListHead == NULL ) mListHead = obj; if( mListTail != NULL ) mListTail->mNext = obj; obj->mPrev = mListTail; mListTail = obj; } void GFXTextureManager::deleteTexture( GFXTextureObject *texture ) { if ( mTextureManagerState == GFXTextureManager::Dead ) return; #ifdef DEBUG_SPEW Platform::outputDebugString( "[GFXTextureManager] deleteTexture '%s'", texture->mTextureLookupName.c_str() ); #endif if( mListHead == texture ) mListHead = texture->mNext; if( mListTail == texture ) mListTail = texture->mPrev; hashRemove( texture ); // If we have a path for the texture then // remove change notifications for it. Path texPath = texture->getPath(); if ( !texPath.isEmpty() ) FS::RemoveChangeNotification( texPath, this, &GFXTextureManager::_onFileChanged ); GFXTextureProfile::updateStatsForDeletion(texture); freeTexture( texture ); } void GFXTextureManager::_validateTexParams( const U32 width, const U32 height, const GFXTextureProfile *profile, U32 &inOutNumMips, GFXFormat &inOutFormat ) { // Validate mipmap parameter. If this profile requests no mips, set mips to 1. if( profile->noMip() ) { inOutNumMips = 1; } else if( !isPow2( width ) || !isPow2( height ) ) { // If a texture is not power-of-2 in size for both dimensions, it must // have only 1 mip level. inOutNumMips = 1; } // Check format, and compatibility with texture profile requirements bool autoGenSupp = ( inOutNumMips == 0 ); // If the format is non-compressed, and the profile requests a compressed format // than change the format. GFXFormat testingFormat = inOutFormat; if( profile->getCompression() != GFXTextureProfile::None ) { const int offset = profile->getCompression() - GFXTextureProfile::DXT1; testingFormat = GFXFormat( GFXFormatDXT1 + offset ); // No auto-gen mips on compressed textures autoGenSupp = false; } // inOutFormat is not modified by this method bool chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); if( !chekFmt ) { // It tested for a compressed format, and didn't like it if( testingFormat != inOutFormat && profile->getCompression() ) testingFormat = inOutFormat; // Reset to requested format, and try again // Trying again here, so reset autogen mip autoGenSupp = ( inOutNumMips == 0 ); // Wow more weak sauce. There should be a better way to do this. switch( inOutFormat ) { case GFXFormatR8G8B8: testingFormat = GFXFormatR8G8B8X8; chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); break; case GFXFormatA8: testingFormat = GFXFormatR8G8B8A8; chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); break; default: chekFmt = GFX->getCardProfiler()->checkFormat( testingFormat, profile, autoGenSupp ); break; } } // Write back num mips that need to be generated by GBitmap if( !chekFmt ) Con::errorf( "Format %s not supported with specified profile.", GFXStringTextureFormat[inOutFormat] ); else { inOutFormat = testingFormat; // If auto gen mipmaps were requested, and they aren't supported for whatever // reason, than write out the number of mips that need to be generated. // // NOTE: Does this belong here? if( inOutNumMips == 0 && !autoGenSupp ) { U32 currWidth = width; U32 currHeight = height; inOutNumMips = 1; do { currWidth >>= 1; currHeight >>= 1; if( currWidth == 0 ) currWidth = 1; if( currHeight == 0 ) currHeight = 1; inOutNumMips++; } while ( currWidth != 1 || currHeight != 1 ); } } } GFXCubemap* GFXTextureManager::createCubemap( const Torque::Path &path ) { // Very first thing... check the cache. CubemapTable::Iterator iter = mCubemapTable.find( path.getFullPath() ); if ( iter != mCubemapTable.end() ) return iter->value; // Not in the cache... we have to load it ourselves. // First check for a DDS file. if ( !sDDSExt.equal( path.getExtension(), String::NoCase ) ) { // At the moment we only support DDS cubemaps. return NULL; } const U32 scalePower = getTextureDownscalePower( NULL ); // Ok... load the DDS file then. Resource dds = DDSFile::load( path, scalePower ); if ( !dds || !dds->isCubemap() ) { // This wasn't a cubemap... give up too. return NULL; } // We loaded the cubemap dds, so now we create the GFXCubemap from it. GFXCubemap *cubemap = GFX->createCubemap(); cubemap->initStatic( dds ); cubemap->_setPath( path.getFullPath() ); // Store the cubemap into the cache. mCubemapTable.insertUnique( path.getFullPath(), cubemap ); return cubemap; } void GFXTextureManager::releaseCubemap( GFXCubemap *cubemap ) { if ( mTextureManagerState == GFXTextureManager::Dead ) return; const String &path = cubemap->getPath(); CubemapTable::Iterator iter = mCubemapTable.find( path ); if ( iter != mCubemapTable.end() && iter->value == cubemap ) mCubemapTable.erase( iter ); // If we have a path for the texture then // remove change notifications for it. //Path texPath = texture->getPath(); //if ( !texPath.isEmpty() ) //FS::RemoveChangeNotification( texPath, this, &GFXTextureManager::_onFileChanged ); } void GFXTextureManager::_onFileChanged( const Torque::Path &path ) { String pathNoExt = Torque::Path::Join( path.getRoot(), ':', path.getPath() ); pathNoExt = Torque::Path::Join( pathNoExt, '/', path.getFileName() ); // See if we've got it loaded. GFXTextureObject *obj = hashFind( pathNoExt ); if ( !obj || path != obj->getPath() ) return; Con::errorf( "[GFXTextureManager::_onFileChanged] : File changed [%s]", path.getFullPath().c_str() ); const U32 scalePower = getTextureDownscalePower( obj->mProfile ); if ( sDDSExt.equal( path.getExtension(), String::NoCase) ) { Resource dds = DDSFile::load( path, scalePower ); if ( dds ) _createTexture( dds, obj->mProfile, false, obj ); } else { Resource bmp = GBitmap::load( path ); if( bmp ) _createTexture( bmp, obj->mTextureLookupName, obj->mProfile, false, obj ); } } void GFXTextureManager::reloadTextures() { GFXTextureObject *tex = mListHead; while ( tex != NULL ) { const Torque::Path path( tex->mPath ); if ( !path.isEmpty() ) { const U32 scalePower = getTextureDownscalePower( tex->mProfile ); if ( sDDSExt.equal( path.getExtension(), String::NoCase ) ) { Resource dds = DDSFile::load( path, scalePower ); if ( dds ) _createTexture( dds, tex->mProfile, false, tex ); } else { Resource bmp = GBitmap::load( path ); if( bmp ) _createTexture( bmp, tex->mTextureLookupName, tex->mProfile, false, tex ); } } tex = tex->mNext; } } DefineEngineFunction( flushTextureCache, void, (),, "Releases all textures and resurrects the texture manager.\n" "@ingroup GFX\n" ) { if ( !GFX || !TEXMGR ) return; TEXMGR->zombify(); TEXMGR->resurrect(); } DefineEngineFunction( cleanupTexturePool, void, (),, "Release the unused pooled textures in texture manager freeing up video memory.\n" "@ingroup GFX\n" ) { if ( !GFX || !TEXMGR ) return; TEXMGR->cleanupPool(); } DefineEngineFunction( reloadTextures, void, (),, "Reload all the textures from disk.\n" "@ingroup GFX\n" ) { if ( !GFX || !TEXMGR ) return; TEXMGR->reloadTextures(); }