rallyunlimited-engine/code/renderer/tr_shade.c
2024-02-02 19:46:17 +03:00

1078 lines
24 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// tr_shade.c
#include "tr_local.h"
/*
THIS ENTIRE FILE IS BACK END
This file deals with applying shaders to surface data in the tess struct.
*/
/*
==================
R_DrawElements
==================
*/
void R_DrawElements( int numIndexes, const glIndex_t *indexes ) {
qglDrawElements( GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, indexes );
}
/*
=============================================================
SURFACE SHADERS
=============================================================
*/
shaderCommands_t tess;
static qboolean setArraysOnce;
/*
=================
R_BindAnimatedImage
=================
*/
void R_BindAnimatedImage( const textureBundle_t *bundle ) {
int64_t index;
double v;
if ( bundle->isVideoMap ) {
ri.CIN_RunCinematic(bundle->videoMapHandle);
ri.CIN_UploadCinematic(bundle->videoMapHandle);
return;
}
if ( bundle->isScreenMap && backEnd.viewParms.frameSceneNum == 1 ) {
GL_BindTexNum( FBO_ScreenTexture() );
return;
}
if ( bundle->numImageAnimations <= 1 ) {
GL_Bind( bundle->image[0] );
return;
}
// it is necessary to do this messy calc to make sure animations line up
// exactly with waveforms of the same frequency
//v = tess.shaderTime * bundle->imageAnimationSpeed * FUNCTABLE_SIZE;
//index = v;
//index >>= FUNCTABLE_SIZE2;
v = tess.shaderTime * bundle->imageAnimationSpeed; // fix for frameloss bug -EC-
index = v;
if ( index < 0 ) {
index = 0; // may happen with shader time offsets
}
index %= bundle->numImageAnimations;
GL_Bind( bundle->image[ index ] );
}
/*
================
DrawTris
Draws triangle outlines for debugging
================
*/
static void DrawTris( const shaderCommands_t *input ) {
if ( r_showtris->integer == 1 && backEnd.drawConsole )
return;
if ( tess.numIndexes == 0 )
return;
GL_ProgramDisable();
tess.dlightUpdateParams = qtrue;
GL_ClientState( 0, CLS_NONE );
qglDisable( GL_TEXTURE_2D );
#ifdef USE_PMLIGHT
if ( tess.dlightPass )
qglColor4f( 1.0f, 0.33f, 0.2f, 1.0f );
else
#endif
qglColor4f( 1, 1, 1, 1 );
GL_State( GLS_POLYMODE_LINE | GLS_DEPTHMASK_TRUE );
qglDepthRange( 0, 0 );
qglVertexPointer( 3, GL_FLOAT, sizeof( input->xyz[0] ), input->xyz );
if ( qglLockArraysEXT ) {
qglLockArraysEXT( 0, input->numVertexes );
}
R_DrawElements( input->numIndexes, input->indexes );
if ( qglUnlockArraysEXT ) {
qglUnlockArraysEXT();
}
qglEnable( GL_TEXTURE_2D );
qglDepthRange( 0, 1 );
}
/*
================
DrawNormals
Draws vertex normals for debugging
================
*/
static void DrawNormals( const shaderCommands_t *input ) {
int i;
GL_ClientState( 0, CLS_NONE );
qglDisable( GL_TEXTURE_2D );
qglColor4f( 1, 1, 1, 1 );
qglDepthRange( 0, 0 ); // never occluded
GL_State( GLS_DEPTHMASK_TRUE );
for ( i = tess.numVertexes-1; i >= 0; i-- ) {
VectorMA( tess.xyz[i], 2.0, tess.normal[i], tess.xyz[i*2 + 1] );
VectorCopy( tess.xyz[i], tess.xyz[i*2] );
}
qglVertexPointer( 3, GL_FLOAT, sizeof( tess.xyz[0] ), tess.xyz );
if ( qglLockArraysEXT ) {
qglLockArraysEXT( 0, tess.numVertexes * 2 );
}
qglDrawArrays( GL_LINES, 0, tess.numVertexes * 2 );
if ( qglUnlockArraysEXT ) {
qglUnlockArraysEXT();
}
qglEnable( GL_TEXTURE_2D );
qglDepthRange( 0, 1 );
}
/*
==============
RB_BeginSurface
We must set some things up before beginning any tesselation,
because a surface may be forced to perform a RB_End due
to overflow.
==============
*/
void RB_BeginSurface( shader_t *shader, int fogNum ) {
shader_t *state;
#ifdef USE_PMLIGHT
if ( !tess.dlightPass && shader->isStaticShader && !shader->remappedShader )
#else
if ( shader->isStaticShader )
#endif
tess.allowVBO = qtrue;
else
tess.allowVBO = qfalse;
if ( shader->remappedShader ) {
state = shader->remappedShader;
} else {
state = shader;
}
#ifdef USE_PMLIGHT
if ( tess.fogNum != fogNum || tess.cullType != state->cullType ) {
tess.dlightUpdateParams = qtrue;
}
#endif
#ifdef USE_TESS_NEEDS_NORMAL
#ifdef USE_PMLIGHT
tess.needsNormal = state->needsNormal || tess.dlightPass || r_shownormals->integer;
#else
tess.needsNormal = state->needsNormal || r_shownormals->integer;
#endif
#endif
#ifdef USE_TESS_NEEDS_ST2
tess.needsST2 = state->needsST2;
#endif
tess.numIndexes = 0;
tess.numVertexes = 0;
tess.shader = state;
tess.fogNum = fogNum;
#ifdef USE_LEGACY_DLIGHTS
tess.dlightBits = 0; // will be OR'd in by surface functions
#endif
tess.xstages = state->stages;
tess.numPasses = state->numUnfoggedPasses;
tess.shaderTime = backEnd.refdef.floatTime - tess.shader->timeOffset;
if ( tess.shader->clampTime && tess.shaderTime >= tess.shader->clampTime ) {
tess.shaderTime = tess.shader->clampTime;
}
}
/*
===================
DrawMultitextured
output = t0 * t1 or t0 + t1
t0 = most upstream according to spec
t1 = most downstream according to spec
===================
*/
static void DrawMultitextured( const shaderCommands_t *input, int stage ) {
const shaderStage_t *pStage;
pStage = tess.xstages[ stage ];
GL_State( pStage->stateBits );
if ( !setArraysOnce ) {
R_ComputeColors( pStage );
R_ComputeTexCoords( 0, &pStage->bundle[0] );
R_ComputeTexCoords( 1, &pStage->bundle[1] );
GL_ClientState( 0, CLS_TEXCOORD_ARRAY | CLS_COLOR_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoordPtr[0] );
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors[0].rgba );
GL_ClientState( 1, CLS_TEXCOORD_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoordPtr[1] );
}
//
// base
//
GL_SelectTexture( 0 );
R_BindAnimatedImage( &pStage->bundle[0] );
//
// lightmap/secondary pass
//
GL_SelectTexture( 1 );
qglEnable( GL_TEXTURE_2D );
R_BindAnimatedImage( &pStage->bundle[1] );
if ( r_lightmap->integer ) {
GL_TexEnv( GL_REPLACE );
} else {
GL_TexEnv( pStage->mtEnv );
}
R_DrawElements( input->numIndexes, input->indexes );
//
// disable texturing on TEXTURE1, then select TEXTURE0
//
if ( r_vbo->integer ) {
// some drivers may try to load texcoord[1] data even with multi-texturing disabled
// (and actually gpu shaders doesn't care about conventional GL_TEXTURE_2D states)
// which doesn't cause problems while data pointer is the same or represents fixed-size set
// but when we switch to/from vbo - texcoord[1] data may point on larger set (it's ok)
// or smaller set - which will cause out-of-bounds index access/crash during non-multitexture rendering
// GL_ClientState( 1, GLS_NONE );
}
qglDisable( GL_TEXTURE_2D );
GL_SelectTexture( 0 );
}
#ifdef USE_LEGACY_DLIGHTS
/*
===================
ProjectDlightTexture
Perform dynamic lighting with another rendering pass
===================
*/
static void ProjectDlightTexture_scalar( void ) {
int i, l;
vec3_t origin;
float *texCoords;
float *colors;
byte clipBits[SHADER_MAX_VERTEXES];
float texCoordsArray[SHADER_MAX_VERTEXES][2];
float colorArray[SHADER_MAX_VERTEXES][4];
glIndex_t hitIndexes[SHADER_MAX_INDEXES];
int numIndexes;
float scale;
float radius;
float modulate = 0.0f;
const dlight_t *dl;
if ( !backEnd.refdef.num_dlights ) {
return;
}
for ( l = 0 ; l < backEnd.refdef.num_dlights ; l++ ) {
if ( !( tess.dlightBits & ( 1 << l ) ) ) {
continue; // this surface definitely doesn't have any of this light
}
texCoords = texCoordsArray[0];
colors = colorArray[0];
dl = &backEnd.refdef.dlights[l];
VectorCopy( dl->transformed, origin );
radius = dl->radius;
scale = 1.0f / radius;
for ( i = 0 ; i < tess.numVertexes ; i++, texCoords += 2, colors += 4 ) {
int clip = 0;
vec3_t dist;
VectorSubtract( origin, tess.xyz[i], dist );
backEnd.pc.c_dlightVertexes++;
texCoords[0] = 0.5f + dist[0] * scale;
texCoords[1] = 0.5f + dist[1] * scale;
if ( !r_dlightBacks->integer &&
// dist . tess.normal[i]
( dist[0] * tess.normal[i][0] +
dist[1] * tess.normal[i][1] +
dist[2] * tess.normal[i][2] ) < 0.0f ) {
clip = 63;
} else {
if ( texCoords[0] < 0.0f ) {
clip |= 1;
} else if ( texCoords[0] > 1.0f ) {
clip |= 2;
}
if ( texCoords[1] < 0.0f ) {
clip |= 4;
} else if ( texCoords[1] > 1.0f ) {
clip |= 8;
}
// modulate the strength based on the height and color
if ( dist[2] > radius ) {
clip |= 16;
modulate = 0.0f;
} else if ( dist[2] < -radius ) {
clip |= 32;
modulate = 0.0f;
} else {
//*((int*)&dist[2]) &= 0x7FFFFFFF;
dist[2] = fabsf( dist[2] );
if ( dist[2] < radius * 0.5f ) {
modulate = 1.0f;
} else {
modulate = 2.0f * (radius - dist[2]) * scale;
}
}
}
clipBits[i] = clip;
colors[0] = dl->color[0] * modulate;
colors[1] = dl->color[1] * modulate;
colors[2] = dl->color[2] * modulate;
colors[3] = 1.0f;
}
// build a list of triangles that need light
numIndexes = 0;
for ( i = 0 ; i < tess.numIndexes ; i += 3 ) {
int a, b, c;
a = tess.indexes[i];
b = tess.indexes[i+1];
c = tess.indexes[i+2];
if ( clipBits[a] & clipBits[b] & clipBits[c] ) {
continue; // not lighted
}
hitIndexes[numIndexes] = a;
hitIndexes[numIndexes+1] = b;
hitIndexes[numIndexes+2] = c;
numIndexes += 3;
}
if ( !numIndexes ) {
continue;
}
GL_ClientState( 1, CLS_NONE );
GL_ClientState( 0, CLS_TEXCOORD_ARRAY | CLS_COLOR_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 0, texCoordsArray[0] );
qglColorPointer( 4, GL_FLOAT, 0, colorArray );
GL_Bind( tr.dlightImage );
// include GLS_DEPTHFUNC_EQUAL so alpha tested surfaces don't add light
// where they aren't rendered
if ( dl->additive ) {
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
} else {
GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
}
R_DrawElements( numIndexes, hitIndexes );
backEnd.pc.c_totalIndexes += numIndexes;
backEnd.pc.c_dlightIndexes += numIndexes;
}
}
static void ProjectDlightTexture( void ) {
ProjectDlightTexture_scalar();
}
#endif // USE_LEGACY_DLIGHTS
/*
===================
RB_FogPass
Blends a fog texture on top of everything else
===================
*/
static void RB_FogPass( void ) {
const fog_t *fog;
int i;
fog = tr.world->fogs + tess.fogNum;
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i].u32 = fog->colorInt.u32;
}
RB_CalcFogTexCoords( ( float * ) tess.svars.texcoords[0] );
GL_ClientState( 1, CLS_NONE );
GL_ClientState( 0, CLS_TEXCOORD_ARRAY | CLS_COLOR_ARRAY );
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors[0].rgba );
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords[0] );
GL_SelectTexture( 0 );
GL_Bind( tr.fogImage );
if ( tess.shader->fogPass == FP_EQUAL ) {
GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_DEPTHFUNC_EQUAL );
} else {
GL_State( GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA );
}
R_DrawElements( tess.numIndexes, tess.indexes );
}
/*
===============
R_ComputeColors
===============
*/
void R_ComputeColors( const shaderStage_t *pStage )
{
int i;
if ( !tess.numVertexes )
return;
//
// rgbGen
//
switch ( pStage->rgbGen )
{
case CGEN_IDENTITY:
Com_Memset( tess.svars.colors, 0xff, tess.numVertexes * 4 );
break;
default:
case CGEN_IDENTITY_LIGHTING:
Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 );
break;
case CGEN_LIGHTING_DIFFUSE:
RB_CalcDiffuseColor( ( unsigned char * ) tess.svars.colors );
break;
case CGEN_EXACT_VERTEX:
Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
break;
case CGEN_CONST:
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i].u32 = pStage->constantColor.u32;
}
break;
case CGEN_VERTEX:
if ( tr.identityLight == 1 )
{
Com_Memcpy( tess.svars.colors, tess.vertexColors, tess.numVertexes * sizeof( tess.vertexColors[0] ) );
}
else
{
for ( i = 0; i < tess.numVertexes; i++ )
{
tess.svars.colors[i].rgba[0] = tess.vertexColors[i].rgba[0] * tr.identityLight;
tess.svars.colors[i].rgba[1] = tess.vertexColors[i].rgba[1] * tr.identityLight;
tess.svars.colors[i].rgba[2] = tess.vertexColors[i].rgba[2] * tr.identityLight;
tess.svars.colors[i].rgba[3] = tess.vertexColors[i].rgba[3];
}
}
break;
case CGEN_ONE_MINUS_VERTEX:
if ( tr.identityLight == 1 )
{
for ( i = 0; i < tess.numVertexes; i++ )
{
tess.svars.colors[i].rgba[0] = 255 - tess.vertexColors[i].rgba[0];
tess.svars.colors[i].rgba[1] = 255 - tess.vertexColors[i].rgba[1];
tess.svars.colors[i].rgba[2] = 255 - tess.vertexColors[i].rgba[2];
}
}
else
{
for ( i = 0; i < tess.numVertexes; i++ )
{
tess.svars.colors[i].rgba[0] = ( 255 - tess.vertexColors[i].rgba[0] ) * tr.identityLight;
tess.svars.colors[i].rgba[1] = ( 255 - tess.vertexColors[i].rgba[1] ) * tr.identityLight;
tess.svars.colors[i].rgba[2] = ( 255 - tess.vertexColors[i].rgba[2] ) * tr.identityLight;
}
}
break;
case CGEN_FOG:
{
const fog_t *fog;
fog = tr.world->fogs + tess.fogNum;
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i].u32 = fog->colorInt.u32;
}
}
break;
case CGEN_WAVEFORM:
RB_CalcWaveColor( &pStage->rgbWave, tess.svars.colors[0].rgba );
break;
case CGEN_ENTITY:
RB_CalcColorFromEntity( tess.svars.colors[0].rgba );
break;
case CGEN_ONE_MINUS_ENTITY:
RB_CalcColorFromOneMinusEntity( tess.svars.colors[0].rgba );
break;
}
//
// alphaGen
//
switch ( pStage->alphaGen )
{
case AGEN_SKIP:
break;
case AGEN_IDENTITY:
if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) ||
pStage->rgbGen != CGEN_VERTEX ) {
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i].rgba[3] = 255;
}
}
break;
case AGEN_CONST:
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i].rgba[3] = pStage->constantColor.rgba[3];
}
break;
case AGEN_WAVEFORM:
RB_CalcWaveAlpha( &pStage->alphaWave, tess.svars.colors[0].rgba );
break;
case AGEN_LIGHTING_SPECULAR:
RB_CalcSpecularAlpha( tess.svars.colors[0].rgba );
break;
case AGEN_ENTITY:
RB_CalcAlphaFromEntity( tess.svars.colors[0].rgba );
break;
case AGEN_ONE_MINUS_ENTITY:
RB_CalcAlphaFromOneMinusEntity( tess.svars.colors[0].rgba );
break;
case AGEN_VERTEX:
for ( i = 0; i < tess.numVertexes; i++ ) {
tess.svars.colors[i].rgba[3] = tess.vertexColors[i].rgba[3];
}
break;
case AGEN_ONE_MINUS_VERTEX:
for ( i = 0; i < tess.numVertexes; i++ )
{
tess.svars.colors[i].rgba[3] = 255 - tess.vertexColors[i].rgba[3];
}
break;
case AGEN_PORTAL:
{
for ( i = 0; i < tess.numVertexes; i++ )
{
unsigned char alpha;
float len;
vec3_t v;
VectorSubtract( tess.xyz[i], backEnd.viewParms.or.origin, v );
len = VectorLength( v ) * tess.shader->portalRangeR;
if ( len > 1 )
{
alpha = 0xff;
}
else
{
alpha = len * 0xff;
}
tess.svars.colors[i].rgba[3] = alpha;
}
}
break;
}
//
// fog adjustment for colors to fade out as fog increases
//
if ( tess.fogNum )
{
switch ( pStage->adjustColorsForFog )
{
case ACFF_MODULATE_RGB:
RB_CalcModulateColorsByFog( tess.svars.colors[0].rgba );
break;
case ACFF_MODULATE_ALPHA:
RB_CalcModulateAlphasByFog( tess.svars.colors[0].rgba );
break;
case ACFF_MODULATE_RGBA:
RB_CalcModulateRGBAsByFog( tess.svars.colors[0].rgba );
break;
case ACFF_NONE:
break;
}
}
}
/*
===============
R_ComputeTexCoords
===============
*/
void R_ComputeTexCoords( const int b, const textureBundle_t *bundle ) {
int i;
int tm;
vec2_t *src, *dst;
if ( !tess.numVertexes )
return;
src = dst = tess.svars.texcoords[b];
//
// generate the texture coordinates
//
switch ( bundle->tcGen )
{
case TCGEN_IDENTITY:
src = tess.texCoords00;
break;
case TCGEN_TEXTURE:
src = tess.texCoords[0];
break;
case TCGEN_LIGHTMAP:
src = tess.texCoords[1];
break;
case TCGEN_VECTOR:
for ( i = 0 ; i < tess.numVertexes ; i++ ) {
dst[i][0] = DotProduct( tess.xyz[i], bundle->tcGenVectors[0] );
dst[i][1] = DotProduct( tess.xyz[i], bundle->tcGenVectors[1] );
}
break;
case TCGEN_FOG:
RB_CalcFogTexCoords( ( float * ) dst );
break;
case TCGEN_ENVIRONMENT_MAPPED:
RB_CalcEnvironmentTexCoords( ( float * ) dst );
break;
case TCGEN_ENVIRONMENT_MAPPED_FP:
RB_CalcEnvironmentTexCoordsFP( ( float * ) dst, bundle->isScreenMap );
break;
case TCGEN_BAD:
return;
}
//
// alter texture coordinates
//
for ( tm = 0; tm < bundle->numTexMods ; tm++ ) {
switch ( bundle->texMods[tm].type )
{
case TMOD_NONE:
tm = TR_MAX_TEXMODS; // break out of for loop
break;
case TMOD_TURBULENT:
RB_CalcTurbulentTexCoords( &bundle->texMods[tm].wave, (float *)src, (float *) dst );
src = dst;
break;
case TMOD_ENTITY_TRANSLATE:
RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, (float *)src, (float *) dst );
src = dst;
break;
case TMOD_SCROLL:
RB_CalcScrollTexCoords( bundle->texMods[tm].scroll, (float *)src, (float *) dst );
src = dst;
break;
case TMOD_SCALE:
RB_CalcScaleTexCoords( bundle->texMods[tm].scale, (float *) src, (float *) dst );
src = dst;
break;
case TMOD_OFFSET:
for ( i = 0; i < tess.numVertexes; i++ ) {
dst[i][0] = src[i][0] + bundle->texMods[tm].offset[0];
dst[i][1] = src[i][1] + bundle->texMods[tm].offset[1];
}
src = dst;
break;
case TMOD_SCALE_OFFSET:
for ( i = 0; i < tess.numVertexes; i++ ) {
dst[i][0] = (src[i][0] * bundle->texMods[tm].scale[0] ) + bundle->texMods[tm].offset[0];
dst[i][1] = (src[i][1] * bundle->texMods[tm].scale[1] ) + bundle->texMods[tm].offset[1];
}
src = dst;
break;
case TMOD_STRETCH:
RB_CalcStretchTexCoords( &bundle->texMods[tm].wave, (float *)src, (float *) dst );
src = dst;
break;
case TMOD_TRANSFORM:
RB_CalcTransformTexCoords( &bundle->texMods[tm], (float *)src, (float *) dst );
src = dst;
break;
case TMOD_ROTATE:
RB_CalcRotateTexCoords( bundle->texMods[tm].rotateSpeed, (float *) src, (float *) dst );
src = dst;
break;
default:
ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'", bundle->texMods[tm].type, tess.shader->name );
break;
}
}
tess.svars.texcoordPtr[ b ] = src;
}
/*
** RB_IterateStagesGeneric
*/
static void RB_IterateStagesGeneric( const shaderCommands_t *input )
{
const shaderStage_t *pStage;
int stage;
for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ )
{
pStage = tess.xstages[ stage ];
if ( !pStage )
break;
//
// do multitexture
//
if ( pStage->mtEnv )
{
DrawMultitextured( input, stage );
}
else
{
if ( !setArraysOnce )
{
R_ComputeTexCoords( 0, &pStage->bundle[0] );
R_ComputeColors( pStage );
GL_ClientState( 1, CLS_NONE );
GL_ClientState( 0, CLS_TEXCOORD_ARRAY | CLS_COLOR_ARRAY );
qglTexCoordPointer( 2, GL_FLOAT, 0, input->svars.texcoordPtr[0] );
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, input->svars.colors[0].rgba );
}
//
// set state
//
R_BindAnimatedImage( &pStage->bundle[0] );
GL_State( pStage->stateBits );
//
// draw
//
R_DrawElements( input->numIndexes, input->indexes );
if ( pStage->depthFragment )
{
GL_State( pStage->stateBits | GLS_DEPTHMASK_TRUE );
GL_ProgramEnable();
R_DrawElements( input->numIndexes, input->indexes );
GL_ProgramDisable();
}
}
// allow skipping out to show just lightmaps during development
if ( r_lightmap->integer && ( pStage->bundle[0].lightmap != LIGHTMAP_INDEX_NONE || pStage->bundle[1].lightmap != LIGHTMAP_INDEX_NONE ) )
break;
}
}
/*
** RB_StageIteratorGeneric
*/
void RB_StageIteratorGeneric( void )
{
const shaderCommands_t *input;
shader_t *shader;
#ifdef USE_PMLIGHT
if ( tess.dlightPass )
{
ARB_LightingPass();
return;
}
GL_ProgramDisable();
#endif // USE_PMLIGHT
if ( tess.vboIndex )
{
RB_StageIteratorVBO();
return;
}
VBO_UnBind();
input = &tess;
shader = input->shader;
RB_DeformTessGeometry();
//
// set face culling appropriately
//
GL_Cull( shader->cullType );
// set polygon offset if necessary
if ( shader->polygonOffset )
{
qglEnable( GL_POLYGON_OFFSET_FILL );
qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
}
//
// if there is only a single pass then we can enable color
// and texture arrays before we compile, otherwise we need
// to avoid compiling those arrays since they will change
// during multipass rendering
//
if ( tess.numPasses > 1 )
{
setArraysOnce = qfalse;
GL_ClientState( 1, CLS_NONE );
GL_ClientState( 0, CLS_NONE );
}
else
{
// FIXME: we can't do that if going to lighting/fog later?
setArraysOnce = qtrue;
GL_ClientState( 0, CLS_COLOR_ARRAY | CLS_TEXCOORD_ARRAY );
if ( tess.xstages[0] )
{
R_ComputeColors( tess.xstages[0] );
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors[0].rgba );
R_ComputeTexCoords( 0, &tess.xstages[0]->bundle[0] );
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoordPtr[0] );
if ( shader->multitextureEnv )
{
GL_ClientState( 1, CLS_TEXCOORD_ARRAY );
R_ComputeTexCoords( 1, &tess.xstages[0]->bundle[1] );
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoordPtr[1] );
}
else
{
GL_ClientState( 1, CLS_NONE );
}
}
}
qglVertexPointer( 3, GL_FLOAT, sizeof( input->xyz[0] ), input->xyz ); // padded for SIMD
//
// lock XYZ
//
if ( qglLockArraysEXT )
{
qglLockArraysEXT( 0, input->numVertexes );
}
//
// call shader function
//
RB_IterateStagesGeneric( input );
//
// now do any dynamic lighting needed
//
#ifdef USE_LEGACY_DLIGHTS
#ifdef USE_PMLIGHT
if ( !r_dlightMode->integer )
#endif
if ( tess.dlightBits && tess.shader->sort <= SS_OPAQUE && !(tess.shader->surfaceFlags & (SURF_NODLIGHT | SURF_SKY) ) )
{
ProjectDlightTexture();
}
#endif // USE_LEGACY_DLIGHTS
//
// now do fog
//
if ( tess.fogNum && tess.shader->fogPass )
{
RB_FogPass();
}
//
// unlock arrays
//
if ( qglUnlockArraysEXT )
{
qglUnlockArraysEXT();
}
GL_ClientState( 1, CLS_NONE );
//
// reset polygon offset
//
if ( shader->polygonOffset )
{
qglDisable( GL_POLYGON_OFFSET_FILL );
}
}
/*
** RB_EndSurface
*/
void RB_EndSurface( void ) {
const shaderCommands_t *input;
input = &tess;
if ( input->numIndexes == 0 ) {
VBO_UnBind();
return;
}
if ( input->numIndexes > SHADER_MAX_INDEXES ) {
ri.Error( ERR_DROP, "RB_EndSurface() - SHADER_MAX_INDEXES hit" );
}
if ( input->numVertexes > SHADER_MAX_VERTEXES ) {
ri.Error( ERR_DROP, "RB_EndSurface() - SHADER_MAX_VERTEXES hit" );
}
if ( tess.shader == tr.shadowShader ) {
RB_ShadowTessEnd();
return;
}
// for debugging of sort order issues, stop rendering after a given sort value
if ( r_debugSort->integer && r_debugSort->integer < tess.shader->sort && !backEnd.doneSurfaces ) {
VBO_UnBind();
return;
}
//
// update performance counters
//
#ifdef USE_PMLIGHT
if ( tess.dlightPass ) {
backEnd.pc.c_lit_batches++;
backEnd.pc.c_lit_vertices += tess.numVertexes;
backEnd.pc.c_lit_indices += tess.numIndexes;
} else
#endif
{
backEnd.pc.c_shaders++;
backEnd.pc.c_vertexes += tess.numVertexes;
backEnd.pc.c_indexes += tess.numIndexes;
}
backEnd.pc.c_totalIndexes += tess.numIndexes * tess.numPasses;
//
// call off to shader specific tess end function
//
tess.shader->optimalStageIteratorFunc();
//
// draw debugging stuff
//
if ( !VBO_Active() ) {
if ( r_showtris->integer ) {
DrawTris( input );
}
if ( r_shownormals->integer ) {
DrawNormals( input );
}
}
// clear shader so we can tell we don't have any unclosed surfaces
tess.numIndexes = 0;
tess.numVertexes = 0;
}