Torque3D Documentation / _generateds / gfxTextureManager.cpp

gfxTextureManager.cpp

Engine/source/gfx/gfxTextureManager.cpp

More...

Public Variables

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" )

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   &reg = 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