gfxTextureManager.cpp
Engine/source/gfx/gfxTextureManager.cpp
Public Functions
DefineEngineFunction(cleanupTexturePool , void , () , "Release the unused pooled textures in texture manager freeing up video <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">memory.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GFX\n</a>" )
DefineEngineFunction(flushTextureCache , void , () , "Releases all textures and resurrects the texture <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GFX\n</a>" )
DefineEngineFunction(reloadTextures , void , () , "Reload all the textures from <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">disk.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GFX\n</a>" )
Detailed Description
Public Variables
const String sDDSExt ("dds")
Public Functions
DefineEngineFunction(cleanupTexturePool , void , () , "Release the unused pooled textures in texture manager freeing up video <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">memory.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GFX\n</a>" )
DefineEngineFunction(flushTextureCache , void , () , "Releases all textures and resurrects the texture <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">manager.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GFX\n</a>" )
DefineEngineFunction(reloadTextures , void , () , "Reload all the textures from <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">disk.\n</a>" "@ingroup <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">GFX\n</a>" )
DefineEngineFunction(saveCompositeTexture , void , (const char *pathR, const char *pathG, const char *pathB, const char *pathA, const char *inputKeyString, const char *saveAs) , ("", "", "", "", "", "") , " File1, file2 , file3 , file4 , saveAs" )
1 2//----------------------------------------------------------------------------- 3// Copyright (c) 2012 GarageGames, LLC 4// 5// Permission is hereby granted, free of charge, to any person obtaining a copy 6// of this software and associated documentation files (the "Software"), to 7// deal in the Software without restriction, including without limitation the 8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9// sell copies of the Software, and to permit persons to whom the Software is 10// furnished to do so, subject to the following conditions: 11// 12// The above copyright notice and this permission notice shall be included in 13// all copies or substantial portions of the Software. 14// 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21// IN THE SOFTWARE. 22//----------------------------------------------------------------------------- 23 24#include "platform/platform.h" 25#include "gfx/gfxTextureManager.h" 26 27#include "gfx/gfxDevice.h" 28#include "gfx/gfxCardProfile.h" 29#include "gfx/gfxStringEnumTranslate.h" 30#include "gfx/bitmap/imageUtils.h" 31#include "core/strings/stringFunctions.h" 32#include "core/util/safeDelete.h" 33#include "core/resourceManager.h" 34#include "core/volume.h" 35#include "core/util/dxt5nmSwizzle.h" 36#include "console/consoleTypes.h" 37#include "console/engineAPI.h" 38#include "renderInstance/renderProbeMgr.h" 39 40using namespace Torque; 41 42//#define DEBUG_SPEW 43 44 45S32 GFXTextureManager::smTextureReductionLevel = 0; 46 47String GFXTextureManager::smMissingTexturePath(Con::getVariable("$Core::MissingTexturePath")); 48String GFXTextureManager::smUnavailableTexturePath(Con::getVariable("$Core::UnAvailableTexturePath")); 49String GFXTextureManager::smWarningTexturePath(Con::getVariable("$Core::WarningTexturePath")); 50String GFXTextureManager::smDefaultIrradianceCubemapPath(Con::getVariable("$Core::DefaultIrradianceCubemap")); 51String GFXTextureManager::smDefaultPrefilterCubemapPath(Con::getVariable("$Core::DefaultPrefilterCubemap")); 52String GFXTextureManager::smBRDFTexturePath(Con::getVariable("$Core::BRDFTexture")); 53 54GFXTextureManager::EventSignal GFXTextureManager::smEventSignal; 55 56static const String sDDSExt( "dds" ); 57 58void GFXTextureManager::init() 59{ 60 Con::addVariable( "$pref::Video::textureReductionLevel", TypeS32, &smTextureReductionLevel, 61 "The number of mipmap levels to drop on loaded textures to reduce " 62 "video memory usage. It will skip any textures that have been defined " 63 "as not allowing down scaling.\n" 64 "@ingroup GFX\n" ); 65 66 Con::addVariable( "$pref::Video::missingTexturePath", TypeRealString, &smMissingTexturePath, 67 "The file path of the texture to display when the requested texture is missing.\n" 68 "@ingroup GFX\n" ); 69 70 Con::addVariable( "$pref::Video::unavailableTexturePath", TypeRealString, &smUnavailableTexturePath, 71 "@brief The file path of the texture to display when the requested texture is unavailable.\n\n" 72 "Often this texture is used by GUI controls to indicate that the request image is unavailable.\n" 73 "@ingroup GFX\n" ); 74 75 Con::addVariable( "$pref::Video::warningTexturePath", TypeRealString, &smWarningTexturePath, 76 "The file path of the texture used to warn the developer.\n" 77 "@ingroup GFX\n" ); 78 79 Con::addVariable("$Core::DefaultIrradianceCubemap", TypeRealString, &smDefaultIrradianceCubemapPath, 80 "The file path of the texture used as the default irradiance cubemap for PBR.\n" 81 "@ingroup GFX\n"); 82 83 Con::addVariable("$Core::DefaultPrefilterCubemap", TypeRealString, &smDefaultPrefilterCubemapPath, 84 "The file path of the texture used as the default specular cubemap for PBR.\n" 85 "@ingroup GFX\n"); 86 87 Con::addVariable("$Core::BRDFTexture", TypeRealString, &smBRDFTexturePath, 88 "The file path of the texture used as the default irradiance cubemap for PBR.\n" 89 "@ingroup GFX\n"); 90} 91 92GFXTextureManager::GFXTextureManager() 93{ 94 mListHead = mListTail = NULL; 95 mTextureManagerState = GFXTextureManager::Living; 96 97 // Set up the hash table 98 mHashCount = 1023; 99 mHashTable = new GFXTextureObject *[mHashCount]; 100 for(U32 i = 0; i < mHashCount; i++) 101 mHashTable[i] = NULL; 102} 103 104GFXTextureManager::~GFXTextureManager() 105{ 106 if( mHashTable ) 107 SAFE_DELETE_ARRAY( mHashTable ); 108 109 mCubemapTable.clear(); 110} 111 112U32 GFXTextureManager::getTextureDownscalePower( GFXTextureProfile *profile ) 113{ 114 if ( !profile || profile->canDownscale() ) 115 return smTextureReductionLevel; 116 117 return 0; 118} 119 120bool GFXTextureManager::validateTextureQuality( GFXTextureProfile *profile, U32 &width, U32 &height ) 121{ 122 U32 scaleFactor = getTextureDownscalePower( profile ); 123 if ( scaleFactor == 0 ) 124 return true; 125 126 // Otherwise apply the appropriate scale... 127 width >>= scaleFactor; 128 height >>= scaleFactor; 129 130 return true; 131} 132 133void GFXTextureManager::kill() 134{ 135 AssertFatal( mTextureManagerState != GFXTextureManager::Dead, "Texture Manager already killed!" ); 136 137 // Release everything in the cache we can 138 // so we don't leak any textures. 139 cleanupCache(); 140 141 GFXTextureObject *curr = mListHead; 142 GFXTextureObject *temp; 143 144 // Actually delete all the textures we know about. 145 while( curr != NULL ) 146 { 147 temp = curr->mNext; 148 curr->kill(); 149 curr = temp; 150 } 151 152 mCubemapTable.clear(); 153 154 mTextureManagerState = GFXTextureManager::Dead; 155} 156 157void GFXTextureManager::zombify() 158{ 159 AssertFatal( mTextureManagerState != GFXTextureManager::Zombie, "Texture Manager already a zombie!" ); 160 161 // Notify everyone that cares about the zombification! 162 smEventSignal.trigger( GFXZombify ); 163 164 // Release unused pool textures. 165 cleanupPool(); 166 167 // Release everything in the cache we can. 168 cleanupCache(); 169 170 // Free all the device copies of the textures. 171 GFXTextureObject *temp = mListHead; 172 while( temp != NULL ) 173 { 174 freeTexture( temp, true ); 175 temp = temp->mNext; 176 } 177 178 // Finally, note our state. 179 mTextureManagerState = GFXTextureManager::Zombie; 180} 181 182void GFXTextureManager::resurrect() 183{ 184 // Reupload all the device copies of the textures. 185 GFXTextureObject *temp = mListHead; 186 187 while( temp != NULL ) 188 { 189 refreshTexture( temp ); 190 temp = temp->mNext; 191 } 192 193 // Notify callback registries. 194 smEventSignal.trigger( GFXResurrect ); 195 196 // Update our state. 197 mTextureManagerState = GFXTextureManager::Living; 198} 199 200void GFXTextureManager::cleanupPool() 201{ 202 PROFILE_SCOPE( GFXTextureManager_CleanupPool ); 203 204 TexturePoolMap::Iterator iter = mTexturePool.begin(); 205 for ( ; iter != mTexturePool.end(); ) 206 { 207 if ( iter->value->getRefCount() == 1 ) 208 { 209 // This texture is unreferenced, so take the time 210 // now to completely remove it from the pool. 211 TexturePoolMap::Iterator unref = iter; 212 ++iter; 213 unref->value = NULL; 214 mTexturePool.erase( unref ); 215 continue; 216 } 217 218 ++iter; 219 } 220} 221 222void GFXTextureManager::requestDeleteTexture( GFXTextureObject *texture ) 223{ 224 // If this is a non-cached texture then just really delete it. 225 if ( texture->mTextureLookupName.isEmpty() ) 226 { 227 delete texture; 228 return; 229 } 230 231 // Set the time and store it. 232 texture->mDeleteTime = Platform::getTime(); 233 mToDelete.push_back_unique( texture ); 234} 235 236void GFXTextureManager::cleanupCache( U32 secondsToLive ) 237{ 238 PROFILE_SCOPE( GFXTextureManager_CleanupCache ); 239 240 U32 killTime = Platform::getTime() - secondsToLive; 241 242 for ( U32 i=0; i < mToDelete.size(); ) 243 { 244 GFXTextureObject *tex = mToDelete[i]; 245 246 // If the texture was picked back up by a user 247 // then just remove it from the list. 248 if ( tex->getRefCount() != 0 ) 249 { 250 mToDelete.erase_fast( i ); 251 continue; 252 } 253 254 // If its time has expired delete it for real. 255 if ( tex->mDeleteTime <= killTime ) 256 { 257 //Con::errorf( "Killed texture: %s", tex->mTextureLookupName.c_str() ); 258 delete tex; 259 mToDelete.erase_fast( i ); 260 continue; 261 } 262 263 i++; 264 } 265} 266 267GFXTextureObject *GFXTextureManager::_lookupTexture( const char *hashName, const GFXTextureProfile *profile ) 268{ 269 GFXTextureObject *ret = hashFind( hashName ); 270 271 //compare just the profile flags and not the entire profile, names could be different but otherwise identical flags 272 if (ret && (ret->mProfile->compareFlags(*profile))) 273 return ret; 274 else if (ret) 275 Con::warnf("GFXTextureManager::_lookupTexture: Cached texture %s has a different profile flag", hashName); 276 277 return NULL; 278} 279 280GFXTextureObject *GFXTextureManager::_lookupTexture( const DDSFile *ddsFile, const GFXTextureProfile *profile ) 281{ 282 if( ddsFile->getTextureCacheString().isNotEmpty() ) 283 { 284 // Call _lookupTexture() 285 return _lookupTexture( ddsFile->getTextureCacheString(), profile ); 286 } 287 288 return NULL; 289} 290 291GFXTextureObject *GFXTextureManager::createTexture( GBitmap *bmp, const String &resourceName, GFXTextureProfile *profile, bool deleteBmp ) 292{ 293 AssertFatal(bmp, "GFXTextureManager::createTexture() - Got NULL bitmap!"); 294 295 GFXTextureObject *cacheHit = _lookupTexture( resourceName, profile ); 296 if( cacheHit != NULL) 297 { 298 // Con::errorf("Cached texture '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); 299 if (deleteBmp) 300 delete bmp; 301 return cacheHit; 302 } 303 304 return _createTexture( bmp, resourceName, profile, deleteBmp, NULL ); 305} 306 307GFXTextureObject *GFXTextureManager::_createTexture( GBitmap *bmp, 308 const String &resourceName, 309 GFXTextureProfile *profile, 310 bool deleteBmp, 311 GFXTextureObject *inObj ) 312{ 313 PROFILE_SCOPE( GFXTextureManager_CreateTexture_Bitmap ); 314 315 #ifdef DEBUG_SPEW 316 Platform::outputDebugString( "[GFXTextureManager] _createTexture (GBitmap) '%s'", 317 resourceName.c_str() 318 ); 319 #endif 320 321 // Massage the bitmap based on any resize rules. 322 U32 scalePower = getTextureDownscalePower( profile ); 323 324 GBitmap *realBmp = bmp; 325 U32 realWidth = bmp->getWidth(); 326 U32 realHeight = bmp->getHeight(); 327 328 if ( scalePower && 329 isPow2(bmp->getWidth()) && 330 isPow2(bmp->getHeight()) && 331 profile->canDownscale() ) 332 { 333 // We only work with power of 2 textures for now, so we 334 // don't have to worry about padding. 335 336 // We downscale the bitmap on the CPU... this is the reason 337 // you should be using DDS which already has good looking mips. 338 GBitmap *padBmp = bmp; 339 padBmp->extrudeMipLevels(); 340 scalePower = getMin( scalePower, padBmp->getNumMipLevels() - 1 ); 341 342 realWidth = getMax( (U32)1, padBmp->getWidth() >> scalePower ); 343 realHeight = getMax( (U32)1, padBmp->getHeight() >> scalePower ); 344 realBmp = new GBitmap( realWidth, realHeight, false, bmp->getFormat() ); 345 346 // Copy to the new bitmap... 347 dMemcpy( realBmp->getWritableBits(), 348 padBmp->getBits(scalePower), 349 padBmp->getBytesPerPixel() * realWidth * realHeight ); 350 351 // This line is commented out because createPaddedBitmap is commented out. 352 // If that line is added back in, this line should be added back in. 353 // delete padBmp; 354 } 355 356 // Call the internal create... (use the real* variables now, as they 357 // reflect the reality of the texture we are creating.) 358 U32 numMips = 0; 359 GFXFormat realFmt = realBmp->getFormat(); 360 _validateTexParams( realWidth, realHeight, profile, numMips, realFmt ); 361 362 GFXTextureObject *ret; 363 if ( inObj ) 364 { 365 // If the texture has changed in dimensions 366 // then we need to recreate it. 367 if ( inObj->getWidth() != realWidth || 368 inObj->getHeight() != realHeight || 369 inObj->getFormat() != realFmt ) 370 ret = _createTextureObject( realHeight, realWidth, 0, realFmt, profile, numMips, false, 0, inObj ); 371 else 372 ret = inObj; 373 } 374 else 375 ret = _createTextureObject(realHeight, realWidth, 0, realFmt, profile, numMips ); 376 377 if(!ret) 378 { 379 SAFE_DELETE(realBmp); 380 381 Con::errorf("GFXTextureManager - failed to create texture (1) for '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); 382 return NULL; 383 } 384 385 // Extrude mip levels 386 // Don't do this for fonts! 387 if( ret->mMipLevels > 1 && ( realBmp->getNumMipLevels() == 1 ) && ( realBmp->getFormat() != GFXFormatA8 ) && 388 isPow2( realBmp->getHeight() ) && isPow2( realBmp->getWidth() ) && !profile->noMip() ) 389 { 390 // NOTE: This should really be done by extruding mips INTO a DDS file instead 391 // of modifying the gbitmap 392 realBmp->extrudeMipLevels(false); 393 } 394 395 // If _validateTexParams kicked back a different format, than there needs to be 396 // a conversion unless it's a sRGB format 397 DDSFile *bmpDDS = NULL; 398 if( realBmp->getFormat() != realFmt && !profile->isSRGB() ) 399 { 400 const GFXFormat oldFmt = realBmp->getFormat(); 401 402 // TODO: Set it up so that ALL format conversions use DDSFile. Rip format 403 // switching out of GBitmap entirely. 404 if( !realBmp->setFormat( realFmt ) ) 405 { 406 // This is not the ideal implementation... 407 bmpDDS = DDSFile::createDDSFileFromGBitmap( realBmp ); 408 409 bool convSuccess = false; 410 411 if( bmpDDS != NULL ) 412 { 413 // This shouldn't live here, I don't think 414 switch( realFmt ) 415 { 416 case GFXFormatBC1: 417 case GFXFormatBC2: 418 case GFXFormatBC3: 419 // If this is a Normal Map profile, than the data needs to be conditioned 420 // to use the swizzle trick 421 if( ret->mProfile->getType() == GFXTextureProfile::NormalMap ) 422 { 423 PROFILE_START(DXT_DXTNMSwizzle); 424 static DXT5nmSwizzle sDXT5nmSwizzle; 425 ImageUtil::swizzleDDS( bmpDDS, sDXT5nmSwizzle ); 426 PROFILE_END(); 427 } 428 429 convSuccess = ImageUtil::ddsCompress( bmpDDS, realFmt ); 430 break; 431 default: 432 AssertFatal(false, "Attempting to convert to a non-DXT format"); 433 break; 434 } 435 } 436 437 if( !convSuccess ) 438 { 439 Con::errorf( "[GFXTextureManager]: Failed to change source format from %s to %s. Cannot create texture.", 440 GFXStringTextureFormat[oldFmt], GFXStringTextureFormat[realFmt] ); 441 delete bmpDDS; 442 443 return NULL; 444 } 445 } 446#ifdef TORQUE_DEBUG 447 else 448 { 449 //Con::warnf( "[GFXTextureManager]: Changed bitmap format from %s to %s.", 450 // GFXStringTextureFormat[oldFmt], GFXStringTextureFormat[realFmt] ); 451 } 452#endif 453 } 454 455 // Call the internal load... 456 if( ( bmpDDS == NULL && !_loadTexture( ret, realBmp ) ) || // If we aren't doing a DDS format change, use bitmap load 457 ( bmpDDS != NULL && !_loadTexture( ret, bmpDDS ) ) ) // If there is a DDS, than load that instead. A format change took place. 458 { 459 Con::errorf("GFXTextureManager - failed to load GBitmap for '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); 460 return NULL; 461 } 462 463 // Do statistics and book-keeping... 464 465 // - info for the texture... 466 ret->mTextureLookupName = resourceName; 467 ret->mBitmapSize.set(realWidth, realHeight,0); 468 469#ifdef TORQUE_DEBUG 470 if (resourceName.isNotEmpty()) 471 ret->mDebugDescription = resourceName; 472 else 473 ret->mDebugDescription = "Anonymous Texture Object"; 474 475#endif 476 477 if(profile->doStoreBitmap()) 478 { 479 // NOTE: may store a downscaled copy! 480 SAFE_DELETE( ret->mBitmap ); 481 SAFE_DELETE( ret->mDDS ); 482 483 if( bmpDDS == NULL ) 484 ret->mBitmap = new GBitmap( *realBmp ); 485 else 486 ret->mDDS = bmpDDS; 487 } 488 else 489 { 490 // Delete the DDS if we made one 491 SAFE_DELETE( bmpDDS ); 492 } 493 494 if ( !inObj ) 495 _linkTexture( ret ); 496 497 // - output debug info? 498 // Save texture for debug purpose 499 // static int texId = 0; 500 // char buff[256]; 501 // dSprintf(buff, sizeof(buff), "tex_%d", texId++); 502 // bmp->writePNGDebug(buff); 503 // texId++; 504 505 // Before we delete the bitmap save our transparency flag 506 ret->mHasTransparency = realBmp->getHasTransparency(); 507 508 // Some final cleanup... 509 if(realBmp != bmp) 510 SAFE_DELETE(realBmp); 511 if (deleteBmp) 512 SAFE_DELETE(bmp); 513 514 // Return the new texture! 515 return ret; 516} 517 518GFXTextureObject *GFXTextureManager::createTexture( DDSFile *dds, GFXTextureProfile *profile, bool deleteDDS ) 519{ 520 AssertFatal(dds, "GFXTextureManager::createTexture() - Got NULL dds!"); 521 522 // Check the cache first... 523 GFXTextureObject *cacheHit = _lookupTexture( dds, profile ); 524 if ( cacheHit ) 525 { 526 // Con::errorf("Cached texture '%s'", (fileName.isNotEmpty() ? fileName.c_str() : "unknown")); 527 if( deleteDDS ) 528 delete dds; 529 530 return cacheHit; 531 } 532 533 return _createTexture( dds, profile, deleteDDS, NULL ); 534} 535 536GFXTextureObject *GFXTextureManager::_createTexture( DDSFile *dds, 537 GFXTextureProfile *profile, 538 bool deleteDDS, 539 GFXTextureObject *inObj ) 540{ 541 PROFILE_SCOPE( GFXTextureManager_CreateTexture_DDS ); 542 543 const char *fileName = dds->getTextureCacheString(); 544 if( !fileName ) 545 fileName = "unknown"; 546 547 #ifdef DEBUG_SPEW 548 Platform::outputDebugString( "[GFXTextureManager] _createTexture (DDS) '%s'", 549 fileName 550 ); 551 #endif 552 553 // Ignore padding from the profile. 554 U32 numMips = dds->mMipMapCount; 555 GFXFormat fmt = dds->mFormat; 556 _validateTexParams( dds->getHeight(), dds->getWidth(), profile, numMips, fmt ); 557 558 if( fmt != dds->mFormat && !profile->isSRGB()) 559 { 560 Con::errorf( "GFXTextureManager - failed to validate texture parameters for DDS file '%s'", fileName ); 561 return NULL; 562 } 563 564 // Call the internal create... (use the real* variables now, as they 565 // reflect the reality of the texture we are creating.) 566 567 GFXTextureObject *ret; 568 if ( inObj ) 569 { 570 // If the texture has changed in dimensions 571 // then we need to recreate it. 572 if ( inObj->getWidth() != dds->getWidth() || 573 inObj->getHeight() != dds->getHeight() || 574 inObj->getFormat() != fmt || 575 inObj->getMipLevels() != numMips ) 576 ret = _createTextureObject( dds->getHeight(), dds->getWidth(), 0, 577 fmt, profile, numMips, 578 true, 0, inObj ); 579 else 580 ret = inObj; 581 } 582 else 583 ret = _createTextureObject( dds->getHeight(), dds->getWidth(), 0, 584 fmt, profile, numMips, true ); 585 586 587 if(!ret) 588 { 589 Con::errorf("GFXTextureManager - failed to create texture (1) for '%s' DDSFile.", fileName); 590 return NULL; 591 } 592 593 // Call the internal load... 594 if(!_loadTexture(ret, dds)) 595 { 596 Con::errorf("GFXTextureManager - failed to load DDS for '%s'", fileName); 597 return NULL; 598 } 599 600 // Do statistics and book-keeping... 601 602 // - info for the texture... 603 ret->mTextureLookupName = dds->getTextureCacheString(); 604 ret->mBitmapSize.set( dds->mWidth, dds->mHeight, 0 ); 605 606#ifdef TORQUE_DEBUG 607 ret->mDebugDescription = fileName; 608#endif 609 610 if(profile->doStoreBitmap()) 611 { 612 // NOTE: may store a downscaled copy! 613 SAFE_DELETE( ret->mBitmap ); 614 SAFE_DELETE( ret->mDDS ); 615 616 ret->mDDS = new DDSFile( *dds ); 617 } 618 619 if ( !inObj ) 620 _linkTexture( ret ); 621 622 // - output debug info? 623 // Save texture for debug purpose 624 // static int texId = 0; 625 // char buff[256]; 626 // dSprintf(buff, sizeof(buff), "tex_%d", texId++); 627 // bmp->writePNGDebug(buff); 628 // texId++; 629 630 // Save our transparency flag 631 ret->mHasTransparency = dds->getHasTransparency(); 632 633 if( deleteDDS ) 634 delete dds; 635 636 // Return the new texture! 637 return ret; 638} 639 640GFXTextureObject *GFXTextureManager::createTexture( const Torque::Path &path, GFXTextureProfile *profile ) 641{ 642 PROFILE_SCOPE( GFXTextureManager_createTexture ); 643 644 // Resource handles used for loading. Hold on to them 645 // throughout this function so that change notifications 646 // don't get added, then removed, and then re-added. 647 648 Resource< DDSFile> dds; 649 Resource< GBitmap> bitmap; 650 651 // We need to handle path's that have had "incorrect" 652 // extensions parsed out of the file name 653 Torque::Path correctPath = validatePath(path); 654 655 // Check the cache first... 656 String pathNoExt = Torque::Path::Join( correctPath.getRoot(), ':', correctPath.getPath() ); 657 pathNoExt = Torque::Path::Join( pathNoExt, '/', correctPath.getFileName() ); 658 659 GFXTextureObject *retTexObj = _lookupTexture( pathNoExt, profile ); 660 if( retTexObj ) 661 return retTexObj; 662 663 const U32 scalePower = getTextureDownscalePower( profile ); 664 665 // If this is a valid file (has an extension) than load it 666 Path realPath; 667 if( Torque::FS::IsFile( correctPath ) ) 668 { 669 // Check for DDS 670 if( sDDSExt.equal(correctPath.getExtension(), String::NoCase ) ) 671 { 672 dds = DDSFile::load( correctPath, scalePower ); 673 if( dds != NULL ) 674 { 675 realPath = dds.getPath(); 676 retTexObj = createTexture( dds, profile, false ); 677 } 678 } 679 else // Let GBitmap take care of it 680 { 681 bitmap = GBitmap::load( correctPath ); 682 if( bitmap != NULL ) 683 { 684 realPath = bitmap.getPath(); 685 retTexObj = createTexture( bitmap, pathNoExt, profile, false ); 686 } 687 } 688 } 689 else 690 { 691 // NOTE -- We should probably remove the code from GBitmap that tries different 692 // extensions for things GBitmap loads, and move it here. I think it should 693 // be a bit more involved than just a list of extensions. Some kind of 694 // extension registration thing, maybe. 695 696 // Check to see if there is a .DDS file with this name (if no extension is provided) 697 Torque::Path tryDDSPath = pathNoExt; 698 if( tryDDSPath.getExtension().isNotEmpty() ) 699 tryDDSPath.setFileName( tryDDSPath.getFullFileName() ); 700 tryDDSPath.setExtension( sDDSExt ); 701 702 if( Torque::FS::IsFile( tryDDSPath ) ) 703 { 704 dds = DDSFile::load( tryDDSPath, scalePower ); 705 if( dds != NULL ) 706 { 707 realPath = dds.getPath(); 708 retTexObj = createTexture( dds, profile, false ); 709 } 710 } 711 712 // Otherwise, retTexObj stays NULL, and fall through to the generic GBitmap 713 // load. 714 } 715 716 // If we still don't have a texture object yet, feed the correctPath to GBitmap and 717 // it will try a bunch of extensions 718 if( retTexObj == NULL ) 719 { 720 // Find and load the texture. 721 bitmap = GBitmap::load( correctPath ); 722 723 if ( bitmap != NULL ) 724 { 725 realPath = bitmap.getPath(); 726 retTexObj = createTexture( bitmap, pathNoExt, profile, false ); 727 } 728 } 729 730 if ( retTexObj ) 731 { 732 // Store the path for later use. 733 retTexObj->mPath = realPath; 734 735 // Register the texture file for change notifications. 736 FS::AddChangeNotification( retTexObj->getPath(), this, &GFXTextureManager::_onFileChanged ); 737 } 738 739 // Could put in a final check for 'retTexObj == NULL' here as an error message. 740 741 return retTexObj; 742} 743 744GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, void *pixels, GFXFormat format, GFXTextureProfile *profile ) 745{ 746 // For now, stuff everything into a GBitmap and pass it off... This may need to be revisited -- BJG 747 GBitmap *bmp = new GBitmap(width, height, 0, format); 748 dMemcpy(bmp->getWritableBits(), pixels, width * height * bmp->getBytesPerPixel()); 749 750 return createTexture( bmp, String::EmptyString, profile, true ); 751} 752 753GFXTextureObject *GFXTextureManager::createTexture( U32 width, U32 height, GFXFormat format, GFXTextureProfile *profile, U32 numMipLevels, S32 antialiasLevel ) 754{ 755 // Deal with sizing issues... 756 U32 localWidth = width; 757 U32 localHeight = height; 758 759 // TODO: Format check HERE! -patw 760 761 validateTextureQuality(profile, localWidth, localHeight); 762 763 U32 numMips = numMipLevels; 764 GFXFormat checkFmt = format; 765 _validateTexParams( localWidth, localHeight, profile, numMips, checkFmt ); 766 767 //check to see if we've handled the mips just now, and if not, then handle them here 768 if (numMips == numMipLevels && (localWidth != width || localHeight != height)) 769 { 770 numMips = mFloor(mLog2(mMax(localWidth, localHeight))) + 1; 771 } 772 773// AssertFatal( checkFmt == format, "Anonymous texture didn't get the format it wanted." ); 774 775 GFXTextureObject *outTex = NULL; 776 777 // If this is a pooled profile then look there first. 778 if ( profile->isPooled() ) 779 { 780 outTex = _findPooledTexure( localWidth, localHeight, checkFmt, 781 profile, numMips, antialiasLevel ); 782 783 // If we got a pooled texture then its 784 // already setup... just return it. 785 if ( outTex ) 786 return outTex; 787 } 788 789 // Create the texture if we didn't get one from the pool. 790 if ( !outTex ) 791 { 792 outTex = _createTextureObject( localHeight, localWidth, 0, format, profile, numMips, false, antialiasLevel ); 793 794 // Make sure we add it to the pool. 795 if ( outTex && profile->isPooled() ) 796 mTexturePool.insertEqual( profile, outTex ); 797 } 798 799 if ( !outTex ) 800 { 801 Con::errorf("GFXTextureManager - failed to create anonymous texture."); 802 return NULL; 803 } 804 805 // And do book-keeping... 806 // - texture info 807 outTex->mBitmapSize.set(localWidth, localHeight, 0); 808 outTex->mAntialiasLevel = antialiasLevel; 809 810 // PWTODO: Need to assign this a lookup name before _linkTexture() is called 811 // otherwise it won't get a hash insert call 812 813 _linkTexture( outTex ); 814 815 return outTex; 816} 817 818GFXTextureObject *GFXTextureManager::createTexture( U32 width, 819 U32 height, 820 U32 depth, 821 GFXFormat format, 822 GFXTextureProfile *profile, 823 U32 numMipLevels) 824{ 825 PROFILE_SCOPE( GFXTextureManager_CreateTexture_3D ); 826 827 // Create texture... 828 GFXTextureObject *ret = _createTextureObject( height, width, depth, format, profile, numMipLevels ); 829 830 if(!ret) 831 { 832 Con::errorf("GFXTextureManager - failed to create anonymous texture."); 833 return NULL; 834 } 835 836 // And do book-keeping... 837 // - texture info 838 ret->mBitmapSize.set( width, height, depth ); 839 840 _linkTexture( ret ); 841 842 843 // Return the new texture! 844 return ret; 845} 846 847Torque::Path GFXTextureManager::validatePath(const Torque::Path &path) 848{ 849 // We need to handle path's that have had "incorrect" 850 // extensions parsed out of the file name 851 Torque::Path correctPath = path; 852 853 bool textureExt = false; 854 855 // Easiest case to handle is when there isn't an extension 856 if (path.getExtension().isEmpty()) 857 textureExt = true; 858 859 // Since "dds" isn't registered with GBitmap currently we 860 // have to test it separately 861 if (sDDSExt.equal(path.getExtension(), String::NoCase)) 862 textureExt = true; 863 864 // Now loop through the rest of the GBitmap extensions 865 // to see if we have any matches 866 for (U32 i = 0; i < GBitmap::sRegistrations.size(); i++) 867 { 868 // If we have gotten a match (either in this loop or before) 869 // then we can exit 870 if (textureExt) 871 break; 872 873 const GBitmap::Registration ® = GBitmap::sRegistrations[i]; 874 const Vector<String> &extensions = reg.extensions; 875 876 for (U32 j = 0; j < extensions.size(); ++j) 877 { 878 if (extensions[j].equal(path.getExtension(), String::NoCase)) 879 { 880 // Found a valid texture extension 881 textureExt = true; 882 break; 883 } 884 } 885 } 886 887 // If we didn't find a valid texture extension then assume that 888 // the parsed out "extension" was actually intended to be part of 889 // the texture name so add it back 890 if (!textureExt) 891 { 892 correctPath.setFileName(Torque::Path::Join(path.getFileName(), '.', path.getExtension())); 893 correctPath.setExtension(String::EmptyString); 894 } 895 return correctPath; 896} 897 898GBitmap *GFXTextureManager::loadUncompressedTexture(const Torque::Path &path, GFXTextureProfile *profile, U32 width, U32 height, bool genMips) 899{ 900 GBitmap* inBitmap = loadUncompressedTexture(path, &GFXTexturePersistentProfile); 901 902 if (inBitmap == NULL) 903 { 904 Con::warnf("GFXTextureManager::loadUncompressedTexture unable to load texture: %s", path.getFullPath().c_str()); 905 return NULL; 906 } 907 908 // Set the format so we don't have to handle which channels are where. 909 if (!inBitmap->setFormat(GFXFormatR8G8B8A8)) 910 { 911 Con::warnf("GFXTextureManager::loadUncompressedTexture unable to handle texture format: %s", path.getFullPath().c_str()); 912 return NULL; 913 } 914 915 GBitmap* outBmp = new GBitmap(width, height, true, GFXFormatR8G8B8A8); 916 917 U8* oBits = (U8*)outBmp->getWritableBits(); 918 for (S32 y = 0; y < width; y++) 919 { 920 for (S32 x = 0; x < height; x++) 921 { 922 ColorI texelColor = inBitmap->sampleTexel(x / F32(width), y / F32(height), true).toColorI(true); 923 924 oBits[(y * width + x) * 4] = texelColor.red; 925 oBits[(y * width + x) * 4 + 1] = texelColor.green; 926 oBits[(y * width + x) * 4 + 2] = texelColor.blue; 927 oBits[(y * width + x) * 4 + 3] = texelColor.alpha; 928 } 929 } 930 931 if (genMips) 932 outBmp->extrudeMipLevels(); 933 934 return outBmp; 935} 936 937GBitmap *GFXTextureManager::loadUncompressedTexture(const Torque::Path &path, GFXTextureProfile *profile) 938{ 939 PROFILE_SCOPE(GFXTextureManager_loadUncompressedTexture); 940 941 GBitmap *retBitmap = NULL; 942 943 // Resource handles used for loading. Hold on to them 944 // throughout this function so that change notifications 945 // don't get added, then removed, and then re-added. 946 947 Resource< DDSFile> dds; 948 Resource< GBitmap> bitmap; 949 950 // We need to handle path's that have had "incorrect" 951 // extensions parsed out of the file name 952 Torque::Path correctPath = validatePath(path); 953 954 U32 scalePower = profile ? getTextureDownscalePower(profile) : 0; 955 956 // Check the cache first... 957 String pathNoExt = Torque::Path::Join(correctPath.getRoot(), ':', correctPath.getPath()); 958 pathNoExt = Torque::Path::Join(pathNoExt, '/', correctPath.getFileName()); 959 960 // If this is a valid file (has an extension) than load it 961 Path realPath; 962 if (Torque::FS::IsFile(correctPath)) 963 { 964 PROFILE_SCOPE(GFXTextureManager_loadUncompressedTexture_INNNER1) 965 // Check for DDS 966 if (sDDSExt.equal(correctPath.getExtension(), String::NoCase)) 967 { 968 dds = DDSFile::load(correctPath, scalePower); 969 if (dds != NULL) 970 { 971 realPath = dds.getPath(); 972 retBitmap = new GBitmap(); 973 if (!dds->decompressToGBitmap(retBitmap)) 974 { 975 delete retBitmap; 976 retBitmap = NULL; 977 } 978 } 979 } 980 else // Let GBitmap take care of it 981 { 982 bitmap = GBitmap::load(correctPath); 983 if (bitmap != NULL) 984 { 985 realPath = bitmap.getPath(); 986 retBitmap = new GBitmap(*bitmap); 987 988 if (scalePower && 989 isPow2(retBitmap->getWidth()) && 990 isPow2(retBitmap->getHeight()) && 991 profile->canDownscale()) 992 { 993 retBitmap->extrudeMipLevels(); 994 retBitmap->chopTopMips(scalePower); 995 } 996 } 997 } 998 } 999 else 1000 { 1001 PROFILE_SCOPE(GFXTextureManager_loadUncompressedTexture_INNNER2) 1002 // NOTE -- We should probably remove the code from GBitmap that tries different 1003 // extensions for things GBitmap loads, and move it here. I think it should 1004 // be a bit more involved than just a list of extensions. Some kind of 1005 // extension registration thing, maybe. 1006 1007 // Check to see if there is a .DDS file with this name (if no extension is provided) 1008 Torque::Path tryDDSPath = pathNoExt; 1009 if (tryDDSPath.getExtension().isNotEmpty()) 1010 tryDDSPath.setFileName(tryDDSPath.getFullFileName()); 1011 tryDDSPath.setExtension(sDDSExt); 1012 1013 if (Torque::FS::IsFile(tryDDSPath)) 1014 { 1015 dds = DDSFile::load(tryDDSPath, scalePower); 1016 if (dds != NULL) 1017 { 1018 realPath = dds.getPath(); 1019 // Decompress dds into the GBitmap 1020 retBitmap = new GBitmap(); 1021 if (!dds->decompressToGBitmap(retBitmap)) 1022 { 1023 delete retBitmap; 1024 retBitmap = NULL; 1025 } 1026 } 1027 } 1028 1029 // Otherwise, retTexObj stays NULL, and fall through to the generic GBitmap 1030 // load. 1031 } 1032 1033 // If we still don't have a texture object yet, feed the correctPath to GBitmap and 1034 // it will try a bunch of extensions 1035 if (retBitmap == NULL) 1036 { 1037 PROFILE_SCOPE(GFXTextureManager_loadUncompressedTexture_INNNER3) 1038 // Find and load the texture. 1039 bitmap = GBitmap::load(correctPath); 1040 1041 if (bitmap != NULL) 1042 { 1043 retBitmap = new GBitmap(*bitmap); 1044 1045 if (scalePower && 1046 isPow2(retBitmap->getWidth()) && 1047 isPow2(retBitmap->getHeight()) && 1048 profile->canDownscale()) 1049 { 1050 retBitmap->extrudeMipLevels(); 1051 retBitmap->chopTopMips(scalePower); 1052 } 1053 } 1054 } 1055 1056 return retBitmap; 1057} 1058 1059GFXTextureObject *GFXTextureManager::createCompositeTexture(const Torque::Path &pathR, const Torque::Path &pathG, const Torque::Path &pathB, const Torque::Path &pathA, U32 inputKey[4], 1060 GFXTextureProfile *profile) 1061{ 1062 PROFILE_SCOPE(GFXTextureManager_createCompositeTexture); 1063 1064 String inputKeyStr = String::ToString("%d%d%d%d", inputKey[0], inputKey[1], inputKey[2], inputKey[3]); 1065 1066 String resourceTag = pathR.getFileName() + pathG.getFileName() + pathB.getFileName() + pathA.getFileName() + inputKeyStr; //associate texture object with a key combo 1067 1068 GFXTextureObject *cacheHit = _lookupTexture(resourceTag, profile); 1069 if (cacheHit != NULL) return cacheHit; 1070 1071 Torque::Path lastValidPath = ""; 1072 GBitmap*bitmap[4]; 1073 1074 if (!pathR.isEmpty()) 1075 { 1076 bitmap[0] = loadUncompressedTexture(pathR, profile); 1077 lastValidPath = pathR; 1078 } 1079 else 1080 bitmap[0] = NULL; 1081 1082 if (!pathG.isEmpty()) 1083 { 1084 bitmap[1] = loadUncompressedTexture(pathG, profile); 1085 lastValidPath = pathG; 1086 } 1087 else 1088 bitmap[1] = NULL; 1089 1090 if (!pathB.isEmpty()) 1091 { 1092 bitmap[2] = loadUncompressedTexture(pathB, profile); 1093 lastValidPath = pathB; 1094 } 1095 else 1096 bitmap[2] = NULL; 1097 1098 if (!pathA.isEmpty()) 1099 { 1100 bitmap[3] = loadUncompressedTexture(pathA, profile); 1101 lastValidPath = pathA; 1102 } 1103 else 1104 bitmap[3] = NULL; 1105 1106 1107 Path realPath; 1108 GFXTextureObject *retTexObj = NULL; 1109 realPath = validatePath(lastValidPath); //associate path with last valid channel texture in. 1110 1111 retTexObj = createCompositeTexture(bitmap, inputKey, resourceTag, profile, false); 1112 1113 if (retTexObj) 1114 { 1115 // Store the path for later use. 1116 retTexObj->mPath = resourceTag; 1117 1118 // Register the texture file for change notifications. 1119 FS::AddChangeNotification(retTexObj->getPath(), this, &GFXTextureManager::_onFileChanged); 1120 } 1121 1122 // Could put in a final check for 'retTexObj == NULL' here as an error message. 1123 for (U32 i = 0; i < 4; i++) 1124 { 1125 if (bitmap[i]) 1126 { 1127 bitmap[i]->deleteImage(); 1128 delete bitmap[i]; 1129 } 1130 } 1131 return retTexObj; 1132} 1133 1134void GFXTextureManager::saveCompositeTexture(const Torque::Path &pathR, const Torque::Path &pathG, const Torque::Path &pathB, const Torque::Path &pathA, U32 inputKey[4], 1135 const Torque::Path &saveAs,GFXTextureProfile *profile) 1136{ 1137 PROFILE_SCOPE(GFXTextureManager_saveCompositeTexture); 1138 1139 String inputKeyStr = String::ToString("%d%d%d%d", inputKey[0], inputKey[1], inputKey[2], inputKey[3]); 1140 1141 String resourceTag = pathR.getFileName() + pathG.getFileName() + pathB.getFileName() + pathA.getFileName() + inputKeyStr; //associate texture object with a key combo 1142 1143 GFXTextureObject *cacheHit = _lookupTexture(resourceTag, profile); 1144 if (cacheHit != NULL) 1145 { 1146 cacheHit->dumpToDisk("png", saveAs.getFullPath()); 1147 return; 1148 } 1149 1150 Torque::Path lastValidPath = ""; 1151 GBitmap* bitmap[4]; 1152 1153 if (!pathR.isEmpty()) 1154 { 1155 bitmap[0] = loadUncompressedTexture(pathR, profile); 1156 lastValidPath = pathR; 1157 } 1158 else 1159 bitmap[0] = NULL; 1160 1161 if (!pathG.isEmpty()) 1162 { 1163 bitmap[1] = loadUncompressedTexture(pathG, profile); 1164 lastValidPath = pathG; 1165 } 1166 else 1167 bitmap[1] = NULL; 1168 1169 if (!pathB.isEmpty()) 1170 { 1171 bitmap[2] = loadUncompressedTexture(pathB, profile); 1172 lastValidPath = pathB; 1173 } 1174 else 1175 bitmap[2] = NULL; 1176 1177 if (!pathA.isEmpty()) 1178 { 1179 bitmap[3] = loadUncompressedTexture(pathA, profile); 1180 lastValidPath = pathA; 1181 } 1182 else 1183 bitmap[3] = NULL; 1184 1185 Path realPath; 1186 GFXTextureObject *retTexObj = NULL; 1187 realPath = validatePath(lastValidPath); //associate path with last valid channel texture in. 1188 1189 retTexObj = createCompositeTexture(bitmap, inputKey, resourceTag, profile, false); 1190 if (retTexObj != NULL) 1191 retTexObj->dumpToDisk("png", saveAs.getFullPath()); 1192 return; 1193} 1194 1195DefineEngineFunction(saveCompositeTexture, void, (const char* pathR, const char* pathG, const char* pathB, const char* pathA, 1196 const char * inputKeyString, const char* saveAs), 1197 ("", "", "", "", "", ""), "File1,file2,file3,file4,[chanels for r g b and a locations],saveAs") 1198{ 1199 U32 inputKey[4] = {0,0,0,0}; 1200 1201 if (String::compare(inputKeyString, "") != 0) 1202 { 1203 dSscanf(inputKeyString, "%i %i %i %i", &inputKey[0], &inputKey[1], &inputKey[2], &inputKey[3]); 1204 } 1205 GFX->getTextureManager()->saveCompositeTexture(pathR, pathG, pathB, pathA, inputKey, saveAs, &GFXTexturePersistentProfile); 1206} 1207 1208GFXTextureObject *GFXTextureManager::createCompositeTexture(GBitmap*bmp[4], U32 inputKey[4], 1209 const String &resourceName, GFXTextureProfile *profile, bool deleteBmp) 1210{ 1211 S32 lastValidTex = -1; 1212 for (U32 i = 0; i < 4; i++) 1213 { 1214 if (bmp[i]) 1215 lastValidTex = i; 1216 } 1217 1218 if (lastValidTex == -1) 1219 { 1220 Con::errorf(ConsoleLogEntry::General, "GFXTextureManager::createCompositeTexture() - Got all NULL bitmaps!"); 1221 return NULL; 1222 } 1223 1224 GFXTextureObject* cacheHit = _lookupTexture(resourceName, profile); 1225 if (cacheHit != NULL) 1226 { 1227 // Con::errorf("Cached texture '%s'", (resourceName.isNotEmpty() ? resourceName.c_str() : "unknown")); 1228 return cacheHit; 1229 } 1230 1231 U8 rChan, gChan, bChan, aChan; 1232 GBitmap *outBitmap = new GBitmap(); 1233 outBitmap->allocateBitmap(bmp[lastValidTex]->getWidth(), bmp[lastValidTex]->getHeight(),false, GFXFormatR8G8B8A8); 1234 //pack additional bitmaps into the origional 1235 for (U32 x = 0; x < bmp[lastValidTex]->getWidth(); x++) 1236 { 1237 for (U32 y = 0; y < bmp[lastValidTex]->getHeight(); y++) 1238 { 1239 if (bmp[0]) 1240 rChan = bmp[0]->getChanelValueAt(x, y, inputKey[0]); 1241 else 1242 rChan = 255; 1243 1244 if (bmp[1]) 1245 gChan = bmp[1]->getChanelValueAt(x, y, inputKey[1]); 1246 else 1247 gChan = 255; 1248 1249 if (bmp[2]) 1250 bChan = bmp[2]->getChanelValueAt(x, y, inputKey[2]); 1251 else 1252 bChan = 255; 1253 1254 if (bmp[3]) 1255 aChan = bmp[3]->getChanelValueAt(x, y, inputKey[3]); 1256 else 1257 aChan = 255; 1258 1259 outBitmap->setColor(x, y, ColorI(rChan, gChan, bChan, aChan)); 1260 } 1261 } 1262 1263 if (deleteBmp) 1264 { 1265 delete[] bmp; 1266 } 1267 1268 GFXTextureObject * ret= _createTexture(outBitmap, resourceName, profile, deleteBmp, NULL); 1269 delete outBitmap; 1270 return ret; 1271} 1272 1273GFXTextureObject* GFXTextureManager::_findPooledTexure( U32 width, 1274 U32 height, 1275 GFXFormat format, 1276 GFXTextureProfile *profile, 1277 U32 numMipLevels, 1278 S32 antialiasLevel ) 1279{ 1280 PROFILE_SCOPE( GFXTextureManager_FindPooledTexure ); 1281 1282 GFXTextureObject *outTex; 1283 1284 // First see if we have a free one in the pool. 1285 TexturePoolMap::Iterator iter = mTexturePool.find( profile ); 1286 for ( ; iter != mTexturePool.end() && iter->key == profile; iter++ ) 1287 { 1288 outTex = iter->value; 1289 1290 // If the reference count is 1 then we're the only 1291 // ones holding on to this texture and we can hand 1292 // it out if the size matches... else its in use. 1293 if ( outTex->getRefCount() != 1 ) 1294 continue; 1295 1296 // Check for a match... if so return it. The assignment 1297 // to a GFXTexHandle will take care of incrementing the 1298 // reference count and keeping it from being handed out 1299 // to anyone else. 1300 if ( outTex->getFormat() == format && 1301 outTex->getWidth() == width && 1302 outTex->getHeight() == height && 1303 outTex->getMipLevels() == numMipLevels && 1304 outTex->mAntialiasLevel == antialiasLevel ) 1305 return outTex; 1306 } 1307 1308 return NULL; 1309} 1310 1311void GFXTextureManager::hashInsert( GFXTextureObject *object ) 1312{ 1313 if ( object->mTextureLookupName.isEmpty() ) 1314 return; 1315 1316 U32 key = object->mTextureLookupName.getHashCaseInsensitive() % mHashCount; 1317 object->mHashNext = mHashTable[key]; 1318 mHashTable[key] = object; 1319} 1320 1321void GFXTextureManager::hashRemove( GFXTextureObject *object ) 1322{ 1323 if ( object->mTextureLookupName.isEmpty() ) 1324 return; 1325 1326 U32 key = object->mTextureLookupName.getHashCaseInsensitive() % mHashCount; 1327 GFXTextureObject **walk = &mHashTable[key]; 1328 while(*walk) 1329 { 1330 if(*walk == object) 1331 { 1332 *walk = object->mHashNext; 1333 break; 1334 } 1335 walk = &((*walk)->mHashNext); 1336 } 1337} 1338 1339GFXTextureObject* GFXTextureManager::hashFind( const String &name ) 1340{ 1341 if ( name.isEmpty() ) 1342 return NULL; 1343 1344 U32 key = name.getHashCaseInsensitive() % mHashCount; 1345 GFXTextureObject *walk = mHashTable[key]; 1346 for(; walk; walk = walk->mHashNext) 1347 { 1348 if( walk->mTextureLookupName.equal( name, String::NoCase ) ) 1349 break; 1350 } 1351 1352 return walk; 1353} 1354 1355void GFXTextureManager::freeTexture(GFXTextureObject *texture, bool zombify) 1356{ 1357 // Ok, let the backend deal with it. 1358 _freeTexture(texture, zombify); 1359} 1360 1361void GFXTextureManager::refreshTexture(GFXTextureObject *texture) 1362{ 1363 _refreshTexture(texture); 1364} 1365 1366void GFXTextureManager::_linkTexture( GFXTextureObject *obj ) 1367{ 1368 // info for the profile 1369 GFXTextureProfile::updateStatsForCreation(obj); 1370 1371 // info for the cache 1372 hashInsert(obj); 1373 1374 // info for the master list 1375 if( mListHead == NULL ) 1376 mListHead = obj; 1377 1378 if( mListTail != NULL ) 1379 mListTail->mNext = obj; 1380 1381 obj->mPrev = mListTail; 1382 mListTail = obj; 1383} 1384 1385void GFXTextureManager::deleteTexture( GFXTextureObject *texture ) 1386{ 1387 if ( mTextureManagerState == GFXTextureManager::Dead ) 1388 return; 1389 1390 #ifdef DEBUG_SPEW 1391 Platform::outputDebugString( "[GFXTextureManager] deleteTexture '%s'", 1392 texture->mTextureLookupName.c_str() 1393 ); 1394 #endif 1395 1396 if( mListHead == texture ) 1397 mListHead = texture->mNext; 1398 if( mListTail == texture ) 1399 mListTail = texture->mPrev; 1400 1401 hashRemove( texture ); 1402 1403 // If we have a path for the texture then 1404 // remove change notifications for it. 1405 Path texPath = texture->getPath(); 1406 if ( !texPath.isEmpty() ) 1407 FS::RemoveChangeNotification( texPath, this, &GFXTextureManager::_onFileChanged ); 1408 1409 GFXTextureProfile::updateStatsForDeletion(texture); 1410 1411 freeTexture( texture ); 1412} 1413 1414void GFXTextureManager::_validateTexParams( const U32 width, const U32 height, 1415 const GFXTextureProfile *profile, 1416 U32 &inOutNumMips, GFXFormat &inOutFormat ) 1417{ 1418 // Validate mipmap parameter. If this profile requests no mips, set mips to 1. 1419 if( profile->noMip() ) 1420 { 1421 inOutNumMips = 1; 1422 } 1423 else if( !isPow2( width ) || !isPow2( height ) ) 1424 { 1425 // If a texture is not power-of-2 in size for both dimensions, it must 1426 // have only 1 mip level. 1427 inOutNumMips = 1; 1428 } 1429 1430 // Check format, and compatibility with texture profile requirements 1431 bool autoGenSupp = ( inOutNumMips == 0 ); 1432 1433 // If the format is non-compressed, and the profile requests a compressed format 1434 // than change the format. 1435 GFXFormat testingFormat = inOutFormat; 1436 if( profile->getCompression() != GFXTextureProfile::NONE ) 1437 { 1438 const S32 offset = profile->getCompression() - GFXTextureProfile::BC1; 1439 testingFormat = GFXFormat( GFXFormatBC1 + offset ); 1440 1441 // No auto-gen mips on compressed textures 1442 autoGenSupp = false; 1443 } 1444 1445 if (profile->isSRGB()) 1446 testingFormat = ImageUtil::toSRGBFormat(testingFormat); 1447 1448 // inOutFormat is not modified by this method 1449 GFXCardProfiler* cardProfiler = GFX->getCardProfiler(); 1450 bool chekFmt = cardProfiler->checkFormat(testingFormat, profile, autoGenSupp); 1451 1452 if( !chekFmt ) 1453 { 1454 // It tested for a compressed format, and didn't like it 1455 if( testingFormat != inOutFormat && profile->getCompression() ) 1456 testingFormat = inOutFormat; // Reset to requested format, and try again 1457 1458 // Trying again here, so reset autogen mip 1459 autoGenSupp = ( inOutNumMips == 0 ); 1460 1461 // Wow more weak sauce. There should be a better way to do this. 1462 switch( inOutFormat ) 1463 { 1464 case GFXFormatR8G8B8: 1465 testingFormat = GFXFormatR8G8B8X8; 1466 chekFmt = cardProfiler->checkFormat(testingFormat, profile, autoGenSupp); 1467 break; 1468 1469 case GFXFormatA8: 1470 testingFormat = GFXFormatR8G8B8A8; 1471 chekFmt = cardProfiler->checkFormat(testingFormat, profile, autoGenSupp); 1472 break; 1473 1474 default: 1475 chekFmt = cardProfiler->checkFormat(testingFormat, profile, autoGenSupp); 1476 break; 1477 } 1478 } 1479 1480 // Write back num mips that need to be generated by GBitmap 1481 if( !chekFmt ) 1482 Con::errorf( "Format %s not supported with specified profile.", GFXStringTextureFormat[inOutFormat] ); 1483 else 1484 { 1485 inOutFormat = testingFormat; 1486 1487 // If auto gen mipmaps were requested, and they aren't supported for whatever 1488 // reason, than write out the number of mips that need to be generated. 1489 // 1490 // NOTE: Does this belong here? 1491 if( inOutNumMips == 0 && !autoGenSupp ) 1492 { 1493 inOutNumMips = mFloor(mLog2(mMax(width, height))) + 1; 1494 } 1495 } 1496} 1497 1498GFXCubemap* GFXTextureManager::createCubemap( const Torque::Path &path ) 1499{ 1500 // Very first thing... check the cache. 1501 CubemapTable::Iterator iter = mCubemapTable.find( path.getFullPath() ); 1502 if ( iter != mCubemapTable.end() ) 1503 return iter->value; 1504 1505 // Not in the cache... we have to load it ourselves. 1506 1507 // First check for a DDS file. 1508 if ( !sDDSExt.equal( path.getExtension(), String::NoCase ) ) 1509 { 1510 // At the moment we only support DDS cubemaps. 1511 return NULL; 1512 } 1513 1514 const U32 scalePower = getTextureDownscalePower( NULL ); 1515 1516 // Ok... load the DDS file then. 1517 Resource<DDSFile> dds = DDSFile::load( path, scalePower ); 1518 if ( !dds || !dds->isCubemap() ) 1519 { 1520 // This wasn't a cubemap... give up too. 1521 return NULL; 1522 } 1523 1524 // We loaded the cubemap dds, so now we create the GFXCubemap from it. 1525 GFXCubemap *cubemap = GFX->createCubemap(); 1526 cubemap->initStatic( dds ); 1527 cubemap->_setPath( path.getFullPath() ); 1528 1529 // Store the cubemap into the cache. 1530 mCubemapTable.insertUnique( path.getFullPath(), cubemap ); 1531 1532 return cubemap; 1533} 1534 1535void GFXTextureManager::releaseCubemap( GFXCubemap *cubemap ) 1536{ 1537 if ( mTextureManagerState == GFXTextureManager::Dead ) 1538 return; 1539 1540 const String &path = cubemap->getPath(); 1541 1542 CubemapTable::Iterator iter = mCubemapTable.find( path ); 1543 if ( iter != mCubemapTable.end() && iter->value == cubemap ) 1544 mCubemapTable.erase( iter ); 1545 1546 // If we have a path for the texture then 1547 // remove change notifications for it. 1548 //Path texPath = texture->getPath(); 1549 //if ( !texPath.isEmpty() ) 1550 //FS::RemoveChangeNotification( texPath, this, &GFXTextureManager::_onFileChanged ); 1551} 1552 1553void GFXTextureManager::_onFileChanged( const Torque::Path &path ) 1554{ 1555 String pathNoExt = Torque::Path::Join( path.getRoot(), ':', path.getPath() ); 1556 pathNoExt = Torque::Path::Join( pathNoExt, '/', path.getFileName() ); 1557 1558 // See if we've got it loaded. 1559 GFXTextureObject *obj = hashFind( pathNoExt ); 1560 if ( !obj || path != obj->getPath() ) 1561 return; 1562 1563 Con::errorf( "[GFXTextureManager::_onFileChanged] : File changed [%s]", path.getFullPath().c_str() ); 1564 1565 const U32 scalePower = getTextureDownscalePower( obj->mProfile ); 1566 1567 if ( sDDSExt.equal( path.getExtension(), String::NoCase) ) 1568 { 1569 Resource<DDSFile> dds = DDSFile::load( path, scalePower ); 1570 if ( dds ) 1571 _createTexture( dds, obj->mProfile, false, obj ); 1572 } 1573 else 1574 { 1575 Resource<GBitmap> bmp = GBitmap::load( path ); 1576 if( bmp ) 1577 _createTexture( bmp, obj->mTextureLookupName, obj->mProfile, false, obj ); 1578 } 1579} 1580 1581void GFXTextureManager::reloadTextures() 1582{ 1583 GFXTextureObject *tex = mListHead; 1584 1585 while ( tex != NULL ) 1586 { 1587 const Torque::Path path( tex->mPath ); 1588 if ( !path.isEmpty() ) 1589 { 1590 const U32 scalePower = getTextureDownscalePower( tex->mProfile ); 1591 1592 if ( sDDSExt.equal( path.getExtension(), String::NoCase ) ) 1593 { 1594 Resource<DDSFile> dds = DDSFile::load( path, scalePower ); 1595 if ( dds ) 1596 _createTexture( dds, tex->mProfile, false, tex ); 1597 } 1598 else 1599 { 1600 Resource<GBitmap> bmp = GBitmap::load( path ); 1601 if( bmp ) 1602 _createTexture( bmp, tex->mTextureLookupName, tex->mProfile, false, tex ); 1603 } 1604 } 1605 1606 tex = tex->mNext; 1607 } 1608} 1609 1610DefineEngineFunction( flushTextureCache, void, (),, 1611 "Releases all textures and resurrects the texture manager.\n" 1612 "@ingroup GFX\n" ) 1613{ 1614 if ( !GFX || !TEXMGR ) 1615 return; 1616 1617 TEXMGR->zombify(); 1618 TEXMGR->resurrect(); 1619} 1620 1621DefineEngineFunction( cleanupTexturePool, void, (),, 1622 "Release the unused pooled textures in texture manager freeing up video memory.\n" 1623 "@ingroup GFX\n" ) 1624{ 1625 if ( !GFX || !TEXMGR ) 1626 return; 1627 1628 TEXMGR->cleanupPool(); 1629} 1630 1631DefineEngineFunction( reloadTextures, void, (),, 1632 "Reload all the textures from disk.\n" 1633 "@ingroup GFX\n" ) 1634{ 1635 if ( !GFX || !TEXMGR ) 1636 return; 1637 1638 TEXMGR->reloadTextures(); 1639 PROBEMGR->reloadTextures(); 1640} 1641