mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-25 13:41:34 +00:00
2328 lines
59 KiB
C++
2328 lines
59 KiB
C++
//Anything above this #include will be ignored by the compiler
|
|
#include "../qcommon/exe_headers.h"
|
|
|
|
#include "tr_local.h"
|
|
|
|
#ifndef DEDICATED
|
|
#if !defined __TR_WORLDEFFECTS_H
|
|
#include "tr_WorldEffects.h"
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef VV_LIGHTING
|
|
#include "tr_lightmanager.h"
|
|
#endif
|
|
|
|
#ifdef _XBOX
|
|
#include "../win32/win_highdynamicrange.h"
|
|
#endif
|
|
|
|
backEndData_t *backEndData;
|
|
backEndState_t backEnd;
|
|
|
|
bool tr_stencilled = false;
|
|
extern qboolean tr_distortionPrePost; //tr_shadows.cpp
|
|
extern qboolean tr_distortionNegate; //tr_shadows.cpp
|
|
extern void RB_CaptureScreenImage(void); //tr_shadows.cpp
|
|
extern void RB_DistortionFill(void); //tr_shadows.cpp
|
|
static void RB_DrawGlowOverlay();
|
|
static void RB_BlurGlowTexture();
|
|
|
|
// Whether we are currently rendering only glowing objects or not.
|
|
bool g_bRenderGlowingObjects = false;
|
|
|
|
// Whether the current hardware supports dynamic glows/flares.
|
|
bool g_bDynamicGlowSupported = false;
|
|
|
|
extern void R_RotateForViewer(void);
|
|
extern void R_SetupFrustum(void);
|
|
|
|
static const float s_flipMatrix[16] = {
|
|
// convert from our coordinate system (looking down X)
|
|
// to OpenGL's coordinate system (looking down -Z)
|
|
#if defined (_XBOX)
|
|
0, 0, 1, 0,
|
|
-1, 0, 0, 0,
|
|
0, 1, 0, 0,
|
|
0, 0, 0, 1
|
|
#else
|
|
0, 0, -1, 0,
|
|
-1, 0, 0, 0,
|
|
0, 1, 0, 0,
|
|
0, 0, 0, 1
|
|
#endif
|
|
};
|
|
|
|
#ifndef DEDICATED
|
|
|
|
/*
|
|
** GL_Bind
|
|
*/
|
|
void GL_Bind( image_t *image ) {
|
|
int texnum;
|
|
|
|
if ( !image ) {
|
|
Com_Printf (S_COLOR_YELLOW "GL_Bind: 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 ) {
|
|
#ifndef _XBOX
|
|
image->frameUsed = tr.frameCount;
|
|
#endif
|
|
glState.currenttextures[glState.currenttmu] = texnum;
|
|
qglBindTexture (GL_TEXTURE_2D, texnum);
|
|
}
|
|
}
|
|
|
|
//bind 3D texture -rww
|
|
void GL_Bind3D( image_t *image )
|
|
{
|
|
int texnum;
|
|
|
|
if ( !image ) {
|
|
Com_Printf (S_COLOR_YELLOW "GL_Bind: 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 ) {
|
|
#ifndef _XBOX
|
|
image->frameUsed = tr.frameCount;
|
|
#endif
|
|
glState.currenttextures[glState.currenttmu] = texnum;
|
|
qglBindTexture (GL_TEXTURE_3D, texnum);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** GL_SelectTexture
|
|
*/
|
|
void GL_SelectTexture( int unit )
|
|
{
|
|
if ( glState.currenttmu == unit )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( unit == 0 )
|
|
{
|
|
qglActiveTextureARB( GL_TEXTURE0_ARB );
|
|
GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE0_ARB )\n" );
|
|
qglClientActiveTextureARB( GL_TEXTURE0_ARB );
|
|
GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE0_ARB )\n" );
|
|
}
|
|
else if ( unit == 1 )
|
|
{
|
|
qglActiveTextureARB( GL_TEXTURE1_ARB );
|
|
GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE1_ARB )\n" );
|
|
qglClientActiveTextureARB( GL_TEXTURE1_ARB );
|
|
GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE1_ARB )\n" );
|
|
}
|
|
else if ( unit == 2 )
|
|
{
|
|
qglActiveTextureARB( GL_TEXTURE2_ARB );
|
|
GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE2_ARB )\n" );
|
|
qglClientActiveTextureARB( GL_TEXTURE2_ARB );
|
|
GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE2_ARB )\n" );
|
|
}
|
|
else if ( unit == 3 )
|
|
{
|
|
qglActiveTextureARB( GL_TEXTURE3_ARB );
|
|
GLimp_LogComment( "glActiveTextureARB( GL_TEXTURE3_ARB )\n" );
|
|
qglClientActiveTextureARB( GL_TEXTURE3_ARB );
|
|
GLimp_LogComment( "glClientActiveTextureARB( GL_TEXTURE3_ARB )\n" );
|
|
}
|
|
else {
|
|
Com_Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit );
|
|
}
|
|
|
|
glState.currenttmu = unit;
|
|
}
|
|
|
|
|
|
/*
|
|
** GL_Cull
|
|
*/
|
|
void GL_Cull( int cullType ) {
|
|
if ( glState.faceCulling == cullType ) {
|
|
return;
|
|
}
|
|
glState.faceCulling = cullType;
|
|
if (backEnd.projection2D){ //don't care, we're in 2d when it's always disabled
|
|
return;
|
|
}
|
|
|
|
if ( cullType == CT_TWO_SIDED )
|
|
{
|
|
qglDisable( GL_CULL_FACE );
|
|
}
|
|
else
|
|
{
|
|
qglEnable( GL_CULL_FACE );
|
|
|
|
if ( cullType == CT_BACK_SIDED )
|
|
{
|
|
if ( backEnd.viewParms.isMirror )
|
|
{
|
|
qglCullFace( GL_FRONT );
|
|
}
|
|
else
|
|
{
|
|
qglCullFace( GL_BACK );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( backEnd.viewParms.isMirror )
|
|
{
|
|
qglCullFace( GL_BACK );
|
|
}
|
|
else
|
|
{
|
|
qglCullFace( GL_FRONT );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** GL_TexEnv
|
|
*/
|
|
void GL_TexEnv( int env )
|
|
{
|
|
if ( env == glState.texEnv[glState.currenttmu] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
glState.texEnv[glState.currenttmu] = env;
|
|
|
|
|
|
switch ( env )
|
|
{
|
|
case GL_MODULATE:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
break;
|
|
case GL_REPLACE:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
|
|
break;
|
|
case GL_DECAL:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
|
|
break;
|
|
case GL_ADD:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );
|
|
break;
|
|
#ifdef _XBOX
|
|
case GL_NONE:
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_NONE );
|
|
break;
|
|
#endif
|
|
default:
|
|
Com_Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** GL_State
|
|
**
|
|
** This routine is responsible for setting the most commonly changed state
|
|
** in Q3.
|
|
*/
|
|
void GL_State( unsigned long stateBits )
|
|
{
|
|
unsigned long diff = stateBits ^ glState.glStateBits;
|
|
|
|
if ( !diff )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// check depthFunc bits
|
|
//
|
|
if ( diff & GLS_DEPTHFUNC_EQUAL )
|
|
{
|
|
if ( stateBits & GLS_DEPTHFUNC_EQUAL )
|
|
{
|
|
qglDepthFunc( GL_EQUAL );
|
|
}
|
|
else
|
|
{
|
|
qglDepthFunc( GL_LEQUAL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// check blend bits
|
|
//
|
|
if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
|
|
{
|
|
GLenum srcFactor, dstFactor;
|
|
|
|
if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
|
|
{
|
|
switch ( stateBits & GLS_SRCBLEND_BITS )
|
|
{
|
|
case GLS_SRCBLEND_ZERO:
|
|
srcFactor = GL_ZERO;
|
|
break;
|
|
case GLS_SRCBLEND_ONE:
|
|
srcFactor = GL_ONE;
|
|
break;
|
|
case GLS_SRCBLEND_DST_COLOR:
|
|
srcFactor = GL_DST_COLOR;
|
|
break;
|
|
case GLS_SRCBLEND_ONE_MINUS_DST_COLOR:
|
|
srcFactor = GL_ONE_MINUS_DST_COLOR;
|
|
break;
|
|
case GLS_SRCBLEND_SRC_ALPHA:
|
|
srcFactor = GL_SRC_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA:
|
|
srcFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_DST_ALPHA:
|
|
srcFactor = GL_DST_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA:
|
|
srcFactor = GL_ONE_MINUS_DST_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_ALPHA_SATURATE:
|
|
srcFactor = GL_SRC_ALPHA_SATURATE;
|
|
break;
|
|
default:
|
|
srcFactor = GL_ONE; // to get warning to shut up
|
|
Com_Error( ERR_DROP, "GL_State: invalid src blend state bits\n" );
|
|
break;
|
|
}
|
|
|
|
switch ( stateBits & GLS_DSTBLEND_BITS )
|
|
{
|
|
case GLS_DSTBLEND_ZERO:
|
|
dstFactor = GL_ZERO;
|
|
break;
|
|
case GLS_DSTBLEND_ONE:
|
|
dstFactor = GL_ONE;
|
|
break;
|
|
case GLS_DSTBLEND_SRC_COLOR:
|
|
dstFactor = GL_SRC_COLOR;
|
|
break;
|
|
case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR:
|
|
dstFactor = GL_ONE_MINUS_SRC_COLOR;
|
|
break;
|
|
case GLS_DSTBLEND_SRC_ALPHA:
|
|
dstFactor = GL_SRC_ALPHA;
|
|
break;
|
|
case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA:
|
|
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
break;
|
|
case GLS_DSTBLEND_DST_ALPHA:
|
|
dstFactor = GL_DST_ALPHA;
|
|
break;
|
|
case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA:
|
|
dstFactor = GL_ONE_MINUS_DST_ALPHA;
|
|
break;
|
|
default:
|
|
dstFactor = GL_ONE; // to get warning to shut up
|
|
Com_Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" );
|
|
break;
|
|
}
|
|
|
|
qglEnable( GL_BLEND );
|
|
qglBlendFunc( srcFactor, dstFactor );
|
|
}
|
|
else
|
|
{
|
|
qglDisable( GL_BLEND );
|
|
}
|
|
}
|
|
|
|
//
|
|
// check depthmask
|
|
//
|
|
if ( diff & GLS_DEPTHMASK_TRUE )
|
|
{
|
|
if ( stateBits & GLS_DEPTHMASK_TRUE )
|
|
{
|
|
qglDepthMask( GL_TRUE );
|
|
}
|
|
else
|
|
{
|
|
qglDepthMask( GL_FALSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// fill/line mode
|
|
//
|
|
if ( diff & GLS_POLYMODE_LINE )
|
|
{
|
|
if ( stateBits & GLS_POLYMODE_LINE )
|
|
{
|
|
qglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
|
|
}
|
|
else
|
|
{
|
|
qglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// depthtest
|
|
//
|
|
if ( diff & GLS_DEPTHTEST_DISABLE )
|
|
{
|
|
if ( stateBits & GLS_DEPTHTEST_DISABLE )
|
|
{
|
|
qglDisable( GL_DEPTH_TEST );
|
|
}
|
|
else
|
|
{
|
|
qglEnable( GL_DEPTH_TEST );
|
|
}
|
|
}
|
|
|
|
//
|
|
// alpha test
|
|
//
|
|
if ( diff & GLS_ATEST_BITS )
|
|
{
|
|
switch ( stateBits & GLS_ATEST_BITS )
|
|
{
|
|
case 0:
|
|
qglDisable( GL_ALPHA_TEST );
|
|
break;
|
|
case GLS_ATEST_GT_0:
|
|
qglEnable( GL_ALPHA_TEST );
|
|
qglAlphaFunc( GL_GREATER, 0.0f );
|
|
break;
|
|
case GLS_ATEST_LT_80:
|
|
qglEnable( GL_ALPHA_TEST );
|
|
qglAlphaFunc( GL_LESS, 0.5f );
|
|
break;
|
|
case GLS_ATEST_GE_80:
|
|
qglEnable( GL_ALPHA_TEST );
|
|
qglAlphaFunc( GL_GEQUAL, 0.5f );
|
|
break;
|
|
case GLS_ATEST_GE_C0:
|
|
qglEnable( GL_ALPHA_TEST );
|
|
qglAlphaFunc( GL_GEQUAL, 0.75f );
|
|
break;
|
|
default:
|
|
assert( 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
glState.glStateBits = stateBits;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
================
|
|
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;
|
|
}
|
|
|
|
|
|
void SetViewportAndScissor( void ) {
|
|
qglMatrixMode(GL_PROJECTION);
|
|
qglLoadMatrixf( backEnd.viewParms.projectionMatrix );
|
|
qglMatrixMode(GL_MODELVIEW);
|
|
|
|
// set the window clipping
|
|
qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
|
|
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
qglScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
|
|
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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 = GL_DEPTH_BUFFER_BIT;
|
|
|
|
// 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;
|
|
|
|
//
|
|
// 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
|
|
if ( r_measureOverdraw->integer || r_shadows->integer == 2 || tr_stencilled )
|
|
{
|
|
clearBits |= GL_STENCIL_BUFFER_BIT;
|
|
tr_stencilled = false;
|
|
}
|
|
|
|
if (skyboxportal)
|
|
{
|
|
if ( backEnd.refdef.rdflags & RDF_SKYBOXPORTAL )
|
|
{ // portal scene, clear whatever is necessary
|
|
if (r_fastsky->integer || (backEnd.refdef.rdflags & RDF_NOWORLDMODEL) )
|
|
{ // fastsky: clear color
|
|
// try clearing first with the portal sky fog color, then the world fog color, then finally a default
|
|
clearBits |= GL_COLOR_BUFFER_BIT;
|
|
//rwwFIXMEFIXME: Clear with fog color if there is one
|
|
qglClearColor ( 0.5, 0.5, 0.5, 1.0 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && !g_bRenderGlowingObjects )
|
|
{
|
|
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
|
|
}
|
|
}
|
|
|
|
if ( tr.refdef.rdflags & RDF_AUTOMAP || (!( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) && r_DynamicGlow->integer && !g_bRenderGlowingObjects ) )
|
|
{
|
|
if (tr.world && tr.world->globalFog != -1)
|
|
{ //this is because of a bug in multiple scenes I think, it needs to clear for the second scene but it doesn't normally.
|
|
const fog_t *fog = &tr.world->fogs[tr.world->globalFog];
|
|
|
|
clearBits |= GL_COLOR_BUFFER_BIT;
|
|
qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f );
|
|
}
|
|
}
|
|
|
|
// If this pass is to just render the glowing objects, don't clear the depth buffer since
|
|
// we're sharing it with the main scene (since the main scene has already been rendered). -AReis
|
|
if ( g_bRenderGlowingObjects )
|
|
{
|
|
clearBits &= ~GL_DEPTH_BUFFER_BIT;
|
|
}
|
|
|
|
if (clearBits)
|
|
{
|
|
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;
|
|
|
|
// clip to the plane of the portal
|
|
if ( backEnd.viewParms.isPortal ) {
|
|
float plane[4];
|
|
double plane2[4];
|
|
|
|
plane[0] = backEnd.viewParms.portalPlane.normal[0];
|
|
plane[1] = backEnd.viewParms.portalPlane.normal[1];
|
|
plane[2] = backEnd.viewParms.portalPlane.normal[2];
|
|
plane[3] = backEnd.viewParms.portalPlane.dist;
|
|
|
|
plane2[0] = DotProduct (backEnd.viewParms.ori.axis[0], plane);
|
|
plane2[1] = DotProduct (backEnd.viewParms.ori.axis[1], plane);
|
|
plane2[2] = DotProduct (backEnd.viewParms.ori.axis[2], plane);
|
|
plane2[3] = DotProduct (plane, backEnd.viewParms.ori.origin) - plane[3];
|
|
|
|
qglLoadMatrixf( s_flipMatrix );
|
|
qglClipPlane (GL_CLIP_PLANE0, plane2);
|
|
qglEnable (GL_CLIP_PLANE0);
|
|
} else {
|
|
qglDisable (GL_CLIP_PLANE0);
|
|
}
|
|
}
|
|
|
|
#define MAC_EVENT_PUMP_MSEC 5
|
|
|
|
//used by RF_DISTORTION
|
|
static inline bool R_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y)
|
|
{
|
|
int xcenter, ycenter;
|
|
vec3_t local, transformed;
|
|
vec3_t vfwd;
|
|
vec3_t vright;
|
|
vec3_t vup;
|
|
float xzi;
|
|
float yzi;
|
|
|
|
xcenter = glConfig.vidWidth / 2;
|
|
ycenter = glConfig.vidHeight / 2;
|
|
|
|
//AngleVectors (tr.refdef.viewangles, vfwd, vright, vup);
|
|
VectorCopy(tr.refdef.viewaxis[0], vfwd);
|
|
VectorCopy(tr.refdef.viewaxis[1], vright);
|
|
VectorCopy(tr.refdef.viewaxis[2], vup);
|
|
|
|
VectorSubtract (worldCoord, tr.refdef.vieworg, local);
|
|
|
|
transformed[0] = DotProduct(local,vright);
|
|
transformed[1] = DotProduct(local,vup);
|
|
transformed[2] = DotProduct(local,vfwd);
|
|
|
|
// Make sure Z is not negative.
|
|
if(transformed[2] < 0.01)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
xzi = xcenter / transformed[2] * (90.0/tr.refdef.fov_x);
|
|
yzi = ycenter / transformed[2] * (90.0/tr.refdef.fov_y);
|
|
|
|
*x = xcenter + xzi * transformed[0];
|
|
*y = ycenter - yzi * transformed[1];
|
|
|
|
return true;
|
|
}
|
|
|
|
//used by RF_DISTORTION
|
|
static inline bool R_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y )
|
|
{
|
|
float xF, yF;
|
|
bool retVal = R_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF );
|
|
*x = (int)xF;
|
|
*y = (int)yF;
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
RB_RenderDrawSurfList
|
|
==================
|
|
*/
|
|
//number of possible surfs we can postrender.
|
|
//note that postrenders lack much of the optimization that the standard sort-render crap does,
|
|
//so it's slower.
|
|
#define MAX_POST_RENDERS 128
|
|
|
|
typedef struct
|
|
{
|
|
int fogNum;
|
|
int entNum;
|
|
int dlighted;
|
|
int depthRange;
|
|
drawSurf_t *drawSurf;
|
|
shader_t *shader;
|
|
qboolean eValid;
|
|
} postRender_t;
|
|
|
|
static postRender_t g_postRenders[MAX_POST_RENDERS];
|
|
static int g_numPostRenders = 0;
|
|
|
|
//get the "average" (ideally center) position of a surface on the tess.
|
|
//this is a kind of lame method because I can't think correctly right now.
|
|
static inline bool R_AverageTessXYZ(vec3_t dest)
|
|
{
|
|
int i = 1;
|
|
float bd = 0.0f;
|
|
float d = 0.0f;
|
|
int b = -1;
|
|
vec3_t v;
|
|
|
|
while (i < tess.numVertexes)
|
|
{
|
|
VectorSubtract(tess.xyz[i], tess.xyz[i], v);
|
|
d = VectorLength(v);
|
|
if (b == -1 || d < bd)
|
|
{
|
|
b = i;
|
|
bd = d;
|
|
}
|
|
i++;
|
|
}
|
|
if (b != -1)
|
|
{
|
|
VectorSubtract(tess.xyz[0], tess.xyz[b], v);
|
|
|
|
VectorScale(v, 0.5f, dest);
|
|
VectorAdd(dest, tess.xyz[0], dest);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void RB_RenderDrawSurfList( drawSurf_t *drawSurfs, int numDrawSurfs ) {
|
|
shader_t *shader, *oldShader;
|
|
int fogNum, oldFogNum;
|
|
int entityNum, oldEntityNum;
|
|
int dlighted, oldDlighted;
|
|
int depthRange, oldDepthRange;
|
|
int i;
|
|
drawSurf_t *drawSurf;
|
|
unsigned int oldSort;
|
|
float originalTime;
|
|
trRefEntity_t *curEnt;
|
|
postRender_t *pRender;
|
|
bool didShadowPass = false;
|
|
#ifdef __MACOS__
|
|
int macEventTime;
|
|
|
|
Sys_PumpEvents(); // crutch up the mac's limited buffer queue size
|
|
|
|
// we don't want to pump the event loop too often and waste time, so
|
|
// we are going to check every shader change
|
|
macEventTime = Sys_Milliseconds()*com_timescale->value + MAC_EVENT_PUMP_MSEC;
|
|
#endif
|
|
|
|
if (g_bRenderGlowingObjects)
|
|
{ //only shadow on initial passes
|
|
didShadowPass = true;
|
|
}
|
|
|
|
// save original time for entity shader offsets
|
|
originalTime = backEnd.refdef.floatTime;
|
|
|
|
// clear the z buffer, set the modelview, etc
|
|
RB_BeginDrawingView ();
|
|
|
|
// draw everything
|
|
oldEntityNum = -1;
|
|
backEnd.currentEntity = &tr.worldEntity;
|
|
oldShader = NULL;
|
|
oldFogNum = -1;
|
|
oldDepthRange = qfalse;
|
|
oldDlighted = qfalse;
|
|
oldSort = (unsigned int) -1;
|
|
depthRange = qfalse;
|
|
|
|
backEnd.pc.c_surfaces += numDrawSurfs;
|
|
|
|
for (i = 0, drawSurf = drawSurfs ; i < numDrawSurfs ; i++, drawSurf++)
|
|
{
|
|
if ( drawSurf->sort == oldSort )
|
|
{
|
|
// fast path, same as previous sort
|
|
rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
|
|
continue;
|
|
}
|
|
R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlighted );
|
|
|
|
#ifndef _XBOX // GLOWXXX
|
|
// If we're rendering glowing objects, but this shader has no stages with glow, skip it!
|
|
if ( g_bRenderGlowingObjects && !shader->hasGlow )
|
|
{
|
|
shader = oldShader;
|
|
entityNum = oldEntityNum;
|
|
fogNum = oldFogNum;
|
|
dlighted = oldDlighted;
|
|
continue;
|
|
}
|
|
#endif
|
|
oldSort = drawSurf->sort;
|
|
|
|
//
|
|
// 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 (entityNum != TR_WORLDENT &&
|
|
g_numPostRenders < MAX_POST_RENDERS)
|
|
{
|
|
if ( (backEnd.refdef.entities[entityNum].e.renderfx & RF_DISTORTION) ||
|
|
(backEnd.refdef.entities[entityNum].e.renderfx & RF_FORCEPOST) ||
|
|
(backEnd.refdef.entities[entityNum].e.renderfx & RF_FORCE_ENT_ALPHA) )
|
|
{ //must render last
|
|
curEnt = &backEnd.refdef.entities[entityNum];
|
|
pRender = &g_postRenders[g_numPostRenders];
|
|
|
|
g_numPostRenders++;
|
|
|
|
depthRange = 0;
|
|
//figure this stuff out now and store it
|
|
if ( curEnt->e.renderfx & RF_NODEPTH )
|
|
{
|
|
depthRange = 2;
|
|
}
|
|
else if ( curEnt->e.renderfx & RF_DEPTHHACK )
|
|
{
|
|
depthRange = 1;
|
|
}
|
|
pRender->depthRange = depthRange;
|
|
|
|
//It is not necessary to update the old* values because
|
|
//we are not updating now with the current values.
|
|
depthRange = oldDepthRange;
|
|
|
|
//store off the ent num
|
|
pRender->entNum = entityNum;
|
|
|
|
//remember the other values necessary for rendering this surf
|
|
pRender->drawSurf = drawSurf;
|
|
pRender->dlighted = dlighted;
|
|
pRender->fogNum = fogNum;
|
|
pRender->shader = shader;
|
|
|
|
/*
|
|
if (shader == tr.distortionShader)
|
|
{
|
|
pRender->eValid = qfalse;
|
|
}
|
|
else
|
|
*/
|
|
{
|
|
pRender->eValid = qtrue;
|
|
}
|
|
|
|
//assure the info is back to the last set state
|
|
shader = oldShader;
|
|
entityNum = oldEntityNum;
|
|
fogNum = oldFogNum;
|
|
dlighted = oldDlighted;
|
|
|
|
oldSort = -20; //invalidate this thing, cause we may want to postrender more surfs of the same sort
|
|
|
|
//continue without bothering to begin a draw surf
|
|
continue;
|
|
}
|
|
}
|
|
/*
|
|
else if (shader == tr.distortionShader &&
|
|
g_numPostRenders < MAX_POST_RENDERS)
|
|
{ //not an ent, just a surface that needs this effect
|
|
pRender = &g_postRenders[g_numPostRenders];
|
|
|
|
g_numPostRenders++;
|
|
|
|
depthRange = 0;
|
|
pRender->depthRange = depthRange;
|
|
|
|
//It is not necessary to update the old* values because
|
|
//we are not updating now with the current values.
|
|
depthRange = oldDepthRange;
|
|
|
|
//store off the ent num
|
|
pRender->entNum = entityNum;
|
|
|
|
//remember the other values necessary for rendering this surf
|
|
pRender->drawSurf = drawSurf;
|
|
pRender->dlighted = dlighted;
|
|
pRender->fogNum = fogNum;
|
|
pRender->shader = shader;
|
|
|
|
pRender->eValid = qfalse;
|
|
|
|
//assure the info is back to the last set state
|
|
shader = oldShader;
|
|
entityNum = oldEntityNum;
|
|
fogNum = oldFogNum;
|
|
dlighted = oldDlighted;
|
|
|
|
oldSort = -20; //invalidate this thing, cause we may want to postrender more surfs of the same sort
|
|
|
|
//continue without bothering to begin a draw surf
|
|
continue;
|
|
}
|
|
*/
|
|
|
|
if (shader != oldShader || fogNum != oldFogNum || dlighted != oldDlighted
|
|
|| ( entityNum != oldEntityNum && !shader->entityMergable ) )
|
|
{
|
|
if (oldShader != NULL) {
|
|
#ifdef __MACOS__ // crutch up the mac's limited buffer queue size
|
|
int t;
|
|
|
|
t = Sys_Milliseconds()*com_timescale->value;
|
|
if ( t > macEventTime ) {
|
|
macEventTime = t + MAC_EVENT_PUMP_MSEC;
|
|
Sys_PumpEvents();
|
|
}
|
|
#endif
|
|
RB_EndSurface();
|
|
|
|
if (!didShadowPass && shader && shader->sort > SS_BANNER)
|
|
{
|
|
RB_ShadowFinish();
|
|
didShadowPass = true;
|
|
}
|
|
}
|
|
RB_BeginSurface( shader, fogNum );
|
|
oldShader = shader;
|
|
oldFogNum = fogNum;
|
|
oldDlighted = dlighted;
|
|
}
|
|
|
|
//
|
|
// change the modelview matrix if needed
|
|
//
|
|
if ( entityNum != oldEntityNum ) {
|
|
depthRange = 0;
|
|
|
|
if ( entityNum != TR_WORLDENT ) {
|
|
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.ori );
|
|
|
|
// set up the dynamic lighting if needed
|
|
if ( backEnd.currentEntity->needDlights ) {
|
|
#ifdef VV_LIGHTING
|
|
VVLightMan.R_TransformDlights( &backEnd.ori );
|
|
#else
|
|
R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori );
|
|
#endif
|
|
}
|
|
|
|
if ( backEnd.currentEntity->e.renderfx & RF_NODEPTH ) {
|
|
// No depth at all, very rare but some things for seeing through walls
|
|
depthRange = 2;
|
|
}
|
|
else if ( backEnd.currentEntity->e.renderfx & RF_DEPTHHACK ) {
|
|
// hack the depth range to prevent view model from poking into walls
|
|
depthRange = 1;
|
|
}
|
|
} else {
|
|
backEnd.currentEntity = &tr.worldEntity;
|
|
backEnd.refdef.floatTime = originalTime;
|
|
backEnd.ori = 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;
|
|
#ifdef VV_LIGHTING
|
|
VVLightMan.R_TransformDlights( &backEnd.ori );
|
|
#else
|
|
R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori );
|
|
#endif
|
|
}
|
|
|
|
qglLoadMatrixf( backEnd.ori.modelMatrix );
|
|
|
|
//
|
|
// change depthrange if needed
|
|
//
|
|
if ( oldDepthRange != depthRange ) {
|
|
switch ( depthRange ) {
|
|
default:
|
|
case 0:
|
|
qglDepthRange (0, 1);
|
|
break;
|
|
|
|
case 1:
|
|
qglDepthRange (0, .3);
|
|
break;
|
|
|
|
case 2:
|
|
qglDepthRange (0, 0);
|
|
break;
|
|
}
|
|
|
|
oldDepthRange = depthRange;
|
|
}
|
|
|
|
oldEntityNum = entityNum;
|
|
}
|
|
|
|
// add the triangles for this surface
|
|
rb_surfaceTable[ *drawSurf->surface ]( drawSurf->surface );
|
|
}
|
|
|
|
backEnd.refdef.floatTime = originalTime;
|
|
|
|
// draw the contents of the last shader batch
|
|
//assert(entityNum < MAX_GENTITIES);
|
|
|
|
if (oldShader != NULL) {
|
|
RB_EndSurface();
|
|
}
|
|
|
|
#ifdef _CRAZY_ATTRIB_DEBUG
|
|
qglPopAttrib();
|
|
glState.glStateBits = -1;
|
|
#endif
|
|
|
|
if (tr_stencilled && tr_distortionPrePost)
|
|
{ //ok, cap it now
|
|
RB_CaptureScreenImage();
|
|
RB_DistortionFill();
|
|
}
|
|
|
|
//render distortion surfs (or anything else that needs to be post-rendered)
|
|
if (g_numPostRenders > 0)
|
|
{
|
|
int lastPostEnt = -1;
|
|
|
|
while (g_numPostRenders > 0)
|
|
{
|
|
g_numPostRenders--;
|
|
pRender = &g_postRenders[g_numPostRenders];
|
|
|
|
RB_BeginSurface( pRender->shader, pRender->fogNum );
|
|
|
|
/*
|
|
if (!pRender->eValid && pRender->entNum == TR_WORLDENT)
|
|
{ //world/other surface
|
|
backEnd.currentEntity = &tr.worldEntity;
|
|
backEnd.refdef.floatTime = originalTime;
|
|
backEnd.ori = 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;
|
|
|
|
#ifdef VV_LIGHTING
|
|
VVLightMan.R_TransformDlights( &backEnd.ori );
|
|
#else
|
|
R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori );
|
|
#endif
|
|
}
|
|
else
|
|
*/
|
|
{ //ent
|
|
backEnd.currentEntity = &backEnd.refdef.entities[pRender->entNum];
|
|
|
|
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.ori );
|
|
|
|
// set up the dynamic lighting if needed
|
|
if ( backEnd.currentEntity->needDlights ) {
|
|
#ifdef VV_LIGHTING
|
|
VVLightMan.R_TransformDlights( &backEnd.ori );
|
|
#else
|
|
R_TransformDlights( backEnd.refdef.num_dlights, backEnd.refdef.dlights, &backEnd.ori );
|
|
#endif
|
|
}
|
|
}
|
|
|
|
qglLoadMatrixf( backEnd.ori.modelMatrix );
|
|
|
|
depthRange = pRender->depthRange;
|
|
switch ( depthRange )
|
|
{
|
|
default:
|
|
case 0:
|
|
qglDepthRange (0, 1);
|
|
break;
|
|
|
|
case 1:
|
|
qglDepthRange (0, .3);
|
|
break;
|
|
|
|
case 2:
|
|
qglDepthRange (0, 0);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
if (!pRender->eValid)
|
|
{ //special full-screen "distortion" (or refraction or whatever the heck you want to call it)
|
|
if (!tr_stencilled)
|
|
{ //only need to do this once every frame (that a surface using it is around)
|
|
int radX = 2048;
|
|
int radY = 2048;
|
|
int x = glConfig.vidWidth/2;
|
|
int y = glConfig.vidHeight/2;
|
|
int cX, cY;
|
|
|
|
GL_Bind( tr.screenImage );
|
|
//using this method, we could pixel-filter the texture and all sorts of crazy stuff.
|
|
//but, it is slow as hell.
|
|
#if 0
|
|
static byte *tmp = NULL;
|
|
if (!tmp)
|
|
{
|
|
tmp = (byte *)Z_Malloc((sizeof(byte)*4)*(glConfig.vidWidth*glConfig.vidHeight), TAG_ICARUS, qtrue);
|
|
}
|
|
qglReadPixels(0, 0, glConfig.vidWidth, glConfig.vidHeight, GL_RGBA, GL_UNSIGNED_BYTE, tmp);
|
|
qglTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 512, 512, 0, GL_RGBA, GL_UNSIGNED_BYTE, tmp);
|
|
#endif
|
|
|
|
if (radX > glConfig.maxTextureSize)
|
|
{
|
|
radX = glConfig.maxTextureSize;
|
|
}
|
|
if (radY > glConfig.maxTextureSize)
|
|
{
|
|
radY = glConfig.maxTextureSize;
|
|
}
|
|
|
|
while (glConfig.vidWidth < radX)
|
|
{
|
|
radX /= 2;
|
|
}
|
|
while (glConfig.vidHeight < radY)
|
|
{
|
|
radY /= 2;
|
|
}
|
|
|
|
cX = x-(radX/2);
|
|
cY = y-(radY/2);
|
|
|
|
if (cX+radX > glConfig.vidWidth)
|
|
{ //would it go off screen?
|
|
cX = glConfig.vidWidth-radX;
|
|
}
|
|
else if (cX < 0)
|
|
{ //cap it off at 0
|
|
cX = 0;
|
|
}
|
|
|
|
if (cY+radY > glConfig.vidHeight)
|
|
{ //would it go off screen?
|
|
cY = glConfig.vidHeight-radY;
|
|
}
|
|
else if (cY < 0)
|
|
{ //cap it off at 0
|
|
cY = 0;
|
|
}
|
|
|
|
qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, radX, radY, 0);
|
|
}
|
|
lastPostEnt = ENTITYNUM_NONE;
|
|
}
|
|
*/
|
|
if (!pRender->eValid)
|
|
{
|
|
}
|
|
else if ((backEnd.refdef.entities[pRender->entNum].e.renderfx & RF_DISTORTION) &&
|
|
lastPostEnt != pRender->entNum)
|
|
{ //do the capture now, we only need to do it once per ent
|
|
int x, y;
|
|
int rad;
|
|
bool r;
|
|
//We are going to just bind this, and then the CopyTexImage is going to
|
|
//stomp over this texture num in texture memory.
|
|
GL_Bind( tr.screenImage );
|
|
|
|
#if 0 //yeah.. this kinda worked but it was stupid
|
|
if (pRender->eValid)
|
|
{
|
|
r = R_WorldCoordToScreenCoord( backEnd.currentEntity->e.origin, &x, &y );
|
|
rad = backEnd.currentEntity->e.radius;
|
|
}
|
|
else
|
|
{
|
|
vec3_t v;
|
|
//probably a little bit expensive.. but we're doing this for looks, not speed!
|
|
if (!R_AverageTessXYZ(v))
|
|
{ //failed, just use first vert I guess
|
|
VectorCopy(tess.xyz[0], v);
|
|
}
|
|
r = R_WorldCoordToScreenCoord( v, &x, &y );
|
|
rad = 256;
|
|
}
|
|
#else
|
|
r = R_WorldCoordToScreenCoord( backEnd.currentEntity->e.origin, &x, &y );
|
|
rad = backEnd.currentEntity->e.radius;
|
|
#endif
|
|
|
|
if (r)
|
|
{
|
|
int cX, cY;
|
|
cX = glConfig.vidWidth-x-(rad/2);
|
|
cY = glConfig.vidHeight-y-(rad/2);
|
|
|
|
if (cX+rad > glConfig.vidWidth)
|
|
{ //would it go off screen?
|
|
cX = glConfig.vidWidth-rad;
|
|
}
|
|
else if (cX < 0)
|
|
{ //cap it off at 0
|
|
cX = 0;
|
|
}
|
|
|
|
if (cY+rad > glConfig.vidHeight)
|
|
{ //would it go off screen?
|
|
cY = glConfig.vidHeight-rad;
|
|
}
|
|
else if (cY < 0)
|
|
{ //cap it off at 0
|
|
cY = 0;
|
|
}
|
|
|
|
//now copy a portion of the screen to this texture
|
|
#ifdef _XBOX
|
|
qglCopyBackBufferToTexEXT(rad, rad, cX, (480 - cY), (cX + rad), (480 - (cY + rad)));
|
|
#else
|
|
qglCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, cX, cY, rad, rad, 0);
|
|
#endif
|
|
|
|
lastPostEnt = pRender->entNum;
|
|
}
|
|
}
|
|
|
|
rb_surfaceTable[ *pRender->drawSurf->surface ]( pRender->drawSurf->surface );
|
|
RB_EndSurface();
|
|
}
|
|
}
|
|
|
|
// go back to the world modelview matrix
|
|
qglLoadMatrixf( backEnd.viewParms.world.modelMatrix );
|
|
if ( depthRange ) {
|
|
qglDepthRange (0, 1);
|
|
}
|
|
|
|
#if 0
|
|
RB_DrawSun();
|
|
#endif
|
|
if (tr_stencilled && !tr_distortionPrePost)
|
|
{ //draw in the stencil buffer's cutout
|
|
RB_DistortionFill();
|
|
}
|
|
if (!didShadowPass)
|
|
{
|
|
// darken down any stencil shadows
|
|
RB_ShadowFinish();
|
|
didShadowPass = true;
|
|
}
|
|
|
|
#ifdef _XBOX
|
|
if (Cvar_VariableIntegerValue("r_hdreffect"))
|
|
HDREffect.Render();
|
|
#endif
|
|
|
|
// add light flares on lights that aren't obscured
|
|
|
|
// rww - 9-13-01 [1-26-01-sof2]
|
|
// RB_RenderFlares();
|
|
|
|
#ifdef __MACOS__
|
|
Sys_PumpEvents(); // crutch up the mac's limited buffer queue size
|
|
#endif
|
|
}
|
|
|
|
|
|
/*
|
|
============================================================================
|
|
|
|
RENDER BACK END THREAD FUNCTIONS
|
|
|
|
============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
RB_SetGL2D
|
|
|
|
================
|
|
*/
|
|
void RB_SetGL2D (void) {
|
|
backEnd.projection2D = qtrue;
|
|
|
|
// set 2D virtual screen size
|
|
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
qglMatrixMode(GL_PROJECTION);
|
|
qglLoadIdentity ();
|
|
#ifdef _XBOX
|
|
qglOrtho (0, 640, 0, 480, 0, 1);
|
|
#else
|
|
qglOrtho (0, 640, 480, 0, 0, 1);
|
|
#endif
|
|
qglMatrixMode(GL_MODELVIEW);
|
|
qglLoadIdentity ();
|
|
|
|
GL_State( GLS_DEPTHTEST_DISABLE |
|
|
GLS_SRCBLEND_SRC_ALPHA |
|
|
GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
|
|
|
|
qglDisable( GL_CULL_FACE );
|
|
qglDisable( GL_CLIP_PLANE0 );
|
|
|
|
// set time for 2D shaders
|
|
backEnd.refdef.time = Sys_Milliseconds()*com_timescale->value;
|
|
backEnd.refdef.floatTime = backEnd.refdef.time * 0.001f;
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
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 start, end;
|
|
|
|
if ( !tr.registered ) {
|
|
return;
|
|
}
|
|
R_SyncRenderThread();
|
|
|
|
// we definately want to sync every frame for the cinematics
|
|
qglFinish();
|
|
|
|
start = end = 0;
|
|
if ( r_speeds->integer ) {
|
|
start = Sys_Milliseconds()*com_timescale->value;
|
|
}
|
|
|
|
// make sure rows and cols are powers of 2
|
|
if ( (cols&(cols-1)) || (rows&(rows-1)) )
|
|
{
|
|
Com_Error (ERR_DROP, "Draw_StretchRaw: size not a power of 2: %i by %i", cols, rows);
|
|
}
|
|
|
|
GL_Bind( tr.scratchImage[client] );
|
|
|
|
// if the scratchImage isn't in the format we want, specify it as a new texture
|
|
if ( cols != tr.scratchImage[client]->width || rows != tr.scratchImage[client]->height ) {
|
|
tr.scratchImage[client]->width = cols;
|
|
tr.scratchImage[client]->height = rows;
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
|
|
} else {
|
|
if (dirty) {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression
|
|
qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
}
|
|
}
|
|
|
|
if ( r_speeds->integer ) {
|
|
end = Sys_Milliseconds()*com_timescale->value;
|
|
Com_Printf ("qglTexSubImage2D %i, %i: %i msec\n", cols, rows, end - start );
|
|
}
|
|
|
|
RB_SetGL2D();
|
|
|
|
qglColor3f( tr.identityLight, tr.identityLight, tr.identityLight );
|
|
|
|
qglBegin (GL_QUADS);
|
|
qglTexCoord2f ( 0.5f / cols, 0.5f / rows );
|
|
qglVertex2f (x, y);
|
|
qglTexCoord2f ( ( cols - 0.5f ) / cols , 0.5f / rows );
|
|
qglVertex2f (x+w, y);
|
|
qglTexCoord2f ( ( cols - 0.5f ) / cols, ( rows - 0.5f ) / rows );
|
|
qglVertex2f (x+w, y+h);
|
|
qglTexCoord2f ( 0.5f / cols, ( rows - 0.5f ) / rows );
|
|
qglVertex2f (x, y+h);
|
|
qglEnd ();
|
|
}
|
|
|
|
void RE_UploadCinematic (int 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]->width = cols;
|
|
tr.scratchImage[client]->height = tr.scratchImage[client]->height = rows;
|
|
#ifdef _XBOX
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB5, cols, rows, 0, GL_RGB_SWIZZLE_EXT, GL_UNSIGNED_BYTE, data );
|
|
#else
|
|
qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, cols, rows, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
#endif
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
|
|
qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
|
|
} else {
|
|
if (dirty) {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression
|
|
#ifdef _XBOX
|
|
qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGB_SWIZZLE_EXT, GL_UNSIGNED_BYTE, data );
|
|
#else
|
|
qglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, cols, rows, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
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;
|
|
|
|
if ( !backEnd.projection2D ) {
|
|
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;
|
|
|
|
*(int *)tess.vertexColors[ numVerts ] =
|
|
*(int *)tess.vertexColors[ numVerts + 1 ] =
|
|
*(int *)tess.vertexColors[ numVerts + 2 ] =
|
|
*(int *)tess.vertexColors[ numVerts + 3 ] = *(int *)backEnd.color2D;
|
|
|
|
tess.xyz[ numVerts ][0] = cmd->x;
|
|
tess.xyz[ numVerts ][1] = cmd->y;
|
|
tess.xyz[ numVerts ][2] = 0;
|
|
|
|
tess.texCoords[ numVerts ][0][0] = cmd->s1;
|
|
tess.texCoords[ numVerts ][0][1] = cmd->t1;
|
|
|
|
tess.xyz[ numVerts + 1 ][0] = cmd->x + cmd->w;
|
|
tess.xyz[ numVerts + 1 ][1] = cmd->y;
|
|
tess.xyz[ numVerts + 1 ][2] = 0;
|
|
|
|
tess.texCoords[ numVerts + 1 ][0][0] = cmd->s2;
|
|
tess.texCoords[ numVerts + 1 ][0][1] = cmd->t1;
|
|
|
|
tess.xyz[ numVerts + 2 ][0] = cmd->x + cmd->w;
|
|
tess.xyz[ numVerts + 2 ][1] = cmd->y + cmd->h;
|
|
tess.xyz[ numVerts + 2 ][2] = 0;
|
|
|
|
tess.texCoords[ numVerts + 2 ][0][0] = cmd->s2;
|
|
tess.texCoords[ numVerts + 2 ][0][1] = cmd->t2;
|
|
|
|
tess.xyz[ numVerts + 3 ][0] = cmd->x;
|
|
tess.xyz[ numVerts + 3 ][1] = cmd->y + cmd->h;
|
|
tess.xyz[ numVerts + 3 ][2] = 0;
|
|
|
|
tess.texCoords[ numVerts + 3 ][0][0] = cmd->s1;
|
|
tess.texCoords[ numVerts + 3 ][0][1] = cmd->t2;
|
|
|
|
return (const void *)(cmd + 1);
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
RB_DrawRotatePic
|
|
=============
|
|
*/
|
|
const void *RB_RotatePic ( const void *data )
|
|
{
|
|
const rotatePicCommand_t *cmd;
|
|
image_t *image;
|
|
shader_t *shader;
|
|
|
|
cmd = (const rotatePicCommand_t *)data;
|
|
|
|
shader = cmd->shader;
|
|
image = &shader->stages[0].bundle[0].image[0];
|
|
|
|
if ( image ) {
|
|
if ( !backEnd.projection2D ) {
|
|
RB_SetGL2D();
|
|
}
|
|
|
|
qglColor4ubv( backEnd.color2D );
|
|
qglPushMatrix();
|
|
|
|
qglTranslatef(cmd->x+cmd->w,cmd->y,0);
|
|
qglRotatef(cmd->a, 0.0, 0.0, 1.0);
|
|
|
|
GL_Bind( image );
|
|
#ifdef _XBOX
|
|
qglBeginEXT (GL_QUADS, 4, 0, 0, 4, 0);
|
|
#else
|
|
qglBegin (GL_QUADS);
|
|
#endif
|
|
qglTexCoord2f( cmd->s1, cmd->t1);
|
|
qglVertex2f( -cmd->w, 0 );
|
|
qglTexCoord2f( cmd->s2, cmd->t1 );
|
|
qglVertex2f( 0, 0 );
|
|
qglTexCoord2f( cmd->s2, cmd->t2 );
|
|
qglVertex2f( 0, cmd->h );
|
|
qglTexCoord2f( cmd->s1, cmd->t2 );
|
|
qglVertex2f( -cmd->w, cmd->h );
|
|
qglEnd();
|
|
|
|
qglPopMatrix();
|
|
}
|
|
|
|
return (const void *)(cmd + 1);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
RB_DrawRotatePic2
|
|
=============
|
|
*/
|
|
const void *RB_RotatePic2 ( const void *data )
|
|
{
|
|
const rotatePicCommand_t *cmd;
|
|
image_t *image;
|
|
shader_t *shader;
|
|
|
|
cmd = (const rotatePicCommand_t *)data;
|
|
|
|
shader = cmd->shader;
|
|
|
|
if ( shader->numUnfoggedPasses )
|
|
{
|
|
image = &shader->stages[0].bundle[0].image[0];
|
|
|
|
if ( image )
|
|
{
|
|
if ( !backEnd.projection2D )
|
|
{
|
|
RB_SetGL2D();
|
|
}
|
|
|
|
// Get our current blend mode, etc.
|
|
GL_State( shader->stages[0].stateBits );
|
|
|
|
qglColor4ubv( backEnd.color2D );
|
|
qglPushMatrix();
|
|
|
|
// rotation point is going to be around the center of the passed in coordinates
|
|
qglTranslatef( cmd->x, cmd->y, 0 );
|
|
qglRotatef( cmd->a, 0.0, 0.0, 1.0 );
|
|
|
|
GL_Bind( image );
|
|
#ifdef _XBOX
|
|
qglBeginEXT( GL_QUADS, 4, 0, 0, 4, 0);
|
|
#else
|
|
qglBegin( GL_QUADS );
|
|
#endif
|
|
qglTexCoord2f( cmd->s1, cmd->t1);
|
|
qglVertex2f( -cmd->w * 0.5f, -cmd->h * 0.5f );
|
|
|
|
qglTexCoord2f( cmd->s2, cmd->t1 );
|
|
qglVertex2f( cmd->w * 0.5f, -cmd->h * 0.5f );
|
|
|
|
qglTexCoord2f( cmd->s2, cmd->t2 );
|
|
qglVertex2f( cmd->w * 0.5f, cmd->h * 0.5f );
|
|
|
|
qglTexCoord2f( cmd->s1, cmd->t2 );
|
|
qglVertex2f( -cmd->w * 0.5f, cmd->h * 0.5f );
|
|
qglEnd();
|
|
|
|
qglPopMatrix();
|
|
|
|
// Hmmm, this is not too cool
|
|
GL_State( GLS_DEPTHTEST_DISABLE |
|
|
GLS_SRCBLEND_SRC_ALPHA |
|
|
GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
|
|
|
|
// Dynamic Glow/Flares:
|
|
/*
|
|
The basic idea is to render the glowing parts of the scene to an offscreen buffer, then take
|
|
that buffer and blur it. After it is sufficiently blurred, re-apply that image back to
|
|
the normal screen using a additive blending. To blur the scene I use a vertex program to supply
|
|
four texture coordinate offsets that allow 'peeking' into adjacent pixels. In the register
|
|
combiner (pixel shader), I combine the adjacent pixels using a weighting factor. - Aurelio
|
|
*/
|
|
|
|
// Render dynamic glowing/flaring objects.
|
|
#ifndef _XBOX // GLOWXXX
|
|
if ( !(backEnd.refdef.rdflags & RDF_NOWORLDMODEL) && g_bDynamicGlowSupported && r_DynamicGlow->integer )
|
|
{
|
|
// Copy the normal scene to texture.
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage );
|
|
qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
|
|
// Just clear colors, but leave the depth buffer intact so we can 'share' it.
|
|
qglClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
|
|
qglClear( GL_COLOR_BUFFER_BIT );
|
|
|
|
// Render the glowing objects.
|
|
g_bRenderGlowingObjects = true;
|
|
RB_RenderDrawSurfList( cmd->drawSurfs, cmd->numDrawSurfs );
|
|
g_bRenderGlowingObjects = false;
|
|
|
|
qglFinish();
|
|
|
|
// Copy the glow scene to texture.
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.screenGlow );
|
|
qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
|
|
// Resize the viewport to the blur texture size.
|
|
const int oldViewWidth = backEnd.viewParms.viewportWidth;
|
|
const int oldViewHeight = backEnd.viewParms.viewportHeight;
|
|
backEnd.viewParms.viewportWidth = r_DynamicGlowWidth->integer;
|
|
backEnd.viewParms.viewportHeight = r_DynamicGlowHeight->integer;
|
|
SetViewportAndScissor();
|
|
|
|
// Blur the scene.
|
|
RB_BlurGlowTexture();
|
|
|
|
// Copy the finished glow scene back to texture.
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage );
|
|
qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
|
|
// Set the viewport back to normal.
|
|
backEnd.viewParms.viewportWidth = oldViewWidth;
|
|
backEnd.viewParms.viewportHeight = oldViewHeight;
|
|
SetViewportAndScissor();
|
|
qglClear( GL_COLOR_BUFFER_BIT );
|
|
|
|
// Draw the glow additively over the screen.
|
|
RB_DrawGlowOverlay();
|
|
}
|
|
#endif // _XBOX
|
|
|
|
return (const void *)(cmd + 1);
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
RB_DrawBuffer
|
|
|
|
=============
|
|
*/
|
|
const void *RB_DrawBuffer( const void *data ) {
|
|
const drawBufferCommand_t *cmd;
|
|
|
|
cmd = (const drawBufferCommand_t *)data;
|
|
|
|
qglDrawBuffer( cmd->buffer );
|
|
|
|
// clear screen for debugging
|
|
if (tr.world && tr.world->globalFog != -1)
|
|
{
|
|
const fog_t *fog = &tr.world->fogs[tr.world->globalFog];
|
|
|
|
qglClearColor(fog->parms.color[0], fog->parms.color[1], fog->parms.color[2], 1.0f );
|
|
qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
|
|
}
|
|
else if ( r_clear->integer ) {
|
|
int i = r_clear->integer;
|
|
if (i == 42) {
|
|
i = Q_irand(0,8);
|
|
}
|
|
switch (i)
|
|
{
|
|
default:
|
|
qglClearColor( 1, 0, 0.5, 1 );
|
|
break;
|
|
case 1:
|
|
qglClearColor( 1.0, 0.0, 0.0, 1.0); //red
|
|
break;
|
|
case 2:
|
|
qglClearColor( 0.0, 1.0, 0.0, 1.0); //green
|
|
break;
|
|
case 3:
|
|
qglClearColor( 1.0, 1.0, 0.0, 1.0); //yellow
|
|
break;
|
|
case 4:
|
|
qglClearColor( 0.0, 0.0, 1.0, 1.0); //blue
|
|
break;
|
|
case 5:
|
|
qglClearColor( 0.0, 1.0, 1.0, 1.0); //cyan
|
|
break;
|
|
case 6:
|
|
qglClearColor( 1.0, 0.0, 1.0, 1.0); //magenta
|
|
break;
|
|
case 7:
|
|
qglClearColor( 1.0, 1.0, 1.0, 1.0); //white
|
|
break;
|
|
case 8:
|
|
qglClearColor( 0.0, 0.0, 0.0, 1.0); //black
|
|
break;
|
|
}
|
|
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 ) {
|
|
image_t *image;
|
|
float x, y, w, h;
|
|
// int start, end;
|
|
|
|
if ( !backEnd.projection2D ) {
|
|
RB_SetGL2D();
|
|
}
|
|
|
|
qglClear( GL_COLOR_BUFFER_BIT );
|
|
|
|
qglFinish();
|
|
|
|
// start = Sys_Milliseconds()*com_timescale->value;
|
|
|
|
|
|
int i=0;
|
|
R_Images_StartIteration();
|
|
while ( (image = R_Images_GetNextIteration()) != NULL)
|
|
{
|
|
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->width / 512.0;
|
|
h *= image->height / 512.0;
|
|
}
|
|
|
|
GL_Bind( image );
|
|
#ifdef _XBOX
|
|
qglBeginEXT (GL_QUADS, 4, 0, 0, 4, 0);
|
|
#else
|
|
qglBegin (GL_QUADS);
|
|
#endif
|
|
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();
|
|
i++;
|
|
}
|
|
|
|
qglFinish();
|
|
|
|
// end = Sys_Milliseconds()*com_timescale->value;
|
|
// Com_Printf ("%i msec to draw all images\n", end - start );
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
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
|
|
#ifndef _XBOX
|
|
if ( r_measureOverdraw->integer ) {
|
|
int i;
|
|
long sum = 0;
|
|
unsigned char *stencilReadback;
|
|
|
|
stencilReadback = (unsigned char *)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;
|
|
Hunk_FreeTempMemory( stencilReadback );
|
|
}
|
|
#endif
|
|
|
|
if ( !glState.finishCalled ) {
|
|
qglFinish();
|
|
}
|
|
|
|
GLimp_LogComment( "***************** RB_SwapBuffers *****************\n\n\n" );
|
|
|
|
GLimp_EndFrame();
|
|
|
|
backEnd.projection2D = qfalse;
|
|
|
|
return (const void *)(cmd + 1);
|
|
}
|
|
|
|
const void *RB_WorldEffects( const void *data )
|
|
{
|
|
const drawBufferCommand_t *cmd;
|
|
|
|
cmd = (const drawBufferCommand_t *)data;
|
|
|
|
// Always flush the tess buffer
|
|
if ( tess.shader && tess.numIndexes )
|
|
{
|
|
RB_EndSurface();
|
|
}
|
|
RB_RenderWorldEffects();
|
|
|
|
if(tess.shader)
|
|
{
|
|
RB_BeginSurface( tess.shader, tess.fogNum );
|
|
}
|
|
|
|
return (const void *)(cmd + 1);
|
|
}
|
|
|
|
/*
|
|
====================
|
|
RB_ExecuteRenderCommands
|
|
|
|
This function will be called syncronously if running without
|
|
smp extensions, or asyncronously by another thread.
|
|
====================
|
|
*/
|
|
extern const void *R_DrawWireframeAutomap(const void *data); //tr_world.cpp
|
|
void RB_ExecuteRenderCommands( const void *data ) {
|
|
int t1, t2;
|
|
|
|
t1 = Sys_Milliseconds()*com_timescale->value;
|
|
|
|
while ( 1 ) {
|
|
switch ( *(const int *)data ) {
|
|
case RC_SET_COLOR:
|
|
data = RB_SetColor( data );
|
|
break;
|
|
case RC_STRETCH_PIC:
|
|
data = RB_StretchPic( data );
|
|
break;
|
|
case RC_ROTATE_PIC:
|
|
data = RB_RotatePic( data );
|
|
break;
|
|
case RC_ROTATE_PIC2:
|
|
data = RB_RotatePic2( 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_WORLD_EFFECTS:
|
|
data = RB_WorldEffects( data );
|
|
break;
|
|
case RC_AUTO_MAP:
|
|
data = R_DrawWireframeAutomap(data);
|
|
break;
|
|
case RC_END_OF_LIST:
|
|
default:
|
|
// stop rendering on this thread
|
|
t2 = Sys_Milliseconds()*com_timescale->value;
|
|
backEnd.pc.msec = t2 - t1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#ifndef _XBOX // GLOWXXX
|
|
|
|
// What Pixel Shader type is currently active (regcoms or fragment programs).
|
|
GLuint g_uiCurrentPixelShaderType = 0x0;
|
|
|
|
// Begin using a Pixel Shader.
|
|
void BeginPixelShader( GLuint uiType, GLuint uiID )
|
|
{
|
|
switch ( uiType )
|
|
{
|
|
// Using Register Combiners, so call the Display List that stores it.
|
|
case GL_REGISTER_COMBINERS_NV:
|
|
{
|
|
// Just in case...
|
|
if ( !qglCombinerParameterfvNV )
|
|
return;
|
|
|
|
// Call the list with the regcom in it.
|
|
qglEnable( GL_REGISTER_COMBINERS_NV );
|
|
qglCallList( uiID );
|
|
|
|
g_uiCurrentPixelShaderType = GL_REGISTER_COMBINERS_NV;
|
|
}
|
|
return;
|
|
|
|
// Using Fragment Programs, so call the program.
|
|
case GL_FRAGMENT_PROGRAM_ARB:
|
|
{
|
|
// Just in case...
|
|
if ( !qglGenProgramsARB )
|
|
return;
|
|
|
|
qglEnable( GL_FRAGMENT_PROGRAM_ARB );
|
|
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, uiID );
|
|
|
|
g_uiCurrentPixelShaderType = GL_FRAGMENT_PROGRAM_ARB;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Stop using a Pixel Shader and return states to normal.
|
|
void EndPixelShader()
|
|
{
|
|
if ( g_uiCurrentPixelShaderType == 0x0 )
|
|
return;
|
|
|
|
qglDisable( g_uiCurrentPixelShaderType );
|
|
}
|
|
|
|
// Hack variable for deciding which kind of texture rectangle thing to do (for some
|
|
// reason it acts different on radeon! It's against the spec!).
|
|
extern bool g_bTextureRectangleHack;
|
|
|
|
static inline void RB_BlurGlowTexture()
|
|
{
|
|
qglDisable (GL_CLIP_PLANE0);
|
|
GL_Cull( CT_TWO_SIDED );
|
|
qglDisable( GL_DEPTH_TEST );
|
|
|
|
// Go into orthographic 2d mode.
|
|
qglMatrixMode(GL_PROJECTION);
|
|
qglPushMatrix();
|
|
qglLoadIdentity();
|
|
qglOrtho(0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight, 0, -1, 1);
|
|
qglMatrixMode(GL_MODELVIEW);
|
|
qglPushMatrix();
|
|
qglLoadIdentity();
|
|
|
|
GL_State(0);
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// Setup vertex and pixel programs.
|
|
/////////////////////////////////////////////////////////
|
|
|
|
// NOTE: The 0.25 is because we're blending 4 textures (so = 1.0) and we want a relatively normalized pixel
|
|
// intensity distribution, but this won't happen anyways if intensity is higher than 1.0.
|
|
float fBlurDistribution = r_DynamicGlowIntensity->value * 0.25f;
|
|
float fBlurWeight[4] = { fBlurDistribution, fBlurDistribution, fBlurDistribution, 1.0f };
|
|
|
|
// Enable and set the Vertex Program.
|
|
qglEnable( GL_VERTEX_PROGRAM_ARB );
|
|
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, tr.glowVShader );
|
|
|
|
// Apply Pixel Shaders.
|
|
if ( qglCombinerParameterfvNV )
|
|
{
|
|
BeginPixelShader( GL_REGISTER_COMBINERS_NV, tr.glowPShader );
|
|
|
|
// Pass the blur weight to the regcom.
|
|
qglCombinerParameterfvNV( GL_CONSTANT_COLOR0_NV, (float*)&fBlurWeight );
|
|
}
|
|
else if ( qglProgramEnvParameter4fARB )
|
|
{
|
|
BeginPixelShader( GL_FRAGMENT_PROGRAM_ARB, tr.glowPShader );
|
|
|
|
// Pass the blur weight to the Fragment Program.
|
|
qglProgramEnvParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, fBlurWeight[0], fBlurWeight[1], fBlurWeight[2], fBlurWeight[3] );
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// Set the blur texture to the 4 texture stages.
|
|
/////////////////////////////////////////////////////////
|
|
|
|
// How much to offset each texel by.
|
|
float fTexelWidthOffset = 0.1f, fTexelHeightOffset = 0.1f;
|
|
|
|
GLuint uiTex = tr.screenGlow;
|
|
|
|
qglActiveTextureARB( GL_TEXTURE3_ARB );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
|
|
qglActiveTextureARB( GL_TEXTURE2_ARB );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
|
|
qglActiveTextureARB( GL_TEXTURE1_ARB );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
|
|
qglActiveTextureARB(GL_TEXTURE0_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// Draw the blur passes (each pass blurs it more, increasing the blur radius ).
|
|
/////////////////////////////////////////////////////////
|
|
|
|
//int iTexWidth = backEnd.viewParms.viewportWidth, iTexHeight = backEnd.viewParms.viewportHeight;
|
|
int iTexWidth = glConfig.vidWidth, iTexHeight = glConfig.vidHeight;
|
|
|
|
for ( int iNumBlurPasses = 0; iNumBlurPasses < r_DynamicGlowPasses->integer; iNumBlurPasses++ )
|
|
{
|
|
// Load the Texel Offsets into the Vertex Program.
|
|
qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 0, -fTexelWidthOffset, -fTexelWidthOffset, 0.0f, 0.0f );
|
|
qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 1, -fTexelWidthOffset, fTexelWidthOffset, 0.0f, 0.0f );
|
|
qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 2, fTexelWidthOffset, -fTexelWidthOffset, 0.0f, 0.0f );
|
|
qglProgramEnvParameter4fARB( GL_VERTEX_PROGRAM_ARB, 3, fTexelWidthOffset, fTexelWidthOffset, 0.0f, 0.0f );
|
|
|
|
// After first pass put the tex coords to the viewport size.
|
|
if ( iNumBlurPasses == 1 )
|
|
{
|
|
// OK, very weird, but dependent on which texture rectangle extension we're using, the
|
|
// texture either needs to be always texure correct or view correct...
|
|
if ( !g_bTextureRectangleHack )
|
|
{
|
|
iTexWidth = backEnd.viewParms.viewportWidth;
|
|
iTexHeight = backEnd.viewParms.viewportHeight;
|
|
}
|
|
|
|
uiTex = tr.blurImage;
|
|
qglActiveTextureARB( GL_TEXTURE3_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
qglActiveTextureARB( GL_TEXTURE2_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
qglActiveTextureARB( GL_TEXTURE1_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
qglActiveTextureARB(GL_TEXTURE0_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
|
|
// Copy the current image over.
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, uiTex );
|
|
qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
}
|
|
|
|
// Draw the fullscreen quad.
|
|
qglBegin( GL_QUADS );
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, iTexHeight );
|
|
qglVertex2f( 0, 0 );
|
|
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, 0 );
|
|
qglVertex2f( 0, backEnd.viewParms.viewportHeight );
|
|
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, iTexWidth, 0 );
|
|
qglVertex2f( backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, iTexWidth, iTexHeight );
|
|
qglVertex2f( backEnd.viewParms.viewportWidth, 0 );
|
|
qglEnd();
|
|
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage );
|
|
qglCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_EXT, 0, 0, 0, 0, 0, backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
|
|
// Increase the texel offsets.
|
|
// NOTE: This is possibly the most important input to the effect. Even by using an exponential function I've been able to
|
|
// make it look better (at a much higher cost of course). This is cheap though and still looks pretty great. In the future
|
|
// I might want to use an actual gaussian equation to correctly calculate the pixel coefficients and attenuates, texel
|
|
// offsets, gaussian amplitude and radius...
|
|
fTexelWidthOffset += r_DynamicGlowDelta->value;
|
|
fTexelHeightOffset += r_DynamicGlowDelta->value;
|
|
}
|
|
|
|
// Disable multi-texturing.
|
|
qglActiveTextureARB( GL_TEXTURE3_ARB );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
|
|
qglActiveTextureARB( GL_TEXTURE2_ARB );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
|
|
qglActiveTextureARB( GL_TEXTURE1_ARB );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
|
|
qglActiveTextureARB(GL_TEXTURE0_ARB );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
|
|
qglDisable( GL_VERTEX_PROGRAM_ARB );
|
|
EndPixelShader();
|
|
|
|
qglMatrixMode(GL_PROJECTION);
|
|
qglPopMatrix();
|
|
qglMatrixMode(GL_MODELVIEW);
|
|
qglPopMatrix();
|
|
|
|
qglDisable( GL_BLEND );
|
|
qglEnable( GL_DEPTH_TEST );
|
|
|
|
glState.currenttmu = 0; //this matches the last one we activated
|
|
}
|
|
|
|
// Draw the glow blur over the screen additively.
|
|
static inline void RB_DrawGlowOverlay()
|
|
{
|
|
qglDisable (GL_CLIP_PLANE0);
|
|
GL_Cull( CT_TWO_SIDED );
|
|
qglDisable( GL_DEPTH_TEST );
|
|
|
|
// Go into orthographic 2d mode.
|
|
qglMatrixMode(GL_PROJECTION);
|
|
qglPushMatrix();
|
|
qglLoadIdentity();
|
|
qglOrtho(0, glConfig.vidWidth, glConfig.vidHeight, 0, -1, 1);
|
|
qglMatrixMode(GL_MODELVIEW);
|
|
qglPushMatrix();
|
|
qglLoadIdentity();
|
|
|
|
GL_State(0);
|
|
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
|
|
// For debug purposes.
|
|
if ( r_DynamicGlow->integer != 2 )
|
|
{
|
|
// Render the normal scene texture.
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage );
|
|
qglBegin(GL_QUADS);
|
|
qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
qglTexCoord2f( 0, glConfig.vidHeight );
|
|
qglVertex2f( 0, 0 );
|
|
|
|
qglTexCoord2f( 0, 0 );
|
|
qglVertex2f( 0, glConfig.vidHeight );
|
|
|
|
qglTexCoord2f( glConfig.vidWidth, 0 );
|
|
qglVertex2f( glConfig.vidWidth, glConfig.vidHeight );
|
|
|
|
qglTexCoord2f( glConfig.vidWidth, glConfig.vidHeight );
|
|
qglVertex2f( glConfig.vidWidth, 0 );
|
|
qglEnd();
|
|
}
|
|
|
|
// One and Inverse Src Color give a very soft addition, while one one is a bit stronger. With one one we can
|
|
// use additive blending through multitexture though.
|
|
if ( r_DynamicGlowSoft->integer )
|
|
{
|
|
qglBlendFunc( GL_ONE, GL_ONE_MINUS_SRC_COLOR );
|
|
}
|
|
else
|
|
{
|
|
qglBlendFunc( GL_ONE, GL_ONE );
|
|
}
|
|
qglEnable( GL_BLEND );
|
|
|
|
// Now additively render the glow texture.
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.blurImage );
|
|
qglBegin(GL_QUADS);
|
|
qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
qglTexCoord2f( 0, r_DynamicGlowHeight->integer );
|
|
qglVertex2f( 0, 0 );
|
|
|
|
qglTexCoord2f( 0, 0 );
|
|
qglVertex2f( 0, glConfig.vidHeight );
|
|
|
|
qglTexCoord2f( r_DynamicGlowWidth->integer, 0 );
|
|
qglVertex2f( glConfig.vidWidth, glConfig.vidHeight );
|
|
|
|
qglTexCoord2f( r_DynamicGlowWidth->integer, r_DynamicGlowHeight->integer );
|
|
qglVertex2f( glConfig.vidWidth, 0 );
|
|
qglEnd();
|
|
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
qglBlendFunc( GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR );
|
|
qglDisable( GL_BLEND );
|
|
|
|
// NOTE: Multi-texture wasn't that much faster (we're obviously not bottlenecked by transform pipeline),
|
|
// and besides, soft glow looks better anyways.
|
|
/* else
|
|
{
|
|
int iTexWidth = glConfig.vidWidth, iTexHeight = glConfig.vidHeight;
|
|
if ( GL_TEXTURE_RECTANGLE_EXT == GL_TEXTURE_RECTANGLE_NV )
|
|
{
|
|
iTexWidth = r_DynamicGlowWidth->integer;
|
|
iTexHeight = r_DynamicGlowHeight->integer;
|
|
}
|
|
|
|
qglActiveTextureARB( GL_TEXTURE1_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.screenGlow );
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD );
|
|
|
|
qglActiveTextureARB(GL_TEXTURE0_ARB );
|
|
qglDisable( GL_TEXTURE_2D );
|
|
qglEnable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglBindTexture( GL_TEXTURE_RECTANGLE_EXT, tr.sceneImage );
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
|
|
|
|
qglBegin(GL_QUADS);
|
|
qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0, iTexHeight );
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, glConfig.vidHeight );
|
|
qglVertex2f( 0, 0 );
|
|
|
|
qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, 0, 0 );
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0, 0 );
|
|
qglVertex2f( 0, glConfig.vidHeight );
|
|
|
|
qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, iTexWidth, 0 );
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, glConfig.vidWidth, 0 );
|
|
qglVertex2f( glConfig.vidWidth, glConfig.vidHeight );
|
|
|
|
qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, iTexWidth, iTexHeight );
|
|
qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, glConfig.vidWidth, glConfig.vidHeight );
|
|
qglVertex2f( glConfig.vidWidth, 0 );
|
|
qglEnd();
|
|
|
|
qglActiveTextureARB( GL_TEXTURE1_ARB );
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
|
|
qglActiveTextureARB(GL_TEXTURE0_ARB );
|
|
qglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
qglDisable( GL_TEXTURE_RECTANGLE_EXT );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
}*/
|
|
|
|
qglMatrixMode(GL_PROJECTION);
|
|
qglPopMatrix();
|
|
qglMatrixMode(GL_MODELVIEW);
|
|
qglPopMatrix();
|
|
|
|
qglEnable( GL_DEPTH_TEST );
|
|
}
|
|
#endif //XBOX
|
|
|
|
#endif //!DEDICATED
|