DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

BinaryImage.cpp (14443B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 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.
     23 
     24 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.
     25 
     26 ===========================================================================
     27 */
     28 #pragma hdrstop
     29 #include "../idlib/precompiled.h"
     30 
     31 /*
     32 ================================================================================================
     33 
     34 	idBinaryImage
     35 
     36 ================================================================================================
     37 */
     38 
     39 #include "tr_local.h"
     40 #include "dxt/DXTCodec.h"
     41 #include "color/ColorSpace.h"
     42 
     43 idCVar image_highQualityCompression( "image_highQualityCompression", "0", CVAR_BOOL, "Use high quality (slow) compression" );
     44 
     45 /*
     46 ========================
     47 idBinaryImage::Load2DFromMemory
     48 ========================
     49 */
     50 void idBinaryImage::Load2DFromMemory( int width, int height, const byte * pic_const, int numLevels, textureFormat_t & textureFormat, textureColor_t & colorFormat, bool gammaMips ) {
     51 	fileData.textureType = TT_2D;
     52 	fileData.format = textureFormat;
     53 	fileData.colorFormat = colorFormat;
     54 	fileData.width = width;
     55 	fileData.height = height;
     56 	fileData.numLevels = numLevels;
     57 
     58 	byte * pic = (byte *)Mem_Alloc( width * height * 4, TAG_TEMP );
     59 	memcpy( pic, pic_const, width * height * 4 );
     60 
     61 	if ( colorFormat == CFM_YCOCG_DXT5 ) {
     62 		// convert the image data to YCoCg and use the YCoCgDXT5 compressor
     63 		idColorSpace::ConvertRGBToCoCg_Y( pic, pic, width, height );
     64 	} else if ( colorFormat == CFM_NORMAL_DXT5 ) {
     65 		// Blah, HQ swizzles automatically, Fast doesn't
     66 		if ( !image_highQualityCompression.GetBool() ) {
     67 			for ( int i = 0; i < width * height; i++ ) {
     68 				pic[i*4+3] = pic[i*4+0];
     69 				pic[i*4+0] = 0;
     70 				pic[i*4+2] = 0;
     71 			}
     72 		}
     73 	} else if ( colorFormat == CFM_GREEN_ALPHA ) {
     74 		for ( int i = 0; i < width * height; i++ ) {
     75 			pic[i*4+1] = pic[i*4+3];
     76 			pic[i*4+0] = 0;
     77 			pic[i*4+2] = 0;
     78 			pic[i*4+3] = 0;
     79 		}
     80 	}
     81 
     82 	int	scaledWidth = width;
     83 	int scaledHeight = height;
     84 	images.SetNum( numLevels );
     85 	for ( int level = 0; level < images.Num(); level++ ) {
     86 		idBinaryImageData &img = images[ level ];
     87 
     88 		// Images that are going to be DXT compressed and aren't multiples of 4 need to be 
     89 		// padded out before compressing.
     90 		byte * dxtPic = pic;
     91 		int	dxtWidth = 0;
     92 		int	dxtHeight = 0;
     93 		if ( textureFormat == FMT_DXT5 || textureFormat == FMT_DXT1 ) {
     94 			if ( ( scaledWidth & 3 ) || ( scaledHeight & 3 ) ) {
     95 				dxtWidth = ( scaledWidth + 3 ) & ~3;
     96 				dxtHeight = ( scaledHeight + 3 ) & ~3;
     97 				dxtPic = (byte *)Mem_ClearedAlloc( dxtWidth*4*dxtHeight, TAG_IMAGE );
     98 				for ( int i = 0; i < scaledHeight; i++ ) {
     99 					memcpy( dxtPic + i*dxtWidth*4, pic + i*scaledWidth*4, scaledWidth*4 );
    100 				}
    101 			} else {
    102 				dxtPic = pic;
    103 				dxtWidth = scaledWidth;
    104 				dxtHeight = scaledHeight;
    105 			}
    106 		}
    107 
    108 		img.level = level;
    109 		img.destZ = 0;
    110 		img.width = scaledWidth;
    111 		img.height = scaledHeight;
    112 
    113 		// compress data or convert floats as necessary
    114 		if ( textureFormat == FMT_DXT1 ) {
    115 			idDxtEncoder dxt;
    116 			img.Alloc( dxtWidth * dxtHeight / 2 );
    117 			if ( image_highQualityCompression.GetBool() ) {
    118 				dxt.CompressImageDXT1HQ( dxtPic, img.data, dxtWidth, dxtHeight );
    119 			} else {
    120 				dxt.CompressImageDXT1Fast( dxtPic, img.data, dxtWidth, dxtHeight );
    121 			}
    122 		} else if ( textureFormat == FMT_DXT5 ) {
    123 			idDxtEncoder dxt;
    124 			img.Alloc( dxtWidth * dxtHeight );
    125 			if ( colorFormat == CFM_NORMAL_DXT5 ) {
    126 				if ( image_highQualityCompression.GetBool() ) {
    127 					dxt.CompressNormalMapDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight );
    128 				} else {
    129 					dxt.CompressNormalMapDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight );
    130 				}
    131 			} else if ( colorFormat == CFM_YCOCG_DXT5 ) {
    132 				if ( image_highQualityCompression.GetBool() ) {
    133 					dxt.CompressYCoCgDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight );
    134 				} else {
    135 					dxt.CompressYCoCgDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight );
    136 				}
    137 			} else {
    138 				fileData.colorFormat = colorFormat = CFM_DEFAULT;
    139 				if ( image_highQualityCompression.GetBool() ) {
    140 					dxt.CompressImageDXT5HQ( dxtPic, img.data, dxtWidth, dxtHeight );
    141 				} else {
    142 					dxt.CompressImageDXT5Fast( dxtPic, img.data, dxtWidth, dxtHeight );
    143 				}
    144 			}
    145 		} else if ( textureFormat == FMT_LUM8 || textureFormat == FMT_INT8 ) {
    146 			// LUM8 and INT8 just read the red channel
    147 			img.Alloc( scaledWidth * scaledHeight );
    148 			for ( int i = 0; i < img.dataSize; i++ ) {
    149 				img.data[ i ] = pic[ i * 4 ];
    150 			}
    151 		} else if ( textureFormat == FMT_ALPHA ) {
    152 			// ALPHA reads the alpha channel
    153 			img.Alloc( scaledWidth * scaledHeight );
    154 			for ( int i = 0; i < img.dataSize; i++ ) {
    155 				img.data[ i ] = pic[ i * 4 + 3 ];
    156 			}
    157 		} else if ( textureFormat == FMT_L8A8 ) {
    158 			// L8A8 reads the alpha and red channels
    159 			img.Alloc( scaledWidth * scaledHeight * 2 );
    160 			for ( int i = 0; i < img.dataSize / 2; i++ ) {
    161 				img.data[ i * 2 + 0 ] = pic[ i * 4 + 0 ];
    162 				img.data[ i * 2 + 1 ] = pic[ i * 4 + 3 ];
    163 			}
    164 		} else if ( textureFormat == FMT_RGB565 ) {
    165 			img.Alloc( scaledWidth * scaledHeight * 2 );
    166 			for ( int i = 0; i < img.dataSize / 2; i++ ) {
    167 				unsigned short color = ( ( pic[ i * 4 + 0 ] >> 3 ) << 11 ) | ( ( pic[ i * 4 + 1 ] >> 2 ) << 5 ) | ( pic[ i * 4 + 2 ] >> 3 );
    168 				img.data[ i * 2 + 0 ] = ( color >> 8 ) & 0xFF;
    169 				img.data[ i * 2 + 1 ] = color & 0xFF;
    170 			}
    171 		} else {
    172 			fileData.format = textureFormat = FMT_RGBA8;
    173 			img.Alloc( scaledWidth * scaledHeight * 4 );
    174 			for ( int i = 0; i < img.dataSize; i++ ) {
    175 				img.data[ i ] = pic[ i ];
    176 			}
    177 		}
    178 
    179 		// if we had to pad to quads, free the padded version
    180 		if ( pic != dxtPic ) {
    181 			Mem_Free( dxtPic );
    182 			dxtPic = NULL;
    183 		}
    184 
    185 		// downsample for the next level
    186 		byte * shrunk = NULL;
    187 		if ( gammaMips ) {
    188 			shrunk = R_MipMapWithGamma( pic, scaledWidth, scaledHeight );
    189 		} else {
    190 			shrunk = R_MipMap( pic, scaledWidth, scaledHeight );
    191 		}
    192 		Mem_Free( pic );
    193 		pic = shrunk;
    194 
    195 		scaledWidth = Max( 1, scaledWidth >> 1 );
    196 		scaledHeight = Max( 1, scaledHeight >> 1 );
    197 	}
    198 
    199 	Mem_Free( pic );
    200 }
    201 
    202 /*
    203 ========================
    204 PadImageTo4x4
    205 
    206 DXT Compression requres a complete 4x4 block, even if the GPU will only be sampling
    207 a subset of it, so pad to 4x4 with replicated texels to maximize compression.
    208 ========================
    209 */
    210 static void PadImageTo4x4( const byte *src, int width, int height, byte dest[64] ) {
    211 	// we probably will need to support this for non-square images, but I'll address
    212 	// that when needed
    213 	assert( width <= 4 && height <= 4 );
    214 	assert( width > 0 && height > 0 );
    215 
    216 	for ( int y = 0 ; y < 4 ; y++ ) {
    217 		int	sy = y % height;
    218 		for ( int x = 0 ; x < 4 ; x++ ) {
    219 			int	sx = x % width;
    220 			for ( int c = 0 ; c < 4 ; c++ ) {
    221 				dest[(y*4+x)*4+c] = src[(sy*width+sx)*4+c];
    222 			}
    223 		}
    224 	}
    225 }
    226 
    227 /*
    228 ========================
    229 idBinaryImage::LoadCubeFromMemory
    230 ========================
    231 */
    232 void idBinaryImage::LoadCubeFromMemory( int width, const byte * pics[6], int numLevels, textureFormat_t & textureFormat, bool gammaMips ) {
    233 	fileData.textureType = TT_CUBIC;
    234 	fileData.format = textureFormat;
    235 	fileData.colorFormat = CFM_DEFAULT;
    236 	fileData.height = fileData.width = width;
    237 	fileData.numLevels = numLevels;
    238 
    239 	images.SetNum( fileData.numLevels * 6 );
    240 
    241 	for ( int side = 0; side < 6; side++ ) {
    242 		const byte *orig = pics[side];
    243 		const byte *pic = orig;
    244 		int	scaledWidth = fileData.width;
    245 		for ( int level = 0; level < fileData.numLevels; level++ ) {
    246 			// compress data or convert floats as necessary
    247 			idBinaryImageData &img = images[ level * 6 + side ];
    248 
    249 			// handle padding blocks less than 4x4 for the DXT compressors
    250 			ALIGN16( byte padBlock[64] );
    251 			int		padSize;
    252 			const byte *padSrc;
    253 			if ( scaledWidth < 4 && ( textureFormat == FMT_DXT1 || textureFormat == FMT_DXT5 ) ) {
    254 				PadImageTo4x4( pic, scaledWidth, scaledWidth, padBlock );
    255 				padSize = 4;
    256 				padSrc = padBlock;
    257 			} else {
    258 				padSize = scaledWidth;
    259 				padSrc = pic;
    260 			}
    261 
    262 			img.level = level;
    263 			img.destZ = side;
    264 			img.width = padSize;
    265 			img.height = padSize;
    266 			if ( textureFormat == FMT_DXT1 ) {
    267 				img.Alloc( padSize * padSize / 2 );
    268 				idDxtEncoder dxt;
    269 				dxt.CompressImageDXT1Fast( padSrc, img.data, padSize, padSize );
    270 			} else if ( textureFormat == FMT_DXT5 ) {
    271 				img.Alloc( padSize * padSize );
    272 				idDxtEncoder dxt;
    273 				dxt.CompressImageDXT5Fast( padSrc, img.data, padSize, padSize );
    274 			} else {
    275 				fileData.format = textureFormat = FMT_RGBA8;
    276 				img.Alloc( padSize * padSize * 4 );
    277 				memcpy( img.data, pic, img.dataSize );
    278 			}
    279 
    280 			// downsample for the next level
    281 			byte * shrunk = NULL;
    282 			if ( gammaMips ) {
    283 				shrunk = R_MipMapWithGamma( pic, scaledWidth, scaledWidth );
    284 			} else {
    285 				shrunk = R_MipMap( pic, scaledWidth, scaledWidth );
    286 			}
    287 			if ( pic != orig ) {
    288 				Mem_Free( (void *)pic );
    289 				pic = NULL;
    290 			}
    291 			pic = shrunk;
    292 
    293 			scaledWidth = Max( 1, scaledWidth >> 1 );
    294 		}
    295 		if ( pic != orig ) {
    296 			// free the down sampled version
    297 			Mem_Free( (void *)pic );
    298 			pic = NULL;
    299 		}
    300 	}
    301 }
    302 
    303 /*
    304 ========================
    305 idBinaryImage::WriteGeneratedFile
    306 ========================
    307 */
    308 ID_TIME_T idBinaryImage::WriteGeneratedFile( ID_TIME_T sourceFileTime ) {
    309 	idStr binaryFileName;
    310 	MakeGeneratedFileName( binaryFileName );
    311 	idFileLocal file( fileSystem->OpenFileWrite( binaryFileName, "fs_basepath" ) );
    312 	if ( file == NULL ) {
    313 		idLib::Warning( "idBinaryImage: Could not open file '%s'", binaryFileName.c_str() );
    314 		return FILE_NOT_FOUND_TIMESTAMP;
    315 	}
    316 	idLib::Printf( "Writing %s\n", binaryFileName.c_str() );
    317 
    318 	fileData.headerMagic = BIMAGE_MAGIC;
    319 	fileData.sourceFileTime = sourceFileTime;
    320 
    321 	file->WriteBig( fileData.sourceFileTime );
    322 	file->WriteBig( fileData.headerMagic );
    323 	file->WriteBig( fileData.textureType );
    324 	file->WriteBig( fileData.format );
    325 	file->WriteBig( fileData.colorFormat );
    326 	file->WriteBig( fileData.width );
    327 	file->WriteBig( fileData.height );
    328 	file->WriteBig( fileData.numLevels );
    329 
    330 	for ( int i = 0; i < images.Num(); i++ ) {
    331 		idBinaryImageData &img = images[ i ];
    332 		file->WriteBig( img.level );
    333 		file->WriteBig( img.destZ );
    334 		file->WriteBig( img.width );
    335 		file->WriteBig( img.height );
    336 		file->WriteBig( img.dataSize );
    337 		file->Write( img.data, img.dataSize );
    338 	}
    339 	return file->Timestamp();
    340 }
    341 
    342 /*
    343 ==========================
    344 idBinaryImage::LoadFromGeneratedFile
    345 
    346 Load the preprocessed image from the generated folder.
    347 ==========================
    348 */
    349 ID_TIME_T idBinaryImage::LoadFromGeneratedFile( ID_TIME_T sourceFileTime ) {
    350 	idStr binaryFileName;
    351 	MakeGeneratedFileName( binaryFileName );
    352 	idFileLocal bFile = fileSystem->OpenFileRead( binaryFileName );
    353 	if ( bFile == NULL ) {
    354 		return FILE_NOT_FOUND_TIMESTAMP;
    355 	}
    356 	if ( LoadFromGeneratedFile( bFile, sourceFileTime ) ) {
    357 		return bFile->Timestamp();
    358 	}
    359 	return FILE_NOT_FOUND_TIMESTAMP;
    360 }
    361 
    362 /*
    363 ==========================
    364 idBinaryImage::LoadFromGeneratedFile
    365 
    366 Load the preprocessed image from the generated folder.
    367 ==========================
    368 */
    369 bool idBinaryImage::LoadFromGeneratedFile( idFile * bFile, ID_TIME_T sourceFileTime ) {
    370 	if ( bFile->Read( &fileData, sizeof( fileData ) ) <= 0 ) {
    371 		return false;
    372 	}
    373 	idSwapClass<bimageFile_t> swap;
    374 	swap.Big( fileData.sourceFileTime );
    375 	swap.Big( fileData.headerMagic );
    376 	swap.Big( fileData.textureType );
    377 	swap.Big( fileData.format );
    378 	swap.Big( fileData.colorFormat );
    379 	swap.Big( fileData.width );
    380 	swap.Big( fileData.height );
    381 	swap.Big( fileData.numLevels );
    382 
    383 	if ( BIMAGE_MAGIC != fileData.headerMagic ) {
    384 		return false;
    385 	}
    386 	if ( fileData.sourceFileTime != sourceFileTime && !fileSystem->InProductionMode() ) {
    387 		return false;
    388 	}
    389 
    390 	int numImages = fileData.numLevels;
    391 	if ( fileData.textureType == TT_CUBIC ) {
    392 		numImages *= 6;
    393 	}
    394 
    395 	images.SetNum( numImages );
    396 
    397 	for ( int i = 0; i < numImages; i++ ) {
    398 		idBinaryImageData &img = images[ i ];
    399 		if ( bFile->Read( &img, sizeof( bimageImage_t ) ) <= 0 ) {
    400 			return false;
    401 		}
    402 		idSwapClass<bimageImage_t> swap;
    403 		swap.Big( img.level );
    404 		swap.Big( img.destZ );
    405 		swap.Big( img.width );
    406 		swap.Big( img.height );
    407 		swap.Big( img.dataSize );
    408 		assert( img.level >= 0 && img.level < fileData.numLevels );
    409 		assert( img.destZ == 0 || fileData.textureType == TT_CUBIC );
    410 		assert( img.dataSize > 0 );
    411 		// DXT images need to be padded to 4x4 block sizes, but the original image
    412 		// sizes are still retained, so the stored data size may be larger than
    413 		// just the multiplication of dimensions
    414 		assert( img.dataSize >= img.width * img.height * BitsForFormat( (textureFormat_t)fileData.format ) / 8 );
    415 		img.Alloc( img.dataSize );
    416 		if ( img.data == NULL ) {
    417 			return false;
    418 		}
    419 
    420 		if ( bFile->Read( img.data, img.dataSize ) <= 0 ) {
    421 			return false;
    422 		}
    423 	}
    424 
    425 	return true;
    426 }
    427 
    428 /*
    429 ==========================
    430 idBinaryImage::MakeGeneratedFileName
    431 ==========================
    432 */
    433 void idBinaryImage::MakeGeneratedFileName( idStr & gfn ) {
    434 	GetGeneratedFileName( gfn, GetName() );
    435 }
    436 /*
    437 ==========================
    438 idBinaryImage::GetGeneratedFileName
    439 ==========================
    440 */
    441 void idBinaryImage::GetGeneratedFileName( idStr & gfn, const char *name ) {
    442 	gfn.Format( "generated/images/%s.bimage", name );
    443 	gfn.Replace( "(", "/" );
    444 	gfn.Replace( ",", "/" );
    445 	gfn.Replace( ")", "" );
    446 	gfn.Replace( " ", "" );
    447 }
    448 
    449