/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena 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 2 of the License, or (at your option) any later version. Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tr_image.c #include "tr_local.h" 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; int force32upload; // leilei - hack to get bloom/post to always do 32bit texture int detailhack; // leilei - hack to fade detail textures, kill repeat patterns int palettedformat = GL_COLOR_INDEX8_EXT; // leilei - paletted texture support #define FILE_HASH_SIZE 1024 static image_t* hashTable[FILE_HASH_SIZE]; extern int ismaptexture; // // leilei - paletted texture support START // byte *palettemain; byte palmap[32][32][32]; // 15bpp lookup table unsigned d_8to24table[256]; // for non-palette supporting hardware qboolean paletteability; // If our hardware has paletted texture support... qboolean paletteavailable; // If we got a palette... qboolean paletteenabled; // If we wish to enable the 32-to-8 conversions (and 8-to-32 also) // // leilei - blending hack support // // // Some cards just cannot use blending functions such as additive or subtractive. // So to get around this, I try to turn them into alpha channels and then alpha blend them. // Chipsets affected by this (That can run OA otherwise) are PowerVR PCX2 and Matrox Mystique / MGA-100A // // Furthermore, this may also be useful for a software rasterizer by turning additives into // 1-bit alpha textures. // int hackoperation; // 0 - do nothing // 1 - additive to alpha texture // 2 - demote alpha texture if not alphaed on the edges // 3 - subtractive to alpha texture // 4 - modulate to alpha texture byte BestColor (int r, int g, int b, int start, int stop) { int i; int dr, dg, db; int bestdistortion, distortion; int berstcolor; byte *pal; // // let any color go to 0 as a last resort // bestdistortion = 256*256*4; berstcolor = 0; pal = palettemain + start*3; for (i=start ; i<= stop ; i++) { dr = r - (int)pal[0]; dg = g - (int)pal[1]; db = b - (int)pal[2]; pal += 3; distortion = dr*dr + dg*dg + db*db; if (distortion < bestdistortion) { if (!distortion) return i; // perfect match bestdistortion = distortion; berstcolor = i; } } if (berstcolor) //ri.Printf( PRINT_ALL, "returned %i\n", berstcolor ); return berstcolor; } // From QUAKE2, but modified to support a transparent color unsigned char paltable[1024]; unsigned char paltablergb[768]; void R_SetTexturePalette( unsigned palette[256]) { int i; { for ( i = 0; i < 256; i++ ) { paltable[i*4+0] = ( palette[i] >> 0 ) & 0xff; paltable[i*4+1] = ( palette[i] >> 8 ) & 0xff; paltable[i*4+2] = ( palette[i] >> 16 ) & 0xff; paltable[i*4+3] = 0xFF; // all of these colors will be opaque paltablergb[i*3+0] = ( palette[i] >> 0 ) & 0xff; paltablergb[i*3+1] = ( palette[i] >> 8 ) & 0xff; paltablergb[i*3+2] = ( palette[i] >> 16 ) & 0xff; } paltable[255*4+3] = 0x00; // EXCEPT DREADED INDEX 255!!!! } } // leilei - paletted texture extern GLvoid (APIENTRYP qglColorTableEXT)( GLint, GLint, GLint, GLint, GLint, const GLvoid *); void R_PickTexturePalette(int alpha) { qglEnable(GL_SHARED_TEXTURE_PALETTE_EXT); if (alpha) qglColorTableEXT( GL_SHARED_TEXTURE_PALETTE_EXT, GL_RGBA, 256, GL_RGBA, GL_UNSIGNED_BYTE, paltable ); else qglColorTableEXT( GL_SHARED_TEXTURE_PALETTE_EXT, GL_RGB, 256, GL_RGB, GL_UNSIGNED_BYTE, paltablergb ); qglEnable(GL_SHARED_TEXTURE_PALETTE_EXT); } unsigned r_rawpalette[256]; void R_SetPalette ( const unsigned char *palette) { int i; byte *rp = ( byte * ) r_rawpalette; if ( palette ) { for ( i = 0; i < 256; i++ ) { rp[i*4+0] = palette[i*3+0]; rp[i*4+1] = palette[i*3+1]; rp[i*4+2] = palette[i*3+2]; rp[i*4+3] = 0xff; } rp[255*4+3] = 0x00; // alpha? } else { for ( i = 0; i < 256; i++ ) { rp[i*4+0] = d_8to24table[i] & 0xff; rp[i*4+1] = ( d_8to24table[i] >> 8 ) & 0xff; rp[i*4+2] = ( d_8to24table[i] >> 16 ) & 0xff; rp[i*4+3] = 0xff; } rp[255*4+3] = 0x00; // alpha? } R_SetTexturePalette( r_rawpalette ); } // leilei - for palettizing GLSL shaders, dump palette values to console void R_GLSLPalette ( const unsigned char *palette) { int i; float redf, greenf, bluef; if ( palette ) { for ( i = 0; i < 256; i++ ) { redf = (d_8to24table[i] & 0xff) * 0.003921568627451; greenf = (( d_8to24table[i] >> 8 ) & 0xff) * 0.003921568627451; bluef = (( d_8to24table[i] >> 16 ) & 0xff) * 0.003921568627451; ri.Printf( PRINT_ALL, " vec3(%f, %f, %f),\n", redf, greenf, bluef ); } } } void R_GLSLPalette_f ( ) { R_GLSLPalette(palettemain); } void R_InitPalette( void ) { byte *buff; int len; int i, v; ri.Printf( PRINT_ALL, "INIT PALETTE......\n"); len = ri.FS_ReadFile("gfx/palette.lmp", (void **)&buff); if(!buff){ ri.Printf( PRINT_ALL, "PALLETE FALED :(!\n" ); paletteavailable = 0; // Don't have a palette paletteenabled = 0; // Don't do 8-bit textures return; } palettemain = buff; ri.Printf( PRINT_ALL, "PALETTE LOADDEEEED!!!!!!!!!!!!1\n" ); paletteavailable = 1; // Do have a palette if (palettedTextureSupport) paletteability = 1; else paletteability = 0; if (paletteability) // load this palette for GL { qglEnable( GL_SHARED_TEXTURE_PALETTE_EXT ); R_SetTexturePalette( palettemain ); R_SetPalette(palettemain); } // 15bpp lookup, straight out of Engoo { int r, g, b, beastcolor; ri.Printf( PRINT_ALL, "15bpp lookup generation.\n" ); for (r=0 ; r<256 ; r+=8) { ri.Printf( PRINT_ALL, "." ); for (g=0 ; g<256 ; g+=8) { for (b=0 ; b<256 ; b+=8) { beastcolor = BestColor (r, g, b, 1, 254); palmap[r>>3][g>>3][b>>3] = beastcolor; } } } } { int re, ge, be; for (i=0 ; i<256 ; i++) { re = palettemain[0]; ge = palettemain[1]; be = palettemain[2]; palettemain += 3; v = (255<<24) + (re<<0) + (ge<<8) + (be<<16); d_8to24table[i] = v; } } } // // leilei - paletted texture support END // /* ** 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} }; /* ================ return a hash value for the filename ================ */ static long generateHashValue( const char *fname ) { int i; long hash; char letter; hash = 0; i = 0; while (fname[i] != '\0') { letter = tolower(fname[i]); if (letter =='.') break; // don't include extension if (letter =='\\') letter = '/'; // damn path names hash+=(long)(letter)*(i+119); i++; } hash &= (FILE_HASH_SIZE-1); return hash; } /* =============== 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; } } // hack to prevent trilinear from being set on voodoo, // because their driver freaks... if ( i == 5 && glConfig.hardwareType == GLHW_3DFX_2D3D || r_leifx->integer ) { ri.Printf( PRINT_ALL, "Refusing to set trilinear on a voodoo.\n" ); i = 3; } if ( i == 6 ) { ri.Printf (PRINT_ALL, "bad filter name\n"); return; } gl_filter_min = modes[i].minimize; gl_filter_max = modes[i].maximize; // change all the existing mipmap texture objects for ( i = 0 ; i < tr.numImages ; i++ ) { glt = tr.images[ i ]; if ( glt->flags & IMGFLAG_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); } } } /* =============== R_SumOfUsedImages =============== */ int R_SumOfUsedImages( void ) { int total; int i; total = 0; for ( i = 0; i < tr.numImages; i++ ) { if ( tr.images[i]->frameUsed == tr.frameCount ) { total += tr.images[i]->uploadWidth * tr.images[i]->uploadHeight; } } return total; } /* =============== R_ImageList_f =============== */ void R_ImageList_f( void ) { int i; int estTotalSize = 0; float estTotalTimeLoaded = 0; ri.Printf(PRINT_ALL, "\n -w-- -h-- type -size- --name-------\n"); for ( i = 0 ; i < tr.numImages ; i++ ) { image_t *image = tr.images[i]; char *format = "???? "; char *sizeSuffix; int estSize; int displaySize; estSize = image->uploadHeight * image->uploadWidth; switch(image->internalFormat) { case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: format = "sDXT1"; // 64 bits per 16 pixels, so 4 bits per pixel estSize /= 2; break; case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: format = "sDXT5"; // 128 bits per 16 pixels, so 1 byte per pixel break; case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: format = "sBPTC"; // 128 bits per 16 pixels, so 1 byte per pixel break; case GL_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT: format = "LATC "; // 128 bits per 16 pixels, so 1 byte per pixel break; case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: format = "DXT1 "; // 64 bits per 16 pixels, so 4 bits per pixel estSize /= 2; break; case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: format = "DXT5 "; // 128 bits per 16 pixels, so 1 byte per pixel break; case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: format = "BPTC "; // 128 bits per 16 pixels, so 1 byte per pixel break; case GL_RGB4_S3TC: format = "S3TC "; // same as DXT1? estSize /= 2; break; case GL_RGBA4: case GL_RGBA8: case GL_RGBA: format = "RGBA "; // 4 bytes per pixel estSize *= 4; break; case GL_LUMINANCE8: case GL_LUMINANCE16: case GL_LUMINANCE: format = "L "; // 1 byte per pixel? break; case GL_RGB5: case GL_RGB8: case GL_RGB: format = "RGB "; // 3 bytes per pixel? estSize *= 3; break; case GL_LUMINANCE8_ALPHA8: case GL_LUMINANCE16_ALPHA16: case GL_LUMINANCE_ALPHA: format = "LA "; // 2 bytes per pixel? estSize *= 2; break; case GL_SRGB_EXT: case GL_SRGB8_EXT: format = "sRGB "; // 3 bytes per pixel? estSize *= 3; break; case GL_SRGB_ALPHA_EXT: case GL_SRGB8_ALPHA8_EXT: format = "sRGBA"; // 4 bytes per pixel? estSize *= 4; break; case GL_SLUMINANCE_EXT: case GL_SLUMINANCE8_EXT: format = "sL "; // 1 byte per pixel? break; case GL_SLUMINANCE_ALPHA_EXT: case GL_SLUMINANCE8_ALPHA8_EXT: format = "sLA "; // 2 byte per pixel? estSize *= 2; break; } // mipmap adds about 50% if (image->flags & IMGFLAG_MIPMAP) estSize += estSize / 2; sizeSuffix = "b "; displaySize = estSize; if (displaySize > 1024) { displaySize /= 1024; sizeSuffix = "kb"; } if (displaySize > 1024) { displaySize /= 1024; sizeSuffix = "Mb"; } if (displaySize > 1024) { displaySize /= 1024; sizeSuffix = "Gb"; } //ri.Printf(PRINT_ALL, "%4i: %4ix%4i %s %4i%s %s\n", i, image->uploadWidth, image->uploadHeight, format, displaySize, sizeSuffix, image->imgName); ri.Printf(PRINT_ALL, "%4i: %4ix%4i %s %4i%s %s %f %f\n", i, image->uploadWidth, image->uploadHeight, format, displaySize, sizeSuffix, image->imgName, image->loadTime, image->procTime); estTotalSize += estSize; estTotalTimeLoaded += image->loadTime + image->procTime; } ri.Printf (PRINT_ALL, " ---------\n"); ri.Printf (PRINT_ALL, " approx %i bytes\n", estTotalSize); ri.Printf (PRINT_ALL, " %i total images\n\n", tr.numImages ); ri.Printf (PRINT_ALL, " %f msec loaded\n\n", estTotalTimeLoaded ); } /* =============== R_ImageListMapOnly_f This version is used to make life easier to see which map textures are used and also provide a bunch of lame zip commands to modularize maps better (i.e. packing only used stuff by the *good* maps for releases) =============== */ void R_ImageListMapOnly_f( void ) { int i; for ( i = 0 ; i < tr.numImages ; i++ ) { image_t *image = tr.images[i]; char *zipcommand = "zip -9 "; char localName[ MAX_QPATH ]; char *sizeSuffix; int estSize; int displaySize; estSize = image->uploadHeight * image->uploadWidth; // mipmap adds about 50% if (image->flags & IMGFLAG_MIPMAP) estSize += estSize / 2; sizeSuffix = "b "; displaySize = estSize; //if ( !strncmp( image->imgName, "textures", 8 ) ) { if (image->maptexture){ COM_StripExtension( image->imgName, localName, MAX_QPATH ); ri.Printf(PRINT_ALL, "%s pak1-map-mapname.pk3 %s.*\n", zipcommand, localName); } } } //======================================================================= /* ================ ResampleTexture Used to resample images in a more general than quartering fashion. This will only be filtered properly if the resampled size is greater than half the original size. If a larger shrinking is needed, use the mipmap function before or after. ================ */ static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight ) { int i, j; unsigned *inrow, *inrow2; unsigned frac, fracstep; unsigned p1[2048], p2[2048]; byte *pix1, *pix2, *pix3, *pix4; if (outwidth>2048) ri.Error(ERR_DROP, "ResampleTexture: max width"); fracstep = inwidth*0x10000/outwidth; frac = fracstep>>2; for ( i=0 ; i>16); frac += fracstep; } frac = 3*(fracstep>>2); for ( i=0 ; i>16); frac += fracstep; } for (i=0 ; i>2; ((byte *)(out+j))[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; ((byte *)(out+j))[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; ((byte *)(out+j))[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; } } } // // Darkplaces texture resampling with lerping // from Twilight/Darkplaces, code by LordHavoc (I AM ASSUMING) // static void Image_Resample32LerpLine (const unsigned char *in, unsigned char *out, int inwidth, int outwidth) { int j, xi, oldx = 0, f, fstep, endx, lerp; fstep = (int) (inwidth*65536.0f/outwidth); endx = (inwidth-1); for (j = 0,f = 0;j < outwidth;j++, f += fstep) { xi = f >> 16; if (xi != oldx) { in += (xi - oldx) * 4; oldx = xi; } if (xi < endx) { lerp = f & 0xFFFF; *out++ = (unsigned char) ((((in[4] - in[0]) * lerp) >> 16) + in[0]); *out++ = (unsigned char) ((((in[5] - in[1]) * lerp) >> 16) + in[1]); *out++ = (unsigned char) ((((in[6] - in[2]) * lerp) >> 16) + in[2]); *out++ = (unsigned char) ((((in[7] - in[3]) * lerp) >> 16) + in[3]); } else // last pixel of the line has no pixel to lerp to { *out++ = in[0]; *out++ = in[1]; *out++ = in[2]; *out++ = in[3]; } } } #define LERPBYTE(i) r = resamplerow1[i];out[i] = (unsigned char) ((((resamplerow2[i] - r) * lerp) >> 16) + r) static void Image_Resample32Lerp(const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight) { int i, j, r, yi, oldy, f, fstep, lerp, endy = (inheight - 1), inwidth4 = inwidth*4, outwidth4 = outwidth*4; unsigned char *out; const unsigned char *inrow; unsigned char *resamplerow1; unsigned char *resamplerow2; out = (unsigned char *)outdata; fstep = (int) (inheight*65536.0f/outheight); resamplerow1 = ri.Hunk_AllocateTempMemory(outwidth*4*2); resamplerow2 = resamplerow1 + outwidth*4; inrow = (const unsigned char *)indata; oldy = 0; Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); for (i = 0, f = 0;i < outheight;i++,f += fstep) { yi = f >> 16; if (yi < endy) { lerp = f & 0xFFFF; if (yi != oldy) { inrow = (unsigned char *)indata + inwidth4*yi; if (yi == oldy+1) memcpy(resamplerow1, resamplerow2, outwidth4); else Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); Image_Resample32LerpLine (inrow + inwidth4, resamplerow2, inwidth, outwidth); oldy = yi; } j = outwidth - 4; while(j >= 0) { LERPBYTE( 0); LERPBYTE( 1); LERPBYTE( 2); LERPBYTE( 3); LERPBYTE( 4); LERPBYTE( 5); LERPBYTE( 6); LERPBYTE( 7); LERPBYTE( 8); LERPBYTE( 9); LERPBYTE(10); LERPBYTE(11); LERPBYTE(12); LERPBYTE(13); LERPBYTE(14); LERPBYTE(15); out += 16; resamplerow1 += 16; resamplerow2 += 16; j -= 4; } if (j & 2) { LERPBYTE( 0); LERPBYTE( 1); LERPBYTE( 2); LERPBYTE( 3); LERPBYTE( 4); LERPBYTE( 5); LERPBYTE( 6); LERPBYTE( 7); out += 8; resamplerow1 += 8; resamplerow2 += 8; } if (j & 1) { LERPBYTE( 0); LERPBYTE( 1); LERPBYTE( 2); LERPBYTE( 3); out += 4; resamplerow1 += 4; resamplerow2 += 4; } resamplerow1 -= outwidth4; resamplerow2 -= outwidth4; } else { if (yi != oldy) { inrow = (unsigned char *)indata + inwidth4*yi; if (yi == oldy+1) memcpy(resamplerow1, resamplerow2, outwidth4); else Image_Resample32LerpLine (inrow, resamplerow1, inwidth, outwidth); oldy = yi; } memcpy(out, resamplerow1, outwidth4); } } ri.Hunk_FreeTempMemory( resamplerow1 ); resamplerow1 = NULL; resamplerow2 = NULL; } /* ================ 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> 1; outHeight = inHeight >> 1; temp = 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>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>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; } } } // // leilei - paletted texture support START // // This function came out of GLQuake // with modifications static void R_MipMap8 (byte *in, int width, int height) { int i, j; byte *out, *at1;//, *at2, *at3, *at4; height >>= 1; out = in; for (i=0 ; i> 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}, }; /* ================== R_BlendToGray leilei - Tries to fade mips to gray for detail texture pattern artifact stomping ================== */ static void R_BlendToGray( byte *data, int pixelCount, int fadeto) { int i, j; float gary = 127 * 0.5; float blended; float alphed, alpher; if (fadeto < 1) return; // we don't need to for the highest mip. if (fadeto == 1){ alphed = 0.75; alpher = 0.25; } else if (fadeto == 2){ alphed = 0.50; alpher = 0.50; } else if (fadeto == 3){ alphed = 0.25; alpher = 0.75; } else { alphed = 0.0; alpher = 1.00; } //alphed = 1 / fadeto; //alpher = 1 - alphed; fadeto += 1; gary /= fadeto; for ( i = 0 ; i < pixelCount ; i++, data+=4 ) { for(j=0;j<3;j++){ blended = (data[j] * alphed) + (127 * alpher); data[j] = (int)blended; } } } /* =============== Upload32 =============== */ extern qboolean charSet; int hqresample = 0; // leilei - high quality texture resampling // Currently 0 as there is an alignment issue I haven't fixed. int isicon; // leilei - for determining if it's an icon. char dumpname[ MAX_QPATH ]; // leilei - name for texture dumping static void DumpTex( unsigned *data, int width, int height ) { // leilei - Do crazy dumping crap byte *scan; byte *baffer, *alffer, *flipper; scan = ((byte *)data); size_t offset = 0;//, memcount; int padlen = 0; int be, ber; int scrale = width * height; int scravg = width + height / 2; int quality = 85; // estimate quality from total size int hasalf = 0; float countw = 0; if (scravg > 511) quality = 42; // huge textures else if (scravg > 255) quality = 62; // large textures else if (scravg > 127) quality = 72; // large textures else if (scravg < 127) quality = 95; // tiny textures baffer = ri.Hunk_AllocateTempMemory( width * height * 3 ); flipper = ri.Hunk_AllocateTempMemory( width * height * 3 ); alffer = ri.Hunk_AllocateTempMemory( width * height * 3 ); // TODO: Save alpha separately // I'm gonna flip...... int alfcnt = 0; for (be=0; be width) countw = 0; else countw++; ber = scrale - be - 1; bib = be; if (bib < 0) bib = 0; if (bib > scrale) bib = 0; baffer[bib*3] = scan[ber*4]; baffer[bib*3+1] = scan[ber*4+1]; baffer[bib*3+2] = scan[ber*4+2]; alffer[bib*3] = scan[ber*4+3]; alffer[bib*3+1] = scan[ber*4+3]; alffer[bib*3+2] = scan[ber*4+3]; if (scan[ber*4+3] > 1){ hasalf = 1;} if (scan[ber*4+3] == 255){ alfcnt += 1; } } // NOW FIX IT //memcount = (width * 3 + padlen) * height; if ((width > 16) && (height > 16)){ RE_SaveJPG( va("dump/%s.jpg", dumpname), 85,width, height, baffer, padlen); if (hasalf) RE_SaveJPG( va("dump/%s_alpha.jpg", dumpname), 85,width, height, alffer, padlen); } ri.Printf( PRINT_ALL, "TEXDUMP: %s \n", dumpname ); // if ( baffer != 0 ) ri.Hunk_FreeTempMemory( baffer ); // if ( alffer != 0 ) ri.Hunk_FreeTempMemory( alffer ); ri.Hunk_FreeTempMemory( flipper ); } static void Upload32( unsigned *data, int width, int height, qboolean mipmap, qboolean picmip, qboolean lightMap, int *format, int *pUploadWidth, int *pUploadHeight ) { int samples; unsigned *scaledBuffer = NULL; unsigned *resampledBuffer = NULL; int scaled_width, scaled_height; int orig_width, orig_height; int i, c; byte *scan; GLenum internalFormat = GL_RGB; GLenum temp_GLformat = GL_RGBA; GLenum temp_GLtype = GL_UNSIGNED_BYTE; float rMax = 0, gMax = 0, bMax = 0; int texsizex, texsizey; int forceBits = 0; if (lightMap && r_parseStageSimple->integer) hackoperation = 4; // leilei - npot support orig_width = width; orig_height = height; // // convert to exact power of 2 sizes // if (r_roundImagesDown->integer == 2) { scaled_width = width; scaled_height = height; //for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) // ; //for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) // ; if ( scaled_width != width || scaled_height != height ) { resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height); data = resampledBuffer; width = scaled_width; height = scaled_height; } } else { for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) ; for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) ; if ( (r_roundImagesDown->integer == 1) && scaled_width > width ) scaled_width >>= 1; if ( (r_roundImagesDown->integer == 1) && scaled_height > height ) scaled_height >>= 1; if ( scaled_width != width || scaled_height != height ) { resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); if (hqresample) Image_Resample32Lerp(data, width, height, resampledBuffer, scaled_width, scaled_height - 1); else ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height); data = resampledBuffer; width = scaled_width; height = scaled_height; } } texsizex = glConfig.maxTextureSize; texsizey = glConfig.maxTextureSize; if (r_leifx->integer && !force32upload){ // leilei texsizex = 256; // 3dfx texsizey = 256; // 3dfx } // // perform optional picmip operation // if ( picmip ) { scaled_width >>= r_picmip->integer; scaled_height >>= r_picmip->integer; } // // leilei - icon picmip for certain 2d graphics elements that get wasted in lower resolutions, saving vram // if ( isicon ){ if (r_iconmip->integer){ // Auto-determine from resolution division if (r_iconmip->integer == 1){ int wadth, haght, dev = 0; wadth = floor(1280 / glConfig.vidWidth) - 1; haght = floor(960 / glConfig.vidHeight) - 1; if (wadth > haght) dev = wadth; else if (haght > wadth) dev = haght; if (dev < 0) dev = 0; scaled_width >>= dev; scaled_height >>= dev; if (scaled_width < 32) scaled_width = 32; if (scaled_height < 32) scaled_height = 32; } else // Force it { scaled_width >>= (r_iconmip->integer - 1); scaled_height >>= (r_iconmip->integer - 1); if (scaled_width < 16) scaled_width = 16; if (scaled_height < 16) scaled_height = 16; } } if (r_iconBits->integer){ forceBits = r_iconBits->integer; } } // leilei - lightmap color bits, for saving vram/tex cache if (lightMap){ if (r_lightmapBits->integer){ forceBits = r_lightmapBits->integer; force32upload = 0; } } // // clamp to minimum size // if (scaled_width < 1) { scaled_width = 1; } if (scaled_height < 1) { scaled_height = 1; } // // clamp to the current upper OpenGL limit // scale both axis down equally so we don't have to // deal with a half mip resampling // while ( scaled_width > texsizex || scaled_height > texsizey ) { scaled_width >>= 1; scaled_height >>= 1; } scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); // // 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; if (hackoperation == 1) { // leilei - additive to alpha for ( i = 0; i < c; i++ ) { int r, g, b; vec3_t rgb; float amplify; byte alfa = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); //byte alfa = (scan[i*4]+ scan[i*4 + 1]+ scan[i*4 + 2]) / 3; r = scan[i*4 + 0]; g = scan[i*4 + 1]; b = scan[i*4 + 2]; rgb[0] = r; rgb[1] = g; rgb[2] = b; VectorNormalize(rgb); r = rgb[0] * 256; g = rgb[1] * 256; b = rgb[2] * 256; if (r>255) r=255; if (g>255) g=255; if (b>255) b=255; if (scan[i*4 + 0] > r) r = scan[i*4 + 0]; if (scan[i*4 + 1] > g) g = scan[i*4 + 1]; if (scan[i*4 + 2] > b) b = scan[i*4 + 2]; scan[i*4 + 0] = r; scan[i*4 + 1] = g; scan[i*4 + 2] = b; scan[i*4 + 3] = alfa; } } else if (hackoperation == 2) { // leilei - alpha killing for ( i = 0; i < c; i++ ) { scan[i*4 + 3] = 255; } } else if(hackoperation == 3 ) // Subtractives { for ( i = 0; i < c; i++ ) { byte alfa = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); scan[i*4] = 0; scan[i*4 + 1] = 0; scan[i*4 + 2] = 0; scan[i*4 + 3] = alfa; } } else if (hackoperation == 4) // modulateply { for ( i = 0; i < c; i++ ) { int r, g, b; vec3_t rgb; float amplify; byte alfa = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); //byte alfa = (scan[i*4]+ scan[i*4 + 1]+ scan[i*4 + 2]) / 3; alfa = sin(alfa/64) * 32; amplify = (alfa - 128) / 255; r = scan[i*4 + 0]; g = scan[i*4 + 1]; b = scan[i*4 + 2]; rgb[0] = r; rgb[1] = g; rgb[2] = b; VectorNormalize(rgb); r = rgb[0] * 256; g = rgb[1] * 256; b = rgb[2] * 256; r *= amplify; g *= amplify; b *= amplify; if (r<0) r=0; if (g<0) g=0; if (b<0) b=0; if (r>255) r=255; if (g>255) g=255; if (b>255) b=255; if (scan[i*4 + 0] > r) r = scan[i*4 + 0]; if (scan[i*4 + 1] > g) g = scan[i*4 + 1]; if (scan[i*4 + 2] > b) b = scan[i*4 + 2]; scan[i*4 + 0] = r; scan[i*4 + 1] = g; scan[i*4 + 2] = b; scan[i*4 + 3] = alfa; } } if(r_textureDither->integer) // possibly the stupidest texture dithering ever { #ifdef DONTEVENTHINKABOUTTHIS int passes = r_textureDither->integer; int that; /* LEIFX FILTER C PORT PROTOTYPE SCRATCH AREA No vectors, only ints ints ints Obviously can be refactored with SSE */ #define FILTCAP (0.075 * 255) #define FILTCAPG (FILTCAP / 2) for (that=0;that FILTCAP ) rd = FILTCAP; if (gd > FILTCAPG) gd = FILTCAPG; if (bd > FILTCAP ) bd = FILTCAP; if (rd < -FILTCAP ) rd = -FILTCAP; if (gd < -FILTCAPG) gd = -FILTCAPG; if (bd < -FILTCAP ) bd = -FILTCAP; /* Add our Differences */ r1 += (rd/3); g1 += (gd/3); b1 += (bd/3); /* Obligatory clamping part */ if (r1 < 0) r1 = 0; if (g1 < 0) g1 = 0; if (b1 < 0) b1 = 0; if (r1 > 255) r1 = 255; if (g1 > 255) g1 = 255; if (b1 > 255) b1 = 255; if (r2 < 0) r2 = 0; if (g2 < 0) g2 = 0; if (b2 < 0) b2 = 0; if (r2 > 255) r2 = 255; if (g2 > 255) g2 = 255; if (b2 > 255) b2 = 255; /* Put processed image back into the buffer */ scan[i*4] = r1; scan[i*4 + 1] = g1; scan[i*4 + 2] = b1; } } #endif for ( i = 0; i < c; i++ ) { int ren = (crandom() * 4); int rg,gg,bb; rg = scan[i*4]; gg = scan[i*4+1]; bb = scan[i*4+2]; //if ((rg / 64) != ceil(rg / 64)) rg = scan[i*4] + ren; //if ((gg / 32) != ceil(gg / 32)) gg = scan[i*4 + 1] + (ren / 2); //if ((bb / 64) != ceil(bb / 64)) bb = scan[i*4 + 2] + ren; if (rg < 0) rg = 0; if (gg < 0) gg = 0; if (bb < 0) bb = 0; if (rg > 255) rg = 255; if (gg > 255) gg = 255; if (bb > 255) bb = 255; scan[i*4] = rg; scan[i*4 + 1] = gg; scan[i*4 + 2] = bb; } } if( r_greyscale->value ) { // leilei - replaced with saturation processing for ( i = 0; i < c; i++ ) { float saturated = (scan[i*4] * 0.333) + (scan[i*4 + 1] * 0.333) + (scan[i*4 + 2] * 0.333); scan[i*4] = saturated + (scan[i*4] - saturated) * (1-r_greyscale->value); scan[i*4 + 1] = saturated + (scan[i*4 + 1] - saturated) * (1-r_greyscale->value); scan[i*4 + 2] = saturated + (scan[i*4 + 2] - saturated) * (1-r_greyscale->value); if (scan[i*4] > 255) scan[i*4] = 255; if (scan[i*4 + 1] > 255) scan[i*4 + 1] = 255; if (scan[i*4 + 2] > 255) scan[i*4 + 2] = 255; } } if(lightMap) { if( r_monolightmaps->value ) { for ( i = 0; i < c; i++ ) { float saturated = (scan[i*4] * 0.333) + (scan[i*4 + 1] * 0.333) + (scan[i*4 + 2] * 0.333); scan[i*4] = saturated + (scan[i*4] - saturated) * (1-r_monolightmaps->value); scan[i*4 + 1] = saturated + (scan[i*4 + 1] - saturated) * (1-r_monolightmaps->value ); scan[i*4 + 2] = saturated + (scan[i*4 + 2] - saturated) * (1-r_monolightmaps->value ); if (scan[i*4] > 255) scan[i*4] = 255; if (scan[i*4 + 1] > 255) scan[i*4 + 1] = 255; if (scan[i*4 + 2] > 255) scan[i*4 + 2] = 255; } } if(r_greyscale->integer) internalFormat = GL_LUMINANCE; else if(r_monolightmaps->integer) internalFormat = GL_LUMINANCE; else internalFormat = GL_RGB; // leilei - lightmap color bits, for saving vram/tex cache if (r_lightmapBits->integer){ forceBits = r_lightmapBits->integer; force32upload = 0; if ( forceBits == 16) { internalFormat = GL_RGB5; } else if ( forceBits == 15) { internalFormat = GL_RGB5; } else if ( forceBits == 12) { internalFormat = GL_RGB4; } else if ( forceBits == 6) { internalFormat = GL_R3_G3_B2; } else if ( forceBits == 32) { internalFormat = GL_RGB8; } else { internalFormat = GL_RGB; } } } else { for ( i = 0; i < c; i++ ) { if ( scan[i*4+0] > rMax ) { 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(r_greyscale->integer) { if(r_texturebits->integer == 16 || forceBits == 16) internalFormat = GL_LUMINANCE8; else if(r_texturebits->integer == 32 || forceBits == 32) internalFormat = GL_LUMINANCE16; else internalFormat = GL_LUMINANCE; } else { if ( glConfig.textureCompression == TC_S3TC_ARB ) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; } else if ( glConfig.textureCompression == TC_S3TC ) { internalFormat = GL_RGB4_S3TC; } else if ( r_texturebits->integer == 16 || forceBits == 16) { internalFormat = GL_RGB5; } else if ( r_texturebits->integer == 15 || forceBits == 15) { internalFormat = GL_RGB5; } else if ( r_texturebits->integer == 12 || forceBits == 12) { internalFormat = GL_RGB4; } else if ( r_texturebits->integer == 6 || forceBits == 6) { internalFormat = GL_R3_G3_B2; } else if ( r_texturebits->integer == 32 || forceBits == 32) { internalFormat = GL_RGB8; } else { internalFormat = GL_RGB; } if (detailhack) internalFormat = GL_LUMINANCE; // leilei - use paletted mono format for detail textures if (force32upload) internalFormat = GL_RGB8; // leilei - gets bloom and postproc working on s3tc & 8bit & palettes if ((r_leifx->integer) && (!force32upload)) internalFormat = GL_RGB5; } } else if ( samples == 4 ) { if(r_greyscale->integer) { if(r_texturebits->integer == 16 || forceBits == 16) internalFormat = GL_LUMINANCE8_ALPHA8; else if(r_texturebits->integer == 32 || forceBits == 32) internalFormat = GL_LUMINANCE16_ALPHA16; else internalFormat = GL_LUMINANCE_ALPHA; } else { if ( glConfig.textureCompression == TC_S3TC_ARB ) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; // leilei - this was missing } else if ( r_texturebits->integer == 16 || forceBits == 16) { internalFormat = GL_RGBA4; } else if ( r_texturebits->integer == 15 || forceBits == 15) { internalFormat = GL_RGB5_A1; } else if ( r_texturebits->integer == 12 || forceBits == 12) { internalFormat = GL_RGBA4; } else if ( r_texturebits->integer == 6 || forceBits == 6) { internalFormat = GL_RGBA2; } else if ( r_texturebits->integer == 32 || forceBits == 32) { internalFormat = GL_RGBA8; } else { internalFormat = GL_RGBA; } if (force32upload) internalFormat = GL_RGBA8; // leilei - gets bloom and postproc working on s3tc & 8bit & palettes if ((r_leifx->integer) && (!force32upload)) internalFormat = GL_RGBA4; } } } if (depthimage) { mipmap=0; internalFormat = GL_DEPTH_COMPONENT; temp_GLformat=GL_DEPTH_COMPONENT; temp_GLtype=GL_FLOAT; } else { temp_GLformat=GL_RGBA; temp_GLtype=GL_UNSIGNED_BYTE; } // copy or resample data as appropriate for first MIP level if ( ( scaled_width == width ) && ( scaled_height == height ) ) { if (!mipmap) { qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, temp_GLformat, temp_GLtype, data); *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; *format = internalFormat; goto done; } Com_Memcpy (scaledBuffer, data, width*height*4); } else { // use the normal mip-mapping function to go down from here while ( width > scaled_width || height > scaled_height ) { R_MipMap( (byte *)data, width, height ); width >>= 1; height >>= 1; if ( width < 1 ) { width = 1; } if ( height < 1 ) { height = 1; } } Com_Memcpy( scaledBuffer, data, width * height * 4 ); } R_LightScaleTexture (scaledBuffer, scaled_width, scaled_height, !mipmap ); *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; *format = internalFormat; qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, temp_GLformat, temp_GLtype, scaledBuffer ); if (mipmap) { int miplevel; miplevel = 0; while (scaled_width > 1 || scaled_height > 1) { R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height ); scaled_width >>= 1; scaled_height >>= 1; if (scaled_width < 1) scaled_width = 1; if (scaled_height < 1) scaled_height = 1; miplevel++; if ( r_colorMipLevels->integer ) { R_BlendOverTexture( (byte *)scaledBuffer, scaled_width * scaled_height, mipBlendColors[miplevel] ); } if (detailhack) // leilei - blend detail textures to gray to defeat pattern effects in distances { R_BlendToGray( (byte *)scaledBuffer, scaled_width * scaled_height, miplevel ); } qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, temp_GLformat, temp_GLtype, scaledBuffer ); } } done: if (mipmap) { if ( textureFilterAnisotropic ) qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) ); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); } else { if ( textureFilterAnisotropic ) qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } if (depthimage) { glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glTexParameteri (GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_ALPHA); } if (softwaremode) { // leilei - software speedup qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); } GL_CheckErrors(); if ( scaledBuffer != 0 ) ri.Hunk_FreeTempMemory( scaledBuffer ); if ( resampledBuffer != 0 ) ri.Hunk_FreeTempMemory( resampledBuffer ); } // // leilei - paletted texture support START // static void Upload8( unsigned *data, int width, int height, qboolean mipmap, qboolean picmip, qboolean lightMap, int *format, int *pUploadWidth, int *pUploadHeight ) { int samples; unsigned *scaledBuffer = NULL; unsigned *resampledBuffer = NULL; unsigned char *palettedBuffer = NULL; int scaled_width, scaled_height; int i, c; byte *scan; GLenum internalFormat = GL_RGB; GLenum temp_GLformat = GL_RGBA; GLenum temp_GLtype = GL_UNSIGNED_BYTE; int texsizex, texsizey; // leilei int superfactor = 1; int isalphaedrgba = 0; // for cards that SCUK at it //unsigned char *data8; // // convert to exact power of 2 sizes // for (scaled_width = 1 ; scaled_width < width ; scaled_width<<=1) ; for (scaled_height = 1 ; scaled_height < height ; scaled_height<<=1) ; if ( r_roundImagesDown->integer && scaled_width > width ) scaled_width >>= 1; if ( r_roundImagesDown->integer && scaled_height > height ) scaled_height >>= 1; scaled_width *= superfactor; scaled_height *= superfactor; if ( scaled_width != width || scaled_height != height ) { resampledBuffer = ri.Hunk_AllocateTempMemory( scaled_width * scaled_height * 4 ); ResampleTexture (data, width, height, resampledBuffer, scaled_width, scaled_height); data = resampledBuffer; width = scaled_width; height = scaled_height; } // // perform optional picmip operation // if ( picmip ) { scaled_width >>= r_picmip->integer; scaled_height >>= r_picmip->integer; } // // clamp to minimum size // if (scaled_width < 1) { scaled_width = 1; } if (scaled_height < 1) { scaled_height = 1; } // // clamp to the current upper OpenGL limit // scale both axis down equally so we don't have to // deal with a half mip resampling // texsizex = glConfig.maxTextureSize; texsizey = glConfig.maxTextureSize; if ((r_leifx->integer) && (!force32upload)){ // leilei texsizex = 256; // 3dfx texsizey = 256; // 3dfx } while ( scaled_width > texsizex || scaled_height > texsizey ) { scaled_width >>= 1; scaled_height >>= 1; } scaledBuffer = ri.Hunk_AllocateTempMemory( sizeof( unsigned ) * scaled_width * scaled_height ); // // 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; // LEILEI - paletted texturing hack // Check for an alpha for ( i = 0; i < c; i++ ) { int a; a = scan[i*4 +3]; a *= 1.9; a /= 255; a *= 255; } if (paletteability) R_PickTexturePalette(1); if (paletteability && !isalphaedrgba) // Preparing for native upload { for ( i = 0; i < c; i++ ) { int thecol; int r, g, b, a; r = scan[i*4]; g = scan[i*4 +1]; b = scan[i*4 +2]; a = scan[i*4 +3]; thecol = palmap[r>>3][g>>3][b>>3]; a *= 1.9; a /= 255; a *= 255; if (!a){ thecol = 255; // transparent color } scan[i] = thecol; } } else // Preparing for simulated RGBA upload { for ( i = 0; i < c; i++ ) { int thecol; int r, g, b, a; unsigned char *pix24; r = scan[i*4]; g = scan[i*4 +1]; b = scan[i*4 +2]; a = scan[i*4 +3]; thecol = palmap[r>>3][g>>3][b>>3]; a *= 1.9; a /= 255; a *= 255; pix24 = (unsigned char *)&d_8to24table[thecol]; if (!a){ thecol = 255; // transparent color samples = 4; } scan[i*4] = pix24[0]; scan[i*4+1] = pix24[1]; scan[i*4+2] = pix24[2]; scan[i*4+3] = a; } } { // select proper internal format if ( samples == 3 ) { internalFormat = GL_RGB; } else if ( samples == 4 ) { internalFormat = GL_RGBA; } } // copy or resample data as appropriate for first MIP level if ( ( scaled_width == width ) && ( scaled_height == height ) ) { if (!mipmap) { if (paletteability && !isalphaedrgba) qglTexImage2D( GL_TEXTURE_2D, 0, palettedformat, scaled_width, scaled_height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, data); else qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, temp_GLformat, temp_GLtype, data); *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; *format = internalFormat; goto done; } if (!paletteability || isalphaedrgba) Com_Memcpy (scaledBuffer, data, width*height*4); else Com_Memcpy (scaledBuffer, data, width*height); } else { // use the normal mip-mapping function to go down from here while ( width > scaled_width || height > scaled_height ) { if (paletteability && !isalphaedrgba) R_MipMap8( (byte *)data, width, height ); else R_MipMap( (byte *)data, width, height ); width >>= 1; height >>= 1; if ( width < 1 ) { width = 1; } if ( height < 1 ) { height = 1; } } if (!paletteability || isalphaedrgba) Com_Memcpy( scaledBuffer, data, width * height * 4 ); else Com_Memcpy( scaledBuffer, data, width * height); } *pUploadWidth = scaled_width; *pUploadHeight = scaled_height; *format = internalFormat; if (paletteability && !isalphaedrgba) qglTexImage2D( GL_TEXTURE_2D, 0, palettedformat, scaled_width, scaled_height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, scaledBuffer ); else qglTexImage2D (GL_TEXTURE_2D, 0, internalFormat, scaled_width, scaled_height, 0, temp_GLformat, temp_GLtype, scaledBuffer ); if (mipmap) { int miplevel; miplevel = 0; while (scaled_width > 1 || scaled_height > 1) { if (paletteability && !isalphaedrgba) R_MipMap8( (byte *)scaledBuffer, scaled_width, scaled_height ); else R_MipMap( (byte *)scaledBuffer, scaled_width, scaled_height ); scaled_width >>= 1; scaled_height >>= 1; if (scaled_width < 1) scaled_width = 1; if (scaled_height < 1) scaled_height = 1; miplevel++; if (paletteability && !isalphaedrgba) qglTexImage2D( GL_TEXTURE_2D, miplevel, palettedformat, scaled_width, scaled_height, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, scaledBuffer ); else qglTexImage2D (GL_TEXTURE_2D, miplevel, internalFormat, scaled_width, scaled_height, 0, temp_GLformat, temp_GLtype, scaledBuffer ); } } done: if (mipmap) { if ( textureFilterAnisotropic ) qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (GLint)Com_Clamp( 1, maxAnisotropy, r_ext_max_anisotropy->integer ) ); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); } else { if ( textureFilterAnisotropic ) qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1 ); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } if (softwaremode) { // leilei - software speedup qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); } GL_CheckErrors(); if ( scaledBuffer != 0 ) ri.Hunk_FreeTempMemory( scaledBuffer ); if ( resampledBuffer != 0 ) ri.Hunk_FreeTempMemory( resampledBuffer ); if ( palettedBuffer != 0 ) ri.Hunk_FreeTempMemory( palettedBuffer ); } // // leilei - paletted texture support END // /* ================ R_CreateImage This is the only way any image_t are created ================ */ image_t *R_CreateImage( const char *name, byte *pic, int width, int height, imgType_t type, imgFlags_t flags, int internalFormat ) { image_t *image; qboolean isLightmap = qfalse; long hash; int glWrapClampMode; float oldtime = backEnd.refdef.floatTime; if (strlen(name) >= MAX_QPATH ) { ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); } if ( !strncmp( name, "*lightmap", 9 ) ) { isLightmap = qtrue; } if ( tr.numImages == MAX_DRAWIMAGES ) { ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit"); } image = tr.images[tr.numImages] = ri.Hunk_Alloc( sizeof( image_t ), h_low ); image->texnum = 1024 + tr.numImages; tr.numImages++; image->type = type; image->flags = flags; strcpy (image->imgName, name); image->width = width; image->height = height; if (flags & IMGFLAG_CLAMPTOEDGE) glWrapClampMode = GL_CLAMP_TO_EDGE; else glWrapClampMode = GL_REPEAT; // 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); // leilei - texture dumping if (r_texdump->integer){ COM_StripExtension( name, dumpname, MAX_QPATH ); // leilei - transfer name for texdump DumpTex( (unsigned *)pic, image->width, image->height); } if (paletteavailable && r_texturebits->integer == 8 && !isLightmap && !depthimage && !force32upload) Upload8( (unsigned *)pic, image->width, image->height, image->flags & IMGFLAG_MIPMAP, image->flags & IMGFLAG_PICMIP, isLightmap, &image->internalFormat, &image->uploadWidth, &image->uploadHeight ); else Upload32( (unsigned *)pic, image->width, image->height, image->flags & IMGFLAG_MIPMAP, image->flags & IMGFLAG_PICMIP, isLightmap, &image->internalFormat, &image->uploadWidth, &image->uploadHeight ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, glWrapClampMode ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, glWrapClampMode ); // FIXME: this stops fog from setting border color? glState.currenttextures[glState.currenttmu] = 0; qglBindTexture( GL_TEXTURE_2D, 0 ); if ( image->TMU == 1 ) { GL_SelectTexture( 0 ); } // leilei - map texture listing hack image->maptexture = ismaptexture; hash = generateHashValue(name); image->next = hashTable[hash]; hashTable[hash] = image; image->procTime = backEnd.refdef.floatTime - oldtime; return image; } //=================================================================== typedef struct { char *ext; void (*ImageLoader)( const char *, unsigned char **, int *, int * ); } imageExtToLoaderMap_t; // Note that the ordering indicates the order of preference used // when there are multiple images of different formats available static imageExtToLoaderMap_t imageLoaders[ ] = { { "tga", R_LoadTGA }, { "jpg", R_LoadJPG }, { "jpeg", R_LoadJPG }, { "png", R_LoadPNG }, { "pcx", R_LoadPCX }, { "bmp", R_LoadBMP } }; static int numImageLoaders = ARRAY_LEN( imageLoaders ); /* ================= R_LoadImage Loads any of the supported image types into a cannonical 32 bit format. ================= */ void R_LoadImage( const char *name, byte **pic, int *width, int *height ) { qboolean orgNameFailed = qfalse; int orgLoader = -1; int i; char localName[ MAX_QPATH ]; const char *ext; char *altName; *pic = NULL; *width = 0; *height = 0; Q_strncpyz( localName, name, MAX_QPATH ); ext = COM_GetExtension( localName ); if( *ext ) { // Look for the correct loader and use it for( i = 0; i < numImageLoaders; i++ ) { if( !Q_stricmp( ext, imageLoaders[ i ].ext ) ) { // Load imageLoaders[ i ].ImageLoader( localName, pic, width, height ); break; } } // A loader was found if( i < numImageLoaders ) { if( *pic == NULL ) { // Loader failed, most likely because the file isn't there; // try again without the extension orgNameFailed = qtrue; orgLoader = i; COM_StripExtension( name, localName, MAX_QPATH ); } else { // Something loaded return; } } } // Try and find a suitable match using all // the image formats supported for( i = 0; i < numImageLoaders; i++ ) { if (i == orgLoader) continue; altName = va( "%s.%s", localName, imageLoaders[ i ].ext ); // Load imageLoaders[ i ].ImageLoader( altName, pic, width, height ); if( *pic ) { if( orgNameFailed ) { ri.Printf( PRINT_DEVELOPER, "WARNING: %s not present, using %s instead\n", name, altName ); } break; } } } /* =============== R_FindImageFile Finds or loads the given image. Returns NULL if it fails, not a default image. ============== */ image_t *R_FindImageFile( const char *name, imgType_t type, imgFlags_t flags ) { image_t *image; int width, height; int dontgotsafe; byte *pic; long hash; float oldtime; float loadtime; float proctime; if (!name) { return NULL; } detailhack = 0; hash = generateHashValue(name); // // see if the image is already loaded // for (image=hashTable[hash]; image; image=image->next) { if ( !strcmp( name, image->imgName ) ) { // the white image can be used with any set of parms, but other mismatches are errors if ( strcmp( name, "*white" ) ) { if ( image->flags != flags ) { ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed flags (%i vs %i)\n", name, image->flags, flags ); } } return image; } } // leilei - Detail texture hack // to kill artifacts of shimmer of pattern of terrible if ( !Q_strncmp( name, "textures/detail/", 16 ) || !Q_strncmp( name, "gfx/fx/detail/", 14 )) { ri.Printf( PRINT_DEVELOPER, "DETAILHACK: %s - mips will be gray\n", name ); detailhack = 1; // leilei - attempt to fade detail mips to gray, EXPECTS DST_COLOR/SRC_COLOR for this to work right } // leilei - iconmip hack if ( !Q_strncmp( name, "icons/", 5 ) || ((!Q_strncmp( name, "gfx/2d", 6 )) && (Q_strncmp( name, "gfx/2d/bigchars", 14 )))){ isicon = 1; } else isicon = 0; // // load the pic from disk // oldtime = ri.Milliseconds() * 100; dontgotsafe = 0; if (r_suggestiveThemes->integer == 1) dontgotsafe = 1; // leilei - load safe or lewd textures if desired. if (!Q_strncmp( name, "models/player", 13) ){ if (r_suggestiveThemes->integer < 1){ char narm[ MAX_QPATH ]; COM_StripExtension( name, narm, MAX_QPATH ); R_LoadImage( va("%s_safe", narm), &pic, &width, &height ); if ( pic == NULL ) dontgotsafe = 1; else dontgotsafe = 0; } else if (r_suggestiveThemes->integer > 1){ char narm[ MAX_QPATH ]; COM_StripExtension( name, narm, MAX_QPATH ); R_LoadImage( va("%s_lewd", narm), &pic, &width, &height ); if ( pic == NULL ) dontgotsafe = 1; else dontgotsafe = 0; } } else { dontgotsafe = 1; } //oldtime = backEnd.refdef.floatTime; if (dontgotsafe){ R_LoadImage( name, &pic, &width, &height ); if ( pic == NULL ) { return NULL; } } loadtime = (ri.Milliseconds() * 100) - oldtime; // loadtime = backEnd.refdef.floatTime - oldtime; // leilei - if we need to change the texture upload with a special image prefix to separate from differently blended things if (hackoperation) { char hackName[MAX_QPATH]; char *hackedName; COM_StripExtension( name, hackName, MAX_QPATH ); if(hackoperation==1) hackedName = va("%shackadd", hackName); if(hackoperation==2) hackedName = va("%shacknob", hackName); if(hackoperation==3) hackedName = va("%shacksub", hackName); if(hackoperation==4) hackedName = va("%shackmod", hackName); else hackedName = va("%shackblend", hackName); image = R_CreateImage( ( char * ) hackedName, pic, width, height, type, flags, 0 ); } else { image = R_CreateImage( ( char * ) name, pic, width, height, type, flags, 0 ); } ri.Free( pic ); image->loadTime = loadtime; return image; } /* =============== R_FindImageFileIfItsThere leilei Finds the given image and does not load it. ============== */ image_t *R_FindImageFileIfItsThere( const char *name, imgType_t type, imgFlags_t flags ) { image_t *image; int width, height; byte *pic; long hash; if (!name) { return NULL; } detailhack = 0; hash = generateHashValue(name); // // see if the image is already loaded // for (image=hashTable[hash]; image; image=image->next) { if ( !strcmp( name, image->imgName ) ) { // the white image can be used with any set of parms, but other mismatches are errors if ( strcmp( name, "*white" ) ) { if ( image->flags != flags ) { ri.Printf( PRINT_DEVELOPER, "WARNING: reused image %s with mixed flags (%i vs %i)\n", name, image->flags, flags ); } } return image; } } // leilei - Detail texture hack // to kill artifacts of shimmer of pattern of terrible if ( !Q_strncmp( name, "textures/detail/", 16 ) || !Q_strncmp( name, "gfx/fx/detail/", 14 )) { ri.Printf( PRINT_DEVELOPER, "DETAILHACK: %s - mips will be gray\n", name ); detailhack = 1; // leilei - attempt to fade detail mips to gray, EXPECTS DST_COLOR/SRC_COLOR for this to work right } // // load the pic from disk // R_LoadImage( name, &pic, &width, &height ); if ( pic == NULL ) { return NULL; } image = R_CreateImage( ( char * ) name, pic, width, height, type, flags, 0 ); ri.Free( pic ); ismaptexture = 0; 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 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, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 ); // tr.dlightImage = R_CreateImage("*dlight", (byte *)data, DLIGHT_SIZE, DLIGHT_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE, 0 ); } /* ================= 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 d; float borderColor[4]; force32upload = 1; // leilei - paletted fog fix data = ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); // S is distance, T is depth for (x=0 ; xinteger; if ( !glConfig.deviceSupportsGamma && !r_alternateBrightness->integer) { tr.overbrightBits = 0; // need hardware gamma for overbright } // never overbright in windowed mode if ( !glConfig.isFullscreen && !r_alternateBrightness->integer) { tr.overbrightBits = 0; } // allow 2 overbright bits in 24 bit, but only 1 in 16 bit if ( glConfig.colorBits > 16 ) { if ( tr.overbrightBits > 2 ) { tr.overbrightBits = 2; } } else { 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 ) { 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; if (r_alternateBrightness->integer != 1) // leilei - don't do the shift to the brightness when we do alternate. This allows shift = tr.overbrightBits; // hardware gamma to work (if available) since we can't do alternate gamma via blends else shift = 0; // don't 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 && r_alternateBrightness->integer != 2) { GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); } } /* =============== R_InitImages =============== */ void R_InitImages( void ) { Com_Memset(hashTable, 0, sizeof(hashTable)); // build brightness translation tables R_SetColorMappings(); // leilei - paletted texture support if (r_texturebits->integer == 8) R_InitPalette(); // create default texture and white texture R_CreateBuiltinImages(); } /* =============== R_DeleteTextures =============== */ void R_DeleteTextures( void ) { int i; for ( i=0; itexnum ); } Com_Memset( tr.images, 0, sizeof( tr.images ) ); tr.numImages = 0; Com_Memset( glState.currenttextures, 0, sizeof( glState.currenttextures ) ); if ( qglActiveTextureARB ) { GL_SelectTexture( 1 ); qglBindTexture( GL_TEXTURE_2D, 0 ); GL_SelectTexture( 0 ); qglBindTexture( GL_TEXTURE_2D, 0 ); } else { qglBindTexture( GL_TEXTURE_2D, 0 ); } } /* ============================================================================ 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) { // ri.Printf (PRINT_DEVELOPER, "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; union { char *c; void *v; } text; char *text_p; char *token; char surfName[MAX_QPATH]; if ( !name || !name[0] ) { ri.Printf( PRINT_DEVELOPER, "Empty name passed to RE_RegisterSkin\n" ); return 0; } if ( strlen( name ) >= MAX_QPATH ) { ri.Printf( PRINT_DEVELOPER, "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 = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); tr.skins[hSkin] = skin; Q_strncpyz( skin->name, name, sizeof( skin->name ) ); skin->numSurfaces = 0; R_IssuePendingRenderCommands(); // If not a .skin file, load as a single shader if ( strcmp( name + strlen( name ) - 5, ".skin" ) ) { skin->numSurfaces = 1; skin->surfaces[0] = ri.Hunk_Alloc( sizeof(skin->surfaces[0]), h_low ); skin->surfaces[0]->shader = R_FindShader( name, LIGHTMAP_NONE, qtrue ); return hSkin; } // load and parse the skin file ri.FS_ReadFile( name, &text.v ); if ( !text.c ) { return 0; } text_p = text.c; 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 ); if ( skin->numSurfaces >= MD3_MAX_SURFACES ) { ri.Printf( PRINT_WARNING, "WARNING: Ignoring surfaces in '%s', the max is %d surfaces!\n", name, MD3_MAX_SURFACES ); break; } surf = skin->surfaces[ skin->numSurfaces ] = ri.Hunk_Alloc( sizeof( *skin->surfaces[0] ), h_low ); Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); surf->shader = R_FindShader( token, LIGHTMAP_NONE, qtrue ); skin->numSurfaces++; } ri.FS_FreeFile( text.v ); // never let a skin have 0 shaders if ( skin->numSurfaces == 0 ) { return 0; // use default skin } return hSkin; } /* =============== R_InitSkins =============== */ void R_InitSkins( void ) { skin_t *skin; tr.numSkins = 1; // make the default skin have all default shaders skin = tr.skins[0] = ri.Hunk_Alloc( sizeof( skin_t ), h_low ); Q_strncpyz( skin->name, "", sizeof( skin->name ) ); skin->numSurfaces = 1; skin->surfaces[0] = 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 ]; } /* =============== 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"); }