mirror of
https://github.com/DrBeef/Doom3Quest.git
synced 2024-12-11 04:30:50 +00:00
b2b8f43c9d
Builds, runs, no stereo or much else is working, menus work ok though
1326 lines
36 KiB
C++
1326 lines
36 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/Cinematic.h"
|
|
|
|
#include "renderer/Image.h"
|
|
|
|
|
|
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 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
return 32;
|
|
case GL_RGBA4:
|
|
return 16;
|
|
case GL_RGB5_A1:
|
|
return 16;
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=======================================================================
|
|
|
|
|
|
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},
|
|
};
|
|
|
|
/*
|
|
==================
|
|
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 );
|
|
}
|
|
}
|
|
|
|
// 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_EDGE );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
|
|
// Disabled for OES2
|
|
//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;
|
|
}
|
|
}
|
|
|
|
|
|
//Code from raspberrypi q3
|
|
|
|
static int isopaque(GLint width, GLint height, const GLvoid *pixels) {
|
|
unsigned char const *cpixels = (unsigned char const *)pixels;
|
|
int i;
|
|
for (i = 0; i < width * height; i++) {
|
|
if (cpixels[i*4+3] != 0xff)
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
void rgba4444_convert_tex_image(
|
|
char* cachefname,
|
|
GLenum target,
|
|
GLint level,
|
|
GLenum internalformat,
|
|
GLsizei width,
|
|
GLsizei height,
|
|
GLint border,
|
|
GLenum format,
|
|
GLenum type,
|
|
const GLvoid *pixels) {
|
|
unsigned char const *cpixels = (unsigned char const *)pixels;
|
|
unsigned short *rgba4444data = (unsigned short *)malloc(2*width*height+1);
|
|
((unsigned char*)rgba4444data)[0]=1;
|
|
rgba4444data=(unsigned short *)((unsigned char*)rgba4444data+1);
|
|
int i;
|
|
for (i = 0; i < width * height; i++) {
|
|
unsigned char r,g,b,a;
|
|
r = cpixels[4*i]>>4;
|
|
g = cpixels[4*i+1]>>4;
|
|
b = cpixels[4*i+2]>>4;
|
|
a = cpixels[4*i+3]>>4;
|
|
rgba4444data[i] = r << 12 | g << 8 | b << 4 | a;
|
|
}
|
|
qglTexImage2D(target, level, format, width, height,border,format,GL_UNSIGNED_SHORT_4_4_4_4,rgba4444data);
|
|
rgba4444data=(unsigned short *)((unsigned char*)rgba4444data-1);
|
|
if (cachefname!=0) {
|
|
fileSystem->WriteFile(cachefname, rgba4444data, width*height*2+1);
|
|
}
|
|
free(rgba4444data);
|
|
}
|
|
//#define USE_RG_ETC1
|
|
#ifdef USE_RG_ETC1
|
|
#include "etc_rg_etc1.h"
|
|
#else
|
|
#include "etc1_android.h"
|
|
#endif
|
|
|
|
unsigned int etc1_data_size(unsigned int width, unsigned int height) {
|
|
return (((width + 3) & ~3) * ((height + 3) & ~3)) >> 1;
|
|
}
|
|
|
|
void etc1_compress_tex_image(
|
|
char* cachefname,
|
|
GLenum target,
|
|
GLint level,
|
|
GLenum internalformat,
|
|
GLsizei width,
|
|
GLsizei height,
|
|
GLint border,
|
|
GLenum format,
|
|
GLenum type,
|
|
const GLvoid *pixels) {
|
|
unsigned char const *cpixels = (unsigned char const *)pixels;
|
|
unsigned char *etc1data;
|
|
unsigned int size=etc1_data_size(width,height);
|
|
etc1data = (unsigned char *)malloc(size+1);
|
|
etc1data[0]=0;
|
|
etc1data++;
|
|
#ifdef USE_RG_ETC1
|
|
rg_etc1::etc1_encode_image(cpixels, width, height,
|
|
4, width*4, etc1data);
|
|
#else
|
|
etc1_encode_image(cpixels, width, height,
|
|
4, width*4, etc1data);
|
|
#endif
|
|
qglCompressedTexImage2D(
|
|
target,
|
|
level,
|
|
GL_ETC1_RGB8_OES,
|
|
width,
|
|
height,
|
|
0,
|
|
size,
|
|
etc1data);
|
|
etc1data--;
|
|
if (cachefname!=0) {
|
|
fileSystem->WriteFile(cachefname, etc1data, size+1);
|
|
}
|
|
free(etc1data);
|
|
}
|
|
|
|
int etcavail(char* cachefname) {
|
|
return (r_useETC1Cache.GetBool())&&(r_useETC1.GetBool())&&(cachefname!=0)&&(fileSystem->ReadFile(cachefname,0,0)!=-1);
|
|
}
|
|
|
|
int uploadetc(char* cachefname,GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type) {
|
|
char* tmp;
|
|
int failed=0;
|
|
|
|
if( !etcavail( cachefname ) )
|
|
return 1;
|
|
|
|
int sz = fileSystem->ReadFile(cachefname,(void**)&tmp,0);
|
|
|
|
if( sz == -1 )
|
|
return 1;
|
|
|
|
if (tmp[0]==0) {
|
|
if (sz==etc1_data_size(width,height)+1) {
|
|
qglCompressedTexImage2D(target,level,GL_ETC1_RGB8_OES,width,height,0,etc1_data_size(width,height),tmp + 1);
|
|
} else {
|
|
failed=1;
|
|
}
|
|
} else {
|
|
if (sz==width*height*2+1) {
|
|
qglTexImage2D(target,level,format,width,height,border,format,GL_UNSIGNED_SHORT_4_4_4_4,tmp + 1);
|
|
} else {
|
|
failed=1;
|
|
}
|
|
}
|
|
|
|
tmp--;
|
|
fileSystem->FreeFile(tmp);
|
|
return failed;
|
|
}
|
|
|
|
void myglTexImage2D(char* cachefname,GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels) {
|
|
static int opaque = 0;
|
|
|
|
// LOGI("myglTexImage2D, name = %s", cachefname );
|
|
|
|
if (r_useETC1.GetBool() && format == GL_RGBA && type == GL_UNSIGNED_BYTE) {
|
|
|
|
if (level == 0)
|
|
opaque = isopaque(width, height, pixels);
|
|
|
|
if (!r_useETC1Cache.GetBool())
|
|
cachefname=0;
|
|
|
|
if( uploadetc(cachefname, target, level, internalformat, width, height, border, format, type ) != 0 )
|
|
{
|
|
if (opaque)
|
|
etc1_compress_tex_image(cachefname,target, level, format, width, height, border, format, type, pixels);
|
|
else
|
|
rgba4444_convert_tex_image(cachefname,target, level, format, width, height, border, format, type, pixels);
|
|
|
|
}
|
|
else
|
|
{
|
|
LOGI("Loaded cached image from %s", cachefname);
|
|
}
|
|
} else {
|
|
qglTexImage2D(target,level,internalformat,width,height,border,format,type,pixels);
|
|
}
|
|
}
|
|
|
|
//end
|
|
|
|
/*
|
|
================
|
|
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 = GL_RGBA;
|
|
|
|
// 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 ( depth == TD_BUMP ) {
|
|
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();
|
|
// LOGI("LOADING IMAGE %d (%s)",texnum,imgName.c_str());
|
|
|
|
// qglTexImage2D( GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
char filename[MAX_IMAGE_NAME];
|
|
char*fptr=&filename[0];
|
|
ImageProgramStringToCompressedFileName(imgName, filename);
|
|
char *ext = strrchr(filename, '.');
|
|
if (ext) {
|
|
strcpy(ext, ".etc");
|
|
} else
|
|
fptr=0;
|
|
myglTexImage2D(fptr,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
|
|
//qglTexImage2D( GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height,
|
|
// 0, GL_RGBA, GL_UNSIGNED_BYTE, scaledBuffer );
|
|
char filename[MAX_IMAGE_NAME];
|
|
char*fptr=&filename[0];
|
|
ImageProgramStringToCompressedFileName(imgName, filename);
|
|
char *ext = strrchr(filename, '.');
|
|
if (ext) {
|
|
strcpy(ext, ".e");
|
|
ext[2]='0'+miplevel/10;
|
|
ext[3]='0'+miplevel%10;
|
|
} else
|
|
fptr=0;
|
|
myglTexImage2D(fptr,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();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
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;
|
|
}
|
|
|
|
width = height = size;
|
|
|
|
// generate the texture number
|
|
qglGenTextures( 1, &texnum );
|
|
|
|
// select proper internal format before we resample
|
|
internalFormat = GL_RGBA;
|
|
|
|
// 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, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
qglTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
|
|
|
// set the minimize / maximize filtering
|
|
switch( filter ) {
|
|
case TF_DEFAULT:
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, globalImages->textureMinFilter );
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, globalImages->textureMaxFilter );
|
|
break;
|
|
case TF_LINEAR:
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
break;
|
|
case TF_NEAREST:
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
|
qglTexParameterf(GL_TEXTURE_CUBE_MAP, 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+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+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;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
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 fromBind ) {
|
|
int width, height;
|
|
byte *pic;
|
|
|
|
if(fromBind)
|
|
{
|
|
LOGI("ERROR!! CAN NOT LOAD IMAGE FROM BIND");
|
|
globalImages->AddAllocList( this );
|
|
return;
|
|
}
|
|
|
|
|
|
if( cinematic )
|
|
{
|
|
cinData_t cin;
|
|
|
|
cin = cinematic->ImageForTime( cinmaticNextTime );
|
|
|
|
if( texnum == TEXTURE_NOT_LOADED )
|
|
qglGenTextures( 1, &texnum );
|
|
|
|
if ( cin.image ) {
|
|
UploadScratch( cin.image, cin.imageWidth, cin.imageHeight );
|
|
} else {
|
|
//globalImages->blackImage->Bind();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
// this is the ONLY place generatorFunction will ever be called
|
|
if ( generatorFunction ) {
|
|
generatorFunction( this );
|
|
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 );
|
|
|
|
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
|
|
|
|
R_LoadImageProgram( imgName, &pic, &width, &height, ×tamp, &depth );
|
|
|
|
if ( pic == NULL ) {
|
|
common->Warning( "Couldn't load image: %s", imgName.c_str() );
|
|
MakeDefault();
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
|
|
R_StaticFree( pic );
|
|
}
|
|
}
|
|
|
|
//=========================================================================================================
|
|
|
|
/*
|
|
===============
|
|
PurgeImage
|
|
===============
|
|
*/
|
|
void idImage::PurgeImage() {
|
|
if ( texnum != TEXTURE_NOT_LOADED ) {
|
|
// LOGI("DELETING IMAGE %d",texnum);
|
|
qglDeleteTextures( 1, &texnum ); // this should be the ONLY place it is ever called!
|
|
texnum = TEXTURE_NOT_LOADED;
|
|
}
|
|
}
|
|
|
|
/*
|
|
==============
|
|
Bind
|
|
|
|
Automatically enables 2D mapping, cube mapping, or 3D texturing if needed
|
|
==============
|
|
*/
|
|
bool idImage::Bind() {
|
|
// load the image if necessary (FIXME: not SMP safe!)
|
|
if ( texnum == TEXTURE_NOT_LOADED ) {
|
|
|
|
// load the image on demand here, which isn't our normal game operating mode
|
|
ActuallyLoadImage( true );
|
|
// Load a black image to reduce flicker
|
|
globalImages->blackImage->Bind();
|
|
return false;
|
|
}
|
|
|
|
|
|
// bump our statistic counters
|
|
frameUsed = backEnd.frameCount;
|
|
bindCount++;
|
|
|
|
// bind the texture
|
|
if ( type == TT_2D ) {
|
|
qglBindTexture( GL_TEXTURE_2D, texnum );
|
|
} else if ( type == TT_CUBIC ) {
|
|
qglBindTexture( GL_TEXTURE_CUBE_MAP, texnum );
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
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() {
|
|
|
|
// load the image if necessary (FIXME: not SMP safe!)
|
|
if ( texnum == TEXTURE_NOT_LOADED ) {
|
|
// load the image on demand here, which isn't our normal game operating mode
|
|
ActuallyLoadImage( true );
|
|
}
|
|
|
|
|
|
// bump our statistic counters
|
|
frameUsed = backEnd.frameCount;
|
|
bindCount++;
|
|
|
|
// bind the texture
|
|
if ( type == TT_2D ) {
|
|
qglBindTexture( GL_TEXTURE_2D, texnum );
|
|
} else if ( type == TT_CUBIC ) {
|
|
qglBindTexture( GL_TEXTURE_CUBE_MAP, 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 );
|
|
|
|
// Don't do this, otherwise the Grabber gun graphics from ROE do not work properly
|
|
//GetDownsize( imageWidth, imageHeight );
|
|
//GetDownsize( potWidth, potHeight );
|
|
|
|
//Disabled for OES2
|
|
//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_RGB, 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 * 3 );
|
|
memset( junk, 0, potWidth * potHeight * 3 ); //!@#
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, potWidth, potHeight, 0, GL_RGB, 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+i, 0, GL_RGBA, 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+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, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
|
qglTexParameteri(GL_TEXTURE_CUBE_MAP, 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_RGBA, 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;
|
|
}
|
|
|
|
|
|
bool idImage::isLoaded()
|
|
{
|
|
return (!purgePending && (texnum != TEXTURE_NOT_LOADED));
|
|
}
|
|
|
|
/*
|
|
==================
|
|
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_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 ( generatorFunction ) {
|
|
common->Printf( "F" );
|
|
} else {
|
|
common->Printf( " " );
|
|
}
|
|
|
|
switch ( type ) {
|
|
case TT_2D:
|
|
common->Printf( " " );
|
|
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 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
common->Printf( "RGBA " );
|
|
break;
|
|
case GL_RGBA4:
|
|
common->Printf( "RGBA4 " );
|
|
break;
|
|
case GL_RGB5_A1:
|
|
common->Printf( "RGB5_A1 " );
|
|
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() );
|
|
}
|