mirror of
https://github.com/Q3Rally-Team/q3rally.git
synced 2024-11-29 23:22:55 +00:00
3b4f4cdfa9
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.
1888 lines
46 KiB
C
1888 lines
46 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;
|
|
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|