mirror of
https://github.com/dhewm/dhewm3.git
synced 2025-02-01 22:20:45 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
2214 lines
61 KiB
C++
2214 lines
61 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 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 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 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 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 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "idlib/hashing/MD4.h"
|
|
#include "renderer/tr_local.h"
|
|
|
|
#include "renderer/Image.h"
|
|
|
|
/*
|
|
PROBLEM: compressed textures may break the zero clamp rule!
|
|
*/
|
|
|
|
static bool FormatIsDXT( int internalFormat ) {
|
|
if ( internalFormat < GL_COMPRESSED_RGB_S3TC_DXT1_EXT
|
|
|| internalFormat > GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int MakePowerOfTwo( int num ) {
|
|
int pot;
|
|
for (pot = 1 ; pot < num ; pot<<=1) {
|
|
}
|
|
return pot;
|
|
}
|
|
|
|
/*
|
|
================
|
|
BitsForInternalFormat
|
|
|
|
Used for determining memory utilization
|
|
================
|
|
*/
|
|
int idImage::BitsForInternalFormat( int internalFormat ) const {
|
|
switch ( internalFormat ) {
|
|
case GL_INTENSITY8:
|
|
case 1:
|
|
return 8;
|
|
case 2:
|
|
case GL_LUMINANCE8_ALPHA8:
|
|
return 16;
|
|
case 3:
|
|
return 32; // on some future hardware, this may actually be 24, but be conservative
|
|
case 4:
|
|
return 32;
|
|
case GL_LUMINANCE8:
|
|
return 8;
|
|
case GL_ALPHA8:
|
|
return 8;
|
|
case GL_RGBA8:
|
|
return 32;
|
|
case GL_RGB8:
|
|
return 32; // on some future hardware, this may actually be 24, but be conservative
|
|
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
|
return 4;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
|
return 4;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
|
return 8;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
|
return 8;
|
|
case GL_RGBA4:
|
|
return 16;
|
|
case GL_RGB5:
|
|
return 16;
|
|
case GL_COLOR_INDEX8_EXT:
|
|
return 8;
|
|
case GL_COLOR_INDEX:
|
|
return 8;
|
|
case GL_COMPRESSED_RGB_ARB:
|
|
return 4; // not sure
|
|
case GL_COMPRESSED_RGBA_ARB:
|
|
return 8; // not sure
|
|
default:
|
|
common->Error( "R_BitsForInternalFormat: BAD FORMAT:%i", internalFormat );
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
UploadCompressedNormalMap
|
|
|
|
Create a 256 color palette to be used by compressed normal maps
|
|
==================
|
|
*/
|
|
void idImage::UploadCompressedNormalMap( int width, int height, const byte *rgba, int mipLevel ) {
|
|
byte *normals;
|
|
const byte *in;
|
|
byte *out;
|
|
int i, j;
|
|
int x, y, z;
|
|
int row;
|
|
|
|
// OpenGL's pixel packing rule
|
|
row = width < 4 ? 4 : width;
|
|
|
|
normals = (byte *)_alloca( row * height );
|
|
if ( !normals ) {
|
|
common->Error( "R_UploadCompressedNormalMap: _alloca failed" );
|
|
}
|
|
|
|
in = rgba;
|
|
out = normals;
|
|
for ( i = 0 ; i < height ; i++, out += row, in += width * 4 ) {
|
|
for ( j = 0 ; j < width ; j++ ) {
|
|
x = in[ j * 4 + 0 ];
|
|
y = in[ j * 4 + 1 ];
|
|
z = in[ j * 4 + 2 ];
|
|
|
|
int c;
|
|
if ( x == 128 && y == 128 && z == 128 ) {
|
|
// the "nullnormal" color
|
|
c = 255;
|
|
} else {
|
|
c = ( globalImages->originalToCompressed[x] << 4 ) | globalImages->originalToCompressed[y];
|
|
if ( c == 255 ) {
|
|
c = 254; // don't use the nullnormal color
|
|
}
|
|
}
|
|
out[j] = c;
|
|
}
|
|
}
|
|
|
|
if ( mipLevel == 0 ) {
|
|
// Optionally write out the paletized normal map to a .tga
|
|
if ( globalImages->image_writeNormalTGAPalletized.GetBool() ) {
|
|
char filename[MAX_IMAGE_NAME];
|
|
ImageProgramStringToCompressedFileName( imgName, filename );
|
|
char *ext = strrchr(filename, '.');
|
|
if ( ext ) {
|
|
strcpy(ext, "_pal.tga");
|
|
R_WritePalTGA( filename, normals, globalImages->compressedPalette, width, height);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( glConfig.sharedTexturePaletteAvailable ) {
|
|
qglTexImage2D( GL_TEXTURE_2D,
|
|
mipLevel,
|
|
GL_COLOR_INDEX8_EXT,
|
|
width,
|
|
height,
|
|
0,
|
|
GL_COLOR_INDEX,
|
|
GL_UNSIGNED_BYTE,
|
|
normals );
|
|
}
|
|
}
|
|
|
|
|
|
//=======================================================================
|
|
|
|
|
|
static byte mipBlendColors[16][4] = {
|
|
{0,0,0,0},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
{255,0,0,128},
|
|
{0,255,0,128},
|
|
{0,0,255,128},
|
|
};
|
|
|
|
/*
|
|
===============
|
|
SelectInternalFormat
|
|
|
|
This may need to scan six cube map images
|
|
===============
|
|
*/
|
|
GLenum idImage::SelectInternalFormat( const byte **dataPtrs, int numDataPtrs, int width, int height,
|
|
textureDepth_t minimumDepth, bool *monochromeResult ) const {
|
|
int i, c;
|
|
const byte *scan;
|
|
int rgbOr, rgbAnd, aOr, aAnd;
|
|
int rgbDiffer, rgbaDiffer;
|
|
|
|
// determine if the rgb channels are all the same
|
|
// and if either all rgb or all alpha are 255
|
|
c = width*height;
|
|
rgbDiffer = 0;
|
|
rgbaDiffer = 0;
|
|
rgbOr = 0;
|
|
rgbAnd = -1;
|
|
aOr = 0;
|
|
aAnd = -1;
|
|
|
|
*monochromeResult = true; // until shown otherwise
|
|
|
|
for ( int side = 0 ; side < numDataPtrs ; side++ ) {
|
|
scan = dataPtrs[side];
|
|
for ( i = 0; i < c; i++, scan += 4 ) {
|
|
int cor, cand;
|
|
|
|
aOr |= scan[3];
|
|
aAnd &= scan[3];
|
|
|
|
cor = scan[0] | scan[1] | scan[2];
|
|
cand = scan[0] & scan[1] & scan[2];
|
|
|
|
// if rgb are all the same, the or and and will match
|
|
rgbDiffer |= ( cor ^ cand );
|
|
|
|
// our "isMonochrome" test is more lax than rgbDiffer,
|
|
// allowing the values to be off by several units and
|
|
// still use the NV20 mono path
|
|
if ( *monochromeResult ) {
|
|
if ( abs( scan[0] - scan[1] ) > 16
|
|
|| abs( scan[0] - scan[2] ) > 16 ) {
|
|
*monochromeResult = false;
|
|
}
|
|
}
|
|
|
|
rgbOr |= cor;
|
|
rgbAnd &= cand;
|
|
|
|
cor |= scan[3];
|
|
cand &= scan[3];
|
|
|
|
rgbaDiffer |= ( cor ^ cand );
|
|
}
|
|
}
|
|
|
|
// we assume that all 0 implies that the alpha channel isn't needed,
|
|
// because some tools will spit out 32 bit images with a 0 alpha instead
|
|
// of 255 alpha, but if the alpha actually is referenced, there will be
|
|
// different behavior in the compressed vs uncompressed states.
|
|
bool needAlpha;
|
|
if ( aAnd == 255 || aOr == 0 ) {
|
|
needAlpha = false;
|
|
} else {
|
|
needAlpha = true;
|
|
}
|
|
|
|
// catch normal maps first
|
|
if ( minimumDepth == TD_BUMP ) {
|
|
if ( globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() == 1 && glConfig.sharedTexturePaletteAvailable ) {
|
|
// image_useNormalCompression should only be set to 1 on nv_10 and nv_20 paths
|
|
return GL_COLOR_INDEX8_EXT;
|
|
} else if ( globalImages->image_useCompression.GetBool() && globalImages->image_useNormalCompression.GetInteger() && glConfig.textureCompressionAvailable ) {
|
|
// image_useNormalCompression == 2 uses rxgb format which produces really good quality for medium settings
|
|
return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
} else {
|
|
// we always need the alpha channel for bump maps for swizzling
|
|
return GL_RGBA8;
|
|
}
|
|
}
|
|
|
|
// allow a complete override of image compression with a cvar
|
|
if ( !globalImages->image_useCompression.GetBool() ) {
|
|
minimumDepth = TD_HIGH_QUALITY;
|
|
}
|
|
|
|
if ( minimumDepth == TD_SPECULAR ) {
|
|
// we are assuming that any alpha channel is unintentional
|
|
if ( glConfig.textureCompressionAvailable ) {
|
|
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
|
} else {
|
|
return GL_RGB5;
|
|
}
|
|
}
|
|
if ( minimumDepth == TD_DIFFUSE ) {
|
|
// we might intentionally have an alpha channel for alpha tested textures
|
|
if ( glConfig.textureCompressionAvailable ) {
|
|
if ( !needAlpha ) {
|
|
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
|
} else {
|
|
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
|
}
|
|
} else if ( ( aAnd == 255 || aOr == 0 ) ) {
|
|
return GL_RGB5;
|
|
} else {
|
|
return GL_RGBA4;
|
|
}
|
|
}
|
|
|
|
// there will probably be some drivers that don't
|
|
// correctly handle the intensity/alpha/luminance/luminance+alpha
|
|
// formats, so provide a fallback that only uses the rgb/rgba formats
|
|
if ( !globalImages->image_useAllFormats.GetBool() ) {
|
|
// pretend rgb is varying and inconsistant, which
|
|
// prevents any of the more compact forms
|
|
rgbDiffer = 1;
|
|
rgbaDiffer = 1;
|
|
rgbAnd = 0;
|
|
}
|
|
|
|
// cases without alpha
|
|
if ( !needAlpha ) {
|
|
if ( minimumDepth == TD_HIGH_QUALITY ) {
|
|
return GL_RGB8; // four bytes
|
|
}
|
|
if ( glConfig.textureCompressionAvailable ) {
|
|
return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; // half byte
|
|
}
|
|
return GL_RGB5; // two bytes
|
|
}
|
|
|
|
// cases with alpha
|
|
if ( !rgbaDiffer ) {
|
|
if ( minimumDepth != TD_HIGH_QUALITY && glConfig.textureCompressionAvailable ) {
|
|
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte
|
|
}
|
|
return GL_INTENSITY8; // single byte for all channels
|
|
}
|
|
|
|
#if 0
|
|
// we don't support alpha textures any more, because there
|
|
// is a discrepancy in the definition of TEX_ENV_COMBINE that
|
|
// causes them to be treated as 0 0 0 A, instead of 1 1 1 A as
|
|
// normal texture modulation treats them
|
|
if ( rgbAnd == 255 ) {
|
|
return GL_ALPHA8; // single byte, only alpha
|
|
}
|
|
#endif
|
|
|
|
if ( minimumDepth == TD_HIGH_QUALITY ) {
|
|
return GL_RGBA8; // four bytes
|
|
}
|
|
if ( glConfig.textureCompressionAvailable ) {
|
|
return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; // one byte
|
|
}
|
|
if ( !rgbDiffer ) {
|
|
return GL_LUMINANCE8_ALPHA8; // two bytes, max quality
|
|
}
|
|
return GL_RGBA4; // two bytes
|
|
}
|
|
|
|
/*
|
|
==================
|
|
SetImageFilterAndRepeat
|
|
==================
|
|
*/
|
|
void idImage::SetImageFilterAndRepeat() const {
|
|
// set the minimize / maximize filtering
|
|
switch( filter ) {
|
|
case TF_DEFAULT:
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter );
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter );
|
|
break;
|
|
case TF_LINEAR:
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
break;
|
|
case TF_NEAREST:
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
|
|
break;
|
|
default:
|
|
common->FatalError( "R_CreateImage: bad texture filter" );
|
|
}
|
|
|
|
if ( glConfig.anisotropicAvailable ) {
|
|
// only do aniso filtering on mip mapped images
|
|
if ( filter == TF_DEFAULT ) {
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, globalImages->textureAnisotropy );
|
|
} else {
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 );
|
|
}
|
|
}
|
|
if ( glConfig.textureLODBiasAvailable ) {
|
|
qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS_EXT, globalImages->textureLODBias );
|
|
}
|
|
|
|
// set the wrap/clamp modes
|
|
switch( repeat ) {
|
|
case TR_REPEAT:
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
|
|
break;
|
|
case TR_CLAMP_TO_BORDER:
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );
|
|
break;
|
|
case TR_CLAMP_TO_ZERO:
|
|
case TR_CLAMP_TO_ZERO_ALPHA:
|
|
case TR_CLAMP:
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
|
break;
|
|
default:
|
|
common->FatalError( "R_CreateImage: bad texture repeat" );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idImage::Downsize
|
|
helper function that takes the current width/height and might make them smaller
|
|
================
|
|
*/
|
|
void idImage::GetDownsize( int &scaled_width, int &scaled_height ) const {
|
|
int size = 0;
|
|
|
|
// perform optional picmip operation to save texture memory
|
|
if ( depth == TD_SPECULAR && globalImages->image_downSizeSpecular.GetInteger() ) {
|
|
size = globalImages->image_downSizeSpecularLimit.GetInteger();
|
|
if ( size == 0 ) {
|
|
size = 64;
|
|
}
|
|
} else if ( depth == TD_BUMP && globalImages->image_downSizeBump.GetInteger() ) {
|
|
size = globalImages->image_downSizeBumpLimit.GetInteger();
|
|
if ( size == 0 ) {
|
|
size = 64;
|
|
}
|
|
} else if ( ( allowDownSize || globalImages->image_forceDownSize.GetBool() ) && globalImages->image_downSize.GetInteger() ) {
|
|
size = globalImages->image_downSizeLimit.GetInteger();
|
|
if ( size == 0 ) {
|
|
size = 256;
|
|
}
|
|
}
|
|
|
|
if ( size > 0 ) {
|
|
while ( scaled_width > size || scaled_height > size ) {
|
|
if ( scaled_width > 1 ) {
|
|
scaled_width >>= 1;
|
|
}
|
|
if ( scaled_height > 1 ) {
|
|
scaled_height >>= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// clamp to minimum size
|
|
if ( scaled_width < 1 ) {
|
|
scaled_width = 1;
|
|
}
|
|
if ( scaled_height < 1 ) {
|
|
scaled_height = 1;
|
|
}
|
|
|
|
// clamp size to the hardware specific upper limit
|
|
// scale both axis down equally so we don't have to
|
|
// deal with a half mip resampling
|
|
// This causes a 512*256 texture to sample down to
|
|
// 256*128 on a voodoo3, even though it could be 256*256
|
|
while ( scaled_width > glConfig.maxTextureSize
|
|
|| scaled_height > glConfig.maxTextureSize ) {
|
|
scaled_width >>= 1;
|
|
scaled_height >>= 1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
GenerateImage
|
|
|
|
The alpha channel bytes should be 255 if you don't
|
|
want the channel.
|
|
|
|
We need a material characteristic to ask for specific texture modes.
|
|
|
|
Designed limitations of flexibility:
|
|
|
|
No support for texture borders.
|
|
|
|
No support for texture border color.
|
|
|
|
No support for texture environment colors or GL_BLEND or GL_DECAL
|
|
texture environments, because the automatic optimization to single
|
|
or dual component textures makes those modes potentially undefined.
|
|
|
|
No non-power-of-two images.
|
|
|
|
No palettized textures.
|
|
|
|
There is no way to specify separate wrap/clamp values for S and T
|
|
|
|
There is no way to specify explicit mip map levels
|
|
|
|
================
|
|
*/
|
|
void idImage::GenerateImage( const byte *pic, int width, int height,
|
|
textureFilter_t filterParm, bool allowDownSizeParm,
|
|
textureRepeat_t repeatParm, textureDepth_t depthParm ) {
|
|
bool preserveBorder;
|
|
byte *scaledBuffer;
|
|
int scaled_width, scaled_height;
|
|
byte *shrunk;
|
|
|
|
PurgeImage();
|
|
|
|
filter = filterParm;
|
|
allowDownSize = allowDownSizeParm;
|
|
repeat = repeatParm;
|
|
depth = depthParm;
|
|
|
|
// if we don't have a rendering context, just return after we
|
|
// have filled in the parms. We must have the values set, or
|
|
// an image match from a shader before OpenGL starts would miss
|
|
// the generated texture
|
|
if ( !glConfig.isInitialized ) {
|
|
return;
|
|
}
|
|
|
|
// don't let mip mapping smear the texture into the clamped border
|
|
if ( repeat == TR_CLAMP_TO_ZERO ) {
|
|
preserveBorder = true;
|
|
} else {
|
|
preserveBorder = false;
|
|
}
|
|
|
|
// make sure it is a power of 2
|
|
scaled_width = MakePowerOfTwo( width );
|
|
scaled_height = MakePowerOfTwo( height );
|
|
|
|
if ( scaled_width != width || scaled_height != height ) {
|
|
common->Error( "R_CreateImage: not a power of 2 image" );
|
|
}
|
|
|
|
// Optionally modify our width/height based on options/hardware
|
|
GetDownsize( scaled_width, scaled_height );
|
|
|
|
scaledBuffer = NULL;
|
|
|
|
// generate the texture number
|
|
qglGenTextures( 1, &texnum );
|
|
|
|
// select proper internal format before we resample
|
|
internalFormat = SelectInternalFormat( &pic, 1, width, height, depth, &isMonochrome );
|
|
|
|
// copy or resample data as appropriate for first MIP level
|
|
if ( ( scaled_width == width ) && ( scaled_height == height ) ) {
|
|
// we must copy even if unchanged, because the border zeroing
|
|
// would otherwise modify const data
|
|
scaledBuffer = (byte *)R_StaticAlloc( sizeof( unsigned ) * scaled_width * scaled_height );
|
|
memcpy (scaledBuffer, pic, width*height*4);
|
|
} else {
|
|
// resample down as needed (FIXME: this doesn't seem like it resamples anymore!)
|
|
// scaledBuffer = R_ResampleTexture( pic, width, height, width >>= 1, height >>= 1 );
|
|
scaledBuffer = R_MipMap( pic, width, height, preserveBorder );
|
|
width >>= 1;
|
|
height >>= 1;
|
|
if ( width < 1 ) {
|
|
width = 1;
|
|
}
|
|
if ( height < 1 ) {
|
|
height = 1;
|
|
}
|
|
|
|
while ( width > scaled_width || height > scaled_height ) {
|
|
shrunk = R_MipMap( scaledBuffer, width, height, preserveBorder );
|
|
R_StaticFree( scaledBuffer );
|
|
scaledBuffer = shrunk;
|
|
|
|
width >>= 1;
|
|
height >>= 1;
|
|
if ( width < 1 ) {
|
|
width = 1;
|
|
}
|
|
if ( height < 1 ) {
|
|
height = 1;
|
|
}
|
|
}
|
|
|
|
// one might have shrunk down below the target size
|
|
scaled_width = width;
|
|
scaled_height = height;
|
|
}
|
|
|
|
uploadHeight = scaled_height;
|
|
uploadWidth = scaled_width;
|
|
type = TT_2D;
|
|
|
|
// zero the border if desired, allowing clamped projection textures
|
|
// even after picmip resampling or careless artists.
|
|
if ( repeat == TR_CLAMP_TO_ZERO ) {
|
|
byte rgba[4];
|
|
|
|
rgba[0] = rgba[1] = rgba[2] = 0;
|
|
rgba[3] = 255;
|
|
R_SetBorderTexels( (byte *)scaledBuffer, width, height, rgba );
|
|
}
|
|
if ( repeat == TR_CLAMP_TO_ZERO_ALPHA ) {
|
|
byte rgba[4];
|
|
|
|
rgba[0] = rgba[1] = rgba[2] = 255;
|
|
rgba[3] = 0;
|
|
R_SetBorderTexels( (byte *)scaledBuffer, width, height, rgba );
|
|
}
|
|
|
|
if ( generatorFunction == NULL && ( (depth == TD_BUMP && globalImages->image_writeNormalTGA.GetBool()) || (depth != TD_BUMP && globalImages->image_writeTGA.GetBool()) ) ) {
|
|
// Optionally write out the texture to a .tga
|
|
char filename[MAX_IMAGE_NAME];
|
|
ImageProgramStringToCompressedFileName( imgName, filename );
|
|
char *ext = strrchr(filename, '.');
|
|
if ( ext ) {
|
|
strcpy( ext, ".tga" );
|
|
// swap the red/alpha for the write
|
|
/*
|
|
if ( depth == TD_BUMP ) {
|
|
for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) {
|
|
scaledBuffer[ i ] = scaledBuffer[ i + 3 ];
|
|
scaledBuffer[ i + 3 ] = 0;
|
|
}
|
|
}
|
|
*/
|
|
R_WriteTGA( filename, scaledBuffer, scaled_width, scaled_height, false );
|
|
|
|
// put it back
|
|
/*
|
|
if ( depth == TD_BUMP ) {
|
|
for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) {
|
|
scaledBuffer[ i + 3 ] = scaledBuffer[ i ];
|
|
scaledBuffer[ i ] = 0;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
// swap the red and alpha for rxgb support
|
|
// do this even on tga normal maps so we only have to use
|
|
// one fragment program
|
|
// if the image is precompressed ( either in palletized mode or true rxgb mode )
|
|
// then it is loaded above and the swap never happens here
|
|
if ( depth == TD_BUMP && globalImages->image_useNormalCompression.GetInteger() != 1 ) {
|
|
for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) {
|
|
scaledBuffer[ i + 3 ] = scaledBuffer[ i ];
|
|
scaledBuffer[ i ] = 0;
|
|
}
|
|
}
|
|
// upload the main image level
|
|
Bind();
|
|
|
|
|
|
if ( internalFormat == GL_COLOR_INDEX8_EXT ) {
|
|
/*
|
|
if ( depth == TD_BUMP ) {
|
|
for ( int i = 0; i < scaled_width * scaled_height * 4; i += 4 ) {
|
|
scaledBuffer[ i ] = scaledBuffer[ i + 3 ];
|
|
scaledBuffer[ i + 3 ] = 0;
|
|
}
|
|
}
|
|
*/
|
|
UploadCompressedNormalMap( scaled_width, scaled_height, scaledBuffer, 0 );
|
|
} else {
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
}
|
|
|
|
// create and upload the mip map levels, which we do in all cases, even if we don't think they are needed
|
|
int miplevel;
|
|
|
|
miplevel = 0;
|
|
while ( scaled_width > 1 || scaled_height > 1 ) {
|
|
// preserve the border after mip map unless repeating
|
|
shrunk = R_MipMap( scaledBuffer, scaled_width, scaled_height, preserveBorder );
|
|
R_StaticFree( scaledBuffer );
|
|
scaledBuffer = shrunk;
|
|
|
|
scaled_width >>= 1;
|
|
scaled_height >>= 1;
|
|
if ( scaled_width < 1 ) {
|
|
scaled_width = 1;
|
|
}
|
|
if ( scaled_height < 1 ) {
|
|
scaled_height = 1;
|
|
}
|
|
miplevel++;
|
|
|
|
// this is a visualization tool that shades each mip map
|
|
// level with a different color so you can see the
|
|
// rasterizer's texture level selection algorithm
|
|
// Changing the color doesn't help with lumminance/alpha/intensity formats...
|
|
if ( depth == TD_DIFFUSE && globalImages->image_colorMipLevels.GetBool() ) {
|
|
R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] );
|
|
}
|
|
|
|
// upload the mip map
|
|
if ( internalFormat == GL_COLOR_INDEX8_EXT ) {
|
|
UploadCompressedNormalMap( scaled_width, scaled_height, scaledBuffer, miplevel );
|
|
} else {
|
|
qglTexImage2D( GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height,
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
}
|
|
}
|
|
|
|
if ( scaledBuffer != 0 ) {
|
|
R_StaticFree( scaledBuffer );
|
|
}
|
|
|
|
SetImageFilterAndRepeat();
|
|
|
|
// see if we messed anything up
|
|
GL_CheckErrors();
|
|
}
|
|
|
|
|
|
/*
|
|
==================
|
|
Generate3DImage
|
|
==================
|
|
*/
|
|
void idImage::Generate3DImage( const byte *pic, int width, int height, int picDepth,
|
|
textureFilter_t filterParm, bool allowDownSizeParm,
|
|
textureRepeat_t repeatParm, textureDepth_t minDepthParm ) {
|
|
int scaled_width, scaled_height, scaled_depth;
|
|
|
|
PurgeImage();
|
|
|
|
filter = filterParm;
|
|
allowDownSize = allowDownSizeParm;
|
|
repeat = repeatParm;
|
|
depth = minDepthParm;
|
|
|
|
// if we don't have a rendering context, just return after we
|
|
// have filled in the parms. We must have the values set, or
|
|
// an image match from a shader before OpenGL starts would miss
|
|
// the generated texture
|
|
if ( !glConfig.isInitialized ) {
|
|
return;
|
|
}
|
|
|
|
// make sure it is a power of 2
|
|
scaled_width = MakePowerOfTwo( width );
|
|
scaled_height = MakePowerOfTwo( height );
|
|
scaled_depth = MakePowerOfTwo( picDepth );
|
|
if ( scaled_width != width || scaled_height != height || scaled_depth != picDepth ) {
|
|
common->Error( "R_Create3DImage: not a power of 2 image" );
|
|
}
|
|
|
|
// FIXME: allow picmip here
|
|
|
|
// generate the texture number
|
|
qglGenTextures( 1, &texnum );
|
|
|
|
// select proper internal format before we resample
|
|
// this function doesn't need to know it is 3D, so just make it very "tall"
|
|
internalFormat = SelectInternalFormat( &pic, 1, width, height * picDepth, minDepthParm, &isMonochrome );
|
|
|
|
uploadHeight = scaled_height;
|
|
uploadWidth = scaled_width;
|
|
uploadDepth = scaled_depth;
|
|
|
|
|
|
type = TT_3D;
|
|
|
|
// upload the main image level
|
|
Bind();
|
|
|
|
qglTexImage3D(GL_TEXTURE_3D, 0, internalFormat, scaled_width, scaled_height, scaled_depth,
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, pic );
|
|
|
|
// create and upload the mip map levels
|
|
int miplevel;
|
|
byte *scaledBuffer, *shrunk;
|
|
|
|
scaledBuffer = (byte *)R_StaticAlloc( scaled_width * scaled_height * scaled_depth * 4 );
|
|
memcpy( scaledBuffer, pic, scaled_width * scaled_height * scaled_depth * 4 );
|
|
miplevel = 0;
|
|
while ( scaled_width > 1 || scaled_height > 1 || scaled_depth > 1 ) {
|
|
// preserve the border after mip map unless repeating
|
|
shrunk = R_MipMap3D( scaledBuffer, scaled_width, scaled_height, scaled_depth,
|
|
(bool)(repeat != TR_REPEAT) );
|
|
R_StaticFree( scaledBuffer );
|
|
scaledBuffer = shrunk;
|
|
|
|
scaled_width >>= 1;
|
|
scaled_height >>= 1;
|
|
scaled_depth >>= 1;
|
|
if ( scaled_width < 1 ) {
|
|
scaled_width = 1;
|
|
}
|
|
if ( scaled_height < 1 ) {
|
|
scaled_height = 1;
|
|
}
|
|
if ( scaled_depth < 1 ) {
|
|
scaled_depth = 1;
|
|
}
|
|
miplevel++;
|
|
|
|
// upload the mip map
|
|
qglTexImage3D(GL_TEXTURE_3D, miplevel, internalFormat, scaled_width, scaled_height, scaled_depth,
|
|
0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
}
|
|
R_StaticFree( scaledBuffer );
|
|
|
|
// set the minimize / maximize filtering
|
|
switch( filter ) {
|
|
case TF_DEFAULT:
|
|
qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter );
|
|
qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter );
|
|
break;
|
|
case TF_LINEAR:
|
|
qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
break;
|
|
case TF_NEAREST:
|
|
qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
|
qglTexParameterf(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
|
|
break;
|
|
default:
|
|
common->FatalError( "R_CreateImage: bad texture filter" );
|
|
}
|
|
|
|
// set the wrap/clamp modes
|
|
switch( repeat ) {
|
|
case TR_REPEAT:
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT );
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT );
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT );
|
|
break;
|
|
case TR_CLAMP_TO_BORDER:
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER );
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER );
|
|
break;
|
|
case TR_CLAMP_TO_ZERO:
|
|
case TR_CLAMP_TO_ZERO_ALPHA:
|
|
case TR_CLAMP:
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE );
|
|
break;
|
|
default:
|
|
common->FatalError( "R_CreateImage: bad texture repeat" );
|
|
}
|
|
|
|
// see if we messed anything up
|
|
GL_CheckErrors();
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
GenerateCubeImage
|
|
|
|
Non-square cube sides are not allowed
|
|
====================
|
|
*/
|
|
void idImage::GenerateCubeImage( const byte *pic[6], int size,
|
|
textureFilter_t filterParm, bool allowDownSizeParm,
|
|
textureDepth_t depthParm ) {
|
|
int scaled_width, scaled_height;
|
|
int width, height;
|
|
int i;
|
|
|
|
PurgeImage();
|
|
|
|
filter = filterParm;
|
|
allowDownSize = allowDownSizeParm;
|
|
depth = depthParm;
|
|
|
|
type = TT_CUBIC;
|
|
|
|
// if we don't have a rendering context, just return after we
|
|
// have filled in the parms. We must have the values set, or
|
|
// an image match from a shader before OpenGL starts would miss
|
|
// the generated texture
|
|
if ( !glConfig.isInitialized ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! glConfig.cubeMapAvailable ) {
|
|
return;
|
|
}
|
|
|
|
width = height = size;
|
|
|
|
// generate the texture number
|
|
qglGenTextures( 1, &texnum );
|
|
|
|
// select proper internal format before we resample
|
|
internalFormat = SelectInternalFormat( pic, 6, width, height, depth, &isMonochrome );
|
|
|
|
// don't bother with downsample for now
|
|
scaled_width = width;
|
|
scaled_height = height;
|
|
|
|
uploadHeight = scaled_height;
|
|
uploadWidth = scaled_width;
|
|
|
|
Bind();
|
|
|
|
// no other clamp mode makes sense
|
|
qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
// set the minimize / maximize filtering
|
|
switch( filter ) {
|
|
case TF_DEFAULT:
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter );
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter );
|
|
break;
|
|
case TF_LINEAR:
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
break;
|
|
case TF_NEAREST:
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
|
|
break;
|
|
default:
|
|
common->FatalError( "R_CreateImage: bad texture filter" );
|
|
}
|
|
|
|
// upload the base level
|
|
// FIXME: support GL_COLOR_INDEX8_EXT?
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
qglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, 0, internalFormat, scaled_width, scaled_height, 0,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, pic[i] );
|
|
}
|
|
|
|
|
|
// create and upload the mip map levels
|
|
int miplevel;
|
|
byte *shrunk[6];
|
|
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
shrunk[i] = R_MipMap( pic[i], scaled_width, scaled_height, false );
|
|
}
|
|
|
|
miplevel = 1;
|
|
while ( scaled_width > 1 ) {
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
byte *shrunken;
|
|
|
|
qglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, miplevel, internalFormat,
|
|
scaled_width / 2, scaled_height / 2, 0,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, shrunk[i] );
|
|
|
|
if ( scaled_width > 2 ) {
|
|
shrunken = R_MipMap( shrunk[i], scaled_width/2, scaled_height/2, false );
|
|
} else {
|
|
shrunken = NULL;
|
|
}
|
|
|
|
R_StaticFree( shrunk[i] );
|
|
shrunk[i] = shrunken;
|
|
}
|
|
|
|
scaled_width >>= 1;
|
|
scaled_height >>= 1;
|
|
miplevel++;
|
|
}
|
|
|
|
// see if we messed anything up
|
|
GL_CheckErrors();
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
ImageProgramStringToFileCompressedFileName
|
|
================
|
|
*/
|
|
void idImage::ImageProgramStringToCompressedFileName( const char *imageProg, char *fileName ) const {
|
|
const char *s;
|
|
char *f;
|
|
|
|
strcpy( fileName, "dds/" );
|
|
f = fileName + strlen( fileName );
|
|
|
|
int depth = 0;
|
|
|
|
// convert all illegal characters to underscores
|
|
// this could conceivably produce a duplicated mapping, but we aren't going to worry about it
|
|
for ( s = imageProg ; *s ; s++ ) {
|
|
if ( *s == '/' || *s == '\\' || *s == '(') {
|
|
if ( depth < 4 ) {
|
|
*f = '/';
|
|
depth ++;
|
|
} else {
|
|
*f = ' ';
|
|
}
|
|
f++;
|
|
} else if ( *s == '<' || *s == '>' || *s == ':' || *s == '|' || *s == '"' || *s == '.' ) {
|
|
*f = '_';
|
|
f++;
|
|
} else if ( *s == ' ' && *(f-1) == '/' ) { // ignore a space right after a slash
|
|
} else if ( *s == ')' || *s == ',' ) { // always ignore these
|
|
} else {
|
|
*f = *s;
|
|
f++;
|
|
}
|
|
}
|
|
*f++ = 0;
|
|
strcat( fileName, ".dds" );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
NumLevelsForImageSize
|
|
==================
|
|
*/
|
|
int idImage::NumLevelsForImageSize( int width, int height ) const {
|
|
int numLevels = 1;
|
|
|
|
while ( width > 1 || height > 1 ) {
|
|
numLevels++;
|
|
width >>= 1;
|
|
height >>= 1;
|
|
}
|
|
|
|
return numLevels;
|
|
}
|
|
|
|
/*
|
|
================
|
|
WritePrecompressedImage
|
|
|
|
When we are happy with our source data, we can write out precompressed
|
|
versions of everything to speed future load times.
|
|
================
|
|
*/
|
|
void idImage::WritePrecompressedImage() {
|
|
|
|
// Always write the precompressed image if we're making a build
|
|
if ( !com_makingBuild.GetBool() ) {
|
|
if ( !globalImages->image_writePrecompressedTextures.GetBool() || !globalImages->image_usePrecompressedTextures.GetBool() ) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !glConfig.isInitialized ) {
|
|
return;
|
|
}
|
|
|
|
char filename[MAX_IMAGE_NAME];
|
|
ImageProgramStringToCompressedFileName( imgName, filename );
|
|
|
|
|
|
|
|
int numLevels = NumLevelsForImageSize( uploadWidth, uploadHeight );
|
|
if ( numLevels > MAX_TEXTURE_LEVELS ) {
|
|
common->Warning( "R_WritePrecompressedImage: level > MAX_TEXTURE_LEVELS for image %s", filename );
|
|
return;
|
|
}
|
|
|
|
// glGetTexImage only supports a small subset of all the available internal formats
|
|
// We have to use BGRA because DDS is a windows based format
|
|
int altInternalFormat = 0;
|
|
int bitSize = 0;
|
|
switch ( internalFormat ) {
|
|
case GL_COLOR_INDEX8_EXT:
|
|
case GL_COLOR_INDEX:
|
|
// this will not work with dds viewers but we need it in this format to save disk
|
|
// load speed ( i.e. size )
|
|
altInternalFormat = GL_COLOR_INDEX;
|
|
bitSize = 24;
|
|
break;
|
|
case 1:
|
|
case GL_INTENSITY8:
|
|
case GL_LUMINANCE8:
|
|
case 3:
|
|
case GL_RGB8:
|
|
altInternalFormat = GL_BGR_EXT;
|
|
bitSize = 24;
|
|
break;
|
|
case GL_LUMINANCE8_ALPHA8:
|
|
case 4:
|
|
case GL_RGBA8:
|
|
altInternalFormat = GL_BGRA_EXT;
|
|
bitSize = 32;
|
|
break;
|
|
case GL_ALPHA8:
|
|
altInternalFormat = GL_ALPHA;
|
|
bitSize = 8;
|
|
break;
|
|
default:
|
|
if ( FormatIsDXT( internalFormat ) ) {
|
|
altInternalFormat = internalFormat;
|
|
} else {
|
|
common->Warning("Unknown or unsupported format for %s", filename);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( globalImages->image_useOffLineCompression.GetBool() && FormatIsDXT( altInternalFormat ) ) {
|
|
idStr outFile = fileSystem->RelativePathToOSPath( filename, "fs_basepath" );
|
|
idStr inFile = outFile;
|
|
inFile.StripFileExtension();
|
|
inFile.SetFileExtension( "tga" );
|
|
idStr format;
|
|
if ( depth == TD_BUMP ) {
|
|
format = "RXGB +red 0.0 +green 0.5 +blue 0.5";
|
|
} else {
|
|
switch ( altInternalFormat ) {
|
|
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
|
format = "DXT1";
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
|
format = "DXT1 -alpha_threshold";
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
|
format = "DXT3";
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
|
format = "DXT5";
|
|
break;
|
|
}
|
|
}
|
|
globalImages->AddDDSCommand( va( "z:/d3xp/compressonator/thecompressonator -convert \"%s\" \"%s\" %s -mipmaps\n", inFile.c_str(), outFile.c_str(), format.c_str() ) );
|
|
return;
|
|
}
|
|
|
|
|
|
ddsFileHeader_t header;
|
|
memset( &header, 0, sizeof(header) );
|
|
header.dwSize = sizeof(header);
|
|
header.dwFlags = DDSF_CAPS | DDSF_PIXELFORMAT | DDSF_WIDTH | DDSF_HEIGHT;
|
|
header.dwHeight = uploadHeight;
|
|
header.dwWidth = uploadWidth;
|
|
|
|
// hack in our monochrome flag for the NV20 optimization
|
|
if ( isMonochrome ) {
|
|
header.dwFlags |= DDSF_ID_MONOCHROME;
|
|
}
|
|
|
|
if ( FormatIsDXT( altInternalFormat ) ) {
|
|
// size (in bytes) of the compressed base image
|
|
header.dwFlags |= DDSF_LINEARSIZE;
|
|
header.dwPitchOrLinearSize = ( ( uploadWidth + 3 ) / 4 ) * ( ( uploadHeight + 3 ) / 4 )*
|
|
(altInternalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16);
|
|
}
|
|
else {
|
|
// 4 Byte aligned line width (from nv_dds)
|
|
header.dwFlags |= DDSF_PITCH;
|
|
header.dwPitchOrLinearSize = ( ( uploadWidth * bitSize + 31 ) & -32 ) >> 3;
|
|
}
|
|
|
|
header.dwCaps1 = DDSF_TEXTURE;
|
|
|
|
if ( numLevels > 1 ) {
|
|
header.dwMipMapCount = numLevels;
|
|
header.dwFlags |= DDSF_MIPMAPCOUNT;
|
|
header.dwCaps1 |= DDSF_MIPMAP | DDSF_COMPLEX;
|
|
}
|
|
|
|
header.ddspf.dwSize = sizeof(header.ddspf);
|
|
if ( FormatIsDXT( altInternalFormat ) ) {
|
|
header.ddspf.dwFlags = DDSF_FOURCC;
|
|
switch ( altInternalFormat ) {
|
|
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
|
header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','1');
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
|
header.ddspf.dwFlags |= DDSF_ALPHAPIXELS;
|
|
header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','1');
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
|
header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','3');
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
|
header.ddspf.dwFourCC = DDS_MAKEFOURCC('D','X','T','5');
|
|
break;
|
|
}
|
|
} else {
|
|
header.ddspf.dwFlags = ( internalFormat == GL_COLOR_INDEX8_EXT ) ? DDSF_RGB | DDSF_ID_INDEXCOLOR : DDSF_RGB;
|
|
header.ddspf.dwRGBBitCount = bitSize;
|
|
switch ( altInternalFormat ) {
|
|
case GL_BGRA_EXT:
|
|
case GL_LUMINANCE_ALPHA:
|
|
header.ddspf.dwFlags |= DDSF_ALPHAPIXELS;
|
|
header.ddspf.dwABitMask = 0xFF000000;
|
|
// Fall through
|
|
case GL_BGR_EXT:
|
|
case GL_LUMINANCE:
|
|
case GL_COLOR_INDEX:
|
|
header.ddspf.dwRBitMask = 0x00FF0000;
|
|
header.ddspf.dwGBitMask = 0x0000FF00;
|
|
header.ddspf.dwBBitMask = 0x000000FF;
|
|
break;
|
|
case GL_ALPHA:
|
|
header.ddspf.dwFlags = DDSF_ALPHAPIXELS;
|
|
header.ddspf.dwABitMask = 0xFF000000;
|
|
break;
|
|
default:
|
|
common->Warning( "Unknown or unsupported format for %s", filename );
|
|
return;
|
|
}
|
|
}
|
|
|
|
idFile *f = fileSystem->OpenFileWrite( filename );
|
|
if ( f == NULL ) {
|
|
common->Warning( "Could not open %s trying to write precompressed image", filename );
|
|
return;
|
|
}
|
|
common->Printf( "Writing precompressed image: %s\n", filename );
|
|
|
|
f->Write( "DDS ", 4 );
|
|
f->Write( &header, sizeof(header) );
|
|
|
|
// bind to the image so we can read back the contents
|
|
Bind();
|
|
|
|
qglPixelStorei( GL_PACK_ALIGNMENT, 1 ); // otherwise small rows get padded to 32 bits
|
|
|
|
int uw = uploadWidth;
|
|
int uh = uploadHeight;
|
|
|
|
// Will be allocated first time through the loop
|
|
byte *data = NULL;
|
|
|
|
for ( int level = 0 ; level < numLevels ; level++ ) {
|
|
|
|
int size = 0;
|
|
if ( FormatIsDXT( altInternalFormat ) ) {
|
|
size = ( ( uw + 3 ) / 4 ) * ( ( uh + 3 ) / 4 ) *
|
|
(altInternalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16);
|
|
} else {
|
|
size = uw * uh * (bitSize / 8);
|
|
}
|
|
|
|
if (data == NULL) {
|
|
data = (byte *)R_StaticAlloc( size );
|
|
}
|
|
|
|
if ( FormatIsDXT( altInternalFormat ) ) {
|
|
qglGetCompressedTexImageARB( GL_TEXTURE_2D, level, data );
|
|
} else {
|
|
qglGetTexImage( GL_TEXTURE_2D, level, altInternalFormat, GL_UNSIGNED_BYTE, data );
|
|
}
|
|
|
|
f->Write( data, size );
|
|
|
|
uw /= 2;
|
|
uh /= 2;
|
|
if (uw < 1) {
|
|
uw = 1;
|
|
}
|
|
if (uh < 1) {
|
|
uh = 1;
|
|
}
|
|
}
|
|
|
|
if (data != NULL) {
|
|
R_StaticFree( data );
|
|
}
|
|
|
|
fileSystem->CloseFile( f );
|
|
}
|
|
|
|
/*
|
|
================
|
|
ShouldImageBePartialCached
|
|
|
|
Returns true if there is a precompressed image, and it is large enough
|
|
to be worth caching
|
|
================
|
|
*/
|
|
bool idImage::ShouldImageBePartialCached() {
|
|
if ( !glConfig.textureCompressionAvailable ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !globalImages->image_useCache.GetBool() ) {
|
|
return false;
|
|
}
|
|
|
|
// the allowDownSize flag does double-duty as don't-partial-load
|
|
if ( !allowDownSize ) {
|
|
return false;
|
|
}
|
|
|
|
if ( globalImages->image_cacheMinK.GetInteger() <= 0 ) {
|
|
return false;
|
|
}
|
|
|
|
// if we are doing a copyFiles, make sure the original images are referenced
|
|
if ( fileSystem->PerformingCopyFiles() ) {
|
|
return false;
|
|
}
|
|
|
|
char filename[MAX_IMAGE_NAME];
|
|
ImageProgramStringToCompressedFileName( imgName, filename );
|
|
|
|
// get the file timestamp
|
|
fileSystem->ReadFile( filename, NULL, ×tamp );
|
|
|
|
if ( timestamp == FILE_NOT_FOUND_TIMESTAMP ) {
|
|
return false;
|
|
}
|
|
|
|
// open it and get the file size
|
|
idFile *f;
|
|
|
|
f = fileSystem->OpenFileRead( filename );
|
|
if ( !f ) {
|
|
return false;
|
|
}
|
|
|
|
int len = f->Length();
|
|
fileSystem->CloseFile( f );
|
|
|
|
if ( len <= globalImages->image_cacheMinK.GetInteger() * 1024 ) {
|
|
return false;
|
|
}
|
|
|
|
// we do want to do a partial load
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CheckPrecompressedImage
|
|
|
|
If fullLoad is false, only the small mip levels of the image will be loaded
|
|
================
|
|
*/
|
|
bool idImage::CheckPrecompressedImage( bool fullLoad ) {
|
|
if ( !glConfig.isInitialized || !glConfig.textureCompressionAvailable ) {
|
|
return false;
|
|
}
|
|
|
|
#if 1 // ( _D3XP had disabled ) - Allow grabbing of DDS's from original Doom pak files
|
|
// if we are doing a copyFiles, make sure the original images are referenced
|
|
if ( fileSystem->PerformingCopyFiles() ) {
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
if ( depth == TD_BUMP && globalImages->image_useNormalCompression.GetInteger() != 2 ) {
|
|
return false;
|
|
}
|
|
|
|
// god i love last minute hacks :-)
|
|
if ( com_machineSpec.GetInteger() >= 1 && com_videoRam.GetInteger() >= 128 && imgName.Icmpn( "lights/", 7 ) == 0 ) {
|
|
return false;
|
|
}
|
|
|
|
char filename[MAX_IMAGE_NAME];
|
|
ImageProgramStringToCompressedFileName( imgName, filename );
|
|
|
|
// get the file timestamp
|
|
ID_TIME_T precompTimestamp;
|
|
fileSystem->ReadFile( filename, NULL, &precompTimestamp );
|
|
|
|
|
|
if ( precompTimestamp == FILE_NOT_FOUND_TIMESTAMP ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !generatorFunction && timestamp != FILE_NOT_FOUND_TIMESTAMP ) {
|
|
if ( precompTimestamp < timestamp ) {
|
|
// The image has changed after being precompressed
|
|
return false;
|
|
}
|
|
}
|
|
|
|
timestamp = precompTimestamp;
|
|
|
|
// open it and just read the header
|
|
idFile *f;
|
|
|
|
f = fileSystem->OpenFileRead( filename );
|
|
if ( !f ) {
|
|
return false;
|
|
}
|
|
|
|
int len = f->Length();
|
|
if ( len < sizeof( ddsFileHeader_t ) ) {
|
|
fileSystem->CloseFile( f );
|
|
return false;
|
|
}
|
|
|
|
if ( !fullLoad && len > globalImages->image_cacheMinK.GetInteger() * 1024 ) {
|
|
len = globalImages->image_cacheMinK.GetInteger() * 1024;
|
|
}
|
|
|
|
byte *data = (byte *)R_StaticAlloc( len );
|
|
|
|
f->Read( data, len );
|
|
|
|
fileSystem->CloseFile( f );
|
|
|
|
unsigned long magic = LittleLong( *(unsigned long *)data );
|
|
ddsFileHeader_t *_header = (ddsFileHeader_t *)(data + 4);
|
|
int ddspf_dwFlags = LittleLong( _header->ddspf.dwFlags );
|
|
|
|
if ( magic != DDS_MAKEFOURCC('D', 'D', 'S', ' ')) {
|
|
common->Printf( "CheckPrecompressedImage( %s ): magic != 'DDS '\n", imgName.c_str() );
|
|
R_StaticFree( data );
|
|
return false;
|
|
}
|
|
|
|
// if we don't support color index textures, we must load the full image
|
|
// should we just expand the 256 color image to 32 bit for upload?
|
|
if ( ddspf_dwFlags & DDSF_ID_INDEXCOLOR && !glConfig.sharedTexturePaletteAvailable ) {
|
|
R_StaticFree( data );
|
|
return false;
|
|
}
|
|
|
|
// upload all the levels
|
|
UploadPrecompressedImage( data, len );
|
|
|
|
R_StaticFree( data );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
UploadPrecompressedImage
|
|
|
|
This can be called by the front end during nromal loading,
|
|
or by the backend after a background read of the file
|
|
has completed
|
|
===================
|
|
*/
|
|
void idImage::UploadPrecompressedImage( byte *data, int len ) {
|
|
ddsFileHeader_t *header = (ddsFileHeader_t *)(data + 4);
|
|
|
|
// ( not byte swapping dwReserved1 dwReserved2 )
|
|
header->dwSize = LittleLong( header->dwSize );
|
|
header->dwFlags = LittleLong( header->dwFlags );
|
|
header->dwHeight = LittleLong( header->dwHeight );
|
|
header->dwWidth = LittleLong( header->dwWidth );
|
|
header->dwPitchOrLinearSize = LittleLong( header->dwPitchOrLinearSize );
|
|
header->dwDepth = LittleLong( header->dwDepth );
|
|
header->dwMipMapCount = LittleLong( header->dwMipMapCount );
|
|
header->dwCaps1 = LittleLong( header->dwCaps1 );
|
|
header->dwCaps2 = LittleLong( header->dwCaps2 );
|
|
|
|
header->ddspf.dwSize = LittleLong( header->ddspf.dwSize );
|
|
header->ddspf.dwFlags = LittleLong( header->ddspf.dwFlags );
|
|
header->ddspf.dwFourCC = LittleLong( header->ddspf.dwFourCC );
|
|
header->ddspf.dwRGBBitCount = LittleLong( header->ddspf.dwRGBBitCount );
|
|
header->ddspf.dwRBitMask = LittleLong( header->ddspf.dwRBitMask );
|
|
header->ddspf.dwGBitMask = LittleLong( header->ddspf.dwGBitMask );
|
|
header->ddspf.dwBBitMask = LittleLong( header->ddspf.dwBBitMask );
|
|
header->ddspf.dwABitMask = LittleLong( header->ddspf.dwABitMask );
|
|
|
|
// generate the texture number
|
|
qglGenTextures( 1, &texnum );
|
|
|
|
int externalFormat = 0;
|
|
|
|
precompressedFile = true;
|
|
|
|
uploadWidth = header->dwWidth;
|
|
uploadHeight = header->dwHeight;
|
|
if ( header->ddspf.dwFlags & DDSF_FOURCC ) {
|
|
switch ( header->ddspf.dwFourCC ) {
|
|
case DDS_MAKEFOURCC( 'D', 'X', 'T', '1' ):
|
|
if ( header->ddspf.dwFlags & DDSF_ALPHAPIXELS ) {
|
|
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
|
} else {
|
|
internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
|
|
}
|
|
break;
|
|
case DDS_MAKEFOURCC( 'D', 'X', 'T', '3' ):
|
|
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
|
|
break;
|
|
case DDS_MAKEFOURCC( 'D', 'X', 'T', '5' ):
|
|
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
break;
|
|
case DDS_MAKEFOURCC( 'R', 'X', 'G', 'B' ):
|
|
internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
|
break;
|
|
default:
|
|
common->Warning( "Invalid compressed internal format\n" );
|
|
return;
|
|
}
|
|
} else if ( ( header->ddspf.dwFlags & DDSF_RGBA ) && header->ddspf.dwRGBBitCount == 32 ) {
|
|
externalFormat = GL_BGRA_EXT;
|
|
internalFormat = GL_RGBA8;
|
|
} else if ( ( header->ddspf.dwFlags & DDSF_RGB ) && header->ddspf.dwRGBBitCount == 32 ) {
|
|
externalFormat = GL_BGRA_EXT;
|
|
internalFormat = GL_RGBA8;
|
|
} else if ( ( header->ddspf.dwFlags & DDSF_RGB ) && header->ddspf.dwRGBBitCount == 24 ) {
|
|
if ( header->ddspf.dwFlags & DDSF_ID_INDEXCOLOR ) {
|
|
externalFormat = GL_COLOR_INDEX;
|
|
internalFormat = GL_COLOR_INDEX8_EXT;
|
|
} else {
|
|
externalFormat = GL_BGR_EXT;
|
|
internalFormat = GL_RGB8;
|
|
}
|
|
} else if ( header->ddspf.dwRGBBitCount == 8 ) {
|
|
externalFormat = GL_ALPHA;
|
|
internalFormat = GL_ALPHA8;
|
|
} else {
|
|
common->Warning( "Invalid uncompressed internal format\n" );
|
|
return;
|
|
}
|
|
|
|
// we need the monochrome flag for the NV20 optimized path
|
|
if ( header->dwFlags & DDSF_ID_MONOCHROME ) {
|
|
isMonochrome = true;
|
|
}
|
|
|
|
type = TT_2D; // FIXME: we may want to support pre-compressed cube maps in the future
|
|
|
|
Bind();
|
|
|
|
int numMipmaps = 1;
|
|
if ( header->dwFlags & DDSF_MIPMAPCOUNT ) {
|
|
numMipmaps = header->dwMipMapCount;
|
|
}
|
|
|
|
int uw = uploadWidth;
|
|
int uh = uploadHeight;
|
|
|
|
// We may skip some mip maps if we are downsizing
|
|
int skipMip = 0;
|
|
GetDownsize( uploadWidth, uploadHeight );
|
|
|
|
byte *imagedata = data + sizeof(ddsFileHeader_t) + 4;
|
|
|
|
for ( int i = 0 ; i < numMipmaps; i++ ) {
|
|
int size = 0;
|
|
if ( FormatIsDXT( internalFormat ) ) {
|
|
size = ( ( uw + 3 ) / 4 ) * ( ( uh + 3 ) / 4 ) *
|
|
(internalFormat <= GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ? 8 : 16);
|
|
} else {
|
|
size = uw * uh * (header->ddspf.dwRGBBitCount / 8);
|
|
}
|
|
|
|
if ( uw > uploadWidth || uh > uploadHeight ) {
|
|
skipMip++;
|
|
} else {
|
|
if ( FormatIsDXT( internalFormat ) ) {
|
|
qglCompressedTexImage2DARB( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, size, imagedata );
|
|
} else {
|
|
qglTexImage2D( GL_TEXTURE_2D, i - skipMip, internalFormat, uw, uh, 0, externalFormat, GL_UNSIGNED_BYTE, imagedata );
|
|
}
|
|
}
|
|
|
|
imagedata += size;
|
|
uw /= 2;
|
|
uh /= 2;
|
|
if (uw < 1) {
|
|
uw = 1;
|
|
}
|
|
if (uh < 1) {
|
|
uh = 1;
|
|
}
|
|
}
|
|
|
|
SetImageFilterAndRepeat();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
ActuallyLoadImage
|
|
|
|
Absolutely every image goes through this path
|
|
On exit, the idImage will have a valid OpenGL texture number that can be bound
|
|
===============
|
|
*/
|
|
void idImage::ActuallyLoadImage( bool checkForPrecompressed, bool fromBackEnd ) {
|
|
int width, height;
|
|
byte *pic;
|
|
|
|
// this is the ONLY place generatorFunction will ever be called
|
|
if ( generatorFunction ) {
|
|
generatorFunction( this );
|
|
return;
|
|
}
|
|
|
|
// if we are a partial image, we are only going to load from a compressed file
|
|
if ( isPartialImage ) {
|
|
if ( CheckPrecompressedImage( false ) ) {
|
|
return;
|
|
}
|
|
// this is an error -- the partial image failed to load
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// load the image from disk
|
|
//
|
|
if ( cubeFiles != CF_2D ) {
|
|
byte *pics[6];
|
|
|
|
// we don't check for pre-compressed cube images currently
|
|
R_LoadCubeImages( imgName, cubeFiles, pics, &width, ×tamp );
|
|
|
|
if ( pics[0] == NULL ) {
|
|
common->Warning( "Couldn't load cube image: %s", imgName.c_str() );
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
GenerateCubeImage( (const byte **)pics, width, filter, allowDownSize, depth );
|
|
precompressedFile = false;
|
|
|
|
for ( int i = 0 ; i < 6 ; i++ ) {
|
|
if ( pics[i] ) {
|
|
R_StaticFree( pics[i] );
|
|
}
|
|
}
|
|
} else {
|
|
// see if we have a pre-generated image file that is
|
|
// already image processed and compressed
|
|
if ( checkForPrecompressed && globalImages->image_usePrecompressedTextures.GetBool() ) {
|
|
if ( CheckPrecompressedImage( true ) ) {
|
|
// we got the precompressed image
|
|
return;
|
|
}
|
|
// fall through to load the normal image
|
|
}
|
|
|
|
R_LoadImageProgram( imgName, &pic, &width, &height, ×tamp, &depth );
|
|
|
|
if ( pic == NULL ) {
|
|
common->Warning( "Couldn't load image: %s", imgName.c_str() );
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
/*
|
|
// swap the red and alpha for rxgb support
|
|
// do this even on tga normal maps so we only have to use
|
|
// one fragment program
|
|
// if the image is precompressed ( either in palletized mode or true rxgb mode )
|
|
// then it is loaded above and the swap never happens here
|
|
if ( depth == TD_BUMP && globalImages->image_useNormalCompression.GetInteger() != 1 ) {
|
|
for ( int i = 0; i < width * height * 4; i += 4 ) {
|
|
pic[ i + 3 ] = pic[ i ];
|
|
pic[ i ] = 0;
|
|
}
|
|
}
|
|
*/
|
|
// build a hash for checking duplicate image files
|
|
// NOTE: takes about 10% of image load times (SD)
|
|
// may not be strictly necessary, but some code uses it, so let's leave it in
|
|
imageHash = MD4_BlockChecksum( pic, width * height * 4 );
|
|
|
|
GenerateImage( pic, width, height, filter, allowDownSize, repeat, depth );
|
|
timestamp = timestamp;
|
|
precompressedFile = false;
|
|
|
|
R_StaticFree( pic );
|
|
|
|
// write out the precompressed version of this file if needed
|
|
WritePrecompressedImage();
|
|
}
|
|
}
|
|
|
|
//=========================================================================================================
|
|
|
|
/*
|
|
===============
|
|
PurgeImage
|
|
===============
|
|
*/
|
|
void idImage::PurgeImage() {
|
|
if ( texnum != TEXTURE_NOT_LOADED ) {
|
|
#ifdef _WIN32
|
|
// sometimes is NULL when exiting with an error
|
|
if ( qglDeleteTextures )
|
|
#endif
|
|
qglDeleteTextures( 1, &texnum ); // this should be the ONLY place it is ever called!
|
|
texnum = TEXTURE_NOT_LOADED;
|
|
}
|
|
|
|
// clear all the current binding caches, so the next bind will do a real one
|
|
for ( int i = 0 ; i < MAX_MULTITEXTURE_UNITS ; i++ ) {
|
|
backEnd.glState.tmu[i].current2DMap = -1;
|
|
backEnd.glState.tmu[i].current3DMap = -1;
|
|
backEnd.glState.tmu[i].currentCubeMap = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Bind
|
|
|
|
Automatically enables 2D mapping, cube mapping, or 3D texturing if needed
|
|
==============
|
|
*/
|
|
void idImage::Bind() {
|
|
if ( tr.logFile ) {
|
|
RB_LogComment( "idImage::Bind( %s )\n", imgName.c_str() );
|
|
}
|
|
|
|
// if this is an image that we are caching, move it to the front of the LRU chain
|
|
if ( partialImage ) {
|
|
if ( cacheUsageNext ) {
|
|
// unlink from old position
|
|
cacheUsageNext->cacheUsagePrev = cacheUsagePrev;
|
|
cacheUsagePrev->cacheUsageNext = cacheUsageNext;
|
|
}
|
|
// link in at the head of the list
|
|
cacheUsageNext = globalImages->cacheLRU.cacheUsageNext;
|
|
cacheUsagePrev = &globalImages->cacheLRU;
|
|
|
|
cacheUsageNext->cacheUsagePrev = this;
|
|
cacheUsagePrev->cacheUsageNext = this;
|
|
}
|
|
|
|
// load the image if necessary (FIXME: not SMP safe!)
|
|
if ( texnum == TEXTURE_NOT_LOADED ) {
|
|
if ( partialImage ) {
|
|
// if we have a partial image, go ahead and use that
|
|
this->partialImage->Bind();
|
|
|
|
// start a background load of the full thing if it isn't already in the queue
|
|
if ( !backgroundLoadInProgress ) {
|
|
StartBackgroundImageLoad();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// load the image on demand here, which isn't our normal game operating mode
|
|
ActuallyLoadImage( true, true ); // check for precompressed, load is from back end
|
|
}
|
|
|
|
|
|
// bump our statistic counters
|
|
frameUsed = backEnd.frameCount;
|
|
bindCount++;
|
|
|
|
tmu_t *tmu = &backEnd.glState.tmu[backEnd.glState.currenttmu];
|
|
|
|
// enable or disable apropriate texture modes
|
|
if ( tmu->textureType != type && ( backEnd.glState.currenttmu < glConfig.maxTextureUnits ) ) {
|
|
if ( tmu->textureType == TT_CUBIC ) {
|
|
qglDisable( GL_TEXTURE_CUBE_MAP_EXT );
|
|
} else if ( tmu->textureType == TT_3D ) {
|
|
qglDisable( GL_TEXTURE_3D );
|
|
} else if ( tmu->textureType == TT_2D ) {
|
|
qglDisable( GL_TEXTURE_2D );
|
|
}
|
|
|
|
if ( type == TT_CUBIC ) {
|
|
qglEnable( GL_TEXTURE_CUBE_MAP_EXT );
|
|
} else if ( type == TT_3D ) {
|
|
qglEnable( GL_TEXTURE_3D );
|
|
} else if ( type == TT_2D ) {
|
|
qglEnable( GL_TEXTURE_2D );
|
|
}
|
|
tmu->textureType = type;
|
|
}
|
|
|
|
// bind the texture
|
|
if ( type == TT_2D ) {
|
|
if ( tmu->current2DMap != texnum ) {
|
|
tmu->current2DMap = texnum;
|
|
qglBindTexture( GL_TEXTURE_2D, texnum );
|
|
}
|
|
} else if ( type == TT_CUBIC ) {
|
|
if ( tmu->currentCubeMap != texnum ) {
|
|
tmu->currentCubeMap = texnum;
|
|
qglBindTexture( GL_TEXTURE_CUBE_MAP_EXT, texnum );
|
|
}
|
|
} else if ( type == TT_3D ) {
|
|
if ( tmu->current3DMap != texnum ) {
|
|
tmu->current3DMap = texnum;
|
|
qglBindTexture( GL_TEXTURE_3D, texnum );
|
|
}
|
|
}
|
|
|
|
if ( com_purgeAll.GetBool() ) {
|
|
GLclampf priority = 1.0f;
|
|
qglPrioritizeTextures( 1, &texnum, &priority );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
BindFragment
|
|
|
|
Fragment programs explicitly say which type of map they want, so we don't need to
|
|
do any enable / disable changes
|
|
==============
|
|
*/
|
|
void idImage::BindFragment() {
|
|
if ( tr.logFile ) {
|
|
RB_LogComment( "idImage::BindFragment %s )\n", imgName.c_str() );
|
|
}
|
|
|
|
// if this is an image that we are caching, move it to the front of the LRU chain
|
|
if ( partialImage ) {
|
|
if ( cacheUsageNext ) {
|
|
// unlink from old position
|
|
cacheUsageNext->cacheUsagePrev = cacheUsagePrev;
|
|
cacheUsagePrev->cacheUsageNext = cacheUsageNext;
|
|
}
|
|
// link in at the head of the list
|
|
cacheUsageNext = globalImages->cacheLRU.cacheUsageNext;
|
|
cacheUsagePrev = &globalImages->cacheLRU;
|
|
|
|
cacheUsageNext->cacheUsagePrev = this;
|
|
cacheUsagePrev->cacheUsageNext = this;
|
|
}
|
|
|
|
// load the image if necessary (FIXME: not SMP safe!)
|
|
if ( texnum == TEXTURE_NOT_LOADED ) {
|
|
if ( partialImage ) {
|
|
// if we have a partial image, go ahead and use that
|
|
this->partialImage->BindFragment();
|
|
|
|
// start a background load of the full thing if it isn't already in the queue
|
|
if ( !backgroundLoadInProgress ) {
|
|
StartBackgroundImageLoad();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// load the image on demand here, which isn't our normal game operating mode
|
|
ActuallyLoadImage( true, true ); // check for precompressed, load is from back end
|
|
}
|
|
|
|
|
|
// bump our statistic counters
|
|
frameUsed = backEnd.frameCount;
|
|
bindCount++;
|
|
|
|
// bind the texture
|
|
if ( type == TT_2D ) {
|
|
qglBindTexture( GL_TEXTURE_2D, texnum );
|
|
} else if ( type == TT_RECT ) {
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_NV, texnum );
|
|
} else if ( type == TT_CUBIC ) {
|
|
qglBindTexture( GL_TEXTURE_CUBE_MAP_EXT, texnum );
|
|
} else if ( type == TT_3D ) {
|
|
qglBindTexture( GL_TEXTURE_3D, texnum );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
CopyFramebuffer
|
|
====================
|
|
*/
|
|
void idImage::CopyFramebuffer( int x, int y, int imageWidth, int imageHeight, bool useOversizedBuffer ) {
|
|
Bind();
|
|
|
|
if ( cvarSystem->GetCVarBool( "g_lowresFullscreenFX" ) ) {
|
|
imageWidth = 512;
|
|
imageHeight = 512;
|
|
}
|
|
|
|
// if the size isn't a power of 2, the image must be increased in size
|
|
int potWidth, potHeight;
|
|
|
|
potWidth = MakePowerOfTwo( imageWidth );
|
|
potHeight = MakePowerOfTwo( imageHeight );
|
|
|
|
GetDownsize( imageWidth, imageHeight );
|
|
GetDownsize( potWidth, potHeight );
|
|
|
|
qglReadBuffer( GL_BACK );
|
|
|
|
// only resize if the current dimensions can't hold it at all,
|
|
// otherwise subview renderings could thrash this
|
|
if ( ( useOversizedBuffer && ( uploadWidth < potWidth || uploadHeight < potHeight ) )
|
|
|| ( !useOversizedBuffer && ( uploadWidth != potWidth || uploadHeight != potHeight ) ) ) {
|
|
uploadWidth = potWidth;
|
|
uploadHeight = potHeight;
|
|
if ( potWidth == imageWidth && potHeight == imageHeight ) {
|
|
qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, x, y, imageWidth, imageHeight, 0 );
|
|
} else {
|
|
byte *junk;
|
|
// we need to create a dummy image with power of two dimensions,
|
|
// then do a qglCopyTexSubImage2D of the data we want
|
|
// this might be a 16+ meg allocation, which could fail on _alloca
|
|
junk = (byte *)Mem_Alloc( potWidth * potHeight * 4 );
|
|
memset( junk, 0, potWidth * potHeight * 4 ); //!@#
|
|
#if 0 // Disabling because it's unnecessary and introduces a green strip on edge of _currentRender
|
|
for ( int i = 0 ; i < potWidth * potHeight * 4 ; i+=4 ) {
|
|
junk[i+1] = 255;
|
|
}
|
|
#endif
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, potWidth, potHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, junk );
|
|
Mem_Free( junk );
|
|
|
|
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
|
|
}
|
|
} else {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression or some other silliness
|
|
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
|
|
}
|
|
|
|
// if the image isn't a full power of two, duplicate an extra row and/or column to fix bilerps
|
|
if ( imageWidth != potWidth ) {
|
|
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, imageWidth, 0, x+imageWidth-1, y, 1, imageHeight );
|
|
}
|
|
if ( imageHeight != potHeight ) {
|
|
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, imageHeight, x, y+imageHeight-1, imageWidth, 1 );
|
|
}
|
|
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
|
|
|
backEnd.c_copyFrameBuffer++;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CopyDepthbuffer
|
|
|
|
This should just be part of copyFramebuffer once we have a proper image type field
|
|
====================
|
|
*/
|
|
void idImage::CopyDepthbuffer( int x, int y, int imageWidth, int imageHeight ) {
|
|
Bind();
|
|
|
|
// if the size isn't a power of 2, the image must be increased in size
|
|
int potWidth, potHeight;
|
|
|
|
potWidth = MakePowerOfTwo( imageWidth );
|
|
potHeight = MakePowerOfTwo( imageHeight );
|
|
|
|
if ( uploadWidth != potWidth || uploadHeight != potHeight ) {
|
|
uploadWidth = potWidth;
|
|
uploadHeight = potHeight;
|
|
if ( potWidth == imageWidth && potHeight == imageHeight ) {
|
|
qglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, x, y, imageWidth, imageHeight, 0 );
|
|
} else {
|
|
// we need to create a dummy image with power of two dimensions,
|
|
// then do a qglCopyTexSubImage2D of the data we want
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, potWidth, potHeight, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );
|
|
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
|
|
}
|
|
} else {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression or some other silliness
|
|
qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, x, y, imageWidth, imageHeight );
|
|
}
|
|
|
|
// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
|
// qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
|
|
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
RB_UploadScratchImage
|
|
|
|
if rows = cols * 6, assume it is a cube map animation
|
|
=============
|
|
*/
|
|
void idImage::UploadScratch( const byte *data, int cols, int rows ) {
|
|
int i;
|
|
|
|
// if rows = cols * 6, assume it is a cube map animation
|
|
if ( rows == cols * 6 ) {
|
|
if ( type != TT_CUBIC ) {
|
|
type = TT_CUBIC;
|
|
uploadWidth = -1; // for a non-sub upload
|
|
}
|
|
|
|
Bind();
|
|
|
|
rows /= 6;
|
|
// if the scratchImage isn't in the format we want, specify it as a new texture
|
|
if ( cols != uploadWidth || rows != uploadHeight ) {
|
|
uploadWidth = cols;
|
|
uploadHeight = rows;
|
|
|
|
// upload the base level
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
qglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, 0, GL_RGB8, cols, rows, 0,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, data + cols*rows*4*i );
|
|
}
|
|
} else {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression
|
|
for ( i = 0 ; i < 6 ; i++ ) {
|
|
qglTexSubImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT+i, 0, 0, 0, cols, rows,
|
|
GL_RGBA, GL_UNSIGNED_BYTE, data + cols*rows*4*i );
|
|
}
|
|
}
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
// no other clamp mode makes sense
|
|
qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
qglTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
} else {
|
|
// otherwise, it is a 2D image
|
|
if ( type != TT_2D ) {
|
|
type = TT_2D;
|
|
uploadWidth = -1; // for a non-sub upload
|
|
}
|
|
|
|
Bind();
|
|
|
|
// if the scratchImage isn't in the format we want, specify it as a new texture
|
|
if ( cols != uploadWidth || rows != uploadHeight ) {
|
|
uploadWidth = cols;
|
|
uploadHeight = rows;
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
} else {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression
|
|
qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
}
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
// these probably should be clamp, but we have a lot of issues with editor
|
|
// geometry coming out with texcoords slightly off one side, resulting in
|
|
// a smear across the entire polygon
|
|
#if 1
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
|
|
#else
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void idImage::SetClassification( int tag ) {
|
|
classification = tag;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
StorageSize
|
|
==================
|
|
*/
|
|
int idImage::StorageSize() const {
|
|
int baseSize;
|
|
|
|
if ( texnum == TEXTURE_NOT_LOADED ) {
|
|
return 0;
|
|
}
|
|
|
|
switch ( type ) {
|
|
default:
|
|
case TT_2D:
|
|
baseSize = uploadWidth*uploadHeight;
|
|
break;
|
|
case TT_3D:
|
|
baseSize = uploadWidth*uploadHeight*uploadDepth;
|
|
break;
|
|
case TT_CUBIC:
|
|
baseSize = 6 * uploadWidth*uploadHeight;
|
|
break;
|
|
}
|
|
|
|
baseSize *= BitsForInternalFormat( internalFormat );
|
|
|
|
baseSize /= 8;
|
|
|
|
// account for mip mapping
|
|
baseSize = baseSize * 4 / 3;
|
|
|
|
return baseSize;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
Print
|
|
==================
|
|
*/
|
|
void idImage::Print() const {
|
|
if ( precompressedFile ) {
|
|
common->Printf( "P" );
|
|
} else if ( generatorFunction ) {
|
|
common->Printf( "F" );
|
|
} else {
|
|
common->Printf( " " );
|
|
}
|
|
|
|
switch ( type ) {
|
|
case TT_2D:
|
|
common->Printf( " " );
|
|
break;
|
|
case TT_3D:
|
|
common->Printf( "3" );
|
|
break;
|
|
case TT_CUBIC:
|
|
common->Printf( "C" );
|
|
break;
|
|
case TT_RECT:
|
|
common->Printf( "R" );
|
|
break;
|
|
default:
|
|
common->Printf( "<BAD TYPE:%i>", type );
|
|
break;
|
|
}
|
|
|
|
common->Printf( "%4i %4i ", uploadWidth, uploadHeight );
|
|
|
|
switch( filter ) {
|
|
case TF_DEFAULT:
|
|
common->Printf( "dflt " );
|
|
break;
|
|
case TF_LINEAR:
|
|
common->Printf( "linr " );
|
|
break;
|
|
case TF_NEAREST:
|
|
common->Printf( "nrst " );
|
|
break;
|
|
default:
|
|
common->Printf( "<BAD FILTER:%i>", filter );
|
|
break;
|
|
}
|
|
|
|
switch ( internalFormat ) {
|
|
case GL_INTENSITY8:
|
|
case 1:
|
|
common->Printf( "I " );
|
|
break;
|
|
case 2:
|
|
case GL_LUMINANCE8_ALPHA8:
|
|
common->Printf( "LA " );
|
|
break;
|
|
case 3:
|
|
common->Printf( "RGB " );
|
|
break;
|
|
case 4:
|
|
common->Printf( "RGBA " );
|
|
break;
|
|
case GL_LUMINANCE8:
|
|
common->Printf( "L " );
|
|
break;
|
|
case GL_ALPHA8:
|
|
common->Printf( "A " );
|
|
break;
|
|
case GL_RGBA8:
|
|
common->Printf( "RGBA8 " );
|
|
break;
|
|
case GL_RGB8:
|
|
common->Printf( "RGB8 " );
|
|
break;
|
|
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
|
|
common->Printf( "DXT1 " );
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
|
|
common->Printf( "DXT1A " );
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
|
|
common->Printf( "DXT3 " );
|
|
break;
|
|
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
|
|
common->Printf( "DXT5 " );
|
|
break;
|
|
case GL_RGBA4:
|
|
common->Printf( "RGBA4 " );
|
|
break;
|
|
case GL_RGB5:
|
|
common->Printf( "RGB5 " );
|
|
break;
|
|
case GL_COLOR_INDEX8_EXT:
|
|
common->Printf( "CI8 " );
|
|
break;
|
|
case GL_COLOR_INDEX:
|
|
common->Printf( "CI " );
|
|
break;
|
|
case GL_COMPRESSED_RGB_ARB:
|
|
common->Printf( "RGBC " );
|
|
break;
|
|
case GL_COMPRESSED_RGBA_ARB:
|
|
common->Printf( "RGBAC " );
|
|
break;
|
|
case 0:
|
|
common->Printf( " " );
|
|
break;
|
|
default:
|
|
common->Printf( "<BAD FORMAT:%i>", internalFormat );
|
|
break;
|
|
}
|
|
|
|
switch ( repeat ) {
|
|
case TR_REPEAT:
|
|
common->Printf( "rept " );
|
|
break;
|
|
case TR_CLAMP_TO_ZERO:
|
|
common->Printf( "zero " );
|
|
break;
|
|
case TR_CLAMP_TO_ZERO_ALPHA:
|
|
common->Printf( "azro " );
|
|
break;
|
|
case TR_CLAMP:
|
|
common->Printf( "clmp " );
|
|
break;
|
|
default:
|
|
common->Printf( "<BAD REPEAT:%i>", repeat );
|
|
break;
|
|
}
|
|
|
|
common->Printf( "%4ik ", StorageSize() / 1024 );
|
|
|
|
common->Printf( " %s\n", imgName.c_str() );
|
|
}
|