/* =========================================================================== 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 . 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 "sys/platform.h" #include "renderer/tr_local.h" static idCVar r_fillWindowAlphaChan( "r_fillWindowAlphaChan", "-1", CVAR_SYSTEM | CVAR_NOCHEAT | CVAR_ARCHIVE, "Make sure alpha channel of windows default framebuffer is completely opaque at the end of each frame. Needed at least when using Wayland.\n 1: do this, 0: don't do it, -1: let dhewm3 decide (default)" ); 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; qglClearDepth( 1.0f ); qglColor4f (1,1,1,1); // 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 ); } } } //============================================================================= /* ==================== 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 ); 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; 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( 0.4f, 0.0f, 0.25f, 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(); } int fillAlpha = r_fillWindowAlphaChan.GetInteger(); if ( fillAlpha == 1 || (fillAlpha == -1 && glConfig.shouldFillWindowAlpha) ) { // make sure the whole alpha chan of the (default) framebuffer is opaque. // at least Wayland needs this, see also the big comment in GLimp_Init() bool blendEnabled = qglIsEnabled( GL_BLEND ); if ( !blendEnabled ) qglEnable( GL_BLEND ); // TODO: GL_DEPTH_TEST ? (should be disabled, if it needs changing at all) bool scissorEnabled = qglIsEnabled( GL_SCISSOR_TEST ); if( scissorEnabled ) qglDisable( GL_SCISSOR_TEST ); bool tex2Denabled = qglIsEnabled( GL_TEXTURE_2D ); if( tex2Denabled ) qglDisable( GL_TEXTURE_2D ); qglDisable( GL_VERTEX_PROGRAM_ARB ); qglDisable( GL_FRAGMENT_PROGRAM_ARB ); qglBlendEquation( GL_FUNC_ADD ); qglBlendFunc( GL_ONE, GL_ONE ); // setup transform matrices so we can easily/reliably draw a fullscreen quad qglMatrixMode( GL_MODELVIEW ); qglPushMatrix(); qglLoadIdentity(); qglMatrixMode( GL_PROJECTION ); qglPushMatrix(); qglLoadIdentity(); qglOrtho( 0, 1, 0, 1, -1, 1 ); // draw screen-sized quad with color (0.0, 0.0, 0.0, 1.0) const float x=0, y=0, w=1, h=1; qglColor4f( 0.0f, 0.0f, 0.0f, 1.0f ); // debug values: //const float x = 0.1, y = 0.1, w = 0.8, h = 0.8; //qglColor4f( 0.0f, 0.0f, 0.5f, 1.0f ); qglBegin( GL_QUADS ); qglVertex2f( x, y ); // ( 0,0 ); qglVertex2f( x, y+h ); // ( 0,1 ); qglVertex2f( x+w, y+h ); // ( 1,1 ); qglVertex2f( x+w, y ); // ( 1,0 ); qglEnd(); // restore previous transform matrix states qglPopMatrix(); // for projection qglMatrixMode( GL_MODELVIEW ); qglPopMatrix(); // for modelview // restore default or previous states qglBlendEquation( GL_FUNC_ADD ); if ( !blendEnabled ) qglDisable( GL_BLEND ); if( tex2Denabled ) qglEnable( GL_TEXTURE_2D ); if( scissorEnabled ) qglEnable( GL_SCISSOR_TEST ); } // force a gl sync if requested if ( r_finish.GetBool() ) { qglFinish(); } // 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; } 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; } }