mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-27 14:42:23 +00:00
a5678d2368
This was only used for the nv20 renderer.
2180 lines
60 KiB
C++
2180 lines
60 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 ) 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;
|
|
|
|
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 );
|
|
|
|
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 );
|
|
|
|
// 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 );
|
|
|
|
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 );
|
|
|
|
// 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;
|
|
|
|
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 && 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 int magic = LittleInt( *(unsigned int *)data );
|
|
ddsFileHeader_t *_header = (ddsFileHeader_t *)(data + 4);
|
|
int ddspf_dwFlags = LittleInt( _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 = LittleInt( header->dwSize );
|
|
header->dwFlags = LittleInt( header->dwFlags );
|
|
header->dwHeight = LittleInt( header->dwHeight );
|
|
header->dwWidth = LittleInt( header->dwWidth );
|
|
header->dwPitchOrLinearSize = LittleInt( header->dwPitchOrLinearSize );
|
|
header->dwDepth = LittleInt( header->dwDepth );
|
|
header->dwMipMapCount = LittleInt( header->dwMipMapCount );
|
|
header->dwCaps1 = LittleInt( header->dwCaps1 );
|
|
header->dwCaps2 = LittleInt( header->dwCaps2 );
|
|
|
|
header->ddspf.dwSize = LittleInt( header->ddspf.dwSize );
|
|
header->ddspf.dwFlags = LittleInt( header->ddspf.dwFlags );
|
|
header->ddspf.dwFourCC = LittleInt( header->ddspf.dwFourCC );
|
|
header->ddspf.dwRGBBitCount = LittleInt( header->ddspf.dwRGBBitCount );
|
|
header->ddspf.dwRBitMask = LittleInt( header->ddspf.dwRBitMask );
|
|
header->ddspf.dwGBitMask = LittleInt( header->ddspf.dwGBitMask );
|
|
header->ddspf.dwBBitMask = LittleInt( header->ddspf.dwBBitMask );
|
|
header->ddspf.dwABitMask = LittleInt( 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;
|
|
}
|
|
|
|
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 ) {
|
|
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 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 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() );
|
|
}
|