quadrilateralcowboy/renderer/tr_backend.cpp

695 lines
16 KiB
C++
Raw Normal View History

2020-06-12 21:06:25 +00:00
/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
Doom 3 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 3 of the License, or
(at your option) any later version.
Doom 3 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 Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "tr_local.h"
extern GLuint glOffscreenBB;
frameData_t *frameData;
backEndState_t backEnd;
/*
======================
RB_SetDefaultGLState
This should initialize all GL state that any part of the entire program
may touch, including the editor.
======================
*/
void RB_SetDefaultGLState( void ) {
int i;
RB_LogComment( "--- R_SetDefaultGLState ---\n" );
qglClearDepth( 1.0f );
qglColor4f (1,1,1,1);
qglDisableClientState( GL_INDEX_ARRAY );
qglDisableClientState( GL_COLOR_ARRAY );
qglDisableClientState( GL_SECONDARY_COLOR_ARRAY_EXT );
qglDisableClientState( GL_VERTEX_ARRAY );
// the vertex array is always enabled
qglEnableClientState( GL_VERTEX_ARRAY );
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
qglDisableClientState( GL_COLOR_ARRAY );
//
// make sure our GL state vector is set correctly
//
memset( &backEnd.glState, 0, sizeof( backEnd.glState ) );
backEnd.glState.forceGlState = true;
qglColorMask( 1, 1, 1, 1 );
qglEnable( GL_DEPTH_TEST );
qglEnable( GL_BLEND );
qglEnable( GL_SCISSOR_TEST );
qglEnable( GL_CULL_FACE );
qglDisable( GL_LIGHTING );
qglDisable( GL_LINE_STIPPLE );
qglDisable( GL_STENCIL_TEST );
qglPolygonMode (GL_FRONT_AND_BACK, GL_FILL);
qglDepthMask( GL_TRUE );
qglDepthFunc( GL_ALWAYS );
qglCullFace( GL_FRONT_AND_BACK );
qglShadeModel( GL_SMOOTH );
if ( r_useScissor.GetBool() ) {
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
}
for ( i = glConfig.maxTextureUnits - 1 ; i >= 0 ; i-- ) {
GL_SelectTexture( i );
// object linear texgen is our default
qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
qglTexGenf( GL_Q, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
GL_TexEnv( GL_MODULATE );
qglDisable( GL_TEXTURE_2D );
if ( glConfig.texture3DAvailable ) {
qglDisable( GL_TEXTURE_3D );
}
if ( glConfig.cubeMapAvailable ) {
qglDisable( GL_TEXTURE_CUBE_MAP_EXT );
}
}
}
/*
====================
RB_LogComment
====================
*/
void RB_LogComment( const char *comment, ... ) {
va_list marker;
if ( !tr.logFile ) {
return;
}
fprintf( tr.logFile, "// " );
va_start( marker, comment );
vfprintf( tr.logFile, comment, marker );
va_end( marker );
}
//=============================================================================
/*
====================
GL_SelectTexture
====================
*/
void GL_SelectTexture( int unit ) {
if ( backEnd.glState.currenttmu == unit ) {
return;
}
if ( unit < 0 || unit >= glConfig.maxTextureUnits && unit >= glConfig.maxTextureImageUnits ) {
common->Warning( "GL_SelectTexture: unit = %i", unit );
return;
}
qglActiveTextureARB( GL_TEXTURE0_ARB + unit );
qglClientActiveTextureARB( GL_TEXTURE0_ARB + unit );
RB_LogComment( "glActiveTextureARB( %i );\nglClientActiveTextureARB( %i );\n", unit, unit );
backEnd.glState.currenttmu = unit;
}
/*
====================
GL_Cull
This handles the flipping needed when the view being
rendered is a mirored view.
====================
*/
void GL_Cull( int cullType ) {
if ( backEnd.glState.faceCulling == cullType ) {
return;
}
if ( cullType == CT_TWO_SIDED ) {
qglDisable( GL_CULL_FACE );
} else {
if ( backEnd.glState.faceCulling == CT_TWO_SIDED ) {
qglEnable( GL_CULL_FACE );
}
if ( cullType == CT_BACK_SIDED ) {
if ( backEnd.viewDef->isMirror ) {
qglCullFace( GL_FRONT );
} else {
qglCullFace( GL_BACK );
}
} else {
if ( backEnd.viewDef->isMirror ) {
qglCullFace( GL_BACK );
} else {
qglCullFace( GL_FRONT );
}
}
}
backEnd.glState.faceCulling = cullType;
}
/*
====================
GL_TexEnv
====================
*/
void GL_TexEnv( int env ) {
tmu_t *tmu;
tmu = &backEnd.glState.tmu[backEnd.glState.currenttmu];
if ( env == tmu->texEnv ) {
return;
}
tmu->texEnv = env;
switch ( env ) {
case GL_COMBINE_EXT:
case GL_MODULATE:
case GL_REPLACE:
case GL_DECAL:
case GL_ADD:
qglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env );
break;
default:
common->Error( "GL_TexEnv: invalid env '%d' passed\n", env );
break;
}
}
/*
=================
GL_ClearStateDelta
Clears the state delta bits, so the next GL_State
will set every item
=================
*/
void GL_ClearStateDelta( void ) {
backEnd.glState.forceGlState = true;
}
/*
====================
GL_State
This routine is responsible for setting the most commonly changed state
====================
*/
void GL_State( int stateBits ) {
int diff;
if ( !r_useStateCaching.GetBool() || backEnd.glState.forceGlState ) {
// make sure everything is set all the time, so we
// can see if our delta checking is screwing up
diff = -1;
backEnd.glState.forceGlState = false;
} else {
diff = stateBits ^ backEnd.glState.glStateBits;
if ( !diff ) {
return;
}
}
//
// check depthFunc bits
//
if ( diff & ( GLS_DEPTHFUNC_EQUAL | GLS_DEPTHFUNC_LESS | GLS_DEPTHFUNC_ALWAYS ) ) {
if ( stateBits & GLS_DEPTHFUNC_EQUAL ) {
qglDepthFunc( GL_EQUAL );
} else if ( stateBits & GLS_DEPTHFUNC_ALWAYS ) {
qglDepthFunc( GL_ALWAYS );
} else {
qglDepthFunc( GL_LEQUAL );
}
}
//
// check blend bits
//
if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) ) {
GLenum srcFactor, dstFactor;
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
common->Error( "GL_State: invalid src blend state bits\n" );
break;
}
switch ( stateBits & GLS_DSTBLEND_BITS ) {
case GLS_DSTBLEND_ZERO:
dstFactor = GL_ZERO;
break;
case GLS_DSTBLEND_ONE:
dstFactor = GL_ONE;
break;
case GLS_DSTBLEND_SRC_COLOR:
dstFactor = GL_SRC_COLOR;
break;
case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR:
dstFactor = GL_ONE_MINUS_SRC_COLOR;
break;
case GLS_DSTBLEND_SRC_ALPHA:
dstFactor = GL_SRC_ALPHA;
break;
case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA:
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
break;
case GLS_DSTBLEND_DST_ALPHA:
dstFactor = GL_DST_ALPHA;
break;
case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA:
dstFactor = GL_ONE_MINUS_DST_ALPHA;
break;
default:
dstFactor = GL_ONE; // to get warning to shut up
common->Error( "GL_State: invalid dst blend state bits\n" );
break;
}
qglBlendFunc( srcFactor, dstFactor );
}
//
// check depthmask
//
if ( diff & GLS_DEPTHMASK ) {
if ( stateBits & GLS_DEPTHMASK ) {
qglDepthMask( GL_FALSE );
} else {
qglDepthMask( GL_TRUE );
}
}
//
// check colormask
//
if ( diff & (GLS_REDMASK|GLS_GREENMASK|GLS_BLUEMASK|GLS_ALPHAMASK) ) {
GLboolean r, g, b, a;
r = ( stateBits & GLS_REDMASK ) ? 0 : 1;
g = ( stateBits & GLS_GREENMASK ) ? 0 : 1;
b = ( stateBits & GLS_BLUEMASK ) ? 0 : 1;
a = ( stateBits & GLS_ALPHAMASK ) ? 0 : 1;
qglColorMask( r, g, b, a );
}
//
// 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 );
}
}
//
// alpha test
//
if ( diff & GLS_ATEST_BITS ) {
switch ( stateBits & GLS_ATEST_BITS ) {
case 0:
qglDisable( GL_ALPHA_TEST );
break;
case GLS_ATEST_EQ_255:
qglEnable( GL_ALPHA_TEST );
qglAlphaFunc( GL_EQUAL, 1 );
break;
case GLS_ATEST_LT_128:
qglEnable( GL_ALPHA_TEST );
qglAlphaFunc( GL_LESS, 0.5 );
break;
case GLS_ATEST_GE_128:
qglEnable( GL_ALPHA_TEST );
qglAlphaFunc( GL_GEQUAL, 0.5 );
break;
default:
assert( 0 );
break;
}
}
backEnd.glState.glStateBits = stateBits;
}
/*
============================================================================
RENDER BACK END THREAD FUNCTIONS
============================================================================
*/
/*
=============
RB_SetGL2D
This is not used by the normal game paths, just by some tools
=============
*/
void RB_SetGL2D( void ) {
// set 2D virtual screen size
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
if ( r_useScissor.GetBool() ) {
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
}
qglMatrixMode( GL_PROJECTION );
qglLoadIdentity();
qglOrtho( 0, 640, 480, 0, 0, 1 ); // always assume 640x480 virtual coordinates
qglMatrixMode( GL_MODELVIEW );
qglLoadIdentity();
GL_State( GLS_DEPTHFUNC_ALWAYS |
GLS_SRCBLEND_SRC_ALPHA |
GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
GL_Cull( CT_TWO_SIDED );
qglDisable( GL_DEPTH_TEST );
qglDisable( GL_STENCIL_TEST );
}
/*
=============
RB_SetBuffer
=============
*/
static void RB_SetBuffer( const void *data ) {
const setBufferCommand_t *cmd;
// see which draw buffer we want to render the frame to
cmd = (const setBufferCommand_t *)data;
backEnd.frameCount = cmd->frameCount;
// if we have an offscreen back buffer then let's capture to that.
if ( glOffscreenBB > 0 && cmd->buffer == GL_BACK ) {
qglBindFramebufferEXT(GL_FRAMEBUFFER, glOffscreenBB);
assert(backEnd.offscreenBBSet == false);
backEnd.offscreenBBSet = true;
}
else {
qglDrawBuffer( cmd->buffer );
}
// clear screen for debugging
// automatically enable this with several other debug tools
// that might leave unrendered portions of the screen
if ( r_clear.GetFloat() || idStr::Length( r_clear.GetString() ) != 1 || r_lockSurfaces.GetBool() || r_singleArea.GetBool() || r_showOverDraw.GetBool() ) {
float c[3];
if ( sscanf( r_clear.GetString(), "%f %f %f", &c[0], &c[1], &c[2] ) == 3 ) {
qglClearColor( c[0], c[1], c[2], 1 );
} else if ( r_clear.GetInteger() == 2 ) {
qglClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
} else if ( r_showOverDraw.GetBool() ) {
qglClearColor( 1.0f, 1.0f, 1.0f, 1.0f );
} else {
qglClearColor( 1.0f, 0.0f, 1.0f, 1.0f );
}
qglClear( GL_COLOR_BUFFER_BIT );
}
}
/*
===============
RB_ShowImages
Draw all the images to the screen, on top of whatever
was there. This is used to test for texture thrashing.
===============
*/
void RB_ShowImages( void ) {
int i;
idImage *image;
float x, y, w, h;
int start, end;
RB_SetGL2D();
//qglClearColor( 0.2, 0.2, 0.2, 1 );
//qglClear( GL_COLOR_BUFFER_BIT );
qglFinish();
start = Sys_Milliseconds();
for ( i = 0 ; i < globalImages->images.Num() ; i++ ) {
image = globalImages->images[i];
if ( image->texnum == idImage::TEXTURE_NOT_LOADED && image->partialImage == NULL ) {
continue;
}
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.GetInteger() == 2 ) {
w *= image->uploadWidth / 512.0f;
h *= image->uploadHeight / 512.0f;
}
image->Bind();
qglBegin (GL_QUADS);
qglTexCoord2f( 0, 0 );
qglVertex2f( x, y );
qglTexCoord2f( 1, 0 );
qglVertex2f( x + w, y );
qglTexCoord2f( 1, 1 );
qglVertex2f( x + w, y + h );
qglTexCoord2f( 0, 1 );
qglVertex2f( x, y + h );
qglEnd();
}
qglFinish();
end = Sys_Milliseconds();
common->Printf( "%i msec to draw all images\n", end - start );
}
/*
=============
RB_SwapBuffers
=============
*/
const void RB_SwapBuffers( const void *data ) {
// texture swapping test
if ( r_showImages.GetInteger() != 0 ) {
RB_ShowImages();
}
// resolve the offscreen backbuffer to the screen.
if ( backEnd.offscreenBBSet && glOffscreenBB > 0) {
// blit the offscreen buffer
qglBindFramebufferEXT(GL_READ_FRAMEBUFFER, glOffscreenBB);
qglBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
qglReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
qglDrawBuffer(GL_BACK);
qglViewport( 0, 0, glConfig.winWidth, glConfig.winHeight );
qglScissor( 0, 0, glConfig.winWidth, glConfig.winHeight );
qglBlitFramebufferEXT(0, 0, glConfig.vidWidth, glConfig.vidHeight,
0, 0, glConfig.winWidth, glConfig.winHeight,
GL_COLOR_BUFFER_BIT, GL_LINEAR);
qglBindFramebufferEXT(GL_FRAMEBUFFER, 0);
qglDrawBuffer(GL_BACK);
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
backEnd.offscreenBBSet = false;
}
// force a gl sync if requested
if ( r_finish.GetBool() ) {
qglFinish();
}
RB_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" );
// don't flip if drawing to front buffer
if ( !r_frontBuffer.GetBool() ) {
GLimp_SwapBuffers();
}
}
/*
=============
RB_CopyRender
Copy part of the current framebuffer to an image
=============
*/
const void RB_CopyRender( const void *data ) {
const copyRenderCommand_t *cmd;
cmd = (const copyRenderCommand_t *)data;
if ( r_skipCopyTexture.GetBool() ) {
return;
}
RB_LogComment( "***************** RB_CopyRender *****************\n" );
if (cmd->image) {
cmd->image->CopyFramebuffer( cmd->x, cmd->y, cmd->imageWidth, cmd->imageHeight, false );
}
}
/*
====================
RB_ExecuteBackEndCommands
This function will be called syncronously if running without
smp extensions, or asyncronously by another thread.
====================
*/
int backEndStartTime, backEndFinishTime;
void RB_ExecuteBackEndCommands( const emptyCommand_t *cmds ) {
// r_debugRenderToTexture
int c_draw3d = 0, c_draw2d = 0, c_setBuffers = 0, c_swapBuffers = 0, c_copyRenders = 0;
if ( cmds->commandId == RC_NOP && !cmds->next ) {
return;
}
backEndStartTime = Sys_Milliseconds();
// needed for editor rendering
RB_SetDefaultGLState();
// upload any image loads that have completed
globalImages->CompleteBackgroundImageLoads();
for ( ; cmds ; cmds = (const emptyCommand_t *)cmds->next ) {
switch ( cmds->commandId ) {
case RC_NOP:
break;
case RC_DRAW_VIEW:
RB_DrawView( cmds );
if ( ((const drawSurfsCommand_t *)cmds)->viewDef->viewEntitys ) {
c_draw3d++;
}
else {
c_draw2d++;
}
break;
case RC_SET_BUFFER:
RB_SetBuffer( cmds );
c_setBuffers++;
break;
case RC_SWAP_BUFFERS:
RB_SwapBuffers( cmds );
c_swapBuffers++;
break;
case RC_COPY_RENDER:
RB_CopyRender( cmds );
c_copyRenders++;
break;
default:
common->Error( "RB_ExecuteBackEndCommands: bad commandId" );
break;
}
}
// go back to the default texture so the editor doesn't mess up a bound image
qglBindTexture( GL_TEXTURE_2D, 0 );
backEnd.glState.tmu[0].current2DMap = -1;
// stop rendering on this thread
backEndFinishTime = Sys_Milliseconds();
backEnd.pc.msec = backEndFinishTime - backEndStartTime;
if ( r_debugRenderToTexture.GetInteger() == 1 ) {
common->Printf( "3d: %i, 2d: %i, SetBuf: %i, SwpBuf: %i, CpyRenders: %i, CpyFrameBuf: %i\n", c_draw3d, c_draw2d, c_setBuffers, c_swapBuffers, c_copyRenders, backEnd.c_copyFrameBuffer );
backEnd.c_copyFrameBuffer = 0;
}
}