/* Copyright (C) 1996-2001 Id Software, Inc. Copyright (C) 2002-2009 John Fitzgibbons and others Copyright (C) 2007-2008 Kristian Duske Copyright (C) 2010-2014 QuakeSpasm developers 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. */ // draw.c -- 2d drawing #include "q_defs.h" //extern uint8_t d_15to8table[65536]; //johnfitz -- never used cvar_t scr_conalpha = {"scr_conalpha", "0.5", CVAR_ARCHIVE}; //johnfitz qpic_t *draw_disc; qpic_t *draw_backtile; gltexture_t *char_texture; //johnfitz qpic_t *pic_ovr, *pic_ins; //johnfitz -- new cursor handling qpic_t *pic_nul; //johnfitz -- for missing gfx, don't crash //johnfitz -- new pics byte pic_ovr_data[8][8] = { {255, 255, 255, 255, 255, 255, 255, 255}, {255, 15, 15, 15, 15, 15, 15, 255}, {255, 15, 15, 15, 15, 15, 15, 2}, {255, 15, 15, 15, 15, 15, 15, 2}, {255, 15, 15, 15, 15, 15, 15, 2}, {255, 15, 15, 15, 15, 15, 15, 2}, {255, 15, 15, 15, 15, 15, 15, 2}, {255, 255, 2, 2, 2, 2, 2, 2}, }; byte pic_ins_data[9][8] = { { 15, 15, 255, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, { 15, 15, 2, 255, 255, 255, 255, 255}, {255, 2, 2, 255, 255, 255, 255, 255}, }; byte pic_nul_data[8][8] = { {252, 252, 252, 252, 0, 0, 0, 0}, {252, 252, 252, 252, 0, 0, 0, 0}, {252, 252, 252, 252, 0, 0, 0, 0}, {252, 252, 252, 252, 0, 0, 0, 0}, { 0, 0, 0, 0, 252, 252, 252, 252}, { 0, 0, 0, 0, 252, 252, 252, 252}, { 0, 0, 0, 0, 252, 252, 252, 252}, { 0, 0, 0, 0, 252, 252, 252, 252}, }; byte pic_stipple_data[8][8] = { {255, 0, 0, 0, 255, 0, 0, 0}, { 0, 0, 255, 0, 0, 0, 255, 0}, {255, 0, 0, 0, 255, 0, 0, 0}, { 0, 0, 255, 0, 0, 0, 255, 0}, {255, 0, 0, 0, 255, 0, 0, 0}, { 0, 0, 255, 0, 0, 0, 255, 0}, {255, 0, 0, 0, 255, 0, 0, 0}, { 0, 0, 255, 0, 0, 0, 255, 0}, }; byte pic_crosshair_data[8][8] = { {255, 255, 255, 255, 255, 255, 255, 255}, {255, 255, 255, 8, 9, 255, 255, 255}, {255, 255, 255, 6, 8, 2, 255, 255}, {255, 6, 8, 8, 6, 8, 8, 255}, {255, 255, 2, 8, 8, 2, 2, 2}, {255, 255, 255, 7, 8, 2, 255, 255}, {255, 255, 255, 255, 2, 2, 255, 255}, {255, 255, 255, 255, 255, 255, 255, 255}, }; //johnfitz typedef struct { gltexture_t *gltexture; float sl, tl, sh, th; } glpic_t; canvastype currentcanvas = CANVAS_NONE; //johnfitz -- for GL_SetCanvas //============================================================================== // // PIC CACHING // //============================================================================== typedef struct cachepic_s { char name[MAX_QPATH]; qpic_t pic; byte padding[32]; // for appended glpic } cachepic_t; #define MAX_CACHED_PICS 128 cachepic_t menu_cachepics[MAX_CACHED_PICS]; int32_t menu_numcachepics; byte menuplyr_pixels[4096]; // scrap allocation // Allocate all the little status bar obejcts into a single texture // to crutch up stupid hardware / drivers #define MAX_SCRAPS 2 #define BLOCK_WIDTH 256 #define BLOCK_HEIGHT 256 int32_t scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH]; byte scrap_texels[MAX_SCRAPS][BLOCK_WIDTH * BLOCK_HEIGHT]; //johnfitz -- removed *4 after BLOCK_HEIGHT bool scrap_dirty; gltexture_t *scrap_textures[MAX_SCRAPS]; //johnfitz /* ================ Scrap_AllocBlock returns an index into scrap_texnums[] and the position inside it ================ */ int32_t Scrap_AllocBlock(int32_t w, int32_t h, int32_t *x, int32_t *y) { int32_t i, j; int32_t best, best2; int32_t 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; } Sys_Error("Scrap_AllocBlock: full"); //johnfitz -- correct function name return 0; //johnfitz -- shut up compiler } /* ================ Scrap_Upload -- johnfitz -- now uses TexMgr ================ */ void Scrap_Upload(void) { char name[8]; int32_t i; for(i = 0; i < MAX_SCRAPS; i++) { sprintf(name, "scrap%" PRIi32, i); scrap_textures[i] = TexMgr_LoadImage(NULL, name, BLOCK_WIDTH, BLOCK_HEIGHT, SRC_INDEXED, scrap_texels[i], "", (src_offset_t)scrap_texels[i], TEXPREF_ALPHA | TEXPREF_OVERWRITE | TEXPREF_NOPICMIP); } scrap_dirty = false; } /* ================ Draw_PicFromWad ================ */ qpic_t *Draw_PicFromWad(const char *name) { qpic_t *p; glpic_t gl; src_offset_t offset; //johnfitz p = (qpic_t *) W_GetLumpName(name); if(!p) return pic_nul; //johnfitz // load little ones into the scrap if(p->width < 64 && p->height < 64) { int32_t x = 0, y = 0; int32_t i, j, k; int32_t texnum; texnum = Scrap_AllocBlock(p->width, p->height, &x, &y); scrap_dirty = true; k = 0; for(i = 0 ; i < p->height ; i++) { for(j = 0 ; j < p->width ; j++, k++) scrap_texels[texnum][(y + i)*BLOCK_WIDTH + x + j] = p->data[k]; } gl.gltexture = scrap_textures[texnum]; //johnfitz -- changed to an array //johnfitz -- no longer go from 0.01 to 0.99 gl.sl = x / (float)BLOCK_WIDTH; gl.sh = (x + p->width) / (float)BLOCK_WIDTH; gl.tl = y / (float)BLOCK_WIDTH; gl.th = (y + p->height) / (float)BLOCK_WIDTH; } else { char texturename[64]; //johnfitz q_snprintf(texturename, sizeof(texturename), "%s:%s", WADFILENAME, name); //johnfitz offset = (src_offset_t)p - (src_offset_t)wad_base + sizeof(int32_t) * 2; //johnfitz gl.gltexture = TexMgr_LoadImage(NULL, texturename, p->width, p->height, SRC_INDEXED, p->data, WADFILENAME, offset, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr gl.sl = 0; gl.sh = (float)p->width / (float)TexMgr_PadConditional(p->width); //johnfitz gl.tl = 0; gl.th = (float)p->height / (float)TexMgr_PadConditional(p->height); //johnfitz } memcpy(p->data, &gl, sizeof(glpic_t)); return p; } /* ================ Draw_CachePic ================ */ qpic_t *Draw_CachePic(const char *path) { cachepic_t *pic; int32_t i; qpic_t *dat; glpic_t gl; for(pic = menu_cachepics, i = 0 ; i < menu_numcachepics ; pic++, i++) { if(!strcmp(path, pic->name)) return &pic->pic; } if(menu_numcachepics == MAX_CACHED_PICS) Sys_Error("menu_numcachepics == MAX_CACHED_PICS"); menu_numcachepics++; strcpy(pic->name, path); // // load the pic from disk // dat = (qpic_t *)COM_LoadTempFile(path, NULL); if(!dat) Sys_Error("Draw_CachePic: failed to load %s", path); SwapPic(dat); // HACK HACK HACK --- we need to keep the bytes for // the translatable player picture just for the menu // configuration dialog if(!strcmp(path, "gfx/menuplyr.lmp")) memcpy(menuplyr_pixels, dat->data, dat->width * dat->height); pic->pic.width = dat->width; pic->pic.height = dat->height; gl.gltexture = TexMgr_LoadImage(NULL, path, dat->width, dat->height, SRC_INDEXED, dat->data, path, sizeof(int32_t) * 2, TEXPREF_ALPHA | TEXPREF_PAD | TEXPREF_NOPICMIP); //johnfitz -- TexMgr gl.sl = 0; gl.sh = (float)dat->width / (float)TexMgr_PadConditional(dat->width); //johnfitz gl.tl = 0; gl.th = (float)dat->height / (float)TexMgr_PadConditional(dat->height); //johnfitz memcpy(pic->pic.data, &gl, sizeof(glpic_t)); return &pic->pic; } /* ================ Draw_MakePic -- johnfitz -- generate pics from internal data ================ */ qpic_t *Draw_MakePic(const char *name, int32_t width, int32_t height, byte *data) { int32_t flags = TEXPREF_NEAREST | TEXPREF_ALPHA | TEXPREF_PERSIST | TEXPREF_NOPICMIP | TEXPREF_PAD; qpic_t *pic; glpic_t gl; pic = Hunk_AllocName(sizeof(qpic_t) - 4 + sizeof(glpic_t), __func__); pic->width = width; pic->height = height; gl.gltexture = TexMgr_LoadImage(NULL, name, width, height, SRC_INDEXED, data, "", (src_offset_t)data, flags); gl.sl = 0; gl.sh = (float)width / (float)TexMgr_PadConditional(width); gl.tl = 0; gl.th = (float)height / (float)TexMgr_PadConditional(height); memcpy(pic->data, &gl, sizeof(glpic_t)); return pic; } //============================================================================== // // INIT // //============================================================================== /* =============== Draw_LoadPics -- johnfitz =============== */ void Draw_LoadPics(void) { byte *data; src_offset_t offset; data = (byte *) W_GetLumpName("conchars"); if(!data) Sys_Error("Draw_LoadPics: couldn't load conchars"); offset = (src_offset_t)data - (src_offset_t)wad_base; char_texture = TexMgr_LoadImage(NULL, WADFILENAME":conchars", 128, 128, SRC_INDEXED, data, WADFILENAME, offset, TEXPREF_ALPHA | TEXPREF_NEAREST | TEXPREF_NOPICMIP | TEXPREF_CONCHARS); draw_disc = Draw_PicFromWad("disc"); draw_backtile = Draw_PicFromWad("backtile"); } /* =============== Draw_NewGame -- johnfitz =============== */ void Draw_NewGame(void) { cachepic_t *pic; int32_t i; // empty scrap and reallocate gltextures memset(scrap_allocated, 0, sizeof(scrap_allocated)); memset(scrap_texels, 255, sizeof(scrap_texels)); Scrap_Upload(); //creates 2 empty gltextures // reload wad pics W_LoadWadFile(); //johnfitz -- filename is now hard-coded for honesty Draw_LoadPics(); SCR_LoadPics(); Sbar_LoadPics(); // empty lmp cache for(pic = menu_cachepics, i = 0; i < menu_numcachepics; pic++, i++) pic->name[0] = 0; menu_numcachepics = 0; } /* =============== Draw_Init -- johnfitz -- rewritten =============== */ void Draw_Init(void) { Cvar_RegisterVariable(&scr_conalpha); // clear scrap and allocate gltextures memset(scrap_allocated, 0, sizeof(scrap_allocated)); memset(scrap_texels, 255, sizeof(scrap_texels)); Scrap_Upload(); //creates 2 empty textures // create internal pics pic_ins = Draw_MakePic("ins", 8, 9, &pic_ins_data[0][0]); pic_ovr = Draw_MakePic("ovr", 8, 8, &pic_ovr_data[0][0]); pic_nul = Draw_MakePic("nul", 8, 8, &pic_nul_data[0][0]); // load game pics Draw_LoadPics(); } //============================================================================== // // 2D DRAWING // //============================================================================== /* ================ Draw_CharacterQuad -- johnfitz -- seperate function to spit out verts ================ */ void Draw_CharacterQuad(int32_t x, int32_t y, char num) { int32_t row, col; float frow, fcol, size; row = num >> 4; col = num & 15; frow = row * 0.0625; fcol = col * 0.0625; size = 0.0625; glTexCoord2f(fcol, frow); glVertex2f(x, y); glTexCoord2f(fcol + size, frow); glVertex2f(x + 8, y); glTexCoord2f(fcol + size, frow + size); glVertex2f(x + 8, y + 8); glTexCoord2f(fcol, frow + size); glVertex2f(x, y + 8); } /* ================ Draw_Character -- johnfitz -- modified to call Draw_CharacterQuad ================ */ void Draw_Character(int32_t x, int32_t y, int32_t num) { if(y <= -8) return; // totally off screen num &= 255; if(num == 32) return; //don't waste verts on spaces GL_Bind(char_texture); glBegin(GL_QUADS); Draw_CharacterQuad(x, y, (char) num); glEnd(); } /* ================ Draw_String -- johnfitz -- modified to call Draw_CharacterQuad ================ */ void Draw_String(int32_t x, int32_t y, const char *str) { if(y <= -8) return; // totally off screen GL_Bind(char_texture); glBegin(GL_QUADS); while(*str) { if(*str != 32) //don't waste verts on spaces Draw_CharacterQuad(x, y, *str); str++; x += 8; } glEnd(); } /* ============= Draw_Pic -- johnfitz -- modified ============= */ void Draw_Pic(int32_t x, int32_t y, qpic_t *pic) { glpic_t *gl; if(scrap_dirty) Scrap_Upload(); gl = (glpic_t *)pic->data; GL_Bind(gl->gltexture); glBegin(GL_QUADS); glTexCoord2f(gl->sl, gl->tl); glVertex2f(x, y); glTexCoord2f(gl->sh, gl->tl); glVertex2f(x + pic->width, y); glTexCoord2f(gl->sh, gl->th); glVertex2f(x + pic->width, y + pic->height); glTexCoord2f(gl->sl, gl->th); glVertex2f(x, y + pic->height); glEnd(); } /* ============= Draw_TransPicTranslate -- johnfitz -- rewritten to use texmgr to do translation Only used for the player color selection menu ============= */ void Draw_TransPicTranslate(int32_t x, int32_t y, qpic_t *pic, int32_t top, int32_t bottom) { static int32_t oldtop = -2; static int32_t oldbottom = -2; if(top != oldtop || bottom != oldbottom) { glpic_t *p = (glpic_t *)pic->data; gltexture_t *glt = p->gltexture; oldtop = top; oldbottom = bottom; TexMgr_ReloadImage(glt, top, bottom); } Draw_Pic(x, y, pic); } /* ================ Draw_ConsoleBackground -- johnfitz -- rewritten ================ */ void Draw_ConsoleBackground(void) { qpic_t *pic; float alpha; pic = Draw_CachePic("gfx/conback.lmp"); pic->width = vid.conwidth; pic->height = vid.conheight; alpha = (con_forcedup) ? 1.0 : scr_conalpha.value; GL_SetCanvas(CANVAS_CONSOLE); //in case this is called from weird places if(alpha > 0.0) { if(alpha < 1.0) { glEnable(GL_BLEND); glColor4f(1, 1, 1, alpha); glDisable(GL_ALPHA_TEST); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } Draw_Pic(0, 0, pic); if(alpha < 1.0) { glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); glEnable(GL_ALPHA_TEST); glDisable(GL_BLEND); glColor4f(1, 1, 1, 1); } } } /* ============= Draw_TileClear This repeats a 64*64 tile graphic to fill the screen around a sized down refresh window. ============= */ void Draw_TileClear(int32_t x, int32_t y, int32_t w, int32_t h) { glpic_t *gl; gl = (glpic_t *)draw_backtile->data; glColor3f(1, 1, 1); GL_Bind(gl->gltexture); glBegin(GL_QUADS); glTexCoord2f(x / 64.0, y / 64.0); glVertex2f(x, y); glTexCoord2f((x + w) / 64.0, y / 64.0); glVertex2f(x + w, y); glTexCoord2f((x + w) / 64.0, (y + h) / 64.0); glVertex2f(x + w, y + h); glTexCoord2f(x / 64.0, (y + h) / 64.0); glVertex2f(x, y + h); glEnd(); } /* ============= Draw_Fill Fills a box of pixels with a single color ============= */ void Draw_Fill(int32_t x, int32_t y, int32_t w, int32_t h, int32_t c, float alpha) //johnfitz -- added alpha { byte *pal = (byte *)d_8to24table; //johnfitz -- use d_8to24table instead of host_basepal glDisable(GL_TEXTURE_2D); glEnable(GL_BLEND); //johnfitz -- for alpha glDisable(GL_ALPHA_TEST); //johnfitz -- for alpha glColor4f(pal[c * 4] / 255.0, pal[c * 4 + 1] / 255.0, pal[c * 4 + 2] / 255.0, alpha); //johnfitz -- added alpha glBegin(GL_QUADS); glVertex2f(x, y); glVertex2f(x + w, y); glVertex2f(x + w, y + h); glVertex2f(x, y + h); glEnd(); glColor3f(1, 1, 1); glDisable(GL_BLEND); //johnfitz -- for alpha glEnable(GL_ALPHA_TEST); //johnfitz -- for alpha glEnable(GL_TEXTURE_2D); } /* ================ Draw_FadeScreen -- johnfitz -- revised ================ */ void Draw_FadeScreen(void) { GL_SetCanvas(CANVAS_DEFAULT); glEnable(GL_BLEND); glDisable(GL_ALPHA_TEST); glDisable(GL_TEXTURE_2D); glColor4f(0, 0, 0, 0.5); glBegin(GL_QUADS); glVertex2f(0, 0); glVertex2f(glwidth, 0); glVertex2f(glwidth, glheight); glVertex2f(0, glheight); glEnd(); glColor4f(1, 1, 1, 1); glEnable(GL_TEXTURE_2D); glEnable(GL_ALPHA_TEST); glDisable(GL_BLEND); Sbar_Changed(); } /* ================ GL_SetCanvas -- johnfitz -- support various canvas types ================ */ void GL_SetCanvas(canvastype newcanvas) { extern vrect_t scr_vrect; float s; int32_t lines; if(newcanvas == currentcanvas) return; currentcanvas = newcanvas; glMatrixMode(GL_PROJECTION); glLoadIdentity(); switch(newcanvas) { case CANVAS_DEFAULT: glOrtho(0, glwidth, glheight, 0, -99999, 99999); glViewport(glx, gly, glwidth, glheight); break; case CANVAS_CONSOLE: lines = vid.conheight - (scr_con_current * vid.conheight / glheight); glOrtho(0, vid.conwidth, vid.conheight + lines, lines, -99999, 99999); glViewport(glx, gly, glwidth, glheight); break; case CANVAS_MENU: s = q_min((float)glwidth / 320.0, (float)glheight / 200.0); s = CLAMP(1.0, scr_menuscale.value, s); // ericw -- doubled width to 640 to accommodate long keybindings glOrtho(0, 640, 200, 0, -99999, 99999); glViewport(glx + (glwidth - 320 * s) / 2, gly + (glheight - 200 * s) / 2, 640 * s, 200 * s); break; case CANVAS_SBAR: s = CLAMP(1.0, scr_sbarscale.value, (float)glwidth / 320.0); if(cl.gametype == GAME_DEATHMATCH) { glOrtho(0, glwidth / s, 48, 0, -99999, 99999); glViewport(glx, gly, glwidth, 48 * s); } else { glOrtho(0, 320, 48, 0, -99999, 99999); glViewport(glx + (glwidth - 320 * s) / 2, gly, 320 * s, 48 * s); } break; case CANVAS_WARPIMAGE: glOrtho(0, 128, 0, 128, -99999, 99999); glViewport(glx, gly + glheight - gl_warpimagesize, gl_warpimagesize, gl_warpimagesize); break; case CANVAS_CROSSHAIR: //0,0 is center of viewport s = CLAMP(1.0, scr_crosshairscale.value, 10.0); glOrtho(scr_vrect.width / -2 / s, scr_vrect.width / 2 / s, scr_vrect.height / 2 / s, scr_vrect.height / -2 / s, -99999, 99999); glViewport(scr_vrect.x, glheight - scr_vrect.y - scr_vrect.height, scr_vrect.width & ~1, scr_vrect.height & ~1); break; case CANVAS_BOTTOMLEFT: //used by devstats s = (float)glwidth / vid.conwidth; //use console scale glOrtho(0, 320, 200, 0, -99999, 99999); glViewport(glx, gly, 320 * s, 200 * s); break; case CANVAS_BOTTOMRIGHT: //used by fps/clock s = (float)glwidth / vid.conwidth; //use console scale glOrtho(0, 320, 200, 0, -99999, 99999); glViewport(glx + glwidth - 320 * s, gly, 320 * s, 200 * s); break; case CANVAS_TOPRIGHT: //used by disc s = 1; glOrtho(0, 320, 200, 0, -99999, 99999); glViewport(glx + glwidth - 320 * s, gly + glheight - 200 * s, 320 * s, 200 * s); break; default: Sys_Error("GL_SetCanvas: bad canvas type"); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } /* ================ GL_Set2D -- johnfitz -- rewritten ================ */ void GL_Set2D(void) { currentcanvas = CANVAS_INVALID; GL_SetCanvas(CANVAS_DEFAULT); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST); glColor4f(1, 1, 1, 1); }