<commit_before>/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2011 <NAME> <<EMAIL>>

This file is part of Daemon source code.

Daemon source code 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.

Daemon source code 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 Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_image.c
#include "tr_local.h"
#include "../../common/Maths.h"

int                  gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
int                  gl_filter_max = GL_LINEAR;

image_t              *r_imageHashTable[ IMAGE_FILE_HASH_SIZE ];

#define Tex_ByteToFloat(v) ( ( (int)(v) - 128 ) / 127.0f )
#define Tex_FloatToByte(v) ( 128 + (int) ( (v) * 127.0f + 0.5 ) )

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

static const textureMode_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  }
};

/*
================
return a hash value for the filename
================
*/
long GenerateImageHashValue( const char *fname )
{
	int  i;
	long hash;
	char letter;

//  ri.Printf(PRINT_ALL, "tr_image::GenerateImageHashValue: '%s'\n", fname);

	hash = 0;
	i = 0;

	while ( fname[ i ] != '\0' )
	{
		letter = tolower( fname[ i ] );

		if ( letter == '\\' )
		{
			letter = '/'; // damn path names
		}

		hash += ( long )( letter ) * ( i + 119 );
		i++;
	}

	hash &= ( IMAGE_FILE_HASH_SIZE - 1 );
	return hash;
}

/*
===============
GL_TextureMode
===============
*/
void GL_TextureMode( const char *string )
{
	int     i;
	image_t *image;

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

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

	gl_filter_min = modes[ i ].minimize;
	gl_filter_max = modes[ i ].maximize;

	// bound texture anisotropy
	if ( glConfig2.textureAnisotropyAvailable )
	{
		if ( r_ext_texture_filter_anisotropic->value > glConfig2.maxTextureAnisotropy )
		{
			ri.Cvar_Set( "r_ext_texture_filter_anisotropic", va( "%f", glConfig2.maxTextureAnisotropy ) );
		}
		else if ( r_ext_texture_filter_anisotropic->value < 1.0 )
		{
			ri.Cvar_Set( "r_ext_texture_filter_anisotropic", "1.0" );
		}
	}

	// change all the existing mipmap texture objects
	for ( i = 0; i < tr.images.currentElements; i++ )
	{
		image = (image_t*) Com_GrowListElement( &tr.images, i );

		if ( image->filterType == FT_DEFAULT )
		{
			GL_Bind( image );

			// set texture filter
			glTexParameterf( image->type, GL_TEXTURE_MIN_FILTER, gl_filter_min );
			glTexParameterf( image->type, GL_TEXTURE_MAG_FILTER, gl_filter_max );

			// set texture anisotropy
			if ( glConfig2.textureAnisotropyAvailable )
			{
				glTexParameterf( image->type, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value );
			}
		}
	}
}

/*
===============
R_ImageList_f
===============
*/
void R_ImageList_f( void )
{
	int        i;
	image_t    *image;
	int        texels;
	int        dataSize;
	int        imageDataSize;
	const char *yesno[] =
	{
		"no ", "yes"
	};
	const char *filter = ri.Cmd_Argc() > 1 ? ri.Cmd_Argv(1) : NULL;

	ri.Printf( PRINT_ALL, "\n      -w-- -h-- -mm- -type-   -if-- wrap --name-------\n" );

	texels = 0;
	dataSize = 0;

	for ( i = 0; i < tr.images.currentElements; i++ )
	{
		image = (image_t*) Com_GrowListElement( &tr.images, i );
		char buffer[ MAX_TOKEN_CHARS ];
		std::string out;

		if ( filter && !Com_Filter( filter, image->name, qtrue ) )
		{
			continue;
		}

		Com_sprintf( buffer, sizeof( buffer ), "%4i: %4i %4i  %s   ",
		           i, image->uploadWidth, image->uploadHeight, yesno[ image->filterType == FT_DEFAULT ] );
		out += buffer;
		switch ( image->type )
		{
			case GL_TEXTURE_2D:
				texels += image->uploadWidth * image->uploadHeight;
				imageDataSize = image->uploadWidth * image->uploadHeight;

				Com_sprintf( buffer, sizeof( buffer ),  "2D   " );
				out += buffer;
				break;

			case GL_TEXTURE_CUBE_MAP:
				texels += image->uploadWidth * image->uploadHeight * 6;
				imageDataSize = image->uploadWidth * image->uploadHeight * 6;

				Com_sprintf( buffer, sizeof( buffer ),  "CUBE " );
				out += buffer;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "???? " );
				out += buffer;
				imageDataSize = image->uploadWidth * image->uploadHeight;
				break;
		}

		switch ( image->internalFormat )
		{
			case GL_RGB8:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB8     " );
				out += buffer;
				imageDataSize *= 3;
				break;

			case GL_RGBA8:
				Com_sprintf( buffer, sizeof( buffer ),  "RGBA8    " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_RGB16:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB      " );
				out += buffer;
				imageDataSize *= 6;
				break;

			case GL_RGB16F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB16F   " );
				out += buffer;
				imageDataSize *= 6;
				break;

			case GL_RGB32F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB32F   " );
				out += buffer;
				imageDataSize *= 12;
				break;

			case GL_RGBA16F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGBA16F  " );
				out += buffer;
				imageDataSize *= 8;
				break;

			case GL_RGBA32F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGBA32F  " );
				out += buffer;
				imageDataSize *= 16;
				break;

			case GL_ALPHA16F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "A16F     " );
				out += buffer;
				imageDataSize *= 2;
				break;

			case GL_ALPHA32F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "A32F     " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_R16F:
				Com_sprintf( buffer, sizeof( buffer ),  "R16F     " );
				out += buffer;
				imageDataSize *= 2;
				break;

			case GL_R32F:
				Com_sprintf( buffer, sizeof( buffer ),  "R32F     " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_LUMINANCE_ALPHA16F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "LA16F    " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_LUMINANCE_ALPHA32F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "LA32F    " );
				out += buffer;
				imageDataSize *= 8;
				break;

			case GL_RG16F:
				Com_sprintf( buffer, sizeof( buffer ),  "RG16F    " );
				out += buffer;
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_RG32F:
				Com_sprintf( buffer, sizeof( buffer ),  "RG32F    " );
				out += buffer;
				imageDataSize *= 8;
				break;

			case GL_COMPRESSED_RGBA:
				Com_sprintf( buffer, sizeof( buffer ),  "      " );
				out += buffer;
				imageDataSize *= 4; // FIXME
				break;

			case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT1     " );
				out += buffer;
				imageDataSize *= 4 / 8;
				break;

			case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT1a    " );
				out += buffer;
				imageDataSize *= 4 / 8;
				break;

			case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT3     " );
				out += buffer;
				imageDataSize *= 4 / 4;
				break;

			case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT5     " );
				out += buffer;
				imageDataSize *= 4 / 4;
				break;

			case GL_DEPTH_COMPONENT16:
				Com_sprintf( buffer, sizeof( buffer ),  "D16      " );
				out += buffer;
				imageDataSize *= 2;
				break;

			case GL_DEPTH_COMPONENT24:
				Com_sprintf( buffer, sizeof( buffer ),  "D24      " );
				out += buffer;
				imageDataSize *= 3;
				break;

			case GL_DEPTH_COMPONENT32:
				Com_sprintf( buffer, sizeof( buffer ),  "D32      " );
				out += buffer;
				imageDataSize *= 4;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "????     " );
				out += buffer;
				imageDataSize *= 4;
				break;
		}

		switch ( image->wrapType.s )
		{
			case WT_REPEAT:
				Com_sprintf( buffer, sizeof( buffer ),  "s.rept  " );
				out += buffer;
				break;

			case WT_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.clmp  " );
				out += buffer;
				break;

			case WT_EDGE_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.eclmp " );
				out += buffer;
				break;

			case WT_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.zclmp " );
				out += buffer;
				break;

			case WT_ALPHA_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.azclmp" );
				out += buffer;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "s.%4i  ", image->wrapType.s );
				out += buffer;
				break;
		}

		switch ( image->wrapType.t )
		{
			case WT_REPEAT:
				Com_sprintf( buffer, sizeof( buffer ),  "t.rept  " );
				out += buffer;
				break;

			case WT_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.clmp  " );
				out += buffer;
				break;

			case WT_EDGE_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.eclmp " );
				out += buffer;
				break;

			case WT_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.zclmp " );
				out += buffer;
				break;

			case WT_ALPHA_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.azclmp" );
				out += buffer;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "t.%4i  ", image->wrapType.t );
				out += buffer;
				break;
		}

		dataSize += imageDataSize;

		ri.Printf( PRINT_ALL, "%s %s\n", out.c_str(), image->name );
	}

	ri.Printf( PRINT_ALL, " ---------\n" );
	ri.Printf( PRINT_ALL, " %i total texels (not including mipmaps)\n", texels );
	ri.Printf( PRINT_ALL, " %d.%02d MB total image memory\n", dataSize / ( 1024 * 1024 ),
	           ( dataSize % ( 1024 * 1024 ) ) * 100 / ( 1024 * 1024 ) );
	ri.Printf( PRINT_ALL, " %i total images\n\n", tr.images.currentElements );
}

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

/*
================
ResampleTexture

Used to resample images in a more general than quartering fashion.

This will only be filtered properly if the resampled size
is greater than half the original size.

If a larger shrinking is needed, use the mipmap function
before or after.
================
*/
static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight,
                             qboolean normalMap )
{
	int      x, y;
	unsigned *inrow, *inrow2;
	unsigned frac, fracstep;
	unsigned p1[ 2048 ], p2[ 2048 ];
	byte     *pix1, *pix2, *pix3, *pix4;
	vec3_t   n, n2, n3, n4;

	fracstep = inwidth * 0x10000 / outwidth;

	frac = fracstep >> 2;

	for ( x = 0; x < outwidth; x++ )
	{
		p1[ x ] = 4 * ( frac >> 16 );
		frac += fracstep;
	}

	frac = 3 * ( fracstep >> 2 );

	for ( x = 0; x < outwidth; x++ )
	{
		p2[ x ] = 4 * ( frac >> 16 );
		frac += fracstep;
	}

	if ( normalMap )
	{
		for ( y = 0; y < outheight; y++ )
		{
			inrow = in + inwidth * ( int )( ( y + 0.25 ) * inheight / outheight );
			inrow2 = in + inwidth * ( int )( ( y + 0.75 ) * inheight / outheight );

			for ( x = 0; x < outwidth; x++ )
			{
				pix1 = ( byte * ) inrow + p1[ x ];
				pix2 = ( byte * ) inrow + p2[ x ];
				pix3 = ( byte * ) inrow2 + p1[ x ];
				pix4 = ( byte * ) inrow2 + p2[ x ];

				n[ 0 ] = Tex_ByteToFloat( pix1[ 0 ] );
				n[ 1 ] = Tex_ByteToFloat( pix1[ 1 ] );
				n[ 2 ] = Tex_ByteToFloat( pix1[ 2 ] );

				n2[ 0 ] = Tex_ByteToFloat( pix2[ 0 ] );
				n2[ 1 ] = Tex_ByteToFloat( pix2[ 1 ] );
				n2[ 2 ] = Tex_ByteToFloat( pix2[ 2 ] );

				n3[ 0 ] = Tex_ByteToFloat( pix3[ 0 ] );
				n3[ 1 ] = Tex_ByteToFloat( pix3[ 1 ] );
				n3[ 2 ] = Tex_ByteToFloat( pix3[ 2 ] );

				n4[ 0 ] = Tex_ByteToFloat( pix4[ 0 ] );
				n4[ 1 ] = Tex_ByteToFloat( pix4[ 1 ] );
				n4[ 2 ] = Tex_ByteToFloat( pix4[ 2 ] );

				VectorAdd( n, n2, n );
				VectorAdd( n, n3, n );
				VectorAdd( n, n4, n );

				if ( !VectorNormalize( n ) )
				{
					VectorSet( n, 0, 0, 1 );
				}

				( ( byte * )( out ) ) [ 0 ] = Tex_FloatToByte( n[ 0 ] );
				( ( byte * )( out ) ) [ 1 ] = Tex_FloatToByte( n[ 1 ] );
				( ( byte * )( out ) ) [ 2 ] = Tex_FloatToByte( n[ 2 ] );
				( ( byte * )( out ) ) [ 3 ] = 255;

				++out;
			}
		}
	}
	else
	{
		for ( y = 0; y < outheight; y++ )
		{
			inrow = in + inwidth * ( int )( ( y + 0.25 ) * inheight / outheight );
			inrow2 = in + inwidth * ( int )( ( y + 0.75 ) * inheight / outheight );

			for ( x = 0; x < outwidth; x++ )
			{
				pix1 = ( byte * ) inrow + p1[ x ];
				pix2 = ( byte * ) inrow + p2[ x ];
				pix3 = ( byte * ) inrow2 + p1[ x ];
				pix4 = ( byte * ) inrow2 + p2[ x ];

				( ( byte * )( out ) ) [ 0 ] = ( pix1[ 0 ] + pix2[ 0 ] + pix3[ 0 ] + pix4[ 0 ] ) >> 2;
				( ( byte * )( out ) ) [ 1 ] = ( pix1[ 1 ] + pix2[ 1 ] + pix3[ 1 ] + pix4[ 1 ] ) >> 2;
				( ( byte * )( out ) ) [ 2 ] = ( pix1[ 2 ] + pix2[ 2 ] + pix3[ 2 ] + pix4[ 2 ] ) >> 2;
				( ( byte * )( out ) ) [ 3 ] = ( pix1[ 3 ] + pix2[ 3 ] + pix3[ 3 ] + pix4[ 3 ] ) >> 2;

				++out;
			}
		}
	}
}

/*
================
R_MipMap2

Operates in place, quartering the size of the texture
Proper linear filter
================
*/
static void R_MipMap2( unsigned *in, int inWidth, int inHeight )
{
	int      i, j, k;
	byte     *outpix;
	int      inWidthMask, inHeightMask;
	int      outWidth, outHeight;
	unsigned *temp;
	byte     *row[ 4 ];

	outWidth = inWidth >> 1;
	outHeight = inHeight >> 1;
	temp = (unsigned int*) ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 );
	outpix = (byte *) temp;

	inWidthMask = ( inWidth << 2 ) - 1; // applied to row indices
	inHeightMask = inHeight - 1; // applied to in indices

	row[ 1 ] = (byte *) &in[( -1 & inHeightMask ) * inWidth ];
	row[ 2 ] = (byte *) &in[   0                            ];
	row[ 3 ] = (byte *) &in[(  1 & inHeightMask ) * inWidth ];

	for ( i = 0; i < inHeight; i += 2 ) // count source, row pairs
	{
		row[ 0 ] = row[ 1 ];
		row[ 1 ] = row[ 2 ];
		row[ 2 ] = row[ 3 ];
		row[ 3 ] = (byte *) &in[ ( ( i + 2 ) & inHeightMask ) * inWidth ];

		for ( j = 0; j < inWidth * 4; j += 8 ) // count source, bytes comprising texel pairs
		{
			for ( k = j; k < j + 4; k++ )
			{
				const int km1 = ( k - 4 ) & inWidthMask;
				const int kp1 = ( k + 4 ) & inWidthMask;
				const int kp2 = ( k + 8 ) & inWidthMask;

				*outpix++ = ( 1 * row[ 0 ][ km1 ] + 2 * row[ 0 ][ k   ] + 2 * row[ 0 ][ kp1 ] + 1 * row[ 0 ][ kp2 ] +
						2 * row[ 1 ][ km1 ] + 4 * row[ 1 ][ k   ] + 4 * row[ 1 ][ kp1 ] + 2 * row[ 1 ][ kp2 ] +
						2 * row[ 2 ][ km1 ] + 4 * row[ 2 ][ k   ] + 4 * row[ 2 ][ kp1 ] + 2 * row[ 2 ][ kp2 ] +
						1 * row[ 3 ][ km1 ] + 2 * row[ 3 ][ k   ] + 2 * row[ 3 ][ kp1 ] + 1 * row[ 3 ][ kp2 ] ) / 36;
			}
		}
	}

	Com_Memcpy( in, temp, outWidth * outHeight * 4 );
	ri.Hunk_FreeTempMemory( temp );
}

/*
================
R_MipMap

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

	if ( !r_simpleMipMaps->integer )
	{
		R_MipMap2( ( unsigned * ) in, width, height );
		return;
	}

	if ( width == 1 && height == 1 )
	{
		return;
	}

	row = width * 4;
	out = in;
	width >>= 1;
	height >>= 1;

	if ( width == 0 || height == 0 )
	{
		width += height; // get largest

		for ( i = 0; i < width; i++, 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;
		}

		return;
	}

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

/*
================
R_MipNormalMap

Operates in place, quartering the size of the texture
================
*/
// *INDENT-OFF*
static void R_MipNormalMap( byte *in, int width, int height )
{
	int    i, j;
	byte   *out;
	vec4_t n;
	vec_t  length;

	if ( width == 1 && height == 1 )
	{
		return;
	}

	out = in;
	width <<= 2;
	height >>= 1;

	for ( i = 0; i < height; i++, in += width )
	{
		for ( j = 0; j < width; j += 8, out += 4, in += 8 )
		{
			// these calculations were centred on 127.5: (p / 255.0 - 0.5) * 2.0
			n[ 0 ] = Tex_ByteToFloat( in[ 0 ] ) +
			         Tex_ByteToFloat( in[ 4 ] ) +
			         Tex_ByteToFloat( in[ width + 0 ] ) +
			         Tex_ByteToFloat( in[ width + 4 ] );

			n[ 1 ] = Tex_ByteToFloat( in[ 1 ] ) +
			         Tex_ByteToFloat( in[ 5 ] ) +
			         Tex_ByteToFloat( in[ width + 1 ] ) +
			         Tex_ByteToFloat( in[ width + 5 ] );

			n[ 2 ] = Tex_ByteToFloat( in[ 2 ] ) +
			         Tex_ByteToFloat( in[ 6 ] ) +
			         Tex_ByteToFloat( in[ width + 2 ] ) +
			         Tex_ByteToFloat( in[ width + 6 ] );

			n[ 3 ] = ( in[ 3 ] + in[ 7 ] + in[ width + 3 ] +  in[ width + 7 ] ) / 255.0f;

			length = VectorLength( n );

			if ( length )
			{
				n[ 0 ] /= length;
				n[ 1 ] /= length;
				n[ 2 ] /= length;
			}
			else
			{
				VectorSet( n, 0.0, 0.0, 1.0 );
			}

			out[ 0 ] = Tex_FloatToByte( n[ 0 ] );
			out[ 1 ] = Tex_FloatToByte( n[ 1 ] );
			out[ 2 ] = Tex_FloatToByte( n[ 2 ] );
			out[ 3 ] = ( byte )( n[ 3 ] * 255.0 / 4.0 );
		}
	}
}

/*
==================
R_BlendOverTexture

Apply a color blend over a set of pixels
==================
*/
static void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[ 4 ] )
{
	int i;
	int inverseAlpha;
	int premult[ 3 ];

	inverseAlpha = 255 - blend[ 3 ];
	premult[ 0 ] = blend[ 0 ] * blend[ 3 ];
	premult[ 1 ] = blend[ 1 ] * blend[ 3 ];
	premult[ 2 ] = blend[ 2 ] * blend[ 3 ];

	for ( i = 0; i < pixelCount; i++, data += 4 )
	{
		data[ 0 ] = ( data[ 0 ] * inverseAlpha + premult[ 0 ] ) >> 9;
		data[ 1 ] = ( data[ 1 ] * inverseAlpha + premult[ 1 ] ) >> 9;
		data[ 2 ] = ( data[ 2 ] * inverseAlpha + premult[ 2 ] ) >> 9;
	}
}

static const byte mipBlendColors[ 16 ][ 4 ] =
{
	{ 0,   0,   0,   0   }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
};

/*
==================
R_UnpackDXT5A
==================
*/
static void
R_UnpackDXT5A( const byte *in, byte *out )
{
	unsigned int bits0, bits1, val;
	int i;

	bits0 = in[2] + (in[3] << 8) + (in[4] << 16);
	bits1 = in[5] + (in[6] << 8) + (in[7] << 16);

	if( in[0] > in[1] ) {
		for( i = 0; i < 8; i++ ) {
			val = bits0 & 7; bits0 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (6 * in[0] + 1 * in[1]) / 7; break;
			case 3: val = (5 * in[0] + 2 * in[1]) / 7; break;
			case 4: val = (4 * in[0] + 3 * in[1]) / 7; break;
			case 5: val = (3 * in[0] + 4 * in[1]) / 7; break;
			case 6: val = (2 * in[0] + 5 * in[1]) / 7; break;
			case 7: val = (1 * in[0] + 6 * in[1]) / 7; break;
			}
			out[ i ] = val;

			val = bits1 & 7; bits1 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (6 * in[0] + 1 * in[1]) / 7; break;
			case 3: val = (5 * in[0] + 2 * in[1]) / 7; break;
			case 4: val = (4 * in[0] + 3 * in[1]) / 7; break;
			case 5: val = (3 * in[0] + 4 * in[1]) / 7; break;
			case 6: val = (2 * in[0] + 5 * in[1]) / 7; break;
			case 7: val = (1 * in[0] + 6 * in[1]) / 7; break;
			}
			out[ i + 8 ] = val;
		}
	} else {
		for( i = 0; i < 8; i++ ) {
			val = bits0 & 7; bits0 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (4 * in[0] + 1 * in[1]) / 5; break;
			case 3: val = (3 * in[0] + 2 * in[1]) / 5; break;
			case 4: val = (2 * in[0] + 3 * in[1]) / 5; break;
			case 5: val = (1 * in[0] + 4 * in[1]) / 5; break;
			case 6: val = 0;                           break;
			case 7: val = 0xff;                        break;
			}
			out[ i ] = val;

			val = bits1 & 7; bits1 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (4 * in[0] + 1 * in[1]) / 5; break;
			case 3: val = (3 * in[0] + 2 * in[1]) / 5; break;
			case 4: val = (2 * in[0] + 3 * in[1]) / 5; break;
			case 5: val = (1 * in[0] + 4 * in[1]) / 5; break;
			case 6: val = 0;                           break;
			case 7: val = 0xff;                        break;
			}
			out[ i + 8 ] = val;
		}
	}
}

static void
R_PackDXT1_Green( const byte *in, byte *out )
{
	int i;
	byte min, max;
	byte lim1, lim2, lim3;
	unsigned int bits0, bits1;

	min = max = in[ 0 ];
	for( i = 1; i < 16; i++ ) {
		if( in[ i ] < min )
			min = in[ i ];
		if( in[ i ] > max )
			max = in[ i ];
	}

	// truncate min and max to 6 bits
	i = (max - min) >> 3;
	min = (min + i) & 0xfc;
	max = (max - i) & 0xfc;
	if( min == max ) {
		if( max == 0xfc ) {
			min -= 4;
		} else {
			max += 4;
		}
	}
	min |= min >> 6;
	max |= max >> 6;

	// find best match for every pixel
	lim1 = (max + 5 * min) / 6;
	lim2 = (max + min) / 2;
	lim3 = (5 * max + min) / 6;
	bits0 = bits1 = 0;
	for( i = 7; i >= 0; i-- ) {
		bits0 <<= 2;
		bits1 <<= 2;
		if( in[ i ] > lim2 ) {
			if( in[ i ] > lim3 ) {
				bits0 |= 0;
			} else {
				bits0 |= 2;
			}
		} else {
			if( in[ i ] > lim1 ) {
				bits0 |= 3;
			} else {
				bits0 |= 1;
			}
		}
		if( in[ i + 8 ] > lim2 ) {
			if( in[ i + 8 ] > lim3 ) {
				bits1 |= 0;
			} else {
				bits1 |= 2;
			}
		} else {
			if( in[ i + 8 ] > lim1 ) {
				bits1 |= 3;
			} else {
				bits1 |= 1;
			}
		}
	}

	// write back DXT1 format data into out[]
	out[0] = (max & 0x1c) << 3;
	out[1] = 0xf8 | (max >> 5);
	out[2] = (min & 0x1c) << 3;
	out[3] = 0xf8 | (min >> 5);
	out[4] = bits0 & 0xff;
	out[5] = (bits0 >> 8) & 0xff;
	out[6] = bits1 & 0xff;
	out[7] = (bits1 >> 8) & 0xff;
}

/*
==================
R_ConvertBC5Image

If the OpenGL doesn't support BC5 (= OpenGL rgtc) textures, convert
them to BC3 (= dxt5). Sampling the original texture yields RGBA = XY01,
the converted texture yields RGBA = 1Y0X, so the shader can reconstruct
XY as vec2(tex.x * tex.a, tex.y).
==================
*/
static void
R_ConvertBC5Image(const byte **in, byte **out, int numMips, int numLayers,
		  int width, int height, qboolean is3D )
{
	int blocks = 0;
	int mipWidth, mipHeight, mipLayers, mipSize;
	byte data[16], *to;
	const byte *from;
	int i, j, k;

	// Allocate buffer for converted images
	mipWidth = width;
	mipHeight = height;
	mipLayers = numLayers;
	for ( i = 0; i < numMips; i++ ) {
		mipSize = ((mipWidth + 3) >> 2) * ((mipHeight + 3) >> 2);

		blocks += mipSize * mipLayers;

		if( mipWidth > 1 )
			mipWidth >>= 1;
		if( mipHeight > 1 )
			mipHeight >>= 1;
		if( is3D && mipLayers > 1 )
			mipLayers >>= 1;
	}

	*out = to = (byte *)ri.Hunk_AllocateTempMemory( blocks * 16 );

	// Convert each mipmap
	mipWidth = width;
	mipHeight = height;
	mipLayers = numLayers;
	for ( i = 0; i < numMips; i++ ) {
		mipSize = ((mipWidth + 3) >> 2) * ((mipHeight + 3) >> 2);

		for( j = 0; j < mipLayers; j++ ) {
			from = in[ i * numLayers + j ];
			in [ i * numLayers + j ] = to;

			for( k = 0; k < mipSize; k++ ) {
				// red channel is unchanged
				Com_Memcpy( to, from, 8 );

				// green channel is converted to DXT1
				R_UnpackDXT5A( from + 8, data );
				R_PackDXT1_Green( data, to + 8 );

				from += 16; to += 16;
			}
		}

		if( mipWidth > 1 )
			mipWidth >>= 1;
		if( mipHeight > 1 )
			mipHeight >>= 1;
		if( is3D && mipLayers > 1 )
			mipLayers >>= 1;
	}
}

/*
===============
R_UploadImage

dataArray points to an array of numLayers * numMips pointers, first all layers
of mip level 0, then all layers of mip level 1, etc.

Note that for 3d textures the higher mip levels have fewer layers, e.g. mip
level 1 has only numLayers/2 layers. There are still numLayers pointers in
the dataArray for every mip level, the unneeded elements at the end aren't used.
===============
*/
void R_UploadImage( const byte **dataArray, int numLayers, int numMips,
		    image_t *image )
{
	const byte *data;
	byte       *scaledBuffer = NULL;
	int        scaledWidth, scaledHeight;
	int        mipWidth, mipHeight, mipLayers, mipSize, blockSize;
	int        i, j, c;
	const byte *scan;
	GLenum     target;
	GLenum     format = GL_RGBA;
	GLenum     internalFormat = GL_RGB;

	static const vec4_t oneClampBorder = { 1, 1, 1, 1 };
	static const vec4_t zeroClampBorder = { 0, 0, 0, 1 };
	static const vec4_t alphaZeroClampBorder = { 0, 0, 0, 0 };

	if ( numMips <= 0 )
		numMips = 1;

	GL_Bind( image );

	if ( glConfig2.textureNPOTAvailable )
	{
		scaledWidth = image->width;
		scaledHeight = image->height;
	}
	else
	{
		// convert to exact power of 2 sizes
		for ( scaledWidth = 1; scaledWidth < image->width; scaledWidth <<= 1 )
		{
			;
		}

		for ( scaledHeight = 1; scaledHeight < image->height; scaledHeight <<= 1 )
		{
			;
		}
	}

	if ( r_roundImagesDown->integer && scaledWidth > image->width )
	{
		scaledWidth >>= 1;
	}

	if ( r_roundImagesDown->integer && scaledHeight > image->height )
	{
		scaledHeight >>= 1;
	}

	// perform optional picmip operation
	if ( !( image->bits & IF_NOPICMIP ) )
	{
		int picmip = r_picmip->integer;
		if( picmip < 0 )
			picmip = 0;

		scaledWidth >>= picmip;
		scaledHeight >>= picmip;

		if( dataArray && numMips > picmip ) {
			dataArray += numLayers * picmip;
			numMips -= picmip;
		}
	}

	// clamp to minimum size
	if ( scaledWidth < 1 )
	{
		scaledWidth = 1;
	}

	if ( scaledHeight < 1 )
	{
		scaledHeight = 1;
	}

	// clamp to the current upper OpenGL limit
	// scale both axis down equally so we don't have to
	// deal with a half mip resampling
	if ( image->type == GL_TEXTURE_CUBE_MAP )
	{
		while ( scaledWidth > glConfig2.maxCubeMapTextureSize || scaledHeight > glConfig2.maxCubeMapTextureSize )
		{
			scaledWidth >>= 1;
			scaledHeight >>= 1;
		}
	}
	else
	{
		while ( scaledWidth > glConfig.maxTextureSize || scaledHeight > glConfig.maxTextureSize )
		{
			scaledWidth >>= 1;
			scaledHeight >>= 1;
		}
	}

	// set target
	switch ( image->type )
	{
		case GL_TEXTURE_3D:
			target = GL_TEXTURE_3D;
			break;

		case GL_TEXTURE_CUBE_MAP:
			target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
			break;

		default:
			target = GL_TEXTURE_2D;
			break;
	}

	if ( image->bits & ( IF_DEPTH16 | IF_DEPTH24 | IF_DEPTH32 ) )
	{
		format = GL_DEPTH_COMPONENT;

		if ( image->bits & IF_DEPTH16 )
		{
			internalFormat = GL_DEPTH_COMPONENT16;
		}
		else if ( image->bits & IF_DEPTH24 )
		{
			internalFormat = GL_DEPTH_COMPONENT24;
		}
		else if ( image->bits & IF_DEPTH32 )
		{
			internalFormat = GL_DEPTH_COMPONENT32;
		}
	}
	else if ( image->bits & ( IF_PACKED_DEPTH24_STENCIL8 ) )
	{
		format = GL_DEPTH_STENCIL_EXT;
		internalFormat = GL_DEPTH24_STENCIL8_EXT;
	}
	else if ( image->bits & ( IF_RGBA16F | IF_RGBA32F | IF_RGBA16 | IF_TWOCOMP16F | IF_TWOCOMP32F | IF_ONECOMP16F | IF_ONECOMP32F ) )
	{
		if( !glConfig2.textureFloatAvailable ) {
			ri.Printf( PRINT_WARNING, "WARNING: floating point image '%s' cannot be loaded\n", image->name );
			internalFormat = GL_RGBA8;
		}
		else if ( image->bits & IF_RGBA16F )
		{
			internalFormat = GL_RGBA16F;
		}
		else if ( image->bits & IF_RGBA32F )
		{
			internalFormat = GL_RGBA32F;
		}
		else if ( image->bits & IF_TWOCOMP16F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_RG16F : GL_LUMINANCE_ALPHA16F_ARB;
		}
		else if ( image->bits & IF_TWOCOMP32F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_RG32F : GL_LUMINANCE_ALPHA32F_ARB;
		}
		else if ( image->bits & IF_RGBA16 )
		{
			internalFormat = GL_RGBA16;
		}
		else if ( image->bits & IF_ONECOMP16F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_R16F : GL_ALPHA16F_ARB;
		}
		else if ( image->bits & IF_ONECOMP32F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_R32F : GL_ALPHA32F_ARB;
		}
	}
	else if ( IsImageCompressed( image->bits ) )
	{
		if( !GLEW_EXT_texture_compression_dxt1 &&
		    !GLEW_EXT_texture_compression_s3tc ) {
			ri.Printf( PRINT_WARNING, "WARNING: compressed image '%s' cannot be loaded\n", image->name );
			internalFormat = GL_RGBA8;
		}
		else if( image->bits & IF_BC1 ) {
			format = GL_NONE;
			internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
			blockSize = 8;
		}
		else if ( image->bits & IF_BC3 ) {
			format = GL_NONE;
			internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
			blockSize = 16;
		}
		else if ( image->bits & IF_BC4 ) {
			if( !GLEW_ARB_texture_compression_rgtc ) {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
				blockSize = 8;

				if( dataArray ) {
					// convert to BC1/dxt1
				}
			}
			else {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RED_RGTC1;
				blockSize = 8;
			}
		}
		else if ( image->bits & IF_BC5 ) {
			if( !GLEW_ARB_texture_compression_rgtc ) {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
				blockSize = 16;

				R_ConvertBC5Image( dataArray, &scaledBuffer,
						   numMips, numLayers,
						   scaledWidth, scaledHeight,
						   image->type == GL_TEXTURE_3D );
			}
			else {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RG_RGTC2;
				blockSize = 16;
			}
		}
	}
	else if ( image->bits & IF_RGBE )
	{
		internalFormat = GL_RGBA8;
	}
	else if ( !dataArray ) {
		internalFormat = GL_RGBA8;
	}
	else
	{
		int samples;

		// scan the texture for each channel's max values
		// and verify if the alpha channel is being used or not
		c = image->width * image->height;
		scan = dataArray[0];

		samples = 3;

		// Tr3B: normalmaps have the displacement maps in the alpha channel
		// samples 3 would cause an opaque alpha channel and odd displacements!
		if ( image->bits & IF_NORMALMAP )
		{
			if ( image->bits & ( IF_DISPLACEMAP | IF_ALPHATEST ) )
			{
				samples = 4;
			}
			else
			{
				samples = 3;
			}
		}
		else if ( image->bits & IF_LIGHTMAP )
		{
			samples = 3;
		}
		else
		{
			for ( i = 0; i < c; i++ )
			{
				if ( scan[ i * 4 + 3 ] != 255 )
				{
					samples = 4;
					break;
				}
			}
		}

		// select proper internal format
		if ( samples == 3 )
		{
			if ( glConfig.textureCompression == TC_S3TC && !( image->bits & IF_NOCOMPRESSION ) )
			{
				internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
			}
			else
			{
				internalFormat = GL_RGB8;
			}
		}
		else if ( samples == 4 )
		{
			if ( image->bits & IF_ALPHA )
			{
				internalFormat = GL_ALPHA8;
			}
			else
			{
				if ( glConfig.textureCompression == TC_S3TC && !( image->bits & IF_NOCOMPRESSION ) )
				{
					if ( image->bits & IF_DISPLACEMAP )
					{
						internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
					}
					else if ( image->bits & IF_ALPHATEST )
					{
						internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
					}
					else
					{
						internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
					}
				}
				else
				{
					internalFormat = GL_RGBA8;
				}
			}
		}
	}

	// 3D textures are uploaded in slices via glTexSubImage3D,
	// so the storage has to be allocated before the loop
	if( image->type == GL_TEXTURE_3D ) {
		mipWidth = scaledWidth;
		mipHeight = scaledHeight;
		mipLayers = numLayers;

		for( i = 0; i < numMips; i++ ) {
			glTexImage3D( GL_TEXTURE_3D, i, internalFormat,
				      scaledWidth, scaledHeight, mipLayers,
				      0, format, GL_UNSIGNED_BYTE, NULL );

			if( mipWidth  > 1 ) mipWidth  >>= 1;
			if( mipHeight > 1 ) mipHeight >>= 1;
			if( mipLayers > 1 ) mipLayers >>= 1;
		}
	}

	if( format != GL_NONE ) {
		if( dataArray )
			scaledBuffer = (byte*) ri.Hunk_AllocateTempMemory( sizeof( byte ) * scaledWidth * scaledHeight * 4 );

		for ( i = 0; i < numLayers; i++ )
		{
			if( dataArray )
				data = dataArray[ i ];
			else
				data = NULL;

			if( scaledBuffer )
			{
				// copy or resample data as appropriate for first MIP level
				if ( ( scaledWidth == image->width ) && ( scaledHeight == image->height ) )
				{
					Com_Memcpy( scaledBuffer, data, scaledWidth * scaledHeight * 4 );
				}
				else
				{
					ResampleTexture( ( unsigned * ) data, image->width, image->height, ( unsigned * ) scaledBuffer, scaledWidth, scaledHeight,
							 ( image->bits & IF_NORMALMAP ) );
				}

				if( image->bits & IF_NORMALMAP ) {
					c = image->width * image->height;
					for ( i = 0; i < c; i++ )
					{
						vec3_t n;

						n[ 0 ] = Tex_ByteToFloat( scaledBuffer[ i * 4 + 0 ] );
						n[ 1 ] = Tex_ByteToFloat( scaledBuffer[ i * 4 + 1 ] );
						n[ 2 ] = Tex_ByteToFloat( scaledBuffer[ i * 4 + 2 ] );

						VectorNormalize( n );

						scaledBuffer[ i * 4 + 0 ] = Tex_FloatToByte( n[ 0 ] );
						scaledBuffer[ i * 4 + 1 ] = Tex_FloatToByte( n[ 1 ] );
						scaledBuffer[ i * 4 + 2 ] = Tex_FloatToByte( n[ 2 ] );
					}
				}
			}

			image->uploadWidth = scaledWidth;
			image->uploadHeight = scaledHeight;
			image->internalFormat = internalFormat;

			switch ( image->type )
			{
			case GL_TEXTURE_3D:
				glTexSubImage3D( GL_TEXTURE_3D, 0, 0, 0, i,
						 scaledWidth, scaledHeight, 1,
						 format, GL_UNSIGNED_BYTE,
						 scaledBuffer );
				break;
			case GL_TEXTURE_CUBE_MAP:
				glTexImage2D( target + i, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE,
				              scaledBuffer );
				break;

			default:
				if ( image->bits & IF_PACKED_DEPTH24_STENCIL8 )
				{
					glTexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_INT_24_8_EXT, NULL );
				}
				else
				{
					glTexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer );
				}

				break;
			}

			if ( image->filterType == FT_DEFAULT )
			{
				if ( glConfig.driverType == GLDRV_OPENGL3 || glConfig2.framebufferObjectAvailable )
				{
					glGenerateMipmapEXT( image->type );
					glTexParameteri( image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );  // default to trilinear
				}
				else if ( glConfig2.generateMipmapAvailable )
				{
					glTexParameteri( image->type, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
					glTexParameteri( image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );  // default to trilinear
				}
			}<commit_msg>Delay mipmap computation of cubemaps until the texture is complete.<commit_after>/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2006-2011 <NAME> <<EMAIL>>

This file is part of Daemon source code.

Daemon source code 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.

Daemon source code 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 Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_image.c
#include "tr_local.h"
#include "../../common/Maths.h"

int                  gl_filter_min = GL_LINEAR_MIPMAP_NEAREST;
int                  gl_filter_max = GL_LINEAR;

image_t              *r_imageHashTable[ IMAGE_FILE_HASH_SIZE ];

#define Tex_ByteToFloat(v) ( ( (int)(v) - 128 ) / 127.0f )
#define Tex_FloatToByte(v) ( 128 + (int) ( (v) * 127.0f + 0.5 ) )

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

static const textureMode_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  }
};

/*
================
return a hash value for the filename
================
*/
long GenerateImageHashValue( const char *fname )
{
	int  i;
	long hash;
	char letter;

//  ri.Printf(PRINT_ALL, "tr_image::GenerateImageHashValue: '%s'\n", fname);

	hash = 0;
	i = 0;

	while ( fname[ i ] != '\0' )
	{
		letter = tolower( fname[ i ] );

		if ( letter == '\\' )
		{
			letter = '/'; // damn path names
		}

		hash += ( long )( letter ) * ( i + 119 );
		i++;
	}

	hash &= ( IMAGE_FILE_HASH_SIZE - 1 );
	return hash;
}

/*
===============
GL_TextureMode
===============
*/
void GL_TextureMode( const char *string )
{
	int     i;
	image_t *image;

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

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

	gl_filter_min = modes[ i ].minimize;
	gl_filter_max = modes[ i ].maximize;

	// bound texture anisotropy
	if ( glConfig2.textureAnisotropyAvailable )
	{
		if ( r_ext_texture_filter_anisotropic->value > glConfig2.maxTextureAnisotropy )
		{
			ri.Cvar_Set( "r_ext_texture_filter_anisotropic", va( "%f", glConfig2.maxTextureAnisotropy ) );
		}
		else if ( r_ext_texture_filter_anisotropic->value < 1.0 )
		{
			ri.Cvar_Set( "r_ext_texture_filter_anisotropic", "1.0" );
		}
	}

	// change all the existing mipmap texture objects
	for ( i = 0; i < tr.images.currentElements; i++ )
	{
		image = (image_t*) Com_GrowListElement( &tr.images, i );

		if ( image->filterType == FT_DEFAULT )
		{
			GL_Bind( image );

			// set texture filter
			glTexParameterf( image->type, GL_TEXTURE_MIN_FILTER, gl_filter_min );
			glTexParameterf( image->type, GL_TEXTURE_MAG_FILTER, gl_filter_max );

			// set texture anisotropy
			if ( glConfig2.textureAnisotropyAvailable )
			{
				glTexParameterf( image->type, GL_TEXTURE_MAX_ANISOTROPY_EXT, r_ext_texture_filter_anisotropic->value );
			}
		}
	}
}

/*
===============
R_ImageList_f
===============
*/
void R_ImageList_f( void )
{
	int        i;
	image_t    *image;
	int        texels;
	int        dataSize;
	int        imageDataSize;
	const char *yesno[] =
	{
		"no ", "yes"
	};
	const char *filter = ri.Cmd_Argc() > 1 ? ri.Cmd_Argv(1) : NULL;

	ri.Printf( PRINT_ALL, "\n      -w-- -h-- -mm- -type-   -if-- wrap --name-------\n" );

	texels = 0;
	dataSize = 0;

	for ( i = 0; i < tr.images.currentElements; i++ )
	{
		image = (image_t*) Com_GrowListElement( &tr.images, i );
		char buffer[ MAX_TOKEN_CHARS ];
		std::string out;

		if ( filter && !Com_Filter( filter, image->name, qtrue ) )
		{
			continue;
		}

		Com_sprintf( buffer, sizeof( buffer ), "%4i: %4i %4i  %s   ",
		           i, image->uploadWidth, image->uploadHeight, yesno[ image->filterType == FT_DEFAULT ] );
		out += buffer;
		switch ( image->type )
		{
			case GL_TEXTURE_2D:
				texels += image->uploadWidth * image->uploadHeight;
				imageDataSize = image->uploadWidth * image->uploadHeight;

				Com_sprintf( buffer, sizeof( buffer ),  "2D   " );
				out += buffer;
				break;

			case GL_TEXTURE_CUBE_MAP:
				texels += image->uploadWidth * image->uploadHeight * 6;
				imageDataSize = image->uploadWidth * image->uploadHeight * 6;

				Com_sprintf( buffer, sizeof( buffer ),  "CUBE " );
				out += buffer;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "???? " );
				out += buffer;
				imageDataSize = image->uploadWidth * image->uploadHeight;
				break;
		}

		switch ( image->internalFormat )
		{
			case GL_RGB8:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB8     " );
				out += buffer;
				imageDataSize *= 3;
				break;

			case GL_RGBA8:
				Com_sprintf( buffer, sizeof( buffer ),  "RGBA8    " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_RGB16:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB      " );
				out += buffer;
				imageDataSize *= 6;
				break;

			case GL_RGB16F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB16F   " );
				out += buffer;
				imageDataSize *= 6;
				break;

			case GL_RGB32F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGB32F   " );
				out += buffer;
				imageDataSize *= 12;
				break;

			case GL_RGBA16F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGBA16F  " );
				out += buffer;
				imageDataSize *= 8;
				break;

			case GL_RGBA32F:
				Com_sprintf( buffer, sizeof( buffer ),  "RGBA32F  " );
				out += buffer;
				imageDataSize *= 16;
				break;

			case GL_ALPHA16F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "A16F     " );
				out += buffer;
				imageDataSize *= 2;
				break;

			case GL_ALPHA32F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "A32F     " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_R16F:
				Com_sprintf( buffer, sizeof( buffer ),  "R16F     " );
				out += buffer;
				imageDataSize *= 2;
				break;

			case GL_R32F:
				Com_sprintf( buffer, sizeof( buffer ),  "R32F     " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_LUMINANCE_ALPHA16F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "LA16F    " );
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_LUMINANCE_ALPHA32F_ARB:
				Com_sprintf( buffer, sizeof( buffer ),  "LA32F    " );
				out += buffer;
				imageDataSize *= 8;
				break;

			case GL_RG16F:
				Com_sprintf( buffer, sizeof( buffer ),  "RG16F    " );
				out += buffer;
				out += buffer;
				imageDataSize *= 4;
				break;

			case GL_RG32F:
				Com_sprintf( buffer, sizeof( buffer ),  "RG32F    " );
				out += buffer;
				imageDataSize *= 8;
				break;

			case GL_COMPRESSED_RGBA:
				Com_sprintf( buffer, sizeof( buffer ),  "      " );
				out += buffer;
				imageDataSize *= 4; // FIXME
				break;

			case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT1     " );
				out += buffer;
				imageDataSize *= 4 / 8;
				break;

			case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT1a    " );
				out += buffer;
				imageDataSize *= 4 / 8;
				break;

			case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT3     " );
				out += buffer;
				imageDataSize *= 4 / 4;
				break;

			case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
				Com_sprintf( buffer, sizeof( buffer ),  "DXT5     " );
				out += buffer;
				imageDataSize *= 4 / 4;
				break;

			case GL_DEPTH_COMPONENT16:
				Com_sprintf( buffer, sizeof( buffer ),  "D16      " );
				out += buffer;
				imageDataSize *= 2;
				break;

			case GL_DEPTH_COMPONENT24:
				Com_sprintf( buffer, sizeof( buffer ),  "D24      " );
				out += buffer;
				imageDataSize *= 3;
				break;

			case GL_DEPTH_COMPONENT32:
				Com_sprintf( buffer, sizeof( buffer ),  "D32      " );
				out += buffer;
				imageDataSize *= 4;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "????     " );
				out += buffer;
				imageDataSize *= 4;
				break;
		}

		switch ( image->wrapType.s )
		{
			case WT_REPEAT:
				Com_sprintf( buffer, sizeof( buffer ),  "s.rept  " );
				out += buffer;
				break;

			case WT_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.clmp  " );
				out += buffer;
				break;

			case WT_EDGE_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.eclmp " );
				out += buffer;
				break;

			case WT_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.zclmp " );
				out += buffer;
				break;

			case WT_ALPHA_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "s.azclmp" );
				out += buffer;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "s.%4i  ", image->wrapType.s );
				out += buffer;
				break;
		}

		switch ( image->wrapType.t )
		{
			case WT_REPEAT:
				Com_sprintf( buffer, sizeof( buffer ),  "t.rept  " );
				out += buffer;
				break;

			case WT_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.clmp  " );
				out += buffer;
				break;

			case WT_EDGE_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.eclmp " );
				out += buffer;
				break;

			case WT_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.zclmp " );
				out += buffer;
				break;

			case WT_ALPHA_ZERO_CLAMP:
				Com_sprintf( buffer, sizeof( buffer ),  "t.azclmp" );
				out += buffer;
				break;

			default:
				Com_sprintf( buffer, sizeof( buffer ),  "t.%4i  ", image->wrapType.t );
				out += buffer;
				break;
		}

		dataSize += imageDataSize;

		ri.Printf( PRINT_ALL, "%s %s\n", out.c_str(), image->name );
	}

	ri.Printf( PRINT_ALL, " ---------\n" );
	ri.Printf( PRINT_ALL, " %i total texels (not including mipmaps)\n", texels );
	ri.Printf( PRINT_ALL, " %d.%02d MB total image memory\n", dataSize / ( 1024 * 1024 ),
	           ( dataSize % ( 1024 * 1024 ) ) * 100 / ( 1024 * 1024 ) );
	ri.Printf( PRINT_ALL, " %i total images\n\n", tr.images.currentElements );
}

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

/*
================
ResampleTexture

Used to resample images in a more general than quartering fashion.

This will only be filtered properly if the resampled size
is greater than half the original size.

If a larger shrinking is needed, use the mipmap function
before or after.
================
*/
static void ResampleTexture( unsigned *in, int inwidth, int inheight, unsigned *out, int outwidth, int outheight,
                             qboolean normalMap )
{
	int      x, y;
	unsigned *inrow, *inrow2;
	unsigned frac, fracstep;
	unsigned p1[ 2048 ], p2[ 2048 ];
	byte     *pix1, *pix2, *pix3, *pix4;
	vec3_t   n, n2, n3, n4;

	fracstep = inwidth * 0x10000 / outwidth;

	frac = fracstep >> 2;

	for ( x = 0; x < outwidth; x++ )
	{
		p1[ x ] = 4 * ( frac >> 16 );
		frac += fracstep;
	}

	frac = 3 * ( fracstep >> 2 );

	for ( x = 0; x < outwidth; x++ )
	{
		p2[ x ] = 4 * ( frac >> 16 );
		frac += fracstep;
	}

	if ( normalMap )
	{
		for ( y = 0; y < outheight; y++ )
		{
			inrow = in + inwidth * ( int )( ( y + 0.25 ) * inheight / outheight );
			inrow2 = in + inwidth * ( int )( ( y + 0.75 ) * inheight / outheight );

			for ( x = 0; x < outwidth; x++ )
			{
				pix1 = ( byte * ) inrow + p1[ x ];
				pix2 = ( byte * ) inrow + p2[ x ];
				pix3 = ( byte * ) inrow2 + p1[ x ];
				pix4 = ( byte * ) inrow2 + p2[ x ];

				n[ 0 ] = Tex_ByteToFloat( pix1[ 0 ] );
				n[ 1 ] = Tex_ByteToFloat( pix1[ 1 ] );
				n[ 2 ] = Tex_ByteToFloat( pix1[ 2 ] );

				n2[ 0 ] = Tex_ByteToFloat( pix2[ 0 ] );
				n2[ 1 ] = Tex_ByteToFloat( pix2[ 1 ] );
				n2[ 2 ] = Tex_ByteToFloat( pix2[ 2 ] );

				n3[ 0 ] = Tex_ByteToFloat( pix3[ 0 ] );
				n3[ 1 ] = Tex_ByteToFloat( pix3[ 1 ] );
				n3[ 2 ] = Tex_ByteToFloat( pix3[ 2 ] );

				n4[ 0 ] = Tex_ByteToFloat( pix4[ 0 ] );
				n4[ 1 ] = Tex_ByteToFloat( pix4[ 1 ] );
				n4[ 2 ] = Tex_ByteToFloat( pix4[ 2 ] );

				VectorAdd( n, n2, n );
				VectorAdd( n, n3, n );
				VectorAdd( n, n4, n );

				if ( !VectorNormalize( n ) )
				{
					VectorSet( n, 0, 0, 1 );
				}

				( ( byte * )( out ) ) [ 0 ] = Tex_FloatToByte( n[ 0 ] );
				( ( byte * )( out ) ) [ 1 ] = Tex_FloatToByte( n[ 1 ] );
				( ( byte * )( out ) ) [ 2 ] = Tex_FloatToByte( n[ 2 ] );
				( ( byte * )( out ) ) [ 3 ] = 255;

				++out;
			}
		}
	}
	else
	{
		for ( y = 0; y < outheight; y++ )
		{
			inrow = in + inwidth * ( int )( ( y + 0.25 ) * inheight / outheight );
			inrow2 = in + inwidth * ( int )( ( y + 0.75 ) * inheight / outheight );

			for ( x = 0; x < outwidth; x++ )
			{
				pix1 = ( byte * ) inrow + p1[ x ];
				pix2 = ( byte * ) inrow + p2[ x ];
				pix3 = ( byte * ) inrow2 + p1[ x ];
				pix4 = ( byte * ) inrow2 + p2[ x ];

				( ( byte * )( out ) ) [ 0 ] = ( pix1[ 0 ] + pix2[ 0 ] + pix3[ 0 ] + pix4[ 0 ] ) >> 2;
				( ( byte * )( out ) ) [ 1 ] = ( pix1[ 1 ] + pix2[ 1 ] + pix3[ 1 ] + pix4[ 1 ] ) >> 2;
				( ( byte * )( out ) ) [ 2 ] = ( pix1[ 2 ] + pix2[ 2 ] + pix3[ 2 ] + pix4[ 2 ] ) >> 2;
				( ( byte * )( out ) ) [ 3 ] = ( pix1[ 3 ] + pix2[ 3 ] + pix3[ 3 ] + pix4[ 3 ] ) >> 2;

				++out;
			}
		}
	}
}

/*
================
R_MipMap2

Operates in place, quartering the size of the texture
Proper linear filter
================
*/
static void R_MipMap2( unsigned *in, int inWidth, int inHeight )
{
	int      i, j, k;
	byte     *outpix;
	int      inWidthMask, inHeightMask;
	int      outWidth, outHeight;
	unsigned *temp;
	byte     *row[ 4 ];

	outWidth = inWidth >> 1;
	outHeight = inHeight >> 1;
	temp = (unsigned int*) ri.Hunk_AllocateTempMemory( outWidth * outHeight * 4 );
	outpix = (byte *) temp;

	inWidthMask = ( inWidth << 2 ) - 1; // applied to row indices
	inHeightMask = inHeight - 1; // applied to in indices

	row[ 1 ] = (byte *) &in[( -1 & inHeightMask ) * inWidth ];
	row[ 2 ] = (byte *) &in[   0                            ];
	row[ 3 ] = (byte *) &in[(  1 & inHeightMask ) * inWidth ];

	for ( i = 0; i < inHeight; i += 2 ) // count source, row pairs
	{
		row[ 0 ] = row[ 1 ];
		row[ 1 ] = row[ 2 ];
		row[ 2 ] = row[ 3 ];
		row[ 3 ] = (byte *) &in[ ( ( i + 2 ) & inHeightMask ) * inWidth ];

		for ( j = 0; j < inWidth * 4; j += 8 ) // count source, bytes comprising texel pairs
		{
			for ( k = j; k < j + 4; k++ )
			{
				const int km1 = ( k - 4 ) & inWidthMask;
				const int kp1 = ( k + 4 ) & inWidthMask;
				const int kp2 = ( k + 8 ) & inWidthMask;

				*outpix++ = ( 1 * row[ 0 ][ km1 ] + 2 * row[ 0 ][ k   ] + 2 * row[ 0 ][ kp1 ] + 1 * row[ 0 ][ kp2 ] +
						2 * row[ 1 ][ km1 ] + 4 * row[ 1 ][ k   ] + 4 * row[ 1 ][ kp1 ] + 2 * row[ 1 ][ kp2 ] +
						2 * row[ 2 ][ km1 ] + 4 * row[ 2 ][ k   ] + 4 * row[ 2 ][ kp1 ] + 2 * row[ 2 ][ kp2 ] +
						1 * row[ 3 ][ km1 ] + 2 * row[ 3 ][ k   ] + 2 * row[ 3 ][ kp1 ] + 1 * row[ 3 ][ kp2 ] ) / 36;
			}
		}
	}

	Com_Memcpy( in, temp, outWidth * outHeight * 4 );
	ri.Hunk_FreeTempMemory( temp );
}

/*
================
R_MipMap

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

	if ( !r_simpleMipMaps->integer )
	{
		R_MipMap2( ( unsigned * ) in, width, height );
		return;
	}

	if ( width == 1 && height == 1 )
	{
		return;
	}

	row = width * 4;
	out = in;
	width >>= 1;
	height >>= 1;

	if ( width == 0 || height == 0 )
	{
		width += height; // get largest

		for ( i = 0; i < width; i++, 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;
		}

		return;
	}

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

/*
================
R_MipNormalMap

Operates in place, quartering the size of the texture
================
*/
// *INDENT-OFF*
static void R_MipNormalMap( byte *in, int width, int height )
{
	int    i, j;
	byte   *out;
	vec4_t n;
	vec_t  length;

	if ( width == 1 && height == 1 )
	{
		return;
	}

	out = in;
	width <<= 2;
	height >>= 1;

	for ( i = 0; i < height; i++, in += width )
	{
		for ( j = 0; j < width; j += 8, out += 4, in += 8 )
		{
			// these calculations were centred on 127.5: (p / 255.0 - 0.5) * 2.0
			n[ 0 ] = Tex_ByteToFloat( in[ 0 ] ) +
			         Tex_ByteToFloat( in[ 4 ] ) +
			         Tex_ByteToFloat( in[ width + 0 ] ) +
			         Tex_ByteToFloat( in[ width + 4 ] );

			n[ 1 ] = Tex_ByteToFloat( in[ 1 ] ) +
			         Tex_ByteToFloat( in[ 5 ] ) +
			         Tex_ByteToFloat( in[ width + 1 ] ) +
			         Tex_ByteToFloat( in[ width + 5 ] );

			n[ 2 ] = Tex_ByteToFloat( in[ 2 ] ) +
			         Tex_ByteToFloat( in[ 6 ] ) +
			         Tex_ByteToFloat( in[ width + 2 ] ) +
			         Tex_ByteToFloat( in[ width + 6 ] );

			n[ 3 ] = ( in[ 3 ] + in[ 7 ] + in[ width + 3 ] +  in[ width + 7 ] ) / 255.0f;

			length = VectorLength( n );

			if ( length )
			{
				n[ 0 ] /= length;
				n[ 1 ] /= length;
				n[ 2 ] /= length;
			}
			else
			{
				VectorSet( n, 0.0, 0.0, 1.0 );
			}

			out[ 0 ] = Tex_FloatToByte( n[ 0 ] );
			out[ 1 ] = Tex_FloatToByte( n[ 1 ] );
			out[ 2 ] = Tex_FloatToByte( n[ 2 ] );
			out[ 3 ] = ( byte )( n[ 3 ] * 255.0 / 4.0 );
		}
	}
}

/*
==================
R_BlendOverTexture

Apply a color blend over a set of pixels
==================
*/
static void R_BlendOverTexture( byte *data, int pixelCount, const byte blend[ 4 ] )
{
	int i;
	int inverseAlpha;
	int premult[ 3 ];

	inverseAlpha = 255 - blend[ 3 ];
	premult[ 0 ] = blend[ 0 ] * blend[ 3 ];
	premult[ 1 ] = blend[ 1 ] * blend[ 3 ];
	premult[ 2 ] = blend[ 2 ] * blend[ 3 ];

	for ( i = 0; i < pixelCount; i++, data += 4 )
	{
		data[ 0 ] = ( data[ 0 ] * inverseAlpha + premult[ 0 ] ) >> 9;
		data[ 1 ] = ( data[ 1 ] * inverseAlpha + premult[ 1 ] ) >> 9;
		data[ 2 ] = ( data[ 2 ] * inverseAlpha + premult[ 2 ] ) >> 9;
	}
}

static const byte mipBlendColors[ 16 ][ 4 ] =
{
	{ 0,   0,   0,   0   }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
	{ 255, 0,   0,   128 }
	,
	{ 0,   255, 0,   128 }
	,
	{ 0,   0,   255, 128 }
	,
};

/*
==================
R_UnpackDXT5A
==================
*/
static void
R_UnpackDXT5A( const byte *in, byte *out )
{
	unsigned int bits0, bits1, val;
	int i;

	bits0 = in[2] + (in[3] << 8) + (in[4] << 16);
	bits1 = in[5] + (in[6] << 8) + (in[7] << 16);

	if( in[0] > in[1] ) {
		for( i = 0; i < 8; i++ ) {
			val = bits0 & 7; bits0 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (6 * in[0] + 1 * in[1]) / 7; break;
			case 3: val = (5 * in[0] + 2 * in[1]) / 7; break;
			case 4: val = (4 * in[0] + 3 * in[1]) / 7; break;
			case 5: val = (3 * in[0] + 4 * in[1]) / 7; break;
			case 6: val = (2 * in[0] + 5 * in[1]) / 7; break;
			case 7: val = (1 * in[0] + 6 * in[1]) / 7; break;
			}
			out[ i ] = val;

			val = bits1 & 7; bits1 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (6 * in[0] + 1 * in[1]) / 7; break;
			case 3: val = (5 * in[0] + 2 * in[1]) / 7; break;
			case 4: val = (4 * in[0] + 3 * in[1]) / 7; break;
			case 5: val = (3 * in[0] + 4 * in[1]) / 7; break;
			case 6: val = (2 * in[0] + 5 * in[1]) / 7; break;
			case 7: val = (1 * in[0] + 6 * in[1]) / 7; break;
			}
			out[ i + 8 ] = val;
		}
	} else {
		for( i = 0; i < 8; i++ ) {
			val = bits0 & 7; bits0 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (4 * in[0] + 1 * in[1]) / 5; break;
			case 3: val = (3 * in[0] + 2 * in[1]) / 5; break;
			case 4: val = (2 * in[0] + 3 * in[1]) / 5; break;
			case 5: val = (1 * in[0] + 4 * in[1]) / 5; break;
			case 6: val = 0;                           break;
			case 7: val = 0xff;                        break;
			}
			out[ i ] = val;

			val = bits1 & 7; bits1 >>= 3;
			switch( val ) {
			case 0: val = in[0]; break;
			case 1: val = in[1]; break;
			case 2: val = (4 * in[0] + 1 * in[1]) / 5; break;
			case 3: val = (3 * in[0] + 2 * in[1]) / 5; break;
			case 4: val = (2 * in[0] + 3 * in[1]) / 5; break;
			case 5: val = (1 * in[0] + 4 * in[1]) / 5; break;
			case 6: val = 0;                           break;
			case 7: val = 0xff;                        break;
			}
			out[ i + 8 ] = val;
		}
	}
}

static void
R_PackDXT1_Green( const byte *in, byte *out )
{
	int i;
	byte min, max;
	byte lim1, lim2, lim3;
	unsigned int bits0, bits1;

	min = max = in[ 0 ];
	for( i = 1; i < 16; i++ ) {
		if( in[ i ] < min )
			min = in[ i ];
		if( in[ i ] > max )
			max = in[ i ];
	}

	// truncate min and max to 6 bits
	i = (max - min) >> 3;
	min = (min + i) & 0xfc;
	max = (max - i) & 0xfc;
	if( min == max ) {
		if( max == 0xfc ) {
			min -= 4;
		} else {
			max += 4;
		}
	}
	min |= min >> 6;
	max |= max >> 6;

	// find best match for every pixel
	lim1 = (max + 5 * min) / 6;
	lim2 = (max + min) / 2;
	lim3 = (5 * max + min) / 6;
	bits0 = bits1 = 0;
	for( i = 7; i >= 0; i-- ) {
		bits0 <<= 2;
		bits1 <<= 2;
		if( in[ i ] > lim2 ) {
			if( in[ i ] > lim3 ) {
				bits0 |= 0;
			} else {
				bits0 |= 2;
			}
		} else {
			if( in[ i ] > lim1 ) {
				bits0 |= 3;
			} else {
				bits0 |= 1;
			}
		}
		if( in[ i + 8 ] > lim2 ) {
			if( in[ i + 8 ] > lim3 ) {
				bits1 |= 0;
			} else {
				bits1 |= 2;
			}
		} else {
			if( in[ i + 8 ] > lim1 ) {
				bits1 |= 3;
			} else {
				bits1 |= 1;
			}
		}
	}

	// write back DXT1 format data into out[]
	out[0] = (max & 0x1c) << 3;
	out[1] = 0xf8 | (max >> 5);
	out[2] = (min & 0x1c) << 3;
	out[3] = 0xf8 | (min >> 5);
	out[4] = bits0 & 0xff;
	out[5] = (bits0 >> 8) & 0xff;
	out[6] = bits1 & 0xff;
	out[7] = (bits1 >> 8) & 0xff;
}

/*
==================
R_ConvertBC5Image

If the OpenGL doesn't support BC5 (= OpenGL rgtc) textures, convert
them to BC3 (= dxt5). Sampling the original texture yields RGBA = XY01,
the converted texture yields RGBA = 1Y0X, so the shader can reconstruct
XY as vec2(tex.x * tex.a, tex.y).
==================
*/
static void
R_ConvertBC5Image(const byte **in, byte **out, int numMips, int numLayers,
		  int width, int height, qboolean is3D )
{
	int blocks = 0;
	int mipWidth, mipHeight, mipLayers, mipSize;
	byte data[16], *to;
	const byte *from;
	int i, j, k;

	// Allocate buffer for converted images
	mipWidth = width;
	mipHeight = height;
	mipLayers = numLayers;
	for ( i = 0; i < numMips; i++ ) {
		mipSize = ((mipWidth + 3) >> 2) * ((mipHeight + 3) >> 2);

		blocks += mipSize * mipLayers;

		if( mipWidth > 1 )
			mipWidth >>= 1;
		if( mipHeight > 1 )
			mipHeight >>= 1;
		if( is3D && mipLayers > 1 )
			mipLayers >>= 1;
	}

	*out = to = (byte *)ri.Hunk_AllocateTempMemory( blocks * 16 );

	// Convert each mipmap
	mipWidth = width;
	mipHeight = height;
	mipLayers = numLayers;
	for ( i = 0; i < numMips; i++ ) {
		mipSize = ((mipWidth + 3) >> 2) * ((mipHeight + 3) >> 2);

		for( j = 0; j < mipLayers; j++ ) {
			from = in[ i * numLayers + j ];
			in [ i * numLayers + j ] = to;

			for( k = 0; k < mipSize; k++ ) {
				// red channel is unchanged
				Com_Memcpy( to, from, 8 );

				// green channel is converted to DXT1
				R_UnpackDXT5A( from + 8, data );
				R_PackDXT1_Green( data, to + 8 );

				from += 16; to += 16;
			}
		}

		if( mipWidth > 1 )
			mipWidth >>= 1;
		if( mipHeight > 1 )
			mipHeight >>= 1;
		if( is3D && mipLayers > 1 )
			mipLayers >>= 1;
	}
}

/*
===============
R_UploadImage

dataArray points to an array of numLayers * numMips pointers, first all layers
of mip level 0, then all layers of mip level 1, etc.

Note that for 3d textures the higher mip levels have fewer layers, e.g. mip
level 1 has only numLayers/2 layers. There are still numLayers pointers in
the dataArray for every mip level, the unneeded elements at the end aren't used.
===============
*/
void R_UploadImage( const byte **dataArray, int numLayers, int numMips,
		    image_t *image )
{
	const byte *data;
	byte       *scaledBuffer = NULL;
	int        scaledWidth, scaledHeight;
	int        mipWidth, mipHeight, mipLayers, mipSize, blockSize;
	int        i, j, c;
	const byte *scan;
	GLenum     target;
	GLenum     format = GL_RGBA;
	GLenum     internalFormat = GL_RGB;

	static const vec4_t oneClampBorder = { 1, 1, 1, 1 };
	static const vec4_t zeroClampBorder = { 0, 0, 0, 1 };
	static const vec4_t alphaZeroClampBorder = { 0, 0, 0, 0 };

	if ( numMips <= 0 )
		numMips = 1;

	GL_Bind( image );

	if ( glConfig2.textureNPOTAvailable )
	{
		scaledWidth = image->width;
		scaledHeight = image->height;
	}
	else
	{
		// convert to exact power of 2 sizes
		for ( scaledWidth = 1; scaledWidth < image->width; scaledWidth <<= 1 )
		{
			;
		}

		for ( scaledHeight = 1; scaledHeight < image->height; scaledHeight <<= 1 )
		{
			;
		}
	}

	if ( r_roundImagesDown->integer && scaledWidth > image->width )
	{
		scaledWidth >>= 1;
	}

	if ( r_roundImagesDown->integer && scaledHeight > image->height )
	{
		scaledHeight >>= 1;
	}

	// perform optional picmip operation
	if ( !( image->bits & IF_NOPICMIP ) )
	{
		int picmip = r_picmip->integer;
		if( picmip < 0 )
			picmip = 0;

		scaledWidth >>= picmip;
		scaledHeight >>= picmip;

		if( dataArray && numMips > picmip ) {
			dataArray += numLayers * picmip;
			numMips -= picmip;
		}
	}

	// clamp to minimum size
	if ( scaledWidth < 1 )
	{
		scaledWidth = 1;
	}

	if ( scaledHeight < 1 )
	{
		scaledHeight = 1;
	}

	// clamp to the current upper OpenGL limit
	// scale both axis down equally so we don't have to
	// deal with a half mip resampling
	if ( image->type == GL_TEXTURE_CUBE_MAP )
	{
		while ( scaledWidth > glConfig2.maxCubeMapTextureSize || scaledHeight > glConfig2.maxCubeMapTextureSize )
		{
			scaledWidth >>= 1;
			scaledHeight >>= 1;
		}
	}
	else
	{
		while ( scaledWidth > glConfig.maxTextureSize || scaledHeight > glConfig.maxTextureSize )
		{
			scaledWidth >>= 1;
			scaledHeight >>= 1;
		}
	}

	// set target
	switch ( image->type )
	{
		case GL_TEXTURE_3D:
			target = GL_TEXTURE_3D;
			break;

		case GL_TEXTURE_CUBE_MAP:
			target = GL_TEXTURE_CUBE_MAP_POSITIVE_X;
			break;

		default:
			target = GL_TEXTURE_2D;
			break;
	}

	if ( image->bits & ( IF_DEPTH16 | IF_DEPTH24 | IF_DEPTH32 ) )
	{
		format = GL_DEPTH_COMPONENT;

		if ( image->bits & IF_DEPTH16 )
		{
			internalFormat = GL_DEPTH_COMPONENT16;
		}
		else if ( image->bits & IF_DEPTH24 )
		{
			internalFormat = GL_DEPTH_COMPONENT24;
		}
		else if ( image->bits & IF_DEPTH32 )
		{
			internalFormat = GL_DEPTH_COMPONENT32;
		}
	}
	else if ( image->bits & ( IF_PACKED_DEPTH24_STENCIL8 ) )
	{
		format = GL_DEPTH_STENCIL_EXT;
		internalFormat = GL_DEPTH24_STENCIL8_EXT;
	}
	else if ( image->bits & ( IF_RGBA16F | IF_RGBA32F | IF_RGBA16 | IF_TWOCOMP16F | IF_TWOCOMP32F | IF_ONECOMP16F | IF_ONECOMP32F ) )
	{
		if( !glConfig2.textureFloatAvailable ) {
			ri.Printf( PRINT_WARNING, "WARNING: floating point image '%s' cannot be loaded\n", image->name );
			internalFormat = GL_RGBA8;
		}
		else if ( image->bits & IF_RGBA16F )
		{
			internalFormat = GL_RGBA16F;
		}
		else if ( image->bits & IF_RGBA32F )
		{
			internalFormat = GL_RGBA32F;
		}
		else if ( image->bits & IF_TWOCOMP16F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_RG16F : GL_LUMINANCE_ALPHA16F_ARB;
		}
		else if ( image->bits & IF_TWOCOMP32F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_RG32F : GL_LUMINANCE_ALPHA32F_ARB;
		}
		else if ( image->bits & IF_RGBA16 )
		{
			internalFormat = GL_RGBA16;
		}
		else if ( image->bits & IF_ONECOMP16F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_R16F : GL_ALPHA16F_ARB;
		}
		else if ( image->bits & IF_ONECOMP32F )
		{
			internalFormat = glConfig2.textureRGAvailable ?
			  GL_R32F : GL_ALPHA32F_ARB;
		}
	}
	else if ( IsImageCompressed( image->bits ) )
	{
		if( !GLEW_EXT_texture_compression_dxt1 &&
		    !GLEW_EXT_texture_compression_s3tc ) {
			ri.Printf( PRINT_WARNING, "WARNING: compressed image '%s' cannot be loaded\n", image->name );
			internalFormat = GL_RGBA8;
		}
		else if( image->bits & IF_BC1 ) {
			format = GL_NONE;
			internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
			blockSize = 8;
		}
		else if ( image->bits & IF_BC3 ) {
			format = GL_NONE;
			internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
			blockSize = 16;
		}
		else if ( image->bits & IF_BC4 ) {
			if( !GLEW_ARB_texture_compression_rgtc ) {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
				blockSize = 8;

				if( dataArray ) {
					// convert to BC1/dxt1
				}
			}
			else {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RED_RGTC1;
				blockSize = 8;
			}
		}
		else if ( image->bits & IF_BC5 ) {
			if( !GLEW_ARB_texture_compression_rgtc ) {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
				blockSize = 16;

				R_ConvertBC5Image( dataArray, &scaledBuffer,
						   numMips, numLayers,
						   scaledWidth, scaledHeight,
						   image->type == GL_TEXTURE_3D );
			}
			else {
				format = GL_NONE;
				internalFormat = GL_COMPRESSED_RG_RGTC2;
				blockSize = 16;
			}
		}
	}
	else if ( image->bits & IF_RGBE )
	{
		internalFormat = GL_RGBA8;
	}
	else if ( !dataArray ) {
		internalFormat = GL_RGBA8;
	}
	else
	{
		int samples;

		// scan the texture for each channel's max values
		// and verify if the alpha channel is being used or not
		c = image->width * image->height;
		scan = dataArray[0];

		samples = 3;

		// Tr3B: normalmaps have the displacement maps in the alpha channel
		// samples 3 would cause an opaque alpha channel and odd displacements!
		if ( image->bits & IF_NORMALMAP )
		{
			if ( image->bits & ( IF_DISPLACEMAP | IF_ALPHATEST ) )
			{
				samples = 4;
			}
			else
			{
				samples = 3;
			}
		}
		else if ( image->bits & IF_LIGHTMAP )
		{
			samples = 3;
		}
		else
		{
			for ( i = 0; i < c; i++ )
			{
				if ( scan[ i * 4 + 3 ] != 255 )
				{
					samples = 4;
					break;
				}
			}
		}

		// select proper internal format
		if ( samples == 3 )
		{
			if ( glConfig.textureCompression == TC_S3TC && !( image->bits & IF_NOCOMPRESSION ) )
			{
				internalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT;
			}
			else
			{
				internalFormat = GL_RGB8;
			}
		}
		else if ( samples == 4 )
		{
			if ( image->bits & IF_ALPHA )
			{
				internalFormat = GL_ALPHA8;
			}
			else
			{
				if ( glConfig.textureCompression == TC_S3TC && !( image->bits & IF_NOCOMPRESSION ) )
				{
					if ( image->bits & IF_DISPLACEMAP )
					{
						internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
					}
					else if ( image->bits & IF_ALPHATEST )
					{
						internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
					}
					else
					{
						internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
					}
				}
				else
				{
					internalFormat = GL_RGBA8;
				}
			}
		}
	}

	// 3D textures are uploaded in slices via glTexSubImage3D,
	// so the storage has to be allocated before the loop
	if( image->type == GL_TEXTURE_3D ) {
		mipWidth = scaledWidth;
		mipHeight = scaledHeight;
		mipLayers = numLayers;

		for( i = 0; i < numMips; i++ ) {
			glTexImage3D( GL_TEXTURE_3D, i, internalFormat,
				      scaledWidth, scaledHeight, mipLayers,
				      0, format, GL_UNSIGNED_BYTE, NULL );

			if( mipWidth  > 1 ) mipWidth  >>= 1;
			if( mipHeight > 1 ) mipHeight >>= 1;
			if( mipLayers > 1 ) mipLayers >>= 1;
		}
	}

	if( format != GL_NONE ) {
		if( dataArray )
			scaledBuffer = (byte*) ri.Hunk_AllocateTempMemory( sizeof( byte ) * scaledWidth * scaledHeight * 4 );

		for ( i = 0; i < numLayers; i++ )
		{
			if( dataArray )
				data = dataArray[ i ];
			else
				data = NULL;

			if( scaledBuffer )
			{
				// copy or resample data as appropriate for first MIP level
				if ( ( scaledWidth == image->width ) && ( scaledHeight == image->height ) )
				{
					Com_Memcpy( scaledBuffer, data, scaledWidth * scaledHeight * 4 );
				}
				else
				{
					ResampleTexture( ( unsigned * ) data, image->width, image->height, ( unsigned * ) scaledBuffer, scaledWidth, scaledHeight,
							 ( image->bits & IF_NORMALMAP ) );
				}

				if( image->bits & IF_NORMALMAP ) {
					c = image->width * image->height;
					for ( i = 0; i < c; i++ )
					{
						vec3_t n;

						n[ 0 ] = Tex_ByteToFloat( scaledBuffer[ i * 4 + 0 ] );
						n[ 1 ] = Tex_ByteToFloat( scaledBuffer[ i * 4 + 1 ] );
						n[ 2 ] = Tex_ByteToFloat( scaledBuffer[ i * 4 + 2 ] );

						VectorNormalize( n );

						scaledBuffer[ i * 4 + 0 ] = Tex_FloatToByte( n[ 0 ] );
						scaledBuffer[ i * 4 + 1 ] = Tex_FloatToByte( n[ 1 ] );
						scaledBuffer[ i * 4 + 2 ] = Tex_FloatToByte( n[ 2 ] );
					}
				}
			}

			image->uploadWidth = scaledWidth;
			image->uploadHeight = scaledHeight;
			image->internalFormat = internalFormat;

			switch ( image->type )
			{
			case GL_TEXTURE_3D:
				glTexSubImage3D( GL_TEXTURE_3D, 0, 0, 0, i,
						 scaledWidth, scaledHeight, 1,
						 format, GL_UNSIGNED_BYTE,
						 scaledBuffer );
				break;
			case GL_TEXTURE_CUBE_MAP:
				glTexImage2D( target + i, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE,
				              scaledBuffer );
				break;

			default:
				if ( image->bits & IF_PACKED_DEPTH24_STENCIL8 )
				{
					glTexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_INT_24_8_EXT, NULL );
				}
				else
				{
					glTexImage2D( target, 0, internalFormat, scaledWidth, scaledHeight, 0, format, GL_UNSIGNED_BYTE, scaledBuffer );
				}

				break;
			}

			if ( image->filterType == FT_DEFAULT )
			{
				if ( glConfig.driverType == GLDRV_OPENGL3 || glConfig2.framebufferObjectAvailable )
				{
					if( image->type != GL_TEXTURE_CUBE_MAP || i == 5 ) {
						glGenerateMipmapEXT( image->type );
						glTexParameteri( image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );  // default to trilinear
					}
				}
				else if ( glConfig2.generateMipmapAvailable )
				{
					glTexParameteri( image->type, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
					glTexParameteri( image->type, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );  // default to trilinear
				}
			}
{'fraction_non_alphanumeric': 0.16242839476340065, 'fraction_numerical': 0.03782462225128285, 'mean_word_length': 2.4262893878147374, 'pattern_counts': {'":': 0, '<': 131, '<?xml version=': 0, '>': 472, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 2, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7128225', 'n_tokens_mistral': 33468, 'n_tokens_neox': 27537, 'n_words': 7336}