2013-04-04 14:52:42 +00:00
// tr_image.c
# include "tr_local.h"
# include "glext.h"
# pragma warning (push, 3) //go back down to 3 for the stl include
# include <map>
# pragma warning (pop)
using namespace std ;
/*
* Include file for users of JPEG library .
* You will need to have included system headers that define at least
* the typedefs FILE and size_t before you can include jpeglib . h .
* ( stdio . h is sufficient on ANSI - conforming systems . )
* You may also wish to include " jerror.h " .
*/
# define JPEG_INTERNALS
# include "../jpeg-6/jpeglib.h"
# include "../png/png.h"
2013-04-04 18:24:26 +00:00
# ifndef DEDICATED
2013-04-04 14:52:42 +00:00
static void LoadTGA ( const char * name , byte * * pic , int * width , int * height ) ;
static void LoadJPG ( const char * name , byte * * pic , int * width , int * height ) ;
static byte s_intensitytable [ 256 ] ;
static unsigned char s_gammatable [ 256 ] ;
int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST ;
int gl_filter_max = GL_LINEAR ;
//#define FILE_HASH_SIZE 1024 // actually the shader code still needs this (from another module, great),
//static image_t* hashTable[FILE_HASH_SIZE];
/*
* * R_GammaCorrect
*/
void R_GammaCorrect ( byte * buffer , int bufSize ) {
int i ;
for ( i = 0 ; i < bufSize ; i + + ) {
buffer [ i ] = s_gammatable [ buffer [ i ] ] ;
}
}
typedef struct {
char * name ;
int minimize , maximize ;
} textureMode_t ;
textureMode_t modes [ ] = {
{ " GL_NEAREST " , GL_NEAREST , GL_NEAREST } ,
{ " GL_LINEAR " , GL_LINEAR , GL_LINEAR } ,
{ " GL_NEAREST_MIPMAP_NEAREST " , GL_NEAREST_MIPMAP_NEAREST , GL_NEAREST } ,
{ " GL_LINEAR_MIPMAP_NEAREST " , GL_LINEAR_MIPMAP_NEAREST , GL_LINEAR } ,
{ " GL_NEAREST_MIPMAP_LINEAR " , GL_NEAREST_MIPMAP_LINEAR , GL_NEAREST } ,
{ " GL_LINEAR_MIPMAP_LINEAR " , GL_LINEAR_MIPMAP_LINEAR , GL_LINEAR }
} ;
// makeup a nice clean, consistant name to query for and file under, for map<> usage...
//
static char * GenerateImageMappingName ( const char * name )
{
static char sName [ MAX_QPATH ] ;
int i = 0 ;
char letter ;
while ( name [ i ] ! = ' \0 ' & & i < MAX_QPATH - 1 )
{
letter = tolower ( name [ i ] ) ;
if ( letter = = ' . ' ) break ; // don't include extension
if ( letter = = ' \\ ' ) letter = ' / ' ; // damn path names
sName [ i + + ] = letter ;
}
sName [ i ] = 0 ;
return & sName [ 0 ] ;
}
/*
= = = = = = = = = = = = = = =
GL_TextureMode
= = = = = = = = = = = = = = =
*/
void GL_TextureMode ( const char * string ) {
int i ;
image_t * glt ;
for ( i = 0 ; i < 6 ; i + + ) {
if ( ! Q_stricmp ( modes [ i ] . name , string ) ) {
break ;
}
}
if ( i = = 6 ) {
ri . Printf ( PRINT_ALL , " bad filter name \n " ) ;
for ( i = 0 ; i < 6 ; i + + ) {
ri . Printf ( PRINT_ALL , " %s \n " , modes [ i ] . name ) ;
}
return ;
}
gl_filter_min = modes [ i ] . minimize ;
gl_filter_max = modes [ i ] . maximize ;
// change all the existing mipmap texture objects
R_Images_StartIteration ( ) ;
while ( ( glt = R_Images_GetNextIteration ( ) ) ! = NULL )
{
if ( glt - > mipmap ) {
GL_Bind ( glt ) ;
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , gl_filter_min ) ;
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , gl_filter_max ) ;
if ( glConfig . textureFilterAnisotropicAvailable ) {
if ( r_ext_texture_filter_anisotropic - > integer ) {
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , 2.0f ) ;
} else {
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , 1.0f ) ;
}
}
}
}
}
static float R_BytesPerTex ( int format )
{
switch ( format ) {
case 1 :
//"I "
return 1 ;
break ;
case 2 :
//"IA "
return 2 ;
break ;
case 3 :
//"RGB "
return glConfig . colorBits / 8.0f ;
break ;
case 4 :
//"RGBA "
return glConfig . colorBits / 8.0f ;
break ;
case GL_RGBA4 :
//"RGBA4"
return 2 ;
break ;
case GL_RGB5 :
//"RGB5 "
return 2 ;
break ;
case GL_RGBA8 :
//"RGBA8"
return 4 ;
break ;
case GL_RGB8 :
//"RGB8"
return 4 ;
break ;
case GL_RGB4_S3TC :
//"S3TC "
return 0.33333f ;
break ;
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT :
//"DXT1 "
return 0.33333f ;
break ;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT :
//"DXT5 "
return 1 ;
break ;
default :
//"???? "
return 4 ;
}
}
/*
= = = = = = = = = = = = = = =
R_SumOfUsedImages
= = = = = = = = = = = = = = =
*/
float R_SumOfUsedImages ( qboolean bUseFormat )
{
int total = 0 ;
image_t * pImage ;
R_Images_StartIteration ( ) ;
while ( ( pImage = R_Images_GetNextIteration ( ) ) ! = NULL )
{
if ( pImage - > frameUsed = = tr . frameCount - 1 ) { //it has already been advanced for the next frame, so...
if ( bUseFormat )
{
float bytePerTex = R_BytesPerTex ( pImage - > internalFormat ) ;
total + = bytePerTex * ( pImage - > uploadWidth * pImage - > uploadHeight ) ;
}
else
{
total + = pImage - > uploadWidth * pImage - > uploadHeight ;
}
}
}
return total ;
}
/*
= = = = = = = = = = = = = = =
R_ImageList_f
= = = = = = = = = = = = = = =
*/
void R_ImageList_f ( void ) {
int i = 0 ;
image_t * image ;
int texels = 0 ;
float texBytes = 0.0f ;
const char * yesno [ ] = { " no " , " yes " } ;
ri . Printf ( PRINT_ALL , " \n -w-- -h-- -mm- -TMU- -if-- wrap --name------- \n " ) ;
int iNumImages = R_Images_StartIteration ( ) ;
while ( ( image = R_Images_GetNextIteration ( ) ) ! = NULL )
{
texels + = image - > uploadWidth * image - > uploadHeight ;
texBytes + = image - > uploadWidth * image - > uploadHeight * R_BytesPerTex ( image - > internalFormat ) ;
ri . Printf ( PRINT_ALL , " %4i: %4i %4i %s %d " ,
i , image - > uploadWidth , image - > uploadHeight , yesno [ image - > mipmap ] , image - > TMU ) ;
switch ( image - > internalFormat ) {
case 1 :
ri . Printf ( PRINT_ALL , " I " ) ;
break ;
case 2 :
ri . Printf ( PRINT_ALL , " IA " ) ;
break ;
case 3 :
ri . Printf ( PRINT_ALL , " RGB " ) ;
break ;
case 4 :
ri . Printf ( PRINT_ALL , " RGBA " ) ;
break ;
case GL_RGBA8 :
ri . Printf ( PRINT_ALL , " RGBA8 " ) ;
break ;
case GL_RGB8 :
ri . Printf ( PRINT_ALL , " RGB8 " ) ;
break ;
case GL_RGB4_S3TC :
ri . Printf ( PRINT_ALL , " S3TC " ) ;
break ;
case GL_COMPRESSED_RGB_S3TC_DXT1_EXT :
ri . Printf ( PRINT_ALL , " DXT1 " ) ;
break ;
case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT :
ri . Printf ( PRINT_ALL , " DXT5 " ) ;
break ;
case GL_RGBA4 :
ri . Printf ( PRINT_ALL , " RGBA4 " ) ;
break ;
case GL_RGB5 :
ri . Printf ( PRINT_ALL , " RGB5 " ) ;
break ;
default :
ri . Printf ( PRINT_ALL , " ???? " ) ;
}
switch ( image - > wrapClampMode ) {
case GL_REPEAT :
ri . Printf ( PRINT_ALL , " rept " ) ;
break ;
case GL_CLAMP :
ri . Printf ( PRINT_ALL , " clmp " ) ;
break ;
case GL_CLAMP_TO_EDGE :
ri . Printf ( PRINT_ALL , " clpE " ) ;
break ;
default :
ri . Printf ( PRINT_ALL , " %4i " , image - > wrapClampMode ) ;
break ;
}
ri . Printf ( PRINT_ALL , " %s \n " , image - > imgName ) ;
i + + ;
}
ri . Printf ( PRINT_ALL , " --------- \n " ) ;
ri . Printf ( PRINT_ALL , " -w-- -h-- -mm- -TMU- -if- wrap --name------- \n " ) ;
ri . Printf ( PRINT_ALL , " %i total texels (not including mipmaps) \n " , texels ) ;
ri . Printf ( PRINT_ALL , " %.2fMB total texture mem (not including mipmaps) \n " , texBytes / 1048576.0f ) ;
ri . Printf ( PRINT_ALL , " %i total images \n \n " , iNumImages ) ;
}
//=======================================================================
/*
= = = = = = = = = = = = = = = =
R_LightScaleTexture
Scale up the pixel values in a texture to increase the
lighting range
= = = = = = = = = = = = = = = =
*/
void R_LightScaleTexture ( unsigned * in , int inwidth , int inheight , qboolean only_gamma )
{
if ( only_gamma )
{
if ( ! glConfig . deviceSupportsGamma )
{
int i , c ;
byte * p ;
p = ( byte * ) in ;
c = inwidth * inheight ;
for ( i = 0 ; i < c ; i + + , p + = 4 )
{
p [ 0 ] = s_gammatable [ p [ 0 ] ] ;
p [ 1 ] = s_gammatable [ p [ 1 ] ] ;
p [ 2 ] = s_gammatable [ p [ 2 ] ] ;
}
}
}
else
{
int i , c ;
byte * p ;
p = ( byte * ) in ;
c = inwidth * inheight ;
if ( glConfig . deviceSupportsGamma )
{
for ( i = 0 ; i < c ; i + + , p + = 4 )
{
p [ 0 ] = s_intensitytable [ p [ 0 ] ] ;
p [ 1 ] = s_intensitytable [ p [ 1 ] ] ;
p [ 2 ] = s_intensitytable [ p [ 2 ] ] ;
}
}
else
{
for ( i = 0 ; i < c ; i + + , p + = 4 )
{
p [ 0 ] = s_gammatable [ s_intensitytable [ p [ 0 ] ] ] ;
p [ 1 ] = s_gammatable [ s_intensitytable [ p [ 1 ] ] ] ;
p [ 2 ] = s_gammatable [ s_intensitytable [ p [ 2 ] ] ] ;
}
}
}
}
/*
= = = = = = = = = = = = = = = =
R_MipMap2
Operates in place , quartering the size of the texture
Proper linear filter
= = = = = = = = = = = = = = = =
*/
static void R_MipMap2 ( unsigned * in , int inWidth , int inHeight ) {
int i , j , k ;
byte * outpix ;
int inWidthMask , inHeightMask ;
int total ;
int outWidth , outHeight ;
unsigned * temp ;
outWidth = inWidth > > 1 ;
outHeight = inHeight > > 1 ;
temp = ( unsigned int * ) ri . Hunk_AllocateTempMemory ( outWidth * outHeight * 4 ) ;
inWidthMask = inWidth - 1 ;
inHeightMask = inHeight - 1 ;
for ( i = 0 ; i < outHeight ; i + + ) {
for ( j = 0 ; j < outWidth ; j + + ) {
outpix = ( byte * ) ( temp + i * outWidth + j ) ;
for ( k = 0 ; k < 4 ; k + + ) {
total =
1 * ( ( byte * ) & in [ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] ) [ k ] +
1 * ( ( byte * ) & in [ ( ( i * 2 - 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] ) [ k ] +
4 * ( ( byte * ) & in [ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] ) [ k ] +
4 * ( ( byte * ) & in [ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] ) [ k ] +
4 * ( ( byte * ) & in [ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] ) [ k ] +
4 * ( ( byte * ) & in [ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 + 1 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] ) [ k ] +
1 * ( ( byte * ) & in [ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 - 1 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 ) & inWidthMask ) ] ) [ k ] +
2 * ( ( byte * ) & in [ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 1 ) & inWidthMask ) ] ) [ k ] +
1 * ( ( byte * ) & in [ ( ( i * 2 + 2 ) & inHeightMask ) * inWidth + ( ( j * 2 + 2 ) & inWidthMask ) ] ) [ k ] ;
outpix [ k ] = total / 36 ;
}
}
}
Com_Memcpy ( in , temp , outWidth * outHeight * 4 ) ;
ri . Hunk_FreeTempMemory ( temp ) ;
}
/*
= = = = = = = = = = = = = = = =
R_MipMap
Operates in place , quartering the size of the texture
= = = = = = = = = = = = = = = =
*/
static void R_MipMap ( byte * in , int width , int height ) {
int i , j ;
byte * out ;
int row ;
if ( ! r_simpleMipMaps - > integer ) {
R_MipMap2 ( ( unsigned * ) in , width , height ) ;
return ;
}
if ( width = = 1 & & height = = 1 ) {
return ;
}
row = width * 4 ;
out = in ;
width > > = 1 ;
height > > = 1 ;
if ( width = = 0 | | height = = 0 ) {
width + = height ; // get largest
for ( i = 0 ; i < width ; i + + , out + = 4 , in + = 8 ) {
out [ 0 ] = ( in [ 0 ] + in [ 4 ] ) > > 1 ;
out [ 1 ] = ( in [ 1 ] + in [ 5 ] ) > > 1 ;
out [ 2 ] = ( in [ 2 ] + in [ 6 ] ) > > 1 ;
out [ 3 ] = ( in [ 3 ] + in [ 7 ] ) > > 1 ;
}
return ;
}
for ( i = 0 ; i < height ; i + + , in + = row ) {
for ( j = 0 ; j < width ; j + + , out + = 4 , in + = 8 ) {
out [ 0 ] = ( in [ 0 ] + in [ 4 ] + in [ row + 0 ] + in [ row + 4 ] ) > > 2 ;
out [ 1 ] = ( in [ 1 ] + in [ 5 ] + in [ row + 1 ] + in [ row + 5 ] ) > > 2 ;
out [ 2 ] = ( in [ 2 ] + in [ 6 ] + in [ row + 2 ] + in [ row + 6 ] ) > > 2 ;
out [ 3 ] = ( in [ 3 ] + in [ 7 ] + in [ row + 3 ] + in [ row + 7 ] ) > > 2 ;
}
}
}
/*
= = = = = = = = = = = = = = = = = =
R_BlendOverTexture
Apply a color blend over a set of pixels
= = = = = = = = = = = = = = = = = =
*/
static void R_BlendOverTexture ( byte * data , int pixelCount , byte blend [ 4 ] ) {
int i ;
int inverseAlpha ;
int premult [ 3 ] ;
inverseAlpha = 255 - blend [ 3 ] ;
premult [ 0 ] = blend [ 0 ] * blend [ 3 ] ;
premult [ 1 ] = blend [ 1 ] * blend [ 3 ] ;
premult [ 2 ] = blend [ 2 ] * blend [ 3 ] ;
for ( i = 0 ; i < pixelCount ; i + + , data + = 4 ) {
data [ 0 ] = ( data [ 0 ] * inverseAlpha + premult [ 0 ] ) > > 9 ;
data [ 1 ] = ( data [ 1 ] * inverseAlpha + premult [ 1 ] ) > > 9 ;
data [ 2 ] = ( data [ 2 ] * inverseAlpha + premult [ 2 ] ) > > 9 ;
}
}
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 } ,
} ;
class CStringComparator
{
public :
bool operator ( ) ( const char * s1 , const char * s2 ) const { return ( strcmp ( s1 , s2 ) < 0 ) ; }
} ;
typedef map < LPCSTR , image_t * , CStringComparator > AllocatedImages_t ;
AllocatedImages_t AllocatedImages ;
AllocatedImages_t : : iterator itAllocatedImages ;
int giTextureBindNum = 1024 ; // will be set to this anyway at runtime, but wtf?
// return = number of images in the list, for those interested
//
int R_Images_StartIteration ( void )
{
itAllocatedImages = AllocatedImages . begin ( ) ;
return AllocatedImages . size ( ) ;
}
image_t * R_Images_GetNextIteration ( void )
{
if ( itAllocatedImages = = AllocatedImages . end ( ) )
return NULL ;
image_t * pImage = ( * itAllocatedImages ) . second ;
+ + itAllocatedImages ;
return pImage ;
}
// clean up anything to do with an image_t struct, but caller will have to clear the internal to an image_t struct ready for either struct free() or overwrite...
//
// (avoid using ri.xxxx stuff here in case running on dedicated)
//
static void R_Images_DeleteImageContents ( image_t * pImage )
{
assert ( pImage ) ; // should never be called with NULL
if ( pImage )
{
2013-04-04 18:24:26 +00:00
if ( qglDeleteTextures ) { //won't have one if we switched to dedicated.
qglDeleteTextures ( 1 , & pImage - > texnum ) ;
}
2013-04-04 14:52:42 +00:00
Z_Free ( pImage ) ;
}
}
/*
= = = = = = = = = = = = = = =
Upload32
= = = = = = = = = = = = = = =
*/
extern qboolean charSet ;
static void Upload32 ( unsigned * data ,
2013-04-04 18:24:26 +00:00
int img_width , int img_height ,
qboolean mipmap ,
qboolean picmip ,
qboolean isLightmap ,
qboolean allowTC ,
int * pformat ,
int * pUploadWidth , int * pUploadHeight )
2013-04-04 14:52:42 +00:00
{
int samples ;
int i , c ;
byte * scan ;
float rMax = 0 , gMax = 0 , bMax = 0 ;
2013-04-04 18:24:26 +00:00
int width = img_width ;
int height = img_height ;
2013-04-04 14:52:42 +00:00
//
// perform optional picmip operation
//
if ( picmip ) {
2013-04-04 18:24:26 +00:00
for ( i = 0 ; i < r_picmip - > integer ; i + + ) {
R_MipMap ( ( byte * ) data , width , height ) ;
width > > = 1 ;
height > > = 1 ;
if ( width < 1 ) {
width = 1 ;
}
if ( height < 1 ) {
height = 1 ;
}
}
2013-04-04 14:52:42 +00:00
}
//
// clamp to the current upper OpenGL limit
// scale both axis down equally so we don't have to
// deal with a half mip resampling
//
2013-04-04 18:24:26 +00:00
while ( width > glConfig . maxTextureSize | | height > glConfig . maxTextureSize ) {
R_MipMap ( ( byte * ) data , width , height ) ;
width > > = 1 ;
height > > = 1 ;
2013-04-04 14:52:42 +00:00
}
//
// scan the texture for each channel's max values
// and verify if the alpha channel is being used or not
//
c = width * height ;
scan = ( ( byte * ) data ) ;
samples = 3 ;
2013-04-04 18:24:26 +00:00
for ( i = 0 ; i < c ; i + + )
2013-04-04 18:01:17 +00:00
{
2013-04-04 18:24:26 +00:00
if ( scan [ i * 4 + 0 ] > rMax )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
rMax = scan [ i * 4 + 0 ] ;
}
if ( scan [ i * 4 + 1 ] > gMax )
{
gMax = scan [ i * 4 + 1 ] ;
}
if ( scan [ i * 4 + 2 ] > bMax )
{
bMax = scan [ i * 4 + 2 ] ;
}
if ( scan [ i * 4 + 3 ] ! = 255 )
{
samples = 4 ;
break ;
}
}
// select proper internal format
if ( samples = = 3 )
{
if ( glConfig . textureCompression = = TC_S3TC & & allowTC )
{
* pformat = GL_RGB4_S3TC ;
}
else if ( glConfig . textureCompression = = TC_S3TC_DXT & & allowTC )
{ // Compress purely color - no alpha
if ( r_texturebits - > integer = = 16 ) {
* pformat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT ; //this format cuts to 16 bit
2013-04-04 14:52:42 +00:00
}
2013-04-04 18:24:26 +00:00
else { //if we aren't using 16 bit then, use 32 bit compression
* pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ;
2013-04-04 14:52:42 +00:00
}
}
2013-04-04 18:24:26 +00:00
else if ( isLightmap & & r_texturebitslm - > integer > 0 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
// Allow different bit depth when we are a lightmap
if ( r_texturebitslm - > integer = = 16 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
* pformat = GL_RGB5 ;
2013-04-04 14:52:42 +00:00
}
2013-04-04 18:24:26 +00:00
else if ( r_texturebitslm - > integer = = 32 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
* pformat = GL_RGB8 ;
2013-04-04 14:52:42 +00:00
}
}
2013-04-04 18:24:26 +00:00
else if ( r_texturebits - > integer = = 16 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
* pformat = GL_RGB5 ;
2013-04-04 14:52:42 +00:00
}
2013-04-04 18:24:26 +00:00
else if ( r_texturebits - > integer = = 32 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
* pformat = GL_RGB8 ;
}
else
{
* pformat = 3 ;
2013-04-04 14:52:42 +00:00
}
}
2013-04-04 18:24:26 +00:00
else if ( samples = = 4 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
if ( glConfig . textureCompression = = TC_S3TC_DXT & & allowTC )
{ // Compress both alpha and color
* pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ;
}
else if ( r_texturebits - > integer = = 16 )
{
* pformat = GL_RGBA4 ;
}
else if ( r_texturebits - > integer = = 32 )
{
* pformat = GL_RGBA8 ;
}
else
{
* pformat = 4 ;
2013-04-04 14:52:42 +00:00
}
}
2013-04-04 18:24:26 +00:00
* pUploadWidth = width ;
* pUploadHeight = height ;
// copy or resample data as appropriate for first MIP level
if ( ! mipmap )
{
qglTexImage2D ( GL_TEXTURE_2D , 0 , * pformat , width , height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , data ) ;
goto done ;
}
2013-04-04 14:52:42 +00:00
2013-04-04 18:24:26 +00:00
R_LightScaleTexture ( data , width , height , ( qboolean ) ! mipmap ) ;
2013-04-04 14:52:42 +00:00
2013-04-04 18:24:26 +00:00
qglTexImage2D ( GL_TEXTURE_2D , 0 , * pformat , width , height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , data ) ;
2013-04-04 14:52:42 +00:00
if ( mipmap )
{
int miplevel ;
miplevel = 0 ;
2013-04-04 18:24:26 +00:00
while ( width > 1 | | height > 1 )
2013-04-04 14:52:42 +00:00
{
2013-04-04 18:24:26 +00:00
R_MipMap ( ( byte * ) data , width , height ) ;
width > > = 1 ;
height > > = 1 ;
if ( width < 1 )
width = 1 ;
if ( height < 1 )
height = 1 ;
2013-04-04 14:52:42 +00:00
miplevel + + ;
2013-04-04 18:24:26 +00:00
if ( r_colorMipLevels - > integer )
{
R_BlendOverTexture ( ( byte * ) data , width * height , mipBlendColors [ miplevel ] ) ;
2013-04-04 14:52:42 +00:00
}
2013-04-04 18:24:26 +00:00
qglTexImage2D ( GL_TEXTURE_2D , miplevel , * pformat , width , height , 0 , GL_RGBA , GL_UNSIGNED_BYTE , data ) ;
2013-04-04 14:52:42 +00:00
}
}
done :
if ( mipmap )
{
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , gl_filter_min ) ;
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , gl_filter_max ) ;
if ( r_ext_texture_filter_anisotropic - > integer & & glConfig . textureFilterAnisotropicAvailable ) {
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAX_ANISOTROPY_EXT , 2.0f ) ;
}
}
else
{
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MIN_FILTER , GL_LINEAR ) ;
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_MAG_FILTER , GL_LINEAR ) ;
}
GL_CheckErrors ( ) ;
}
static void GL_ResetBinds ( void )
{
memset ( glState . currenttextures , 0 , sizeof ( glState . currenttextures ) ) ;
if ( qglBindTexture )
{
if ( qglActiveTextureARB )
{
GL_SelectTexture ( 1 ) ;
qglBindTexture ( GL_TEXTURE_2D , 0 ) ;
GL_SelectTexture ( 0 ) ;
qglBindTexture ( GL_TEXTURE_2D , 0 ) ;
}
else
{
qglBindTexture ( GL_TEXTURE_2D , 0 ) ;
}
}
}
// special function used in conjunction with "devmapbsp"...
//
// (avoid using ri.xxxx stuff here in case running on dedicated)
//
void R_Images_DeleteLightMaps ( void )
{
qboolean bEraseOccured = qfalse ;
for ( AllocatedImages_t : : iterator itImage = AllocatedImages . begin ( ) ; itImage ! = AllocatedImages . end ( ) ; bEraseOccured ? itImage : + + itImage )
{
bEraseOccured = qfalse ;
image_t * pImage = ( * itImage ) . second ;
if ( pImage - > imgName [ 0 ] = = ' * ' & & strstr ( pImage - > imgName , " lightmap " ) ) // loose check, but should be ok
{
R_Images_DeleteImageContents ( pImage ) ;
2013-04-04 18:24:26 +00:00
# ifndef __linux__
2013-04-04 14:52:42 +00:00
itImage = AllocatedImages . erase ( itImage ) ;
bEraseOccured = qtrue ;
2013-04-04 18:24:26 +00:00
# else
// MS & Dinkimware got the map::erase return wrong (it's null)
AllocatedImages_t : : iterator itTemp = itImage ;
itImage + + ;
AllocatedImages . erase ( itTemp ) ;
# endif
2013-04-04 14:52:42 +00:00
}
}
GL_ResetBinds ( ) ;
}
// special function currently only called by Dissolve code...
//
void R_Images_DeleteImage ( image_t * pImage )
{
// Even though we supply the image handle, we need to get the corresponding iterator entry...
//
AllocatedImages_t : : iterator itImage = AllocatedImages . find ( pImage - > imgName ) ;
if ( itImage ! = AllocatedImages . end ( ) )
{
R_Images_DeleteImageContents ( pImage ) ;
AllocatedImages . erase ( itImage ) ;
}
else
{
assert ( 0 ) ;
}
}
// called only at app startup, vid_restart, app-exit
//
void R_Images_Clear ( void )
{
image_t * pImage ;
// int iNumImages =
R_Images_StartIteration ( ) ;
while ( ( pImage = R_Images_GetNextIteration ( ) ) ! = NULL )
{
R_Images_DeleteImageContents ( pImage ) ;
}
AllocatedImages . clear ( ) ;
giTextureBindNum = 1024 ;
}
void RE_RegisterImages_Info_f ( void )
{
image_t * pImage = NULL ;
int iImage = 0 ;
int iTexels = 0 ;
int iNumImages = R_Images_StartIteration ( ) ;
while ( ( pImage = R_Images_GetNextIteration ( ) ) ! = NULL )
{
ri . Printf ( PRINT_ALL , " %d: (%4dx%4dy) \" %s \" " , iImage , pImage - > uploadWidth , pImage - > uploadHeight , pImage - > imgName ) ;
ri . Printf ( PRINT_DEVELOPER , " , levused %d " , pImage - > iLastLevelUsedOn ) ;
ri . Printf ( PRINT_ALL , " \n " ) ;
iTexels + = pImage - > uploadWidth * pImage - > uploadHeight ;
iImage + + ;
}
ri . Printf ( PRINT_ALL , " %d Images. %d (%.2fMB) texels total, (not including mipmaps) \n " , iNumImages , iTexels , ( float ) iTexels / 1024.0f / 1024.0f ) ;
ri . Printf ( PRINT_DEVELOPER , " RE_RegisterMedia_GetLevel(): %d " , RE_RegisterMedia_GetLevel ( ) ) ;
}
// implement this if you need to, do a find for the caller. I don't need it though, so far.
//
//void RE_RegisterImages_LevelLoadBegin(const char *psMapName);
// currently, this just goes through all the images and dumps any not referenced on this level...
//
2013-04-04 18:01:17 +00:00
qboolean RE_RegisterImages_LevelLoadEnd ( void )
2013-04-04 14:52:42 +00:00
{
ri . Printf ( PRINT_DEVELOPER , " RE_RegisterImages_LevelLoadEnd(): \n " ) ;
// int iNumImages = AllocatedImages.size(); // more for curiosity, really.
qboolean bEraseOccured = qfalse ;
for ( AllocatedImages_t : : iterator itImage = AllocatedImages . begin ( ) ; itImage ! = AllocatedImages . end ( ) ; bEraseOccured ? itImage : + + itImage )
{
bEraseOccured = qfalse ;
image_t * pImage = ( * itImage ) . second ;
// don't un-register system shaders (*fog, *dlight, *white, *default), but DO de-register lightmaps ("*<mapname>/lightmap%d")
if ( pImage - > imgName [ 0 ] ! = ' * ' | | strchr ( pImage - > imgName , ' / ' ) )
{
// image used on this level?
//
if ( pImage - > iLastLevelUsedOn ! = RE_RegisterMedia_GetLevel ( ) )
{
// nope, so dump it...
//
ri . Printf ( PRINT_DEVELOPER , " Dumping image \" %s \" \n " , pImage - > imgName ) ;
R_Images_DeleteImageContents ( pImage ) ;
2013-04-04 18:24:26 +00:00
# ifndef __linux__
2013-04-04 14:52:42 +00:00
itImage = AllocatedImages . erase ( itImage ) ;
bEraseOccured = qtrue ;
2013-04-04 18:24:26 +00:00
# else
AllocatedImages_t : : iterator itTemp = itImage ;
itImage + + ;
AllocatedImages . erase ( itTemp ) ;
# endif
2013-04-04 14:52:42 +00:00
}
}
}
// this check can be deleted AFAIC, it seems to be just a quake thing...
//
// iNumImages = R_Images_StartIteration();
// if (iNumImages > MAX_DRAWIMAGES)
// {
// ri.Printf( PRINT_WARNING, "Level uses %d images, old limit was MAX_DRAWIMAGES (%d)\n", iNumImages, MAX_DRAWIMAGES);
// }
ri . Printf ( PRINT_DEVELOPER , " RE_RegisterImages_LevelLoadEnd(): Ok \n " ) ;
GL_ResetBinds ( ) ;
2013-04-04 18:01:17 +00:00
return bEraseOccured ;
2013-04-04 14:52:42 +00:00
}
// returns image_t struct if we already have this, else NULL. No disk-open performed
// (important for creating default images).
//
// This is called by both R_FindImageFile and anything that creates default images...
//
static image_t * R_FindImageFile_NoLoad ( const char * name , qboolean mipmap , qboolean allowPicmip , qboolean allowTC , int glWrapClampMode )
{
if ( ! name ) {
return NULL ;
}
char * pName = GenerateImageMappingName ( name ) ;
//
// see if the image is already loaded
//
AllocatedImages_t : : iterator itAllocatedImage = AllocatedImages . find ( pName ) ;
if ( itAllocatedImage ! = AllocatedImages . end ( ) )
{
image_t * pImage = ( * itAllocatedImage ) . second ;
// the white image can be used with any set of parms, but other mismatches are errors...
//
if ( strcmp ( pName , " *white " ) ) {
if ( pImage - > mipmap ! = mipmap ) {
ri . Printf ( PRINT_WARNING , " WARNING: reused image %s with mixed mipmap parm \n " , pName ) ;
}
if ( pImage - > allowPicmip ! = allowPicmip ) {
ri . Printf ( PRINT_WARNING , " WARNING: reused image %s with mixed allowPicmip parm \n " , pName ) ;
}
if ( pImage - > wrapClampMode ! = glWrapClampMode ) {
ri . Printf ( PRINT_WARNING , " WARNING: reused image %s with mixed glWrapClampMode parm \n " , pName ) ;
}
}
pImage - > iLastLevelUsedOn = RE_RegisterMedia_GetLevel ( ) ;
return pImage ;
}
return NULL ;
}
/*
= = = = = = = = = = = = = = = =
R_CreateImage
This is the only way any image_t are created
= = = = = = = = = = = = = = = =
*/
image_t * R_CreateImage ( const char * name , const byte * pic , int width , int height ,
qboolean mipmap , qboolean allowPicmip , qboolean allowTC , int glWrapClampMode ) {
image_t * image ;
qboolean isLightmap = qfalse ;
if ( strlen ( name ) > = MAX_QPATH ) {
ri . Error ( ERR_DROP , " R_CreateImage: \" %s \" is too long \n " , name ) ;
}
if ( glConfig . clampToEdgeAvailable & & glWrapClampMode = = GL_CLAMP ) {
glWrapClampMode = GL_CLAMP_TO_EDGE ;
}
if ( name [ 0 ] = = ' * ' )
{
char * psLightMapNameSearchPos = strrchr ( name , ' / ' ) ;
if ( psLightMapNameSearchPos & & ! strncmp ( psLightMapNameSearchPos + 1 , " lightmap " , 8 ) ) {
isLightmap = qtrue ;
}
}
if ( ( width & ( width - 1 ) ) | | ( height & ( height - 1 ) ) )
{
ri . Error ( ERR_FATAL , " R_CreateImage: %s dimensions (%i x %i) not power of 2! \n " , name , width , height ) ;
}
image = R_FindImageFile_NoLoad ( name , mipmap , allowPicmip , allowTC , glWrapClampMode ) ;
if ( image ) {
return image ;
}
image = ( image_t * ) ri . Malloc ( sizeof ( image_t ) , TAG_IMAGE_T , qtrue ) ;
// memset(image,0,sizeof(*image)); // qtrue above does this
image - > texnum = 1024 + giTextureBindNum + + ; // ++ is of course staggeringly important...
// record which map it was used on...
//
image - > iLastLevelUsedOn = RE_RegisterMedia_GetLevel ( ) ;
image - > mipmap = mipmap ;
image - > allowPicmip = allowPicmip ;
Q_strncpyz ( image - > imgName , name , sizeof ( image - > imgName ) ) ;
image - > width = width ;
image - > height = height ;
image - > wrapClampMode = glWrapClampMode ;
// lightmaps are always allocated on TMU 1
if ( qglActiveTextureARB & & isLightmap ) {
image - > TMU = 1 ;
} else {
image - > TMU = 0 ;
}
if ( qglActiveTextureARB ) {
GL_SelectTexture ( image - > TMU ) ;
}
GL_Bind ( image ) ;
Upload32 ( ( unsigned * ) pic , image - > width , image - > height ,
image - > mipmap ,
allowPicmip ,
isLightmap ,
allowTC ,
& image - > internalFormat ,
& image - > uploadWidth ,
& image - > uploadHeight ) ;
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_S , glWrapClampMode ) ;
qglTexParameterf ( GL_TEXTURE_2D , GL_TEXTURE_WRAP_T , glWrapClampMode ) ;
qglBindTexture ( GL_TEXTURE_2D , 0 ) ; //jfm: i don't know why this is here, but it breaks lightmaps when there's only 1
glState . currenttextures [ glState . currenttmu ] = 0 ; //mark it not bound
if ( image - > TMU = = 1 ) {
GL_SelectTexture ( 0 ) ;
}
LPCSTR psNewName = GenerateImageMappingName ( name ) ;
Q_strncpyz ( image - > imgName , psNewName , sizeof ( image - > imgName ) ) ;
AllocatedImages [ image - > imgName ] = image ;
return image ;
}
2013-04-04 18:24:26 +00:00
# endif // !DEDICATED
2013-04-04 14:52:42 +00:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
TARGA LOADING
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
Ghoul2 Insert Start
*/
bool LoadTGAPalletteImage ( const char * name , byte * * pic , int * width , int * height )
{
int columns , rows , numPixels ;
byte * buf_p ;
byte * buffer ;
TargaHeader targa_header ;
byte * dataStart ;
* pic = NULL ;
//
// load the file
//
ri . FS_ReadFile ( ( char * ) name , ( void * * ) & buffer ) ;
if ( ! buffer ) {
return false ;
}
buf_p = buffer ;
targa_header . id_length = * buf_p + + ;
targa_header . colormap_type = * buf_p + + ;
targa_header . image_type = * buf_p + + ;
targa_header . colormap_index = LittleShort ( * ( short * ) buf_p ) ;
buf_p + = 2 ;
targa_header . colormap_length = LittleShort ( * ( short * ) buf_p ) ;
buf_p + = 2 ;
targa_header . colormap_size = * buf_p + + ;
targa_header . x_origin = LittleShort ( * ( short * ) buf_p ) ;
buf_p + = 2 ;
targa_header . y_origin = LittleShort ( * ( short * ) buf_p ) ;
buf_p + = 2 ;
targa_header . width = LittleShort ( * ( short * ) buf_p ) ;
buf_p + = 2 ;
targa_header . height = LittleShort ( * ( short * ) buf_p ) ;
buf_p + = 2 ;
targa_header . pixel_size = * buf_p + + ;
targa_header . attributes = * buf_p + + ;
if ( targa_header . image_type ! = 1 )
{
ri . Error ( ERR_DROP , " LoadTGAPalletteImage: Only type 1 (uncompressed pallettised) TGA images supported \n " ) ;
}
if ( targa_header . colormap_type = = 0 )
{
ri . Error ( ERR_DROP , " LoadTGAPalletteImage: colormaps ONLY supported \n " ) ;
}
columns = targa_header . width ;
rows = targa_header . height ;
numPixels = columns * rows ;
if ( width )
* width = columns ;
if ( height )
* height = rows ;
* pic = ( unsigned char * ) ri . Malloc ( numPixels , TAG_TEMP_WORKSPACE , qfalse ) ;
if ( targa_header . id_length ! = 0 )
{
buf_p + = targa_header . id_length ; // skip TARGA image comment
}
dataStart = buf_p + ( targa_header . colormap_length * ( targa_header . colormap_size / 4 ) ) ;
memcpy ( * pic , dataStart , numPixels ) ;
ri . FS_FreeFile ( buffer ) ;
return true ;
}
2013-04-04 18:24:26 +00:00
# ifndef DEDICATED
2013-04-04 14:52:42 +00:00
/*
Ghoul2 Insert End
*/
// My TGA loader...
//
//---------------------------------------------------
# pragma pack(push,1)
typedef struct
{
byte byIDFieldLength ; // must be 0
byte byColourmapType ; // 0 = truecolour, 1 = paletted, else bad
byte byImageType ; // 1 = colour mapped (palette), uncompressed, 2 = truecolour, uncompressed, else bad
word w1stColourMapEntry ; // must be 0
word wColourMapLength ; // 256 for 8-bit palettes, else 0 for true-colour
byte byColourMapEntrySize ; // 24 for 8-bit palettes, else 0 for true-colour
word wImageXOrigin ; // ignored
word wImageYOrigin ; // ignored
word wImageWidth ; // in pixels
word wImageHeight ; // in pixels
byte byImagePlanes ; // bits per pixel (8 for paletted, else 24 for true-colour)
byte byScanLineOrder ; // Image descriptor bytes
// bits 0-3 = # attr bits (alpha chan)
// bits 4-5 = pixel order/dir
// bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way)
} TGAHeader_t ;
# pragma pack(pop)
// *pic == pic, else NULL for failed.
//
// returns false if found but had a format error, else true for either OK or not-found (there's a reason for this)
//
void LoadTGA ( const char * name , byte * * pic , int * width , int * height )
{
char sErrorString [ 1024 ] ;
bool bFormatErrors = false ;
// these don't need to be declared or initialised until later, but the compiler whines that 'goto' skips them.
//
byte * pRGBA = NULL ;
byte * pOut = NULL ;
byte * pIn = NULL ;
* pic = NULL ;
# define TGA_FORMAT_ERROR(blah) {sprintf(sErrorString,blah); bFormatErrors = true; goto TGADone;}
//#define TGA_FORMAT_ERROR(blah) ri.Error( ERR_DROP, blah );
//
// load the file
//
byte * pTempLoadedBuffer = 0 ;
ri . FS_ReadFile ( ( char * ) name , ( void * * ) & pTempLoadedBuffer ) ;
if ( ! pTempLoadedBuffer ) {
return ;
}
TGAHeader_t * pHeader = ( TGAHeader_t * ) pTempLoadedBuffer ;
if ( pHeader - > byColourmapType ! = 0 )
{
TGA_FORMAT_ERROR ( " LoadTGA: colourmaps not supported \n " ) ;
}
if ( pHeader - > byImageType ! = 2 & & pHeader - > byImageType ! = 3 & & pHeader - > byImageType ! = 10 )
{
TGA_FORMAT_ERROR ( " LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RLE-RGB) images supported \n " ) ;
}
if ( pHeader - > w1stColourMapEntry ! = 0 )
{
TGA_FORMAT_ERROR ( " LoadTGA: colourmaps not supported \n " ) ;
}
if ( pHeader - > wColourMapLength ! = 0 & & pHeader - > wColourMapLength ! = 256 )
{
TGA_FORMAT_ERROR ( " LoadTGA: ColourMapLength must be either 0 or 256 \n " ) ;
}
if ( pHeader - > byColourMapEntrySize ! = 0 & & pHeader - > byColourMapEntrySize ! = 24 )
{
TGA_FORMAT_ERROR ( " LoadTGA: ColourMapEntrySize must be either 0 or 24 \n " ) ;
}
if ( ( pHeader - > byImagePlanes ! = 24 & & pHeader - > byImagePlanes ! = 32 ) & & ( pHeader - > byImagePlanes ! = 8 & & pHeader - > byImageType ! = 3 ) )
{
TGA_FORMAT_ERROR ( " LoadTGA: Only type 2 (RGB), 3 (gray), and 10 (RGB) TGA images supported \n " ) ;
}
if ( ( pHeader - > byScanLineOrder & 0x30 ) ! = 0x00 & &
( pHeader - > byScanLineOrder & 0x30 ) ! = 0x10 & &
( pHeader - > byScanLineOrder & 0x30 ) ! = 0x20 & &
( pHeader - > byScanLineOrder & 0x30 ) ! = 0x30
)
{
TGA_FORMAT_ERROR ( " LoadTGA: ScanLineOrder must be either 0x00,0x10,0x20, or 0x30 \n " ) ;
}
// these last checks are so i can use ID's RLE-code. I don't dare fiddle with it or it'll probably break...
//
if ( pHeader - > byImageType = = 10 )
{
if ( ( pHeader - > byScanLineOrder & 0x30 ) ! = 0x00 )
{
TGA_FORMAT_ERROR ( " LoadTGA: RLE-RGB Images (type 10) must be in bottom-to-top format \n " ) ;
}
if ( pHeader - > byImagePlanes ! = 24 & & pHeader - > byImagePlanes ! = 32 ) // probably won't happen, but avoids compressed greyscales?
{
TGA_FORMAT_ERROR ( " LoadTGA: RLE-RGB Images (type 10) must be 24 or 32 bit \n " ) ;
}
}
// now read the actual bitmap in...
//
// Image descriptor bytes
// bits 0-3 = # attr bits (alpha chan)
// bits 4-5 = pixel order/dir
// bits 6-7 scan line interleave (00b=none,01b=2way interleave,10b=4way)
//
int iYStart , iXStart , iYStep , iXStep ;
switch ( pHeader - > byScanLineOrder & 0x30 )
{
default : // default case stops the compiler complaining about using uninitialised vars
case 0x00 : // left to right, bottom to top
iXStart = 0 ;
iXStep = 1 ;
iYStart = pHeader - > wImageHeight - 1 ;
iYStep = - 1 ;
break ;
case 0x10 : // right to left, bottom to top
iXStart = pHeader - > wImageWidth - 1 ;
iXStep = - 1 ;
iYStart = pHeader - > wImageHeight - 1 ;
iYStep = - 1 ;
break ;
case 0x20 : // left to right, top to bottom
iXStart = 0 ;
iXStep = 1 ;
iYStart = 0 ;
iYStep = 1 ;
break ;
case 0x30 : // right to left, top to bottom
iXStart = pHeader - > wImageWidth - 1 ;
iXStep = - 1 ;
iYStart = 0 ;
iYStep = 1 ;
break ;
}
// feed back the results...
//
if ( width )
* width = pHeader - > wImageWidth ;
if ( height )
* height = pHeader - > wImageHeight ;
pRGBA = ( byte * ) ri . Malloc ( pHeader - > wImageWidth * pHeader - > wImageHeight * 4 , TAG_TEMP_WORKSPACE , qfalse ) ;
* pic = pRGBA ;
pOut = pRGBA ;
pIn = pTempLoadedBuffer + sizeof ( * pHeader ) ;
// I don't know if this ID-thing here is right, since comments that I've seen are at the end of the file,
// with a zero in this field. However, may as well...
//
if ( pHeader - > byIDFieldLength ! = 0 )
pIn + = pHeader - > byIDFieldLength ; // skip TARGA image comment
byte red , green , blue , alpha ;
if ( pHeader - > byImageType = = 2 | | pHeader - > byImageType = = 3 ) // RGB or greyscale
{
for ( int y = iYStart , iYCount = 0 ; iYCount < pHeader - > wImageHeight ; y + = iYStep , iYCount + + )
{
pOut = pRGBA + y * pHeader - > wImageWidth * 4 ;
for ( int x = iXStart , iXCount = 0 ; iXCount < pHeader - > wImageWidth ; x + = iXStep , iXCount + + )
{
switch ( pHeader - > byImagePlanes )
{
case 8 :
blue = * pIn + + ;
green = blue ;
red = blue ;
* pOut + + = red ;
* pOut + + = green ;
* pOut + + = blue ;
* pOut + + = 255 ;
break ;
case 24 :
blue = * pIn + + ;
green = * pIn + + ;
red = * pIn + + ;
* pOut + + = red ;
* pOut + + = green ;
* pOut + + = blue ;
* pOut + + = 255 ;
break ;
case 32 :
blue = * pIn + + ;
green = * pIn + + ;
red = * pIn + + ;
alpha = * pIn + + ;
* pOut + + = red ;
* pOut + + = green ;
* pOut + + = blue ;
* pOut + + = alpha ;
break ;
default :
assert ( 0 ) ; // if we ever hit this, someone deleted a header check higher up
TGA_FORMAT_ERROR ( " LoadTGA: Image can only have 8, 24 or 32 planes for RGB/greyscale \n " ) ;
break ;
}
}
}
}
else
if ( pHeader - > byImageType = = 10 ) // RLE-RGB
{
// I've no idea if this stuff works, I normally reject RLE targas, but this is from ID's code
// so maybe I should try and support it...
//
byte packetHeader , packetSize , j ;
for ( int y = pHeader - > wImageHeight - 1 ; y > = 0 ; y - - )
{
pOut = pRGBA + y * pHeader - > wImageWidth * 4 ;
for ( int x = 0 ; x < pHeader - > wImageWidth ; )
{
packetHeader = * pIn + + ;
packetSize = 1 + ( packetHeader & 0x7f ) ;
if ( packetHeader & 0x80 ) // run-length packet
{
switch ( pHeader - > byImagePlanes )
{
case 24 :
blue = * pIn + + ;
green = * pIn + + ;
red = * pIn + + ;
alpha = 255 ;
break ;
case 32 :
blue = * pIn + + ;
green = * pIn + + ;
red = * pIn + + ;
alpha = * pIn + + ;
break ;
default :
assert ( 0 ) ; // if we ever hit this, someone deleted a header check higher up
TGA_FORMAT_ERROR ( " LoadTGA: RLE-RGB can only have 24 or 32 planes \n " ) ;
break ;
}
for ( j = 0 ; j < packetSize ; j + + )
{
* pOut + + = red ;
* pOut + + = green ;
* pOut + + = blue ;
* pOut + + = alpha ;
x + + ;
if ( x = = pHeader - > wImageWidth ) // run spans across rows
{
x = 0 ;
if ( y > 0 )
y - - ;
else
goto breakOut ;
pOut = pRGBA + y * pHeader - > wImageWidth * 4 ;
}
}
}
else
{ // non run-length packet
for ( j = 0 ; j < packetSize ; j + + )
{
switch ( pHeader - > byImagePlanes )
{
case 24 :
blue = * pIn + + ;
green = * pIn + + ;
red = * pIn + + ;
* pOut + + = red ;
* pOut + + = green ;
* pOut + + = blue ;
* pOut + + = 255 ;
break ;
case 32 :
blue = * pIn + + ;
green = * pIn + + ;
red = * pIn + + ;
alpha = * pIn + + ;
* pOut + + = red ;
* pOut + + = green ;
* pOut + + = blue ;
* pOut + + = alpha ;
break ;
default :
assert ( 0 ) ; // if we ever hit this, someone deleted a header check higher up
TGA_FORMAT_ERROR ( " LoadTGA: RLE-RGB can only have 24 or 32 planes \n " ) ;
break ;
}
x + + ;
if ( x = = pHeader - > wImageWidth ) // pixel packet run spans across rows
{
x = 0 ;
if ( y > 0 )
y - - ;
else
goto breakOut ;
pOut = pRGBA + y * pHeader - > wImageWidth * 4 ;
}
}
}
}
breakOut : ;
}
}
TGADone :
ri . FS_FreeFile ( pTempLoadedBuffer ) ;
if ( bFormatErrors )
{
ri . Error ( ERR_DROP , " %s( File: \" %s \" ) \n " , sErrorString , name ) ;
}
}
static void LoadJPG ( const char * filename , unsigned char * * pic , int * width , int * height ) {
/* This struct contains the JPEG decompression parameters and pointers to
* working space ( which is allocated as needed by the JPEG library ) .
*/
struct jpeg_decompress_struct cinfo ;
/* We use our private extension JPEG error handler.
* Note that this struct must live as long as the main JPEG parameter
* struct , to avoid dangling - pointer problems .
*/
/* This struct represents a JPEG error handler. It is declared separately
* because applications often want to supply a specialized error handler
* ( see the second half of this file for an example ) . But here we just
* take the easy way out and use the standard error handler , which will
* print a message on stderr and call exit ( ) if compression fails .
* Note that this struct must live as long as the main JPEG parameter
* struct , to avoid dangling - pointer problems .
*/
struct jpeg_error_mgr jerr ;
/* More stuff */
JSAMPARRAY buffer ; /* Output row buffer */
int row_stride ; /* physical row width in output buffer */
unsigned char * out ;
byte * fbuffer ;
byte * bbuf ;
/* In this example we want to open the input file before doing anything else,
* so that the setjmp ( ) error recovery below can assume the file is open .
* VERY IMPORTANT : use " b " option to fopen ( ) if you are on a machine that
* requires it in order to read binary files .
*/
fileHandle_t h ;
const int len = FS_FOpenFileRead ( filename , & h , qfalse ) ;
if ( ! h )
{
return ;
}
fbuffer = ( byte * ) Z_Malloc ( len + 4096 , TAG_TEMP_WORKSPACE ) ;
FS_Read ( fbuffer , len , h ) ;
FS_FCloseFile ( h ) ;
/* Step 1: allocate and initialize JPEG decompression object */
/* We have to set up the error handler first, in case the initialization
* step fails . ( Unlikely , but it could happen if you are out of memory . )
* This routine fills in the contents of struct jerr , and returns jerr ' s
* address which we place into the link field in cinfo .
*/
cinfo . err = jpeg_std_error ( & jerr ) ;
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress ( & cinfo ) ;
/* Step 2: specify data source (eg, a file) */
jpeg_stdio_src ( & cinfo , fbuffer ) ;
/* Step 3: read file parameters with jpeg_read_header() */
( void ) jpeg_read_header ( & cinfo , TRUE ) ;
/* We can ignore the return value from jpeg_read_header since
* ( a ) suspension is not possible with the stdio data source , and
* ( b ) we passed TRUE to reject a tables - only JPEG file as an error .
* See libjpeg . doc for more info .
*/
/* Step 4: set parameters for decompression */
/* In this example, we don't need to change any of the defaults set by
* jpeg_read_header ( ) , so we do nothing here .
*/
/* Step 5: Start decompressor */
( void ) jpeg_start_decompress ( & cinfo ) ;
/* We can ignore the return value since suspension is not possible
* with the stdio data source .
*/
/* We may need to do some setup of our own at this point before reading
* the data . After jpeg_start_decompress ( ) we have the correct scaled
* output image dimensions available , as well as the output colormap
* if we asked for color quantization .
* In this example , we need to make an output work buffer of the right size .
*/
/* JSAMPLEs per row in output buffer */
row_stride = cinfo . output_width * cinfo . output_components ;
// rww - 9-13-01 [1-26-01-sof2]
if ( cinfo . output_components ! = 4 & & cinfo . output_components ! = 1 ) {
Com_Printf ( " JPG %s is unsupported color depth (%d) \n " , filename , cinfo . output_components ) ;
}
out = ( unsigned char * ) ri . Malloc ( cinfo . output_width * cinfo . output_height * 4 , TAG_TEMP_WORKSPACE , qfalse ) ;
* pic = out ;
* width = cinfo . output_width ;
* height = cinfo . output_height ;
/* Step 6: while (scan lines remain to be read) */
/* jpeg_read_scanlines(...); */
/* Here we use the library's state variable cinfo.output_scanline as the
* loop counter , so that we don ' t have to keep track ourselves .
*/
while ( cinfo . output_scanline < cinfo . output_height ) {
/* jpeg_read_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long , but you could ask for
* more than one scanline at a time if that ' s more convenient .
*/
bbuf = ( ( out + ( row_stride * cinfo . output_scanline ) ) ) ;
buffer = & bbuf ;
( void ) jpeg_read_scanlines ( & cinfo , buffer , 1 ) ;
}
if ( cinfo . output_components = = 1 )
{
byte * pbDest = ( * pic + ( cinfo . output_width * cinfo . output_height * 4 ) ) - 1 ;
byte * pbSrc = ( * pic + ( cinfo . output_width * cinfo . output_height ) ) - 1 ;
int iPixels = cinfo . output_width * cinfo . output_height ;
for ( int i = 0 ; i < iPixels ; i + + )
{
byte b = * pbSrc - - ;
* pbDest - - = 255 ;
* pbDest - - = b ;
* pbDest - - = b ;
* pbDest - - = b ;
}
}
else
// clear all the alphas to 255
{
int i , j ;
byte * buf ;
buf = * pic ;
j = cinfo . output_width * cinfo . output_height * 4 ;
for ( i = 3 ; i < j ; i + = 4 )
{
buf [ i ] = 255 ;
}
}
/* Step 7: Finish decompression */
( void ) jpeg_finish_decompress ( & cinfo ) ;
/* We can ignore the return value since suspension is not possible
* with the stdio data source .
*/
/* Step 8: Release JPEG decompression object */
/* This is an important step since it will release a good deal of memory. */
jpeg_destroy_decompress ( & cinfo ) ;
/* After finish_decompress, we can close the input file.
* Here we postpone it until after no more JPEG errors are possible ,
* so as to simplify the setjmp error logic above . ( Actually , I don ' t
* think that jpeg_destroy can do an error exit , but why assume anything . . . )
*/
Z_Free ( fbuffer ) ;
/* At this point you may want to check to see whether any corrupt-data
* warnings occurred ( test whether jerr . pub . num_warnings is nonzero ) .
*/
/* And we're done! */
}
/* Expanded data destination object for stdio output */
typedef struct {
struct jpeg_destination_mgr pub ; /* public fields */
byte * outfile ; /* target stream */
int size ;
} my_destination_mgr ;
typedef my_destination_mgr * my_dest_ptr ;
/*
* Initialize destination - - - called by jpeg_start_compress
* before any data is actually written .
*/
void init_destination ( j_compress_ptr cinfo )
{
my_dest_ptr dest = ( my_dest_ptr ) cinfo - > dest ;
dest - > pub . next_output_byte = dest - > outfile ;
dest - > pub . free_in_buffer = dest - > size ;
}
/*
* Empty the output buffer - - - called whenever buffer fills up .
*
* In typical applications , this should write the entire output buffer
* ( ignoring the current state of next_output_byte & free_in_buffer ) ,
* reset the pointer & count to the start of the buffer , and return TRUE
* indicating that the buffer has been dumped .
*
* In applications that need to be able to suspend compression due to output
* overrun , a FALSE return indicates that the buffer cannot be emptied now .
* In this situation , the compressor will return to its caller ( possibly with
* an indication that it has not accepted all the supplied scanlines ) . The
* application should resume compression after it has made more room in the
* output buffer . Note that there are substantial restrictions on the use of
* suspension - - - see the documentation .
*
* When suspending , the compressor will back up to a convenient restart point
* ( typically the start of the current MCU ) . next_output_byte & free_in_buffer
* indicate where the restart point will be if the current call returns FALSE .
* Data beyond this point will be regenerated after resumption , so do not
* write it out when emptying the buffer externally .
*/
boolean empty_output_buffer ( j_compress_ptr cinfo )
{
return TRUE ;
}
/*
* Compression initialization .
* Before calling this , all parameters and a data destination must be set up .
*
* We require a write_all_tables parameter as a failsafe check when writing
* multiple datastreams from the same compression object . Since prior runs
* will have left all the tables marked sent_table = TRUE , a subsequent run
* would emit an abbreviated stream ( no tables ) by default . This may be what
* is wanted , but for safety ' s sake it should not be the default behavior :
* programmers should have to make a deliberate choice to emit abbreviated
* images . Therefore the documentation and examples should encourage people
* to pass write_all_tables = TRUE ; then it will take active thought to do the
* wrong thing .
*/
GLOBAL void
jpeg_start_compress ( j_compress_ptr cinfo , boolean write_all_tables )
{
if ( cinfo - > global_state ! = CSTATE_START )
ERREXIT1 ( cinfo , JERR_BAD_STATE , cinfo - > global_state ) ;
if ( write_all_tables )
jpeg_suppress_tables ( cinfo , FALSE ) ; /* mark all tables to be written */
/* (Re)initialize error mgr and destination modules */
( * cinfo - > err - > reset_error_mgr ) ( ( j_common_ptr ) cinfo ) ;
( * cinfo - > dest - > init_destination ) ( cinfo ) ;
/* Perform master selection of active modules */
jinit_compress_master ( cinfo ) ;
/* Set up for the first pass */
( * cinfo - > master - > prepare_for_pass ) ( cinfo ) ;
/* Ready for application to drive first pass through jpeg_write_scanlines
* or jpeg_write_raw_data .
*/
cinfo - > next_scanline = 0 ;
cinfo - > global_state = ( cinfo - > raw_data_in ? CSTATE_RAW_OK : CSTATE_SCANNING ) ;
}
/*
* Write some scanlines of data to the JPEG compressor .
*
* The return value will be the number of lines actually written .
* This should be less than the supplied num_lines only in case that
* the data destination module has requested suspension of the compressor ,
* or if more than image_height scanlines are passed in .
*
* Note : we warn about excess calls to jpeg_write_scanlines ( ) since
* this likely signals an application programmer error . However ,
* excess scanlines passed in the last valid call are * silently * ignored ,
* so that the application need not adjust num_lines for end - of - image
* when using a multiple - scanline buffer .
*/
GLOBAL JDIMENSION
jpeg_write_scanlines ( j_compress_ptr cinfo , JSAMPARRAY scanlines ,
JDIMENSION num_lines )
{
JDIMENSION row_ctr , rows_left ;
if ( cinfo - > global_state ! = CSTATE_SCANNING )
ERREXIT1 ( cinfo , JERR_BAD_STATE , cinfo - > global_state ) ;
if ( cinfo - > next_scanline > = cinfo - > image_height )
WARNMS ( cinfo , JWRN_TOO_MUCH_DATA ) ;
/* Call progress monitor hook if present */
if ( cinfo - > progress ! = NULL ) {
cinfo - > progress - > pass_counter = ( long ) cinfo - > next_scanline ;
cinfo - > progress - > pass_limit = ( long ) cinfo - > image_height ;
( * cinfo - > progress - > progress_monitor ) ( ( j_common_ptr ) cinfo ) ;
}
/* Give master control module another chance if this is first call to
* jpeg_write_scanlines . This lets output of the frame / scan headers be
* delayed so that application can write COM , etc , markers between
* jpeg_start_compress and jpeg_write_scanlines .
*/
if ( cinfo - > master - > call_pass_startup )
( * cinfo - > master - > pass_startup ) ( cinfo ) ;
/* Ignore any extra scanlines at bottom of image. */
rows_left = cinfo - > image_height - cinfo - > next_scanline ;
if ( num_lines > rows_left )
num_lines = rows_left ;
row_ctr = 0 ;
( * cinfo - > main - > process_data ) ( cinfo , scanlines , & row_ctr , num_lines ) ;
cinfo - > next_scanline + = row_ctr ;
return row_ctr ;
}
/*
* Terminate destination - - - called by jpeg_finish_compress
* after all data has been written . Usually needs to flush buffer .
*
* NB : * not * called by jpeg_abort or jpeg_destroy ; surrounding
* application must deal with any cleanup that should happen even
* for error exit .
*/
static int hackSize ;
void term_destination ( j_compress_ptr cinfo )
{
my_dest_ptr dest = ( my_dest_ptr ) cinfo - > dest ;
size_t datacount = dest - > size - dest - > pub . free_in_buffer ;
hackSize = datacount ;
}
/*
* Prepare for output to a stdio stream .
* The caller must have already opened the stream , and is responsible
* for closing it after finishing compression .
*/
void jpegDest ( j_compress_ptr cinfo , byte * outfile , int size )
{
my_dest_ptr dest ;
/* The destination object is made permanent so that multiple JPEG images
* can be written to the same file without re - executing jpeg_stdio_dest .
* This makes it dangerous to use this manager and a different destination
* manager serially with the same JPEG object , because their private object
* sizes may be different . Caveat programmer .
*/
if ( cinfo - > dest = = NULL ) { /* first time for this JPEG object? */
cinfo - > dest = ( struct jpeg_destination_mgr * )
( * cinfo - > mem - > alloc_small ) ( ( j_common_ptr ) cinfo , JPOOL_PERMANENT ,
sizeof ( my_destination_mgr ) ) ;
}
dest = ( my_dest_ptr ) cinfo - > dest ;
dest - > pub . init_destination = init_destination ;
dest - > pub . empty_output_buffer = empty_output_buffer ;
dest - > pub . term_destination = term_destination ;
dest - > outfile = outfile ;
dest - > size = size ;
}
void SaveJPG ( char * filename , int quality , int image_width , int image_height , unsigned char * image_buffer ) {
/* This struct contains the JPEG compression parameters and pointers to
* working space ( which is allocated as needed by the JPEG library ) .
* It is possible to have several such structures , representing multiple
* compression / decompression processes , in existence at once . We refer
* to any one struct ( and its associated working data ) as a " JPEG object " .
*/
struct jpeg_compress_struct cinfo ;
/* This struct represents a JPEG error handler. It is declared separately
* because applications often want to supply a specialized error handler
* ( see the second half of this file for an example ) . But here we just
* take the easy way out and use the standard error handler , which will
* print a message on stderr and call exit ( ) if compression fails .
* Note that this struct must live as long as the main JPEG parameter
* struct , to avoid dangling - pointer problems .
*/
struct jpeg_error_mgr jerr ;
/* More stuff */
JSAMPROW row_pointer [ 1 ] ; /* pointer to JSAMPLE row[s] */
int row_stride ; /* physical row width in image buffer */
unsigned char * out ;
/* Step 1: allocate and initialize JPEG compression object */
/* We have to set up the error handler first, in case the initialization
* step fails . ( Unlikely , but it could happen if you are out of memory . )
* This routine fills in the contents of struct jerr , and returns jerr ' s
* address which we place into the link field in cinfo .
*/
cinfo . err = jpeg_std_error ( & jerr ) ;
/* Now we can initialize the JPEG compression object. */
jpeg_create_compress ( & cinfo ) ;
/* Step 2: specify data destination (eg, a file) */
/* Note: steps 2 and 3 can be done in either order. */
/* Here we use the library-supplied code to send compressed data to a
* stdio stream . You can also write your own code to do something else .
* VERY IMPORTANT : use " b " option to fopen ( ) if you are on a machine that
* requires it in order to write binary files .
*/
out = ( unsigned char * ) ri . Hunk_AllocateTempMemory ( image_width * image_height * 4 ) ;
jpegDest ( & cinfo , out , image_width * image_height * 4 ) ;
/* Step 3: set parameters for compression */
/* First we supply a description of the input image.
* Four fields of the cinfo struct must be filled in :
*/
cinfo . image_width = image_width ; /* image width and height, in pixels */
cinfo . image_height = image_height ;
cinfo . input_components = 4 ; /* # of color components per pixel */
cinfo . in_color_space = JCS_RGB ; /* colorspace of input image */
/* Now use the library's routine to set default compression parameters.
* ( You must set at least cinfo . in_color_space before calling this ,
* since the defaults depend on the source color space . )
*/
jpeg_set_defaults ( & cinfo ) ;
/* Now you can set any non-default parameters you wish to.
* Here we just illustrate the use of quality ( quantization table ) scaling :
*/
jpeg_set_quality ( & cinfo , quality , TRUE /* limit to baseline-JPEG values */ ) ;
/* Step 4: Start compressor */
/* TRUE ensures that we will write a complete interchange-JPEG file.
* Pass TRUE unless you are very sure of what you ' re doing .
*/
jpeg_start_compress ( & cinfo , TRUE ) ;
/* Step 5: while (scan lines remain to be written) */
/* jpeg_write_scanlines(...); */
/* Here we use the library's state variable cinfo.next_scanline as the
* loop counter , so that we don ' t have to keep track ourselves .
* To keep things simple , we pass one scanline per call ; you can pass
* more if you wish , though .
*/
row_stride = image_width * 4 ; /* JSAMPLEs per row in image_buffer */
while ( cinfo . next_scanline < cinfo . image_height ) {
/* jpeg_write_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long , but you could pass
* more than one scanline at a time if that ' s more convenient .
*/
row_pointer [ 0 ] = & image_buffer [ ( ( cinfo . image_height - 1 ) * row_stride ) - cinfo . next_scanline * row_stride ] ;
( void ) jpeg_write_scanlines ( & cinfo , row_pointer , 1 ) ;
}
/* Step 6: Finish compression */
jpeg_finish_compress ( & cinfo ) ;
/* After finish_compress, we can close the output file. */
ri . FS_WriteFile ( filename , out , hackSize ) ;
ri . Hunk_FreeTempMemory ( out ) ;
/* Step 7: release JPEG compression object */
/* This is an important step since it will release a good deal of memory. */
jpeg_destroy_compress ( & cinfo ) ;
/* And we're done! */
}
//===================================================================
/*
= = = = = = = = = = = = = = = = =
R_LoadImage
Loads any of the supported image types into a cannonical
32 bit format .
= = = = = = = = = = = = = = = = =
*/
void R_LoadImage ( const char * shortname , byte * * pic , int * width , int * height )
{
int bytedepth ;
char name [ MAX_QPATH ] ;
* pic = NULL ;
* width = 0 ;
* height = 0 ;
COM_StripExtension ( shortname , name ) ;
COM_DefaultExtension ( name , sizeof ( name ) , " .jpg " ) ;
LoadJPG ( name , pic , width , height ) ;
if ( * pic ) {
return ;
}
COM_StripExtension ( shortname , name ) ;
COM_DefaultExtension ( name , sizeof ( name ) , " .png " ) ;
LoadPNG32 ( name , pic , width , height , & bytedepth ) ; // try png first
if ( * pic ) {
return ;
}
COM_StripExtension ( shortname , name ) ;
COM_DefaultExtension ( name , sizeof ( name ) , " .tga " ) ;
LoadTGA ( name , pic , width , height ) ; // try tga first
if ( * pic ) {
return ;
}
}
/*
= = = = = = = = = = = = = = =
R_FindImageFile
Finds or loads the given image .
Returns NULL if it fails , not a default image .
= = = = = = = = = = = = = =
*/
image_t * R_FindImageFile ( const char * name , qboolean mipmap , qboolean allowPicmip , qboolean allowTC , int glWrapClampMode ) {
image_t * image ;
int width , height ;
byte * pic ;
if ( ! name
| | com_dedicated - > integer // stop ghoul2 horribleness as regards image loading from server
)
{
return NULL ;
}
// need to do this here as well as in R_CreateImage, or R_FindImageFile_NoLoad() may complain about
// different clamp parms used...
//
if ( glConfig . clampToEdgeAvailable & & glWrapClampMode = = GL_CLAMP ) {
glWrapClampMode = GL_CLAMP_TO_EDGE ;
}
image = R_FindImageFile_NoLoad ( name , mipmap , allowPicmip , allowTC , glWrapClampMode ) ;
if ( image ) {
return image ;
}
//
// load the pic from disk
//
R_LoadImage ( name , & pic , & width , & height ) ;
if ( pic = = NULL ) { // if we dont get a successful load
return NULL ; // bail
}
// refuse to find any files not power of 2 dims...
//
if ( ( width & ( width - 1 ) ) | | ( height & ( height - 1 ) ) )
{
ri . Printf ( PRINT_ALL , " Refusing to load non-power-2-dims(%d,%d) pic \" %s \" ... \n " , width , height , name ) ;
return NULL ;
}
image = R_CreateImage ( ( char * ) name , pic , width , height , mipmap , allowPicmip , allowTC , glWrapClampMode ) ;
ri . Free ( pic ) ;
return image ;
}
/*
= = = = = = = = = = = = = = = =
R_CreateDlightImage
= = = = = = = = = = = = = = = =
*/
# define DLIGHT_SIZE 16
static void R_CreateDlightImage ( void ) {
int x , y ;
byte data [ DLIGHT_SIZE ] [ DLIGHT_SIZE ] [ 4 ] ;
int b ;
// make a centered inverse-square falloff blob for dynamic lighting
for ( x = 0 ; x < DLIGHT_SIZE ; x + + ) {
for ( y = 0 ; y < DLIGHT_SIZE ; y + + ) {
float d ;
d = ( DLIGHT_SIZE / 2 - 0.5f - x ) * ( DLIGHT_SIZE / 2 - 0.5f - x ) +
( DLIGHT_SIZE / 2 - 0.5f - y ) * ( DLIGHT_SIZE / 2 - 0.5f - y ) ;
b = 4000 / d ;
if ( b > 255 ) {
b = 255 ;
} else if ( b < 75 ) {
b = 0 ;
}
data [ y ] [ x ] [ 0 ] =
data [ y ] [ x ] [ 1 ] =
data [ y ] [ x ] [ 2 ] = b ;
data [ y ] [ x ] [ 3 ] = 255 ;
}
}
tr . dlightImage = R_CreateImage ( " *dlight " , ( byte * ) data , DLIGHT_SIZE , DLIGHT_SIZE , qfalse , qfalse , qfalse , GL_CLAMP ) ;
}
/*
= = = = = = = = = = = = = = = = =
R_InitFogTable
= = = = = = = = = = = = = = = = =
*/
void R_InitFogTable ( void ) {
int i ;
float d ;
float exp ;
exp = 0.5 ;
for ( i = 0 ; i < FOG_TABLE_SIZE ; i + + ) {
d = pow ( ( float ) i / ( FOG_TABLE_SIZE - 1 ) , exp ) ;
tr . fogTable [ i ] = d ;
}
}
/*
= = = = = = = = = = = = = = = =
R_FogFactor
Returns a 0.0 to 1.0 fog density value
This is called for each texel of the fog texture on startup
and for each vertex of transparent shaders in fog dynamically
= = = = = = = = = = = = = = = =
*/
float R_FogFactor ( float s , float t ) {
float d ;
s - = 1.0 / 512 ;
if ( s < 0 ) {
return 0 ;
}
if ( t < 1.0 / 32 ) {
return 0 ;
}
if ( t < 31.0 / 32 ) {
s * = ( t - 1.0f / 32.0f ) / ( 30.0f / 32.0f ) ;
}
// we need to leave a lot of clamp range
s * = 8 ;
if ( s > 1.0 ) {
s = 1.0 ;
}
d = tr . fogTable [ ( int ) ( s * ( FOG_TABLE_SIZE - 1 ) ) ] ;
return d ;
}
/*
= = = = = = = = = = = = = = = =
R_CreateFogImage
= = = = = = = = = = = = = = = =
*/
# define FOG_S 256
# define FOG_T 32
static void R_CreateFogImage ( void ) {
int x , y ;
byte * data ;
float g ;
float d ;
float borderColor [ 4 ] ;
data = ( unsigned char * ) ri . Hunk_AllocateTempMemory ( FOG_S * FOG_T * 4 ) ;
g = 2.0 ;
// S is distance, T is depth
for ( x = 0 ; x < FOG_S ; x + + ) {
for ( y = 0 ; y < FOG_T ; y + + ) {
d = R_FogFactor ( ( x + 0.5f ) / FOG_S , ( y + 0.5f ) / FOG_T ) ;
data [ ( y * FOG_S + x ) * 4 + 0 ] =
data [ ( y * FOG_S + x ) * 4 + 1 ] =
data [ ( y * FOG_S + x ) * 4 + 2 ] = 255 ;
data [ ( y * FOG_S + x ) * 4 + 3 ] = 255 * d ;
}
}
// standard openGL clamping doesn't really do what we want -- it includes
// the border color at the edges. OpenGL 1.2 has clamp-to-edge, which does
// what we want.
tr . fogImage = R_CreateImage ( " *fog " , ( byte * ) data , FOG_S , FOG_T , qfalse , qfalse , qfalse , GL_CLAMP ) ;
ri . Hunk_FreeTempMemory ( data ) ;
borderColor [ 0 ] = 1.0 ;
borderColor [ 1 ] = 1.0 ;
borderColor [ 2 ] = 1.0 ;
borderColor [ 3 ] = 1 ;
qglTexParameterfv ( GL_TEXTURE_2D , GL_TEXTURE_BORDER_COLOR , borderColor ) ;
}
/*
= = = = = = = = = = = = = = = = = =
R_CreateDefaultImage
= = = = = = = = = = = = = = = = = =
*/
# define DEFAULT_SIZE 16
static void R_CreateDefaultImage ( void ) {
int x ;
byte data [ DEFAULT_SIZE ] [ DEFAULT_SIZE ] [ 4 ] ;
// the default image will be a box, to allow you to see the mapping coordinates
Com_Memset ( data , 32 , sizeof ( data ) ) ;
for ( x = 0 ; x < DEFAULT_SIZE ; x + + ) {
data [ 0 ] [ x ] [ 0 ] =
data [ 0 ] [ x ] [ 1 ] =
data [ 0 ] [ x ] [ 2 ] =
data [ 0 ] [ x ] [ 3 ] = 255 ;
data [ x ] [ 0 ] [ 0 ] =
data [ x ] [ 0 ] [ 1 ] =
data [ x ] [ 0 ] [ 2 ] =
data [ x ] [ 0 ] [ 3 ] = 255 ;
data [ DEFAULT_SIZE - 1 ] [ x ] [ 0 ] =
data [ DEFAULT_SIZE - 1 ] [ x ] [ 1 ] =
data [ DEFAULT_SIZE - 1 ] [ x ] [ 2 ] =
data [ DEFAULT_SIZE - 1 ] [ x ] [ 3 ] = 255 ;
data [ x ] [ DEFAULT_SIZE - 1 ] [ 0 ] =
data [ x ] [ DEFAULT_SIZE - 1 ] [ 1 ] =
data [ x ] [ DEFAULT_SIZE - 1 ] [ 2 ] =
data [ x ] [ DEFAULT_SIZE - 1 ] [ 3 ] = 255 ;
}
tr . defaultImage = R_CreateImage ( " *default " , ( byte * ) data , DEFAULT_SIZE , DEFAULT_SIZE , qtrue , qfalse , qfalse , GL_REPEAT ) ;
}
/*
= = = = = = = = = = = = = = = = = =
R_CreateBuiltinImages
= = = = = = = = = = = = = = = = = =
*/
void R_CreateBuiltinImages ( void ) {
int x , y ;
byte data [ DEFAULT_SIZE ] [ DEFAULT_SIZE ] [ 4 ] ;
R_CreateDefaultImage ( ) ;
// we use a solid white image instead of disabling texturing
Com_Memset ( data , 255 , sizeof ( data ) ) ;
tr . whiteImage = R_CreateImage ( " *white " , ( byte * ) data , 8 , 8 , qfalse , qfalse , qfalse , GL_REPEAT ) ;
// with overbright bits active, we need an image which is some fraction of full color,
// for default lightmaps, etc
for ( x = 0 ; x < DEFAULT_SIZE ; x + + ) {
for ( y = 0 ; y < DEFAULT_SIZE ; y + + ) {
data [ y ] [ x ] [ 0 ] =
data [ y ] [ x ] [ 1 ] =
data [ y ] [ x ] [ 2 ] = tr . identityLightByte ;
data [ y ] [ x ] [ 3 ] = 255 ;
}
}
tr . identityLightImage = R_CreateImage ( " *identityLight " , ( byte * ) data , 8 , 8 , qfalse , qfalse , qfalse , GL_REPEAT ) ;
for ( x = 0 ; x < 32 ; x + + ) {
// scratchimage is usually used for cinematic drawing
tr . scratchImage [ x ] = R_CreateImage ( va ( " *scratch%d " , x ) , ( byte * ) data , DEFAULT_SIZE , DEFAULT_SIZE , qfalse , qtrue , qfalse , GL_CLAMP ) ;
}
R_CreateDlightImage ( ) ;
R_CreateFogImage ( ) ;
}
/*
= = = = = = = = = = = = = = =
R_SetColorMappings
= = = = = = = = = = = = = = =
*/
void R_SetColorMappings ( void ) {
int i , j ;
float g ;
int inf ;
int shift ;
// setup the overbright lighting
tr . overbrightBits = r_overBrightBits - > integer ;
if ( ! glConfig . deviceSupportsGamma ) {
tr . overbrightBits = 0 ; // need hardware gamma for overbright
}
// never overbright in windowed mode
if ( ! glConfig . isFullscreen )
{
tr . overbrightBits = 0 ;
}
if ( tr . overbrightBits > 1 ) {
tr . overbrightBits = 1 ;
}
if ( tr . overbrightBits < 0 ) {
tr . overbrightBits = 0 ;
}
tr . identityLight = 1.0f / ( 1 < < tr . overbrightBits ) ;
tr . identityLightByte = 255 * tr . identityLight ;
if ( r_intensity - > value < 1.0f ) {
ri . Cvar_Set ( " r_intensity " , " 1 " ) ;
}
if ( r_gamma - > value < 0.5f ) {
ri . Cvar_Set ( " r_gamma " , " 0.5 " ) ;
} else if ( r_gamma - > value > 3.0f ) {
ri . Cvar_Set ( " r_gamma " , " 3.0 " ) ;
}
g = r_gamma - > value ;
shift = tr . overbrightBits ;
for ( i = 0 ; i < 256 ; i + + ) {
if ( g = = 1 ) {
inf = i ;
} else {
inf = 255 * pow ( i / 255.0f , 1.0f / g ) + 0.5f ;
}
inf < < = shift ;
if ( inf < 0 ) {
inf = 0 ;
}
if ( inf > 255 ) {
inf = 255 ;
}
s_gammatable [ i ] = inf ;
}
for ( i = 0 ; i < 256 ; i + + ) {
j = i * r_intensity - > value ;
if ( j > 255 ) {
j = 255 ;
}
s_intensitytable [ i ] = j ;
}
if ( glConfig . deviceSupportsGamma )
{
GLimp_SetGamma ( s_gammatable , s_gammatable , s_gammatable ) ;
}
}
/*
= = = = = = = = = = = = = = =
R_InitImages
= = = = = = = = = = = = = = =
*/
void R_InitImages ( void ) {
//memset(hashTable, 0, sizeof(hashTable)); // DO NOT DO THIS NOW (because of image cacheing) -ste.
// build brightness translation tables
R_SetColorMappings ( ) ;
// create default texture and white texture
R_CreateBuiltinImages ( ) ;
}
/*
= = = = = = = = = = = = = = =
R_DeleteTextures
= = = = = = = = = = = = = = =
*/
// (only gets called during vid_restart now (and app exit), not during map load)
//
void R_DeleteTextures ( void ) {
R_Images_Clear ( ) ;
GL_ResetBinds ( ) ;
}
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
SKINS
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
/*
= = = = = = = = = = = = = = = = = =
CommaParse
This is unfortunate , but the skin files aren ' t
compatable with our normal parsing rules .
= = = = = = = = = = = = = = = = = =
*/
static char * CommaParse ( char * * data_p ) {
int c = 0 , len ;
char * data ;
static char com_token [ MAX_TOKEN_CHARS ] ;
data = * data_p ;
len = 0 ;
com_token [ 0 ] = 0 ;
// make sure incoming data is valid
if ( ! data ) {
* data_p = NULL ;
return com_token ;
}
while ( 1 ) {
// skip whitespace
while ( ( c = * data ) < = ' ' ) {
if ( ! c ) {
break ;
}
data + + ;
}
c = * data ;
// skip double slash comments
if ( c = = ' / ' & & data [ 1 ] = = ' / ' )
{
while ( * data & & * data ! = ' \n ' )
data + + ;
}
// skip /* */ comments
else if ( c = = ' / ' & & data [ 1 ] = = ' * ' )
{
while ( * data & & ( * data ! = ' * ' | | data [ 1 ] ! = ' / ' ) )
{
data + + ;
}
if ( * data )
{
data + = 2 ;
}
}
else
{
break ;
}
}
if ( c = = 0 ) {
return " " ;
}
// handle quoted strings
if ( c = = ' \" ' )
{
data + + ;
while ( 1 )
{
c = * data + + ;
if ( c = = ' \" ' | | ! c )
{
com_token [ len ] = 0 ;
* data_p = ( char * ) data ;
return com_token ;
}
if ( len < MAX_TOKEN_CHARS )
{
com_token [ len ] = c ;
len + + ;
}
}
}
// parse a regular word
do
{
if ( len < MAX_TOKEN_CHARS )
{
com_token [ len ] = c ;
len + + ;
}
data + + ;
c = * data ;
} while ( c > 32 & & c ! = ' , ' ) ;
if ( len = = MAX_TOKEN_CHARS )
{
// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
len = 0 ;
}
com_token [ len ] = 0 ;
* data_p = ( char * ) data ;
return com_token ;
}
/*
= = = = = = = = = = = = = = =
RE_RegisterSkin
= = = = = = = = = = = = = = =
*/
qhandle_t RE_RegisterSkin ( const char * name ) {
qhandle_t hSkin ;
skin_t * skin ;
skinSurface_t * surf ;
char * text , * text_p ;
char * token ;
char surfName [ MAX_QPATH ] ;
if ( ! name | | ! name [ 0 ] ) {
Com_Printf ( " Empty name passed to RE_RegisterSkin \n " ) ;
return 0 ;
}
if ( strlen ( name ) > = MAX_QPATH ) {
Com_Printf ( " Skin name exceeds MAX_QPATH \n " ) ;
return 0 ;
}
// see if the skin is already loaded
for ( hSkin = 1 ; hSkin < tr . numSkins ; hSkin + + ) {
skin = tr . skins [ hSkin ] ;
if ( ! Q_stricmp ( skin - > name , name ) ) {
if ( skin - > numSurfaces = = 0 ) {
return 0 ; // default skin
}
return hSkin ;
}
}
// allocate a new skin
if ( tr . numSkins = = MAX_SKINS ) {
ri . Printf ( PRINT_WARNING , " WARNING: RE_RegisterSkin( '%s' ) MAX_SKINS hit \n " , name ) ;
return 0 ;
}
tr . numSkins + + ;
skin = ( struct skin_s * ) ri . Hunk_Alloc ( sizeof ( skin_t ) , h_low ) ;
tr . skins [ hSkin ] = skin ;
Q_strncpyz ( skin - > name , name , sizeof ( skin - > name ) ) ;
skin - > numSurfaces = 0 ;
// make sure the render thread is stopped
R_SyncRenderThread ( ) ;
// If not a .skin file, load as a single shader
if ( strcmp ( name + strlen ( name ) - 5 , " .skin " ) ) {
skin - > numSurfaces = 1 ;
skin - > surfaces [ 0 ] = ( skinSurface_t * ) ri . Hunk_Alloc ( sizeof ( skin - > surfaces [ 0 ] ) , h_low ) ;
skin - > surfaces [ 0 ] - > shader = R_FindShader ( name , lightmapsNone , stylesDefault , qtrue ) ;
return hSkin ;
}
// load and parse the skin file
ri . FS_ReadFile ( name , ( void * * ) & text ) ;
if ( ! text ) {
return 0 ;
}
text_p = text ;
while ( text_p & & * text_p ) {
// get surface name
token = CommaParse ( & text_p ) ;
Q_strncpyz ( surfName , token , sizeof ( surfName ) ) ;
if ( ! token [ 0 ] ) {
break ;
}
// lowercase the surface name so skin compares are faster
Q_strlwr ( surfName ) ;
if ( * text_p = = ' , ' ) {
text_p + + ;
}
if ( strstr ( token , " tag_ " ) ) {
continue ;
}
// parse the shader name
token = CommaParse ( & text_p ) ;
2013-04-04 18:01:17 +00:00
assert ( skin - > numSurfaces < MD3_MAX_SURFACES ) ;
2013-04-04 14:52:42 +00:00
surf = skin - > surfaces [ skin - > numSurfaces ] = ( skinSurface_t * ) ri . Hunk_Alloc ( sizeof ( * skin - > surfaces [ 0 ] ) , h_low ) ;
Q_strncpyz ( surf - > name , surfName , sizeof ( surf - > name ) ) ;
surf - > shader = R_FindShader ( token , lightmapsNone , stylesDefault , qtrue ) ;
skin - > numSurfaces + + ;
}
ri . FS_FreeFile ( text ) ;
// never let a skin have 0 shaders
if ( skin - > numSurfaces = = 0 ) {
return 0 ; // use default skin
}
return hSkin ;
}
2013-04-04 18:24:26 +00:00
# endif // !DEDICATED
2013-04-04 14:52:42 +00:00
/*
= = = = = = = = = = = = = = =
R_InitSkins
= = = = = = = = = = = = = = =
*/
void R_InitSkins ( void ) {
skin_t * skin ;
tr . numSkins = 1 ;
// make the default skin have all default shaders
skin = tr . skins [ 0 ] = ( struct skin_s * ) ri . Hunk_Alloc ( sizeof ( skin_t ) , h_low ) ;
Q_strncpyz ( skin - > name , " <default skin> " , sizeof ( skin - > name ) ) ;
skin - > numSurfaces = 1 ;
skin - > surfaces [ 0 ] = ( skinSurface_t * ) ri . Hunk_Alloc ( sizeof ( * skin - > surfaces ) , h_low ) ;
skin - > surfaces [ 0 ] - > shader = tr . defaultShader ;
}
/*
= = = = = = = = = = = = = = =
R_GetSkinByHandle
= = = = = = = = = = = = = = =
*/
skin_t * R_GetSkinByHandle ( qhandle_t hSkin ) {
if ( hSkin < 1 | | hSkin > = tr . numSkins ) {
return tr . skins [ 0 ] ;
}
return tr . skins [ hSkin ] ;
}
2013-04-04 18:24:26 +00:00
# ifndef DEDICATED
2013-04-04 14:52:42 +00:00
/*
= = = = = = = = = = = = = = =
R_SkinList_f
= = = = = = = = = = = = = = =
*/
void R_SkinList_f ( void ) {
int i , j ;
skin_t * skin ;
ri . Printf ( PRINT_ALL , " ------------------ \n " ) ;
for ( i = 0 ; i < tr . numSkins ; i + + ) {
skin = tr . skins [ i ] ;
ri . Printf ( PRINT_ALL , " %3i:%s \n " , i , skin - > name ) ;
for ( j = 0 ; j < skin - > numSurfaces ; j + + ) {
ri . Printf ( PRINT_ALL , " %s = %s \n " ,
skin - > surfaces [ j ] - > name , skin - > surfaces [ j ] - > shader - > name ) ;
}
}
ri . Printf ( PRINT_ALL , " ------------------ \n " ) ;
}
2013-04-04 18:24:26 +00:00
# endif // !DEDICATED