/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "precompiled.h" #include "tr_local.h" // do this with a pointer, in case we want to make the actual manager // a private virtual subclass idImageManager imageManager; idImageManager* globalImages = &imageManager; idCVar preLoad_Images( "preLoad_Images", "1", CVAR_SYSTEM | CVAR_BOOL, "preload images during beginlevelload" ); /* =============== R_ReloadImages_f Regenerate all images that came directly from files that have changed, so any saved changes will show up in place. New r_texturesize/r_texturedepth variables will take effect on reload reloadImages =============== */ void R_ReloadImages_f( const idCmdArgs& args ) { bool all = false; if( args.Argc() == 2 ) { if( !idStr::Icmp( args.Argv( 1 ), "all" ) ) { all = true; } else { common->Printf( "USAGE: reloadImages \n" ); return; } } globalImages->ReloadImages( all ); } typedef struct { idImage* image; int size; int index; } sortedImage_t; /* ======================= R_QsortImageSizes ======================= */ static int R_QsortImageSizes( const void* a, const void* b ) { const sortedImage_t* ea, *eb; ea = ( sortedImage_t* )a; eb = ( sortedImage_t* )b; if( ea->size > eb->size ) { return -1; } if( ea->size < eb->size ) { return 1; } return idStr::Icmp( ea->image->GetName(), eb->image->GetName() ); } /* ======================= R_QsortImageName ======================= */ static int R_QsortImageName( const void* a, const void* b ) { const sortedImage_t* ea, *eb; ea = ( sortedImage_t* )a; eb = ( sortedImage_t* )b; return idStr::Icmp( ea->image->GetName(), eb->image->GetName() ); } /* =============== R_ListImages_f =============== */ void R_ListImages_f( const idCmdArgs& args ) { int i, partialSize; idImage* image; int totalSize; int count = 0; bool uncompressedOnly = false; bool unloaded = false; bool failed = false; bool sorted = false; bool duplicated = false; bool overSized = false; bool sortByName = false; if( args.Argc() == 1 ) { } else if( args.Argc() == 2 ) { if( idStr::Icmp( args.Argv( 1 ), "uncompressed" ) == 0 ) { uncompressedOnly = true; } else if( idStr::Icmp( args.Argv( 1 ), "sorted" ) == 0 ) { sorted = true; } else if( idStr::Icmp( args.Argv( 1 ), "namesort" ) == 0 ) { sortByName = true; } else if( idStr::Icmp( args.Argv( 1 ), "unloaded" ) == 0 ) { unloaded = true; } else if( idStr::Icmp( args.Argv( 1 ), "duplicated" ) == 0 ) { duplicated = true; } else if( idStr::Icmp( args.Argv( 1 ), "oversized" ) == 0 ) { sorted = true; overSized = true; } else { failed = true; } } else { failed = true; } if( failed ) { common->Printf( "usage: listImages [ sorted | namesort | unloaded | duplicated | showOverSized ]\n" ); return; } const char* header = " -w-- -h-- filt -fmt-- wrap size --name-------\n"; common->Printf( "\n%s", header ); totalSize = 0; sortedImage_t* sortedArray = ( sortedImage_t* )alloca( sizeof( sortedImage_t ) * globalImages->images.Num() ); for( i = 0 ; i < globalImages->images.Num() ; i++ ) { image = globalImages->images[ i ]; if( uncompressedOnly ) { if( image->IsCompressed() ) { continue; } } if( unloaded == image->IsLoaded() ) { continue; } // only print duplicates (from mismatched wrap / clamp, etc) if( duplicated ) { int j; for( j = i + 1 ; j < globalImages->images.Num() ; j++ ) { if( idStr::Icmp( image->GetName(), globalImages->images[ j ]->GetName() ) == 0 ) { break; } } if( j == globalImages->images.Num() ) { continue; } } if( sorted || sortByName ) { sortedArray[count].image = image; sortedArray[count].size = image->StorageSize(); sortedArray[count].index = i; } else { common->Printf( "%4i:", i ); image->Print(); } totalSize += image->StorageSize(); count++; } if( sorted || sortByName ) { if( sortByName ) { qsort( sortedArray, count, sizeof( sortedImage_t ), R_QsortImageName ); } else { qsort( sortedArray, count, sizeof( sortedImage_t ), R_QsortImageSizes ); } partialSize = 0; for( i = 0 ; i < count ; i++ ) { common->Printf( "%4i:", sortedArray[i].index ); sortedArray[i].image->Print(); partialSize += sortedArray[i].image->StorageSize(); if( ( ( i + 1 ) % 10 ) == 0 ) { common->Printf( "-------- %5.1f of %5.1f megs --------\n", partialSize / ( 1024 * 1024.0 ), totalSize / ( 1024 * 1024.0 ) ); } } } common->Printf( "%s", header ); common->Printf( " %i images (%i total)\n", count, globalImages->images.Num() ); common->Printf( " %5.1f total megabytes of images\n\n\n", totalSize / ( 1024 * 1024.0 ) ); } /* ============== AllocImage Allocates an idImage, adds it to the list, copies the name, and adds it to the hash chain. ============== */ idImage* idImageManager::AllocImage( const char* name ) { if( strlen( name ) >= MAX_IMAGE_NAME ) { common->Error( "idImageManager::AllocImage: \"%s\" is too long\n", name ); } int hash = idStr( name ).FileNameHash(); idImage* image = new( TAG_IMAGE ) idImage( name ); imageHash.Add( hash, images.Append( image ) ); return image; } /* ============== AllocStandaloneImage Allocates an idImage,does not add it to the list or hash chain ============== */ idImage* idImageManager::AllocStandaloneImage( const char* name ) { if( strlen( name ) >= MAX_IMAGE_NAME ) { common->Error( "idImageManager::AllocImage: \"%s\" is too long\n", name ); } idImage* image = new( TAG_IMAGE ) idImage( name ); return image; } /* ================== ImageFromFunction Images that are procedurally generated are allways specified with a callback which must work at any time, allowing the OpenGL system to be completely regenerated if needed. ================== */ idImage* idImageManager::ImageFromFunction( const char* _name, void ( *generatorFunction )( idImage* image ) ) { // strip any .tga file extensions from anywhere in the _name idStr name = _name; name.Replace( ".tga", "" ); name.BackSlashesToSlashes(); // see if the image already exists int hash = name.FileNameHash(); for( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { idImage* image = images[i]; if( name.Icmp( image->GetName() ) == 0 ) { if( image->generatorFunction != generatorFunction ) { common->DPrintf( "WARNING: reused image %s with mixed generators\n", name.c_str() ); } return image; } } // create the image and issue the callback idImage* image = AllocImage( name ); image->generatorFunction = generatorFunction; // check for precompressed, load is from the front end image->referencedOutsideLevelLoad = true; image->ActuallyLoadImage( false ); return image; } /* =============== GetImageWithParameters ============== */ idImage* idImageManager::GetImageWithParameters( const char* _name, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage, cubeFiles_t cubeMap ) const { if( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { declManager->MediaPrint( "DEFAULTED\n" ); return globalImages->defaultImage; } if( idStr::Icmpn( _name, "fonts", 5 ) == 0 || idStr::Icmpn( _name, "newfonts", 8 ) == 0 ) { usage = TD_FONT; } if( idStr::Icmpn( _name, "lights", 6 ) == 0 ) { usage = TD_LIGHT; } // strip any .tga file extensions from anywhere in the _name, including image program parameters idStrStatic< MAX_OSPATH > name = _name; name.Replace( ".tga", "" ); name.BackSlashesToSlashes(); int hash = name.FileNameHash(); for( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { idImage* image = images[i]; if( name.Icmp( image->GetName() ) == 0 ) { // the built in's, like _white and _flat always match the other options if( name[0] == '_' ) { return image; } if( image->cubeFiles != cubeMap ) { common->Error( "Image '%s' has been referenced with conflicting cube map states", _name ); } if( image->filter != filter || image->repeat != repeat ) { // we might want to have the system reset these parameters on every bind and // share the image data continue; } if( image->usage != usage ) { // If an image is used differently then we need 2 copies of it because usage affects the way it's compressed and swizzled continue; } return image; } } return NULL; } /* =============== ImageFromFile Finds or loads the given image, always returning a valid image pointer. Loading of the image may be deferred for dynamic loading. ============== */ idImage* idImageManager::ImageFromFile( const char* _name, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage, cubeFiles_t cubeMap ) { if( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { declManager->MediaPrint( "DEFAULTED\n" ); return globalImages->defaultImage; } if( idStr::Icmpn( _name, "fonts", 5 ) == 0 || idStr::Icmpn( _name, "newfonts", 8 ) == 0 ) { usage = TD_FONT; } if( idStr::Icmpn( _name, "lights", 6 ) == 0 ) { usage = TD_LIGHT; } // strip any .tga file extensions from anywhere in the _name, including image program parameters idStrStatic< MAX_OSPATH > name = _name; name.Replace( ".tga", "" ); name.BackSlashesToSlashes(); // // see if the image is already loaded, unless we // are in a reloadImages call // int hash = name.FileNameHash(); for( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { idImage* image = images[i]; if( name.Icmp( image->GetName() ) == 0 ) { // the built in's, like _white and _flat always match the other options if( name[0] == '_' ) { return image; } if( image->cubeFiles != cubeMap ) { common->Error( "Image '%s' has been referenced with conflicting cube map states", _name ); } if( image->filter != filter || image->repeat != repeat ) { // we might want to have the system reset these parameters on every bind and // share the image data continue; } if( image->usage != usage ) { // If an image is used differently then we need 2 copies of it because usage affects the way it's compressed and swizzled continue; } image->usage = usage; image->levelLoadReferenced = true; if( ( !insideLevelLoad || preloadingMapImages ) && !image->IsLoaded() ) { image->referencedOutsideLevelLoad = ( !insideLevelLoad && !preloadingMapImages ); image->ActuallyLoadImage( false ); // load is from front end declManager->MediaPrint( "%ix%i %s (reload for mixed referneces)\n", image->GetUploadWidth(), image->GetUploadHeight(), image->GetName() ); } return image; } } // // create a new image // idImage* image = AllocImage( name ); image->cubeFiles = cubeMap; image->usage = usage; image->filter = filter; image->repeat = repeat; image->levelLoadReferenced = true; // load it if we aren't in a level preload if( !insideLevelLoad || preloadingMapImages ) { image->referencedOutsideLevelLoad = ( !insideLevelLoad && !preloadingMapImages ); image->ActuallyLoadImage( false ); // load is from front end declManager->MediaPrint( "%ix%i %s\n", image->GetUploadWidth(), image->GetUploadHeight(), image->GetName() ); } else { declManager->MediaPrint( "%s\n", image->GetName() ); } return image; } /* ======================== idImageManager::ScratchImage ======================== */ idImage* idImageManager::ScratchImage( const char* _name, idImageOpts* imgOpts, textureFilter_t filter, textureRepeat_t repeat, textureUsage_t usage ) { if( !_name || !_name[0] ) { idLib::FatalError( "idImageManager::ScratchImage called with empty name" ); } if( imgOpts == NULL ) { idLib::FatalError( "idImageManager::ScratchImage called with NULL imgOpts" ); } idStr name = _name; // // see if the image is already loaded, unless we // are in a reloadImages call // int hash = name.FileNameHash(); for( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { idImage* image = images[i]; if( name.Icmp( image->GetName() ) == 0 ) { // the built in's, like _white and _flat always match the other options if( name[0] == '_' ) { return image; } if( image->filter != filter || image->repeat != repeat ) { // we might want to have the system reset these parameters on every bind and // share the image data continue; } if( image->usage != usage ) { // If an image is used differently then we need 2 copies of it because usage affects the way it's compressed and swizzled continue; } image->usage = usage; image->levelLoadReferenced = true; image->referencedOutsideLevelLoad = true; return image; } } // clamp is the only repeat mode that makes sense for cube maps, but // some platforms let them stay in repeat mode and get border seam issues if( imgOpts->textureType == TT_CUBIC && repeat != TR_CLAMP ) { repeat = TR_CLAMP; } // // create a new image // idImage* newImage = AllocImage( name ); if( newImage != NULL ) { newImage->AllocImage( *imgOpts, filter, repeat ); } return newImage; } /* =============== idImageManager::GetImage =============== */ idImage* idImageManager::GetImage( const char* _name ) const { if( !_name || !_name[0] || idStr::Icmp( _name, "default" ) == 0 || idStr::Icmp( _name, "_default" ) == 0 ) { declManager->MediaPrint( "DEFAULTED\n" ); return globalImages->defaultImage; } // strip any .tga file extensions from anywhere in the _name, including image program parameters idStr name = _name; name.Replace( ".tga", "" ); name.BackSlashesToSlashes(); // // look in loaded images // int hash = name.FileNameHash(); for( int i = imageHash.First( hash ); i != -1; i = imageHash.Next( i ) ) { idImage* image = images[i]; if( name.Icmp( image->GetName() ) == 0 ) { return image; } } return NULL; } /* =============== PurgeAllImages =============== */ void idImageManager::PurgeAllImages() { int i; idImage* image; for( i = 0; i < images.Num() ; i++ ) { image = images[i]; image->PurgeImage(); } } /* =============== ReloadImages =============== */ void idImageManager::ReloadImages( bool all ) { for( int i = 0 ; i < globalImages->images.Num() ; i++ ) { globalImages->images[ i ]->Reload( all ); } } /* =============== R_CombineCubeImages_f Used to combine animations of six separate tga files into a serials of 6x taller tga files, for preparation to roq compress =============== */ void R_CombineCubeImages_f( const idCmdArgs& args ) { if( args.Argc() != 2 ) { common->Printf( "usage: combineCubeImages \n" ); common->Printf( " combines basename[1-6][0001-9999].tga to basenameCM[0001-9999].tga\n" ); common->Printf( " 1: forward 2:right 3:back 4:left 5:up 6:down\n" ); return; } idStr baseName = args.Argv( 1 ); common->SetRefreshOnPrint( true ); for( int frameNum = 1 ; frameNum < 10000 ; frameNum++ ) { char filename[MAX_IMAGE_NAME]; byte* pics[6]; int width = 0, height = 0; int side; int orderRemap[6] = { 1, 3, 4, 2, 5, 6 }; for( side = 0 ; side < 6 ; side++ ) { sprintf( filename, "%s%i%04i.tga", baseName.c_str(), orderRemap[side], frameNum ); common->Printf( "reading %s\n", filename ); R_LoadImage( filename, &pics[side], &width, &height, NULL, true ); if( !pics[side] ) { common->Printf( "not found.\n" ); break; } // convert from "camera" images to native cube map images switch( side ) { case 0: // forward R_RotatePic( pics[side], width ); break; case 1: // back R_RotatePic( pics[side], width ); R_HorizontalFlip( pics[side], width, height ); R_VerticalFlip( pics[side], width, height ); break; case 2: // left R_VerticalFlip( pics[side], width, height ); break; case 3: // right R_HorizontalFlip( pics[side], width, height ); break; case 4: // up R_RotatePic( pics[side], width ); break; case 5: // down R_RotatePic( pics[side], width ); break; } } if( side != 6 ) { for( int i = 0 ; i < side ; side++ ) { Mem_Free( pics[side] ); } break; } idTempArray buf( width * height * 6 * 4 ); byte* combined = ( byte* )buf.Ptr(); for( side = 0 ; side < 6 ; side++ ) { memcpy( combined + width * height * 4 * side, pics[side], width * height * 4 ); Mem_Free( pics[side] ); } sprintf( filename, "%sCM%04i.tga", baseName.c_str(), frameNum ); common->Printf( "writing %s\n", filename ); R_WriteTGA( filename, combined, width, height * 6 ); } common->SetRefreshOnPrint( false ); } /* =============== UnbindAll =============== */ void idImageManager::UnbindAll() { int oldTMU = backEnd.glState.currenttmu; for( int i = 0; i < MAX_PROG_TEXTURE_PARMS; ++i ) { backEnd.glState.currenttmu = i; BindNull(); } backEnd.glState.currenttmu = oldTMU; } /* =============== BindNull =============== */ void idImageManager::BindNull() { RENDERLOG_PRINTF( "BindNull()\n" ); } /* =============== Init =============== */ void idImageManager::Init() { images.Resize( 1024, 1024 ); imageHash.ResizeIndex( 1024 ); CreateIntrinsicImages(); cmdSystem->AddCommand( "reloadImages", R_ReloadImages_f, CMD_FL_RENDERER, "reloads images" ); cmdSystem->AddCommand( "listImages", R_ListImages_f, CMD_FL_RENDERER, "lists images" ); cmdSystem->AddCommand( "combineCubeImages", R_CombineCubeImages_f, CMD_FL_RENDERER, "combines six images for roq compression" ); // should forceLoadImages be here? } /* =============== Shutdown =============== */ void idImageManager::Shutdown() { images.DeleteContents( true ); imageHash.Clear(); } /* ==================== idImageManager::BeginLevelLoad Frees all images used by the previous level ==================== */ void idImageManager::BeginLevelLoad() { insideLevelLoad = true; for( int i = 0 ; i < images.Num() ; i++ ) { idImage* image = images[ i ]; // generator function images are always kept around if( image->generatorFunction ) { continue; } if( !image->referencedOutsideLevelLoad && image->IsLoaded() ) { image->PurgeImage(); //idLib::Printf( "purging %s\n", image->GetName() ); } else { //idLib::Printf( "not purging %s\n", image->GetName() ); } image->levelLoadReferenced = false; } } /* ==================== idImageManager::ExcludePreloadImage ==================== */ bool idImageManager::ExcludePreloadImage( const char* name ) { idStrStatic< MAX_OSPATH > imgName = name; imgName.ToLower(); if( imgName.Find( "newfonts/", false ) >= 0 ) { return true; } if( imgName.Find( "generated/swf/", false ) >= 0 ) { return true; } if( imgName.Find( "/loadscreens/", false ) >= 0 ) { return true; } return false; } /* ==================== idImageManager::Preload ==================== */ void idImageManager::Preload( const idPreloadManifest& manifest, const bool& mapPreload ) { if( preLoad_Images.GetBool() && manifest.NumResources() > 0 ) { // preload this levels images common->Printf( "Preloading images...\n" ); preloadingMapImages = mapPreload; int start = Sys_Milliseconds(); int numLoaded = 0; //fileSystem->StartPreload( preloadImageFiles ); for( int i = 0; i < manifest.NumResources(); i++ ) { const preloadEntry_s& p = manifest.GetPreloadByIndex( i ); if( p.resType == PRELOAD_IMAGE && !ExcludePreloadImage( p.resourceName ) ) { globalImages->ImageFromFile( p.resourceName, ( textureFilter_t )p.imgData.filter, ( textureRepeat_t )p.imgData.repeat, ( textureUsage_t )p.imgData.usage, ( cubeFiles_t )p.imgData.cubeMap ); numLoaded++; } } //fileSystem->StopPreload(); int end = Sys_Milliseconds(); common->Printf( "%05d images preloaded ( or were already loaded ) in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 ); common->Printf( "----------------------------------------\n" ); preloadingMapImages = false; } } /* =============== idImageManager::LoadLevelImages =============== */ int idImageManager::LoadLevelImages( bool pacifier ) { int loadCount = 0; for( int i = 0 ; i < images.Num() ; i++ ) { if( pacifier ) { common->UpdateLevelLoadPacifier(); } idImage* image = images[ i ]; if( image->generatorFunction ) { continue; } if( image->levelLoadReferenced && !image->IsLoaded() ) { loadCount++; image->ActuallyLoadImage( false ); } } return loadCount; } /* =============== idImageManager::EndLevelLoad =============== */ void idImageManager::EndLevelLoad() { insideLevelLoad = false; common->Printf( "----- idImageManager::EndLevelLoad -----\n" ); int start = Sys_Milliseconds(); int loadCount = LoadLevelImages( true ); int end = Sys_Milliseconds(); common->Printf( "%5i images loaded in %5.1f seconds\n", loadCount, ( end - start ) * 0.001 ); common->Printf( "----------------------------------------\n" ); //R_ListImages_f( idCmdArgs( "sorted sorted", false ) ); } /* =============== idImageManager::StartBuild =============== */ void idImageManager::StartBuild() { } /* =============== idImageManager::FinishBuild =============== */ void idImageManager::FinishBuild( bool removeDups ) { } /* =============== idImageManager::PrintMemInfo =============== */ void idImageManager::PrintMemInfo( MemInfo_t* mi ) { int i, j, total = 0; int* sortIndex; idFile* f; f = fileSystem->OpenFileWrite( mi->filebase + "_images.txt" ); if( !f ) { return; } // sort first sortIndex = new( TAG_IMAGE ) int[images.Num()]; for( i = 0; i < images.Num(); i++ ) { sortIndex[i] = i; } for( i = 0; i < images.Num() - 1; i++ ) { for( j = i + 1; j < images.Num(); j++ ) { if( images[sortIndex[i]]->StorageSize() < images[sortIndex[j]]->StorageSize() ) { int temp = sortIndex[i]; sortIndex[i] = sortIndex[j]; sortIndex[j] = temp; } } } // print next for( i = 0; i < images.Num(); i++ ) { idImage* im = images[sortIndex[i]]; int size; size = im->StorageSize(); total += size; f->Printf( "%s %3i %s\n", idStr::FormatNumber( size ).c_str(), im->refCount, im->GetName() ); } delete [] sortIndex; mi->imageAssetsTotal = total; f->Printf( "\nTotal image bytes allocated: %s\n", idStr::FormatNumber( total ).c_str() ); fileSystem->CloseFile( f ); }