xash3d-fwgs/ref_gl/gl_backend.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

761 lines
17 KiB
C

/*
gl_backend.c - rendering backend
Copyright (C) 2010 Uncle Mike
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 3 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.
*/
#include "gl_local.h"
#include "xash3d_mathlib.h"
char r_speeds_msg[MAX_SYSPATH];
ref_speeds_t r_stats; // r_speeds counters
/*
===============
R_SpeedsMessage
===============
*/
qboolean R_SpeedsMessage( char *out, size_t size )
{
if( gEngfuncs.drawFuncs->R_SpeedsMessage != NULL )
{
if( gEngfuncs.drawFuncs->R_SpeedsMessage( out, size ))
return true;
// otherwise pass to default handler
}
if( r_speeds->value <= 0 ) return false;
if( !out || !size ) return false;
Q_strncpy( out, r_speeds_msg, size );
return true;
}
/*
==============
R_Speeds_Printf
helper to print into r_speeds message
==============
*/
void R_Speeds_Printf( const char *msg, ... )
{
va_list argptr;
char text[2048];
va_start( argptr, msg );
Q_vsprintf( text, msg, argptr );
va_end( argptr );
Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg ));
}
/*
==============
GL_BackendStartFrame
==============
*/
void GL_BackendStartFrame( void )
{
r_speeds_msg[0] = '\0';
}
/*
==============
GL_BackendEndFrame
==============
*/
void GL_BackendEndFrame( void )
{
mleaf_t *curleaf;
if( r_speeds->value <= 0 || !RI.drawWorld )
return;
if( !RI.viewleaf )
curleaf = WORLDMODEL->leafs;
else curleaf = RI.viewleaf;
R_Speeds_Printf( "Renderer: ^1Engine^7\n\n" );
switch( (int)r_speeds->value )
{
case 1:
Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i wpoly, %3i apoly\n%3i epoly, %3i spoly",
r_stats.c_world_polys, r_stats.c_alias_polys, r_stats.c_studio_polys, r_stats.c_sprite_polys );
break;
case 2:
R_Speeds_Printf( "visible leafs:\n%3i leafs\ncurrent leaf %3i\n", r_stats.c_world_leafs, curleaf - WORLDMODEL->leafs );
R_Speeds_Printf( "ReciusiveWorldNode: %3lf secs\nDrawTextureChains %lf\n", r_stats.t_world_node, r_stats.t_world_draw );
break;
case 3:
Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i alias models drawn\n%3i studio models drawn\n%3i sprites drawn",
r_stats.c_alias_models_drawn, r_stats.c_studio_models_drawn, r_stats.c_sprite_models_drawn );
break;
case 4:
Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i static entities\n%3i normal entities\n%3i server entities",
r_numStatics, r_numEntities - r_numStatics, ENGINE_GET_PARM( PARM_NUMENTITIES ));
break;
case 5:
Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i tempents\n%3i viewbeams\n%3i particles",
r_stats.c_active_tents_count, r_stats.c_view_beams_count, r_stats.c_particle_count );
break;
}
memset( &r_stats, 0, sizeof( r_stats ));
}
/*
=================
GL_LoadTexMatrix
=================
*/
void GL_LoadTexMatrix( const matrix4x4 m )
{
pglMatrixMode( GL_TEXTURE );
GL_LoadMatrix( m );
glState.texIdentityMatrix[glState.activeTMU] = false;
}
/*
=================
GL_LoadTexMatrixExt
=================
*/
void GL_LoadTexMatrixExt( const float *glmatrix )
{
Assert( glmatrix != NULL );
pglMatrixMode( GL_TEXTURE );
pglLoadMatrixf( glmatrix );
glState.texIdentityMatrix[glState.activeTMU] = false;
}
/*
=================
GL_LoadMatrix
=================
*/
void GL_LoadMatrix( const matrix4x4 source )
{
GLfloat dest[16];
Matrix4x4_ToArrayFloatGL( source, dest );
pglLoadMatrixf( dest );
}
/*
=================
GL_LoadIdentityTexMatrix
=================
*/
void GL_LoadIdentityTexMatrix( void )
{
if( glState.texIdentityMatrix[glState.activeTMU] )
return;
pglMatrixMode( GL_TEXTURE );
pglLoadIdentity();
glState.texIdentityMatrix[glState.activeTMU] = true;
}
/*
=================
GL_SelectTexture
=================
*/
void GL_SelectTexture( GLint tmu )
{
if( !GL_Support( GL_ARB_MULTITEXTURE ))
return;
// don't allow negative texture units
if( tmu < 0 ) return;
if( tmu >= GL_MaxTextureUnits( ))
{
gEngfuncs.Con_Reportf( S_ERROR "GL_SelectTexture: bad tmu state %i\n", tmu );
return;
}
if( glState.activeTMU == tmu )
return;
glState.activeTMU = tmu;
if( pglActiveTextureARB )
{
pglActiveTextureARB( tmu + GL_TEXTURE0_ARB );
if( tmu < glConfig.max_texture_coords )
pglClientActiveTextureARB( tmu + GL_TEXTURE0_ARB );
}
}
/*
==============
GL_DisableAllTexGens
==============
*/
void GL_DisableAllTexGens( void )
{
GL_TexGen( GL_S, 0 );
GL_TexGen( GL_T, 0 );
GL_TexGen( GL_R, 0 );
GL_TexGen( GL_Q, 0 );
}
/*
==============
GL_CleanUpTextureUnits
==============
*/
void GL_CleanUpTextureUnits( int last )
{
int i;
for( i = glState.activeTMU; i > (last - 1); i-- )
{
// disable upper units
if( glState.currentTextureTargets[i] != GL_NONE )
{
pglDisable( glState.currentTextureTargets[i] );
glState.currentTextureTargets[i] = GL_NONE;
glState.currentTextures[i] = -1; // unbind texture
}
GL_SetTexCoordArrayMode( GL_NONE );
GL_LoadIdentityTexMatrix();
GL_DisableAllTexGens();
GL_SelectTexture( i - 1 );
}
}
/*
==============
GL_CleanupAllTextureUnits
==============
*/
void GL_CleanupAllTextureUnits( void )
{
if( !glw_state.initialized ) return;
// force to cleanup all the units
GL_SelectTexture( GL_MaxTextureUnits() - 1 );
GL_CleanUpTextureUnits( 0 );
}
/*
=================
GL_MultiTexCoord2f
=================
*/
void GL_MultiTexCoord2f( GLenum texture, GLfloat s, GLfloat t )
{
if( !GL_Support( GL_ARB_MULTITEXTURE ))
return;
#ifndef XASH_GL_STATIC
if( pglMultiTexCoord2f != NULL )
#endif
pglMultiTexCoord2f( texture + GL_TEXTURE0_ARB, s, t );
}
/*
=================
GL_TextureTarget
=================
*/
void GL_TextureTarget( uint target )
{
if( glState.activeTMU < 0 || glState.activeTMU >= GL_MaxTextureUnits( ))
{
gEngfuncs.Con_Reportf( S_ERROR "GL_TextureTarget: bad tmu state %i\n", glState.activeTMU );
return;
}
if( glState.currentTextureTargets[glState.activeTMU] != target )
{
if( glState.currentTextureTargets[glState.activeTMU] != GL_NONE )
pglDisable( glState.currentTextureTargets[glState.activeTMU] );
glState.currentTextureTargets[glState.activeTMU] = target;
if( target != GL_NONE )
pglEnable( glState.currentTextureTargets[glState.activeTMU] );
}
}
/*
=================
GL_TexGen
=================
*/
void GL_TexGen( GLenum coord, GLenum mode )
{
int tmu = min( glConfig.max_texture_coords, glState.activeTMU );
int bit, gen;
switch( coord )
{
case GL_S:
bit = 1;
gen = GL_TEXTURE_GEN_S;
break;
case GL_T:
bit = 2;
gen = GL_TEXTURE_GEN_T;
break;
case GL_R:
bit = 4;
gen = GL_TEXTURE_GEN_R;
break;
case GL_Q:
bit = 8;
gen = GL_TEXTURE_GEN_Q;
break;
default: return;
}
if( mode )
{
if( !( glState.genSTEnabled[tmu] & bit ))
{
pglEnable( gen );
glState.genSTEnabled[tmu] |= bit;
}
pglTexGeni( coord, GL_TEXTURE_GEN_MODE, mode );
}
else
{
if( glState.genSTEnabled[tmu] & bit )
{
pglDisable( gen );
glState.genSTEnabled[tmu] &= ~bit;
}
}
}
/*
=================
GL_SetTexCoordArrayMode
=================
*/
void GL_SetTexCoordArrayMode( GLenum mode )
{
int tmu = min( glConfig.max_texture_coords, glState.activeTMU );
int bit, cmode = glState.texCoordArrayMode[tmu];
if( mode == GL_TEXTURE_COORD_ARRAY )
bit = 1;
else if( mode == GL_TEXTURE_CUBE_MAP_ARB )
bit = 2;
else bit = 0;
if( cmode != bit )
{
if( cmode == 1 ) pglDisableClientState( GL_TEXTURE_COORD_ARRAY );
else if( cmode == 2 ) pglDisable( GL_TEXTURE_CUBE_MAP_ARB );
if( bit == 1 ) pglEnableClientState( GL_TEXTURE_COORD_ARRAY );
else if( bit == 2 ) pglEnable( GL_TEXTURE_CUBE_MAP_ARB );
glState.texCoordArrayMode[tmu] = bit;
}
}
/*
=================
GL_Cull
=================
*/
void GL_Cull( GLenum cull )
{
if( !cull )
{
pglDisable( GL_CULL_FACE );
glState.faceCull = 0;
return;
}
pglEnable( GL_CULL_FACE );
pglCullFace( cull );
glState.faceCull = cull;
}
void GL_SetRenderMode( int mode )
{
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
switch( mode )
{
case kRenderNormal:
default:
pglDisable( GL_BLEND );
pglDisable( GL_ALPHA_TEST );
break;
case kRenderTransColor:
case kRenderTransTexture:
pglEnable( GL_BLEND );
pglDisable( GL_ALPHA_TEST );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
break;
case kRenderTransAlpha:
pglDisable( GL_BLEND );
pglEnable( GL_ALPHA_TEST );
break;
case kRenderGlow:
case kRenderTransAdd:
pglEnable( GL_BLEND );
pglDisable( GL_ALPHA_TEST );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
break;
}
}
/*
==============================================================================
SCREEN SHOTS
==============================================================================
*/
// used for 'env' and 'sky' shots
typedef struct envmap_s
{
vec3_t angles;
int flags;
} envmap_t;
const envmap_t r_skyBoxInfo[6] =
{
{{ 0, 270, 180}, IMAGE_FLIP_X },
{{ 0, 90, 180}, IMAGE_FLIP_X },
{{ -90, 0, 180}, IMAGE_FLIP_X },
{{ 90, 0, 180}, IMAGE_FLIP_X },
{{ 0, 0, 180}, IMAGE_FLIP_X },
{{ 0, 180, 180}, IMAGE_FLIP_X },
};
const envmap_t r_envMapInfo[6] =
{
{{ 0, 0, 90}, 0 },
{{ 0, 180, -90}, 0 },
{{ 0, 90, 0}, 0 },
{{ 0, 270, 180}, 0 },
{{-90, 180, -90}, 0 },
{{ 90, 0, 90}, 0 }
};
qboolean VID_ScreenShot( const char *filename, int shot_type )
{
rgbdata_t *r_shot;
uint flags = IMAGE_FLIP_Y;
int width = 0, height = 0;
qboolean result;
r_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));
r_shot->width = (gpGlobals->width + 3) & ~3;
r_shot->height = (gpGlobals->height + 3) & ~3;
r_shot->flags = IMAGE_HAS_COLOR;
r_shot->type = PF_RGB_24;
r_shot->size = r_shot->width * r_shot->height * gEngfuncs.Image_GetPFDesc( r_shot->type )->bpp;
r_shot->palette = NULL;
r_shot->buffer = Mem_Malloc( r_temppool, r_shot->size );
// get screen frame
pglReadPixels( 0, 0, r_shot->width, r_shot->height, GL_RGB, GL_UNSIGNED_BYTE, r_shot->buffer );
switch( shot_type )
{
case VID_SCREENSHOT:
break;
case VID_SNAPSHOT:
gEngfuncs.FS_AllowDirectPaths( true );
break;
case VID_LEVELSHOT:
flags |= IMAGE_RESAMPLE;
if( gpGlobals->wideScreen )
{
height = 480;
width = 800;
}
else
{
height = 480;
width = 640;
}
break;
case VID_MINISHOT:
flags |= IMAGE_RESAMPLE;
height = 200;
width = 320;
break;
case VID_MAPSHOT:
flags |= IMAGE_RESAMPLE|IMAGE_QUANTIZE; // GoldSrc request overviews in 8-bit format
height = 768;
width = 1024;
break;
}
gEngfuncs.Image_Process( &r_shot, width, height, flags, 0.0f );
// write image
result = gEngfuncs.FS_SaveImage( filename, r_shot );
gEngfuncs.FS_AllowDirectPaths( false ); // always reset after store screenshot
gEngfuncs.FS_FreeImage( r_shot );
return result;
}
/*
=================
VID_CubemapShot
=================
*/
qboolean VID_CubemapShot( const char *base, uint size, const float *vieworg, qboolean skyshot )
{
rgbdata_t *r_shot, *r_side;
byte *temp = NULL;
byte *buffer = NULL;
string basename;
int i = 1, flags, result;
if( !RI.drawWorld || !WORLDMODEL )
return false;
// make sure the specified size is valid
while( i < size ) i<<=1;
if( i != size ) return false;
if( size > gpGlobals->width || size > gpGlobals->height )
return false;
// alloc space
temp = Mem_Malloc( r_temppool, size * size * 3 );
buffer = Mem_Malloc( r_temppool, size * size * 3 * 6 );
r_shot = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));
r_side = Mem_Calloc( r_temppool, sizeof( rgbdata_t ));
// use client vieworg
if( !vieworg ) vieworg = RI.vieworg;
R_CheckGamma();
for( i = 0; i < 6; i++ )
{
// go into 3d mode
R_Set2DMode( false );
if( skyshot )
{
R_DrawCubemapView( vieworg, r_skyBoxInfo[i].angles, size );
flags = r_skyBoxInfo[i].flags;
}
else
{
R_DrawCubemapView( vieworg, r_envMapInfo[i].angles, size );
flags = r_envMapInfo[i].flags;
}
pglReadPixels( 0, 0, size, size, GL_RGB, GL_UNSIGNED_BYTE, temp );
r_side->flags = IMAGE_HAS_COLOR;
r_side->width = r_side->height = size;
r_side->type = PF_RGB_24;
r_side->size = r_side->width * r_side->height * 3;
r_side->buffer = temp;
if( flags ) gEngfuncs.Image_Process( &r_side, 0, 0, flags, 0.0f );
memcpy( buffer + (size * size * 3 * i), r_side->buffer, size * size * 3 );
}
r_shot->flags = IMAGE_HAS_COLOR;
r_shot->flags |= (skyshot) ? IMAGE_SKYBOX : IMAGE_CUBEMAP;
r_shot->width = size;
r_shot->height = size;
r_shot->type = PF_RGB_24;
r_shot->size = r_shot->width * r_shot->height * 3 * 6;
r_shot->palette = NULL;
r_shot->buffer = buffer;
// make sure what we have right extension
Q_strncpy( basename, base, MAX_STRING );
COM_StripExtension( basename );
COM_DefaultExtension( basename, ".tga" );
// write image as 6 sides
result = gEngfuncs.FS_SaveImage( basename, r_shot );
gEngfuncs.FS_FreeImage( r_shot );
gEngfuncs.FS_FreeImage( r_side );
return result;
}
//=======================================================
/*
===============
R_ShowTextures
Draw all the images to the screen, on top of whatever
was there. This is used to test for texture thrashing.
===============
*/
void R_ShowTextures( void )
{
gl_texture_t *image;
float x, y, w, h;
int total, start, end;
int i, j, k, base_w, base_h;
rgba_t color = { 192, 192, 192, 255 };
int charHeight, numTries = 0;
static qboolean showHelp = true;
string shortname;
if( !CVAR_TO_BOOL( gl_showtextures ))
return;
if( showHelp )
{
gEngfuncs.CL_CenterPrint( "use '<-' and '->' keys to change atlas page, ESC to quit", 0.25f );
showHelp = false;
}
GL_SetRenderMode( kRenderNormal );
pglClear( GL_COLOR_BUFFER_BIT );
pglFinish();
base_w = 8; // textures view by horizontal
base_h = 6; // textures view by vertical
rebuild_page:
total = base_w * base_h;
start = total * (gl_showtextures->value - 1);
end = total * gl_showtextures->value;
if( end > MAX_TEXTURES ) end = MAX_TEXTURES;
w = gpGlobals->width / base_w;
h = gpGlobals->height / base_h;
gEngfuncs.Con_DrawStringLen( NULL, NULL, &charHeight );
for( i = j = 0; i < MAX_TEXTURES; i++ )
{
image = R_GetTexture( i );
if( j == start ) break; // found start
if( pglIsTexture( image->texnum )) j++;
}
if( i == MAX_TEXTURES && gl_showtextures->value != 1 )
{
// bad case, rewind to one and try again
gEngfuncs.Cvar_SetValue( "r_showtextures", max( 1, gl_showtextures->value - 1 ));
if( ++numTries < 2 ) goto rebuild_page; // to prevent infinite loop
}
for( k = 0; i < MAX_TEXTURES; i++ )
{
if( j == end ) break; // page is full
image = R_GetTexture( i );
if( !pglIsTexture( image->texnum ))
continue;
x = k % base_w * w;
y = k / base_w * h;
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
GL_Bind( XASH_TEXTURE0, i ); // NOTE: don't use image->texnum here, because skybox has a 'wrong' indexes
if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE ))
pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );
pglBegin( GL_QUADS );
pglTexCoord2f( 0, 0 );
pglVertex2f( x, y );
if( image->target == GL_TEXTURE_RECTANGLE_EXT )
pglTexCoord2f( image->width, 0 );
else pglTexCoord2f( 1, 0 );
pglVertex2f( x + w, y );
if( image->target == GL_TEXTURE_RECTANGLE_EXT )
pglTexCoord2f( image->width, image->height );
else pglTexCoord2f( 1, 1 );
pglVertex2f( x + w, y + h );
if( image->target == GL_TEXTURE_RECTANGLE_EXT )
pglTexCoord2f( 0, image->height );
else pglTexCoord2f( 0, 1 );
pglVertex2f( x, y + h );
pglEnd();
if( FBitSet( image->flags, TF_DEPTHMAP ) && !FBitSet( image->flags, TF_NOCOMPARE ))
pglTexParameteri( image->target, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB );
COM_FileBase( image->name, shortname );
if( Q_strlen( shortname ) > 18 )
{
// cutoff too long names, it looks ugly
shortname[16] = '.';
shortname[17] = '.';
shortname[18] = '\0';
}
gEngfuncs.Con_DrawString( x + 1, y + h - charHeight, shortname, color );
j++, k++;
}
gEngfuncs.CL_DrawCenterPrint ();
pglFinish();
}
/*
================
SCR_TimeRefresh_f
timerefresh [noflip]
================
*/
void SCR_TimeRefresh_f( void )
{
int i;
double start, stop;
double time;
if( ENGINE_GET_PARM( PARM_CONNSTATE ) != ca_active )
return;
start = gEngfuncs.pfnTime();
// run without page flipping like GoldSrc
if( gEngfuncs.Cmd_Argc() == 1 )
{
pglDrawBuffer( GL_FRONT );
for( i = 0; i < 128; i++ )
{
gpGlobals->viewangles[1] = i / 128.0f * 360.0f;
R_RenderScene();
}
pglFinish();
R_EndFrame();
}
else
{
for( i = 0; i < 128; i++ )
{
R_BeginFrame( true );
gpGlobals->viewangles[1] = i / 128.0f * 360.0f;
R_RenderScene();
R_EndFrame();
}
}
stop = gEngfuncs.pfnTime ();
time = (stop - start);
gEngfuncs.Con_Printf( "%f seconds (%f fps)\n", time, 128 / time );
}