Been a long time since i last done some work on any engine, so in a fit of boredom i added an old friend of mine to doom3
Code: Select all
/*
================
BorderCheck macro. the pixels that get passed in should be pre-converted
to the YCbCr colorspace.
================
*/
#define BorderCheck( pix1, pix2, dY, dCb, dCr ) ( ( abs( *pix1 - *pix2 ) > dY ) || ( abs( *( pix1 + 1 ) - *( pix2 + 1 ) ) > dCb ) || ( abs( *(pix1 + 2) - *( pix2 + 2) ) > dCr) )
/*
================
LinearScale macro.
================
*/
#define LinearScale( src1, src2, pct ) ( ( src1 * (1 - pct) ) + ( src2 * pct) )
/*
================
GetOffSet macro.
================
*/
#define GetOffSet( new, start, cur ) ( new + ( cur - ( static_cast< unsigned char * >( start ) ) ) )
/*
================
Clamp macro.
================
*/
#define Clamp( a, b, c ) ( max( a, min( b, c ) ) )
/*
================
RGBAtoTCbCrA - converts a source RGBA pixel into a destination YCbCrA pixel
================
*/
ID_INLINE void RGBAtoYCbCrA( unsigned char *dest, unsigned char *src )
{
unsigned char s0, s1, s2;
s0 = *( src );
s1 = *( src + 1 );
s2 = *( src + 2 );
#define MIX(i, n, m0, m1, m2) ( *( dest + i ) = static_cast< unsigned char >( n + ( ( ( s0 * m0 ) + ( s1 * m0 ) + ( s2 * m2 )) / 256.0f ) ) )
MIX( 0, 16.0f, 65.738f, 129.057f, 25.064f );
MIX( 1, 128.0f, -37.945f, -74.494f, 112.439f );
MIX( 2, 128.0f, 112.439f, -94.154f, -18.285f );
#undef MIX
*( dest + 3 ) = *( src + 3 );
}
/*
================
R_ResampleTexture - resamples the texture given in indata, of the
dimensions inwidth by inheight to outdata, of the dimensions outwidth by
outheight, using a method based on the brief description of SmartFlt
given at http://www.hiend3d.com/smartflt.html
this could probably do with some optimizations.
================
*/
void R_ResampleTexture( void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight )
{
float xstep = ( static_cast< float >( inwidth ) ) / ( static_cast< float >( outwidth ) );
float ystep = ( static_cast< float >( inheight ) ) / ( static_cast< float >( outheight ) );
int dY = r_smartflt_y.GetInteger();
int dCb = r_smartflt_cb.GetInteger();
int dCr = r_smartflt_cr.GetInteger();
int DestX, DestY;
float SrcX, SrcY;
// buffer to store the YCbCrA version of the input texture.
unsigned char *Ybuffer = static_cast< byte * >( R_StaticAlloc( inwidth * inheight * 4 ) );
unsigned char *id = static_cast< byte * >( indata );
unsigned char *od = static_cast< byte * >( outdata );
unsigned char *idrowstart = id;
// convert the input texture to YCbCr into a temp buffer, for border detections.
for( DestX = 0, idrowstart = Ybuffer; DestX < ( inwidth * inheight ); DestX++, idrowstart += 4, id += 4 )
{
RGBAtoYCbCrA( idrowstart, id );
}
for( DestY = 0, SrcY = 0; DestY < outheight; DestY++, SrcY += ystep )
{
// four "work" pointers to make code a little nicer.
unsigned char *w0, *w1, *w2, *w3;
// right == clockwise, left == counter-clockwise
unsigned char *nearest, *left, *right, *opposite;
float pctnear, pctleft, pctright, pctopp;
float w0pct, w1pct, w2pct, w3pct;
float x, y, tmpx, tmpy;
char edges[6];
// clamp SrcY to cover for possible float error
// to make sure the edges fall into the special cases
if( SrcY > ( inheight - 1.01f ) )
{
SrcY = ( inheight - 1.01f );
}
// go to the start of the next row. "od" should be pointing at the right place already.
idrowstart = ( static_cast< byte * >( indata ) ) + ( static_cast< int >( SrcY ) ) * inwidth * 4;
for( DestX = 0, SrcX = 0; DestX < outwidth; DestX++, od += 4, SrcX += xstep )
{
// clamp SrcY to cover for possible float error
// to make sure that the edges fall into the special cases
if( SrcX > ( inwidth - 1.01f ) )
{
SrcX = inwidth - 1.01f;
}
id = idrowstart + ( static_cast< int >( SrcX ) ) * 4;
x = ( static_cast< int >( SrcX ) );
y = ( static_cast< int >( SrcY ) );
// if we happen to be directly on a source row
if( SrcY == y )
{
// and also directly on a source column
if( SrcX == x )
{
// then we are directly on a source pixel
// just copy it and move on.
SIMDProcessor->Memcpy( od, id, 4 );
continue;
}
// if there is a border between the two surrounding source pixels
if( BorderCheck( GetOffSet( Ybuffer, indata, id ), GetOffSet( Ybuffer, indata, ( id + 4 ) ), dY, dCb, dCr ) )
{
// if we are closer to the left
if( x == ( static_cast< int >( SrcX + 0.5f ) ) )
{
// copy the left pixel
SIMDProcessor->Memcpy( od, id, 4 );
continue;
}
else
{
// otherwise copy the right pixel
SIMDProcessor->Memcpy( od, id + 4, 4 );
continue;
}
}
else
{
// these two bordering pixels are part of the same region.
// blend them using a weighted average
x = SrcX - x;
w0 = id;
w1 = id + 4;
*od = static_cast< unsigned char >( LinearScale( *w0, *w1, x ) );
*( od + 1 ) = static_cast< unsigned char >( LinearScale( *( w0 + 1 ), *( w1 + 1 ), x ) );
*( od + 2 ) = static_cast< unsigned char >( LinearScale( *( w0 + 2 ), *( w1 + 2 ), x ) );
*( od + 3 ) = static_cast< unsigned char >( LinearScale( *( w0 + 3 ), *( w1 + 3 ), x ) );
continue;
}
}
// if we aren't direcly on a source row, but we are on a source column
if( SrcX == x )
{
// if there is a border between this source pixel and the one on
// the next row
if( BorderCheck( GetOffSet( Ybuffer, indata, id ), GetOffSet( Ybuffer, indata, ( id + inwidth * 4 ) ), dY, dCb, dCr ) )
{
// if we are closer to the top
if( y == ( static_cast< int >( SrcY + 0.5f ) ) )
{
// copy the top
SIMDProcessor->Memcpy( od, id, 4 );
continue;
}
else
{
// copy the bottom
SIMDProcessor->Memcpy( od, (id + inwidth * 4), 4 );
continue;
}
}
else
{
// the two pixels are part of the same region, blend them
// together with a weighted average
y = SrcY - y;
w0 = id;
w1 = id + ( inwidth * 4 );
*od = static_cast< unsigned char >( LinearScale( *w0, *w1, y ) );
*( od + 1 ) = static_cast< unsigned char >( LinearScale( *( w0 + 1 ), *( w1 + 1 ), y ) );
*( od + 2 ) = static_cast< unsigned char >( LinearScale( *( w0 + 2 ), *( w1 + 2 ), y ) );
*( od + 3 ) = static_cast< unsigned char >( LinearScale( *( w0 + 3 ), *( w1 + 3 ), y ) );
continue;
}
}
// now for the non-simple case: somewhere between four pixels.
// w0 is top-left, w1 is top-right, w2 is bottom-left, and w3 is bottom-right
w0 = id;
w1 = id + 4;
w2 = id + ( inwidth * 4 );
w3 = w2 + 4;
x = SrcX - x;
y = SrcY - y;
w0pct = 1.0f - sqrtf( x * x + y * y );
w1pct = 1.0f - sqrtf( ( 1 - x ) * ( 1 - x ) + y * y );
w2pct = 1.0f - sqrtf( x * x + ( 1 - y ) * ( 1 - y ) );
w3pct = 1.0f - sqrtf( ( 1 - x ) * ( 1 - x ) + ( 1 - y ) * ( 1 - y ) );
// set up our symbolic identification.
// "nearest" is the pixel whose quadrant we are in.
// "left" is counter-clockwise from "nearest"
// "right" is clockwise from "nearest"
// "opposite" is, well, opposite.
if( x < 0.5f )
{
tmpx = x;
if( y < 0.5f )
{
nearest = w0;
left = w2;
right = w1;
opposite = w3;
pctnear = w0pct;
pctleft = w2pct;
pctright = w1pct;
pctopp = w3pct;
tmpy = y;
}
else
{
nearest = w2;
left = w3;
right = w0;
opposite = w1;
pctnear = w2pct;
pctleft = w3pct;
pctright = w0pct;
pctopp = w1pct;
tmpy = 1.0f - y;
}
}
else
{
tmpx = 1.0f - x;
if( y < 0.5f )
{
nearest = w1;
left = w0;
right = w3;
opposite = w2;
pctnear = w1pct;
pctleft = w0pct;
pctright = w3pct;
pctopp = w2pct;
tmpy = y;
}
else
{
nearest = w3;
left = w1;
right = w2;
opposite = w0;
pctnear = w3pct;
pctleft = w1pct;
pctright = w2pct;
pctopp = w0pct;
tmpy = 1.0f - y;
}
}
x = tmpx;
y = tmpy;
w0 = GetOffSet( Ybuffer, indata, nearest );
w1 = GetOffSet( Ybuffer, indata, right );
w2 = GetOffSet( Ybuffer, indata, left );
w3 = GetOffSet( Ybuffer, indata, opposite );
edges[0] = BorderCheck( w0, w2, dY, dCb, dCr );
edges[1] = BorderCheck( w0, w1, dY, dCb, dCr );
edges[2] = BorderCheck( w0, w3, dY, dCb, dCr );
edges[3] = BorderCheck( w3, w2, dY, dCb, dCr );
edges[4] = BorderCheck( w3, w1, dY, dCb, dCr );
edges[5] = BorderCheck( w2, w1, dY, dCb, dCr );
#undef GetOffSet
// do the edge detections.
if( edges[0] && edges[1] && edges[2] && !edges[5] )
{
// borders all around, and no border between the left and right.
// if there is no border between the opposite side and only one
// of the two other corners, or if we are closer to the corner
if( ( edges[3] && !edges[4] ) || ( !edges[3] && edges[4] ) || ( x + y < 0.5f ) )
{
// closer to to the corner.
SIMDProcessor->Memcpy( od, nearest, 4 );
}
else
{
// closer to the center. (note, there is a diagonal line between the nearest pixel
// and the center of the four.)
// exclude the "nearest" pixel
// pctnear = 0.0f;
// if there is a border around the opposite corner,
// exclude it from the current pixel.
if( edges[3] && edges[4] )
{
// pctopp = 0.0f;
*od = static_cast< unsigned char >( Clamp( 0, ( ( ( *left * pctleft ) + ( *right * pctright ) ) / ( pctleft + pctright ) ), 255 ) );
*( od + 1 ) = static_cast< unsigned char >( Clamp( 0, ( ( ( *( left + 1 ) * pctleft ) + ( *( right + 1 ) * pctright ) ) / ( pctleft + pctright ) ), 255 ) );
*( od + 2 ) = static_cast< unsigned char >( Clamp( 0, ( ( ( *( left + 2 ) * pctleft ) + ( *( right + 2 ) * pctright ) ) / ( pctleft + pctright ) ), 255 ) );
*( od + 3 ) = static_cast< unsigned char >( Clamp( 0, ( ( ( *( left + 3 ) * pctleft ) + ( *( right + 3 ) * pctright ) ) / ( pctleft + pctright ) ), 255 ) );
}
else
{
*od = static_cast< unsigned char >( Clamp( 0, ( ( ( *left * pctleft ) + ( *right * pctright ) + ( *opposite * pctopp ) ) / ( pctleft + pctright + pctopp ) ), 255 ) );
*( od + 1 ) = static_cast< unsigned char >( Clamp( 0, ( ( ( *( left + 1 ) * pctleft ) + ( *( right + 1 ) * pctright ) + ( *( opposite + 1 ) * pctopp ) ) / ( pctleft + pctright + pctopp ) ), 255 ) );
*( od + 2 ) = static_cast< unsigned char >( Clamp( 0, ( ( ( *( left + 2 ) * pctleft ) + ( *( right + 2 ) * pctright ) + ( *( opposite + 2 ) * pctopp ) ) / ( pctleft + pctright + pctopp ) ), 255 ) );
*( od + 3 ) = static_cast< unsigned char >( Clamp( 0, ( ( ( *( left + 3 ) * pctleft ) + ( *( right + 3 ) * pctright ) + ( *( opposite + 3 ) * pctopp ) ) / ( pctleft + pctright + pctopp ) ), 255 ) );
}
}
}
else if( edges[0] && edges[1] && edges[2] )
{
SIMDProcessor->Memcpy( od, nearest, 4 );
}
else
{
float num[4], denom = pctnear;
num[0] = ( *nearest * pctnear );
num[1] = ( *( nearest + 1 ) * pctnear );
num[2] = ( *( nearest + 2 ) * pctnear );
num[3] = ( *( nearest + 3 ) * pctnear );
if( !edges[0] )
{
num[0] += *left * pctleft;
num[1] += *( left + 1 ) * pctleft;
num[2] += *( left + 2 ) * pctleft;
num[3] += *( left + 3 ) * pctleft;
denom += pctleft;
}
if( edges[1] )
{
num[0] += *right * pctright;
num[1] += *( right + 1 ) * pctright;
num[2] += *( right + 2 ) * pctright;
num[3] += *( right + 3 ) * pctright;
denom += pctright;
}
if( edges[2] )
{
num[0] += *opposite * pctopp;
num[1] += *( opposite + 1 ) * pctopp;
num[2] += *( opposite + 2 ) * pctopp;
num[3] += *( opposite + 3 ) * pctopp;
denom += pctopp;
}
// blend the source pixels together to get the output pixel.
// if a source pixel doesn't affect the output, it's percent should be set to 0 in the edge check
// code above. if only one pixel affects the output, its percentage should be set to 1 and all
// the others set to 0. (yeah, it is ugly, but I don't see a need to optimize this code (yet)
*od = static_cast< unsigned char >( Clamp( 0, num[0] / denom, 255 ) );
*( od + 1 ) = static_cast< unsigned char >( Clamp( 0, num[1] / denom, 255 ) );
*( od + 2 ) = static_cast< unsigned char >( Clamp( 0, num[2] / denom, 255 ) );
*( od + 3 ) = static_cast< unsigned char >( Clamp( 0, num[3] / denom, 255 ) );
}
}
}
R_StaticFree( Ybuffer );
}
this replaces the old resampletexture function
you need to call it in a different way than the old one to so modify R_LoadImage to look like this
Code: Select all
void R_LoadImage( const char *cname, byte **pic, int *width, int *height, ID_TIME_T *timestamp, bool makePowerOf2 )
{
idStr name = cname;
if( pic )
{
*pic = NULL;
}
if( timestamp )
{
*timestamp = 0xFFFFFFFF;
}
if( width )
{
*width = 0;
}
if( height )
{
*height = 0;
}
name.DefaultFileExtension( ".tga" );
if( name.Length() < 5 )
{
return;
}
name.ToLower();
idStr ext;
name.ExtractFileExtension( ext );
if( ext == "tga" )
{
LoadTGA( name.c_str(), pic, width, height, timestamp ); // try tga first
if( ( pic && *pic == 0 ) || ( timestamp && *timestamp == -1 ) )
{
name.StripFileExtension();
name.DefaultFileExtension( ".jpg" );
LoadJPG( name.c_str(), pic, width, height, timestamp );
}
}
else if( ext == "pcx" )
{
LoadPCX32( name.c_str(), pic, width, height, timestamp );
}
else if( ext == "bmp" )
{
LoadBMP( name.c_str(), pic, width, height, timestamp );
}
else if( ext == "jpg" )
{
LoadJPG( name.c_str(), pic, width, height, timestamp );
}
if( ( width && *width < 1 ) || ( height && *height < 1 ) )
{
if( pic && *pic )
{
R_StaticFree( *pic );
*pic = 0;
}
}
//
// convert to exact power of 2 sizes
//
if( pic && *pic && makePowerOf2 )
{
int w, h;
int scaled_width, scaled_height;
byte *pic_p;
w = *width;
h = *height;
Q_ROUND_POWER2( w, scaled_width );
Q_ROUND_POWER2( h, scaled_height );
if( scaled_width != w || scaled_height != h )
{
if( globalImages->image_roundDown.GetBool() && scaled_width > w )
{
scaled_width >>= 1;
}
if( globalImages->image_roundDown.GetBool() && scaled_height > h )
{
scaled_height >>= 1;
}
pic_p = ( byte * ) R_StaticAlloc( scaled_width * scaled_height * 4 );
R_ResampleTexture( *pic, w, h, pic_p, scaled_width, scaled_height );
R_StaticFree( *pic );
*pic = pic_p;
*width = scaled_width;
*height = scaled_height;
}
}
}
the Q_ROUND_POWER2 macros are just replacements for the round down functions, you can skip those if you want.
you also need these at the top of image_process.cpp
idCVar r_smartflt_y( "r_smartflt_y", "10", CVAR_INTEGER | CVAR_RENDERER, "Smart Filter Y Value" );
idCVar r_smartflt_cb( "r_smartflt_cb", "50", CVAR_INTEGER | CVAR_RENDERER, "Smart Filter CB Value" );
idCVar r_smartflt_cr( "r_smartflt_cr", "50", CVAR_INTEGER | CVAR_RENDERER, "Smart Filter CR Value" );
these allow you to fiddle a bit with the resample colors.
this resampling code is very old (around darkplaces 1.5 time) but has superior image quality (noticable in ultra where textures are now very sharp).
also cleaned up the thread code with a small addition gleaned from darkmod (originally based on boost)
Code: Select all
/*
==================
Sys_Createthread
==================
*/
typedef std::pair<xthread_t, void *> CreateThreadStartParams;
DWORD WINAPI CreateThreadStartRoutine( LPVOID lpThreadParameter )
{
std::pair<xthread_t, void *> arg = *( ( CreateThreadStartParams * )lpThreadParameter );
delete( ( CreateThreadStartParams * )lpThreadParameter );
return arg.first( arg.second );
}
void Sys_CreateThread( xthread_t function, void *parms, xthreadPriority priority, xthreadInfo &info, const char *name, xthreadInfo *threads[MAX_THREADS], int *thread_count )
{
LPVOID helperParam = new CreateThreadStartParams( function, parms );
HANDLE temp = CreateThread( NULL, // LPSECURITY_ATTRIBUTES lpsa,
0, // DWORD cbStack,
CreateThreadStartRoutine, // LPTHREAD_START_ROUTINE lpStartAddr,
helperParam, // LPVOID lpvThreadParm,
0, // DWORD fdwCreate,
&info.threadId );
info.threadHandle = ( intptr_t )temp;
if( priority == THREAD_HIGHEST )
{
SetThreadPriority( ( HANDLE ) info.threadHandle, THREAD_PRIORITY_HIGHEST ); // we better sleep enough to do this
}
else if( priority == THREAD_ABOVE_NORMAL )
{
SetThreadPriority( ( HANDLE ) info.threadHandle, THREAD_PRIORITY_ABOVE_NORMAL );
}
else
{
SetThreadPriority( ( HANDLE ) info.threadHandle, THREAD_PRIORITY_NORMAL );
}
info.name = name;
if( *thread_count <= MAX_THREADS )
{
threads[( *thread_count ) ++] = &info;
}
else
{
common->DPrintf( "WARNING: MAX_THREADS reached\n" );
}
}
now uses std functionality for the same effect.
the helper now correctly passes the thread to CreateThread.
Productivity is a state of mind.