cnq3/code/renderer/tr_backend.cpp
myT 515ca0883e minimizing disables the rendering back-end except for videos/screenshots
when minimized, we call the rest of the logic all the same (including the VM calls)
only 1 screenshot command can be issued per frame (i.e. only first one is kept)
2018-01-07 00:03:42 +01:00

1210 lines
32 KiB
C++

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena 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.
Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "tr_local.h"
backEndData_t *backEndData;
backEndState_t backEnd;
void GL_Bind( const image_t* image )
{
int texnum;
if ( !image ) {
ri.Printf( PRINT_WARNING, "GL_Bind: NULL image\n" );
texnum = tr.defaultImage->texnum;
} else {
texnum = image->texnum;
}
if ( r_nobind->integer && tr.defaultImage && !backEnd.projection2D ) {
texnum = tr.defaultImage->texnum;
}
if ( glState.texID[glState.currenttmu] != texnum ) {
glState.texID[glState.currenttmu] = texnum;
qglBindTexture( GL_TEXTURE_2D, texnum );
}
}
void GL_SelectTexture( int unit )
{
if ( glState.currenttmu == unit )
return;
if ( unit >= MAX_TMUS )
ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit );
qglActiveTextureARB( GL_TEXTURE0_ARB + unit );
qglClientActiveTextureARB( GL_TEXTURE0_ARB + unit );
glState.currenttmu = unit;
}
/*
** GL_Cull
*/
void GL_Cull( int cullType ) {
if ( glState.faceCulling == cullType ) {
return;
}
glState.faceCulling = cullType;
if ( cullType == CT_TWO_SIDED )
{
qglDisable( GL_CULL_FACE );
}
else
{
qglEnable( GL_CULL_FACE );
if ( cullType == CT_BACK_SIDED )
{
if ( backEnd.viewParms.isMirror )
{
qglCullFace( GL_FRONT );
}
else
{
qglCullFace( GL_BACK );
}
}
else
{
if ( backEnd.viewParms.isMirror )
{
qglCullFace( GL_BACK );
}
else
{
qglCullFace( GL_FRONT );
}
}
}
}
/*
** GL_TexEnv
*/
void GL_TexEnv( int env )
{
if ( env == glState.texEnv[glState.currenttmu] )
{
return;
}
glState.texEnv[glState.currenttmu] = env;
switch ( env )
{
case GL_MODULATE:
case GL_REPLACE:
case GL_DECAL:
case GL_ADD:
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env );
break;
default:
ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env );
break;
}
}
/*
** GL_State
**
** This routine is responsible for setting the most commonly changed state
** in Q3.
*/
void GL_State( unsigned long stateBits )
{
unsigned long diff = stateBits ^ glState.glStateBits;
if ( !diff )
{
return;
}
//
// check depthFunc bits
//
if ( diff & GLS_DEPTHFUNC_EQUAL )
{
if ( stateBits & GLS_DEPTHFUNC_EQUAL )
{
qglDepthFunc( GL_EQUAL );
}
else
{
qglDepthFunc( GL_LEQUAL );
}
}
//
// check blend bits
//
if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
{
GLenum srcFactor, dstFactor;
if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
{
switch ( stateBits & GLS_SRCBLEND_BITS )
{
case GLS_SRCBLEND_ZERO:
srcFactor = GL_ZERO;
break;
case GLS_SRCBLEND_ONE:
srcFactor = GL_ONE;
break;
case GLS_SRCBLEND_DST_COLOR:
srcFactor = GL_DST_COLOR;
break;
case GLS_SRCBLEND_ONE_MINUS_DST_COLOR:
srcFactor = GL_ONE_MINUS_DST_COLOR;
break;
case GLS_SRCBLEND_SRC_ALPHA:
srcFactor = GL_SRC_ALPHA;
break;
case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA:
srcFactor = GL_ONE_MINUS_SRC_ALPHA;
break;
case GLS_SRCBLEND_DST_ALPHA:
srcFactor = GL_DST_ALPHA;
break;
case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA:
srcFactor = GL_ONE_MINUS_DST_ALPHA;
break;
case GLS_SRCBLEND_ALPHA_SATURATE:
srcFactor = GL_SRC_ALPHA_SATURATE;
break;
default:
srcFactor = GL_ONE; // to get warning to shut up
ri.Error( ERR_DROP, "GL_State: invalid src blend state bits\n" );
break;
}
switch ( stateBits & GLS_DSTBLEND_BITS )
{
case GLS_DSTBLEND_ZERO:
dstFactor = GL_ZERO;
break;
case GLS_DSTBLEND_ONE:
dstFactor = GL_ONE;
break;
case GLS_DSTBLEND_SRC_COLOR:
dstFactor = GL_SRC_COLOR;
break;
case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR:
dstFactor = GL_ONE_MINUS_SRC_COLOR;
break;
case GLS_DSTBLEND_SRC_ALPHA:
dstFactor = GL_SRC_ALPHA;
break;
case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA:
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
break;
case GLS_DSTBLEND_DST_ALPHA:
dstFactor = GL_DST_ALPHA;
break;
case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA:
dstFactor = GL_ONE_MINUS_DST_ALPHA;
break;
default:
dstFactor = GL_ONE; // to get warning to shut up
ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" );
break;
}
qglEnable( GL_BLEND );
qglBlendFunc( srcFactor, dstFactor );
}
else
{
qglDisable( GL_BLEND );
}
}
//
// check depthmask
//
if ( diff & GLS_DEPTHMASK_TRUE )
{
if ( stateBits & GLS_DEPTHMASK_TRUE )
{
qglDepthMask( GL_TRUE );
}
else
{
qglDepthMask( GL_FALSE );
}
}
//
// fill/line mode
//
if ( diff & GLS_POLYMODE_LINE )
{
if ( stateBits & GLS_POLYMODE_LINE )
{
qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
}
else
{
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
}
//
// depthtest
//
if ( diff & GLS_DEPTHTEST_DISABLE )
{
if ( stateBits & GLS_DEPTHTEST_DISABLE )
{
qglDisable( GL_DEPTH_TEST );
}
else
{
qglEnable( GL_DEPTH_TEST );
}
}
//
// alpha test
//
if ( diff & GLS_ATEST_BITS )
{
switch ( stateBits & GLS_ATEST_BITS )
{
case 0:
qglDisable( GL_ALPHA_TEST );
break;
case GLS_ATEST_GT_0:
qglEnable( GL_ALPHA_TEST );
qglAlphaFunc( GL_GREATER, 0.0f );
break;
case GLS_ATEST_LT_80:
qglEnable( GL_ALPHA_TEST );
qglAlphaFunc( GL_LESS, 0.5f );
break;
case GLS_ATEST_GE_80:
qglEnable( GL_ALPHA_TEST );
qglAlphaFunc( GL_GEQUAL, 0.5f );
break;
default:
assert( 0 );
break;
}
}
glState.glStateBits = stateBits;
}
// a player has predicted a teleport, but hasn't arrived yet
static void RB_Hyperspace()
{
float c = 0.25 + 0.5 * sin( M_PI * (backEnd.refdef.time & 0x01FF) / 0x0200 );
qglClearColor( c, c, c, 1 );
qglClear( GL_COLOR_BUFFER_BIT );
}
static void SetViewportAndScissor()
{
qglMatrixMode(GL_PROJECTION);
qglLoadMatrixf( backEnd.viewParms.projectionMatrix );
qglMatrixMode(GL_MODELVIEW);
// set the window clipping
qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
}
// any mirrored or portaled views have already been drawn
// so prepare to actually render the visible surfaces for this view
static void RB_BeginDrawingView()
{
int clearBits = 0;
// sync with gl if needed
if ( r_finish->integer == 1 && !glState.finishCalled ) {
qglFinish();
glState.finishCalled = qtrue;
}
if ( r_finish->integer == 0 ) {
glState.finishCalled = qtrue;
}
// we will need to change the projection matrix before drawing 2D images again
backEnd.projection2D = qfalse;
backEnd.pc = backEnd.pc3D;
//
// set the modelview matrix for the viewer
//
SetViewportAndScissor();
// ensures that depth writes are enabled for the depth clear
GL_State( GLS_DEFAULT );
// clear relevant buffers
clearBits = GL_DEPTH_BUFFER_BIT;
if ( r_measureOverdraw->integer )
{
clearBits |= GL_STENCIL_BUFFER_BIT;
}
if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) )
{
clearBits |= GL_COLOR_BUFFER_BIT;
// tr.sunLight could have colored fastsky properly for the last 9 years,
// ... if the code had actually been right >:( but, it's a bad idea to trust mappers anyway
qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
}
qglClear( clearBits );
if ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) {
RB_Hyperspace();
return;
}
glState.faceCulling = -1; // force face culling to set next time
// clip to the plane of the portal
if ( backEnd.viewParms.isPortal ) {
float plane[4];
double plane2[4];
plane[0] = backEnd.viewParms.portalPlane.normal[0];
plane[1] = backEnd.viewParms.portalPlane.normal[1];
plane[2] = backEnd.viewParms.portalPlane.normal[2];
plane[3] = backEnd.viewParms.portalPlane.dist;
plane2[0] = DotProduct (backEnd.viewParms.orient.axis[0], plane);
plane2[1] = DotProduct (backEnd.viewParms.orient.axis[1], plane);
plane2[2] = DotProduct (backEnd.viewParms.orient.axis[2], plane);
plane2[3] = DotProduct (plane, backEnd.viewParms.orient.origin) - plane[3];
qglLoadMatrixf( s_flipMatrix );
qglClipPlane (GL_CLIP_PLANE0, plane2);
qglEnable (GL_CLIP_PLANE0);
} else {
qglDisable (GL_CLIP_PLANE0);
}
}
static void RB_RenderDrawSurfList( const drawSurf_t* drawSurfs, int numDrawSurfs )
{
int i;
const shader_t* shader = NULL;
unsigned int sort = (unsigned int)-1;
// save original time for entity shader offsets
double originalTime = backEnd.refdef.floatTime;
// clear the z buffer, set the modelview, etc
RB_BeginDrawingView();
// draw everything
int oldEntityNum = -1;
backEnd.currentEntity = &tr.worldEntity;
qbool oldDepthRange = qfalse;
qbool depthRange = qfalse;
backEnd.pc[RB_SURFACES] += numDrawSurfs;
const drawSurf_t* drawSurf;
for ( i = 0, drawSurf = drawSurfs; i < numDrawSurfs; ++i, ++drawSurf ) {
if ( drawSurf->sort == sort ) {
// fast path, same as previous sort
rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
continue;
}
int fogNum;
const shader_t* shaderPrev = shader;
int entityNum;
R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum );
// "entityMergable" shaders can have surfaces from multiple refentities
// merged into a single batch, like (CONCEPTUALLY) smoke and blood puff sprites
// only legacy code still uses them though, because refents are so heavyweight:
// modern code just billboards in cgame and submits raw polys, all of which are
// ENTITYNUM_WORLD and thus automatically take the "same sort" fast path
if ( !shader->entityMergable || ((sort ^ drawSurf->sort) & ~QSORT_ENTITYNUM_MASK) ) {
if (shaderPrev)
RB_EndSurface();
RB_BeginSurface( shader, fogNum );
}
sort = drawSurf->sort;
//
// change the modelview matrix if needed
//
if ( entityNum != oldEntityNum ) {
depthRange = qfalse;
if ( entityNum != ENTITYNUM_WORLD ) {
backEnd.currentEntity = &backEnd.refdef.entities[entityNum];
if (backEnd.currentEntity->intShaderTime)
backEnd.refdef.floatTime = originalTime - (double)(backEnd.currentEntity->e.shaderTime.iShaderTime) / 1000.0;
else
backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime.fShaderTime;
// we have to reset the shaderTime as well otherwise image animations start
// from the wrong frame
tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
// set up the transformation matrix
R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.orient );
if ( backEnd.currentEntity->e.renderfx & RF_DEPTHHACK ) {
// hack the depth range to prevent view model from poking into walls
depthRange = qtrue;
}
} else {
backEnd.currentEntity = &tr.worldEntity;
backEnd.refdef.floatTime = originalTime;
backEnd.orient = backEnd.viewParms.world;
// we have to reset the shaderTime as well otherwise image animations on
// the world (like water) continue with the wrong frame
tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
}
qglLoadMatrixf( backEnd.orient.modelMatrix );
//
// change depthrange if needed
//
if ( oldDepthRange != depthRange ) {
if ( depthRange ) {
qglDepthRange( 0, 0.3 );
} else {
qglDepthRange( 0, 1 );
}
oldDepthRange = depthRange;
}
oldEntityNum = entityNum;
}
// add the triangles for this surface
rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
}
backEnd.refdef.floatTime = originalTime;
// draw the contents of the last shader batch
if (shader) {
RB_EndSurface();
}
// go back to the world modelview matrix
qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
if ( depthRange ) {
qglDepthRange( 0, 1 );
}
// add light flares on lights that aren't obscured
RB_RenderFlares();
}
static void RB_RenderLitSurfList( dlight_t* dl )
{
const shader_t* shader = NULL;
int entityNum, oldEntityNum;
qbool depthRange, oldDepthRange;
unsigned int sort = (unsigned int)-1;
// save original time for entity shader offsets
double originalTime = backEnd.refdef.floatTime;
//int litsurfs = backEnd.pc[RB_LIT_SURFACES];
// draw everything
oldEntityNum = -1;
backEnd.currentEntity = &tr.worldEntity;
oldDepthRange = qfalse;
depthRange = qfalse;
for ( litSurf_t* litSurf = dl->head; litSurf; litSurf = litSurf->next ) {
++backEnd.pc[RB_LIT_SURFACES];
if ( litSurf->sort == sort ) {
// fast path, same as previous sort
rb_surfaceTable[ *litSurf->surface ]( litSurf->surface );
continue;
}
int fogNum;
const shader_t* shaderPrev = shader;
R_DecomposeSort( litSurf->sort, &entityNum, &shader, &fogNum );
// anything BEFORE opaque is sky/portal, anything AFTER it should never have been added
//assert( shader->sort == SS_OPAQUE );
// !!! but MIRRORS can trip that assert, so just do this for now
if ( shader->sort < SS_OPAQUE )
continue;
if (shaderPrev)
RB_EndSurface();
RB_BeginSurface( shader, fogNum );
sort = litSurf->sort;
//
// change the modelview matrix if needed
//
if ( entityNum != oldEntityNum ) {
depthRange = qfalse;
if ( entityNum != ENTITYNUM_WORLD ) {
backEnd.currentEntity = &backEnd.refdef.entities[entityNum];
if (backEnd.currentEntity->intShaderTime)
backEnd.refdef.floatTime = originalTime - (double)backEnd.currentEntity->e.shaderTime.iShaderTime;
else
backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime.fShaderTime;
// we have to reset the shaderTime as well otherwise image animations start
// from the wrong frame
tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
// set up the transformation matrix
R_RotateForEntity( backEnd.currentEntity, &backEnd.viewParms, &backEnd.orient );
R_TransformDlights( 1, dl, &backEnd.orient );
GL2_SetupDynLight();
if ( backEnd.currentEntity->e.renderfx & RF_DEPTHHACK ) {
// hack the depth range to prevent view model from poking into walls
depthRange = qtrue;
}
} else {
backEnd.currentEntity = &tr.worldEntity;
backEnd.refdef.floatTime = originalTime;
backEnd.orient = backEnd.viewParms.world;
// we have to reset the shaderTime as well otherwise image animations on
// the world (like water) continue with the wrong frame
tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
R_TransformDlights( 1, dl, &backEnd.orient );
GL2_SetupDynLight();
}
qglLoadMatrixf( backEnd.orient.modelMatrix );
//
// change depthrange if needed
//
if ( oldDepthRange != depthRange ) {
if ( depthRange ) {
qglDepthRange( 0, 0.3 );
} else {
qglDepthRange( 0, 1 );
}
oldDepthRange = depthRange;
}
oldEntityNum = entityNum;
}
// add the triangles for this surface
rb_surfaceTable[ *litSurf->surface ]( litSurf->surface );
}
backEnd.refdef.floatTime = originalTime;
// draw the contents of the last shader batch
if (shader) {
RB_EndSurface();
}
// go back to the world modelview matrix
qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
if ( depthRange ) {
qglDepthRange( 0, 1 );
}
/*
if ( r_speeds->integer ) {
ri.Printf( PRINT_ALL, "light %d: %d surfaces\n", dl - backEnd.refdef.dlights, backEnd.pc[RB_LIT_SURFACES] - litsurfs );
}
*/
}
/*
============================================================================
RENDER BACK END THREAD FUNCTIONS
============================================================================
*/
static void RB_SetGL2D()
{
backEnd.projection2D = qtrue;
backEnd.pc = backEnd.pc2D;
// set 2D virtual screen size
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
qglMatrixMode(GL_PROJECTION);
qglLoadIdentity();
qglOrtho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 );
qglMatrixMode(GL_MODELVIEW);
qglLoadIdentity();
GL_State( GLS_DEPTHTEST_DISABLE |
GLS_SRCBLEND_SRC_ALPHA |
GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
qglDisable( GL_CULL_FACE );
qglDisable( GL_CLIP_PLANE0 );
// set time for 2D shaders
backEnd.refdef.time = ri.Milliseconds();
backEnd.refdef.floatTime = (double)backEnd.refdef.time / 1000.0;
}
/*
=============
RE_StretchRaw
FIXME: not exactly backend
Stretches a raw 32 bit power of 2 bitmap image over the given screen rectangle.
Used for cinematics.
=============
*/
void RE_StretchRaw (int x, int y, int w, int h, int cols, int rows, const byte *data, int client, qbool dirty) {
int i, j;
int start, end;
if ( !tr.registered ) {
return;
}
R_SyncRenderThread();
// we definately want to sync every frame for the cinematics
qglFinish();
start = end = 0;
if ( r_speeds->integer ) {
start = ri.Milliseconds();
}
// make sure rows and cols are powers of 2
for ( i = 0 ; ( 1 << i ) < cols ; i++ ) {
}
for ( j = 0 ; ( 1 << j ) < rows ; j++ ) {
}
if ( ( 1 << i ) != cols || ( 1 << j ) != rows) {
ri.Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows);
}
GL_Bind( tr.scratchImage[client] );
// if the scratchImage isn't in the format we want, specify it as a new texture
if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
tr.scratchImage[client]->width = cols;
tr.scratchImage[client]->height = rows;
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
} else {
if (dirty) {
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
// it and don't try and do a texture compression
qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
}
}
if ( r_speeds->integer ) {
end = ri.Milliseconds();
ri.Printf( PRINT_ALL, "qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start );
}
RB_SetGL2D();
qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight );
qglBegin (GL_QUADS);
qglTexCoord2f ( 0.5f / cols, 0.5f / rows );
qglVertex2f (x, y);
qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows );
qglVertex2f (x+w, y);
qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows );
qglVertex2f (x+w, y+h);
qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows );
qglVertex2f (x, y+h);
qglEnd ();
}
void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qbool dirty) {
GL_Bind( tr.scratchImage[client] );
// if the scratchImage isn't in the format we want, specify it as a new texture
if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
tr.scratchImage[client]->width = cols;
tr.scratchImage[client]->height = rows;
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
} else {
if (dirty) {
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
// it and don't try and do a texture compression
qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
}
}
}
static const void* RB_SetColor( const void* data )
{
const setColorCommand_t* cmd = (const setColorCommand_t*)data;
backEnd.color2D[0] = (byte)(cmd->color[0] * 255);
backEnd.color2D[1] = (byte)(cmd->color[1] * 255);
backEnd.color2D[2] = (byte)(cmd->color[2] * 255);
backEnd.color2D[3] = (byte)(cmd->color[3] * 255);
return (const void*)(cmd + 1);
}
static const void* RB_StretchPic( const void* data )
{
const stretchPicCommand_t* cmd = (const stretchPicCommand_t*)data;
if ( !backEnd.projection2D )
RB_SetGL2D();
const shader_t* shader = cmd->shader;
if ( shader != tess.shader ) {
if ( tess.numIndexes ) {
RB_EndSurface();
}
backEnd.currentEntity = &backEnd.entity2D;
RB_BeginSurface( shader, 0 );
}
RB_CHECKOVERFLOW( 4, 6 );
int numVerts = tess.numVertexes;
int numIndexes = tess.numIndexes;
tess.numVertexes += 4;
tess.numIndexes += 6;
tess.indexes[ numIndexes ] = numVerts + 3;
tess.indexes[ numIndexes + 1 ] = numVerts + 0;
tess.indexes[ numIndexes + 2 ] = numVerts + 2;
tess.indexes[ numIndexes + 3 ] = numVerts + 2;
tess.indexes[ numIndexes + 4 ] = numVerts + 0;
tess.indexes[ numIndexes + 5 ] = numVerts + 1;
*(int *)tess.vertexColors[ numVerts ] =
*(int *)tess.vertexColors[ numVerts + 1 ] =
*(int *)tess.vertexColors[ numVerts + 2 ] =
*(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D;
tess.xyz[ numVerts ][0] = cmd->x;
tess.xyz[ numVerts ][1] = cmd->y;
tess.xyz[ numVerts ][2] = 0;
tess.texCoords[ numVerts ][0][0] = cmd->s1;
tess.texCoords[ numVerts ][0][1] = cmd->t1;
tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w;
tess.xyz[ numVerts + 1 ][1] = cmd->y;
tess.xyz[ numVerts + 1 ][2] = 0;
tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2;
tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1;
tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w;
tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h;
tess.xyz[ numVerts + 2 ][2] = 0;
tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2;
tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2;
tess.xyz[ numVerts + 3 ][0] = cmd->x;
tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h;
tess.xyz[ numVerts + 3 ][2] = 0;
tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1;
tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2;
return (const void*)(cmd + 1);
}
static const void* RB_Triangle( const void* data )
{
const triangleCommand_t* cmd = (const triangleCommand_t*)data;
if ( !backEnd.projection2D )
RB_SetGL2D();
const shader_t* shader = cmd->shader;
if ( shader != tess.shader ) {
if ( tess.numIndexes ) {
RB_EndSurface();
}
backEnd.currentEntity = &backEnd.entity2D;
RB_BeginSurface( shader, 0 );
}
RB_CHECKOVERFLOW( 3, 3 );
int numVerts = tess.numVertexes;
int numIndexes = tess.numIndexes;
tess.numVertexes += 3;
tess.numIndexes += 3;
tess.indexes[ numIndexes + 0 ] = numVerts + 0;
tess.indexes[ numIndexes + 1 ] = numVerts + 1;
tess.indexes[ numIndexes + 2 ] = numVerts + 2;
*(int *)tess.vertexColors[ numVerts ] =
*(int *)tess.vertexColors[ numVerts + 1 ] =
*(int *)tess.vertexColors[ numVerts + 2 ] = *(int *)backEnd.color2D;
tess.xyz[ numVerts ][0] = cmd->x0;
tess.xyz[ numVerts ][1] = cmd->y0;
tess.xyz[ numVerts ][2] = 0;
tess.texCoords[ numVerts ][0][0] = cmd->s0;
tess.texCoords[ numVerts ][0][1] = cmd->t0;
tess.xyz[ numVerts + 1 ][0] = cmd->x1;
tess.xyz[ numVerts + 1 ][1] = cmd->y1;
tess.xyz[ numVerts + 1 ][2] = 0;
tess.texCoords[ numVerts + 1 ][0][0] = cmd->s1;
tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1;
tess.xyz[ numVerts + 2 ][0] = cmd->x2;
tess.xyz[ numVerts + 2 ][1] = cmd->y2;
tess.xyz[ numVerts + 2 ][2] = 0;
tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2;
tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2;
return (const void*)(cmd + 1);
}
static const void* RB_DrawSurfs( const void* data )
{
const drawSurfsCommand_t* cmd = (const drawSurfsCommand_t*)data;
// finish any 2D drawing if needed
if ( tess.numIndexes )
RB_EndSurface();
backEnd.refdef = cmd->refdef;
backEnd.viewParms = cmd->viewParms;
tess.pass = shaderCommands_t::TP_BASE;
RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
tess.pass = shaderCommands_t::TP_LIGHT;
if ( r_measureOverdraw->integer == 2 ) {
// see exactly how many fragments are touched by lighting
qglClear( GL_STENCIL_BUFFER_BIT );
qglStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
}
for ( int i = 0; i < backEnd.refdef.num_dlights; ++i ) {
dlight_t* dl = &backEnd.refdef.dlights[i];
if (!dl->head)
continue;
tess.light = dl;
RB_RenderLitSurfList( dl );
}
tess.pass = shaderCommands_t::TP_BASE;
return (const void*)(cmd + 1);
}
static const void* RB_BeginFrame( const void* data )
{
const beginFrameCommand_t* cmd = (const beginFrameCommand_t*)data;
GL2_BeginFrame();
if ( r_clear->integer )
qglClearColor( 1.0f, 0.0f, 0.5f, 1.0f );
else
qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
return (const void*)(cmd + 1);
}
/*
draw all the images to the screen, on top of whatever was there
this was used to test for texture thrashing in 1999, but is useless now
also called by RE_EndRegistration to touch them for residency
*/
void RB_ShowImages()
{
if ( !backEnd.projection2D )
RB_SetGL2D();
qglClear( GL_COLOR_BUFFER_BIT );
qglFinish();
int start = ri.Milliseconds();
for (int i = 0; i < tr.numImages; ++i) {
const image_t* image = tr.images[i];
float w = glConfig.vidWidth / 20;
float h = glConfig.vidHeight / 15;
float x = i % 20 * w;
float y = i / 20 * h;
// show in proportional size in mode 2
if ( r_showImages->integer == 2 ) {
w *= image->width / 512.0f;
h *= image->height / 512.0f;
}
GL_Bind( image );
qglBegin( GL_QUADS );
qglTexCoord2f( 0, 0 );
qglVertex2f( x, y );
qglTexCoord2f( 1, 0 );
qglVertex2f( x + w, y );
qglTexCoord2f( 1, 1 );
qglVertex2f( x + w, y + h );
qglTexCoord2f( 0, 1 );
qglVertex2f( x, y + h );
qglEnd();
}
qglFinish();
ri.Printf( PRINT_ALL, "%i msec to draw all images\n", ri.Milliseconds() - start );
}
static const void* RB_SwapBuffers( const void* data )
{
// finish any 2D drawing if needed
if ( tess.numIndexes ) {
RB_EndSurface();
}
// texture swapping test
if ( r_showImages->integer ) {
RB_ShowImages();
}
const swapBuffersCommand_t* cmd = (const swapBuffersCommand_t*)data;
// we measure overdraw by reading back the stencil buffer and
// counting up the number of increments that have happened
if ( r_measureOverdraw->integer ) {
RI_AutoPtr stencilReadback( glConfig.vidWidth * glConfig.vidHeight );
qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback );
/*
long sum = 0;
for (int i = 0; i < glConfig.vidWidth * glConfig.vidHeight; ++i)
sum += stencilReadback[i];
backEnd.pc.c_overDraw += sum;
*/
// convert the stencil data into human-comprehensible colors
RI_AutoPtr stencilRGBA( glConfig.vidWidth * glConfig.vidHeight * 4 );
for (int i = 0; i < glConfig.vidWidth * glConfig.vidHeight; ++i) {
unsigned* p = stencilRGBA.Get<unsigned>() + i;
switch ( stencilReadback[i] ) {
default: *p = 0xFFFFFFFF; break;
case 0: *p = LittleLong( 0xFF000000 ); break;
case 1: *p = LittleLong( 0xFF0000FF ); break;
case 2: *p = LittleLong( 0xFF00FF00 ); break;
case 3: *p = LittleLong( 0xFF00FFFF ); break;
case 4: *p = LittleLong( 0xFFFF0000 ); break;
case 5: *p = LittleLong( 0xFFFF00FF ); break;
case 6: *p = LittleLong( 0xFFFFFF00 ); break;
}
}
RB_SetGL2D();
qglDisable( GL_TEXTURE_2D );
qglRasterPos3f( 0, glConfig.vidHeight, 0 );
qglDrawPixels( glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, stencilRGBA );
qglEnable( GL_TEXTURE_2D );
}
if ( !backEnd.projection2D )
RB_SetGL2D();
GL2_EndFrame();
if ( !glState.finishCalled ) {
qglFinish();
}
Sys_GL_EndFrame();
backEnd.projection2D = qfalse;
backEnd.pc = backEnd.pc3D;
return (const void*)(cmd + 1);
}
/*
====================
RB_ExecuteRenderCommands
This function will always be called synchronously.
====================
*/
void RB_ExecuteRenderCommands( const void *data )
{
int ms = ri.Milliseconds();
while ( 1 ) {
data = PADP(data, sizeof(void *));
switch ( *(const int *)data ) {
case RC_SET_COLOR:
data = RB_SetColor( data );
break;
case RC_STRETCH_PIC:
data = RB_StretchPic( data );
break;
case RC_TRIANGLE:
data = RB_Triangle( data );
break;
case RC_DRAW_SURFS:
data = RB_DrawSurfs( data );
break;
case RC_BEGIN_FRAME:
data = RB_BeginFrame( data );
break;
case RC_SWAP_BUFFERS:
data = RB_SwapBuffers( data );
break;
case RC_SCREENSHOT:
data = RB_TakeScreenshotCmd( (const screenshotCommand_t*)data );
break;
case RC_VIDEOFRAME:
data = RB_TakeVideoFrameCmd( data );
break;
case RC_END_OF_LIST:
default:
// stop rendering on this thread
// we can't really "charge" 2D/3D properly, so it all counts as 3D
backEnd.pc3D[RB_MSEC] = ri.Milliseconds() - ms;
return;
}
}
}
void* RB_FindRenderCommand( renderCommand_t type )
{
renderCommandList_t* cmdList = &backEndData->commands;
void* data = cmdList->cmds;
void* end = cmdList->cmds + cmdList->used;
while ( 1 ) {
data = PADP(data, sizeof(void *));
if( *(int *)data == type )
return data;
if ( data >= end )
return NULL;
switch ( *(const int *)data ) {
case RC_SET_COLOR:
data = (char*)data + sizeof(setColorCommand_t);
break;
case RC_STRETCH_PIC:
data = (char*)data + sizeof(stretchPicCommand_t);
break;
case RC_TRIANGLE:
data = (char*)data + sizeof(triangleCommand_t);
break;
case RC_DRAW_SURFS:
data = (char*)data + sizeof(drawSurfsCommand_t);
break;
case RC_BEGIN_FRAME:
data = (char*)data + sizeof(beginFrameCommand_t);
break;
case RC_SWAP_BUFFERS:
data = (char*)data + sizeof(swapBuffersCommand_t);
break;
case RC_SCREENSHOT:
data = (char*)data + sizeof(screenshotCommand_t);
break;
case RC_VIDEOFRAME:
data = (char*)data + sizeof(videoFrameCommand_t);
break;
case RC_END_OF_LIST:
default:
return NULL;
}
}
}
void RB_RemoveRenderCommand( void* cmd, int cmdSize )
{
renderCommandList_t* cmdList = &backEndData->commands;
const int endOffset = ((char*)cmd + cmdSize) - (char*)cmdList->cmds;
const int endBytes = cmdList->used - endOffset;
assert( cmd >= cmdList->cmds && ((char*)cmd + cmdSize) <= ((char*)cmdList->cmds + cmdList->used) );
memmove( cmd, (char*)cmd + cmdSize, endBytes );
cmdList->used -= cmdSize;
}