/*
Copyright (C) 2003-2006 Andrey Nazarov
Copyright (C) 1997-2001 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.

*/

#include "gl_local.h"

image_t *r_notexture;
image_t *r_particletexture;
image_t *r_beamtexture;
image_t *r_dlightTex;
image_t *r_whiteimage;

int gl_filter_min;
int gl_filter_max;
float gl_filter_anisotropy;
int gl_tex_alpha_format;
int gl_tex_solid_format;

static int  upload_width;
static int  upload_height;
static image_t  *upload_image;
bspTexinfo_t    *upload_texinfo;

static cvar_t *gl_noscrap;
static cvar_t *gl_round_down;
static cvar_t *gl_picmip;
static cvar_t *gl_gamma_scale_pics;
static cvar_t *gl_bilerp_chars;
static cvar_t *gl_texturemode;
static cvar_t *gl_texturesolidmode;
static cvar_t *gl_texturealphamode;
static cvar_t *gl_anisotropy;
static cvar_t *gl_saturation;
static cvar_t *gl_intensity;
static cvar_t *gl_gamma;

qboolean GL_Upload8( byte *data, int width, int height, qboolean mipmap );

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

static glmode_t filterModes[] = {
	{ "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 }
};

static int numFilterModes = sizeof( filterModes ) / sizeof( filterModes[0] );

typedef struct {
	char *name;
	int mode;
} gltmode_t;

static gltmode_t alphaModes[] = {
	{ "default", 4 },
	{ "GL_RGBA", GL_RGBA },
	{ "GL_RGBA8", GL_RGBA8 },
	{ "GL_RGB5_A1", GL_RGB5_A1 },
	{ "GL_RGBA4", GL_RGBA4 },
	{ "GL_RGBA2", GL_RGBA2 }
};

static int numAlphaModes = sizeof( alphaModes ) / sizeof( alphaModes[0] );

static gltmode_t solidModes[] = {
	{ "default", 4 },
	{ "GL_RGB", GL_RGB },
	{ "GL_RGB8", GL_RGB8 },
	{ "GL_RGB5", GL_RGB5 },
	{ "GL_RGB4", GL_RGB4 },
	{ "GL_R3_G3_B2", GL_R3_G3_B2 },
	{ "GL_LUMINANCE", GL_LUMINANCE },
#ifdef GL_RGB2_EXT
	{ "GL_RGB2", GL_RGB2_EXT }
#endif
};

static int numSolidModes = sizeof( solidModes ) / sizeof( solidModes[0] );

/*
===============
GL_UpdateTextureMode
===============
*/
static void GL_UpdateTextureMode( cvar_t *self, void *arg ) {
	int		i;
	image_t	*image;

	for( i = 0; i < numFilterModes ; i++ ) {
		if( !Q_stricmp( filterModes[i].name, self->string ) )
			break;
	}

	if( i == numFilterModes ) {
		Com_WPrintf( "Bad texture filter name\n" );
		cvar.Set( "gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST" );
		gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
		gl_filter_max = GL_LINEAR;
	} else {
		gl_filter_min = filterModes[i].minimize;
		gl_filter_max = filterModes[i].maximize;
	}

	// change all the existing mipmap texture objects
	for( i = 0, image = r_images; i < r_numImages; i++, image++ ) {
		if( image->type == it_wall || image->type == it_skin ) {
			GL_BindTexture( image->texnum );
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    gl_filter_min );
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                    gl_filter_max );
		}
	}
}

/*
===============
GL_UpdateAnisotropy
===============
*/
static void GL_UpdateAnisotropy( cvar_t *self, void *arg ) {
	int		i;
	image_t	*image;

	if( gl_config.maxAnisotropy < 2 ) {
		return;
	}

	gl_filter_anisotropy = self->value;
	if( gl_filter_anisotropy < 1 ) {
		gl_filter_anisotropy = 1;
	} else if( gl_filter_anisotropy > gl_config.maxAnisotropy ) {
		gl_filter_anisotropy = gl_config.maxAnisotropy;
	}

	// change all the existing mipmap texture objects
	for( i = 0, image = r_images; i < r_numImages; i++, image++ ) {
		if( image->type  == it_wall || image->type  == it_skin ) {
			GL_BindTexture( image->texnum );
			qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
				gl_filter_anisotropy );
		}
	}
}

/*
===============
GL_TextureAlphaMode
===============
*/
static void GL_TextureAlphaMode( void ) {
	int		i;

	for( i = 0; i < numAlphaModes; i++ ) {
		if( !Q_stricmp( alphaModes[i].name, gl_texturealphamode->string ) )
			break;
	}

	if( i == numAlphaModes ) {
		Com_Printf( "Bad texture alpha mode name %s\n",
            gl_texturealphamode->string );
		cvar.Set( "gl_texturealphamode", "default" );
	    gl_tex_alpha_format = alphaModes[0].mode;
		return;
	}

	gl_tex_alpha_format = alphaModes[i].mode;
}

/*
===============
GL_TextureSolidMode
===============
*/
static void GL_TextureSolidMode( void ) {
	int		i;

	for( i = 0; i < numSolidModes; i++ ) {
		if( !Q_stricmp( solidModes[i].name, gl_texturesolidmode->string ) )
			break;
	}

	if( i == numSolidModes ) {
		Com_Printf( "Bad texture texture mode name %s\n",
            gl_texturesolidmode->string );
		cvar.Set( "gl_texturesolidmode", "default" );
	    gl_tex_solid_format = solidModes[0].mode;
		return;
	}

	gl_tex_solid_format = solidModes[i].mode;
}

/*
===============
GL_UpdateCharsetMode
===============
*/
static void GL_UpdateCharsetMode( cvar_t *self, void *arg ) {
	int		i;
	image_t	*image;

	// change all the existing charset texture objects
	for( i = 0, image = r_images; i < r_numImages; i++, image++ ) {
		if( image->type == it_charset ) {
			GL_BindTexture( image->texnum );
			if( self->integer ) {
				qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR );
				qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                    GL_LINEAR );
			} else {
				qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                    GL_NEAREST );
				qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                    GL_NEAREST );
			}
		}
	}
}

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

  SCRAP ALLOCATION

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

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

#define	SCRAP_BLOCK_WIDTH		256
#define	SCRAP_BLOCK_HEIGHT		256

#define SCRAP_TEXNUM			( MAX_RIMAGES + 1 )

static int	scrap_inuse[SCRAP_BLOCK_WIDTH];
static byte	scrap_data[SCRAP_BLOCK_WIDTH * SCRAP_BLOCK_HEIGHT];
qboolean	scrap_dirty;

static qboolean Scrap_AllocBlock( int w, int h, int *s, int *t ) {
    int i, j;
    int x, y, maxInuse, minInuse;

	x = 0; y = SCRAP_BLOCK_HEIGHT;
    minInuse = SCRAP_BLOCK_HEIGHT;
    for( i = 0; i < SCRAP_BLOCK_WIDTH - w; i++ ) {
        maxInuse = 0;
        for( j = 0; j < w; j++ ) {
            if( scrap_inuse[ i + j ] >= minInuse ) {
                break;
            }
            if( maxInuse < scrap_inuse[ i + j ] ) {
                maxInuse = scrap_inuse[ i + j ];
            }
        }
        if( j == w ) {
            x = i;
            y = minInuse = maxInuse;
        }
    }

    if( y + h > SCRAP_BLOCK_HEIGHT ) {
        return qfalse;
    }
    
    for( i = 0; i < w; i++ ) {
        scrap_inuse[ x + i ] = y + h;
    }

    *s = x;
    *t = y;
    return qtrue;
}

void Scrap_Init( void ) {
    int i;
    
    for( i = 0; i < SCRAP_BLOCK_WIDTH; i++ ) {
        scrap_inuse[i] = 0;
    }
}

void Scrap_Upload( void ) {
	//Com_Printf( "Scrap_Upload()\n" );
    GL_BindTexture( SCRAP_TEXNUM );
	GL_Upload8( scrap_data, SCRAP_BLOCK_WIDTH, SCRAP_BLOCK_HEIGHT, qfalse );
	scrap_dirty = qfalse;
}


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

IMAGE FLOOD FILLING

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


typedef struct {
	short		x, y;
} floodfill_t;

// must be a power of 2
#define FLOODFILL_FIFO_SIZE 0x1000
#define FLOODFILL_FIFO_MASK ( FLOODFILL_FIFO_SIZE - 1 )

#define FLOODFILL_STEP( off, dx, dy ) \
    do { \
        if (pos[off] == fillcolor) { \
            pos[off] = 255; \
            fifo[inpt].x = x + (dx); \
            fifo[inpt].y = y + (dy); \
            inpt = (inpt + 1) & FLOODFILL_FIFO_MASK; \
        } else if (pos[off] != 255) { \
            fdc = pos[off]; \
        } \
    } while( 0 )

/*
=================
Mod_FloodFillSkin

Fill background pixels so mipmapping doesn't have haloes
=================
*/
static void R_FloodFillSkin( byte *skin, int skinwidth, int skinheight ) {
	byte				fillcolor = *skin; // assume this is the pixel to fill
	floodfill_t			fifo[FLOODFILL_FIFO_SIZE];
	int					inpt = 0, outpt = 0;
	int					filledcolor = -1;
	int					i;

	if (filledcolor == -1)
	{
		filledcolor = 0;
		// attempt to find opaque black
		for (i = 0; i < 256; ++i)
			if (d_8to24table[i] == 255) {
				// alpha 1.0
				filledcolor = i;
				break;
			}
	}

	// can't fill to filled color or to transparent color
    // (used as visited marker)
	if ((fillcolor == filledcolor) || (fillcolor == 255)) {
		return;
	}

	fifo[inpt].x = 0, fifo[inpt].y = 0;
	inpt = (inpt + 1) & FLOODFILL_FIFO_MASK;

	while (outpt != inpt) {
		int			x = fifo[outpt].x, y = fifo[outpt].y;
		int			fdc = filledcolor;
		byte		*pos = &skin[x + skinwidth * y];

		outpt = (outpt + 1) & FLOODFILL_FIFO_MASK;

		if (x > 0) FLOODFILL_STEP( -1, -1, 0 );
		if (x < skinwidth - 1) FLOODFILL_STEP( 1, 1, 0 );
		if (y > 0) FLOODFILL_STEP( -skinwidth, 0, -1 );
		if (y < skinheight - 1)	FLOODFILL_STEP( skinwidth, 0, 1 );
		
		skin[x + skinwidth * y] = fdc;
	}
}

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

static byte gammatable[256];
static byte intensitytable[256];
static byte gammaintensitytable[256];

/*
================
GL_Saturation

atu 20061129
================
*/
static void GL_Saturation( byte *in, int inwidth, int inheight ) {
	int		i, c;
	byte	*p;
	int	    r, g, b, min, max, mid;
    float   sat;

	p = in;
	c = inwidth * inheight;

    sat = gl_saturation->value;
    if( sat >= 1 ) {
        return;
    }
    if( sat < 0 ) {
        sat = 0;
    }
	
	for( i = 0; i < c; i++, p += 4 ) {
		r = p[0];
		g = p[1];
		b = p[2];
		min = max = r;
		if ( g < min ) min = g;
		if ( b < min ) min = b;
		if ( g > max ) max = g;
		if ( b > max ) max = b;
		mid = ( min + max ) >> 1;
		p[0] = mid + ( r - mid ) * sat;
		p[1] = mid + ( g - mid ) * sat;
		p[2] = mid + ( b - mid ) * sat;
	}
}

/*
================
GL_LightScaleTexture

Scale up the pixel values in a texture to increase the
lighting range
================
*/
static void GL_LightScaleTexture( byte *in, int inwidth, int inheight, qboolean mipmap ) {
	int		i, c;
	byte	*p;

	p = in;
	c = inwidth * inheight;
	

	if( mipmap ) {
		for( i = 0; i < c; i++, p += 4 ) {
			p[0] = gammaintensitytable[p[0]];
			p[1] = gammaintensitytable[p[1]];
			p[2] = gammaintensitytable[p[2]];
		}
	} else {
		for( i = 0; i < c; i++, p += 4 ) {
			p[0] = gammatable[p[0]];
			p[1] = gammatable[p[1]];
			p[2] = gammatable[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;
		}
	}
}

/*
===============
GL_Upload32
===============
*/
qboolean GL_Upload32( byte *data, int width, int height, qboolean mipmap ) {
	byte		*scaled;
	int			scaled_width, scaled_height;
	int			i, c;
	byte	    *scan;
	int         comp;
    qboolean    isalpha;

	scaled_width = Q_CeilPowerOfTwo( width );
	scaled_height = Q_CeilPowerOfTwo( height );

	if( mipmap ) {
		if( gl_round_down->integer && scaled_width > width )
			scaled_width >>= 1;

		if( gl_round_down->integer && scaled_height > height )
			scaled_height >>= 1;

		// let people sample down the world textures for speed
		scaled_width >>= gl_picmip->integer;
		scaled_height >>= gl_picmip->integer;
	}

	// don't ever bother with >256 textures
	while( scaled_width > gl_static.maxTextureSize ||
            scaled_height > gl_static.maxTextureSize )
    {
		scaled_width >>= 1;
		scaled_height >>= 1;
	}
	
	if( scaled_width < 1 ) {
		scaled_width = 1;
	}
	if( scaled_height < 1 ) {
		scaled_height = 1;
	}

	upload_width = scaled_width;
	upload_height = scaled_height;

	// set saturation and lightscale before mipmap
	comp = gl_tex_solid_format;
    if( upload_image->type == it_wall &&
	    gl_saturation->value != 1 &&
        ( !upload_texinfo ||
          !( upload_texinfo->flags & (SURF_SKY|SURF_WARP) ) ) )
    {	
		GL_Saturation( data, width, height );
        if( gl_saturation->value == 0 ) {
            comp = GL_LUMINANCE;
        }
	}

	if( !gl_hwgamma->integer &&
        ( mipmap || gl_gamma_scale_pics->integer ) )
    {
		GL_LightScaleTexture( data, width, height, mipmap );
	}

	// scan the texture for any non-255 alpha
	c = width * height;
	scan = data + 3;
    isalpha = qfalse;
	for( i = 0; i < c; i++, scan += 4 ) {
		if( *scan != 255 ) {
            isalpha = qtrue;
			comp = gl_tex_alpha_format;
			break;
		}
	}

	if( scaled_width == width && scaled_height == height ) {
        /* optimized case, do not reallocate */
		scaled = data;
	} else {
		scaled = R_Malloc( scaled_width * scaled_height * 4 );
		R_ResampleTexture( data, width, height, scaled,
                scaled_width, scaled_height );
	}


	qglTexImage2D( GL_TEXTURE_2D, 0, comp, scaled_width, scaled_height, 0,
            GL_RGBA, GL_UNSIGNED_BYTE, scaled );

	if( mipmap ) {
		int miplevel = 0;

		while( scaled_width > 1 || scaled_height > 1 ) {
			GL_MipMap( 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 );
		}
	}

    if( mipmap ) {
        qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, gl_filter_min );
        qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, gl_filter_max );
        
        if( gl_config.maxAnisotropy >= 2 ) {
            qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT,
                    gl_filter_anisotropy );
        }
	} else if( upload_image->type == it_charset && !gl_bilerp_chars->integer ) {
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	} else {
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
		qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}
    

	if( scaled != data ) {
		com.Free( scaled );
	}

	return isalpha;
}

/*
===============
GL_Upload8

Returns has_alpha
===============
*/
qboolean GL_Upload8( byte *data, int width, int height, qboolean mipmap ) {
	byte	buffer[512*256*4];
	byte		*dest;
	int			i, s;
	int			p;

	s = width * height;
	if( s > 512*256 ) {
		Com_Error( ERR_FATAL, "GL_Upload8: %s is too large: width=%d height=%d",
			upload_image->name, width, height );
	}

    dest = buffer;
    for( i = 0; i < s; i++ ) {
        p = data[i];
        *( uint32 * )dest = d_8to24table[p];

        if (p == 255) {	
            // transparent, so scan around for another color
            // to avoid alpha fringes
            // FIXME: do a full flood fill so mips work...
            if (i > width && data[i-width] != 255)
                p = data[i-width];
            else if (i < s-width && data[i+width] != 255)
                p = data[i+width];
            else if (i > 0 && data[i-1] != 255)
                p = data[i-1];
            else if (i < s-1 && data[i+1] != 255)
                p = data[i+1];
            else
                p = 0;
            // copy rgb components
            dest[0] = ((byte *)&d_8to24table[p])[0];
            dest[1] = ((byte *)&d_8to24table[p])[1];
            dest[2] = ((byte *)&d_8to24table[p])[2];
        }

        dest += 4;
    }

    return GL_Upload32( buffer, width, height, mipmap );

}

/*
===============
R_ImageForHandle
===============
*/
image_t *R_ImageForHandle( qhandle_t hPic ) {
	if( hPic < 0 || hPic >= r_numImages ) {
        *( int * )0 = 1;
		Com_Error( ERR_FATAL, "R_ImageForHandle: %d out of range", hPic );
	}

	return &r_images[hPic];
}

/*
===============
R_RegisterSkin
===============
*/
qhandle_t R_RegisterSkin( const char *name ) {
	image_t	*image;

	image = R_FindImage( name, it_skin );
	if( !image ) {
		return 0;
	}

	return ( image - r_images );
}

/*
================
R_RegisterPic
================
*/
qhandle_t R_RegisterPic( const char *name ) {
	image_t	*image;
	char	fullname[MAX_QPATH];

	if( name[0] == '*' ) {
		image = R_FindImage( name, it_pic );
    } else if( name[0] != '/' && name[0] != '\\' ) {
		Com_sprintf( fullname, sizeof( fullname ), "pics/%s", name );
		COM_DefaultExtension( fullname, ".pcx", sizeof( fullname ) );
		image = R_FindImage( fullname, it_pic );
	} else {
		image = R_FindImage( name + 1, it_pic );
	}

	if( !image ) {
		return 0;
	}

	return ( image - r_images );
}



/*
================
R_LoadImage
================
*/
void R_LoadImage( image_t *image, byte *pic, int width, int height,
        imagetype_t type, imageflags_t flags )
{
	qboolean mipmap, transparent;
	byte *src, *dst, *ptr;
	int i, j, s, t;

	image->width = width;
	image->height = height;
	image->type = type;
	upload_image = image;

	// HACK: get dimensions from 8-bit texture
	if( flags & (if_replace_wal|if_replace_pcx) ) {
		char buffer[MAX_QPATH];
		int length;
	    miptex_t	mt;
        pcx_t pcx;
        fileHandle_t f;

		length = strlen( image->name );
		if( length > 4 && image->name[ length - 4 ] == '.' ) {
		    strncpy( buffer, image->name, length - 4 );
            if( flags & if_replace_wal ) {
                strcpy( buffer + length - 4, ".wal" );
                fs.FOpenFile( buffer, &f, FS_MODE_READ );
                if( f ) {
                    length = fs.Read( &mt, sizeof( mt ), f );
                    if( length == sizeof( mt ) ) {
                        image->width = LittleLong( mt.width );
                        image->height = LittleLong( mt.height );
                    }
                    fs.FCloseFile( f );
                }
            } else {
                strcpy( buffer + length - 4, ".pcx" );
                fs.FOpenFile( buffer, &f, FS_MODE_READ );
                if( f ) {
                    length = fs.Read( &pcx, sizeof( pcx ), f );
                    if( length == sizeof( pcx ) ) {
                        image->width = LittleShort( pcx.xmax );
                        image->height = LittleShort( pcx.ymax );
                    }
                    fs.FCloseFile( f );
                }
            }
		}
	}
	
	if( type == it_pic && ( flags & if_paletted ) &&
		width < 64 && height < 64 && !gl_noscrap->integer )
	{
		if( Scrap_AllocBlock( width, height, &s, &t ) ) {
			src = pic;
			dst = &scrap_data[t * SCRAP_BLOCK_WIDTH + s];
			for( i = 0; i < height; i++ ) {
				ptr = dst;
				for( j = 0; j < width; j++ ) {
					 *ptr++ = *src++;
				}
				dst += SCRAP_BLOCK_WIDTH;
			}

			flags |= if_scrap | if_transparent;

			image->texnum = SCRAP_TEXNUM;
			image->upload_width = width;
			image->upload_height = height;
			image->flags = flags;
			image->sl = ( s + 0.01f ) / ( float )SCRAP_BLOCK_WIDTH;
			image->sh = ( s + width - 0.01f ) / ( float )SCRAP_BLOCK_WIDTH;
			image->tl = ( t + 0.01f ) / ( float )SCRAP_BLOCK_HEIGHT;
			image->th = ( t + height - 0.01f ) / ( float )SCRAP_BLOCK_HEIGHT;

			scrap_dirty = qtrue;
			if( !gl_static.registering ) {
				Scrap_Upload();
			}

			com.Free( pic );

			return;
		}
	}

	if( type == it_skin && ( flags & if_paletted ) )
		R_FloodFillSkin( pic, width, height );

	mipmap = qfalse;
    if( type == it_wall || type == it_skin ) {
        mipmap = qtrue;
    }
    image->texnum = ( image - r_images ) + 1;
	GL_BindTexture( image->texnum );
	if( flags & if_paletted ) {
		transparent = GL_Upload8( pic, width, height, mipmap );
    } else {
		transparent = GL_Upload32( pic, width, height, mipmap );
    }
	if( transparent ) {
		flags |= if_transparent;
	}
	image->upload_width = upload_width;		// after power of 2 and scales
	image->upload_height = upload_height;
	image->flags = flags;
	image->sl = 0;
	image->sh = 1;
	image->tl = 0;
	image->th = 1;

#if 0
    if( width != upload_width || height != upload_height ) {
        Com_Printf( "Resampled '%s' from %dx%d to %dx%d\n",
                image->name, width, height, upload_width, upload_height );
    }
#endif

	/* don't free autogenerated images */
	if( flags & if_auto ) {
		return;
	}
		
	/* don't free *.wal textures */
	if( type == it_wall && ( flags & if_paletted ) ) {
		return;
	}

	com.Free( pic );
}


/*
================
R_LoadWal
================
*/
image_t *R_LoadWal( const char *name ) {
	miptex_t	*mt;
	uint32			width, height, ofs;
	uint32		length;
	image_t		*image;

	length = fs.LoadFile( name, ( void ** )&mt );
	if( !mt ) {
		Com_DPrintf( "GL_LoadWal: can't load %s\n", name);
		return r_notexture;
	}

	width = LittleLong( mt->width );
	height = LittleLong( mt->height );
	ofs = LittleLong( mt->offsets[0] );

	if( ofs + width * height > length ) {
		Com_DPrintf( "GL_LoadWal: '%s' is malformed\n", name );
		fs.FreeFile( ( void * )mt );
		return NULL;
	}

	image = R_CreateImage( name, ( byte * )mt + ofs, width, height, it_wall, if_paletted );

	fs.FreeFile( ( void * )mt );

	return image;
}

void R_FreeImage( image_t *image ) {
    if( !( image->flags & if_scrap ) ) {
    	qglDeleteTextures( 1, &image->texnum );
    }
}

static void GL_BuildGammaTables( void ) {
	int i;
	float inf, g = gl_gamma->value;

	if( gl_config.renderer == GL_RENDERER_VOODOO || g == 1.0f ) {
        for( i = 0; i < 256; i++ ) {
            gammatable[i] = i;
			gammaintensitytable[i] = intensitytable[i];
        }
	} else {
    	for( i = 0; i < 256; i++ ) {
			inf = 255 * pow( ( i + 0.5 ) / 255.5, g ) + 0.5;
			if( inf > 255 ) {
                inf = 255;
            }
			gammatable[i] = inf;
			gammaintensitytable[i] = intensitytable[gammatable[i]];
	    }
    }
}

static void GL_UpdateHardwareGamma( cvar_t *self, void *arg  ) {
    GL_BuildGammaTables();
    video.UpdateGamma( gammatable );
}

#define DLIGHT_TEXTURE_SIZE     16

static void GL_InitDlightTexture( void ) {
    byte pixels[DLIGHT_TEXTURE_SIZE*DLIGHT_TEXTURE_SIZE*4];
    byte *dst;
    float x, y, f;
    int i, j;

    dst = pixels;
    for( i = 0; i < DLIGHT_TEXTURE_SIZE; i++ ) {
        for( j = 0; j < DLIGHT_TEXTURE_SIZE; j++ ) {
            x = j - DLIGHT_TEXTURE_SIZE/2 + 0.5f;
            y = i - DLIGHT_TEXTURE_SIZE/2 + 0.5f;
            f = sqrt( x * x + y * y );
            f = 1.0f - f / ( DLIGHT_TEXTURE_SIZE/2 - 1.5f );
            if( f < 0 ) f = 0;
            else if( f > 1 ) f = 1;
            dst[0] = 255 * f;
            dst[1] = 255 * f;
            dst[2] = 255 * f;
            dst[3] = 255;
            dst += 4;
        }
    }
    
    r_dlightTex = R_CreateImage( "*dlightTexture", pixels, DLIGHT_TEXTURE_SIZE,
            DLIGHT_TEXTURE_SIZE, it_pic, if_auto );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
}

static byte	dottexture[8][8] = {
	{0,0,0,0,0,0,0,0},
	{0,0,1,1,0,0,0,0},
	{0,1,1,1,1,0,0,0},
	{0,1,1,1,1,0,0,0},
	{0,0,1,1,0,0,0,0},
	{0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0},
	{0,0,0,0,0,0,0,0},
};

static void GL_InitDefaultTexture( void ) {
    int i, j;
    byte pixels[8*8*4];
    byte *dst;
    
    dst = pixels;
    for( i = 0; i < 8; i++ ) {
        for( j = 0; j < 8; j++ ) {
            dst[0] = dottexture[ i & 3 ][ j & 3 ] * 255;
            dst[1] = 0;
            dst[2] = 0;
            dst[3] = 255;
            dst += 4;
		}
	}
    
    r_notexture = R_CreateImage( "*notexture", pixels, 8, 8, it_wall, if_auto );
}

static void GL_InitParticleTexture( void ) {
    int i, j;
    byte pixels[8*8*4];
    byte *dst;
    
    dst = pixels;
    for( i = 0; i < 8; i++ ) {
        for( j = 0; j < 8; j++ ) {
            dst[0] = 255;
            dst[1] = 255;
            dst[2] = 255;
            dst[3] = dottexture[ i ][ j ] * 255;
            dst += 4;
		}
	}
    
    r_particletexture = R_CreateImage( "*particletexture", pixels, 8, 8,
            it_sprite, if_auto );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
}

static void GL_InitWhiteImage( void ) {
    int i, j;
    byte pixels[8*8*4];
    byte *dst;
    
    dst = pixels;
    for( i = 0; i < 8; i++ ) {
        for( j = 0; j < 8; j++ ) {
            *( uint32 * )dst = ( uint32 )-1;
            dst += 4;
		}
	}
    
    r_whiteimage = R_CreateImage( "*whiteimage", pixels, 8, 8, it_pic, if_auto );
}

#define BEAM_TEXTURE_SIZE	16

static void GL_InitBeamTexture( void ) {
    byte pixels[BEAM_TEXTURE_SIZE*BEAM_TEXTURE_SIZE*4];
    byte *dst;
    float f;
    int i, j;

    dst = pixels;
    for( i = 0; i < BEAM_TEXTURE_SIZE; i++ ) {
        for( j = 0; j < BEAM_TEXTURE_SIZE; j++ ) {
            f = fabs( j - BEAM_TEXTURE_SIZE/2 ) - 0.5f;
            f = 1.0f - f / ( BEAM_TEXTURE_SIZE/2 - 2.5f );
            if( f < 0 ) f = 0;
            else if( f > 1 ) f = 1;
            dst[0] = 255 * f;
            dst[1] = 255 * f;
            dst[2] = 255 * f;
            dst[3] = 255;
            dst += 4;
        }
    }
    
    r_beamtexture = R_CreateImage( "*beamTexture", pixels, BEAM_TEXTURE_SIZE,
            BEAM_TEXTURE_SIZE, it_pic, if_auto );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
   // qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
   // qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
   /* 
    {
        float borderColor[4];
        
        borderColor[0] = 1;
        borderColor[1] = 1;
        borderColor[2] = 1;
        borderColor[3] = 0;

        qglTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor );
    }
	*/
}

/*
===============
GL_InitImages
===============
*/
void GL_InitImages( void ) {
	int i, j;
	float f;

	registration_sequence = 1;

	if( r_numImages ) {
		Com_Error( ERR_FATAL, "GL_InitImages: %d images still not freed",
            r_numImages );
	}

	gl_bilerp_chars = cvar.Get( "gl_bilerp_chars", "0", 0 );
    gl_bilerp_chars->changedFunc = GL_UpdateCharsetMode;
	gl_texturemode = cvar.Get( "gl_texturemode", "GL_LINEAR_MIPMAP_NEAREST",
            CVAR_ARCHIVE );
    gl_texturemode->changedFunc = GL_UpdateTextureMode;
	gl_anisotropy = cvar.Get( "gl_anisotropy", "1", CVAR_ARCHIVE );
    gl_anisotropy->changedFunc = GL_UpdateAnisotropy;
	gl_noscrap = cvar.Get( "gl_noscrap", "0", CVAR_LATCHED );
    gl_round_down = cvar.Get( "gl_round_down", "0", CVAR_LATCHED );
    gl_picmip = cvar.Get( "gl_picmip", "0", CVAR_LATCHED );
    gl_gamma_scale_pics = cvar.Get( "gl_gamma_scale_pics", "0", CVAR_LATCHED );
	gl_texturealphamode = cvar.Get( "gl_texturealphamode", "default",
            CVAR_ARCHIVE|CVAR_LATCHED );
	gl_texturesolidmode = cvar.Get( "gl_texturesolidmode", "default",
            CVAR_ARCHIVE|CVAR_LATCHED );
	gl_saturation = cvar.Get( "gl_saturation", "1", CVAR_ARCHIVE|CVAR_LATCHED );
	gl_intensity = cvar.Get( "intensity", "1", CVAR_ARCHIVE|CVAR_LATCHED );
    if( gl_hwgamma->integer ) {
        gl_gamma = cvar.Get( "vid_gamma", "1", 0 );
        gl_gamma->changedFunc = GL_UpdateHardwareGamma;
    } else {
        gl_gamma = cvar.Get( "vid_gamma", "1", CVAR_LATCHED );
    }

	R_InitImageManager();

	Scrap_Init();

	R_GetPalette( NULL );

	if( gl_intensity->value < 1 ) {
		cvar.SetValue( "intensity", 1 );
	}
	f = gl_intensity->value;
	for( i = 0; i < 256; i++ ) {
		j = i * f;
		if( j > 255 ) {
			j = 255;
		}
		intensitytable[i] = j;
	}

    if( gl_hwgamma->integer ) {
        GL_UpdateHardwareGamma( gl_gamma, NULL );
    } else {
        GL_BuildGammaTables();
    }

    GL_TextureAlphaMode();
    GL_TextureSolidMode();

    GL_UpdateTextureMode( gl_texturemode, NULL );
	GL_UpdateAnisotropy( gl_anisotropy, NULL );
	GL_UpdateCharsetMode( gl_bilerp_chars, NULL );

	/* make sure r_notexture == &r_images[0] */
	GL_InitDefaultTexture();
    GL_InitDlightTexture();
    GL_InitParticleTexture();
    GL_InitWhiteImage();
	GL_InitBeamTexture();
}

/*
===============
GL_ShutdownImages
===============
*/
void GL_ShutdownImages( void ) {
    gl_bilerp_chars->changedFunc = NULL;
    gl_texturemode->changedFunc = NULL;
    gl_anisotropy->changedFunc = NULL;
    gl_gamma->changedFunc = NULL;

	R_FreeAllImages();
	R_ShutdownImageManager();
}

