/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2005 - 2015, ioquake3 contributors Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program 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 this program; if not, see . =========================================================================== */ // tr_image.c #include "tr_local.h" #include "../rd-common/tr_common.h" #include #include using namespace std; #if !defined(GL_RGB5) #define GL_RGB5 0x8050 // same as GL_RGB5_EXT #endif #if !defined(GL_RGBA4) #define GL_RGBA4 0x8056 // same as GL_RGBA4_EXT and GL_RGBA4_OES #endif 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 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 textureMode_s { const 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} }; static const size_t numTextureModes = ARRAY_LEN(modes); /* ================ return a hash value for the filename ================ */ 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; } // makeup a nice clean, consistant name to query for and file under, for map<> usage... // char *GenerateImageMappingName( const char *name ) { static char sName[MAX_QPATH]; int i=0; char letter; while (name[i] != '\0' && ivalue > glConfig.maxTextureFilterAnisotropy ) ri.Cvar_SetValue( "r_ext_texture_filter_anisotropic", glConfig.maxTextureFilterAnisotropy ); // 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.maxTextureFilterAnisotropy>0) { if(r_ext_texture_filter_anisotropic->integer>1) { qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value); } 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 " #ifdef HAVE_GLES return 3; #else return glConfig.colorBits/8.0f; #endif break; case 4: //"RGBA " #ifdef HAVE_GLES return 4; #else return glConfig.colorBits/8.0f; break; case GL_RGBA4: //"RGBA4" return 2; break; case GL_RGB5: //"RGB5 " return 2; #endif break; #ifndef HAVE_GLES 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; #endif 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->width * pImage->height); } else { total += pImage->width * pImage->height; } } } 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- -if-- wrap --name-------\n"); int iNumImages = R_Images_StartIteration(); while ( (image = R_Images_GetNextIteration()) != NULL) { texels += image->width*image->height; texBytes += image->width*image->height * R_BytesPerTex (image->internalFormat); ri.Printf( PRINT_ALL, "%4i: %4i %4i %s ", i, image->width, image->height, yesno[image->mipmap] ); 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; #ifndef HAVE_GLES 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; #endif default: ri.Printf( PRINT_ALL, "???? " ); } switch ( image->wrapClampMode ) { case GL_REPEAT: ri.Printf( PRINT_ALL, "rept " ); break; #ifndef HAVE_GLES case GL_CLAMP: ri.Printf( PRINT_ALL, "clmp " ); break; #endif 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- -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> 1; outHeight = inHeight >> 1; temp = (unsigned int *)R_Malloc( outWidth * outHeight * 4, TAG_TEMP_WORKSPACE, qfalse ); 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; } } } memcpy( in, temp, outWidth * outHeight * 4 ); R_Free( 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; } } } /* ================== 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}, }; #ifdef HAVE_GLES // helper function for GLES format conversions byte * gles_convertRGB(byte * data, int width, int height) { byte * temp = (byte *) R_Malloc (width*height*3, TAG_TEMP_WORKSPACE, qfalse); byte *src = data; byte *dst = temp; for (int i=0; i> 8) & 0xff; unsigned int b = (pixel >> 16) & 0xff; unsigned int a = (pixel >> 24) & 0xff; // Convert to 4 bit vales r >>= 4; g >>= 4; b >>= 4; a >>= 4; output[i] = r << 12 | g << 8 | b << 4 | a; } return temp; } byte * gles_convertRGB5(byte * data, int width, int height) { byte * temp = (byte *) R_Malloc (width*height*2, TAG_TEMP_WORKSPACE, qfalse); byte *src = data; byte *dst = temp; byte r,g,b; unsigned int * input = ( unsigned int *)(data); unsigned short* output = (unsigned short*)(temp); for (int i = 0; i < width*height; i++) { unsigned int pixel = input[i]; // Unpack the source data as 8 bit values unsigned int r = pixel & 0xff; unsigned int g = (pixel >> 8) & 0xff; unsigned int b = (pixel >> 16) & 0xff; // Convert to 4 bit vales r >>= 3; g >>= 2; b >>= 3; output[i] = r << 11 | g << 5 | b; } return temp; } byte * gles_convertLuminance(byte * data, int width, int height) { byte * temp = (byte *) R_Malloc (width*height, TAG_TEMP_WORKSPACE, qfalse); byte *src = data; byte *dst = temp; byte r,g,b; int i; unsigned int * input = ( unsigned int *)(data); byte* output = (byte*)(temp); for (i = 0; i < width*height; i++) { unsigned int pixel = input[i]; // Unpack the source data as 8 bit values unsigned int r = pixel & 0xff; output[i] = r; } return temp; } byte * gles_convertLuminanceAlpha(byte * data, int width, int height) { byte * temp = (byte *) R_Malloc (width*height*2, TAG_TEMP_WORKSPACE, qfalse); byte *src = data; byte *dst = temp; byte r,g,b; int i; unsigned int * input = ( unsigned int *)(data); unsigned short* output = (unsigned short*)(temp); for (i = 0; i < width*height; i++) { unsigned int pixel = input[i]; // Unpack the source data as 8 bit values unsigned int r = pixel & 0xff; unsigned int a = (pixel >> 24) & 0xff; output[i] = r | a<<8; } return temp; } #endif class CStringComparator { public: bool operator()(const char *s1, const char *s2) const { return(strcmp(s1, s2) < 0); } }; typedef map 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) { qglDeleteTextures( 1, &pImage->texnum ); R_Free(pImage); } } /* =============== Upload32 =============== */ extern qboolean charSet; static void Upload32( unsigned *data, GLenum format, qboolean mipmap, qboolean picmip, qboolean isLightmap, qboolean allowTC, int *pformat, word *pUploadWidth, word *pUploadHeight, bool bRectangle = false ) { GLuint uiTarget = GL_TEXTURE_2D; #ifndef HAVE_GLES if ( bRectangle ) { uiTarget = GL_TEXTURE_RECTANGLE_EXT; } #else //Humm.. #endif if (format == GL_RGBA) { int samples; int i, c; byte *scan; float rMax = 0, gMax = 0, bMax = 0; int width = *pUploadWidth; int height = *pUploadHeight; // // perform optional picmip operation // if ( picmip ) { for(i = 0; i < r_picmip->integer; i++) { if ((width>16) && (height>16)) { R_MipMap( (byte *)data, width, height ); width >>= 1; height >>= 1; if (width < 1) { width = 1; } if (height < 1) { 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 ( width > glConfig.maxTextureSize || height > glConfig.maxTextureSize ) { R_MipMap( (byte *)data, width, height ); width >>= 1; height >>= 1; } // // 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; 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 ( glConfig.textureCompression == TC_S3TC && allowTC ) { #ifdef HAVE_GLES assert(0); #else *pformat = GL_RGB4_S3TC; #endif } else if ( glConfig.textureCompression == TC_S3TC_DXT && allowTC ) { // Compress purely color - no alpha if ( r_texturebits->integer == 16 ) { #ifdef HAVE_GLES assert(0); #else *pformat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; //this format cuts to 16 bit #endif } else {//if we aren't using 16 bit then, use 32 bit compression #ifdef HAVE_GLES assert(0); #else *pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; #endif } } else if ( isLightmap && r_texturebitslm->integer > 0 ) { int lmBits = r_texturebitslm->integer & 0x30; // 16 or 32 // Allow different bit depth when we are a lightmap if ( lmBits == 16 ) *pformat = GL_RGB5; else #ifdef HAVE_GLES *pformat = GL_RGB; #else *pformat = GL_RGB8; #endif } else if ( r_texturebits->integer == 16 ) { *pformat = GL_RGB5; } else if ( r_texturebits->integer == 32 ) { #ifdef HAVE_GLES *pformat = GL_RGB; #else *pformat = GL_RGB8; #endif } else { #ifdef HAVE_GLES *pformat = GL_RGB; #else *pformat = 3; #endif } } else if ( samples == 4 ) { if ( glConfig.textureCompression == TC_S3TC_DXT && allowTC) { // Compress both alpha and color #ifdef HAVE_GLES assert(0); #else *pformat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; #endif } else if ( r_texturebits->integer == 16 ) { *pformat = GL_RGBA4; } else if ( r_texturebits->integer == 32 ) { #ifdef HAVE_GLES *pformat = GL_RGBA; #else *pformat = GL_RGBA8; #endif } else { #ifdef HAVE_GLES *pformat = GL_RGBA; #else *pformat = 4; #endif } } *pUploadWidth = width; *pUploadHeight = height; // copy or resample data as appropriate for first MIP level #ifdef HAVE_GLES #ifdef PANDORA // SGX Workaround#2 : if width or height<=16 => make it bigger #endif //*pformat = GL_RGBA; R_LightScaleTexture (data, width, height, mipmap?qfalse:qtrue ); glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, (mipmap)?GL_TRUE:GL_FALSE ); // and now, convert if needed and upload // GLES doesn't do convertion itself, so we have to handle that byte *temp; switch ( *pformat ) { case GL_RGB5: temp = gles_convertRGB5((byte*)data, width, height); qglTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, temp); R_Free(temp); break; case GL_RGBA4: temp = gles_convertRGBA4((byte*)data, width, height); qglTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, temp); R_Free(temp); break; case 3: case GL_RGB: temp = gles_convertRGB((byte*)data, width, height); qglTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, temp); R_Free(temp); break; case 1: temp = gles_convertLuminance((byte*)data, width, height); qglTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE, width, height, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, temp); R_Free(temp); break; case 2: temp = gles_convertLuminanceAlpha((byte*)data, width, height); qglTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, width, height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, temp); R_Free(temp); break; default: *pformat = GL_RGBA; qglTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); } // qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); // qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); #else if (!mipmap) { qglTexImage2D( uiTarget, 0, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); goto done; } R_LightScaleTexture (data, width, height, (qboolean)!mipmap ); qglTexImage2D( uiTarget, 0, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); if (mipmap) { int miplevel; miplevel = 0; while (width > 1 || height > 1) { R_MipMap( (byte *)data, width, height ); width >>= 1; height >>= 1; if (width < 1) width = 1; if (height < 1) height = 1; miplevel++; if ( r_colorMipLevels->integer ) { R_BlendOverTexture( (byte *)data, width * height, mipBlendColors[miplevel] ); } qglTexImage2D( uiTarget, miplevel, *pformat, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data ); } } #endif } else { } done: if (mipmap) { qglTexParameterf(uiTarget, GL_TEXTURE_MIN_FILTER, gl_filter_min); qglTexParameterf(uiTarget, GL_TEXTURE_MAG_FILTER, gl_filter_max); if(r_ext_texture_filter_anisotropic->integer>1 && glConfig.maxTextureFilterAnisotropy>0) { qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value ); } } else { qglTexParameterf(uiTarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf(uiTarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } GL_CheckErrors(); } static void GL_ResetBinds(void) { 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 ); } } // special function used in conjunction with "devmapbsp"... // // (avoid using ri.xxxx stuff here in case running on dedicated) // void R_Images_DeleteLightMaps(void) { for (AllocatedImages_t::iterator itImage = AllocatedImages.begin(); itImage != AllocatedImages.end(); /* empty */) { image_t *pImage = (*itImage).second; if (pImage->imgName[0] == '*' && strstr(pImage->imgName,"lightmap")) // loose check, but should be ok { R_Images_DeleteImageContents(pImage); AllocatedImages.erase(itImage++); } else { ++itImage; } } 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->width, pImage->height, pImage->imgName); ri.Printf( PRINT_DEVELOPER, S_COLOR_RED ", levused %d",pImage->iLastLevelUsedOn); ri.Printf( PRINT_ALL, "\n"); iTexels += pImage->width * pImage->height; 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, S_COLOR_RED "RE_RegisterMedia_GetLevel(): %d",RE_RegisterMedia_GetLevel()); } // currently, this just goes through all the images and dumps any not referenced on this level... // qboolean RE_RegisterImages_LevelLoadEnd(void) { ri.Printf( PRINT_DEVELOPER, S_COLOR_RED "RE_RegisterImages_LevelLoadEnd():\n"); // int iNumImages = AllocatedImages.size(); // more for curiosity, really. qboolean imageDeleted = qtrue; for (AllocatedImages_t::iterator itImage = AllocatedImages.begin(); itImage != AllocatedImages.end(); /* blank */) { qboolean bEraseOccured = qfalse; image_t *pImage = (*itImage).second; // don't un-register system shaders (*fog, *dlight, *white, *default), but DO de-register lightmaps ("*/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, S_COLOR_RED "Dumping image \"%s\"\n",pImage->imgName); R_Images_DeleteImageContents(pImage); AllocatedImages.erase(itImage++); bEraseOccured = qtrue; imageDeleted = qtrue; } } if ( !bEraseOccured ) { ++itImage; } } // 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_ALL, S_COLOR_YELLOW "Level uses %d images, old limit was MAX_DRAWIMAGES (%d)\n", iNumImages, MAX_DRAWIMAGES); // } ri.Printf( PRINT_DEVELOPER, S_COLOR_RED "RE_RegisterImages_LevelLoadEnd(): Ok\n"); GL_ResetBinds(); return imageDeleted; } // 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_ALL, S_COLOR_YELLOW "WARNING: reused image %s with mixed mipmap parm\n", pName ); } if ( pImage->allowPicmip != !!allowPicmip ) { ri.Printf( PRINT_ALL, S_COLOR_YELLOW "WARNING: reused image %s with mixed allowPicmip parm\n", pName ); } if ( pImage->wrapClampMode != glWrapClampMode ) { ri.Printf( PRINT_ALL, S_COLOR_YELLOW "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, GLenum format, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode, bool bRectangle ) { image_t *image; qboolean isLightmap = qfalse; if (strlen(name) >= MAX_QPATH ) { Com_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] == '*') { const char *psLightMapNameSearchPos = strrchr(name,'/'); if ( psLightMapNameSearchPos && !strncmp( psLightMapNameSearchPos+1, "lightmap", 8 ) ) { isLightmap = qtrue; } } if ( (width&(width-1)) || (height&(height-1)) ) { Com_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*) R_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; if ( qglActiveTextureARB ) { GL_SelectTexture( 0 ); } GLuint uiTarget = GL_TEXTURE_2D; #ifndef HAVE_GLES if ( bRectangle ) { qglDisable( uiTarget ); uiTarget = GL_TEXTURE_RECTANGLE_EXT; qglEnable( uiTarget ); glWrapClampMode = GL_CLAMP_TO_EDGE; // default mode supported by rectangle. qglBindTexture( uiTarget, image->texnum ); } else #endif { GL_Bind(image); } Upload32( (unsigned *)pic, format, (qboolean)image->mipmap, allowPicmip, isLightmap, allowTC, &image->internalFormat, &image->width, &image->height, bRectangle ); qglTexParameterf( uiTarget, GL_TEXTURE_WRAP_S, glWrapClampMode ); qglTexParameterf( uiTarget, GL_TEXTURE_WRAP_T, glWrapClampMode ); qglBindTexture( uiTarget, 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 const char *psNewName = GenerateImageMappingName(name); Q_strncpyz(image->imgName, psNewName, sizeof(image->imgName)); AllocatedImages[ image->imgName ] = image; if ( bRectangle ) { qglDisable( uiTarget ); qglEnable( GL_TEXTURE_2D ); } return image; } //rwwRMG - added void R_CreateAutomapImage( const char *name, const byte *pic, int width, int height, qboolean mipmap, qboolean allowPicmip, qboolean allowTC, int glWrapClampMode ) { R_CreateImage(name, pic, width, height, GL_RGBA, mipmap, allowPicmip, allowTC, glWrapClampMode); } /* =============== 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 || ri.Cvar_VariableIntegerValue( "dedicated" ) ) // 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, GL_RGBA, mipmap, allowPicmip, allowTC, glWrapClampMode ); R_Free( pic ); return image; } /* ================ R_CreateDlightImage ================ */ #define DLIGHT_SIZE 16 static void R_CreateDlightImage( void ) { int width, height; byte *pic; R_LoadImage("gfx/2d/dlight", &pic, &width, &height); if (pic) { tr.dlightImage = R_CreateImage("*dlight", pic, width, height, GL_RGBA, qfalse, qfalse, qfalse, GL_CLAMP ); R_Free(pic); } else { // if we dont get a successful load 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, GL_RGBA, 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 d; float borderColor[4]; data = (unsigned char *)R_Malloc( FOG_S * FOG_T * 4, TAG_TEMP_WORKSPACE, qfalse ); // S is distance, T is depth for (x=0 ; xinteger > glConfig.vidWidth ) { r_DynamicGlowWidth->integer = glConfig.vidWidth; } if ( r_DynamicGlowHeight->integer > glConfig.vidHeight ) { r_DynamicGlowHeight->integer = glConfig.vidHeight; } tr.blurImage = 1024 + giTextureBindNum++; qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage ); qglTexImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, GL_RGBA16, r_DynamicGlowWidth->integer, r_DynamicGlowHeight->integer, 0, GL_RGB, GL_FLOAT, 0 ); qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP ); qglTexParameteri( GL_TEXTURE_RECTANGLE_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP ); qglDisable( GL_TEXTURE_RECTANGLE_EXT ); qglEnable( GL_TEXTURE_2D ); // with overbright bits active, we need an image which is some fraction of full color, // for default lightmaps, etc for (x=0 ; xinteger; 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 ) { ri.WIN_SetGamma( &glConfig, 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(); }