/* * Copyright(c) 1997-2001 Id Software, Inc. * Copyright(c) 2002 The Quakeforge Project. * Copyright(c) 2006 Quetoo. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or(at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "video.h" #include "pcx.h" #include "tga.h" image_t gltextures[MAX_GLTEXTURES]; int numgltextures; int base_textureid; // gltextures[i] = base_textureid+i static byte intensitytable[256]; unsigned d_8to24table[256]; qboolean GL_Upload8(byte *data, int width, int height, qboolean mipmap, qboolean is_sky); qboolean GL_Upload32(unsigned *data, int width, int height, qboolean mipmap); int gl_solid_format = 3; int gl_alpha_format = 4; int gl_tex_solid_format = 3; int gl_tex_alpha_format = 4; int gl_filter_min = GL_LINEAR_MIPMAP_NEAREST; int gl_filter_max = GL_LINEAR; void GL_EnableMultitexture(qboolean enable){ if(!qglActiveTextureARB) return; if(enable){ GL_SelectTexture(QGL_TEXTURE1); qglEnable(GL_TEXTURE_2D); GL_TexEnv(GL_REPLACE); } else { GL_SelectTexture(QGL_TEXTURE1); qglDisable(GL_TEXTURE_2D); GL_TexEnv(GL_REPLACE); } GL_SelectTexture(QGL_TEXTURE0); GL_TexEnv(GL_REPLACE); } void GL_SelectTexture(GLenum texture){ int tmu; if(!qglActiveTextureARB) return; if(texture == QGL_TEXTURE0){ tmu = 0; } else { tmu = 1; } if(tmu == gl_state.currenttmu){ return; } gl_state.currenttmu = tmu; qglActiveTextureARB(texture); qglClientActiveTextureARB(texture); } void GL_TexEnv(GLenum mode){ static int lastmodes[2] = { -1, -1 }; if(mode != lastmodes[gl_state.currenttmu]){ qglTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, mode); lastmodes[gl_state.currenttmu] = mode; } } void GL_Bind(int texnum){ if(gl_state.currenttextures[gl_state.currenttmu] == texnum) return; gl_state.currenttextures[gl_state.currenttmu] = texnum; qglBindTexture(GL_TEXTURE_2D, texnum); } void GL_MBind(GLenum target, int texnum){ GL_SelectTexture(target); if(target == QGL_TEXTURE0){ if(gl_state.currenttextures[0] == texnum) return; } else { if(gl_state.currenttextures[1] == texnum) return; } GL_Bind(texnum); } typedef struct { char *name; int minimize, maximize; } glmode_t; glmode_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} }; #define NUM_GL_MODES (sizeof(modes) / sizeof(glmode_t)) typedef struct { char *name; int mode; } gltmode_t; gltmode_t gl_alpha_modes[] = { {"default", 4}, {"GL_RGBA", GL_RGBA}, {"GL_RGBA8", GL_RGBA8}, {"GL_RGB5_A1", GL_RGB5_A1}, {"GL_RGBA4", GL_RGBA4}, {"GL_RGBA2", GL_RGBA2}, }; #define NUM_GL_ALPHA_MODES (sizeof(gl_alpha_modes) / sizeof(gltmode_t)) gltmode_t gl_solid_modes[] = { {"default", 3}, {"GL_RGB", GL_RGB}, {"GL_RGB8", GL_RGB8}, {"GL_RGB5", GL_RGB5}, {"GL_RGB4", GL_RGB4}, {"GL_R3_G3_B2", GL_R3_G3_B2}, }; #define NUM_GL_SOLID_MODES (sizeof(gl_solid_modes) / sizeof(gltmode_t)) /* GL_TextureMode */ void GL_TextureMode(char *string){ int i; image_t *glt; for(i = 0; i < NUM_GL_MODES; i++){ if(!Q_stricmp(modes[i].name, string)) break; } if(i == NUM_GL_MODES){ Com_Printf( "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, glt = gltextures; i < numgltextures; i++, glt++){ if(glt->type != it_pic && glt->type != it_sky){ GL_Bind(glt->texnum); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); } } } /* GL_TextureAlphaMode */ void GL_TextureAlphaMode(char *string){ int i; for(i = 0; i < NUM_GL_ALPHA_MODES; i++){ if(!Q_stricmp(gl_alpha_modes[i].name, string)) break; } if(i == NUM_GL_ALPHA_MODES){ Com_Printf( "bad alpha texture mode name\n"); return; } gl_tex_alpha_format = gl_alpha_modes[i].mode; } /* GL_TextureSolidMode */ void GL_TextureSolidMode(char *string){ int i; for(i = 0; i < NUM_GL_SOLID_MODES; i++){ if(!Q_stricmp(gl_solid_modes[i].name, string)) break; } if(i == NUM_GL_SOLID_MODES){ Com_Printf( "bad solid texture mode name\n"); return; } gl_tex_solid_format = gl_solid_modes[i].mode; } /* GL_ImageList_f */ void GL_ImageList_f(void){ int i; image_t *image; int texels; Com_Printf( "------------------\n"); texels = 0; for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(image->texnum <= 0) continue; texels += image->upload_width * image->upload_height; switch(image->type){ case it_skin: Com_Printf( "M"); break; case it_sprite: Com_Printf( "S"); break; case it_wall: Com_Printf( "W"); break; case it_pic: Com_Printf( "P"); break; default: Com_Printf( " "); break; } Com_Printf( " %3i %3i: %s\n", image->upload_width, image->upload_height, image->name); } Com_Printf( "Total texel count (not counting mipmaps): %i\n", texels); } /* scrap allocation Allocate all the little status bar obejcts into a single texture to crutch up inefficient hardware / drivers */ #define MAX_SCRAPS 1 #define BLOCK_WIDTH 256 #define BLOCK_HEIGHT 256 int scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH]; byte scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT]; qboolean scrap_dirty; // returns a texture number and the position inside it int Scrap_AllocBlock(int w, int h, int *x, int *y){ int i, j; int best, best2; int texnum; for(texnum = 0; texnum < MAX_SCRAPS; texnum++){ best = BLOCK_HEIGHT; for(i = 0; i < BLOCK_WIDTH - w; i++){ best2 = 0; for(j = 0; j < w; j++){ if(scrap_allocated[texnum][i + j] >= best) break; if(scrap_allocated[texnum][i + j] > best2) best2 = scrap_allocated[texnum][i + j]; } if(j == w){ // this is a valid spot *x = i; *y = best = best2; } } if(best + h > BLOCK_HEIGHT) continue; for(i = 0; i < w; i++) scrap_allocated[texnum][*x + i] = best + h; return texnum; } return -1; } int scrap_uploads; void Scrap_Upload(void){ scrap_uploads++; GL_Bind(TEXNUM_SCRAPS); GL_Upload8(scrap_texels[0], BLOCK_WIDTH, BLOCK_HEIGHT, false, false); scrap_dirty = false; } /* IMAGE FLOOD FILLING */ /* GL_FloodFillSkin Fill background pixels so mipmapping doesn't have haloes */ typedef struct { short x, y; } floodfill_t; // must be a power of 2 #define FLOODFILL_FIFO_SIZE 0x1000 #define FLOODFILL_FIFO_MASK (FLOODFILL_FIFO_SIZE - 1) #define FLOODFILL_STEP(off, dx, dy ) \ { \ if(pos[off] == fillcolor) \ { \ pos[off] = 255; \ fifo[inpt].x = x + (dx), fifo[inpt].y = y + (dy); \ inpt =(inpt + 1) & FLOODFILL_FIFO_MASK; \ } \ else if(pos[off] != 255) fdc = pos[off]; \ } void GL_FloodFillSkin(byte *skin, int skinwidth, int skinheight){ byte fillcolor = *skin; // assume this is the pixel to fill floodfill_t fifo[FLOODFILL_FIFO_SIZE]; int inpt = 0, outpt = 0; int filledcolor = -1; int i; if(filledcolor == -1){ filledcolor = 0; // attempt to find opaque black for(i = 0; i < 256; ++i){ if(d_8to24table[i] == (255 << 0)){ // alpha 1.0 filledcolor = i; break; } } } // can't fill to filled color or to transparent color (used as visited marker) if((fillcolor == filledcolor) || (fillcolor == 255)){ return; } fifo[inpt].x = 0, fifo[inpt].y = 0; inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; while(outpt != inpt){ int x = fifo[outpt].x, y = fifo[outpt].y; int fdc = filledcolor; byte *pos = &skin[x + skinwidth * y]; outpt = (outpt + 1) & FLOODFILL_FIFO_MASK; if(x > 0) FLOODFILL_STEP(-1, -1, 0); if(x < skinwidth - 1) FLOODFILL_STEP(1, 1, 0); if(y > 0) FLOODFILL_STEP(-skinwidth, 0, -1); if(y < skinheight - 1) FLOODFILL_STEP(skinwidth, 0, 1); skin[x + skinwidth * y] = fdc; } } /* GL_ResampleTexture */ void GL_ResampleTexture(unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight){ int i, j; unsigned *inrow, *inrow2; unsigned frac, fracstep; unsigned p1[1024], p2[1024]; byte *pix1, *pix2, *pix3, *pix4; fracstep = inwidth * 0x10000 / outwidth; frac = fracstep >> 2; for(i = 0; i < outwidth; i++){ p1[i] = 4 *(frac >> 16); frac += fracstep; } frac = 3 *(fracstep >> 2); for(i = 0; i < outwidth; i++){ p2[i] = 4 *(frac >> 16); frac += fracstep; } for(i = 0; i < outheight; i++, out += outwidth){ inrow = in + inwidth *(int)((i + 0.25) * inheight / outheight); inrow2 = in + inwidth *(int)((i + 0.75) * inheight / outheight); frac = fracstep >> 1; for(j = 0; j < outwidth; j++){ pix1 =(byte *)inrow + p1[j]; pix2 =(byte *)inrow + p2[j]; pix3 =(byte *)inrow2 + p1[j]; pix4 =(byte *)inrow2 + p2[j]; ((byte *)(out + j))[0] =(pix1[0] + pix2[0] + pix3[0] + pix4[0]) >> 2; ((byte *)(out + j))[1] =(pix1[1] + pix2[1] + pix3[1] + pix4[1]) >> 2; ((byte *)(out + j))[2] =(pix1[2] + pix2[2] + pix3[2] + pix4[2]) >> 2; ((byte *)(out + j))[3] =(pix1[3] + pix2[3] + pix3[3] + pix4[3]) >> 2; } } } /* GL_LightScaleTexture Scale up the pixel values in a texture to increase the lighting range */ void GL_LightScaleTexture(unsigned *in, int inwidth, int inheight){ int i, c; byte *p; p = (byte *)in; c = inwidth * inheight; for(i = 0; i < c; i++, p += 4){ p[0] = intensitytable[p[0]]; p[1] = intensitytable[p[1]]; p[2] = intensitytable[p[2]]; } } /* GL_MipMap Operates in place, quartering the size of the texture */ void GL_MipMap(byte *in, int width, int height){ int i, j; byte *out; width <<= 2; height >>= 1; out = in; for(i = 0; i < height; i++, in += width){ for(j = 0; j < width; j += 8, out += 4, in += 8){ out[0] = (in[0] + in[4] + in[width + 0] + in[width + 4]) >> 2; out[1] = (in[1] + in[5] + in[width + 1] + in[width + 5]) >> 2; out[2] = (in[2] + in[6] + in[width + 2] + in[width + 6]) >> 2; out[3] = (in[3] + in[7] + in[width + 3] + in[width + 7]) >> 2; } } } /* GL_Upload32 Returns has_alpha */ int upload_width, upload_height; qboolean GL_Upload32(unsigned *data, int width, int height, qboolean mipmap){ unsigned scaled[256*256]; int scaled_width, scaled_height; int i, c, comp; byte *scan; for(scaled_width = 1; scaled_width < width; scaled_width <<= 1) ; if(gl_rounddown->value && scaled_width > width && mipmap) scaled_width >>= 1; for(scaled_height = 1; scaled_height < height; scaled_height <<= 1) ; if(gl_rounddown->value && scaled_height > height && mipmap) scaled_height >>= 1; // let people sample down the world textures for speed if(mipmap){ scaled_width >>= (int)gl_picmip->value; scaled_height >>= (int)gl_picmip->value; } // don't ever bother with >256 textures if(scaled_width > 256) scaled_width = 256; if(scaled_height > 256) scaled_height = 256; if(scaled_width < 1) scaled_width = 1; if(scaled_height < 1) scaled_height = 1; upload_width = scaled_width; upload_height = scaled_height; if(scaled_width * scaled_height > sizeof(scaled) / 4) Com_Error(ERR_DROP, "GL_Upload32: too big"); // scan the texture for any non-255 alpha c = width * height; scan = ((byte *)data) + 3; comp = gl_solid_format; for(i = 0; i < c; i++, scan += 4){ if(*scan != 255){ comp = gl_alpha_format; break; } } if(scaled_width == width && scaled_height == height){ // natural width/height if(!mipmap){ qglTexImage2D(GL_TEXTURE_2D, 0, comp, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); goto done; } memcpy(scaled, data, width * height * 4); } else { GL_ResampleTexture(data, width, height, scaled, scaled_width, scaled_height); } GL_LightScaleTexture(scaled, scaled_width, scaled_height); qglTexImage2D(GL_TEXTURE_2D, 0, comp, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled); if(mipmap){ int miplevel; miplevel = 0; while(scaled_width > 1 || scaled_height > 1){ GL_MipMap((byte *)scaled, scaled_width, scaled_height); scaled_width >>= 1; scaled_height >>= 1; if(scaled_width < 1) scaled_width = 1; if(scaled_height < 1) scaled_height = 1; miplevel++; qglTexImage2D(GL_TEXTURE_2D, miplevel, comp, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled); } } done: if(mipmap){ qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); } else { qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max); qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max); } return (comp == gl_alpha_format); } /* GL_Upload8 */ qboolean GL_Upload8(byte *data, int width, int height, qboolean mipmap, qboolean is_sky){ unsigned trans[512 * 256]; int i, s; int p; s = width * height; if(s > sizeof(trans) / 4) Com_Error(ERR_DROP, "GL_Upload8: too large"); for(i = 0; i < s; i++){ p = data[i]; trans[i] = d_8to24table[p]; if(p == 255){ // transparent, so scan around for another color // to avoid alpha fringes // FIXME: do a full flood fill so mips work... if(i > width && data[i - width] != 255) p = data[i - width]; else if(i < s - width && data[i + width] != 255) p = data[i + width]; else if(i > 0 && data[i - 1] != 255) p = data[i - 1]; else if(i < s - 1 && data[i + 1] != 255) p = data[i + 1]; else p = 0; // copy rgb components ((byte *)&trans[i])[0] = ((byte *) & d_8to24table[p])[0]; ((byte *)&trans[i])[1] = ((byte *) & d_8to24table[p])[1]; ((byte *)&trans[i])[2] = ((byte *) & d_8to24table[p])[2]; } } return GL_Upload32(trans, width, height, mipmap); } /* GL_LoadPic This is also used as an entry point for the generated r_notexture. */ image_t *GL_LoadPic(char *name, byte *pic, int width, int height, imagetype_t type, int bits){ extern cvar_t *cl_brightskins; image_t *image; int i, bright; // find a free image_t for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(!image->texnum) break; } if(i == numgltextures){ if(numgltextures == MAX_GLTEXTURES) Com_Error(ERR_DROP, "MAX_GLTEXTURES"); numgltextures++; } image = &gltextures[i]; if(strlen(name) >= sizeof(image->name)) Com_Error(ERR_DROP, "Draw_LoadPic: \"%s\" is too long", name); strcpy(image->name, name); image->registration_sequence = registration_sequence; image->width = width; image->height = height; image->type = type; if(cl_brightskins && cl_brightskins->value && // catch skins and icons, but not weapon skin strstr(name, "players/") && !strstr(name, "weapon.pcx")){ // green girls, red boys bright = strstr(name, "female/") ? 208 : 240; memset(pic, bright, width * height); goto nonscrap; } if(type == it_skin && bits == 8) GL_FloodFillSkin(pic, width, height); // load little pics into the scrap if(image->type == it_pic && bits == 8 && image->width < 64 && image->height < 64){ int x = 0, y = 0; int i, j, k; int texnum; texnum = Scrap_AllocBlock(image->width, image->height, &x, &y); if(texnum == -1) goto nonscrap; scrap_dirty = true; // copy the texels into the scrap block k = 0; for(i = 0; i < image->height; i++) for(j = 0; j < image->width; j++, k++) scrap_texels[texnum][(y + i) * BLOCK_WIDTH + x + j] = pic[k]; image->texnum = TEXNUM_SCRAPS + texnum; image->scrap = true; image->has_alpha = true; image->sl = (x + 0.01) / (float)BLOCK_WIDTH; image->sh = (x + image->width - 0.01) / (float)BLOCK_WIDTH; image->tl = (y + 0.01) / (float)BLOCK_WIDTH; image->th = (y + image->height - 0.01) / (float)BLOCK_WIDTH; } else { nonscrap: image->scrap = false; image->texnum = TEXNUM_IMAGES + (image - gltextures); GL_Bind(image->texnum); if(bits == 8) image->has_alpha = GL_Upload8(pic, width, height, (image->type != it_pic && image->type != it_sky), image->type == it_sky); else image->has_alpha = GL_Upload32((unsigned *)pic, width, height, (image->type != it_pic && image->type != it_sky)); image->upload_width = upload_width; // after power of 2 and scales image->upload_height = upload_height; image->sl = 0; image->sh = 1; image->tl = 0; image->th = 1; } return image; } /* GL_LoadWal */ image_t *GL_LoadWal(char *name){ miptex_t *mt; int width, height, ofs; image_t *image; FS_LoadFile(name,(void **)(char *) &mt); if(!mt){ Com_Printf( "GL_FindImage: can't load %s\n", name); return r_notexture; } width = LittleLong(mt->width); height = LittleLong(mt->height); ofs = LittleLong(mt->offsets[0]); image = GL_LoadPic(name,(byte *)mt + ofs, width, height, it_wall, 8); FS_FreeFile((void *)mt); return image; } /* GL_FindImage Finds or loads the given image */ image_t *GL_FindImage(char *name, imagetype_t type){ image_t *image; int i, len; byte *pic, *palette; int width, height; if(!name) return NULL; len = strlen(name); if(len < 5) return NULL; // look for it for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(!strncmp(name, image->name, len - 3)){ image->registration_sequence = registration_sequence; return image; } } // load the pic from disk pic = NULL; palette = NULL; if(!strcmp(name + len - 4, ".pcx")){ LoadPCX(name, &pic, &palette, &width, &height); if(!pic) return NULL; image = GL_LoadPic(name, pic, width, height, type, 8); } else if(!strcmp(name + len - 4, ".tga")){ LoadTGA(name, &pic, &width, &height); if(!pic) return NULL; image = GL_LoadPic(name, pic, width, height, type, 32); } else if(!strcmp(name + len - 4, ".wal")){ image = GL_LoadWal(name); } else return NULL; if(pic) free(pic); if(palette) free(palette); return image; } // keep track of registered skins for sexed sounds char registered_skins[MAX_MD2SKINS][MAX_QPATH]; int registered_skins_index; /* GL_RegisterSkin */ struct image_s *GL_RegisterSkin(char *name){ struct image_s *image; char *c, skin[MAX_QPATH]; int i; if((image = GL_FindImage(name, it_skin))){ // make sure we've tagged it c = strchr(name, '/') + 1; // chop to "model/skinname" strncpy(skin, c, sizeof(skin) - 1); c = strchr(skin, '.'); *c = 0; for(i = 0; i < registered_skins_index; i++) if(!strcmp(skin, registered_skins[i])) break; if(i == registered_skins_index) // new skin, tag it strcpy(registered_skins[registered_skins_index++], skin); } return image; } /* GL_FreeUnusedImages Any image that was not touched on this registration sequence will be freed. */ void GL_FreeUnusedImages(void){ int i; image_t *image; // never free r_notexture or particle texture r_notexture->registration_sequence = registration_sequence; r_particletexture->registration_sequence = registration_sequence; for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(image->registration_sequence == registration_sequence) continue; // used this sequence if(!image->registration_sequence) continue; // free image_t slot if(image->type == it_pic) continue; // don't free pics // free it qglDeleteTextures(1, &image->texnum); memset(image, 0, sizeof(*image)); } } /* Draw_GetPalette */ int Draw_GetPalette(void){ int i; int r, g, b; unsigned v; byte *pic, *pal; int width, height; // get the palette LoadPCX("pics/colormap.pcx", &pic, &pal, &width, &height); if(!pal) Com_Error(ERR_DROP, "Couldn't load pics/colormap.pcx"); for(i = 0; i < 256; i++){ r = pal[i * 3 + 0]; g = pal[i * 3 + 1]; b = pal[i * 3 + 2]; v = (255 << 24) + (r << 0) + (g << 8) + (b << 16); d_8to24table[i] = LittleLong(v); } d_8to24table[255] &= LittleLong(0xffffff); // 255 is transparent free(pic); free(pal); return 0; } /* GL_InitImages */ void GL_InitImages(void){ int i, j; registration_sequence = 1; if(gl_intensity->value <= 1) Cvar_Set("gl_intensity", "1"); gl_state.inverse_intensity = 1 / gl_intensity->value; Draw_GetPalette(); for(i = 0; i < 256; i++){ j = i * gl_intensity->value; if(j > 255) j = 255; intensitytable[i] = j; } } /* GL_ShutdownImages */ void GL_ShutdownImages(void){ int i; image_t *image; for(i = 0, image = gltextures; i < numgltextures; i++, image++){ if(!image->registration_sequence) continue; // free image_t slot // free it qglDeleteTextures(1, &image->texnum); memset(image, 0, sizeof(*image)); } registered_skins_index = 0; }