Files
Wolf3D-iOS/wolf3d/code/env/texture_manager.c

1466 lines
33 KiB
C

/*
Copyright (C) 2004 Michael Liebscher
Copyright (C) 1997-2001 Id Software, Inc.
Copyright (C) 1995 Spencer Kimball and Peter Mattis.
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.
*/
/*
* texture_manager.c: Texture manager.
*
* Author: Michael Liebscher <johnnycanuck@users.sourceforge.net>
* Date: 2004
*
* Acknowledgement:
* Portion of this code was derived from
* The GIMP (an image manipulation program) and was originally
* written by Spencer Kimball and Peter Mattis.
*
* Portion of this code was derived from Quake II, and was originally
* written by Id Software, Inc.
*
*/
#include "../wolfiphone.h"
PRIVATE texture_t ttextures[ MAX_TEXTURES ];
PRIVATE int numttextures;
PRIVATE texture_t *r_notexture; // use for bad textures
cvar_t *gl_round_down;
int registration_sequence;
W32 texture_registration_sequence;
extern int currentTextures[ 4 ];
extern int currenttmu;
extern int glMaxTexSize;
PRIVATE INLINECALL GLenum WrapToGL( TWrapMode mode )
{
if( mode == Repeat )
{
return GL_REPEAT;
}
else
{
return GL_CLAMP_TO_EDGE;
}
}
PRIVATE INLINECALL GLenum MagFilterToGL( TMagFilter MagFilter )
{
switch( MagFilter )
{
case Nearest:
return GL_NEAREST;
case Linear:
return GL_LINEAR;
default:
break;
}
return GL_LINEAR;
}
PRIVATE INLINECALL GLenum MinFilterToGL( _boolean MipMap, TMinFilter MinFilter )
{
if( MipMap )
{
switch( MinFilter )
{
case NearestMipMapOff:
return GL_NEAREST;
case NearestMipMapNearest:
return GL_NEAREST_MIPMAP_NEAREST;
case NearestMipMapLinear:
return GL_NEAREST_MIPMAP_LINEAR;
case LinearMipMapOff:
return GL_LINEAR;
case LinearMipMapNearest:
return GL_LINEAR_MIPMAP_NEAREST;
case LinearMipMapLinear:
return GL_LINEAR_MIPMAP_LINEAR;
default:
break;
}
}
else
{
switch( MinFilter )
{
case NearestMipMapOff:
case NearestMipMapNearest:
case NearestMipMapLinear:
return GL_NEAREST;
case LinearMipMapOff:
case LinearMipMapNearest:
case LinearMipMapLinear:
return GL_LINEAR;
default:
break;
}
}
return GL_LINEAR;
}
/*
-----------------------------------------------------------------------------
Function: TM_TextureList_f -Console function to list loaded textures.
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void TM_TextureList_f( void )
{
int i;
texture_t *image;
int texels;
const char *palstrings[ 2 ] =
{
"RGB",
"PAL"
};
Com_Printf( "------------------\n" );
texels = 0;
for( i = 0, image = ttextures ; i < numttextures ; ++i, ++image )
{
if( image->texnum <= 0 )
continue;
texels += image->upload_width * image->upload_height;
switch( image->type )
{
case TT_Sprite:
Com_Printf( "S" );
break;
case TT_Wall:
Com_Printf( "W" );
break;
case TT_Pic:
Com_Printf( "P" );
break;
default:
Com_Printf( " " );
break;
}
Com_Printf( " %3i %3i %s: %s\n",
image->upload_width, image->upload_height, palstrings[ 0 ], image->name );
}
Com_Printf( "Total texel count (not counting mipmaps): %i\n", texels );
}
texture_t *TM_AllocateTexture( const char *name ) {
texture_t *tex;
int i;
assert( strlen( name ) < sizeof( tex->name ) );
// find a free texture_t space
for( i = 0, tex = ttextures; i < numttextures; ++i, ++tex )
{
if( ! tex->texnum )
{
break;
}
}
if( i == numttextures )
{
if( numttextures == MAX_TEXTURES )
{
Com_DPrintf( "MAX_TEXTURES reached\n" );
return r_notexture;
}
numttextures++;
}
tex = &ttextures[ i ];
memset( tex, 0, sizeof( *tex ) );
my_strlcpy( tex->name, name, MAX_GAMEPATH );
tex->registration_sequence = texture_registration_sequence;
// don't let R_Bind skip the next bind call
currentTextures[ currenttmu ] = -1;
pfglGenTextures( 1, &tex->texnum );
pfglBindTexture( GL_TEXTURE_2D, tex->texnum );
return tex;
}
/*
-----------------------------------------------------------------------------
Function: TM_LoadTexture -Load raw image into video memory.
Parameters:
name -[in] Name of texture image.
data -[in] Raw pixel data in the format described by PixelFormat.
width -[in] Width of image in pixels.
height -[in] Height of image in pixels.
type -[in]
PixelFormat [in]
Returns: Pointer to filled out texture_t structure.
Notes: Any texture that was not touched on this registration sequence will be freed.
-----------------------------------------------------------------------------
*/
PUBLIC texture_t *TM_LoadTexture( const char *name, W8 *data, int width, int height, texturetype_t type, W16 bytes )
{
texture_t *tex;
W8 *scaled;
W16 scaled_width, scaled_height;
tex = TM_AllocateTexture( name );
tex->width = width;
tex->height = height;
tex->type = type;
tex->bytes = bytes;
switch( type )
{
case TT_Pic:
tex->MipMap = false;
tex->WrapS = Clamp;
tex->WrapT = Clamp;
tex->MinFilter = Nearest;
tex->MagFilter = NearestMipMapOff;
break;
case TT_Wall:
tex->MipMap = true;
tex->WrapS = Repeat;
tex->WrapT = Repeat;
// tex->MinFilter = LinearMipMapLinear;
tex->MinFilter = LinearMipMapNearest;
tex->MagFilter = Linear;
break;
default:
tex->WrapS = Repeat;
tex->WrapT = Repeat;
tex->MinFilter = Nearest;
tex->MagFilter = NearestMipMapOff;
break;
}
for( scaled_width = 1 ; scaled_width < tex->width ; scaled_width <<= 1 )
{
;
}
if( gl_round_down->value && scaled_width > tex->width && tex->MipMap )
{
scaled_width >>= 1;
}
for( scaled_height = 1 ; scaled_height < tex->height ; scaled_height <<= 1 )
{
;
}
if( gl_round_down->value && scaled_height > tex->height && tex->MipMap )
{
scaled_height >>= 1;
}
// let people sample down the world textures for speed
if( tex->MipMap )
{
scaled_width >>= (int)gl_picmip->value;
scaled_height >>= (int)gl_picmip->value;
}
// don't ever bother with > glMaxTexSize textures
if( scaled_width > glMaxTexSize )
{
scaled_width = glMaxTexSize;
}
if( scaled_height > glMaxTexSize )
{
scaled_height = glMaxTexSize;
}
if( scaled_width < 1 )
{
scaled_width = 1;
}
if( scaled_height < 1 )
{
scaled_height = 1;
}
tex->upload_width = scaled_width;
tex->upload_height = scaled_height;
if( scaled_width == tex->width && scaled_height == tex->height )
{
scaled = data;
}
else
{
scaled = Z_Malloc( scaled_width * scaled_height * tex->bytes );
TM_ResampleTexture( data, tex->width, tex->height, scaled, scaled_width, scaled_height, tex->bytes, INTERPOLATION_NONE );
}
{
// upload base image
GLenum internalFormat[] = { GL_LUMINANCE, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA };
#if 0
GLenum externalFormat[] = { GL_LUMINANCE, GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT_5_5_5_1 };
pfglTexImage2D( GL_TEXTURE_2D, 0, internalFormat[ tex->bytes ], scaled_width, scaled_height, 0, tex->bytes == 4 ? GL_RGBA : GL_RGB, externalFormat[ tex->bytes ], scaled );
#else
pfglTexImage2D( GL_TEXTURE_2D, 0, internalFormat[ tex->bytes ], scaled_width, scaled_height, 0, tex->bytes == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, scaled );
#endif
// upload mipmaps if required
#ifdef IPHONE
glGenerateMipmapOES( GL_TEXTURE_2D );
#else
if( tex->MipMap )
{
int miplevel = 0;
while( TM_MipMap( scaled, &scaled_width, &scaled_height, tex->bytes ) )
{
pfglTexImage2D( GL_TEXTURE_2D, ++miplevel, internalFormat[ tex->bytes ], scaled_width, scaled_height, 0, tex->bytes == 4 ? GL_RGBA : GL_RGB, GL_UNSIGNED_BYTE, scaled );
}
}
#endif
}
if ( scaled != data ) {
Z_Free( scaled );
}
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, WrapToGL( tex->WrapS ) );
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, WrapToGL( tex->WrapT ) );
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, MinFilterToGL( tex->MipMap, tex->MinFilter ) );
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, MagFilterToGL( tex->MagFilter ) );
#ifdef IPHONE
if ( type == TT_Wall ) {
pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f );
} else {
pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0 );
}
#endif
return tex;
}
/*
-----------------------------------------------------------------------------
Function: TM_FreeUnusedTextures -Free unused textures.
Parameters: Nothing.
Returns: Nothing.
Notes: Any texture that was not touched on this registration sequence will be freed.
-----------------------------------------------------------------------------
*/
PUBLIC void TM_FreeUnusedTextures( void )
{
#if 0
int i;
texture_t *tex;
// never free r_notexture texture
r_notexture->registration_sequence = texture_registration_sequence;
for( i = 0, tex = ttextures ; i < numttextures ; ++i, ++tex )
{
if( tex->registration_sequence == texture_registration_sequence )
continue; // used this sequence
if( ! tex->registration_sequence )
continue; // free image_t slot
if( tex->type == TT_Pic )
continue; // don't free pics
// free texture
R_DeleteTexture( tex->texnum );
memset( tex, 0, sizeof( *tex ) );
}
#endif
}
/*
-----------------------------------------------------------------------------
Function: TM_FindTexture -Find texture.
Parameters: name -[in] Name of the texture to find.
type -[in] Type of texture (see texturetype_t).
Returns: r_notexture if the texture is not found, otherwise it will
return a valid texture_t structure.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC texture_t *TM_FindTexture( const char *name, texturetype_t type )
{
texture_t *tex;
int i, len;
W8 *data; /* raw texture data */
W16 width, height; /* width, height of texture */
W16 bytes;
char digested[1024];
filehandle_t *fh;
if( ! name || ! *name )
{
return r_notexture;
}
// Check for file extension
len = strlen( name );
if( len < 5 )
{
return r_notexture;
}
// look for it in the texture cache
for( i = 0, tex = ttextures; i < numttextures; ++i, ++tex )
{
if( ! strcmp( name, tex->name ) )
{
tex->registration_sequence = texture_registration_sequence;
return tex;
}
}
//
// load the texture from disk
//
data = NULL;
if( strcmp( name + len - 4, ".tga" ) ) {
return r_notexture;
}
//gsh
//Com_Printf( "Loading texture: %s\n", name );
// look for the pre-digested 5551 version
strcpy( digested, name );
strcpy( digested + len - 4, ".5551" );
fh = FS_OpenFile( digested, 0 );
if ( fh ) {
typedef struct {
int internalFormat;
int externalFormat;
int bpp;
} formatInfo_t;
static formatInfo_t formatInfo[7] = {
// the wall exporter always saved as 5551 even when there was no alpha
{ GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16 },
{ GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, 16 },
{ GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4, 16 },
{ GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, GL_UNSIGNED_BYTE, 4 },
{ GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, GL_UNSIGNED_BYTE, 4 },
{ GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG, GL_UNSIGNED_BYTE, 2 },
{ GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG, GL_UNSIGNED_BYTE, 2 },
};
picHeader_t *ph = (picHeader_t *)fh->filedata;
int noMips = 0;
formatInfo_t *fi;
if ( ph->picFormat & PF_NO_MIPS ) {
noMips = 1;
fi = &formatInfo[ph->picFormat&~PF_NO_MIPS];
} else {
fi = &formatInfo[ph->picFormat];
}
int w = ph->uploadWidth;
int h = ph->uploadHeight;
int l = 0;
texture_t *tx = TM_AllocateTexture( name );
tx->width = ph->srcWidth;
tx->height = ph->srcHeight;
tx->upload_width = w;
tx->upload_height = h;
tx->header = *ph;
tx->maxS = (float)ph->srcWidth / ph->uploadWidth;
tx->maxT = (float)ph->srcHeight / ph->uploadHeight;
unsigned char *s = (unsigned char *)(ph+1);
while( 1 ) {
int size = (w*h*fi->bpp)/8;
if ( fi->bpp < 16 ) {
if ( size < 32 ) {
// minimum PVRTC size
size = 32;
}
qglCompressedTexImage2D( GL_TEXTURE_2D, l, fi->internalFormat, w, h, 0,
size, s );
} else {
qglTexImage2D( GL_TEXTURE_2D, l, fi->internalFormat, w, h, 0,
fi->internalFormat, fi->externalFormat, s );
}
if ( w == 1 && h == 1 ) {
break;
}
if ( noMips ) {
break;
}
l++;
s += size;
w >>= 1;
if ( w == 0 ) {
w = 1;
}
h >>= 1;
if ( h == 0 ) {
h = 1;
}
}
FS_CloseFile( fh );
if ( noMips ) {
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
} else {
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
}
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
if ( type == TT_Wall ) {
pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 2.0f );
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
} else {
pfglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0 );
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
}
pfglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
return tx;
}
// load a normal TGA
LoadTGA( name, &data, &width, &height, &bytes );
if ( data ) {
tex = TM_LoadTexture( name, data, width, height, type, bytes );
MM_FREE( data );
tex->maxS = tex->maxT = 1.0f;
return tex;
}
// load a jpg
{
int jpgSize = 0;
W8 *jpgData;
// try jpeg if no tga exists
strcpy( digested, name );
strcpy( digested + len - 4, ".jpg" );
fh = FS_OpenFile( digested, 0 );
if ( fh == NULL ) {
Com_Printf( "Failed to find texture %s\n", name );
return r_notexture;
} //else { //added the else...gsh
jpgSize = FS_GetFileSize( fh );
jpgData = fh->ptrStart;
SysIPhoneLoadJPG( jpgData, jpgSize, &data, &width, &height, &bytes );
FS_CloseFile( fh );
if ( ! data ) {
free( jpgData );
return r_notexture;
} //else { //added the else
tex = TM_LoadTexture( name, data, width, height, type, bytes );
MM_FREE( data );
tex->maxS = tex->maxT = 1.0f;
return tex;
}
/*
Com_Printf("Trying to find texture made it to the end\n");
//gsh.. couldn't find it... try doing it again, but looking in a new location
if (spritelocation == SODSPRITESDIRNAME && spritelocation != WL6SPRITESDIRNAME)
{
//need to remove the 'sod'
if (strncmp(spritelocation, name, strlen(spritelocation)) == 0)
{
char buffer[64];
char tempName[64];
int offset = strlen(spritelocation) + 1;
for (int i = 0; i < strlen(name) - offset; ++i)
{
buffer[i] = name[i+offset];
}
buffer[i] = '\0'; //just in case
spritelocation = WL6SPRITESDIRNAME;
//TODO:
my_snprintf(tempName, sizeof(tempName), "%s/%s", spritelocation, buffer);
Com_Printf("tempName: %s\n", tempName);
Com_Printf("buffer: %s\n", buffer);
spritelocation = SODSPRITESDIRNAME; //return to sodsprites
tex = TM_FindTexture( tempName, type);
return tex;
}
}
return r_notexture;*/
return NULL;
}
/*
-----------------------------------------------------------------------------
Function: TM_GetTextureSize -Find texture.
Parameters:width -[out] Width of texture.
height -[out] Height of texture.
name -[in] Name of the texture to get dimensions of.
Returns: Nothing.
Notes: If texture is not found, width and height are -1.
-----------------------------------------------------------------------------
*/
PUBLIC void TM_GetTextureSize( SW32 *width, SW32 *height, const char *name )
{
texture_t *tex;
tex = TM_FindTexture( name, TT_Pic );
if( ! tex )
{
*width = *height = -1;
return;
}
*width = tex->width;
*height = tex->height;
}
/* Note: cubic function no longer clips result */
PRIVATE INLINECALL double
cubic (double dx,
int jm1,
int j,
int jp1,
int jp2)
{
/* Catmull-Rom - not bad */
return (double) ((( ( - jm1 + 3 * j - 3 * jp1 + jp2 ) * dx +
( 2 * jm1 - 5 * j + 4 * jp1 - jp2 ) ) * dx +
( - jm1 + jp1 ) ) * dx + (j + j) ) / 2.0;
}
_boolean pixel_region_has_alpha( int bytes )
{
if( bytes == 2 || bytes == 4 )
{
return true;
}
else
{
return false;
}
}
PRIVATE void
expand_line( double *dest,
double *src,
int bytes,
int old_width,
int width )
{
double ratio;
int x,b;
int src_col;
double frac;
double *s;
ratio = old_width / (double) width;
/* we can overflow src's boundaries, so we expect our caller to have
allocated extra space for us to do so safely (see scale_region ()) */
/* this could be optimized much more by precalculating the coefficients for
each x */
for( x = 0; x < width; ++x )
{
src_col = ((int) (x * ratio + 2.0 - 0.5)) - 2;
/* +2, -2 is there because (int) rounds towards 0 and we need
to round down */
frac = (x * ratio - 0.5) - src_col;
s = &src[ src_col * bytes ];
for( b = 0 ; b < bytes ; b++ )
dest[ b ] = cubic( frac, (int)s[ b - bytes ], (int)s[ b ], (int)s[ b + bytes ], (int)s[ b + bytes * 2 ] );
dest += bytes;
}
}
PRIVATE void
shrink_line( double *dest,
double *src,
int bytes,
int old_width,
int width )
{
int x;
int b;
double *srcp;
double *destp;
double accum[4];
double slice;
const double avg_ratio = (double) width / old_width;
const double inv_width = 1.0 / width;
int slicepos; /* slice position relative to width */
#if 0
Com_DPrintf( "shrink_line bytes=%d old_width=%d width=%d interp=%d "
"avg_ratio=%f\n",
bytes, old_width, width, interp, avg_ratio);
#endif
// g_return_if_fail( bytes <= 4 );
/* This algorithm calculates the weighted average of pixel data that
each output pixel must receive, taking into account that it always
scales down, i.e. there's always more than one input pixel per each
output pixel. */
srcp = src;
destp = dest;
slicepos = 0;
/* Initialize accum to the first pixel slice. As there is no partial
pixel at start, that value is 0. The source data is interleaved, so
we maintain BYTES accumulators at the same time to deal with that
many channels simultaneously. */
for( b = 0 ; b < bytes ; ++b )
{
accum[ b ] = 0.0;
}
for( x = 0 ; x < width ; x++ )
{
/* Accumulate whole pixels. */
do
{
for( b = 0 ; b < bytes ; b++ )
accum[ b ] += *srcp++;
slicepos += width;
}
while( slicepos < old_width );
slicepos -= old_width;
if( ! (slicepos < width))
Com_Printf( "Assertion (slicepos < width) failed. Please report.\n" );
if( slicepos == 0 )
{
/* Simplest case: we have reached a whole pixel boundary. Store
the average value per channel and reset the accumulators for
the next round.
The main reason to treat this case separately is to avoid an
access to out-of-bounds memory for the first pixel. */
for (b = 0; b < bytes; b++)
{
*destp++ = accum[b] * avg_ratio;
accum[b] = 0.0;
}
}
else
{
for( b = 0; b < bytes; b++ )
{
/* We have accumulated a whole pixel per channel where just a
slice of it was needed. Subtract now the previous pixel's
extra slice. */
slice = srcp[- bytes + b] * slicepos * inv_width;
*destp++ = (accum[b] - slice) * avg_ratio;
/* That slice is the initial value for the next round. */
accum[b] = slice;
}
}
}
/* Sanity check: srcp should point to the next-to-last position, and
slicepos should be zero. */
if( ! (srcp - src == old_width * bytes && slicepos == 0) )
{
Com_Printf ("Assertion (srcp - src == old_width * bytes && slicepos == 0)"
" failed. Please report.");
}
}
PRIVATE void pixel_region_get_row( W8 *src, int y, int width, W8 *tmp_src, int BytesPerPixel )
{
int i;
unsigned long k = 0;
unsigned char *scanline = tmp_src;
unsigned char *ptr = src;
for( i = 0 ; i < (width * BytesPerPixel) ; ++i )
{
scanline[ k++ ] = ptr[ y * width * BytesPerPixel + i ];
}
}
PRIVATE void pixel_region_set_row( W8 *dest,
int BytesPerPixel,
int y,
int width,
W8 *data )
{
int i;
unsigned long k = 0;
unsigned char *scanline = dest;
unsigned char *ptr = data;
for( i = 0 ; i < (width * BytesPerPixel) ; ++i )
{
scanline[ y * width * BytesPerPixel + i ] = ptr[ k++ ];
}
}
PRIVATE void
get_premultiplied_double_row( W8 *in,
int PRbytes,
int x,
int y,
int w,
double *row,
W8 *tmp_src,
int n )
{
int b;
int bytes = PRbytes;
pixel_region_get_row( in, y, w, tmp_src, bytes );
if( pixel_region_has_alpha( bytes ) )
{
/* premultiply the alpha into the double array */
double *irow = row;
int alpha = bytes - 1;
double mod_alpha;
for( x = 0; x < w; ++x )
{
mod_alpha = tmp_src[ alpha ] / 255.0;
for( b = 0; b < alpha; ++b )
{
irow[ b ] = mod_alpha * tmp_src[ b ];
}
irow[ b ] = tmp_src[ alpha ];
irow += bytes;
tmp_src += bytes;
}
}
else /* no alpha */
{
for( x = 0; x < w * bytes; ++x )
{
row[ x ] = tmp_src[ x ];
}
}
/* set the off edge pixels to their nearest neighbor */
for( b = 0; b < 2 * bytes; b++ )
{
row[ b - 2 * bytes ] = row[ b % bytes ];
}
for( b = 0; b < bytes * 2; b++ )
{
row[ b + w * bytes ] = row[ (w - 1) * bytes + b % bytes ];
}
}
PRIVATE INLINECALL void
rotate_pointers( W8 **p, W32 n )
{
W32 i;
W8 *tmp;
tmp = p[ 0 ];
for( i = 0 ; i < n-1 ; i++ )
{
p[ i ] = p[ i + 1 ];
}
p[ i ] = tmp;
}
PRIVATE void
get_scaled_row( double **src,
int y,
int new_width,
double *row,
W8 *src_tmp,
W8 *srcPR,
int old_width,
int old_height,
int bytes )
{
/* get the necesary lines from the source image, scale them,
and put them into src[] */
rotate_pointers( (unsigned char **)src, 4 );
if( y < 0 )
{
y = 0;
}
if( y < old_height )
{
get_premultiplied_double_row( srcPR, bytes, 0, y, old_width,
row, src_tmp, 1 );
if( new_width > old_width )
{
expand_line( src[3], row, bytes, old_width, new_width );
}
else if( old_width > new_width )
{
shrink_line( src[3], row, bytes, old_width, new_width );
}
else /* no scailing needed */
{
memcpy( src[3], row, sizeof( double ) * new_width * bytes );
}
}
else
{
memcpy( src[3], src[2], sizeof( double ) * new_width * bytes );
}
}
/*
non-interpolating scale_region.
*/
PRIVATE void
scale_region_no_resample( W8 *in, int inwidth, int inheight,
W8 *out, int outwidth, int outheight, char bytes )
{
int *x_src_offsets;
int *y_src_offsets;
W8 *src;
W8 *dest;
int width, height, orig_width, orig_height;
int last_src_y;
int row_bytes;
int x, y, b;
orig_width = inwidth;
orig_height = inheight;
width = outwidth;
height = outheight;
/* the data pointers... */
x_src_offsets = (int *) MM_MALLOC( sizeof( int ) * width * bytes );
y_src_offsets = (int *) MM_MALLOC( sizeof( int ) * height );
src = (unsigned char *) MM_MALLOC( orig_width * bytes);
dest = (unsigned char *) MM_MALLOC( width * bytes);
/* pre-calc the scale tables */
for( b = 0; b < bytes; b++ )
{
for( x = 0; x < width; x++ )
{
x_src_offsets[ b + x * bytes ] =
b + bytes * ((x * orig_width + orig_width / 2) / width);
}
}
for( y = 0; y < height; y++ )
{
y_src_offsets[ y ] = (y * orig_height + orig_height / 2) / height;
}
/* do the scaling */
row_bytes = width * bytes;
last_src_y = -1;
for( y = 0; y < height; y++ )
{
/* if the source of this line was the same as the source
* of the last line, there's no point in re-rescaling.
*/
if( y_src_offsets[ y ] != last_src_y )
{
pixel_region_get_row( in, y_src_offsets[ y ], orig_width, src, bytes );
//pixel_region_get_row( srcPR, 0, y_src_offsets[y], orig_width, src, 1 );
for( x = 0 ; x < row_bytes ; x++ )
{
dest[ x ] = src[ x_src_offsets[ x ] ];
}
last_src_y = y_src_offsets[ y ];
}
pixel_region_set_row( out, bytes, y, width, dest );
}
MM_FREE( x_src_offsets );
MM_FREE( y_src_offsets );
MM_FREE( src );
MM_FREE( dest );
}
/*
-----------------------------------------------------------------------------
Function: TM_ResampleTexture -Resize texture.
Parameters:
in -[in] Original texture data.
inwidth -[in] Original width of texture in pixels.
inheight -[in] Original height of texture in pixels.
out -[in/out] Resized texture data.
outwidth -[in] New width of texture in pixels.
outheight -[in] New height of texture in pixels.
bytes -[in] Number of bytes per pixel.
interpolation -[in] see InterpolationType
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void TM_ResampleTexture( W8 *in, int inwidth, int inheight, W8 *out, int outwidth, int outheight, W8 bytes, InterpolationType interpolation )
{
double *src[ 4 ];
W8 *src_tmp;
W8 *dest;
double *row, *accum;
int b;
int width, height;
int orig_width, orig_height;
double y_rat;
int i;
int old_y = -4;
int new_y;
int x, y;
if( interpolation == INTERPOLATION_NONE )
{
scale_region_no_resample( in, inwidth, inheight, out, outwidth, outheight, bytes );
return;
}
orig_width = inwidth;
orig_height = inheight;
width = outwidth;
height = outheight;
#if 0
Com_DPrintf( "scale_region: (%d x %d) -> (%d x %d)\n",
orig_width, orig_height, width, height );
#endif
/* find the ratios of old y to new y */
y_rat = (double) orig_height / (double) height;
/* the data pointers... */
for( i = 0 ; i < 4 ; ++i )
{
src[ i ] = (double *) MM_MALLOC( sizeof( double ) * width * bytes );
}
dest = (PW8) MM_MALLOC( width * bytes);
src_tmp = (PW8) MM_MALLOC( orig_width * bytes );
/* offset the row pointer by 2*bytes so the range of the array
is [-2*bytes] to [(orig_width + 2)*bytes] */
row = (double *) MM_MALLOC( sizeof( double ) * (orig_width + 2 * 2) * bytes );
row += bytes * 2;
accum = (double *) MM_MALLOC( sizeof( double ) * width * bytes );
/* Scale the selected region */
for( y = 0 ; y < height ; y++ )
{
if( height < orig_height )
{
int max;
double frac;
const double inv_ratio = 1.0 / y_rat;
if( y == 0 ) /* load the first row if this is the first time through */
{
get_scaled_row( &src[0], 0, width, row, src_tmp, in, orig_width, orig_height, bytes );
}
new_y = (int)(y * y_rat);
frac = 1.0 - (y * y_rat - new_y);
for( x = 0 ; x < width * bytes; ++x )
{
accum[x] = src[3][x] * frac;
}
max = (int) ((y + 1) * y_rat) - new_y - 1;
get_scaled_row( &src[ 0 ], ++new_y, width, row, src_tmp, in, orig_width, orig_height, bytes );
while( max > 0 )
{
for( x = 0 ; x < width * bytes ; ++x )
{
accum[x] += src[ 3 ][ x ];
}
get_scaled_row( &src[ 0 ], ++new_y, width, row, src_tmp, in, orig_width, orig_height, bytes );
max--;
}
frac = (y + 1) * y_rat - ((int) ((y + 1) * y_rat));
for( x = 0 ; x < width * bytes ; ++x )
{
accum[ x ] += frac * src[ 3 ][ x ];
accum[ x ] *= inv_ratio;
}
}
else if( height > orig_height )
{
double p0, p1, p2, p3;
double dy;
new_y = (int)floor( y * y_rat - 0.5 );
while( old_y <= new_y )
{
/* get the necesary lines from the source image, scale them,
and put them into src[] */
get_scaled_row( &src[ 0 ], old_y + 2, width, row, src_tmp, in, orig_width, orig_height, bytes );
old_y++;
}
dy = (y * y_rat - 0.5) - new_y;
p0 = cubic( dy, 1, 0, 0, 0 );
p1 = cubic( dy, 0, 1, 0, 0 );
p2 = cubic( dy, 0, 0, 1, 0 );
p3 = cubic( dy, 0, 0, 0, 1 );
for( x = 0 ; x < width * bytes ; ++x )
{
accum[ x ] = ( p0 * src[ 0 ][ x ] + p1 * src[ 1 ][ x ] +
p2 * src[ 2 ][ x ] + p3 * src[ 3 ][ x ] );
}
}
else /* height == orig_height */
{
get_scaled_row( &src[ 0 ], y, width, row, src_tmp, in, orig_width, orig_height, bytes );
memcpy( accum, src[ 3 ], sizeof( double ) * width * bytes );
}
if( pixel_region_has_alpha( bytes ) )
{
/* unmultiply the alpha */
double inv_alpha;
double *p = accum;
int alpha = bytes - 1;
int result;
W8 *d = dest;
for( x = 0 ; x < width ; ++x )
{
if( p[ alpha ] > 0.001 )
{
inv_alpha = 255.0 / p[ alpha ];
for( b = 0 ; b < alpha ; b++ )
{
result = RINT( inv_alpha * p[ b ] );
if( result < 0 )
{
d[ b ] = 0;
}
else if( result > 255 )
{
d[ b ] = 255;
}
else
{
d[ b ] = result;
}
}
result = RINT( p[ alpha ] );
if( result > 255 )
{
d[ alpha ] = 255;
}
else
{
d[ alpha ] = result;
}
}
else /* alpha <= 0 */
{
for( b = 0 ; b <= alpha ; ++b )
{
d[ b ] = 0;
}
}
d += bytes;
p += bytes;
}
}
else
{
int w = width * bytes;
for( x = 0 ; x < w ; ++x )
{
if( accum[ x ] < 0.0 )
{
dest[ x ] = 0;
}
else if( accum[ x ] > 255.0 )
{
dest[ x ] = 255;
}
else
{
dest[ x ] = RINT( accum[ x ] );
}
}
}
pixel_region_set_row( out, bytes, y, width, dest );
}
/* free up temporary arrays */
MM_FREE( accum );
for( i = 0 ; i < 4 ; ++i )
{
MM_FREE( src[ i ] );
}
MM_FREE( src_tmp );
MM_FREE( dest );
row -= 2 * bytes;
MM_FREE( row );
}
/*
-----------------------------------------------------------------------------
Function: TM_MipMap -Generate MipMap.
Parameters:
in -[in/out] Texture data.
width -[in] Width of texture in pixels.
height -[in] Height of texture in pixels.
Returns: Nothing.
Notes: Operates in place, quartering the size of the texture.
-----------------------------------------------------------------------------
*/
PUBLIC _boolean TM_MipMap( PW8 in, W16 *width, W16 *height, W16 bytes )
{
W16 new_width, new_height;
if( *width == 1 && *height == 1 )
{
return false;
}
if( *width < 2 )
{
new_width = 1;
}
else
{
new_width = *width >> 1;
}
if( *height < 2 )
{
new_height = 1;
}
else
{
new_height = *height >> 1;
}
TM_ResampleTexture( in, *width, *height, in, new_width, new_height, bytes, INTERPOLATION_CUBIC );
*width = new_width;
*height = new_height;
return true;
}
/*
-----------------------------------------------------------------------------
Function: TM_Init -Initialize Texture Manager.
Parameters: Nothing.
Returns: Nothing.
Notes: Generates default texture.
-----------------------------------------------------------------------------
*/
PUBLIC void TM_Init( void )
{
W8 *ptr;
W8 *data;
int x, y;
gl_round_down = Cvar_Get ("gl_round_down", "1", CVAR_INIT);
texture_registration_sequence = 1;
// create a checkerboard texture
data = MM_MALLOC( 16 * 16 * 4 );
for( y = 0; y < 16; ++y )
{
for( x = 0; x < 16; ++x )
{
ptr = &data[ (y * 16 + x) * 4 ];
if( (y < 8) ^ (x < 8) )
{
ptr[ 0 ] = ptr[ 1 ] = ptr[ 2 ] = 0x00;
ptr[ 3 ] = 0xFF;
}
else
{
ptr[ 0 ] = ptr[ 1 ] = ptr[ 2 ] = 0xFF;
ptr[ 3 ] = 0xFF;
}
}
}
r_notexture = TM_LoadTexture( "***r_notexture***", data, 16, 16, TT_Pic, 4 );
MM_FREE( data );
Cmd_AddCommand( "listTextures", TM_TextureList_f );
}
/*
-----------------------------------------------------------------------------
Function: TM_Shutdown -Shutdown Texture Manager.
Parameters: Nothing.
Returns: Nothing.
Notes:
-----------------------------------------------------------------------------
*/
PUBLIC void TM_Shutdown( void )
{
int i;
texture_t *tex;
for( i = 0, tex = ttextures; i < numttextures; ++i, ++tex )
{
if( ! tex->registration_sequence )
{
continue; // free image_t slot
}
// free texture
R_DeleteTexture( tex->texnum );
memset( tex, 0, sizeof( *tex ) );
}
Cmd_RemoveCommand( "listTextures" );
}