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