/*
Copyright (C) 1996-1997 Id Software, Inc.

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.

*/
// gl_draw.c -- this is the only file outside the refresh that touches the vid buffer

#include "quakedef.h"

cvar_t	gl_picmip = {"gl_picmip", "0"};
cvar_t	gl_picmip_all = {"gl_picmip_all", "0"};
cvar_t	gl_conalpha = {"gl_conalpha", "0.8"};	// by joe
cvar_t	gl_max_size = {"gl_max_size", "1024", false, false, OnChange_gl_max_size};
cvar_t	gl_texturemode = {"gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST", false, false, OnChange_gl_texturemode};
cvar_t	gl_crosshairimage = {"crosshairimage", "", false, false, OnChange_gl_crosshairimage};
cvar_t  gl_smoothfont = {"gl_smoothfont", "1", false, false, OnChange_gl_smoothfont};
cvar_t	gl_crosshairalpha = {"crosshairalpha", "1"};

static qboolean no24bit;

byte			*draw_chars;			// 8*8 graphic characters
mpic_t			*draw_disc;
mpic_t			*draw_backtile;

int				texture_extension_number = 1;
int				translate_texture;
int				char_texture;

mpic_t			crosshairpic;
static qboolean	crosshairimage_loaded = false;

mpic_t			conback_data;
mpic_t			*conback = &conback_data;

int				gl_max_size_default;
int				gl_lightmap_format = 3, gl_solid_format = 3, gl_alpha_format = 4;

static	int		gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
static	int		gl_filter_max = GL_LINEAR;

#define		NUMCROSSHAIRS	5
static  int		crosshairtextures[NUMCROSSHAIRS];

static byte crosshairdata[NUMCROSSHAIRS][64] = {
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff, 0xfe, 0xff,
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,

	0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
	0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff,
	0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff,
	0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff,
	0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,

	0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 
	0xfe, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xfe, 0xff, 
	0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};

typedef struct
{
	char	 identifier[MAX_QPATH];
	char	 *pathname;	
	int		 texnum;
	int		 width, height;
	int		 scaled_width, scaled_height;
	int		 texmode;
	int		 bpp;
	unsigned crc;
} gltexture_t;

gltexture_t	gltextures[MAX_GLTEXTURES];
int			numgltextures;
int			currenttexture = -1;		// to avoid unnecessary texture sets

void GL_Bind (int texnum)
{
	if (currenttexture == texnum) return;

	currenttexture = texnum;

	glBindTexture (GL_TEXTURE_2D, texnum);
}


/*
=============================================================================

  scrap allocation

  Allocate all the little status bar objects into a single texture
  to crutch up stupid hardware / drivers

=============================================================================
*/

// some cards have low quality of alpha pics, so load the pics
// without transparent pixels into a different scrap block.
// scrap 0 is solid pics, 1 is transparent
#define	MAX_SCRAPS		2
#define	BLOCK_WIDTH		256
#define	BLOCK_HEIGHT	256

int		scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH];
byte	scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT*4];
int		scrap_dirty = 0;	// bit mask
int		scrap_texnum;
int		scrap_uploads;

// returns false if allocation failed
static qboolean Scrap_AllocBlock (int scrapnum, int w, int h, int *x, int *y)
{
	int	i, j, best, best2;

	best = BLOCK_HEIGHT;

	for (i=0 ; i < BLOCK_WIDTH - w ; i++)
	{
		best2 = 0;
		
		for (j=0 ; j<w ; j++)
		{
			if (scrap_allocated[scrapnum][i+j] >= best)
				break;
			if (scrap_allocated[scrapnum][i+j] > best2)
				best2 = scrap_allocated[scrapnum][i+j];
		}
		if (j == w)
		{	// this is a valid spot
			*x = i;
			*y = best = best2;
		}
	}

	if (best + h > BLOCK_HEIGHT)
		return false;

	for (i=0 ; i<w ; i++) {
		scrap_allocated[scrapnum][*x+i] = best + h;
	}
	scrap_dirty |= (1 << scrapnum);

	return true;
}

static void Scrap_Upload (void)
{
	int	i;

	scrap_uploads++;

	for (i=0 ; i<2 ; i++)
	{
		if (!(scrap_dirty & (1 << i))) continue;
		scrap_dirty &= ~(1 << i);
		GL_Bind (scrap_texnum + i);
		GL_Upload8 (scrap_texels[i], BLOCK_WIDTH, BLOCK_HEIGHT, TEX_ALPHA);
	}
}

//=============================================================================
/* Support Routines */

typedef struct cachepic_s
{
	char		name[MAX_QPATH];
	mpic_t		pic;
} cachepic_t;

#define	MAX_CACHED_PICS		128
static  cachepic_t	cachepics[MAX_CACHED_PICS];
static	int			numcachepics;

static	byte		menuplyr_pixels[4096];

static	int			pic_texels;
static	int			pic_count;

mpic_t *Draw_PicFromWad (char *name)
{
	qpic_t	*p;
	mpic_t	*pic, *pic_24bit;

	p = W_GetLumpName (name);
	pic = (mpic_t *)p;

	if ((pic_24bit = GL_LoadPicImage(va("textures/wad/%s", name), name, 0, 0, TEX_ALPHA)) || 
		(pic_24bit = GL_LoadPicImage(va("gfx/%s", name), name, 0, 0, TEX_ALPHA)))
	{
		memcpy (&pic->texnum, &pic_24bit->texnum, sizeof(mpic_t) - 8);
		return pic;
	}

	// load little ones into the scrap
	if (p->width < 64 && p->height < 64)
	{
		int	x, y, i, j, k, texnum;

		texnum = memchr (p->data, 255, p->width*p->height) != NULL;
		if (!Scrap_AllocBlock (texnum, p->width, p->height, &x, &y))
		{
			GL_LoadPicTexture (name, pic, p->data);
			return pic;
		}
		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];
		texnum += scrap_texnum;
		pic->texnum = texnum;
		pic->sl = (x + 0.01) / (float)BLOCK_WIDTH;
		pic->sh = (x + p->width - 0.01) / (float)BLOCK_WIDTH;
		pic->tl = (y + 0.01) / (float)BLOCK_WIDTH;
		pic->th = (y + p->height - 0.01) / (float)BLOCK_WIDTH;

		pic_count++;
		pic_texels += p->width * p->height;
	}
	else
	{
		GL_LoadPicTexture (name, pic, p->data);
	}
	return pic;
}


/*
================
Draw_CachePic
================
*/
mpic_t *Draw_CachePic (char *path)
{
	cachepic_t	*pic;
	int		i;
	qpic_t		*dat;
	mpic_t		*pic_24bit;

	for (pic=cachepics, i=0 ; i<numcachepics ; pic++, i++)
	{
		if (!strcmp(path, pic->name))
		{
			return &pic->pic;
		}
	}

	if (numcachepics == MAX_CACHED_PICS)
	{
		Sys_Error ("numcachepics == MAX_CACHED_PICS");
	}
	numcachepics++;
	strcpy (pic->name, path);

// load the pic from disk
	if (!(dat = (qpic_t *)COM_LoadTempFile(path)))
	{
		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;

	if (pic_24bit = GL_LoadPicImage(path, NULL, 0, 0, TEX_ALPHA))
	{
		memcpy (&pic->pic.texnum, &pic_24bit->texnum, sizeof(mpic_t) - 8);
	}
	else
	{
		GL_LoadPicTexture (path, &pic->pic, dat->data);
	}
	return &pic->pic;
}

void Draw_CharToConback (int num, byte *dest)
{
	int		row, col, drawline, x;
	byte	*source;

	row = num >> 4;
	col = num & 15;
	source = draw_chars + (row<<10) + (col<<3);

	drawline = 8;

	while (drawline--)
	{
		for (x=0 ; x<8 ; x++)
		{
			if (source[x] != 255)
			{
				dest[x] = 0x60 + source[x];
			}
		}
		source += 128;
		dest += 320;
	}

}

void Draw_LoadConback (void)
{
	qpic_t		*cb;
	int			start;
	mpic_t		*pic_24bit;

	start = Hunk_LowMark ();

	if (!(cb = (qpic_t *)COM_LoadHunkFile("gfx/conback.lmp")))
	{
		Sys_Error ("Couldn't load gfx/conback.lmp");
	}
	SwapPic (cb);

	if (cb->width != 320 || cb->height != 200)
	{
		Con_Printf ("Draw_LoadConback: conback.lmp size is not 320x200");
	}

	// hack the version number directly into the pic
	if ((pic_24bit = GL_LoadPicImage("gfx/conback", "conback", 0, 0, 0)))
	{
		memcpy (&conback->texnum, &pic_24bit->texnum, sizeof(mpic_t) - 8);
	}
	else
	{
		conback->width = cb->width;
		conback->height = cb->height;
		GL_LoadPicTexture ("conback", conback, cb->data);
	}
	conback->width = vid.conwidth;
	conback->height = vid.conheight;

	// free loaded console
	Hunk_FreeToLowMark (start);
}

qboolean OnChange_gl_max_size (cvar_t *var, char *string)
{
	int		i;
	float	newval = Q_atof(string);

	if (newval > gl_max_size_default)
	{
		Con_Printf ("Your hardware doesn't support texture sizes bigger than %dx%d\n", gl_max_size_default, gl_max_size_default);
		return true;
	}

	for (i = 1 ; i < newval ; i <<= 1);
	{
		if (i != newval)
		{
			Con_Printf ("Valid values for %s are powers of 2 only\n", var->name);
			return true;
		}
	}
	return false;
}

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 GLMODE_NUMODES	(sizeof(modes) / sizeof(glmode_t))

qboolean OnChange_gl_texturemode (cvar_t *var, char *string)
{
	int			i;
	gltexture_t	*glt;

	for (i=0 ; i<GLMODE_NUMODES ; i++)
	{
		if (!Q_strcasecmp (modes[i].name, string))
			break;
	}

	if (i == GLMODE_NUMODES)
	{
		Con_Printf ("bad filter name: %s\n", string);
		return true;
	}

	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->texmode & TEX_MIPMAP)
		{
			GL_Bind (glt->texnum);
			glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
			glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
		}
	}

	return false;
}

void Draw_LoadCharset (void)
{
	int		i;
	char	buf[128*256];
	char	*src, *dest;

	draw_chars = W_GetLumpName ("conchars");
	for (i=0 ; i<256*64 ; i++)
		if (draw_chars[i] == 0)
			draw_chars[i] = 255;	// proper transparent color

	if (!(char_texture = GL_LoadCharsetImage("charset", "pic:charset")))
	{
		// Convert the 128*128 conchars texture to 128*256 leaving empty space between 
		// rows so that chars don't stumble on each other because of texture smoothing.
		// This hack costs us 64K of GL texture memory
		memset (buf, 255, sizeof(buf));
		src = draw_chars;
		dest = buf;
		for (i=0 ; i<16 ; i++)
		{
			memcpy (dest, src, 128*8);
			src += 128*8;
			dest += 128*8*2;
		}

		char_texture = GL_LoadTexture ("pic:charset", 128, 256, buf, TEX_ALPHA, 1);
	}

	// smooth fonts
	if (gl_smoothfont.value)
	{
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	else
	{
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}
}

qboolean OnChange_gl_smoothfont (cvar_t *var, char *string)
{
	float	newval;

	newval = Q_atof (string);
	if (!newval == !gl_smoothfont.value || !char_texture)
		return false;

	GL_Bind (char_texture);

	if (newval)
	{
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
	else
	{
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}

	return false;
}

qboolean OnChange_gl_crosshairimage (cvar_t *var, char *string)
{
	mpic_t	*pic;

	if (!string[0])
	{
		crosshairimage_loaded = false;
		return false;
	}

	if (!(pic = GL_LoadPicImage(va("crosshairs/%s", string), "crosshair", 0, 0, TEX_ALPHA)))
	{
		crosshairimage_loaded = false;
		Con_Printf ("Couldn't load image %s\n", string);

		return false;
	}

	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	crosshairpic = *pic;
	crosshairimage_loaded = true;

	return false;
}

/*
===============
Draw_Init
===============
*/
void Draw_Init (void)
{
	int	i;

	Cvar_RegisterVariable (&gl_max_size);
	Cvar_RegisterVariable (&gl_picmip);
	Cvar_RegisterVariable (&gl_picmip_all);
	Cvar_RegisterVariable (&gl_conalpha);
	Cvar_RegisterVariable (&gl_smoothfont);
	Cvar_RegisterVariable (&gl_crosshairalpha);
	Cvar_RegisterVariable (&gl_crosshairimage);
	Cvar_RegisterVariable (&gl_texturemode);

	glGetIntegerv (GL_MAX_TEXTURE_SIZE, &gl_max_size_default);
	Cvar_SetValue ("gl_max_size", gl_max_size_default);

	no24bit = COM_CheckParm("-no24bit") ? true : false;

	// 3dfx can only handle 256 wide textures
	if (!Q_strncasecmp((char *)gl_renderer, "3dfx", 4) || strstr((char *)gl_renderer, "Glide"))
		Cvar_Set ("gl_max_size", "256");

	// load the console background and the charset
	// by hand, because we need to write the version
	// string into the background before turning
	// it into a texture

	Draw_LoadCharset ();
	Draw_LoadConback ();

	// save a texture slot for translated picture
	translate_texture = texture_extension_number++;

	// save slots for scraps
	scrap_texnum = texture_extension_number;
	texture_extension_number += MAX_SCRAPS;

	// Load the crosshair pics
	for (i=0 ; i<NUMCROSSHAIRS ; i++)
	{
		crosshairtextures[i] = GL_LoadTexture ("", 8, 8, crosshairdata[i], TEX_ALPHA, 1);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	}

	// get the other pics we need
	draw_disc = Draw_PicFromWad ("disc");
	draw_backtile = Draw_PicFromWad ("backtile");
}


/*
================
Draw_Character

Draws one 8*8 graphics character with 0 being transparent.
It can be clipped to the top of the screen to allow the console to be
smoothly scrolled off.
================
*/
void Draw_Character (int x, int y, int num)
{
	int		row, col;
	float	frow, fcol;

	if (y <= -8)
		return;			// totally off screen

	if (num == 32)
		return;		// space

	num &= 255;
	
	row = num >> 4;
	col = num & 15;

	frow = row * 0.0625;
	fcol = col * 0.0625;

	GL_Bind (char_texture);

	glBegin (GL_QUADS);
	glTexCoord2f (fcol, frow);
	glVertex2f (x, y);
	glTexCoord2f (fcol + 0.0625, frow);
	glVertex2f (x+8, y);
	glTexCoord2f (fcol + 0.0625, frow + 0.03125);
	glVertex2f (x+8, y+8);
	glTexCoord2f (fcol, frow + 0.03125);
	glVertex2f (x, y+8);
	glEnd ();
}

/*
================
Draw_String
================
*/
void Draw_String (int x, int y, char *str)
{
	float	frow, fcol;
	int		num;

	if (y <= -8) return;			// totally off screen
	if (!*str) return;

	GL_Bind (char_texture);

	glBegin (GL_QUADS);

	while (*str)		// stop rendering when out of characters
	{
		if ((num = *str++) != 32)	// skip spaces
		{
			frow = (float)(num >> 4)*0.0625;
			fcol = (float)(num & 15)*0.0625;
			glTexCoord2f (fcol, frow);
			glVertex2f (x, y);
			glTexCoord2f (fcol + 0.0625, frow);
			glVertex2f (x+8, y);
			glTexCoord2f (fcol + 0.0625, frow + 0.03125);
			glVertex2f (x+8, y+8);
			glTexCoord2f (fcol, frow + 0.03125);
			glVertex2f (x, y+8);
		}
		x += 8;
	}

	glEnd ();
}

/*
================
Draw_Alt_String
================
*/
void Draw_Alt_String (int x, int y, char *str)
{
	float	frow, fcol;
	int		num;

	if (y <= -8) return;			// totally off screen
	if (!*str) return;

	GL_Bind (char_texture);

	glBegin (GL_QUADS);

	while (*str)		// stop rendering when out of characters
	{
		if ((num = *str++|0x80) != (32|0x80))	// skip spaces
		{
			frow = (float)(num >> 4)*0.0625;
			fcol = (float)(num & 15)*0.0625;
			glTexCoord2f (fcol, frow);
			glVertex2f (x, y);
			glTexCoord2f (fcol + 0.0625, frow);
			glVertex2f (x+8, y);
			glTexCoord2f (fcol + 0.0625, frow + 0.03125);
			glVertex2f (x+8, y+8);
			glTexCoord2f (fcol, frow + 0.03125);
			glVertex2f (x, y+8);
		}
		x += 8;
	}

	glEnd ();
}

byte *Draw_StringToRGB (char *s)
{
	byte			*col;
	static	byte	rgb[4];

	Cmd_TokenizeString (s);
	if (Cmd_Argc() == 3)
	{
		rgb[0] = (byte)Q_atoi(Cmd_Argv(0));
		rgb[1] = (byte)Q_atoi(Cmd_Argv(1));
		rgb[2] = (byte)Q_atoi(Cmd_Argv(2));
	}
	else
	{
		col = (byte *)&d_8to24table[(byte)Q_atoi(s)];
		rgb[0] = col[0];
		rgb[1] = col[1];
		rgb[2] = col[2];
	}
	rgb[3] = 255;

	return rgb;
}

/*
================
Draw_Crosshair		-- joe, from FuhQuake
================
*/
void Draw_Crosshair (void)
{
	float			x, y, ofs1, ofs2, sh, th, sl, tl;
	byte			*col;
	extern vrect_t	scr_vrect;

	if ((crosshair.value >= 2 && crosshair.value <= NUMCROSSHAIRS + 1) || crosshairimage_loaded)
	{
		x = scr_vrect.x + scr_vrect.width / 2 + cl_crossx.value; 
		y = scr_vrect.y + scr_vrect.height / 2 + cl_crossy.value;

		if (!gl_crosshairalpha.value)
			return;

		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

		col = Draw_StringToRGB (crosshaircolor.string);

		if (gl_crosshairalpha.value)
		{
			glDisable (GL_ALPHA_TEST);
			glEnable (GL_BLEND);
			col[3] = bound(0, gl_crosshairalpha.value, 1) * 255;
			glColor4ubv (col);
		}
		else
		{
			glColor3ubv (col);
		}

		if (crosshairimage_loaded)
		{
			GL_Bind (crosshairpic.texnum);
			ofs1 = 4 - 4.0 / crosshairpic.width;
			ofs2 = 4 + 4.0 / crosshairpic.width;
			sh = crosshairpic.sh;
			sl = crosshairpic.sl;
			th = crosshairpic.th;
			tl = crosshairpic.tl;
		}
		else
		{
			GL_Bind (crosshairtextures[(int)crosshair.value-2]);
			ofs1 = 3.5;
			ofs2 = 4.5;
			tl = sl = 0;
			sh = th = 1;
		}

		ofs1 *= (vid.width / 320) * bound(0, crosshairsize.value, 20);
		ofs2 *= (vid.width / 320) * bound(0, crosshairsize.value, 20);

		glBegin (GL_QUADS);
		glTexCoord2f (sl, tl);
		glVertex2f (x - ofs1, y - ofs1);
		glTexCoord2f (sh, tl);
		glVertex2f (x + ofs2, y - ofs1);
		glTexCoord2f (sh, th);
		glVertex2f (x + ofs2, y + ofs2);
		glTexCoord2f (sl, th);
		glVertex2f (x - ofs1, y + ofs2);
		glEnd ();

		if (gl_crosshairalpha.value)
		{
			glDisable (GL_BLEND);
			glEnable (GL_ALPHA_TEST);
		}

		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		glColor3f (1, 1, 1);
	}
	else if (crosshair.value)
	{
		Draw_Character (scr_vrect.x + scr_vrect.width / 2 - 4 + cl_crossx.value, scr_vrect.y + scr_vrect.height / 2 - 4 + cl_crossy.value, '+');
	}
}

/*
================
Draw_TextBox
================
*/
void Draw_TextBox (int x, int y, int width, int lines)
{
	mpic_t	*p;
	int	cx, cy, n;

	// draw left side
	cx = x;
	cy = y;
	p = Draw_CachePic ("gfx/box_tl.lmp");
	Draw_TransPic (cx, cy, p);
	p = Draw_CachePic ("gfx/box_ml.lmp");
	
	for (n = 0; n < lines; n++)
	{
		cy += 8;
		Draw_TransPic (cx, cy, p);
	}
	p = Draw_CachePic ("gfx/box_bl.lmp");
	Draw_TransPic (cx, cy+8, p);

	// draw middle
	cx += 8;
	while (width > 0)
	{
		cy = y;
		p = Draw_CachePic ("gfx/box_tm.lmp");
		Draw_TransPic (cx, cy, p);
		p = Draw_CachePic ("gfx/box_mm.lmp");

		for (n = 0; n < lines; n++)
		{
			cy += 8;
			if (n == 1)
				p = Draw_CachePic ("gfx/box_mm2.lmp");
			Draw_TransPic (cx, cy, p);
		}
		p = Draw_CachePic ("gfx/box_bm.lmp");
		Draw_TransPic (cx, cy+8, p);
		width -= 2;
		cx += 16;
	}

	// draw right side
	cy = y;
	p = Draw_CachePic ("gfx/box_tr.lmp");
	Draw_TransPic (cx, cy, p);
	p = Draw_CachePic ("gfx/box_mr.lmp");
	
	for (n = 0; n < lines; n++)
	{
		cy += 8;
		Draw_TransPic (cx, cy, p);
	}
	p = Draw_CachePic ("gfx/box_br.lmp");
	Draw_TransPic (cx, cy+8, p);
}

/*
================
Draw_DebugChar

Draws a single character directly to the upper right corner of the screen.
This is for debugging lockups by drawing different chars in different parts
of the code.
================
*/
void Draw_DebugChar (char num)
{
}

/*
=============
Draw_AlphaPic
=============
*/
void Draw_AlphaPic (int x, int y, mpic_t *pic, float alpha)
{
	if (scrap_dirty)
	Scrap_Upload ();
	glDisable(GL_ALPHA_TEST);
	glEnable (GL_BLEND);
	glCullFace (GL_FRONT);
	glColor4f (1, 1, 1, alpha);
	GL_Bind (pic->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (pic->sl, pic->tl);
	glVertex2f (x, y);
	glTexCoord2f (pic->sh, pic->tl);
	glVertex2f (x+pic->width, y);
	glTexCoord2f (pic->sh, pic->th);
	glVertex2f (x+pic->width, y+pic->height);
	glTexCoord2f (pic->sl, pic->th);
	glVertex2f (x, y+pic->height);
	glEnd ();
	glColor3f (1, 1, 1);
	glEnable(GL_ALPHA_TEST);
	glDisable (GL_BLEND);
}


/*
=============
Draw_Pic
=============
*/
void Draw_Pic (int x, int y, mpic_t *pic)
{
	if (scrap_dirty)
	Scrap_Upload ();
	GL_Bind (pic->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (pic->sl, pic->tl);
	glVertex2f (x, y);
	glTexCoord2f (pic->sh, pic->tl);
	glVertex2f (x+pic->width, y);
	glTexCoord2f (pic->sh, pic->th);
	glVertex2f (x+pic->width, y+pic->height);
	glTexCoord2f (pic->sl, pic->th);
	glVertex2f (x, y+pic->height);
	glEnd ();
}

void Draw_SubPic (int x, int y, mpic_t *pic, int srcx, int srcy, int width, int height)
{
	float	newsl, newtl, newsh, newth, oldglwidth, oldglheight;

	if (scrap_dirty)
		Scrap_Upload ();
	
	oldglwidth = pic->sh - pic->sl;
	oldglheight = pic->th - pic->tl;

	newsl = pic->sl + (srcx * oldglwidth) / pic->width;
	newsh = newsl + (width * oldglwidth) / pic->width;

	newtl = pic->tl + (srcy * oldglheight) / pic->height;
	newth = newtl + (height * oldglheight) / pic->height;
	
	GL_Bind (pic->texnum);
	glBegin (GL_QUADS);
	glTexCoord2f (newsl, newtl);
	glVertex2f (x, y);
	glTexCoord2f (newsh, newtl);
	glVertex2f (x+width, y);
	glTexCoord2f (newsh, newth);
	glVertex2f (x+width, y+height);
	glTexCoord2f (newsl, newth);
	glVertex2f (x, y+height);
	glEnd ();
}

/*
=============
Draw_TransPic
=============
*/
void Draw_TransPic (int x, int y, mpic_t *pic)
{
	if (x < 0 || (unsigned)(x + pic->width) > vid.width || y < 0 || (unsigned)(y + pic->height) > vid.height)
		Sys_Error ("Draw_TransPic: bad coordinates");
	Draw_Pic (x, y, pic);
}


/*
=============
Draw_TransPicTranslate

Only used for the player color selection menu
=============
*/
void Draw_TransPicTranslate (int x, int y, mpic_t *pic, byte *translation)
{
	int			v, u, c, p;
	unsigned	trans[64*64], *dest;
	byte		*src;

	GL_Bind (translate_texture);

	c = pic->width * pic->height;

	dest = trans;
	
	for (v=0 ; v<64 ; v++, dest += 64)
	{
		src = &menuplyr_pixels[((v*pic->height)>>6)*pic->width];
		for (u=0 ; u<64 ; u++)
		{
			p = src[(u*pic->width)>>6];
			dest[u] = (p == 255) ? p : d_8to24table[translation[p]];
		}
	}

	glTexImage2D (GL_TEXTURE_2D, 0, gl_alpha_format, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, trans);

	glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glBegin (GL_QUADS);
	glTexCoord2f (0, 0);
	glVertex2f (x, y);
	glTexCoord2f (1, 0);
	glVertex2f (x+pic->width, y);
	glTexCoord2f (1, 1);
	glVertex2f (x+pic->width, y+pic->height);
	glTexCoord2f (0, 1);
	glVertex2f (x, y+pic->height);
	glEnd ();
}


/*
================
Draw_ConsoleBackground

================
*/
void Draw_ConsoleBackground (int lines)
{
	char	ver[80];

	if (!gl_conalpha.value && lines < vid.height)
		goto end;

	if (lines == vid.height)
		Draw_Pic (0, lines - vid.height, conback);
	else
		Draw_AlphaPic (0, lines - vid.height, conback, bound(0, gl_conalpha.value, 1));

end:
	sprintf (ver, "DemonQuake %s", DEMONQUAKE_VERSION);
	Draw_Alt_String (vid.conwidth - strlen(ver) * 8 - 8, lines - 10, ver);
}


/*
=============
Draw_TileClear

This repeats a 64*64 tile graphic to fill the screen around a sized down
refresh window.
=============
*/
void Draw_TileClear (int x, int y, int w, int h)
{
	GL_Bind (draw_backtile->texnum);
	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 (int x, int y, int w, int h, int c)
{
	glDisable (GL_TEXTURE_2D);
	glColor3f (host_basepal[c*3] / 255.0, host_basepal[c*3+1] / 255.0, host_basepal[c*3+2] / 255.0);

	glBegin (GL_QUADS);

	glVertex2f (x, y);
	glVertex2f (x+w, y);
	glVertex2f (x+w, y+h);
	glVertex2f (x, y+h);

	glEnd ();
	glColor3f (1, 1, 1);
	glEnable (GL_TEXTURE_2D);
}

//=============================================================================

/*
================
Draw_FadeScreen

================
*/
void Draw_FadeScreen (void)
{
	glEnable (GL_BLEND);
	glDisable (GL_TEXTURE_2D);
    glDisable(GL_ALPHA_TEST);
    glColor4f (0, 0, 0, 0.4);
	glBegin (GL_QUADS);

	glVertex2f (0,0);
	glVertex2f (vid.width, 0);
	glVertex2f (vid.width, vid.height);
	glVertex2f (0, vid.height);

	glEnd ();
	glColor4f (1,1,1,1);
	glEnable(GL_ALPHA_TEST);
	glEnable (GL_TEXTURE_2D);
	glDisable (GL_BLEND);

	Sbar_Changed();
}

//=============================================================================

/*
================
Draw_BeginDisc

Draws the little blue disc in the corner of the screen.
Call before beginning any disc IO.
================
*/
void Draw_BeginDisc (void)
{
	if (!draw_disc)	return;
	glDrawBuffer  (GL_FRONT);
	Draw_Pic (vid.width - 24, 0, draw_disc);
	glDrawBuffer  (GL_BACK);
}


/*
================
Draw_EndDisc

Erases the disc icon.
Call after completing any disc IO
================
*/
void Draw_EndDisc (void)
{
}

/*
================
GL_Set2D

Setup as if the screen was 320*200
================
*/
void GL_Set2D (void)
{
	glViewport (glx, gly, glwidth, glheight);

	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	glOrtho (0, vid.width, vid.height, 0, -99999, 99999);

	glMatrixMode (GL_MODELVIEW);
	glLoadIdentity ();

	glDisable (GL_DEPTH_TEST);
	glDisable (GL_CULL_FACE);
	glDisable (GL_BLEND);
	glEnable (GL_ALPHA_TEST);

	glColor3f (1, 1, 1);
}


//====================================================================

/*
===============
ColorDiff

Get the difference between 2 colours.  This is accomplished by first converting to
greyscale, then comparing and subtracting.  There are better ways of doing this, but
greyscaling gives the most appropriate visual results for Quake textures.  The
alpha component is generally ignored.
===============
*/
static unsigned int ColorDiff (unsigned int p1, unsigned int p2)
{
	byte *rgba1 = (byte *) &p1;
	byte *rgba2 = (byte *) &p2;

	int avg1;
	int avg2;

	avg1 = ((int) rgba1[0] + (int) rgba1[1] + (int) rgba1[2]) / 3;
	avg2 = ((int) rgba2[0] + (int) rgba2[1] + (int) rgba2[2]) / 3;

	if (avg1 > avg2) {
		return avg1 - avg2;
	} else {
		return avg2 - avg1;
	}
}

/*
===============
ColorAvg

Get the average of 2 colours, including alpha component
===============
*/
static unsigned int ColorAvg (unsigned int p1, unsigned int p2)
{
	byte			*rgba1 = (byte *) &p1;
	byte			*rgba2 = (byte *) &p2;
	byte			*rgbaavg;
	unsigned int	avg;

	rgbaavg = (byte *) &avg;

	rgbaavg[0] = (byte) (((int) rgba1[0] + (int) rgba2[0]) / 2);
	rgbaavg[1] = (byte) (((int) rgba1[1] + (int) rgba2[1]) / 2);
	rgbaavg[2] = (byte) (((int) rgba1[2] + (int) rgba2[2]) / 2);
	rgbaavg[3] = (byte) (((int) rgba1[3] + (int) rgba2[3]) / 2);

	return avg;
}

/*
===============
ChooseLerp

Choose an interpolation function based on the lowest difference (i.e. which colour is
nearest to the original pixel): "colormin" is a confusing name, really...
===============
*/
static unsigned int ChooseLerp (unsigned int me, unsigned int row, unsigned int col, unsigned int diag)
{
	unsigned int d1;
	unsigned int d2;
	unsigned int d3;
	unsigned int d4;
	unsigned int colormin;

	d1 = ColorDiff (me, row);
	d2 = ColorDiff (me, col);
	d3 = ColorDiff (me, diag);
	d4 = ColorDiff (row, col);

	colormin = d1;

	if (colormin > d2) colormin = d2;
	if (colormin > d3) colormin = d3;
	if (colormin > d4) colormin = d4;

	if (colormin == d1) {
		return ColorAvg (me, row);
	} else if (colormin == d2) {
		return ColorAvg (me, col);
	} else if (colormin == d3) {
		return ColorAvg (me, diag);
	} else {
		return ColorAvg (me, ColorAvg (row, col));
	}
}

/*
=================================
InterpolatePixels "Nice"

directional average scaling algorithm from http://www.compuphase.com/graphic/scale2.htm

the paper at the url above discusses use of bit interleaving to determine colour differences.
i've abandoned that as it gives undesirable results with quake textures (likely the fault of
quake textures rather than anything in the paper).  instead i'm using option 2: converting to
greyscale.  the speed implications mentioned in this paper don't really apply to quake as the 
calculations don't occur at run time and the bottleneck is elsewhere (OpenGL texture uploading).
=================================
*/
__inline void InterpolatePixels (unsigned int *scaled, int w, int h)
{
	int hcount;
	int wcount;
	int x;

	// 2D arrays to make life easier for all of us
	static unsigned int temp1[MAX_GLTEXTURES][MAX_GLTEXTURES];	// copy of original
	static unsigned int temp2[MAX_GLTEXTURES][MAX_GLTEXTURES];	// upsampled

	// row up, col left, row down, col right, same row, same col
	int pos[6];

	// copy the original data into our first 2D array
	for (hcount = 0, x = 0; hcount < h; hcount++) {
		for (wcount = 0; wcount < w; wcount++, x++)	{
			temp1[hcount][wcount] = scaled[x];
		}
	}

	for (hcount = 0; hcount < h; hcount++) {
		// determine the positions of the 8 surrounding pixels - part one: rows
		// row up
		pos[0] = hcount - 1;
		if (pos[0] < 0) pos[0] += h;

		// row down
		pos[2] = hcount + 1;
		if (pos[2] >= h) pos[2] -= h;

		// same row
		pos[4] = hcount;

		for (wcount = 0; wcount < w; wcount++) {
			// determine the positions of the 8 surrounding pixels - part two: columns
			// col left
			pos[1] = wcount - 1;
			if (pos[1] < 0) pos[1] += w;

			// col right
			pos[3] = wcount + 1;
			if (pos[3] >= w) pos[3] -= w;

			// same col
			pos[5] = wcount;

			// each pixel in temp1 maps to 4 pixels in temp2
			temp2[hcount * 2][wcount * 2] = ChooseLerp (temp1[pos[4]][pos[5]], temp1[pos[0]][pos[5]], temp1[pos[4]][pos[1]], temp1[pos[0]][pos[1]]);
			temp2[hcount * 2][wcount * 2 + 1] = ChooseLerp (temp1[pos[4]][pos[5]], temp1[pos[0]][pos[5]], temp1[pos[4]][pos[3]], temp1[pos[0]][pos[3]]);
			temp2[hcount * 2 + 1][wcount * 2] = ChooseLerp (temp1[pos[4]][pos[5]], temp1[pos[2]][pos[5]], temp1[pos[4]][pos[1]], temp1[pos[2]][pos[1]]);
			temp2[hcount * 2 + 1][wcount * 2 + 1] = ChooseLerp (temp1[pos[4]][pos[5]], temp1[pos[2]][pos[5]], temp1[pos[4]][pos[3]], temp1[pos[2]][pos[3]]);
		}
	}

	// copy temp2 back to scaled
	for (hcount = 0, x = 0; hcount < h * 2; hcount++, x++) {
		for (wcount = 0, x = 0; wcount < w * 2; wcount++, x++) {
			scaled[x] = temp2[hcount][wcount];
		}
	}
}

/*
================
ResampleTextureLerpLine
================
*/
__inline void ResampleTextureLerpLine (byte *in, byte *out, int inwidth, int outwidth) 
{
	int j, xi, oldx = 0, f, fstep, endx;

	fstep = (int) (inwidth * 65536.0f / outwidth);
	endx = (inwidth - 1);
	
	for (j = 0, f = 0; j < outwidth; j++, f += fstep) {
		xi = (int) f >> 16;
		if (xi != oldx) {
			in += (xi - oldx) * 4;
			oldx = xi;
		}

		if (xi < endx) {
			int lerp = f & 0xFFFF;
			*out++ = (byte) ((((in[4] - in[0]) * lerp) >> 16) + in[0]);
			*out++ = (byte) ((((in[5] - in[1]) * lerp) >> 16) + in[1]);
			*out++ = (byte) ((((in[6] - in[2]) * lerp) >> 16) + in[2]);
			*out++ = (byte) ((((in[7] - in[3]) * lerp) >> 16) + in[3]);
		} else  {		// last pixel of the line has no pixel to lerp to
			*out++ = in[0];
			*out++ = in[1];
			*out++ = in[2];
			*out++ = in[3];
		}
	}
}

/*
================
ResampleTexture
================
*/
__inline void ResampleTexture (unsigned *indata, int inwidth, int inheight, unsigned *outdata, int outwidth, int outheight) 
{
	if (gl_lerpimages.value) {
		int		i, j, yi, oldy, f, fstep, endy = (inheight - 1);
		byte	*inrow, *out, *row1, *row2, *memalloc;

		out = (byte *) outdata;
		fstep = (int) (inheight * 65536.0f / outheight);

		memalloc = Q_Malloc(2 * (outwidth * 4));	
		row1 = memalloc;
		row2 = memalloc + (outwidth * 4);
		inrow = (byte *) indata;
		oldy = 0;

		ResampleTextureLerpLine (inrow, row1, inwidth, outwidth);
		ResampleTextureLerpLine (inrow + inwidth*4, row2, inwidth, outwidth);
		
		for (i = 0, f = 0; i < outheight; i++, f += fstep)	{
			yi = f >> 16;
			if (yi < endy) {
				int lerp = f & 0xFFFF;
				if (yi != oldy) {
					inrow = (byte *)indata + inwidth * 4 * yi;
					if (yi == oldy + 1)
						memcpy(row1, row2, outwidth*4);
					else
						ResampleTextureLerpLine (inrow, row1, inwidth, outwidth);
					ResampleTextureLerpLine (inrow + inwidth * 4, row2, inwidth, outwidth);
					oldy = yi;
				}
				for (j = outwidth ; j ; j--) {
					out[0] = (byte) ((((row2[0] - row1[0]) * lerp) >> 16) + row1[0]);
					out[1] = (byte) ((((row2[1] - row1[1]) * lerp) >> 16) + row1[1]);
					out[2] = (byte) ((((row2[2] - row1[2]) * lerp) >> 16) + row1[2]);
					out[3] = (byte) ((((row2[3] - row1[3]) * lerp) >> 16) + row1[3]);
					out += 4;
					row1 += 4;
					row2 += 4;
				}
				row1 -= outwidth * 4;
				row2 -= outwidth * 4;
			} else {
				if (yi != oldy) {
					inrow = (byte *) indata + inwidth * 4 * yi;
					if (yi == oldy+1)
						memcpy(row1, row2, outwidth * 4);
					else
						ResampleTextureLerpLine (inrow, row1, inwidth, outwidth);
					oldy = yi;
				}
				memcpy(out, row1, outwidth * 4);
			}
		}
		free(memalloc);
	} else {
		int i, j;
		unsigned int frac, fracstep, *inrow, *out;

		out = outdata;

		fracstep = inwidth * 0x10000 / outwidth;
		for (i = 0; i < outheight; i++) {
			inrow = (int *) indata + inwidth * (i * inheight / outheight);
			frac = fracstep >> 1;
			for (j = outwidth >> 2 ; j ; j--) {
				out[0] = inrow[frac >> 16]; frac += fracstep;
				out[1] = inrow[frac >> 16]; frac += fracstep;
				out[2] = inrow[frac >> 16]; frac += fracstep;
				out[3] = inrow[frac >> 16]; frac += fracstep;
				out += 4;
			}
			for (j = outwidth & 3; j; j--) {
				*out = inrow[frac >> 16]; frac += fracstep;
				out++;
			}
		}
	}
}

/*
================
MipMap

Operates in place, quartering the size of the texture
================
*/
__inline void MipMap (byte *in, int *width, int *height)
{
	int		i, j, nextrow;
	byte	*out;

	nextrow = *width << 2;
	out = in;
	
	if (*width > 1)
	{
		*width >>= 1;
		
		if (*height > 1)
		{
			*height >>= 1;
			
			for (i = 0 ; i < *height ; i++, in += nextrow)
			{
				for (j = 0 ; j < *width ; j++, out += 4, in += 8)
				{
					out[0] = (in[0] + in[4] + in[nextrow+0] + in[nextrow+4]) >> 2;
					out[1] = (in[1] + in[5] + in[nextrow+1] + in[nextrow+5]) >> 2;
					out[2] = (in[2] + in[6] + in[nextrow+2] + in[nextrow+6]) >> 2;
					out[3] = (in[3] + in[7] + in[nextrow+3] + in[nextrow+7]) >> 2;
				}
			}
		}
		else
		{
			for (i = 0 ; i < *height ; i++)
			{
				for (j = 0 ; j < *width ; j++, out += 4, in += 8)
				{
					out[0] = (in[0] + in[4]) >> 1;
					out[1] = (in[1] + in[5]) >> 1;
					out[2] = (in[2] + in[6]) >> 1;
					out[3] = (in[3] + in[7]) >> 1;
				}
			}
		}
	}
	else if (*height > 1)
	{
		*height >>= 1;
		
		for (i = 0 ; i < *height ; i++, in += nextrow)
		{
			for (j = 0 ; j < *width ; j++, out += 4, in += 4)
			{
				out[0] = (in[0] + in[nextrow+0]) >> 1;
				out[1] = (in[1] + in[nextrow+1]) >> 1;
				out[2] = (in[2] + in[nextrow+2]) >> 1;
				out[3] = (in[3] + in[nextrow+3]) >> 1;
			}
		}
	}
}

/*
================
ScaleDimensions
================
*/
__inline void ScaleDimensions (int width, int height, int *scaled_width, int *scaled_height, int mode)
{
	int	maxsize;

	for (*scaled_width = 1 ; *scaled_width < width ; *scaled_width *= 2);
	for (*scaled_height = 1 ; *scaled_height < height ; *scaled_height *= 2);

	if (mode & TEX_MIPMAP) {
		*scaled_width >>= (int)gl_picmip.value;
		*scaled_height >>= (int)gl_picmip.value;		
	}
	maxsize = (mode & TEX_MIPMAP) ? gl_max_size.value : gl_max_size_default;

	*scaled_width = bound(1, *scaled_width, maxsize);
	*scaled_height = bound(1, *scaled_height, maxsize);
}

/*
===============
GL_Upload32
===============
*/
void GL_Upload32 (unsigned *data, int width, int height, int mode)
{
	int				internal_format, scaled_width, scaled_height, miplevel;
	unsigned int	*scaled;

	for (scaled_width = 1 ; scaled_width < width ; scaled_width *= 2);
	for (scaled_height = 1 ; scaled_height < height ; scaled_height *= 2);

	scaled = Q_Malloc (scaled_width * scaled_height * 4);

	if (!data)
	{
		Host_Error("GL_Upload32: out of memory\n");
	}

	if (width < scaled_width || height < scaled_height)
	{
		ResampleTexture (data, width, height, scaled, scaled_width, scaled_height);

		// only interpolate after resampling ?
		if (mode & TEX_BLEND)
		{
			// allmost newer happens heh
			Con_DPrintf ("Pixel interpolation is on\n");

			// don't interpolate if it's too big
			if (width < 512 || height < 512)
			{
				InterpolatePixels (scaled, scaled_width, scaled_height);
			}
		}
		width = scaled_width;
		height = scaled_height;
	}
	else
	{
		memcpy (scaled, data, width * height * 4);
	}
	ScaleDimensions (width, height, &scaled_width, &scaled_height, mode);

	while (width > scaled_width || height > scaled_height)
	{
		MipMap ((byte *)scaled, &width, &height);
	}
	internal_format = (mode & TEX_ALPHA) ? gl_alpha_format : gl_solid_format;

	miplevel = 0;

	glTexImage2D (GL_TEXTURE_2D, 0, internal_format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);

	if (mode & TEX_MIPMAP)
	{
		while (width > 1 || height > 1)
		{
			MipMap ((byte *)scaled, &width, &height);
			miplevel++;
			glTexImage2D (GL_TEXTURE_2D, miplevel, internal_format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, scaled);
		}
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
	}
	else
	{
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
	}
	free (scaled);
}

/*
===============
GL_Upload8
===============
*/
void GL_Upload8 (byte *data, int width, int height, int mode)
{
	static unsigned		*uploadimage;
	int					i, size, p;
	unsigned			*table;

	table = (mode & TEX_BRIGHTEN) ? d_8to24table2 : d_8to24table;
	
	// get size
	size = width * height;

	// allocate dynamic image size
	uploadimage = Q_Malloc(size * sizeof(unsigned));

	if (mode & TEX_FULLBRIGHT) {
	// this is a fullbright mask, so make all non-fullbright
	// colors transparent
		mode |= TEX_ALPHA;
		for (i=0 ; i<size ; i++) {
			p = data[i];
			if (p < 224) {
				uploadimage[i] = table[p] & 0x00FFFFFF;	// transparent 
			} else {
				uploadimage[i] = table[p];				// fullbright
			}
		}
	} else if (mode & TEX_ALPHA) {
	// if there are no transparent pixels, make it a 3 component
	// texture even if it was specified as otherwise
		mode &= ~TEX_ALPHA;
		for (i=0 ; i<size ; i++) {
			p = data[i];
			if (p == 255) {
				mode |= TEX_ALPHA;
			}
			uploadimage[i] = table[p];
		}
	} else {
		for (i = 0 ; i<size ; i += 4) {
			uploadimage[i]   = table[data[i]];
			uploadimage[i+1] = table[data[i+1]];
			uploadimage[i+2] = table[data[i+2]];
			uploadimage[i+3] = table[data[i+3]];
		}
	}
	GL_Upload32 (uploadimage, width, height, mode);

	free(uploadimage);
}

/*
================
GL_LoadTexture
================
*/
int GL_LoadTexture (char *identifier, int width, int height, byte *data, int mode, int bpp)
{
	int			i, scaled_width, scaled_height, crc = 0;
	gltexture_t	*glt;

	ScaleDimensions (width, height, &scaled_width, &scaled_height, mode);

	// see if the texture is already present
	if (identifier[0]) {
		crc = CRC_Block (data, width * height * bpp);
		for (i=0, glt=gltextures ; i<numgltextures ; i++, glt++) {
			if (!strncmp(identifier, glt->identifier, sizeof(glt->identifier)-1)) {
				if (width == glt->width && 
					height == glt->height && 
					scaled_width == glt->scaled_width && 
					scaled_height == glt->scaled_height && 
					crc == glt->crc && bpp == glt->bpp && 
					(mode & ~TEX_COMPLAIN) == (glt->texmode & ~TEX_COMPLAIN)) {
					GL_Bind (gltextures[i].texnum);
					return gltextures[i].texnum;	// texture cached
				} else {
					goto GL_LoadTexture_setup;		// reload the texture into the same slot
				}
			}
		}
	}

	if (numgltextures == MAX_GLTEXTURES) {
		Sys_Error ("GL_LoadTexture: numgltextures == MAX_GLTEXTURES");
	}
	glt = &gltextures[numgltextures];
	numgltextures++;

	Q_strncpyz (glt->identifier, identifier, sizeof(glt->identifier));
	glt->texnum = texture_extension_number;
	texture_extension_number++;

	GL_LoadTexture_setup:
	glt->width = width;
	glt->height = height;
	glt->scaled_width = scaled_width;
	glt->scaled_height = scaled_height;
	glt->texmode = mode;
	glt->crc = crc;
	glt->bpp = bpp;
	
	if (glt->pathname) {
		Z_Free (glt->pathname);
		glt->pathname = NULL;
	}

	if (bpp == 4 && com_netpath[0]) {
		glt->pathname = CopyString (com_netpath);
	}
	GL_Bind (glt->texnum);

	switch (bpp) {
	case 1:
		GL_Upload8 (data, width, height, mode);
		break;

	case 4:
		GL_Upload32 ((void *)data, width, height, mode);
		break;

	default:
		Sys_Error ("GL_LoadTexture: unknown bpp\n");
		break;
	}
	return glt->texnum;
}

/*
================
GL_LoadPicTexture
================
*/
int GL_LoadPicTexture (char *name, mpic_t *pic, byte *data)
{
	int		i, glwidth, glheight;
	char	fullname[MAX_QPATH] = "pic:";

	for (glwidth = 1 ; glwidth < pic->width ; glwidth <<= 1)
		;
	for (glheight = 1 ; glheight < pic->height ; glheight <<= 1)
		;

	Q_strncpyz (fullname + 4, name, sizeof(fullname) - 4);

	if (glwidth == pic->width && glheight == pic->height) {
		pic->texnum = GL_LoadTexture (fullname, glwidth, glheight, data, TEX_ALPHA, 1);
		pic->sl = 0;
		pic->sh = 1;
		pic->tl = 0;
		pic->th = 1;
	} else {
		byte	*src, *dest, *buf;

		buf = Q_Calloc (glwidth * glheight, 1);
		src = data;
		dest = buf;
		
		for (i = 0 ; i < pic->height ; i++) {
			memcpy (dest, src, pic->width);
			src += pic->width;
			dest += glwidth;
		}

		pic->texnum = GL_LoadTexture ("", glwidth, glheight, buf, TEX_ALPHA, 1);
		pic->sl = 0;
		pic->sh = (float)pic->width / glwidth;
		pic->tl = 0;
		pic->th = (float)pic->height / glheight;

		free (buf);
	}                                 	
	return pic->texnum;
}

/*
================
GL_FindTexture
================
*/
gltexture_t *GL_FindTexture (char *identifier)
{
	int	i;

	for (i=0 ; i<numgltextures ; i++) {
		if (!strcmp(identifier, gltextures[i].identifier)) {
			return &gltextures[i];
		}
	}
	return NULL;
}

static	gltexture_t	*current_texture = NULL;

#define CHECK_TEXTURE_ALREADY_LOADED	\
	if (CheckTextureLoaded(mode))		\
	{									\
		current_texture = NULL;			\
		fclose (f);						\
		return NULL;					\
	}


static qboolean CheckTextureLoaded (int mode)
{
	int		scaled_width, scaled_height;

	if (current_texture && current_texture->pathname && !strcmp(com_netpath, current_texture->pathname)) {
		ScaleDimensions (current_texture->width, current_texture->height, &scaled_width, &scaled_height, mode);
		if (current_texture->scaled_width == scaled_width && current_texture->scaled_height == scaled_height) {
			return true;
		}
	}
	return false;
}

byte *GL_LoadImagePixels (char* filename, int matchwidth, int matchheight, int mode)
{
	char	basename[MAX_QPATH], name[MAX_QPATH];
	byte	*c, *data;
	FILE	*f;

	COM_StripExtension (filename, basename);

	for (c = basename ; *c ; c++) {
		if (*c == '*') {
			*c = '#';
		}
	}
	
	Q_snprintfz (name, sizeof(name), "%s.tga", basename);

	if (COM_FOpenFile(name, &f, !(hipnotic || rogue)) != -1) {
		CHECK_TEXTURE_ALREADY_LOADED;

		if (data = LoadTGA(f, name, matchwidth, matchheight)) {
			return data;
		}
	}

	Q_snprintfz (name, sizeof(name), "%s.png", basename);
	
	if (COM_FOpenFile(name, &f, !(hipnotic || rogue)) != -1) {
		CHECK_TEXTURE_ALREADY_LOADED;

		if (data = LoadPNG(f, name, matchwidth, matchheight)) {
			return data;
		}
	}

	if (mode & TEX_COMPLAIN) {
		if (!no24bit) {
			Con_Printf ("Couldn't load %s image\n", COM_SkipPath(filename));
		}
	}
	return NULL;
}

int GL_LoadTexturePixels (byte *data, char *identifier, int width, int height, int mode)
{
	int			i, j, image_size;
	qboolean	gamma;

	image_size = width * height;
	gamma = (vid_gamma != 1);

	if (mode & TEX_LUMA) {
		gamma = false;
	} else if (mode & TEX_ALPHA) {
		mode &= ~TEX_ALPHA;
		for (j=0 ; j<image_size ; j++) {
			if (((((unsigned *)data)[j] >> 24) & 0xFF) < 255) {
				mode |= TEX_ALPHA;
				break;
			}
		}
	}

	if (gamma) {
		for (i=0 ; i<image_size ; i++) {
			data[4*i] = vid_gamma_table[data[4*i]];
			data[4*i+1] = vid_gamma_table[data[4*i+1]];
			data[4*i+2] = vid_gamma_table[data[4*i+2]];
		}
	}
	return GL_LoadTexture (identifier, width, height, data, mode, 4);
}

int GL_LoadTextureImage (char *filename, char *identifier, int matchwidth, int matchheight, int mode)
{
	int				texnum;
	byte			*data;
	gltexture_t		*gltexture;

	if (no24bit) {
		return 0;
	}
	
	if (!identifier) {
		identifier = filename;
	}
	gltexture = current_texture = GL_FindTexture (identifier);

	if (!(data = GL_LoadImagePixels(filename, matchwidth, matchheight, mode))) {
		texnum = (gltexture && !current_texture) ? gltexture->texnum : 0;
	} else {
		texnum = GL_LoadTexturePixels (data, identifier, image_width, image_height, mode);
		free (data);
	}

	current_texture = NULL;
	return texnum;
}

mpic_t *GL_LoadPicImage (char *filename, char *id, int matchwidth, int matchheight, int mode)
{
	int				glwidth, glheight, i;
	char			identifier[MAX_QPATH] = "pic:";
	byte			*data, *src, *dest, *buf;
	static mpic_t	pic;

	if (no24bit) {
		return NULL;
	}

	if (!(data = GL_LoadImagePixels(filename, matchwidth, matchheight, 0))) {
		return NULL;
	}

	pic.width = image_width;
	pic.height = image_height;

	if (mode & TEX_ALPHA) {
		mode &= ~TEX_ALPHA;
		for (i=0 ; i < image_width * image_height ; i++) {
			if (((((unsigned *)data)[i] >> 24) & 0xFF) < 255) {
				mode |= TEX_ALPHA;
				break;
			}
		}
	}

	for (glwidth = 1 ; glwidth < pic.width ; glwidth <<= 1)
		;
	for (glheight = 1 ; glheight < pic.height ; glheight <<= 1)
		;

	Q_strncpyz (identifier + 4, id ? id : filename, sizeof(identifier) - 4);

	if (glwidth == pic.width && glheight == pic.height) {
		pic.texnum = GL_LoadTexture (identifier, pic.width, pic.height, data, mode, 4);
		pic.sl = 0;
		pic.sh = 1;
		pic.tl = 0;
		pic.th = 1;
	} else {
		buf = Q_Calloc (glwidth * glheight, 4);
		src = data;
		dest = buf;
		for (i=0 ; i<pic.height ; i++) {
			memcpy (dest, src, pic.width * 4);
			src += pic.width * 4;
			dest += glwidth * 4;
		}
		pic.texnum = GL_LoadTexture (identifier, glwidth, glheight, buf, mode & ~TEX_MIPMAP, 4);
		pic.sl = 0;
		pic.sh = (float)pic.width / glwidth;
		pic.tl = 0;
		pic.th = (float)pic.height / glheight;
		free (buf);
	}

	free (data);
	return &pic;
}

int GL_LoadCharsetImage (char *filename, char *identifier)
{
	int			i, j, texnum, image_size;
	byte		*data, *buf, *dest, *src;
	qboolean	transparent = false;

	if (no24bit) {
		return 0;
	}

	if (!(data = GL_LoadImagePixels(filename, 0, 0, 0))) {
		return 0;
	}

	if (!identifier) {
		identifier = filename;
	}
	image_size = image_width * image_height;

	for (j=0 ; j<image_size ; j++) {
		if (((((unsigned *)data)[j] >> 24) & 0xFF) < 255) {
			transparent = true;
			break;
		}
	}

	if (!transparent) {
		for (i=0 ; i < image_width * image_height ; i++) {
			if (data[i*4] == data[0] && data[i*4+1] == data[1] && data[i*4+2] == data[2])
				data[i*4+3] = 0;
		}
	}
	buf = dest = Q_Calloc (image_size * 2, 4);
	src = data;
	
	for (i=0 ; i<16 ; i++) {
		memcpy (dest, src, image_size >> 2);	
		src += image_size >> 2;
		dest += image_size >> 1;				
	}

	texnum = GL_LoadTexture (identifier, image_width, image_height * 2, buf, TEX_ALPHA, 4);

	free (buf);
	free (data);
	return texnum;
}

static	GLenum	oldtarget = GL_TEXTURE0_ARB;
static	int		cnttextures[4] = {-1, -1, -1, -1};     // cached
static qboolean	mtexenabled = false;

void GL_SelectTexture (GLenum target) 
{
	if (target == oldtarget) return;
	qglActiveTexture (target);
	cnttextures[oldtarget-GL_TEXTURE0_ARB] = currenttexture;
	currenttexture = cnttextures[target-GL_TEXTURE0_ARB];
	oldtarget = target;
}

void GL_DisableMultitexture (void) 
{
	if (mtexenabled) {
		glDisable (GL_TEXTURE_2D);
		GL_SelectTexture (GL_TEXTURE0_ARB);
		mtexenabled = false;
	}
}

void GL_EnableMultitexture (void) 
{
	if (gl_mtexable) {
		GL_SelectTexture (GL_TEXTURE1_ARB);
		glEnable (GL_TEXTURE_2D);
		mtexenabled = true;
	}
}

void GL_EnableTMU (GLenum target)
{
	GL_SelectTexture (target);
	glEnable (GL_TEXTURE_2D);
}

void GL_DisableTMU (GLenum target)
{
	GL_SelectTexture (target);
	glDisable (GL_TEXTURE_2D);
}

