q3rally/engine/code/rend2/tr_backend.c

1889 lines
46 KiB
C
Raw Normal View History

ioquake3 resync to revision 2369 from 2317. Some revision messages: Cache servers for each master server in q3_ui, otherwise servers from last updated master for shown for all Internet# sources. Play correct team sounds when in spectator mode and following a player. Check last listener number instead of clc.clientNum in S_AL_HearingThroughEntity so sound work correctly when spectate following a client. (Related to bug 5741.) When in third person, don't play player's sounds as full volume in Base sound system. OpenAL already does this. (Related to bug 5741.) really fix the confusion with game entity and refentity numbers to further reduce confusion, rename constants like MAX_ENTITIES to MAX_REFENTITIES Added Rend2, an alternate renderer. (Bug #4358) Fix restoring fs_game when default.cfg is missing. Fix restoring old fs_game upon leaving a server. Patch by Ensiform. Change more operator commands to require sv_running to be usable. Patch by Ensiform. Fix some "> MAX_*" to be ">= MAX_*". Fix follow command to find clients whose name begins with a number. Fix up "gc" command, make it more like "tell". Based on patch by Ensiform. Add usage messages for gc, tell, vtell, and votell commands. Check player names in gc, tell, vtell, and votell commands. #5799 - Change messagemode text box to display colors like in console input box. Improve "play" command, based on a patch from Ensiform. Check for invalid filename in OpenAL's RegisterSound function. Changed Base sound system to warn not error when sound filename is empty or too long. Remove references to non-existent functions CM_MarkFragments and CM_LerpTag.
2012-12-06 07:07:19 +00:00
/*
===========================================================================
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;
static float s_flipMatrix[16] = {
// convert from our coordinate system (looking down X)
// to OpenGL's coordinate system (looking down -Z)
0, 0, -1, 0,
-1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 0, 1
};
/*
** GL_Bind2
*/
void GL_Bind2( image_t *image, GLenum type ) {
int texnum;
if ( !image ) {
ri.Printf( PRINT_WARNING, "GL_Bind2: NULL image\n" );
texnum = tr.defaultImage->texnum;
} else {
texnum = image->texnum;
}
if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option
texnum = tr.dlightImage->texnum;
}
if ( glState.currenttextures[glState.currenttmu] != texnum ) {
image->frameUsed = tr.frameCount;
glState.currenttextures[glState.currenttmu] = texnum;
qglBindTexture (type, texnum);
}
}
/*
** GL_Bind2
*/
void GL_Bind( image_t *image )
{
GL_Bind2( image, GL_TEXTURE_2D );
}
/*
** GL_BindCubemap
*/
void GL_BindCubemap( image_t *image )
{
GL_Bind2( image, GL_TEXTURE_CUBE_MAP );
}
/*
** GL_SelectTexture
*/
void GL_SelectTexture( int unit )
{
if ( glState.currenttmu == unit )
{
return;
}
if (!(unit >= 0 && unit <= 31))
ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit );
qglActiveTextureARB( GL_TEXTURE0_ARB + unit );
glState.currenttmu = unit;
}
/*
** GL_BindMultitexture
*/
void GL_BindMultitexture( image_t *image0, GLuint env0, image_t *image1, GLuint env1 ) {
int texnum0, texnum1;
texnum0 = image0->texnum;
texnum1 = image1->texnum;
if ( r_nobind->integer && tr.dlightImage ) { // performance evaluation option
texnum0 = texnum1 = tr.dlightImage->texnum;
}
if ( glState.currenttextures[1] != texnum1 ) {
GL_SelectTexture( 1 );
image1->frameUsed = tr.frameCount;
glState.currenttextures[1] = texnum1;
qglBindTexture( GL_TEXTURE_2D, texnum1 );
}
if ( glState.currenttextures[0] != texnum0 ) {
GL_SelectTexture( 0 );
image0->frameUsed = tr.frameCount;
glState.currenttextures[0] = texnum0;
qglBindTexture( GL_TEXTURE_2D, texnum0 );
}
}
/*
** GL_BindToTMU
*/
void GL_BindToTMU( image_t *image, int tmu )
{
int texnum;
int oldtmu = glState.currenttmu;
if (!image)
texnum = 0;
else
texnum = image->texnum;
if ( glState.currenttextures[tmu] != texnum ) {
GL_SelectTexture( tmu );
if (image)
image->frameUsed = tr.frameCount;
glState.currenttextures[tmu] = texnum;
qglBindTexture( GL_TEXTURE_2D, texnum );
GL_SelectTexture( oldtmu );
}
}
/*
** GL_Cull
*/
void GL_Cull( int cullType ) {
#ifdef REACTION
// Makro - flip culling if needed
qboolean flip = (backEnd.currentEntity != NULL && backEnd.currentEntity->mirrored != qfalse && cullType != CT_TWO_SIDED);
cullType ^= flip; // this assumes CT_BACK_SIDED and CT_FRONT_SIDED are 0 or 1
#endif
if ( glState.faceCulling == cullType ) {
return;
}
glState.faceCulling = cullType;
if ( cullType == CT_TWO_SIDED )
{
qglDisable( GL_CULL_FACE );
}
else
{
qboolean cullFront;
qglEnable( GL_CULL_FACE );
cullFront = (cullType == CT_FRONT_SIDED);
if ( backEnd.viewParms.isMirror )
{
cullFront = !cullFront;
}
qglCullFace( cullFront ? GL_FRONT : GL_BACK );
}
}
/*
** 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", 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_BITS )
{
if ( stateBits & GLS_DEPTHFUNC_EQUAL )
{
qglDepthFunc( GL_EQUAL );
}
else if ( stateBits & GLS_DEPTHFUNC_GREATER)
{
qglDepthFunc( GL_GREATER );
}
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" );
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" );
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;
}
void GL_SetProjectionMatrix(matrix_t matrix)
{
Matrix16Copy(matrix, glState.projection);
Matrix16Multiply(glState.projection, glState.modelview, glState.modelviewProjection);
}
void GL_SetModelviewMatrix(matrix_t matrix)
{
Matrix16Copy(matrix, glState.modelview);
Matrix16Multiply(glState.projection, glState.modelview, glState.modelviewProjection);
}
/*
================
RB_Hyperspace
A player has predicted a teleport, but hasn't arrived yet
================
*/
static void RB_Hyperspace( void ) {
float c;
if ( !backEnd.isHyperspace ) {
// do initialization shit
}
c = ( backEnd.refdef.time & 255 ) / 255.0f;
qglClearColor( c, c, c, 1 );
qglClear( GL_COLOR_BUFFER_BIT );
backEnd.isHyperspace = qtrue;
}
static void SetViewportAndScissor( void ) {
GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
// 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 );
}
/*
=================
RB_BeginDrawingView
Any mirrored or portaled views have already been drawn, so prepare
to actually render the visible surfaces for this view
=================
*/
void RB_BeginDrawingView (void) {
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;
if (glRefConfig.framebufferObject)
{
// FIXME: HUGE HACK: render to the screen fbo if we've already postprocessed the frame and aren't drawing more world
// drawing more world check is in case of double renders, such as skyportals
if (backEnd.viewParms.targetFbo == NULL)
{
if (!tr.renderFbo || (backEnd.framePostProcessed && (backEnd.refdef.rdflags & RDF_NOWORLDMODEL)))
{
FBO_Bind(tr.screenScratchFbo);
}
else
{
FBO_Bind(tr.renderFbo);
}
}
else
{
FBO_Bind(backEnd.viewParms.targetFbo);
}
}
//
// 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 || r_shadows->integer == 2 )
{
clearBits |= GL_STENCIL_BUFFER_BIT;
}
if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) )
{
clearBits |= GL_COLOR_BUFFER_BIT; // FIXME: only if sky shaders have been used
#ifdef _DEBUG
qglClearColor( 0.8f, 0.7f, 0.4f, 1.0f ); // FIXME: get color of sky
#else
qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); // FIXME: get color of sky
#endif
}
// clear to white for shadow maps
if (backEnd.viewParms.flags & VPF_SHADOWMAP)
{
clearBits |= GL_COLOR_BUFFER_BIT;
qglClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
}
qglClear( clearBits );
if ( ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) )
{
RB_Hyperspace();
return;
}
else
{
backEnd.isHyperspace = qfalse;
}
glState.faceCulling = -1; // force face culling to set next time
// we will only draw a sun if there was sky rendered in this view
backEnd.skyRenderedThisView = qfalse;
#ifdef REACTION
backEnd.viewHasSunFlare = qfalse;
#endif
// clip to the plane of the portal
if ( backEnd.viewParms.isPortal ) {
#if 0
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.or.axis[0], plane);
plane2[1] = DotProduct (backEnd.viewParms.or.axis[1], plane);
plane2[2] = DotProduct (backEnd.viewParms.or.axis[2], plane);
plane2[3] = DotProduct (plane, backEnd.viewParms.or.origin) - plane[3];
#endif
GL_SetModelviewMatrix( s_flipMatrix );
}
}
#define MAC_EVENT_PUMP_MSEC 5
/*
==================
RB_RenderDrawSurfList
==================
*/
void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
shader_t *shader, *oldShader;
int fogNum, oldFogNum;
int entityNum, oldEntityNum;
int dlighted, oldDlighted;
int pshadowed, oldPshadowed;
qboolean depthRange, oldDepthRange, isCrosshair, wasCrosshair;
int i;
drawSurf_t *drawSurf;
int oldSort;
float originalTime;
FBO_t* fbo = NULL;
qboolean inQuery = qfalse;
#if 1 //def REACTION
float depth[2];
#endif
// save original time for entity shader offsets
originalTime = backEnd.refdef.floatTime;
fbo = glState.currentFBO;
// draw everything
oldEntityNum = -1;
backEnd.currentEntity = &tr.worldEntity;
oldShader = NULL;
oldFogNum = -1;
oldDepthRange = qfalse;
wasCrosshair = qfalse;
oldDlighted = qfalse;
oldPshadowed = qfalse;
oldSort = -1;
depthRange = qfalse;
#if 1 //def REACTION
depth[0] = 0.f;
depth[1] = 1.f;
#endif
backEnd.pc.c_surfaces += numDrawSurfs;
for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++) {
if ( drawSurf->sort == oldSort ) {
if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
continue;
// fast path, same as previous sort
rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
continue;
}
oldSort = drawSurf->sort;
R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted, &pshadowed );
//
// change the tess parameters if needed
// a "entityMergable" shader is a shader that can have surfaces from seperate
// entities merged into a single batch, like smoke and blood puff sprites
if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted || pshadowed != oldPshadowed
|| ( entityNum != oldEntityNum && !shader->entityMergable ) ) {
if (oldShader != NULL) {
RB_EndSurface();
}
RB_BeginSurface( shader, fogNum );
backEnd.pc.c_surfBatches++;
oldShader = shader;
oldFogNum = fogNum;
oldDlighted = dlighted;
oldPshadowed = pshadowed;
}
if (backEnd.depthFill && shader && shader->sort != SS_OPAQUE)
continue;
//
// change the modelview matrix if needed
//
if ( entityNum != oldEntityNum ) {
qboolean sunflare = qfalse;
depthRange = isCrosshair = qfalse;
#ifdef REACTION
// if we were rendering to a FBO and the previous entity was a sunflare
// and the current one isn't, switch back to the main fbo
if (oldEntityNum != -1 && fbo && !backEnd.depthFill &&
RF_SUNFLARE == (backEnd.refdef.entities[oldEntityNum].e.renderfx & RF_SUNFLARE) &&
0 == (backEnd.refdef.entities[entityNum].e.renderfx & RF_SUNFLARE))
{
if (inQuery) {
inQuery = qfalse;
qglEndQueryARB(GL_SAMPLES_PASSED_ARB);
}
FBO_Bind(fbo);
qglDepthRange(depth[0], depth[1]);
}
#endif
if ( entityNum != REFENTITYNUM_WORLD ) {
backEnd.currentEntity = &backEnd.refdef.entities[entityNum];
backEnd.refdef.floatTime = originalTime - backEnd.currentEntity->e.shaderTime;
// 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.or );
// set up the dynamic lighting if needed
if ( backEnd.currentEntity->needDlights ) {
R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or );
}
#ifdef REACTION
// if the current entity is a sunflare
if(backEnd.currentEntity->e.renderfx & RF_SUNFLARE && !backEnd.depthFill) {
// if we're rendering to a fbo
if (fbo) {
VectorCopy(backEnd.currentEntity->e.origin, backEnd.sunFlarePos);
// switch FBO
FBO_Bind(tr.godRaysFbo);
qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
qglClear( GL_COLOR_BUFFER_BIT );
qglDepthRange(1.f, 1.f);
if (glRefConfig.occlusionQuery && !inQuery && !backEnd.viewHasSunFlare) {
inQuery = qtrue;
tr.sunFlareQueryActive[tr.sunFlareQueryIndex] = qtrue;
qglBeginQueryARB(GL_SAMPLES_PASSED_ARB, tr.sunFlareQuery[tr.sunFlareQueryIndex]);
}
sunflare = qtrue;
} else {
depthRange = qtrue;
}
}
#endif
if(backEnd.currentEntity->e.renderfx & RF_DEPTHHACK)
{
// hack the depth range to prevent view model from poking into walls
depthRange = qtrue;
if(backEnd.currentEntity->e.renderfx & RF_CROSSHAIR)
isCrosshair = qtrue;
}
} else {
backEnd.currentEntity = &tr.worldEntity;
backEnd.refdef.floatTime = originalTime;
backEnd.or = 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( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.or );
}
GL_SetModelviewMatrix( backEnd.or.modelMatrix );
//
// change depthrange. Also change projection matrix so first person weapon does not look like coming
// out of the screen.
//
if (oldDepthRange != depthRange || wasCrosshair != isCrosshair)
{
if (depthRange)
{
if(backEnd.viewParms.stereoFrame != STEREO_CENTER)
{
if(isCrosshair)
{
if(oldDepthRange)
{
// was not a crosshair but now is, change back proj matrix
GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
}
}
else
{
viewParms_t temp = backEnd.viewParms;
R_SetupProjection(&temp, r_znear->value, 0, qfalse);
GL_SetProjectionMatrix( temp.projectionMatrix );
}
}
#if 1 //def REACTION
if(!oldDepthRange)
{
depth[0] = 0;
depth[1] = 0.3f;
qglDepthRange (depth[0], depth[1]);
}
#endif
}
else
{
if(!wasCrosshair && backEnd.viewParms.stereoFrame != STEREO_CENTER)
{
GL_SetProjectionMatrix( backEnd.viewParms.projectionMatrix );
}
if (!sunflare)
qglDepthRange (0, 1);
#if 1 //def REACTION
depth[0] = 0;
depth[1] = 1;
#endif
}
oldDepthRange = depthRange;
wasCrosshair = isCrosshair;
}
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 (oldShader != NULL) {
RB_EndSurface();
}
if (inQuery) {
inQuery = qfalse;
qglEndQueryARB(GL_SAMPLES_PASSED_ARB);
}
#ifdef REACTION
// HACK: flip Z and render black to god rays buffer
if (backEnd.frameHasSunFlare && !backEnd.depthFill)
{
vec4_t black;
VectorSet4(black, 0, 0, 0, 1);
qglDepthRange (1, 1);
FBO_BlitFromTexture(tr.whiteImage, NULL, NULL, tr.godRaysFbo, NULL, NULL, black, GLS_DEPTHFUNC_GREATER);
}
#endif
if (glRefConfig.framebufferObject)
FBO_Bind(fbo);
// go back to the world modelview matrix
GL_SetModelviewMatrix( backEnd.viewParms.world.modelMatrix );
//if ( depthRange ) {
qglDepthRange (0, 1);
//}
}
/*
============================================================================
RENDER BACK END THREAD FUNCTIONS
============================================================================
*/
/*
================
RB_SetGL2D
================
*/
void RB_SetGL2D (void) {
matrix_t matrix;
int width, height;
if (backEnd.projection2D && backEnd.last2DFBO == glState.currentFBO)
return;
backEnd.projection2D = qtrue;
backEnd.last2DFBO = glState.currentFBO;
if (glState.currentFBO)
{
width = glState.currentFBO->width;
height = glState.currentFBO->height;
}
else
{
width = glConfig.vidWidth;
height = glConfig.vidHeight;
}
// set 2D virtual screen size
qglViewport( 0, 0, width, height );
qglScissor( 0, 0, width, height );
Matrix16Ortho(0, width, height, 0, 0, 1, matrix);
GL_SetProjectionMatrix(matrix);
Matrix16Identity(matrix);
GL_SetModelviewMatrix(matrix);
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 = backEnd.refdef.time * 0.001f;
// reset color scaling
backEnd.refdef.colorScale = 1.0f;
}
/*
=============
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, qboolean dirty) {
int i, j;
int start, end;
shaderProgram_t *sp = &tr.textureColorShader;
vec4_t color;
if ( !tr.registered ) {
return;
}
R_SyncRenderThread();
// we definately want to sync every frame for the cinematics
qglFinish();
start = 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 = tr.scratchImage[client]->uploadWidth = cols;
tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = 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_TO_EDGE );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
} 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 );
}
// FIXME: HUGE hack
if (glRefConfig.framebufferObject)
{
if (!tr.renderFbo || backEnd.framePostProcessed)
{
FBO_Bind(tr.screenScratchFbo);
}
else
{
FBO_Bind(tr.renderFbo);
}
}
RB_SetGL2D();
tess.numIndexes = 0;
tess.numVertexes = 0;
tess.firstIndex = 0;
tess.minIndex = 0;
tess.maxIndex = 0;
tess.xyz[tess.numVertexes][0] = x;
tess.xyz[tess.numVertexes][1] = y;
tess.xyz[tess.numVertexes][2] = 0;
tess.xyz[tess.numVertexes][3] = 1;
tess.texCoords[tess.numVertexes][0][0] = 0.5f / cols;
tess.texCoords[tess.numVertexes][0][1] = 0.5f / rows;
tess.texCoords[tess.numVertexes][1][0] = 0;
tess.texCoords[tess.numVertexes][1][1] = 1;
tess.numVertexes++;
tess.xyz[tess.numVertexes][0] = x + w;
tess.xyz[tess.numVertexes][1] = y;
tess.xyz[tess.numVertexes][2] = 0;
tess.xyz[tess.numVertexes][3] = 1;
tess.texCoords[tess.numVertexes][0][0] = (cols - 0.5f) / cols;
tess.texCoords[tess.numVertexes][0][1] = 0.5f / rows;
tess.texCoords[tess.numVertexes][1][0] = 0;
tess.texCoords[tess.numVertexes][1][1] = 1;
tess.numVertexes++;
tess.xyz[tess.numVertexes][0] = x + w;
tess.xyz[tess.numVertexes][1] = y + h;
tess.xyz[tess.numVertexes][2] = 0;
tess.xyz[tess.numVertexes][3] = 1;
tess.texCoords[tess.numVertexes][0][0] = (cols - 0.5f) / cols;
tess.texCoords[tess.numVertexes][0][1] = (rows - 0.5f) / rows;
tess.texCoords[tess.numVertexes][1][0] = 0;
tess.texCoords[tess.numVertexes][1][1] = 1;
tess.numVertexes++;
tess.xyz[tess.numVertexes][0] = x;
tess.xyz[tess.numVertexes][1] = y + h;
tess.xyz[tess.numVertexes][2] = 0;
tess.xyz[tess.numVertexes][3] = 1;
tess.texCoords[tess.numVertexes][0][0] = 0.5f / cols;
tess.texCoords[tess.numVertexes][0][1] = (rows - 0.5f) / rows;
tess.texCoords[tess.numVertexes][1][0] = 0;
tess.texCoords[tess.numVertexes][1][1] = 1;
tess.numVertexes++;
tess.indexes[tess.numIndexes++] = 0;
tess.indexes[tess.numIndexes++] = 1;
tess.indexes[tess.numIndexes++] = 2;
tess.indexes[tess.numIndexes++] = 0;
tess.indexes[tess.numIndexes++] = 2;
tess.indexes[tess.numIndexes++] = 3;
tess.minIndex = 0;
tess.maxIndex = 3;
// FIXME: A lot of this can probably be removed for speed, and refactored into a more convenient function
RB_UpdateVBOs(ATTR_POSITION | ATTR_TEXCOORD);
sp = &tr.textureColorShader;
GLSL_VertexAttribsState(ATTR_POSITION | ATTR_TEXCOORD);
GLSL_BindProgram(sp);
GLSL_SetUniformMatrix16(sp, TEXTURECOLOR_UNIFORM_MODELVIEWPROJECTIONMATRIX, glState.modelviewProjection);
VectorSet4(color, 1, 1, 1, 1);
GLSL_SetUniformVec4(sp, TEXTURECOLOR_UNIFORM_COLOR, color);
R_DrawElementsVBO(tess.numIndexes, tess.firstIndex, tess.minIndex, tess.maxIndex);
//R_BindNullVBO();
//R_BindNullIBO();
tess.numIndexes = 0;
tess.numVertexes = 0;
tess.firstIndex = 0;
tess.minIndex = 0;
tess.maxIndex = 0;
}
void RE_UploadCinematic (int w, int h, int cols, int rows, const byte *data, int client, qboolean 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 = tr.scratchImage[client]->uploadWidth = cols;
tr.scratchImage[client]->height = tr.scratchImage[client]->uploadHeight = 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_TO_EDGE );
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
} 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 );
}
}
}
/*
=============
RB_SetColor
=============
*/
const void *RB_SetColor( const void *data ) {
const setColorCommand_t *cmd;
cmd = (const setColorCommand_t *)data;
backEnd.color2D[0] = cmd->color[0] * 255;
backEnd.color2D[1] = cmd->color[1] * 255;
backEnd.color2D[2] = cmd->color[2] * 255;
backEnd.color2D[3] = cmd->color[3] * 255;
return (const void *)(cmd + 1);
}
/*
=============
RB_StretchPic
=============
*/
const void *RB_StretchPic ( const void *data ) {
const stretchPicCommand_t *cmd;
shader_t *shader;
int numVerts, numIndexes;
cmd = (const stretchPicCommand_t *)data;
// FIXME: HUGE hack
if (glRefConfig.framebufferObject)
{
if (!tr.renderFbo || backEnd.framePostProcessed)
{
FBO_Bind(tr.screenScratchFbo);
}
else
{
FBO_Bind(tr.renderFbo);
}
}
RB_SetGL2D();
shader = cmd->shader;
if ( shader != tess.shader ) {
if ( tess.numIndexes ) {
RB_EndSurface();
}
backEnd.currentEntity = &backEnd.entity2D;
RB_BeginSurface( shader, 0 );
}
RB_CHECKOVERFLOW( 4, 6 );
numVerts = tess.numVertexes;
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;
{
vec4_t color;
VectorScale4(backEnd.color2D, 1.0f / 255.0f, color);
VectorCopy4(color, tess.vertexColors[ numVerts ]);
VectorCopy4(color, tess.vertexColors[ numVerts + 1]);
VectorCopy4(color, tess.vertexColors[ numVerts + 2]);
VectorCopy4(color, tess.vertexColors[ numVerts + 3 ]);
}
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);
}
/*
=============
RB_DrawSurfs
=============
*/
const void *RB_DrawSurfs( const void *data ) {
const drawSurfsCommand_t *cmd;
// finish any 2D drawing if needed
if ( tess.numIndexes ) {
RB_EndSurface();
}
cmd = (const drawSurfsCommand_t *)data;
backEnd.refdef = cmd->refdef;
backEnd.viewParms = cmd->viewParms;
// clear the z buffer, set the modelview, etc
RB_BeginDrawingView ();
if (glRefConfig.framebufferObject && (backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp)
{
qglEnable(GL_DEPTH_CLAMP);
}
if (glRefConfig.framebufferObject && !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && (r_depthPrepass->integer || (backEnd.viewParms.flags & VPF_DEPTHSHADOW)))
{
FBO_t *oldFbo = glState.currentFBO;
backEnd.depthFill = qtrue;
qglColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]);
backEnd.depthFill = qfalse;
if (tr.msaaResolveFbo)
{
// If we're using multisampling, resolve the depth first
FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
}
else if (tr.renderFbo == NULL)
{
// If we're rendering directly to the screen, copy the depth to a texture
GL_BindToTMU(tr.renderDepthImage, 0);
qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 0, 0, glConfig.vidWidth, glConfig.vidHeight, 0);
}
if (r_ssao->integer)
{
// need the depth in a texture we can do GL_LINEAR sampling on, so copy it to an HDR image
FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, tr.hdrDepthFbo, NULL, NULL, NULL, 0);
}
if (backEnd.viewParms.flags & VPF_USESUNLIGHT)
{
vec4_t quadVerts[4];
vec2_t texCoords[4];
FBO_Bind(tr.screenShadowFbo);
qglViewport(0, 0, tr.screenShadowFbo->width, tr.screenShadowFbo->height);
qglScissor(0, 0, tr.screenShadowFbo->width, tr.screenShadowFbo->height);
VectorSet4(quadVerts[0], -1, 1, 0, 1);
VectorSet4(quadVerts[1], 1, 1, 0, 1);
VectorSet4(quadVerts[2], 1, -1, 0, 1);
VectorSet4(quadVerts[3], -1, -1, 0, 1);
texCoords[0][0] = 0; texCoords[0][1] = 1;
texCoords[1][0] = 1; texCoords[1][1] = 1;
texCoords[2][0] = 1; texCoords[2][1] = 0;
texCoords[3][0] = 0; texCoords[3][1] = 0;
GL_State( GLS_DEPTHTEST_DISABLE );
GLSL_BindProgram(&tr.shadowmaskShader);
GL_BindToTMU(tr.renderDepthImage, TB_COLORMAP);
GL_BindToTMU(tr.sunShadowDepthImage[0], TB_SHADOWMAP);
GL_BindToTMU(tr.sunShadowDepthImage[1], TB_SHADOWMAP2);
GL_BindToTMU(tr.sunShadowDepthImage[2], TB_SHADOWMAP3);
GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP, backEnd.refdef.sunShadowMvp[0]);
GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP2, backEnd.refdef.sunShadowMvp[1]);
GLSL_SetUniformMatrix16(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_SHADOWMVP3, backEnd.refdef.sunShadowMvp[2]);
GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWORIGIN, backEnd.refdef.vieworg);
{
vec4_t viewInfo;
vec3_t viewVector;
float zmax = backEnd.viewParms.zFar;
float ymax = zmax * tan(backEnd.viewParms.fovY * M_PI / 360.0f);
float xmax = zmax * tan(backEnd.viewParms.fovX * M_PI / 360.0f);
float zmin = r_znear->value;
VectorScale(backEnd.refdef.viewaxis[0], zmax, viewVector);
GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWFORWARD, viewVector);
VectorScale(backEnd.refdef.viewaxis[1], xmax, viewVector);
GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWLEFT, viewVector);
VectorScale(backEnd.refdef.viewaxis[2], ymax, viewVector);
GLSL_SetUniformVec3(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWUP, viewVector);
VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
GLSL_SetUniformVec4(&tr.shadowmaskShader, SHADOWMASK_UNIFORM_VIEWINFO, viewInfo);
}
RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
}
if (r_ssao->integer)
{
vec4_t quadVerts[4];
vec2_t texCoords[4];
FBO_Bind(tr.quarterFbo[0]);
qglViewport(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
qglScissor(0, 0, tr.quarterFbo[0]->width, tr.quarterFbo[0]->height);
VectorSet4(quadVerts[0], -1, 1, 0, 1);
VectorSet4(quadVerts[1], 1, 1, 0, 1);
VectorSet4(quadVerts[2], 1, -1, 0, 1);
VectorSet4(quadVerts[3], -1, -1, 0, 1);
texCoords[0][0] = 0; texCoords[0][1] = 1;
texCoords[1][0] = 1; texCoords[1][1] = 1;
texCoords[2][0] = 1; texCoords[2][1] = 0;
texCoords[3][0] = 0; texCoords[3][1] = 0;
GL_State( GLS_DEPTHTEST_DISABLE );
GLSL_BindProgram(&tr.ssaoShader);
GL_BindToTMU(tr.hdrDepthImage, TB_COLORMAP);
{
vec4_t viewInfo;
float zmax = backEnd.viewParms.zFar;
float zmin = r_znear->value;
VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
GLSL_SetUniformVec4(&tr.ssaoShader, SSAO_UNIFORM_VIEWINFO, viewInfo);
}
RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
FBO_Bind(tr.quarterFbo[1]);
qglViewport(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height);
qglScissor(0, 0, tr.quarterFbo[1]->width, tr.quarterFbo[1]->height);
GLSL_BindProgram(&tr.depthBlurShader[0]);
GL_BindToTMU(tr.quarterImage[0], TB_COLORMAP);
GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP);
{
vec4_t viewInfo;
float zmax = backEnd.viewParms.zFar;
float zmin = r_znear->value;
VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
GLSL_SetUniformVec4(&tr.depthBlurShader[0], DEPTHBLUR_UNIFORM_VIEWINFO, viewInfo);
}
RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
FBO_Bind(tr.screenSsaoFbo);
qglViewport(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height);
qglScissor(0, 0, tr.screenSsaoFbo->width, tr.screenSsaoFbo->height);
GLSL_BindProgram(&tr.depthBlurShader[1]);
GL_BindToTMU(tr.quarterImage[1], TB_COLORMAP);
GL_BindToTMU(tr.hdrDepthImage, TB_LIGHTMAP);
{
vec4_t viewInfo;
float zmax = backEnd.viewParms.zFar;
float zmin = r_znear->value;
VectorSet4(viewInfo, zmax / zmin, zmax, 0.0, 0.0);
GLSL_SetUniformVec4(&tr.depthBlurShader[1], DEPTHBLUR_UNIFORM_VIEWINFO, viewInfo);
}
RB_InstantQuad2(quadVerts, texCoords); //, color, shaderProgram, invTexRes);
}
// reset viewport and scissor
FBO_Bind(oldFbo);
SetViewportAndScissor();
}
if (glRefConfig.framebufferObject && (backEnd.viewParms.flags & VPF_DEPTHCLAMP) && glRefConfig.depthClamp)
{
qglDisable(GL_DEPTH_CLAMP);
}
if (!(backEnd.viewParms.flags & VPF_DEPTHSHADOW))
{
RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
#if 0
RB_DrawSun();
#endif
// darken down any stencil shadows
RB_ShadowFinish();
// add light flares on lights that aren't obscured
RB_RenderFlares();
}
//if (glRefConfig.framebufferObject)
//FBO_Bind(NULL);
return (const void *)(cmd + 1);
}
/*
=============
RB_DrawBuffer
=============
*/
const void *RB_DrawBuffer( const void *data ) {
const drawBufferCommand_t *cmd;
cmd = (const drawBufferCommand_t *)data;
if (glRefConfig.framebufferObject)
FBO_Bind(NULL);
qglDrawBuffer( cmd->buffer );
// 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);
}
/*
===============
RB_ShowImages
Draw all the images to the screen, on top of whatever
was there. This is used to test for texture thrashing.
Also called by RE_EndRegistration
===============
*/
void RB_ShowImages( void ) {
int i;
image_t *image;
float x, y, w, h;
int start, end;
RB_SetGL2D();
qglClear( GL_COLOR_BUFFER_BIT );
qglFinish();
start = ri.Milliseconds();
for ( i=0 ; i<tr.numImages ; i++ ) {
image = tr.images[i];
w = glConfig.vidWidth / 20;
h = glConfig.vidHeight / 15;
x = i % 20 * w;
y = i / 20 * h;
// show in proportional size in mode 2
if ( r_showImages->integer == 2 ) {
w *= image->uploadWidth / 512.0f;
h *= image->uploadHeight / 512.0f;
}
{
vec4_t quadVerts[4];
GL_Bind(image);
VectorSet4(quadVerts[0], x, y, 0, 1);
VectorSet4(quadVerts[1], x + w, y, 0, 1);
VectorSet4(quadVerts[2], x + w, y + h, 0, 1);
VectorSet4(quadVerts[3], x, y + h, 0, 1);
RB_InstantQuad(quadVerts);
}
}
qglFinish();
end = ri.Milliseconds();
ri.Printf( PRINT_ALL, "%i msec to draw all images\n", end - start );
}
/*
=============
RB_ColorMask
=============
*/
const void *RB_ColorMask(const void *data)
{
const colorMaskCommand_t *cmd = data;
if (glRefConfig.framebufferObject)
{
// reverse color mask, so 0 0 0 0 is the default
backEnd.colorMask[0] = !cmd->rgba[0];
backEnd.colorMask[1] = !cmd->rgba[1];
backEnd.colorMask[2] = !cmd->rgba[2];
backEnd.colorMask[3] = !cmd->rgba[3];
}
qglColorMask(cmd->rgba[0], cmd->rgba[1], cmd->rgba[2], cmd->rgba[3]);
return (const void *)(cmd + 1);
}
/*
=============
RB_ClearDepth
=============
*/
const void *RB_ClearDepth(const void *data)
{
const clearDepthCommand_t *cmd = data;
if(tess.numIndexes)
RB_EndSurface();
// texture swapping test
if (r_showImages->integer)
RB_ShowImages();
if (glRefConfig.framebufferObject)
{
if (!tr.renderFbo || backEnd.framePostProcessed)
{
FBO_Bind(tr.screenScratchFbo);
}
else
{
FBO_Bind(tr.renderFbo);
}
}
qglClear(GL_DEPTH_BUFFER_BIT);
// if we're doing MSAA, clear the depth texture for the resolve buffer
if (tr.msaaResolveFbo)
{
FBO_Bind(tr.msaaResolveFbo);
qglClear(GL_DEPTH_BUFFER_BIT);
}
return (const void *)(cmd + 1);
}
/*
=============
RB_SwapBuffers
=============
*/
const void *RB_SwapBuffers( const void *data ) {
const swapBuffersCommand_t *cmd;
// finish any 2D drawing if needed
if ( tess.numIndexes ) {
RB_EndSurface();
}
// texture swapping test
if ( r_showImages->integer ) {
RB_ShowImages();
}
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 ) {
int i;
long sum = 0;
unsigned char *stencilReadback;
stencilReadback = ri.Hunk_AllocateTempMemory( glConfig.vidWidth * glConfig.vidHeight );
qglReadPixels( 0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, stencilReadback );
for ( i = 0; i < glConfig.vidWidth * glConfig.vidHeight; i++ ) {
sum += stencilReadback[i];
}
backEnd.pc.c_overDraw += sum;
ri.Hunk_FreeTempMemory( stencilReadback );
}
if (glRefConfig.framebufferObject)
{
if (!backEnd.framePostProcessed)
{
if (tr.msaaResolveFbo && r_hdr->integer)
{
// Resolving an RGB16F MSAA FBO to the screen messes with the brightness, so resolve to an RGB16F FBO first
FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
FBO_FastBlit(tr.msaaResolveFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
else if (tr.renderFbo)
{
FBO_FastBlit(tr.renderFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
}
if (tr.screenScratchFbo)
{
vec4_t color;
color[0] =
color[1] =
color[2] = pow(2, tr.overbrightBits); //exp2(tr.overbrightBits);
color[3] = 1.0f;
// turn off colormask when copying final image
if (backEnd.colorMask[0] || backEnd.colorMask[1] || backEnd.colorMask[2] || backEnd.colorMask[3])
qglColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
FBO_Blit(tr.screenScratchFbo, NULL, NULL, NULL, NULL, NULL, color, 0);
if (backEnd.colorMask[0] || backEnd.colorMask[1] || backEnd.colorMask[2] || backEnd.colorMask[3])
qglColorMask(!backEnd.colorMask[0], !backEnd.colorMask[1], !backEnd.colorMask[2], !backEnd.colorMask[3]);
}
}
if ( !glState.finishCalled ) {
qglFinish();
}
GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" );
GLimp_EndFrame();
backEnd.framePostProcessed = qfalse;
backEnd.projection2D = qfalse;
#ifdef REACTION
backEnd.frameHasSunFlare = qfalse;
#endif
return (const void *)(cmd + 1);
}
/*
=============
RB_CapShadowMap
=============
*/
const void *RB_CapShadowMap(const void *data)
{
const capShadowmapCommand_t *cmd = data;
if (cmd->map != -1)
{
GL_SelectTexture(0);
if (cmd->cubeSide != -1)
{
GL_BindCubemap(tr.shadowCubemaps[cmd->map]);
qglCopyTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cmd->cubeSide, 0, GL_RGBA8, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, 0);
}
else
{
GL_Bind(tr.pshadowMaps[cmd->map]);
qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, backEnd.refdef.x, glConfig.vidHeight - ( backEnd.refdef.y + PSHADOW_MAP_SIZE ), PSHADOW_MAP_SIZE, PSHADOW_MAP_SIZE, 0);
}
}
return (const void *)(cmd + 1);
}
/*
=============
RB_PostProcess
=============
*/
const void *RB_PostProcess(const void *data)
{
const postProcessCommand_t *cmd = data;
FBO_t *srcFbo;
qboolean autoExposure;
if (!glRefConfig.framebufferObject || !r_postProcess->integer)
{
// do nothing
return (const void *)(cmd + 1);
}
srcFbo = tr.renderFbo;
if (tr.msaaResolveFbo)
{
// Resolve the MSAA before anything else
FBO_FastBlit(tr.renderFbo, NULL, tr.msaaResolveFbo, NULL, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
srcFbo = tr.msaaResolveFbo;
}
if (r_ssao->integer)
{
FBO_BlitFromTexture(tr.screenSsaoImage, NULL, NULL, srcFbo, NULL, NULL, NULL, GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO);
}
if (srcFbo)
{
if (r_hdr->integer && (r_toneMap->integer || r_forceToneMap->integer))
{
autoExposure = r_autoExposure->integer || r_forceAutoExposure->integer;
RB_ToneMap(srcFbo, autoExposure);
}
else if (!glRefConfig.framebuffer_srgb && r_cameraExposure->value == 0.0f)
{
FBO_FastBlit(srcFbo, NULL, tr.screenScratchFbo, NULL, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
else
{
vec4_t color;
color[0] =
color[1] =
color[2] = pow(2, r_cameraExposure->value); //exp2(r_cameraExposure->value);
color[3] = 1.0f;
FBO_Blit(srcFbo, NULL, NULL, tr.screenScratchFbo, NULL, NULL, color, 0);
}
}
#ifdef REACTION
if (1)
{
RB_GodRays();
if (1)
RB_BokehBlur(backEnd.refdef.blurFactor);
else
RB_GaussianBlur(backEnd.refdef.blurFactor);
}
#endif
if (0)
{
vec4i_t dstBox;
VectorSet4(dstBox, 0, 0, 128, 128);
FBO_BlitFromTexture(tr.sunShadowDepthImage[0], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 128, 0, 128, 128);
FBO_BlitFromTexture(tr.sunShadowDepthImage[1], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 256, 0, 128, 128);
FBO_BlitFromTexture(tr.sunShadowDepthImage[2], NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
}
if (0)
{
vec4i_t dstBox;
VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256);
FBO_BlitFromTexture(tr.renderDepthImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
VectorSet4(dstBox, 512, glConfig.vidHeight - 256, 256, 256);
FBO_BlitFromTexture(tr.screenShadowImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
}
if (0)
{
vec4i_t dstBox;
VectorSet4(dstBox, 256, glConfig.vidHeight - 256, 256, 256);
FBO_BlitFromTexture(tr.renderImage, NULL, NULL, tr.screenScratchFbo, dstBox, NULL, NULL, 0);
}
backEnd.framePostProcessed = qtrue;
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 t1, t2;
t1 = ri.Milliseconds ();
if ( !r_smp->integer || data == backEndData[0]->commands.cmds ) {
backEnd.smpFrame = 0;
} else {
backEnd.smpFrame = 1;
}
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_DRAW_BUFFER:
data = RB_DrawBuffer( data );
break;
case RC_SWAP_BUFFERS:
data = RB_SwapBuffers( data );
break;
case RC_SCREENSHOT:
data = RB_TakeScreenshotCmd( data );
break;
case RC_VIDEOFRAME:
data = RB_TakeVideoFrameCmd( data );
break;
case RC_COLORMASK:
data = RB_ColorMask(data);
break;
case RC_CLEARDEPTH:
data = RB_ClearDepth(data);
break;
case RC_CAPSHADOWMAP:
data = RB_CapShadowMap(data);
break;
case RC_POSTPROCESS:
data = RB_PostProcess(data);
break;
case RC_END_OF_LIST:
default:
// stop rendering on this thread
t2 = ri.Milliseconds ();
backEnd.pc.msec = t2 - t1;
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;
}
}