//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/gl_image.c                                             $
// $Revision:: 5                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/21/01 6:07 PM                                                $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
//

#include "gl_local.h"
#include "crc.h"
#include "client.h"

#if defined(__BIGENDIAN__)
#define ALPHAMASK 0x000000FF
#else
#define ALPHAMASK 0xFF000000
#endif

image_t gltextures[MAX_GLTEXTURES];
int numgltextures;

static byte intensitytable[256];
static byte gammatable[256];
static cvar_t *intensity;

static byte basepalette[1024];		/* R,G,B,A */
static unsigned basepalettecrc;		/* CRC to the palette */
static byte setbasepalette[768];
byte d_8to24table[1024]; /* FS :( */
static char palette_file[256];
static int palette_sequence=0;
qboolean shared_palette_support=false;
qboolean r_multitexture_enabled=false;
static qboolean GL_Upload8bit (byte * data, byte * palette, int width, int height, int has_miplevels, qboolean mipmap );

static int gl_solid_format = 3;
static 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;

static byte *gl_palette = NULL;
static int gl_skin_filter_min = GL_LINEAR_MIPMAP_NEAREST;
static int gl_skin_filter_max = GL_LINEAR;

#define	MAX_SCRAPS 1
#define	BLOCK_WIDTH 256
#define	BLOCK_HEIGHT 256

static int scrap_allocated[MAX_SCRAPS][BLOCK_WIDTH];
static byte scrap_texels[MAX_SCRAPS][BLOCK_WIDTH*BLOCK_HEIGHT];
qboolean scrap_dirty;

static int upload_width, upload_height;
static qboolean uploaded_paletted;
static void GL_FreeImage( image_t * image );

static long GL_HashFileName (const char *name); /* Knightmare */

void GL_SetTexturePalette(const byte *palette)
{
	int i;
	byte *Output;
	byte temptable[768];
	const byte *Input;
	
	if (!shared_palette_support) {
		return;
	}
	
	if ( qglColorTableEXT && gl_ext_palettedtexture->value ) {
		i = 256;
		Output = temptable;
		Input = palette;
		do {
			Output[0] = Input[0];
			Output[1] = Input[1];
			Output[2] = Input[2];
			Input+=4;
			Output+=3;
		} while (--i);
		qglColorTableEXT( GL_SHARED_TEXTURE_PALETTE_EXT,GL_RGB,256,GL_RGB,GL_UNSIGNED_BYTE,temptable);
	}
}

void GL_EnableTexture (unsigned tmu, qboolean enable) /* Knightmare */
{
	if ( tmu < 0 || tmu >= MAX_TMUS || tmu >= gl_config.max_tmus )
		return;

	if ( enable )
	{
		GL_SelectTexture( tmu );
		qglEnable( GL_TEXTURE_2D );
		if (gl_config.havedrawelements || gl_config.havedrawrangeelements) {
			qglEnableClientState(GL_TEXTURE_COORD_ARRAY);
			qglTexCoordPointer(2, GL_FLOAT, sizeof(texCoordArray[tmu][0]), texCoordArray[tmu][0]);
		}
		gl_state.TMU_active[tmu] = true;
	}
	else
	{
		GL_SelectTexture( tmu );
		qglDisable( GL_TEXTURE_2D );
		if (gl_config.havedrawelements || gl_config.havedrawrangeelements) {
			qglDisableClientState(GL_TEXTURE_COORD_ARRAY);
		}
		gl_state.TMU_active[tmu] = false;
	}
}

void GL_EnableMultitexture( qboolean enable )
{
	r_multitexture_enabled=false;
	
	if ( !qglActiveTextureARB )
		return;
	
	if ( enable ) {
		GL_SelectTexture( GL_TEXTURE1_ARB );
		qglEnable( GL_TEXTURE_2D );
		GL_TexEnv( GL_MODULATE );
		r_multitexture_enabled=true;
	} else {
		GL_SelectTexture( GL_TEXTURE1_ARB );
		qglDisable( GL_TEXTURE_2D );
		GL_TexEnv( GL_REPLACE );
	}
	GL_SelectTexture( GL_TEXTURE0_ARB );
	GL_TexEnv( GL_REPLACE );
}

void GL_SelectTexture( GLenum texture )
{
	int tmu;
	
	if ( !qglActiveTextureARB )
		return;
	
	if ( texture == GL_TEXTURE0_ARB )
		tmu = 0;
	else
		tmu = 1;
	
	if ( tmu == gl_state.currenttmu )
		return;
	
	gl_state.currenttmu = tmu;
	
	if ( tmu == 0 )
		qglActiveTextureARB( GL_TEXTURE0_ARB );
	else
		qglActiveTextureARB( GL_TEXTURE1_ARB );
}

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_nobind->value && Draw_Chars)		// performance evaluation option
		texnum = Draw_Chars->TextureNum;
	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 == GL_TEXTURE0_ARB )
	{
		if ( gl_state.currenttextures[0] == texnum )
			return;
	}
	else
	{
		if ( gl_state.currenttextures[1] == texnum )
			return;
	}
	GL_Bind( texnum );
}

static void GL_SetQuickTexturePalette( byte palette[768] )
{
	if (!shared_palette_support)
		return;
	if ( qglColorTableEXT && gl_ext_palettedtexture->value )
	{
		qglColorTableEXT( GL_SHARED_TEXTURE_PALETTE_EXT,GL_RGB,256,GL_RGB,GL_UNSIGNED_BYTE,(byte *)palette );
	}
}

void GL_BindImage( image_t *image )
{
	//
	// has paletted support
	//
	if ( qglColorTableEXT && image->paletted )
	{
		if ( gl_palette != image->palette )
		{
			GL_SetQuickTexturePalette( image->palette );
			gl_palette = image->palette;
		}
	}

	GL_Bind( image->TextureNum );
}

void GL_MBindImage( GLenum target, image_t *image )
{
	GL_SelectTexture( target );
	if ( target == GL_TEXTURE0_ARB )
	{
		if ( gl_state.currenttextures[0] == image->TextureNum )
			return;
	}
	else
	{
		if ( gl_state.currenttextures[1] == image->TextureNum )
			return;
	}
	GL_BindImage( image );
}

typedef struct {
	const char	*name;
	int	minimize, maximize;
} glmode_t;

static 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 {
	const char *name;
	int mode;
} gltmode_t;

static 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))

static 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},
#ifdef GL_RGB2_EXT
	{"GL_RGB2", GL_RGB2_EXT},
#endif
};

#define NUM_GL_SOLID_MODES (sizeof(gl_solid_modes) / sizeof (gltmode_t))

/*
===============
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;
	}

	if (i == 6)
	{
		ri.Con_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, glt=gltextures ; i<numgltextures ; i++, glt++)
	{
		if ( glt->type == it_wall )
		{
			GL_Bind (glt->TextureNum);
			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min);
			qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max);
		}
	}
}

/*
===============
GL_SkinTextureMode
===============
*/
void GL_SkinTextureMode( const char *string )
{
	int i;
	image_t *glt;

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

	if (i == 6)
	{
		ri.Con_Printf ( PRINT_ALL, "bad filter name\n" );
		return;
	}

	gl_skin_filter_min = modes[i].minimize;
	gl_skin_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_skin )
		{
			GL_Bind ( glt->TextureNum );
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_skin_filter_max );
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_skin_filter_max );
		}
	}
}

/*
===============
GL_TextureAlphaMode
===============
*/
void GL_TextureAlphaMode (const 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)
	{
		ri.Con_Printf (PRINT_ALL, "bad alpha texture mode name\n");
		return;
	}

	gl_tex_alpha_format = gl_alpha_modes[i].mode;
}

/*
===============
GL_TextureSolidMode
===============
*/
void GL_TextureSolidMode (const 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)
	{
		ri.Con_Printf (PRINT_ALL, "bad solid texture mode name\n");
		return;
	}

	gl_tex_solid_format = gl_solid_modes[i].mode;
}

/*
===============
GL_ImageList_f
===============
*/

static const char *palstrings[2] = {
	"RGB",
	"PAL"
};

void GL_ImageList_f (void)
{
	int		i;
	image_t	*image;
	int		texels;
	int		size;
	int		m_texels;
	int		w_texels;
	int		o_texels;
	
	ri.Con_Printf (PRINT_ALL, "------------------\n");
	texels = 0;
	m_texels = 0;
	w_texels = 0;
	o_texels = 0;
	
	for (i=0, image=gltextures ; i<numgltextures ; i++, image++)
	{
		if (!image->TextureNum)
			continue;
		size = image->upload_width*image->upload_height*(2-image->paletted);
		switch (image->type)
		{
		case it_skin:
			texels += size;
			m_texels += size;
			ri.Con_Printf (PRINT_ALL, "M");
			break;
		case it_sprite:
			texels += size;
			o_texels += size;
			ri.Con_Printf (PRINT_ALL, "S");
			break;
		case it_wall:
			texels += size;
			w_texels += size;
			ri.Con_Printf (PRINT_ALL, "W");
			break;
		case it_pic:
			texels += size;
			o_texels += size;
			ri.Con_Printf (PRINT_ALL, "P");
			break;
		default:
			texels += size;
			o_texels += size;
			ri.Con_Printf (PRINT_ALL, " ");
			break;
		}

		ri.Con_Printf (PRINT_ALL, " %3i %3i %s: %s\n",
			image->upload_width, image->upload_height, palstrings[image->paletted], image->name);
	}
	ri.Con_Printf (PRINT_ALL, "Total texel count (not counting mipmaps): %i\n", texels);
	ri.Con_Printf (PRINT_ALL, "Total SWL texel count                   : %i\n", w_texels);
	ri.Con_Printf (PRINT_ALL, "Total Model texel count                 : %i\n", m_texels);
	ri.Con_Printf (PRINT_ALL, "Total Other texel count                 : %i\n", o_texels);
}


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

  scrap allocation

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

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


// returns a texture number and the position inside it
static 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;
//	Sys_Error ("Scrap_AllocBlock: full");
}

void Scrap_Upload (void)
{
	GL_Bind(TEXNUM_SCRAPS);
	GL_Upload8bit (scrap_texels[0], setbasepalette, BLOCK_WIDTH, BLOCK_HEIGHT, 0, false );
	scrap_dirty = false;
}

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

PCX LOADING

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

/*
==============
LoadPCX
==============
*/
static void LoadPCX (const char *filename, byte **pic, byte **palette, int *width, int *height)
{
	void *buf;
	byte *raw;
	pcx_t *pcx;
	int x, y;
	int len;
	int dataByte, runLength;
	byte *out, *pix;

	*pic = NULL;
	*palette = NULL;

	//
	// load the file
	//
	len = ri.FS_LoadFile (filename, &buf);
	if (!buf) {
		ri.Con_Printf (PRINT_DEVELOPER, "Bad pcx file %s\n", filename);
		return;
	}

	//
	// parse the PCX file
	//
	pcx = (pcx_t *)buf;

	pcx->xmin = LittleShort(pcx->xmin);
	pcx->ymin = LittleShort(pcx->ymin);
	pcx->xmax = LittleShort(pcx->xmax);
	pcx->ymax = LittleShort(pcx->ymax);
	pcx->hres = LittleShort(pcx->hres);
	pcx->vres = LittleShort(pcx->vres);
	pcx->bytes_per_line = LittleShort(pcx->bytes_per_line);
	pcx->palette_type = LittleShort(pcx->palette_type);

	if (pcx->manufacturer != 0x0a
		|| pcx->version != 5
		|| pcx->encoding != 1
		|| pcx->bits_per_pixel != 8
		|| pcx->xmax >= 640
		|| pcx->ymax >= 480)
	{
		ri.Con_Printf (PRINT_ALL, "Bad pcx file %s\n", filename);
		ri.FS_FreeFile (buf);
		return;
	}

	raw = &pcx->data;

	out = (byte *) malloc((pcx->ymax + 1) * (pcx->xmax + 1));
	*pic = out;
	pix = out;

	if (palette) {
		byte * p;
		byte temp_pal[768];

		memcpy(temp_pal, (byte *)pcx + len - 768, 768);
		p = (byte *) malloc(1024);
		*palette = p;
		for (x = 0; x < 256; x++) {
			p[(x*4) + 0] = temp_pal[(x*3) + 0];
			p[(x*4) + 1] = temp_pal[(x*3) + 1];
			p[(x*4) + 2] = temp_pal[(x*3) + 2];
			p[(x*4) + 3] = 255;
		}
	}

	if (width) *width = pcx->xmax + 1;
	if (height) *height = pcx->ymax + 1;

	for (y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1) {
		for (x = 0; x <= pcx->xmax; ) {
			dataByte = *raw++;

			if ((dataByte & 0xC0) == 0xC0) {
				runLength = dataByte & 0x3F;
				dataByte = *raw++;
			}
			else {
				runLength = 1;
			}

			while (runLength-- > 0) {
				pix[x++] = dataByte;
			}
		}
	}

	if (raw - (byte *)pcx > len) {
		ri.Con_Printf (PRINT_DEVELOPER, "PCX file %s was malformed", filename);
		free(*pic);
		*pic = NULL;
	}
	ri.FS_FreeFile (buf);
}

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

TARGA LOADING

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

typedef struct _TargaHeader {
	unsigned char id_length, colormap_type, image_type;
	unsigned short colormap_index, colormap_length;
	unsigned char colormap_size;
	unsigned short x_origin, y_origin, width, height;
	unsigned char pixel_size, attributes;
} TargaHeader;

/*
=============
LoadTGA
=============
*/
static qboolean LoadTGA (const char *name, byte **pic, int *width, int *height, byte **palette, qboolean *hasalpha)
{
	int columns, rows, numPixels;
	byte *pixbuf;
	int row, column;
	byte *buf_p;
	void *buffer;
	TargaHeader targa_header;
	byte *targa_rgba;
	byte *targa_palette;
	int i;

	*pic = NULL;
	*palette = NULL;
	*hasalpha = false;

	//
	// load the file
	//
	if (ri.FS_LoadFile(name, &buffer) < 0) {
		ri.Con_Printf (PRINT_DEVELOPER, "Bad tga file %s\n", name);
		return false;
	}

	buf_p = (byte *) buffer;

	targa_header.id_length = *buf_p++;
	targa_header.colormap_type = *buf_p++;
	targa_header.image_type = *buf_p++;

	targa_header.colormap_index  = LittleShort(*(short *)buf_p); buf_p += 2;
	targa_header.colormap_length = LittleShort(*(short *)buf_p); buf_p += 2;
	targa_header.colormap_size = *buf_p++ / 8;
	targa_header.x_origin = LittleShort(*(short *)buf_p); buf_p += 2;
	targa_header.y_origin = LittleShort(*(short *)buf_p); buf_p += 2;
	targa_header.width    = LittleShort(*(short *)buf_p); buf_p += 2;
	targa_header.height   = LittleShort(*(short *)buf_p); buf_p += 2;
	targa_header.pixel_size = *buf_p++;
	targa_header.attributes = *buf_p++;

	if (targa_header.image_type != 1 && targa_header.image_type != 2 && targa_header.image_type != 10) {
		ri.Con_Printf (PRINT_DEVELOPER, "LoadTGA: Only type 1, 2 and 10 targa RGB images supported\n");
		return false;
	}

	if ((targa_header.colormap_type != 0 && targa_header.colormap_type != 1) ||
	    (targa_header.pixel_size != 32 && targa_header.pixel_size != 24 && targa_header.pixel_size != 8)) {
		ri.Con_Printf (PRINT_DEVELOPER, "LoadTGA: Only 24, 32 and colormap images supported (no colormaps)\n");
		return false;
	}

	columns = targa_header.width;
	rows = targa_header.height;
	if (width) *width = columns;
	if (height) *height = rows;

	numPixels = columns * rows;
	if (targa_header.image_type != 1)
		numPixels *= 4;
	targa_rgba = (byte *) malloc(numPixels);
	*pic = targa_rgba;

	if (targa_header.id_length != 0)
		buf_p += targa_header.id_length; // skip TARGA image comment

	if (targa_header.image_type == 1) {
		targa_palette = (byte *) malloc(256*4);
		*palette = targa_palette;
		for (i = 0; i < targa_header.colormap_length; i++) {
			byte blue  = *buf_p++;
			byte green = *buf_p++;
			byte red   = *buf_p++;
			byte alpha = 0xff;

			if (targa_header.colormap_size == 4) {
				alpha = *buf_p++;
				if (alpha != 0xff)
					*hasalpha = true;
			}
			targa_palette[i*4+0] = red;
			targa_palette[i*4+1] = green;
			targa_palette[i*4+2] = blue;
			targa_palette[i*4+3] = alpha;
		}
		for (row = rows - 1; row >= 0; row--) {
			pixbuf = targa_rgba + row*columns;
			for (column = 0; column < columns; column++) {
				switch (targa_header.pixel_size) {
				case 8:
					*pixbuf++ = *buf_p++;
					break;
				}
			}
		}
	}
	else if (targa_header.image_type == 2) { // Uncompressed, RGB images
		for (row = rows - 1; row >= 0; row--) {
			pixbuf = targa_rgba + row*columns*4;
			for (column = 0; column < columns; column++) {
				unsigned char alphabyte;
				switch (targa_header.pixel_size) {
				case 24:
					pixbuf[0] = buf_p[2];
					pixbuf[1] = buf_p[1];
					pixbuf[2] = buf_p[0];
					pixbuf[3] = 255;
					buf_p+=3;
					pixbuf+=4;
					break;
				case 32:
					alphabyte = buf_p[3];
					pixbuf[0] = buf_p[2];
					pixbuf[1] = buf_p[1];
					pixbuf[2] = buf_p[0];
					pixbuf[3] = alphabyte;
					buf_p+=4;
					pixbuf+=4;
					if (alphabyte != 0xff)
						*hasalpha = true;
					break;
				}
			}
		}
	}
	else if (targa_header.image_type == 10) { // Runlength encoded RGB images
		unsigned char red,green,blue,alphabyte,packetHeader,packetSize,j;
		for (row = rows - 1; row >= 0; row--) {
			pixbuf = targa_rgba + row*columns*4;
			for (column = 0; column < columns; ) {
				packetHeader= *buf_p++;
				packetSize = 1 + (packetHeader & 0x7f);
				if (packetHeader & 0x80) { // run-length packet
					switch (targa_header.pixel_size) {
					case 24:
						blue = *buf_p++;
						green = *buf_p++;
						red = *buf_p++;
						alphabyte = 255;
						break;
					case 32:
						blue = *buf_p++;
						green = *buf_p++;
						red = *buf_p++;
						alphabyte = *buf_p++;
						if (alphabyte != 0xff)
							*hasalpha = true;
						break;
					default: /* silence compiler */
						blue = red = green = alphabyte = 0;
						break;
					}

					for (j = 0; j < packetSize; j++) {
						*pixbuf++ = red;
						*pixbuf++ = green;
						*pixbuf++ = blue;
						*pixbuf++ = alphabyte;
						column++;
						if (column == columns) { // run spans across rows
							column = 0;
							if (row > 0) row--;
							else goto breakOut;
							pixbuf = targa_rgba + row*columns*4;
						}
					}
				}
				else { // non run-length packet
					for (j = 0; j < packetSize; j++) {
						switch (targa_header.pixel_size) {
						case 24:
							blue = *buf_p++;
							green = *buf_p++;
							red = *buf_p++;
							*pixbuf++ = red;
							*pixbuf++ = green;
							*pixbuf++ = blue;
							*pixbuf++ = 255;
							break;
						case 32:
							blue = *buf_p++;
							green = *buf_p++;
							red = *buf_p++;
							alphabyte = *buf_p++;
							*pixbuf++ = red;
							*pixbuf++ = green;
							*pixbuf++ = blue;
							*pixbuf++ = alphabyte;
							if (alphabyte != 0xff)
								*hasalpha = true;
							break;
						}
						column++;
						if (column == columns) { // pixel packet run spans across rows
							column = 0;
							if (row > 0) row--;
							else goto breakOut;
							pixbuf = targa_rgba + row*columns*4;
						}
					}
				}
			}
breakOut:   ;
		}
	}

	ri.FS_FreeFile (buffer);
	return true;
}


/*
================
GL_Resample8BitTexture
================
*/
static void GL_Resample8BitTexture (byte *in, int inwidth, int inheight, byte *out, int outwidth, int outheight)
{
	int i, j;
	byte *inrow;
	unsigned frac, fracstep;
	unsigned p1[1024];

	fracstep = inwidth*0x10000/outwidth;

	frac = fracstep>>2;
	for (i=0; i<outwidth; i++) {
		p1[i] = frac>>16;
		frac += fracstep;
	}

	for (i=0; i<outheight; i++, out += outwidth) {
		inrow = in + inwidth*(int)(i*inheight/outheight);
		for (j=0; j<outwidth; j++) {
			out[j] = inrow[p1[j]];
		}
	}
}

/*
================
GL_ResampleTexture
================
*/
void GL_ResampleTexture (byte *InPtr, 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 = &((unsigned *)InPtr)[(inwidth*(int)((i+0.25)*inheight/outheight))];
		inrow2 = &((unsigned *)InPtr)[(inwidth*(int)((i+0.75)*inheight/outheight))];
		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
================
*/
static void GL_LightScaleTexture (unsigned *in, int inwidth, int inheight, qboolean only_gamma )
{
	if ( only_gamma )
	{
		int i, c;
		byte *p;

		p = (byte *)in;

		c = inwidth*inheight;
		for (i=0; i<c; i++, p+=4)
		{
			p[0] = gammatable[p[0]];
			p[1] = gammatable[p[1]];
			p[2] = gammatable[p[2]];
		}
	}
	else
	{
		int i, c;
		byte  *p;

		p = (byte *)in;

		c = inwidth*inheight;
		for (i=0; i<c; i++, p+=4)
		{
			p[0] = gammatable[intensitytable[p[0]]];
			p[1] = gammatable[intensitytable[p[1]]];
			p[2] = gammatable[intensitytable[p[2]]];
		}
	}
}

/*
================
GL_LightScalePalette

Scale up the color values in a palette to increase the
lighting range
================
*/
static void GL_LightScalePalette (byte *in, qboolean only_gamma )
{
	if ( only_gamma )
	{
		int i;
		byte *p;

		p = in;

		for (i=0; i<256; i++, p+=4)
		{
			p[0] = gammatable[p[0]];
			p[1] = gammatable[p[1]];
			p[2] = gammatable[p[2]];
		}
	}
	else
	{
		int i;
		byte *p;

		p = in;

		for (i=0; i<256; i++, p+=4)
		{
			p[0] = gammatable[intensitytable[p[0]]];
			p[1] = gammatable[intensitytable[p[1]]];
			p[2] = gammatable[intensitytable[p[2]]];
		}
	}
}

/*
================
GL_MipMap

Operates in place, quartering the size of the texture
================
*/
static 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;
		}
	}
}

#if 0
static void GL_BuildPalettedTexture( unsigned char *paletted_texture, unsigned char *scaled, int scaled_width, int scaled_height )
{
	int i;

	for ( i = 0; i < scaled_width * scaled_height; i++ )
	{
		unsigned int r, g, b, c;

		r = ( scaled[0] >> 3 ) & 31;
		g = ( scaled[1] >> 2 ) & 63;
		b = ( scaled[2] >> 3 ) & 31;

		c = r | ( g << 5 ) | ( b << 11 );

		paletted_texture[i] = gl_state.d_16to8table[c];
		scaled += 4;
	}
}
#endif

/*
===============
GL_Upload8bitLuminance

Returns success
===============
*/
static qboolean GL_Upload8bitLuminance(byte *DataPtr, qboolean has_alpha, int width, int height, int has_miplevels, qboolean mipmap)
{
	unsigned short *scaled;
	int scaled_width, scaled_height;
	int i;

	uploaded_paletted = false;

	scaled = (unsigned short *) malloc(sizeof(unsigned short) * 256*256);
	if (!scaled) {
		return false;
	}
	for (scaled_width = 1; scaled_width < width; scaled_width<<=1)
		;
	if (gl_round_down->value && scaled_width > width && mipmap)
		scaled_width >>= 1;
	for (scaled_height = 1; scaled_height < height; scaled_height<<=1)
		;
	if (gl_round_down->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 > (256*256)) {
		ri.Con_Printf ( PRINT_DEVELOPER, "GL_Upload8bitLuminance: too big" );
		free( scaled );
		return false;
	}

	if (scaled_width == width && scaled_height == height) {
		for (i=0; i<width*height; i++) {
			byte alpha = DataPtr[i*4+3];
			scaled[i] = (alpha<<8) | 0xff;
		}
		qglTexImage2D ( GL_TEXTURE_2D, 0, 2, scaled_width, scaled_height, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, scaled );
		goto done;
	}
done: ;
	qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_max );
	qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max );
	free( scaled );
	return true;
}


/*
===============
GL_Upload32bit

Returns success
===============
*/
static qboolean GL_Upload32bit( byte *DataPtr, qboolean has_alpha, int width, int height, int has_miplevels, qboolean mipmap)
{
	int samples;
	unsigned *scaled;
	int scaled_width, scaled_height;
	int comp;

	uploaded_paletted = false;
	scaled = (unsigned *)malloc( sizeof(unsigned) * 256*256 );
	if (!scaled) {
		return false;
	}

	for (scaled_width = 1; scaled_width < width; scaled_width<<=1)
		;
	if (gl_round_down->value && scaled_width > width && mipmap)
		scaled_width >>= 1;
	for (scaled_height = 1; scaled_height < height; scaled_height<<=1)
		;
	if (gl_round_down->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 > (256*256))
	{
		ri.Con_Printf ( PRINT_DEVELOPER, "GL_Upload32bit: too big" );
		free( scaled );
		return false;
	}
	if (has_alpha)
		samples = gl_alpha_format;
	else
		samples = gl_solid_format;

	if (samples == gl_solid_format)
		comp = gl_tex_solid_format;
	else if (samples == gl_alpha_format)
		comp = gl_tex_alpha_format;
	else {
		ri.Con_Printf ( PRINT_ALL,"Unknown number of texture components %i\n",samples );
		comp = samples;
	}

	if (scaled_width == width && scaled_height == height) {
		if (!mipmap) {
			qglTexImage2D ( GL_TEXTURE_2D, 0, samples, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, DataPtr );
			goto done;
		}
		memcpy( scaled, DataPtr, width*height*4 );
	} else {
		GL_ResampleTexture ( DataPtr, width, height, scaled, scaled_width, scaled_height );
	}
	GL_LightScaleTexture ( scaled, scaled_width, scaled_height, !mipmap );

	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 );
	}
	free( scaled );

	return true;
}

static qboolean GL_Upload8bit (byte * data, byte * palette, int width, int height, int has_miplevels, qboolean mipmap )
{
	byte *scaled;
	int scaled_width, scaled_height;

	uploaded_paletted = true;

	scaled = (byte *)malloc( sizeof(byte) * 256*256 );
	for (scaled_width = 1; scaled_width < width; scaled_width<<=1)
		;
	if (gl_round_down->value && scaled_width > width && mipmap)
		scaled_width >>= 1;
	for (scaled_height = 1; scaled_height < height; scaled_height<<=1)
		;
	if (gl_round_down->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 > (256*256))
	{
		ri.Con_Printf ( PRINT_DEVELOPER, "GL_Upload8bit: too big" );
		free( scaled );
		return false;
	}

	if (scaled_width == width && scaled_height == height) {
		qglTexImage2D( GL_TEXTURE_2D,0,GL_COLOR_INDEX8_EXT,scaled_width,scaled_height,0,GL_COLOR_INDEX,GL_UNSIGNED_BYTE,data );
	} else {
		GL_Resample8BitTexture ( data, width, height, scaled, scaled_width, scaled_height );
		qglTexImage2D( GL_TEXTURE_2D,0,GL_COLOR_INDEX8_EXT,scaled_width,scaled_height,0,GL_COLOR_INDEX,GL_UNSIGNED_BYTE,scaled );
	}
	if (mipmap)
	{
		int miplevel;

		miplevel = 0;
		while (scaled_width > 1 || scaled_height > 1)
		{
			miplevel++;
			// If there are mipmap levels available in the data, then use them
			// otherwise resample the original data at miplevels[0]
			if (has_miplevels > 1)
			{
				if (miplevel < has_miplevels)
				{
					data += width*height;
					width >>= 1;
					height >>= 1;
				}
			}
			else
			{
			}
			scaled_width >>= 1;
			scaled_height >>= 1;
			if (scaled_width < 1)
				scaled_width = 1;
			if (scaled_height < 1)
				scaled_height = 1;

			GL_Resample8BitTexture ( data, width, height, scaled, scaled_width, scaled_height );
			qglTexImage2D( GL_TEXTURE_2D,miplevel,GL_COLOR_INDEX8_EXT,scaled_width,scaled_height,0,GL_COLOR_INDEX,GL_UNSIGNED_BYTE,scaled );
		}
	}

	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 );
	}

	//
	// handle palette upload
	//
	if (!shared_palette_support) {
		if ( qglColorTableEXT && gl_ext_palettedtexture->value ) {
			qglColorTableEXT( GL_TEXTURE_2D,
					  GL_RGB, 256, GL_RGB,GL_UNSIGNED_BYTE,(byte *)palette );
		}
	}
	free( scaled );
	return true;
}


#define MAX_CONVERT_SIZE ( 512 * 256 )

static qboolean GL_Convert_8to24 (byte *indata, unsigned * palette, int width, int height, unsigned * outdata, int miplevels, qboolean has_alpha )
{
	int i, s;
	int p;
	int mip;

	s = width*height;

	if ( s > MAX_CONVERT_SIZE )
	{
		ri.Con_Printf ( PRINT_DEVELOPER, "GL_Convert_8to24: too large" );
		return false;
	}

	if ( !miplevels )
		miplevels++;

	for( mip=0; mip < miplevels; mip++ )
	{
		for (i=0; i<s; i++)
		{
			p = indata[i];
			outdata[i] = palette[p];
			if (has_alpha)
			{
				if ((palette[indata[i]] & ALPHAMASK) != ALPHAMASK)
				{
					// transparent, so scan around for another color
					// to avoid alpha fringes
					if (i > width && (palette[indata[i-width]] & ALPHAMASK) != 0)
						p = indata[i-width];
					else if (i < s-width && (palette[indata[i+width]] & ALPHAMASK) != 0)
						p = indata[i+width];
					else if (i > 0 && (palette[indata[i-1]] & ALPHAMASK) != 0)
						p = indata[i-1];
					else if (i < s-1 && (palette[indata[i+1]] & ALPHAMASK) != 0)
						p = indata[i+1];
					else
						p = 0;
					// copy rgb components
					((byte *)&outdata[i])[0] = ((byte *)&palette[p])[0];
					((byte *)&outdata[i])[1] = ((byte *)&palette[p])[1];
					((byte *)&outdata[i])[2] = ((byte *)&palette[p])[2];
				}
			}
		}
		indata += s;
		outdata += s;
		width >>= 1;
		height >>= 1;
		if (!width) width=1;
		if (!height) height=1;
		s >>= 2;
	}
	return true;
}

static qboolean GL_Convert_8to8( byte * indata, unsigned * palette, int width, int height, byte * outdata, int miplevels )
{
	int i, s, p;
	int mip;

	s = width*height;

	if ( s > MAX_CONVERT_SIZE )
	{
		ri.Con_Printf ( PRINT_DEVELOPER, "GL_Convert_8to8: too large" );
		return false;
	}

	if ( !miplevels )
		miplevels++;

	for( mip=0; mip < miplevels; mip++ )
	{
		unsigned int r, g, b, c;
		byte * pal;

		for (i=0; i<s; i++)
		{
			p = indata[i];
			pal = ((byte *)palette + (p*4));
			r = ( pal[0] >> 3 ) & 31;
			g = ( pal[1] >> 2 ) & 63;
			b = ( pal[2] >> 3 ) & 31;

			c = r | ( g << 5 ) | ( b << 11 );

			outdata[i] = gl_state.d_16to8table[c];
		}
		indata += s;
		outdata += s;
		s >>= 2;
	}
	return true;
}

static qboolean GL_Convert_24to8( byte * indata, int width, int height, byte * outdata, int miplevels )
{
	int i, s;
	int mip;

	s = width*height;

	if ( s > MAX_CONVERT_SIZE )
	{
		ri.Con_Printf ( PRINT_DEVELOPER, "GL_Convert_24to8: too large" );
		return false;
	}

	if ( !miplevels )
		miplevels++;

	for( mip=0; mip < miplevels; mip++ )
	{
		unsigned int r, g, b, c;

		for (i=0; i<s; i++)
		{
			r = ( indata[0] >> 3 ) & 31;
			g = ( indata[1] >> 2 ) & 63;
			b = ( indata[2] >> 3 ) & 31;

			c = r | ( g << 5 ) | ( b << 11 );

			outdata[i] = gl_state.d_16to8table[c];
			indata += 4;
		}
		outdata += s;
		s >>= 2;
	}
	return true;
}

void GL_SetBilinear( const char *name, qboolean bilinear, imagetype_t type )
{
	image_t *image;

	image = GL_FindImage( name, type );


	if (!(bilinear ^ image->bilinear))
		return;

	GL_BindImage( image );

	if (bilinear)
	{
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
		image->bilinear = true;
	}
	else
	{
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		image->bilinear = false;
	}
}


static void GL_RemoveAlphaFringes(byte *pic, int width, int height)
{
	int j, s;
	unsigned int p, *picptr;
	unsigned *sintemp;

	s = width * height;
	if (!(sintemp = (unsigned *) malloc(s * sizeof(unsigned))))
		return;

	for (j = 0, picptr = (unsigned *)pic; j < s; j++) {
		p = *(picptr+j);
		// Check for alpha in the texture.
		if ( (p & ALPHAMASK) != ALPHAMASK) { // transparent, so scan around for another color
			// to avoid alpha fringes

			if (j > width && (picptr[j-width] & ALPHAMASK) != 0)
				p = picptr[j-width];
			else if (j < s-width && (picptr[j+width] & ALPHAMASK) != 0)
				p = picptr[j+width];
			else if (j > 0 && (picptr[j-1] & ALPHAMASK) != 0)
				p = picptr[j-1];
			else if (j < s-1 && (picptr[j+1] & ALPHAMASK) != 0)
				p = picptr[j+1];
			else
				p = 0;
			// copy rgb components from new pixel and alpha from the old pixel.
			sintemp[j] = (picptr[j] & ALPHAMASK) | (p & (~ALPHAMASK));
		} else {
			sintemp[j] = picptr[j];
		}
	}

	for (j = 0; j < s; j++) {
		*(picptr + j) = sintemp[j];
	}

	free(sintemp);
}

/*
================
GL_LoadPic

This is also used as an entry point for the generated r_notexture
================
*/
image_t *GL_LoadPic (const char *name, byte *pic, byte *palette, int width, int height, imagetype_t type, int has_miplevels, qboolean mipmap, int has_alpha )
{
	image_t *image;
	int i;
	int colormode;
	byte *byte_data;
	byte *unsigned_data;
	//byte *pal_trans;
	unsigned *TempBuffer;

	// find a free image_t
	for (i=0, image=gltextures; i<numgltextures; i++,image++) {
		if (!image->TextureNum) {
			break;
		}
	}
	if (i == numgltextures) {
		if (numgltextures == MAX_GLTEXTURES)
			ri.Sys_Error ( ERR_DROP, "MAX_GLTEXTURES" );
		numgltextures++;
	}
	image = &gltextures[i];
	GL_FreeImage( image );

	if (strlen( name ) >= sizeof(image->name)) {
		ri.Con_Printf ( PRINT_DEVELOPER, "Draw_LoadPic: \"%s\" is too long", name );
		return NULL;
	}
	strcpy ( image->name, name );
	image->hash = GL_HashFileName(name); /* Knightmare */
	image->registration_sequence = registration_sequence;
	image->palette_sequence = palette_sequence;
	image->width = width;
	image->height = height;
	image->type = type;
	image->has_alpha = has_alpha;

	TempBuffer = (unsigned *)malloc( sizeof(unsigned) * (4*MAX_CONVERT_SIZE)/2 );
	if (!TempBuffer) {
		return NULL;
	}
	//pal_trans = (byte *)TempBuffer;

	unsigned_data = NULL;
	byte_data = NULL;
	//
	// establish the color mode we are in
	//
	colormode = 0;
	if (qglColorTableEXT && (gl_ext_palettedtexture->value == 1))
		colormode = 1;
	if (qglColorTableEXT && (gl_ext_palettedtexture->value == 2))
		colormode = 2;

	if (image->type == it_decal)
		colormode = 0;

	if (colormode == 0 || has_alpha)
	{
		if (palette)
		{
			if ( !GL_Convert_8to24 ( pic, (unsigned *)palette, width, height, TempBuffer, ( (mipmap) ? has_miplevels : 0 ), has_alpha ) )
			{
				free( TempBuffer );
				return NULL;
			}
			unsigned_data = (byte *)TempBuffer;
		}
		else
		{
			unsigned_data = pic;
			if (has_alpha)
				GL_RemoveAlphaFringes( unsigned_data,width,height );
		}
	}
	else if (colormode == 1)
	{
		if (palette)
		{
			if ( !GL_Convert_8to8 ( pic, (unsigned *)palette, width, height, (byte *)TempBuffer, ( (mipmap) ? has_miplevels : 0 ) ) )
			{
				free( TempBuffer );
				return NULL;
			}
			byte_data = (byte *)TempBuffer;
		}
		else
		{
			if ( !GL_Convert_24to8 ( pic, width, height, (byte *)TempBuffer, ( (mipmap) ? has_miplevels : 0 ) ) )
			{
				free( TempBuffer );
				return NULL;
			}
			byte_data = (byte *)TempBuffer;
		}
		image->palette = setbasepalette;
		image->paletteCRC = basepalettecrc;
	}
	else if (colormode == 2)
	{
		// use palettized texture
		if ( (width * height) > (48*48) )
		{
			if (palette)
			{
				image->paletteCRC = CRC_Block((byte*) palette, 1024 );
				image->palette = (byte*) malloc ( 768 );
				if ( !image->palette )
				{
					ri.Con_Printf ( PRINT_DEVELOPER, "GL_LoadPic: Failed on memory allocation of 1024 byte" );
					free( TempBuffer );
					return NULL;
				}
				GL_LightScalePalette( palette, !mipmap );
				for ( i = 0; i < 256; i++ )
				{
					image->palette[i*3+0] = palette[i*4+0];
					image->palette[i*3+1] = palette[i*4+1];
					image->palette[i*3+2] = palette[i*4+2];
				}
				byte_data = pic;
			}
			else
			{
				// we don't have a palette so forget it
				unsigned_data = pic;
			}
		}
		else
		{
			if (palette)
			{
				if ( !GL_Convert_8to24 ( pic, (unsigned *)palette, width, height, TempBuffer, ( (mipmap) ? has_miplevels : 0 ), has_alpha ) )
				{
					free( TempBuffer );
					return NULL;
				}
				unsigned_data = (byte *)TempBuffer;
			}
			else
			{
				// we don't have a palette so forget it
				unsigned_data = pic;
			}
		}
	}
	// load little pics into the scrap
	if (image->type == it_pic && byte_data && (colormode == 1)
	    && image->width < 64 && image->height < 64)
	{
		int x, y;
		int i, j, k;
		int texnum;

		x = y = 0;/* silence compiler */
		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] = byte_data[k];
		image->TextureNum = 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;
//		qglGenTextures(1,&image->TextureNum);
		image->TextureNum = TEXNUM_IMAGES + (image - gltextures);
		if ( image->type != it_decal )
			GL_Bind( image->TextureNum );
		if (byte_data)
		{
			if ( image->type == it_decal )
			{
				ri.Con_Printf ( PRINT_DEVELOPER, "GL_LoadPic: We shouldn't be here." );
//				image->decal_data = malloc ( image->width * image->height );
//				memcpy( image->decal_data, byte_data, image->width*image->height );
//				uploaded_paletted = true;
			}
			else if (!GL_Upload8bit( byte_data, image->palette, image->width, image->height, has_miplevels, mipmap ))
			{
				GL_FreeImage( image );
				free( TempBuffer );
				return NULL;
			}
		}
		else if (unsigned_data)
		{
			if (type == it_lum)
			{
				if (!GL_Upload8bitLuminance( unsigned_data, image->has_alpha, image->width, image->height, has_miplevels, mipmap ))
				{
					GL_FreeImage( image );
					free( TempBuffer );
					return NULL;
				}
			}
			else if ( image->type == it_decal )
			{
				uploaded_paletted = false;
				image->decal_data = (byte*)malloc ( image->width * image->height * 4 );
				memcpy( image->decal_data,unsigned_data, image->width*image->height * 4 );
			}
			else if (!GL_Upload32bit( unsigned_data, image->has_alpha, image->width, image->height, has_miplevels, mipmap ))
			{
				GL_FreeImage( image );
				free( TempBuffer );
				return NULL;
			}
		}
		if ( image->type == it_skin )
		{
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_skin_filter_min );
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_skin_filter_max );
		}

		image->upload_width = upload_width; // after power of 2 and scales
		image->upload_height = upload_height;
		image->paletted = uploaded_paletted;
		image->sl = 0;
		image->sh = 1;
		image->tl = 0;
		image->th = 1;
	}

	free( TempBuffer );
	return image;
}

/*
================
GL_LoadWal
================
*/

static image_t *GL_LoadWal( const char *name )
{
	void *buf;
	miptex_t *mt;
	int width, height, ofs;
	image_t *image;

	ri.FS_LoadFile ( name, &buf );
	mt = (miptex_t *) buf;
	if (!mt)
	{
		ri.Con_Printf ( PRINT_ALL, "GL_LoadWal: 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,basepalette, width, height, it_wall, MIPLEVELS, true, false );
	image->palette = setbasepalette;
	image->paletteCRC = basepalettecrc;

	ri.FS_FreeFile ( buf );

	return image;
}

/*
================
GL_LoadSWL
================
*/
static image_t *GL_LoadSWL (const char *name)
{
	void *buf;
	sinmiptex_t *mt;
	int width, height;
	image_t *image;
	int i;
	byte *miplevels[MIPLEVELS];
	imagetype_t type = it_wall;
	qboolean has_alpha;

	ri.FS_LoadFile ( name, &buf );
	mt = (sinmiptex_t *) buf;
	if (!mt)
	{
		ri.Con_Printf ( PRINT_ALL, "GL_LoadSWL: can't load %s\n", name );
		return r_notexture;
	}

	has_alpha = false;
	for (i=0; i<256; i++)
	{
		if ((mt->palette[i*4+0] == 255) && (mt->palette[i*4+1] == 0) && (mt->palette[i*4+2] == 255))
		{
			mt->palette[i*4+3] = 0;
			has_alpha = true;
		}
		else
		{
			mt->palette[i*4+3] = 255;
		}
	}

	width = LittleLong ( mt->width );
	height = LittleLong ( mt->height );
	for( i = 0; i < MIPLEVELS; i++ )
	{
		miplevels[i] = ( byte * )mt + LittleLong ( mt->offsets[i] );
	}

	image = GL_LoadPic ( name, miplevels[0], mt->palette, width, height, type, MIPLEVELS, true, has_alpha );

	//
	// set no filtering
	//
	if (mt->flags & SURF_NOFILTER)
	{
		GL_Bind ( image->TextureNum );
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min );
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}

	ri.FS_FreeFile ( buf );

	return image;
}

/*
===============
GL_FindImage

Finds or loads the given image
===============
*/
image_t	*GL_FindImage (const char *name, imagetype_t type)
{
	image_t	*image;
	int		i, len;
	byte	*pic, *palette;
	int		width, height;
	long	hash; /* Knightmare */

	if (!name)
		return NULL;	//	ri.Sys_Error (ERR_DROP, "GL_FindImage: NULL name");
	len = (int)strlen(name);
	if (len<5)
		return NULL;	//	ri.Sys_Error (ERR_DROP, "GL_FindImage: bad name: %s", name);

	// look for it
	hash = GL_HashFileName(name); /* Knightmare */
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (hash != image->hash) /* Knightmare */
		{
			continue;
		}

		if (!Q_stricmp(name, image->name))
		{
			if (image->palette_sequence != palette_sequence) // need to reload due to palette
			{
				GL_FreeImage(image);
				break;
			}
			image->registration_sequence = registration_sequence;
			return image;
		}
	}

	//
	// load the pic from disk
	//
	pic = NULL;
	palette = NULL;
	if (!Q_stricmp(name+len-4, ".pcx"))
	{
		LoadPCX (name, &pic, &palette, &width, &height);
		if (!pic)
			return NULL; // ri.Sys_Error (ERR_DROP, "GL_FindImage: can't load %s", name);

		image = GL_LoadPic ( name, pic, palette, width, height, type, 0, false, false );
	}
	else if (!Q_stricmp(name+len-4, ".wal"))
	{
		image = GL_LoadWal (name);
	}
	else if (!Q_stricmp(name+len-4, ".tga"))
	{
		qboolean has_alpha;

		if ( !LoadTGA( name, &pic, &width, &height, &palette, &has_alpha ) )
			return NULL;
		image = GL_LoadPic ( name, pic, palette, width, height, type, 0, (type==it_skin), has_alpha );
	}
	else if (!Q_stricmp(name+len-4, ".swl"))
	{
		// Check to see if .swl file exists, if it doesn't
		// then fall back to a .wal
		image = r_notexture;
		if (ri.FS_LoadFile (name, NULL) == -1)
		{
			char temp[128];
			strcpy( temp, name );
			temp[len-4] = 0;
			strcat(temp+len-4, ".wal");
			// Check to see if .wal exists, if it doesn't then error.
			if (ri.FS_LoadFile (temp, NULL) == -1)
			{
				temp[len-4] = 0;
				ri.Con_Printf (PRINT_ALL, "GL_FindImage: can't load %s\n", temp);
			}
			else
			{
				image = GL_LoadWal (temp);
			}
		}
		else
		{
			image = GL_LoadSWL (name);
		}
	}
	else
	{
		return NULL;	//	ri.Sys_Error (ERR_DROP, "GL_FindImage: bad extension on: %s", name);
	}

	free(pic);
	free(palette);
	return image;
}


/*
===============
R_RegisterSkin
===============
*/
struct image_s *R_RegisterSkin (const char *name)
{
	return GL_FindImage (name, it_skin);
}


/*
================
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;

	GL_Draw_InitLocal();

	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
		if (image->TextureNum)
		{
			qglDeleteTextures (1, &image->TextureNum);
		}
		if ( image->palette && image->palette != setbasepalette )
		{
			free(image->palette);
		}
		free(image->decal_data);
		memset(image, 0, sizeof(*image));
	}
}

/*
===============
GL_LoadPalFile
===============
*/
void	GL_LoadPalFile( const char * name )
{
	void *buf;
	byte *pal;
	int i, size;
	image_t *image;

	//
	// see if we need to load a palette in
	//
	if (!Q_stricmp( name, palette_file ) )
	{
		return;
	}

	strcpy( palette_file, name );
	palette_sequence++;
	ri.FS_LoadFile( name, &buf );
	if ( !buf )
	{
		ri.Con_Printf( PRINT_DEVELOPER, "GL_LoadPalFile: couldn't load %s\n", name );
		ri.FS_LoadFile( "pics/default.pal", &buf );
		if ( !buf )
		{
			ri.Sys_Error( ERR_FATAL, "Couldn't load pics/default.pal\n" );
		}

		strcpy(palette_file, "pics/default.pal");
	}
	size = (64+256) * 256;

	//
	// load 16to8 table
	//
	free( gl_state.d_16to8table );
	gl_state.d_16to8table = (byte*)malloc( 65536 );
	memcpy( gl_state.d_16to8table, (byte *)buf + size, 65536 );
	//
	// load palette
	//
	size += 65536;
	pal = (byte *)buf + size;
	{
		byte *Dest3Ptr;
		Dest3Ptr = d_8to24table;
		i = 256;
		do {
			Dest3Ptr[0] = pal[0];
			Dest3Ptr[1] = pal[1];
			Dest3Ptr[2] = pal[2];
			Dest3Ptr[3] = 255;
			Dest3Ptr+=4;
			pal+=3;
		} while (--i);
	}
	d_8to24table[(255*4)+3] = 0; // (Alpha of 255) is transparent
	ri.FS_FreeFile ( buf );

	memcpy( basepalette, d_8to24table, sizeof( basepalette ) );
	basepalettecrc = CRC_Block( basepalette, 1024 );
	{
		byte *PalPtr;
		byte *Dest1Ptr;
		PalPtr = basepalette;
		Dest1Ptr = setbasepalette;
		i = 256; /* 256 colors */
		do {
			Dest1Ptr[0] = PalPtr[0];
			Dest1Ptr[1] = PalPtr[1];
			Dest1Ptr[2] = PalPtr[2];
			PalPtr+=4;
			Dest1Ptr+=3;
		} while (--i);
	}

	for (i=0, image=gltextures; i<numgltextures; i++,image++)
	{
		if ( image->palette )
		{
			if ( image->palette == setbasepalette )
			{
				image->palette_sequence = 0;
			}
			else
			{
				//
				// no need to reload
				//
				image->palette_sequence = palette_sequence;
			}
		}
	}

	if ( r_envmap )
		GL_FreeImage( r_envmap );
	if ( r_goldenvmap )
		GL_FreeImage( r_goldenvmap );
	// 2015 code
	if ( r_gogglesmap )
		GL_FreeImage( r_gogglesmap );

	gl_palette = NULL;
	r_envmap  = GL_FindImage ( "env/envmap.tga", it_pic );
	r_goldenvmap  = GL_FindImage ( "env/goldenvmap.tga", it_pic );
	r_gogglesmap  = GL_FindImage( "env/gogglesmap.tga", it_pic );
	if(!r_gogglesmap) // just incase it can't find the proper image
		r_gogglesmap  = GL_FindImage ( "env/envmap.tga", it_pic );
	R_InitParticleTexture ();
	GL_Draw_InitLocal();
}
/*
================
GL_FreeImage

================
*/
static void GL_FreeImage( image_t * image )
{
	if (image->TextureNum)
	{
		qglDeleteTextures ( 1, &image->TextureNum );
	}
	if ( image->palette && image->palette != setbasepalette )
	{
		free( image->palette );
	}
	free( image->decal_data );
	memset( image, 0, sizeof(*image));
}

/*
===============
GL_LoadPalFile_f
===============
*/
void	GL_LoadPalFile_f( void )
{
	int i;
	image_t *image;
	char texturename[ 128 ];
	int type;
	char palfile[ 1024 ];

	if ( ri.Cmd_Argc() < 2 )
	{
		ri.Con_Printf( PRINT_ALL, "usage: loadpal <palfilename>\n" );
		return;
	}
	strcpy( palfile, ri.Cmd_Argv( 1 ) );
	GL_LoadPalFile( palfile );

	for (i=0, image=gltextures; i<numgltextures; i++, image++)
	{
		strcpy( texturename, image->name );
		type = image->type;
		GL_FreeImage( image );
		GL_FindImage( texturename, (imagetype_t)type );
	}
}

/*
===============
GL_InitImages
===============
*/
void	GL_InitImages (void)
{
	int		i, j;
	float	g = vid_gamma->value;

	registration_sequence = 1;

	// init intensity conversions
	intensity = ri.Cvar_Get ("intensity", "1", 0);

	if ( intensity->value <= 1 )
		ri.Cvar_Set( "intensity", "1" );

	gl_state.inverse_intensity = 1 / intensity->value;

	palette_sequence = 1;
	GL_LoadPalFile( "pics/default.pal" );

	if ( gl_config.renderer & ( GL_RENDERER_VOODOO | GL_RENDERER_VOODOO2 /* | GL_RENDERER_VOODOO3 */ ) )
	{
		g = 1.0F;
	}

	for ( i = 0; i < 256; i++ )
	{
		if ( g == 1 )
		{
			gammatable[i] = i;
		}
		else
		{
			float inf;

			inf = 255 * pow ((float)((i+0.5)/255.5) , (float)(g) ) + 0.5;
			if (inf < 0)
				inf = 0;
			if (inf > 255)
				inf = 255;
			gammatable[i] = inf;
		}
	}

	for (i=0 ; i<256 ; i++)
	{
		j = i*intensity->value;
		if (j > 255)
			j = 255;
		intensitytable[i] = j;
	}
}

/*
===============
GL_ShutdownImages
===============
*/
void GL_ShutdownImages (void)
{
	int	i;
	image_t	*image;
	
	image=gltextures;
	for (i=0; i<numgltextures ; i++, image++)
	{
		if (!image->registration_sequence)
			continue;		// free image_t slot
		// free it
		if (image->TextureNum)
		{
			qglDeleteTextures (1, &image->TextureNum);
		}
		if ( image->palette && image->palette != setbasepalette )
		{
			free(image->palette);
		}
		free(image->decal_data);
		memset(image, 0, sizeof(*image));
	}
	numgltextures = 0;
}

/* Knightmare */
/*
================
GL_HashFileName
================
*/
static long GL_HashFileName (const char *name)
{
	int		i=0;
	long	hash = 0x123;
	char	letter;

	if (name[0] == '/' || name[0] == '\\') i++;	// skip leading slash
	while (name[i] != '\0')
	{
		letter = q_tolower(name[i]);
		if (letter == '\\') letter = '/';	// correct backslashes
		hash = hash*101 + letter;
		i++;
	}
	hash = (hash ^ (hash >> 10) ^ (hash >> 20));
	return hash;
}
/* end Knightmare */
