mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-13 07:48:18 +00:00
9ea5d021d5
implemented the dynamic lights and integer shader time extensions
1134 lines
30 KiB
C++
1134 lines
30 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[SMP_FRAMES];
|
|
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:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
break;
|
|
case GL_REPLACE:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
|
|
break;
|
|
case GL_DECAL:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
|
|
break;
|
|
case GL_ADD:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );
|
|
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_DynLights_SetupLight();
|
|
|
|
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_DynLights_SetupLight();
|
|
}
|
|
|
|
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_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();
|
|
|
|
// clear screen for debugging
|
|
if ( r_clear->integer ) {
|
|
qglClearColor( 1, 0, 0.5, 1 );
|
|
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();
|
|
}
|
|
|
|
GLimp_EndFrame();
|
|
|
|
backEnd.projection2D = qfalse;
|
|
backEnd.pc = backEnd.pc3D;
|
|
|
|
return (const void*)(cmd + 1);
|
|
}
|
|
|
|
|
|
/*
|
|
====================
|
|
RB_ExecuteRenderCommands
|
|
|
|
This function will be called synchronously if running without
|
|
smp extensions, or asynchronously by another thread.
|
|
====================
|
|
*/
|
|
void RB_ExecuteRenderCommands( const void *data )
|
|
{
|
|
int ms = ri.Milliseconds();
|
|
|
|
#ifdef USE_R_SMP
|
|
if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) {
|
|
backEnd.smpFrame = 0;
|
|
} else {
|
|
backEnd.smpFrame = 1;
|
|
}
|
|
#else
|
|
backEnd.smpFrame = 0;
|
|
#endif
|
|
|
|
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_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;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
RB_RenderThread
|
|
================
|
|
*/
|
|
void RB_RenderThread( void ) {
|
|
const void *data;
|
|
|
|
// wait for either a rendering command or a quit command
|
|
while ( 1 ) {
|
|
// sleep until we have work to do
|
|
data = GLimp_RendererSleep();
|
|
|
|
if ( !data ) {
|
|
return; // all done, renderer is shutting down
|
|
}
|
|
|
|
renderThreadActive = qtrue;
|
|
|
|
RB_ExecuteRenderCommands( data );
|
|
|
|
renderThreadActive = qfalse;
|
|
}
|
|
}
|
|
|