/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development Copyright (C) 2015-2019 GrangerHub This file is part of Tremulous. Tremulous is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. Tremulous 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 Tremulous; if not, see =========================================================================== */ // tr_image.c #include "tr_local.h" #include "tr_dsa.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; #define FILE_HASH_SIZE 1024 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]]; } } struct textureMode_t { const char *name; int minimize, maximize; }; 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 ) { 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 && !(glt->flags & IMGFLAG_CUBEMAP)) { qglTextureParameterfEXT(glt->texnum, GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); qglTextureParameterfEXT(glt->texnum, 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; ri.Printf(PRINT_ALL, "\n -w-- -h-- -type-- -size- --name-------\n"); for ( i = 0 ; i < tr.numImages ; i++ ) { image_t *image = tr.images[i]; const char *format = "???? "; const 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_RG_RGTC2: format = "RGTC2 "; // 128 bits per 16 pixels, so 1 byte per pixel break; case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: format = "DXT1 "; // 64 bits per 16 pixels, so 4 bits per pixel estSize /= 2; break; case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: format = "DXT1a "; // 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_RGBA16F: format = "RGBA16F"; // 8 bytes per pixel estSize *= 8; break; case GL_RGBA16: format = "RGBA16 "; // 8 bytes per pixel estSize *= 8; 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; case GL_DEPTH_COMPONENT16: format = "Depth16"; // 2 bytes per pixel estSize *= 2; break; case GL_DEPTH_COMPONENT24: format = "Depth24"; // 3 bytes per pixel estSize *= 3; break; case GL_DEPTH_COMPONENT: case GL_DEPTH_COMPONENT32: format = "Depth32"; // 4 bytes per pixel estSize *= 4; 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); estTotalSize += estSize; } 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 ); } //======================================================================= /* ================ 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( byte *in, int inwidth, int inheight, byte *out, int outwidth, int outheight ) { int i, j; byte *inrow, *inrow2; int frac, fracstep; int 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; *out++ = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; *out++ = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; *out++ = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; } } } static void RGBAtoYCoCgA(const byte *in, byte *out, int width, int height) { int x, y; for (y = 0; y < height; y++) { const byte *inbyte = in + y * width * 4; byte *outbyte = out + y * width * 4; for (x = 0; x < width; x++) { byte r, g, b, a, rb2; r = *inbyte++; g = *inbyte++; b = *inbyte++; a = *inbyte++; rb2 = (r + b) >> 1; *outbyte++ = (g + rb2) >> 1; // Y = R/4 + G/2 + B/4 *outbyte++ = (r - b + 256) >> 1; // Co = R/2 - B/2 *outbyte++ = (g - rb2 + 256) >> 1; // Cg = -R/4 + G/2 - B/4 *outbyte++ = a; } } } static void YCoCgAtoRGBA(const byte *in, byte *out, int width, int height) { int x, y; for (y = 0; y < height; y++) { const byte *inbyte = in + y * width * 4; byte *outbyte = out + y * width * 4; for (x = 0; x < width; x++) { byte _Y, Co, Cg, a; _Y = *inbyte++; Co = *inbyte++; Cg = *inbyte++; a = *inbyte++; *outbyte++ = CLAMP(_Y + Co - Cg, 0, 255); // R = Y + Co - Cg *outbyte++ = CLAMP(_Y + Cg - 128, 0, 255); // G = Y + Cg *outbyte++ = CLAMP(_Y - Co - Cg + 256, 0, 255); // B = Y - Co - Cg *outbyte++ = a; } } } // uses a sobel filter to change a texture to a normal map static void RGBAtoNormal(const byte *in, byte *out, int width, int height, bool clampToEdge) { int x, y, max; // convert to heightmap, storing in alpha // same as converting to Y in YCoCg max = 1; for (y = 0; y < height; y++) { const byte *inbyte = in + y * width * 4; byte *outbyte = out + y * width * 4 + 3; for (x = 0; x < width; x++) { byte result = (inbyte[0] >> 2) + (inbyte[1] >> 1) + (inbyte[2] >> 2); result = result * result / 255; // Make linear *outbyte = result; max = MAX(max, *outbyte); outbyte += 4; inbyte += 4; } } // level out heights if (max < 255) { for (y = 0; y < height; y++) { byte *outbyte = out + y * width * 4 + 3; for (x = 0; x < width; x++) { *outbyte = *outbyte + (255 - max); outbyte += 4; } } } // now run sobel filter over height values to generate X and Y // then normalize for (y = 0; y < height; y++) { byte *outbyte = out + y * width * 4; for (x = 0; x < width; x++) { // 0 1 2 // 3 4 5 // 6 7 8 byte s[9]; int x2, y2, i; vec3_t normal; i = 0; for (y2 = -1; y2 <= 1; y2++) { int src_y = y + y2; if (clampToEdge) { src_y = CLAMP(src_y, 0, height - 1); } else { src_y = (src_y + height) % height; } for (x2 = -1; x2 <= 1; x2++) { int src_x = x + x2; if (clampToEdge) { src_x = CLAMP(src_x, 0, width - 1); } else { src_x = (src_x + width) % width; } s[i++] = *(out + (src_y * width + src_x) * 4 + 3); } } normal[0] = s[0] - s[2] + 2 * s[3] - 2 * s[5] + s[6] - s[8]; normal[1] = s[0] + 2 * s[1] + s[2] - s[6] - 2 * s[7] - s[8]; normal[2] = s[4] * 4; if (!VectorNormalize2(normal, normal)) { VectorSet(normal, 0, 0, 1); } *outbyte++ = FloatToOffsetByte(normal[0]); *outbyte++ = FloatToOffsetByte(normal[1]); *outbyte++ = FloatToOffsetByte(normal[2]); outbyte++; } } } #define COPYSAMPLE(a,b) *(unsigned int *)(a) = *(unsigned int *)(b) // based on Fast Curve Based Interpolation // from Fast Artifacts-Free Image Interpolation (http://www.andreagiachetti.it/icbi/) // assumes data has a 2 pixel thick border of clamped or wrapped data // expects data to be a grid with even (0, 0), (2, 0), (0, 2), (2, 2) etc pixels filled // only performs FCBI on specified component static void DoFCBI(byte *in, byte *out, int width, int height, int component) { int x, y; byte *outbyte, *inbyte; // copy in to out for (y = 2; y < height - 2; y += 2) { inbyte = in + (y * width + 2) * 4 + component; outbyte = out + (y * width + 2) * 4 + component; for (x = 2; x < width - 2; x += 2) { *outbyte = *inbyte; outbyte += 8; inbyte += 8; } } for (y = 3; y < height - 3; y += 2) { // diagonals // // NWp - northwest interpolated pixel // NEp - northeast interpolated pixel // NWd - northwest first derivative // NEd - northeast first derivative // NWdd - northwest second derivative // NEdd - northeast second derivative // // Uses these samples: // // 0 // - - a - b - - // - - - - - - - // c - d - e - f // 0 - - - - - - - // g - h - i - j // - - - - - - - // - - k - l - - // // x+2 uses these samples: // // 0 // - - - - a - b - - // - - - - - - - - - // - - c - d - e - f // 0 - - - - - - - - - // - - g - h - i - j // - - - - - - - - - // - - - - k - l - - // // so we can reuse 8 of them on next iteration // // a=b, c=d, d=e, e=f, g=h, h=i, i=j, k=l // // only b, f, j, and l need to be sampled on next iteration byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl; byte *line1, *line2, *line3, *line4; x = 3; // optimization one // SAMPLE2(sa, x-1, y-3); //SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); //SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); // SAMPLE2(sk, x-1, y+3); // optimization two line1 = in + ((y - 3) * width + (x - 1)) * 4 + component; line2 = in + ((y - 1) * width + (x - 3)) * 4 + component; line3 = in + ((y + 1) * width + (x - 3)) * 4 + component; line4 = in + ((y + 3) * width + (x - 1)) * 4 + component; // COPYSAMPLE(sa, line1); line1 += 8; //COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; COPYSAMPLE(se, line2); line2 += 8; //COPYSAMPLE(sg, line3); line3 += 8; COPYSAMPLE(sh, line3); line3 += 8; COPYSAMPLE(si, line3); line3 += 8; // COPYSAMPLE(sk, line4); line4 += 8; sa = *line1; line1 += 8; sc = *line2; line2 += 8; sd = *line2; line2 += 8; se = *line2; line2 += 8; sg = *line3; line3 += 8; sh = *line3; line3 += 8; si = *line3; line3 += 8; sk = *line4; line4 += 8; outbyte = out + (y * width + x) * 4 + component; for ( ; x < width - 3; x += 2) { int NWd, NEd, NWp, NEp; // original // SAMPLE2(sa, x-1, y-3); SAMPLE2(sb, x+1, y-3); //SAMPLE2(sc, x-3, y-1); SAMPLE2(sd, x-1, y-1); SAMPLE2(se, x+1, y-1); SAMPLE2(sf, x+3, y-1); //SAMPLE2(sg, x-3, y+1); SAMPLE2(sh, x-1, y+1); SAMPLE2(si, x+1, y+1); SAMPLE2(sj, x+3, y+1); // SAMPLE2(sk, x-1, y+3); SAMPLE2(sl, x+1, y+3); // optimization one //SAMPLE2(sb, x+1, y-3); //SAMPLE2(sf, x+3, y-1); //SAMPLE2(sj, x+3, y+1); //SAMPLE2(sl, x+1, y+3); // optimization two //COPYSAMPLE(sb, line1); line1 += 8; //COPYSAMPLE(sf, line2); line2 += 8; //COPYSAMPLE(sj, line3); line3 += 8; //COPYSAMPLE(sl, line4); line4 += 8; sb = *line1; line1 += 8; sf = *line2; line2 += 8; sj = *line3; line3 += 8; sl = *line4; line4 += 8; NWp = sd + si; NEp = se + sh; NWd = abs(sd - si); NEd = abs(se - sh); if (NWd > 100 || NEd > 100 || abs(NWp-NEp) > 200) { if (NWd < NEd) *outbyte = NWp >> 1; else *outbyte = NEp >> 1; } else { int NWdd, NEdd; //NEdd = abs(sg + sd + sb - 3 * (se + sh) + sk + si + sf); //NWdd = abs(sa + se + sj - 3 * (sd + si) + sc + sh + sl); NEdd = abs(sg + sb - 3 * NEp + sk + sf + NWp); NWdd = abs(sa + sj - 3 * NWp + sc + sl + NEp); if (NWdd > NEdd) *outbyte = NWp >> 1; else *outbyte = NEp >> 1; } outbyte += 8; // COPYSAMPLE(sa, sb); //COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); COPYSAMPLE(se, sf); //COPYSAMPLE(sg, sh); COPYSAMPLE(sh, si); COPYSAMPLE(si, sj); // COPYSAMPLE(sk, sl); sa = sb; sc = sd; sd = se; se = sf; sg = sh; sh = si; si = sj; sk = sl; } } // hack: copy out to in again for (y = 3; y < height - 3; y += 2) { inbyte = out + (y * width + 3) * 4 + component; outbyte = in + (y * width + 3) * 4 + component; for (x = 3; x < width - 3; x += 2) { *outbyte = *inbyte; outbyte += 8; inbyte += 8; } } for (y = 2; y < height - 3; y++) { // horizontal & vertical // // hp - horizontally interpolated pixel // vp - vertically interpolated pixel // hd - horizontal first derivative // vd - vertical first derivative // hdd - horizontal second derivative // vdd - vertical second derivative // Uses these samples: // // 0 // - a - b - // c - d - e // 0 - f - g - // h - i - j // - k - l - // // x+2 uses these samples: // // 0 // - - - a - b - // - - c - d - e // 0 - - - f - g - // - - h - i - j // - - - k - l - // // so we can reuse 7 of them on next iteration // // a=b, c=d, d=e, f=g, h=i, i=j, k=l // // only b, e, g, j, and l need to be sampled on next iteration byte sa, sb, sc, sd, se, sf, sg, sh, si, sj, sk, sl; byte *line1, *line2, *line3, *line4, *line5; //x = (y + 1) % 2; x = (y + 1) % 2 + 2; // optimization one // SAMPLE2(sa, x-1, y-2); //SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x, y-1); // SAMPLE2(sf, x-1, y ); //SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x, y+1); // SAMPLE2(sk, x-1, y+2); line1 = in + ((y - 2) * width + (x - 1)) * 4 + component; line2 = in + ((y - 1) * width + (x - 2)) * 4 + component; line3 = in + ((y ) * width + (x - 1)) * 4 + component; line4 = in + ((y + 1) * width + (x - 2)) * 4 + component; line5 = in + ((y + 2) * width + (x - 1)) * 4 + component; // COPYSAMPLE(sa, line1); line1 += 8; //COPYSAMPLE(sc, line2); line2 += 8; COPYSAMPLE(sd, line2); line2 += 8; // COPYSAMPLE(sf, line3); line3 += 8; //COPYSAMPLE(sh, line4); line4 += 8; COPYSAMPLE(si, line4); line4 += 8; // COPYSAMPLE(sk, line5); line5 += 8; sa = *line1; line1 += 8; sc = *line2; line2 += 8; sd = *line2; line2 += 8; sf = *line3; line3 += 8; sh = *line4; line4 += 8; si = *line4; line4 += 8; sk = *line5; line5 += 8; outbyte = out + (y * width + x) * 4 + component; for ( ; x < width - 3; x+=2) { int hd, vd, hp, vp; // SAMPLE2(sa, x-1, y-2); SAMPLE2(sb, x+1, y-2); //SAMPLE2(sc, x-2, y-1); SAMPLE2(sd, x, y-1); SAMPLE2(se, x+2, y-1); // SAMPLE2(sf, x-1, y ); SAMPLE2(sg, x+1, y ); //SAMPLE2(sh, x-2, y+1); SAMPLE2(si, x, y+1); SAMPLE2(sj, x+2, y+1); // SAMPLE2(sk, x-1, y+2); SAMPLE2(sl, x+1, y+2); // optimization one //SAMPLE2(sb, x+1, y-2); //SAMPLE2(se, x+2, y-1); //SAMPLE2(sg, x+1, y ); //SAMPLE2(sj, x+2, y+1); //SAMPLE2(sl, x+1, y+2); //COPYSAMPLE(sb, line1); line1 += 8; //COPYSAMPLE(se, line2); line2 += 8; //COPYSAMPLE(sg, line3); line3 += 8; //COPYSAMPLE(sj, line4); line4 += 8; //COPYSAMPLE(sl, line5); line5 += 8; sb = *line1; line1 += 8; se = *line2; line2 += 8; sg = *line3; line3 += 8; sj = *line4; line4 += 8; sl = *line5; line5 += 8; hp = sf + sg; vp = sd + si; hd = abs(sf - sg); vd = abs(sd - si); if (hd > 100 || vd > 100 || abs(hp-vp) > 200) { if (hd < vd) *outbyte = hp >> 1; else *outbyte = vp >> 1; } else { int hdd, vdd; //hdd = abs(sc[i] + sd[i] + se[i] - 3 * (sf[i] + sg[i]) + sh[i] + si[i] + sj[i]); //vdd = abs(sa[i] + sf[i] + sk[i] - 3 * (sd[i] + si[i]) + sb[i] + sg[i] + sl[i]); hdd = abs(sc + se - 3 * hp + sh + sj + vp); vdd = abs(sa + sk - 3 * vp + sb + sl + hp); if (hdd > vdd) *outbyte = hp >> 1; else *outbyte = vp >> 1; } outbyte += 8; // COPYSAMPLE(sa, sb); //COPYSAMPLE(sc, sd); COPYSAMPLE(sd, se); // COPYSAMPLE(sf, sg); //COPYSAMPLE(sh, si); COPYSAMPLE(si, sj); // COPYSAMPLE(sk, sl); sa = sb; sc = sd; sd = se; sf = sg; sh = si; si = sj; sk = sl; } } } // Similar to FCBI, but throws out the second order derivatives for speed static void DoFCBIQuick(byte *in, byte *out, int width, int height, int component) { int x, y; byte *outbyte, *inbyte; // copy in to out for (y = 2; y < height - 2; y += 2) { inbyte = in + (y * width + 2) * 4 + component; outbyte = out + (y * width + 2) * 4 + component; for (x = 2; x < width - 2; x += 2) { *outbyte = *inbyte; outbyte += 8; inbyte += 8; } } for (y = 3; y < height - 4; y += 2) { byte sd, se, sh, si; byte *line2, *line3; x = 3; line2 = in + ((y - 1) * width + (x - 1)) * 4 + component; line3 = in + ((y + 1) * width + (x - 1)) * 4 + component; sd = *line2; line2 += 8; sh = *line3; line3 += 8; outbyte = out + (y * width + x) * 4 + component; for ( ; x < width - 4; x += 2) { int NWd, NEd, NWp, NEp; se = *line2; line2 += 8; si = *line3; line3 += 8; NWp = sd + si; NEp = se + sh; NWd = abs(sd - si); NEd = abs(se - sh); if (NWd < NEd) *outbyte = NWp >> 1; else *outbyte = NEp >> 1; outbyte += 8; sd = se; sh = si; } } // hack: copy out to in again for (y = 3; y < height - 3; y += 2) { inbyte = out + (y * width + 3) * 4 + component; outbyte = in + (y * width + 3) * 4 + component; for (x = 3; x < width - 3; x += 2) { *outbyte = *inbyte; outbyte += 8; inbyte += 8; } } for (y = 2; y < height - 3; y++) { byte sd, sf, sg, si; byte *line2, *line3, *line4; x = (y + 1) % 2 + 2; line2 = in + ((y - 1) * width + (x )) * 4 + component; line3 = in + ((y ) * width + (x - 1)) * 4 + component; line4 = in + ((y + 1) * width + (x )) * 4 + component; outbyte = out + (y * width + x) * 4 + component; sf = *line3; line3 += 8; for ( ; x < width - 3; x+=2) { int hd, vd, hp, vp; sd = *line2; line2 += 8; sg = *line3; line3 += 8; si = *line4; line4 += 8; hp = sf + sg; vp = sd + si; hd = abs(sf - sg); vd = abs(sd - si); if (hd < vd) *outbyte = hp >> 1; else *outbyte = vp >> 1; outbyte += 8; sf = sg; } } } // Similar to DoFCBIQuick, but just takes the average instead of checking derivatives // as well, this operates on all four components static void DoLinear(byte *in, byte *out, int width, int height) { int x, y, i; byte *outbyte, *inbyte; // copy in to out for (y = 2; y < height - 2; y += 2) { x = 2; inbyte = in + (y * width + x) * 4; outbyte = out + (y * width + x) * 4; for ( ; x < width - 2; x += 2) { COPYSAMPLE(outbyte, inbyte); outbyte += 8; inbyte += 8; } } for (y = 1; y < height - 1; y += 2) { byte sd[4] = {0}, se[4] = {0}, sh[4] = {0}, si[4] = {0}; byte *line2, *line3; x = 1; line2 = in + ((y - 1) * width + (x - 1)) * 4; line3 = in + ((y + 1) * width + (x - 1)) * 4; COPYSAMPLE(sd, line2); line2 += 8; COPYSAMPLE(sh, line3); line3 += 8; outbyte = out + (y * width + x) * 4; for ( ; x < width - 1; x += 2) { COPYSAMPLE(se, line2); line2 += 8; COPYSAMPLE(si, line3); line3 += 8; for (i = 0; i < 4; i++) { *outbyte++ = (sd[i] + si[i] + se[i] + sh[i]) >> 2; } outbyte += 4; COPYSAMPLE(sd, se); COPYSAMPLE(sh, si); } } // hack: copy out to in again for (y = 1; y < height - 1; y += 2) { x = 1; inbyte = out + (y * width + x) * 4; outbyte = in + (y * width + x) * 4; for ( ; x < width - 1; x += 2) { COPYSAMPLE(outbyte, inbyte); outbyte += 8; inbyte += 8; } } for (y = 1; y < height - 1; y++) { byte sd[4], sf[4], sg[4], si[4]; byte *line2, *line3, *line4; x = y % 2 + 1; line2 = in + ((y - 1) * width + (x )) * 4; line3 = in + ((y ) * width + (x - 1)) * 4; line4 = in + ((y + 1) * width + (x )) * 4; COPYSAMPLE(sf, line3); line3 += 8; outbyte = out + (y * width + x) * 4; for ( ; x < width - 1; x += 2) { COPYSAMPLE(sd, line2); line2 += 8; COPYSAMPLE(sg, line3); line3 += 8; COPYSAMPLE(si, line4); line4 += 8; for (i = 0; i < 4; i++) { *outbyte++ = (sf[i] + sg[i] + sd[i] + si[i]) >> 2; } outbyte += 4; COPYSAMPLE(sf, sg); } } } static void ExpandHalfTextureToGrid( byte *data, int width, int height) { int x, y; for (y = height / 2; y > 0; y--) { byte *outbyte = data + ((y * 2 - 1) * (width) - 2) * 4; byte *inbyte = data + (y * (width / 2) - 1) * 4; for (x = width / 2; x > 0; x--) { COPYSAMPLE(outbyte, inbyte); outbyte -= 8; inbyte -= 4; } } } static void FillInNormalizedZ(const byte *in, byte *out, int width, int height) { int x, y; for (y = 0; y < height; y++) { const byte *inbyte = in + y * width * 4; byte *outbyte = out + y * width * 4; for (x = 0; x < width; x++) { byte nx, ny, nz, h; float fnx, fny, fll, fnz; nx = *inbyte++; ny = *inbyte++; inbyte++; h = *inbyte++; fnx = OffsetByteToFloat(nx); fny = OffsetByteToFloat(ny); fll = 1.0f - fnx * fnx - fny * fny; if (fll >= 0.0f) fnz = (float)sqrt(fll); else fnz = 0.0f; nz = FloatToOffsetByte(fnz); *outbyte++ = nx; *outbyte++ = ny; *outbyte++ = nz; *outbyte++ = h; } } } // size must be even #define WORKBLOCK_SIZE 128 #define WORKBLOCK_BORDER 4 #define WORKBLOCK_REALSIZE (WORKBLOCK_SIZE + WORKBLOCK_BORDER * 2) // assumes that data has already been expanded into a 2x2 grid static void FCBIByBlock(byte *data, int width, int height, bool clampToEdge, bool normalized) { byte workdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4]; byte outdata[WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4]; byte *inbyte, *outbyte; int x, y; int srcx, srcy; ExpandHalfTextureToGrid(data, width, height); for (y = 0; y < height; y += WORKBLOCK_SIZE) { for (x = 0; x < width; x += WORKBLOCK_SIZE) { int x2, y2; int workwidth, workheight, fullworkwidth, fullworkheight; workwidth = MIN(WORKBLOCK_SIZE, width - x); workheight = MIN(WORKBLOCK_SIZE, height - y); fullworkwidth = workwidth + WORKBLOCK_BORDER * 2; fullworkheight = workheight + WORKBLOCK_BORDER * 2; //memset(workdata, 0, WORKBLOCK_REALSIZE * WORKBLOCK_REALSIZE * 4); // fill in work block for (y2 = 0; y2 < fullworkheight; y2 += 2) { srcy = y + y2 - WORKBLOCK_BORDER; if (clampToEdge) { srcy = CLAMP(srcy, 0, height - 2); } else { srcy = (srcy + height) % height; } outbyte = workdata + y2 * fullworkwidth * 4; inbyte = data + srcy * width * 4; for (x2 = 0; x2 < fullworkwidth; x2 += 2) { srcx = x + x2 - WORKBLOCK_BORDER; if (clampToEdge) { srcx = CLAMP(srcx, 0, width - 2); } else { srcx = (srcx + width) % width; } COPYSAMPLE(outbyte, inbyte + srcx * 4); outbyte += 8; } } // submit work block DoLinear(workdata, outdata, fullworkwidth, fullworkheight); if (!normalized) { switch (r_imageUpsampleType->integer) { case 0: break; case 1: DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0); break; case 2: default: DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0); break; } } else { switch (r_imageUpsampleType->integer) { case 0: break; case 1: DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 0); DoFCBIQuick(workdata, outdata, fullworkwidth, fullworkheight, 1); break; case 2: default: DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 0); DoFCBI(workdata, outdata, fullworkwidth, fullworkheight, 1); break; } } // copy back work block for (y2 = 0; y2 < workheight; y2++) { inbyte = outdata + ((y2 + WORKBLOCK_BORDER) * fullworkwidth + WORKBLOCK_BORDER) * 4; outbyte = data + ((y + y2) * width + x) * 4; for (x2 = 0; x2 < workwidth; x2++) { COPYSAMPLE(outbyte, inbyte); outbyte += 4; inbyte += 4; } } } } } #undef COPYSAMPLE /* ================ R_LightScaleTexture Scale up the pixel values in a texture to increase the lighting range ================ */ void R_LightScaleTexture (byte *in, int inwidth, int inheight, bool only_gamma ) { if ( only_gamma ) { if ( !glConfig.deviceSupportsGamma ) { int i, c; byte *p; p = in; c = inwidth*inheight; for (i=0 ; i> 1; x; x--) { for (c = 3; c; c--, in++) { total = (downmipSrgbLookup[*(in)] + downmipSrgbLookup[*(in + 4)]) * 2.0f; *out++ = (byte)(powf(total, 1.0f / 2.2f) * 255.0f); } *out++ = (*(in) + *(in + 4)) >> 1; in += 5; } return; } stride = inWidth * 4; inWidth >>= 1; inHeight >>= 1; in2 = in + stride; for (y = inHeight; y; y--, in += stride, in2 += stride) { for (x = inWidth; x; x--) { for (c = 3; c; c--, in++, in2++) { total = downmipSrgbLookup[*(in)] + downmipSrgbLookup[*(in + 4)] + downmipSrgbLookup[*(in2)] + downmipSrgbLookup[*(in2 + 4)]; *out++ = (byte)(powf(total, 1.0f / 2.2f) * 255.0f); } *out++ = (*(in) + *(in + 4) + *(in2) + *(in2 + 4)) >> 2; in += 5, in2 += 5; } } } static void R_MipMapNormalHeight (const byte *in, byte *out, int width, int height, bool swizzle) { int i, j; int row; int sx = swizzle ? 3 : 0; int sa = swizzle ? 0 : 3; if ( width == 1 && height == 1 ) { return; } row = width * 4; width >>= 1; height >>= 1; 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}, }; static void RawImage_SwizzleRA( byte *data, int width, int height ) { int i; byte *ptr = data, swap; for (i=0; iinteger && scaled_width > width ) scaled_width >>= 1; if ( r_roundImagesDown->integer && scaled_height > height ) scaled_height >>= 1; if ( picmip && data && resampledBuffer && r_imageUpsample->integer && scaled_width < r_imageUpsampleMaxSize->integer && scaled_height < r_imageUpsampleMaxSize->integer) { int finalwidth, finalheight; //int startTime, endTime; //startTime = ri.Milliseconds(); finalwidth = scaled_width << r_imageUpsample->integer; finalheight = scaled_height << r_imageUpsample->integer; while ( finalwidth > r_imageUpsampleMaxSize->integer || finalheight > r_imageUpsampleMaxSize->integer ) { finalwidth >>= 1; finalheight >>= 1; } while ( finalwidth > glConfig.maxTextureSize || finalheight > glConfig.maxTextureSize ) { finalwidth >>= 1; finalheight >>= 1; } *resampledBuffer = (byte*)ri.Hunk_AllocateTempMemory( finalwidth * finalheight * 4 ); if (scaled_width != width || scaled_height != height) ResampleTexture (*data, width, height, *resampledBuffer, scaled_width, scaled_height); else Com_Memcpy(*resampledBuffer, *data, width * height * 4); if (type == IMGTYPE_COLORALPHA) RGBAtoYCoCgA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); while (scaled_width < finalwidth || scaled_height < finalheight) { scaled_width <<= 1; scaled_height <<= 1; FCBIByBlock(*resampledBuffer, scaled_width, scaled_height, clampToEdge, (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)); } if (type == IMGTYPE_COLORALPHA) YCoCgAtoRGBA(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); else if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) FillInNormalizedZ(*resampledBuffer, *resampledBuffer, scaled_width, scaled_height); //endTime = ri.Milliseconds(); //ri.Printf(PRINT_ALL, "upsampled %dx%d to %dx%d in %dms\n", width, height, scaled_width, scaled_height, endTime - startTime); *data = *resampledBuffer; } else if ( scaled_width != width || scaled_height != height ) { if (data && resampledBuffer) { *resampledBuffer = (byte*)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 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 > glConfig.maxTextureSize || scaled_height > glConfig.maxTextureSize ) { scaled_width >>= 1; scaled_height >>= 1; } // // clamp to minimum size // scaled_width = MAX(1, scaled_width); scaled_height = MAX(1, scaled_height); scaled = (width != scaled_width) || (height != scaled_height); // // rescale texture to new size using existing mipmap functions // if (data) { while (width > scaled_width || height > scaled_height) { if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) R_MipMapNormalHeight(*data, *data, width, height, false); else R_MipMapsRGB(*data, width, height); width = MAX(1, width >> 1); height = MAX(1, height >> 1); } } *inout_width = width; *inout_height = height; return scaled; } static bool RawImage_HasAlpha(const byte *scan, int numPixels) { int i; if (!scan) return true; for ( i = 0; i < numPixels; i++ ) { if ( scan[i*4 + 3] != 255 ) { return true; } } return false; } static GLenum RawImage_GetFormat(const byte *data, int numPixels, GLenum picFormat, bool lightMap, imgType_t type, int/*imgFlags_t*/ flags) { int samples = 3; GLenum internalFormat = GL_RGB; bool forceNoCompression = (flags & IMGFLAG_NO_COMPRESSION); bool normalmap = (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT); if (picFormat != GL_RGBA8) return picFormat; if(normalmap) { if ((type == IMGTYPE_NORMALHEIGHT) && RawImage_HasAlpha(data, numPixels) && r_parallaxMapping->integer) { if (!forceNoCompression && glRefConfig.textureCompression & TCR_BPTC) { internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; } else if (!forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } else if ( r_texturebits->integer == 16 ) { internalFormat = GL_RGBA4; } else if ( r_texturebits->integer == 32 ) { internalFormat = GL_RGBA8; } else { internalFormat = GL_RGBA; } } else { if (!forceNoCompression && glRefConfig.textureCompression & TCR_RGTC) { internalFormat = GL_COMPRESSED_RG_RGTC2; } else if (!forceNoCompression && glRefConfig.textureCompression & TCR_BPTC) { internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; } else if (!forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } else if (r_texturebits->integer == 16) { internalFormat = GL_RGB5; } else if (r_texturebits->integer == 32) { internalFormat = GL_RGB8; } else { internalFormat = GL_RGB; } } } else if(lightMap) { if(r_greyscale->integer) internalFormat = GL_LUMINANCE; else internalFormat = GL_RGBA; } else { if (RawImage_HasAlpha(data, numPixels)) { samples = 4; } // select proper internal format if ( samples == 3 ) { if(r_greyscale->integer) { if(r_texturebits->integer == 16) internalFormat = GL_LUMINANCE8; else if(r_texturebits->integer == 32) internalFormat = GL_LUMINANCE16; else internalFormat = GL_LUMINANCE; } else { if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) ) { internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; } else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; } else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC ) { internalFormat = GL_RGB4_S3TC; } else if ( r_texturebits->integer == 16 ) { internalFormat = GL_RGB5; } else if ( r_texturebits->integer == 32 ) { internalFormat = GL_RGB8; } else { internalFormat = GL_RGB; } } } else if ( samples == 4 ) { if(r_greyscale->integer) { if(r_texturebits->integer == 16) internalFormat = GL_LUMINANCE8_ALPHA8; else if(r_texturebits->integer == 32) internalFormat = GL_LUMINANCE16_ALPHA16; else internalFormat = GL_LUMINANCE_ALPHA; } else { if ( !forceNoCompression && (glRefConfig.textureCompression & TCR_BPTC) ) { internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM_ARB; } else if ( !forceNoCompression && glConfig.textureCompression == TC_S3TC_ARB ) { internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; } else if ( r_texturebits->integer == 16 ) { internalFormat = GL_RGBA4; } else if ( r_texturebits->integer == 32 ) { internalFormat = GL_RGBA8; } else { internalFormat = GL_RGBA; } } } } return internalFormat; } static void CompressMonoBlock(byte outdata[8], const byte indata[16]) { int hi, lo, diff, bias, outbyte, shift, i; byte *p = outdata; hi = lo = indata[0]; for (i = 1; i < 16; i++) { hi = MAX(indata[i], hi); lo = MIN(indata[i], lo); } *p++ = hi; *p++ = lo; diff = hi - lo; if (diff == 0) { outbyte = (hi == 255) ? 255 : 0; for (i = 0; i < 6; i++) *p++ = outbyte; return; } bias = diff / 2 - lo * 7; outbyte = shift = 0; for (i = 0; i < 16; i++) { const byte fixIndex[8] = { 1, 7, 6, 5, 4, 3, 2, 0 }; byte index = fixIndex[(indata[i] * 7 + bias) / diff]; outbyte |= index << shift; shift += 3; if (shift >= 8) { *p++ = outbyte & 0xff; shift -= 8; outbyte >>= 8; } } } static void RawImage_UploadToRgtc2Texture(GLuint texture, int miplevel, int x, int y, int width, int height, byte *data) { int wBlocks, hBlocks, iy, ix, size; byte *compressedData, *p; wBlocks = (width + 3) / 4; hBlocks = (height + 3) / 4; size = wBlocks * hBlocks * 16; p = compressedData = (byte*)ri.Hunk_AllocateTempMemory(size); for (iy = 0; iy < height; iy += 4) { int oh = MIN(4, height - iy); for (ix = 0; ix < width; ix += 4) { byte workingData[16]; int component; int ow = MIN(4, width - ix); for (component = 0; component < 2; component++) { int ox, oy; for (oy = 0; oy < oh; oy++) for (ox = 0; ox < ow; ox++) workingData[oy * 4 + ox] = data[((iy + oy) * width + ix + ox) * 4 + component]; // dupe data to fill for (oy = 0; oy < 4; oy++) for (ox = (oy < oh) ? ow : 0; ox < 4; ox++) workingData[oy * 4 + ox] = workingData[(oy % oh) * 4 + ox % ow]; CompressMonoBlock(p, workingData); p += 8; } } } // FIXME: Won't work for x/y that aren't multiples of 4. qglCompressedTextureSubImage2DEXT(texture, GL_TEXTURE_2D, miplevel, x, y, width, height, GL_COMPRESSED_RG_RGTC2, size, compressedData); ri.Hunk_FreeTempMemory(compressedData); } static int CalculateMipSize(int width, int height, GLenum picFormat) { int numBlocks = ((width + 3) / 4) * ((height + 3) / 4); int numPixels = width * height; switch (picFormat) { case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_SIGNED_RED_RGTC1: return numBlocks * 8; case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SIGNED_RG_RGTC2: case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB: case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB: case GL_COMPRESSED_RGBA_BPTC_UNORM_ARB: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB: return numBlocks * 16; case GL_RGBA8: case GL_SRGB8_ALPHA8_EXT: return numPixels * 4; case GL_RGBA16: return numPixels * 8; default: ri.Printf(PRINT_ALL, "Unsupported texture format %08x\n", picFormat); return 0; } return 0; } static GLenum PixelDataFormatFromInternalFormat(GLenum internalFormat) { switch (internalFormat) { case GL_DEPTH_COMPONENT: case GL_DEPTH_COMPONENT16_ARB: case GL_DEPTH_COMPONENT24_ARB: case GL_DEPTH_COMPONENT32_ARB: return GL_DEPTH_COMPONENT; default: return GL_RGBA; break; } } static void RawImage_UploadTexture(GLuint texture, byte *data, int x, int y, int width, int height, GLenum target, GLenum picFormat, int numMips, GLenum internalFormat, imgType_t type, int/*imgFlags_t*/ flags, bool subtexture ) { GLenum dataFormat, dataType; bool rgtc = internalFormat == GL_COMPRESSED_RG_RGTC2; bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; bool rgba = rgba8 || picFormat == GL_RGBA16; bool mipmap = !!(flags & IMGFLAG_MIPMAP); int size, miplevel; bool lastMip = false; dataFormat = PixelDataFormatFromInternalFormat(internalFormat); dataType = picFormat == GL_RGBA16 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE; miplevel = 0; do { lastMip = (width == 1 && height == 1) || !mipmap; size = CalculateMipSize(width, height, picFormat); if (!rgba) { qglCompressedTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, picFormat, size, data); } else { if (rgba8 && miplevel != 0 && r_colorMipLevels->integer) R_BlendOverTexture((byte *)data, width * height, mipBlendColors[miplevel]); if (rgba8 && rgtc) RawImage_UploadToRgtc2Texture(texture, miplevel, x, y, width, height, data); else qglTextureSubImage2DEXT(texture, target, miplevel, x, y, width, height, dataFormat, dataType, data); } if (!lastMip && numMips < 2) { if (glRefConfig.framebufferObject) { qglGenerateTextureMipmapEXT(texture, target); break; } else if (rgba8) { if (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT) R_MipMapNormalHeight(data, data, width, height, glRefConfig.swizzleNormalmap); else R_MipMapsRGB(data, width, height); } } x >>= 1; y >>= 1; width = MAX(1, width >> 1); height = MAX(1, height >> 1); miplevel++; if (numMips > 1) { data += size; numMips--; } } while (!lastMip); } /* =============== Upload32 =============== */ static void Upload32(byte *data, int x, int y, int width, int height, GLenum picFormat, int numMips, image_t *image, bool scaled) { int i, c; byte *scan; imgType_t type = image->type; int/*imgFlags_t*/ flags = image->flags; GLenum internalFormat = image->internalFormat; bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; bool mipmap = !!(flags & IMGFLAG_MIPMAP) && (rgba8 || numMips > 1); bool cubemap = !!(flags & IMGFLAG_CUBEMAP); // These operations cannot be performed on non-rgba8 images. if (rgba8 && !cubemap) { c = width*height; scan = data; if (type == IMGTYPE_COLORALPHA) { if( r_greyscale->integer ) { for ( i = 0; i < c; i++ ) { byte luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); scan[i*4] = luma; scan[i*4 + 1] = luma; scan[i*4 + 2] = luma; } } else if( r_greyscale->value ) { for ( i = 0; i < c; i++ ) { float luma = LUMA(scan[i*4], scan[i*4 + 1], scan[i*4 + 2]); scan[i*4] = LERP(scan[i*4], luma, r_greyscale->value); scan[i*4 + 1] = LERP(scan[i*4 + 1], luma, r_greyscale->value); scan[i*4 + 2] = LERP(scan[i*4 + 2], luma, r_greyscale->value); } } // This corresponds to what the OpenGL1 renderer does. if (!(flags & IMGFLAG_NOLIGHTSCALE) && (scaled || mipmap)) R_LightScaleTexture(data, width, height, !mipmap); } if (glRefConfig.swizzleNormalmap && (type == IMGTYPE_NORMAL || type == IMGTYPE_NORMALHEIGHT)) RawImage_SwizzleRA(data, width, height); } if (cubemap) { for (i = 0; i < 6; i++) { int w2 = width, h2 = height; RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, picFormat, numMips, internalFormat, type, flags, false); for (c = numMips; c; c--) { data += CalculateMipSize(w2, h2, picFormat); w2 = MAX(1, w2 >> 1); h2 = MAX(1, h2 >> 1); } } } else { RawImage_UploadTexture(image->texnum, data, x, y, width, height, GL_TEXTURE_2D, picFormat, numMips, internalFormat, type, flags, false); } GL_CheckErrors(); } /* ================ R_CreateImage2 This is the only way any image_t are created ================ */ image_t *R_CreateImage2( const char *name, byte *pic, int width, int height, GLenum picFormat, int numMips, imgType_t type, int/*imgFlags_t*/ flags, int internalFormat ) { byte *resampledBuffer = NULL; image_t *image; bool isLightmap = false, scaled = false; long hash; int glWrapClampMode, mipWidth, mipHeight, miplevel; bool rgba8 = picFormat == GL_RGBA8 || picFormat == GL_SRGB8_ALPHA8_EXT; bool mipmap = !!(flags & IMGFLAG_MIPMAP); bool cubemap = !!(flags & IMGFLAG_CUBEMAP); bool picmip = !!(flags & IMGFLAG_PICMIP); bool lastMip; GLenum textureTarget = cubemap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D; GLenum dataFormat; if (strlen(name) >= MAX_QPATH ) { ri.Error (ERR_DROP, "R_CreateImage: \"%s\" is too long", name); } if ( !strncmp( name, "*lightmap", 9 ) ) { isLightmap = true; } if ( tr.numImages == MAX_DRAWIMAGES ) { ri.Error( ERR_DROP, "R_CreateImage: MAX_DRAWIMAGES hit"); } image = tr.images[tr.numImages] = (image_t*)ri.Hunk_Alloc( sizeof( image_t ), h_low ); qglGenTextures(1, &image->texnum); 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; if (!internalFormat) internalFormat = RawImage_GetFormat(pic, width * height, picFormat, isLightmap, image->type, image->flags); image->internalFormat = internalFormat; // Possibly scale image before uploading. // if not rgba8 and uploading an image, skip picmips. if (!cubemap) { if (rgba8) scaled = RawImage_ScaleToPower2(&pic, &width, &height, type, flags, &resampledBuffer); else if (pic && picmip) { for (miplevel = r_picmip->integer; miplevel > 0 && numMips > 1; miplevel--, numMips--) { int size = CalculateMipSize(width, height, picFormat); width = MAX(1, width >> 1); height = MAX(1, height >> 1); pic += size; } } } image->uploadWidth = width; image->uploadHeight = height; // Allocate texture storage so we don't have to worry about it later. dataFormat = PixelDataFormatFromInternalFormat(internalFormat); mipWidth = width; mipHeight = height; miplevel = 0; do { lastMip = !mipmap || (mipWidth == 1 && mipHeight == 1); if (cubemap) { int i; for (i = 0; i < 6; i++) qglTextureImage2DEXT(image->texnum, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL); } else { qglTextureImage2DEXT(image->texnum, GL_TEXTURE_2D, miplevel, internalFormat, mipWidth, mipHeight, 0, dataFormat, GL_UNSIGNED_BYTE, NULL); } mipWidth = MAX(1, mipWidth >> 1); mipHeight = MAX(1, mipHeight >> 1); miplevel++; } while (!lastMip); // Upload data. if (pic) Upload32(pic, 0, 0, width, height, picFormat, numMips, image, scaled); if (resampledBuffer != NULL) ri.Hunk_FreeTempMemory(resampledBuffer); // Set all necessary texture parameters. qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_S, glWrapClampMode); qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_T, glWrapClampMode); if (cubemap) qglTextureParameteriEXT(image->texnum, textureTarget, GL_TEXTURE_WRAP_R, glWrapClampMode); if (glConfig.textureFilterAnisotropic && !cubemap) qglTextureParameteriEXT(image->texnum, textureTarget, GL_TEXTURE_MAX_ANISOTROPY_EXT, mipmap ? (GLint)Com_Clamp(1, glConfig.maxAnisotropy, r_ext_max_anisotropy->integer) : 1); switch(internalFormat) { case GL_DEPTH_COMPONENT: case GL_DEPTH_COMPONENT16_ARB: case GL_DEPTH_COMPONENT24_ARB: case GL_DEPTH_COMPONENT32_ARB: // Fix for sampling depth buffer on old nVidia cards. // from http://www.idevgames.com/forums/thread-4141-post-34844.html#pid34844 qglTextureParameterfEXT(image->texnum, textureTarget, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE); qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST); qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); break; default: qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MIN_FILTER, mipmap ? gl_filter_min : GL_LINEAR); qglTextureParameterfEXT(image->texnum, textureTarget, GL_TEXTURE_MAG_FILTER, mipmap ? gl_filter_max : GL_LINEAR); break; } GL_CheckErrors(); hash = generateHashValue(name); image->next = hashTable[hash]; hashTable[hash] = image; return image; } /* ================ R_CreateImage Wrapper for R_CreateImage2(), for the old parameters. ================ */ image_t *R_CreateImage(const char *name, byte *pic, int width, int height, imgType_t type, int/*imgFlags_t*/ flags, int internalFormat) { return R_CreateImage2(name, pic, width, height, GL_RGBA8, 0, type, flags, internalFormat); } void R_UpdateSubImage( image_t *image, byte *pic, int x, int y, int width, int height, GLenum picFormat ) { Upload32(pic, x, y, width, height, picFormat, 0, image, false); } //=================================================================== // Prototype for dds loader function which isn't common to both renderers void R_LoadDDS(const char *filename, byte **pic, int *width, int *height, GLenum *picFormat, int *numMips); typedef struct { const 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[ ] = { { "png", R_LoadPNG }, { "tga", R_LoadTGA }, { "jpg", R_LoadJPG }, { "jpeg", R_LoadJPG }, { "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, GLenum *picFormat, int *numMips ) { bool orgNameFailed = false; int orgLoader = -1; int i; char localName[ MAX_QPATH ]; const char *ext; const char *altName; *pic = NULL; *width = 0; *height = 0; *picFormat = GL_RGBA8; *numMips = 0; Q_strncpyz( localName, name, MAX_QPATH ); ext = COM_GetExtension( localName ); // If compressed textures are enabled, try loading a DDS first, it'll load fastest if (r_ext_compressed_textures->integer) { char ddsName[MAX_QPATH]; COM_StripExtension(name, ddsName, MAX_QPATH); Q_strcat(ddsName, MAX_QPATH, ".dds"); R_LoadDDS(ddsName, pic, width, height, picFormat, numMips); // If loaded, we're done. if (*pic) return; } 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 = true; 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, int/*imgFlags_t*/ flags ) { image_t *image; int width, height; byte *pic; GLenum picFormat; int picNumMips; long hash; int/*imgFlags_t*/ checkFlagsTrue, checkFlagsFalse; if (!name) { return NULL; } 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; } } // // load the pic from disk // R_LoadImage( name, &pic, &width, &height, &picFormat, &picNumMips ); if ( pic == NULL ) { return NULL; } checkFlagsTrue = IMGFLAG_PICMIP | IMGFLAG_MIPMAP | IMGFLAG_GENNORMALMAP; checkFlagsFalse = IMGFLAG_CUBEMAP; if (r_normalMapping->integer && (picFormat == GL_RGBA8) && (type == IMGTYPE_COLORALPHA) && ((flags & checkFlagsTrue) == checkFlagsTrue) && !(flags & checkFlagsFalse)) { char normalName[MAX_QPATH]; image_t *normalImage; int normalWidth, normalHeight; int/*imgFlags_t*/ normalFlags; normalFlags = (flags & ~IMGFLAG_GENNORMALMAP) | IMGFLAG_NOLIGHTSCALE; COM_StripExtension(name, normalName, MAX_QPATH); Q_strcat(normalName, MAX_QPATH, "_n"); // find normalmap in case it's there normalImage = R_FindImageFile(normalName, IMGTYPE_NORMAL, normalFlags); // if not, generate it if (normalImage == NULL) { byte *normalPic; int x, y; normalWidth = width; normalHeight = height; normalPic = (byte*)ri.Malloc(width * height * 4); RGBAtoNormal(pic, normalPic, width, height, flags & IMGFLAG_CLAMPTOEDGE); #if 1 // Brighten up the original image to work with the normal map RGBAtoYCoCgA(pic, pic, width, height); for (y = 0; y < height; y++) { byte *picbyte = pic + y * width * 4; byte *normbyte = normalPic + y * width * 4; for (x = 0; x < width; x++) { int div = MAX(normbyte[2] - 127, 16); picbyte[0] = CLAMP(picbyte[0] * 128 / div, 0, 255); picbyte += 4; normbyte += 4; } } YCoCgAtoRGBA(pic, pic, width, height); #else // Blur original image's luma to work with the normal map { byte *blurPic; RGBAtoYCoCgA(pic, pic, width, height); blurPic = ri.Malloc(width * height); for (y = 1; y < height - 1; y++) { byte *picbyte = pic + y * width * 4; byte *blurbyte = blurPic + y * width; picbyte += 4; blurbyte += 1; for (x = 1; x < width - 1; x++) { int result; result = *(picbyte - (width + 1) * 4) + *(picbyte - width * 4) + *(picbyte - (width - 1) * 4) + *(picbyte - 1 * 4) + *(picbyte ) + *(picbyte + 1 * 4) + *(picbyte + (width - 1) * 4) + *(picbyte + width * 4) + *(picbyte + (width + 1) * 4); result /= 9; *blurbyte = result; picbyte += 4; blurbyte += 1; } } // FIXME: do borders for (y = 1; y < height - 1; y++) { byte *picbyte = pic + y * width * 4; byte *blurbyte = blurPic + y * width; picbyte += 4; blurbyte += 1; for (x = 1; x < width - 1; x++) { picbyte[0] = *blurbyte; picbyte += 4; blurbyte += 1; } } ri.Free(blurPic); YCoCgAtoRGBA(pic, pic, width, height); } #endif R_CreateImage( normalName, normalPic, normalWidth, normalHeight, IMGTYPE_NORMAL, normalFlags, 0 ); ri.Free( normalPic ); } } // force mipmaps off if image is compressed but doesn't have enough mips if ((flags & IMGFLAG_MIPMAP) && picFormat != GL_RGBA8 && picFormat != GL_SRGB8_ALPHA8_EXT) { int wh = MAX(width, height); int neededMips = 0; while (wh) { neededMips++; wh >>= 1; } if (neededMips > picNumMips) flags &= ~IMGFLAG_MIPMAP; } image = R_CreateImage2( ( char * ) name, pic, width, height, picFormat, picNumMips, type, flags, 0 ); ri.Free( pic ); return image; } /* ================ R_CreateDlightImage ================ */ #define DLIGHT_SIZE 16 static void R_CreateDlightImage( void ) { int x,y; byte data[DLIGHT_SIZE][DLIGHT_SIZE][4]; int b; // make a centered inverse-square falloff blob for dynamic lighting for (x=0 ; x 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 ); } /* ================= 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; data = (byte*)ri.Hunk_AllocateTempMemory( FOG_S * FOG_T * 4 ); // S is distance, T is depth for (x=0 ; xinteger >= 2) { for( x = 0; x < MAX_DLIGHTS; x++) { tr.shadowCubemaps[x] = R_CreateImage(va("*shadowcubemap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_CLAMPTOEDGE | IMGFLAG_CUBEMAP, 0); } } // with overbright bits active, we need an image which is some fraction of full color, // for default lightmaps, etc for (x=0 ; xinteger && glRefConfig.textureFloat) hdrFormat = GL_RGBA16F_ARB; rgbFormat = GL_RGBA8; tr.renderImage = R_CreateImage("_render", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); if (r_shadowBlur->integer) tr.screenScratchImage = R_CreateImage("screenScratch", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat); //if (r_shadowBlur->integer || r_ssao->integer) // tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_INTENSITY32F_ARB); if (r_shadowBlur->integer || r_ssao->integer) tr.hdrDepthImage = R_CreateImage("*hdrDepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_R32F); if (r_drawSunRays->integer) tr.sunRaysImage = R_CreateImage("*sunRays", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, rgbFormat); tr.renderDepthImage = R_CreateImage("*renderdepth", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); tr.textureDepthImage = R_CreateImage("*texturedepth", NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); { byte *p; data[0][0][0] = 0; data[0][0][1] = 0.45f * 255; data[0][0][2] = 255; data[0][0][3] = 255; p = (byte*)data; tr.calcLevelsImage = R_CreateImage("*calcLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); tr.targetLevelsImage = R_CreateImage("*targetLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); tr.fixedLevelsImage = R_CreateImage("*fixedLevels", p, 1, 1, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, hdrFormat); } for (x = 0; x < 2; x++) { tr.textureScratchImage[x] = R_CreateImage(va("*textureScratch%d", x), NULL, 256, 256, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); } for (x = 0; x < 2; x++) { tr.quarterImage[x] = R_CreateImage(va("*quarter%d", x), NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); } if (r_ssao->integer) { tr.screenSsaoImage = R_CreateImage("*screenSsao", NULL, width / 2, height / 2, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); } for( x = 0; x < MAX_DRAWN_PSHADOWS; x++) { tr.pshadowMaps[x] = R_CreateImage(va("*shadowmap%i", x), NULL, PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24); //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); //qglTextureParameterfEXT(tr.pshadowMaps[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); } if (r_sunlightMode->integer) { for ( x = 0; x < 4; x++) { tr.sunShadowDepthImage[x] = R_CreateImage(va("*sunshadowdepth%i", x), NULL, r_shadowMapSize->integer, r_shadowMapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_DEPTH_COMPONENT24_ARB); qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); qglTextureParameterfEXT(tr.sunShadowDepthImage[x]->texnum, GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); } tr.screenShadowImage = R_CreateImage("*screenShadow", NULL, width, height, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE, GL_RGBA8); } if (r_cubeMapping->integer) { tr.renderCubeImage = R_CreateImage("*renderCube", NULL, r_cubemapSize->integer, r_cubemapSize->integer, IMGTYPE_COLORALPHA, IMGFLAG_NO_COMPRESSION | IMGFLAG_CLAMPTOEDGE | IMGFLAG_MIPMAP | IMGFLAG_CUBEMAP, rgbFormat); } } } /* =============== R_SetColorMappings =============== */ void R_SetColorMappings( void ) { int i, j; float g; int inf; // setup the overbright lighting tr.overbrightBits = r_overBrightBits->integer; // allow 2 overbright bits if ( tr.overbrightBits > 2 ) { tr.overbrightBits = 2; } else if ( tr.overbrightBits < 0 ) { tr.overbrightBits = 0; } // don't allow more overbright bits than map overbright bits if ( tr.overbrightBits > r_mapOverBrightBits->integer ) { tr.overbrightBits = r_mapOverBrightBits->integer; } 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; for ( i = 0; i < 256; i++ ) { if ( g == 1 ) { inf = i; } else { inf = 255 * pow ( i/255.0f, 1.0f / g ) + 0.5f; } if (inf < 0) { inf = 0; } if (inf > 255) { inf = 255; } s_gammatable[i] = inf; } for (i=0 ; i<256 ; i++) { j = i * r_intensity->value; if (j > 255) { j = 255; } s_intensitytable[i] = j; } if ( glConfig.deviceSupportsGamma ) { GLimp_SetGamma( s_gammatable, s_gammatable, s_gammatable ); } } /* =============== R_InitImages =============== */ void R_InitImages( void ) { Com_Memset(hashTable, 0, sizeof(hashTable)); // build brightness translation tables R_SetColorMappings(); // 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; GL_BindNullTextures(); } /* ============================================================================ SKINS ============================================================================ */ /* ================== CommaParse This is unfortunate, but the skin files aren't compatable with our normal parsing rules. ================== */ static const 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] == '/' ) { data += 2; while (*data && *data != '\n') { data++; } } // skip /* */ comments else if ( c=='/' && data[1] == '*' ) { data += 2; 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 - 1) { com_token[len] = c; len++; } } } // parse a regular word do { if (len < MAX_TOKEN_CHARS - 1) { com_token[len] = c; len++; } data++; c = *data; } while (c>32 && c != ',' ); com_token[len] = 0; *data_p = ( char * ) data; return com_token; } /* =============== RE_RegisterSkin =============== */ qhandle_t RE_RegisterSkin( const char *name ) { skinSurface_t parseSurfaces[MAX_SKIN_SURFACES]; qhandle_t hSkin; skin_t *skin; skinSurface_t *surf; union { char *c; void *v; } text; char *text_p; const 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 = (skin_t*)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 = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), h_low ); skin->surfaces[0].shader = R_FindShader( name, LIGHTMAP_NONE, true ); return hSkin; } // load and parse the skin file ri.FS_ReadFile( name, &text.v ); if ( !text.c ) { return 0; } int totalSurfaces = 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 < MAX_SKIN_SURFACES ) { surf = &parseSurfaces[skin->numSurfaces]; Q_strncpyz( surf->name, surfName, sizeof( surf->name ) ); surf->shader = R_FindShader( token, LIGHTMAP_NONE, true ); skin->numSurfaces++; } totalSurfaces++; } ri.FS_FreeFile( text.v ); if ( totalSurfaces > MAX_SKIN_SURFACES ) { ri.Printf( PRINT_WARNING, "WARNING: Ignoring excess surfaces (found %d, max is %d) in skin '%s'!\n", totalSurfaces, MAX_SKIN_SURFACES, name ); } // never let a skin have 0 shaders if ( skin->numSurfaces == 0 ) { return 0; // use default skin } // copy surfaces to skin skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( skin->numSurfaces * sizeof( skinSurface_t ), h_low ); memcpy( skin->surfaces, parseSurfaces, skin->numSurfaces * sizeof( skinSurface_t ) ); 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] = (skin_t*)ri.Hunk_Alloc( sizeof( skin_t ), h_low ); Q_strncpyz( skin->name, "", sizeof( skin->name ) ); skin->numSurfaces = 1; skin->surfaces = (skinSurface_t*)ri.Hunk_Alloc( sizeof( skinSurface_t ), 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 (%d surfaces)\n", i, skin->name, skin->numSurfaces ); 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"); }