xash3d-fwgs/ref_gl/gl_rsurf.c

3643 lines
94 KiB
C

/*
gl_rsurf.c - surface-related refresh code
Copyright (C) 2010 Uncle Mike
This program 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.
This program 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.
*/
#include "gl_local.h"
#include "xash3d_mathlib.h"
#include "mod_local.h"
typedef struct
{
int allocated[BLOCK_SIZE_MAX];
int current_lightmap_texture;
msurface_t *dynamic_surfaces;
msurface_t *lightmap_surfaces[MAX_LIGHTMAPS];
byte lightmap_buffer[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*4];
} gllightmapstate_t;
static int nColinElim; // stats
static vec2_t world_orthocenter;
static vec2_t world_orthohalf;
static uint r_blocklights[BLOCK_SIZE_MAX*BLOCK_SIZE_MAX*3];
static mextrasurf_t *fullbright_surfaces[MAX_TEXTURES];
static mextrasurf_t *detail_surfaces[MAX_TEXTURES];
static int rtable[MOD_FRAMES][MOD_FRAMES];
static qboolean draw_alpha_surfaces = false;
static qboolean draw_fullbrights = false;
static qboolean draw_details = false;
static msurface_t *skychain = NULL;
static gllightmapstate_t gl_lms;
static void LM_UploadBlock( qboolean dynamic );
static qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmaps );
static void R_DrawVBO( qboolean drawlightmaps, qboolean drawtextures );
byte *Mod_GetCurrentVis( void )
{
if( gEngfuncs.drawFuncs->Mod_GetCurrentVis && tr.fCustomRendering )
return gEngfuncs.drawFuncs->Mod_GetCurrentVis();
return RI.visbytes;
}
void Mod_SetOrthoBounds( const float *mins, const float *maxs )
{
if( gEngfuncs.drawFuncs->GL_OrthoBounds )
{
gEngfuncs.drawFuncs->GL_OrthoBounds( mins, maxs );
}
Vector2Average( maxs, mins, world_orthocenter );
Vector2Subtract( maxs, world_orthocenter, world_orthohalf );
}
static void BoundPoly( int numverts, float *verts, vec3_t mins, vec3_t maxs )
{
int i, j;
float *v;
ClearBounds( mins, maxs );
for( i = 0, v = verts; i < numverts; i++ )
{
for( j = 0; j < 3; j++, v++ )
{
if( *v < mins[j] ) mins[j] = *v;
if( *v > maxs[j] ) maxs[j] = *v;
}
}
}
static void SubdividePolygon_r( msurface_t *warpface, int numverts, float *verts )
{
vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE];
mextrasurf_t *warpinfo = warpface->info;
float dist[SUBDIVIDE_SIZE];
float m, frac, s, t, *v;
int i, j, k, f, b;
float sample_size;
vec3_t mins, maxs;
glpoly_t *poly;
model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel();
if( numverts > ( SUBDIVIDE_SIZE - 4 ))
gEngfuncs.Host_Error( "Mod_SubdividePolygon: too many vertexes on face ( %i )\n", numverts );
sample_size = gEngfuncs.Mod_SampleSizeForFace( warpface );
BoundPoly( numverts, verts, mins, maxs );
for( i = 0; i < 3; i++ )
{
m = ( mins[i] + maxs[i] ) * 0.5f;
m = SUBDIVIDE_SIZE * floor( m / SUBDIVIDE_SIZE + 0.5f );
if( maxs[i] - m < 8 ) continue;
if( m - mins[i] < 8 ) continue;
// cut it
v = verts + i;
for( j = 0; j < numverts; j++, v += 3 )
dist[j] = *v - m;
// wrap cases
dist[j] = dist[0];
v -= i;
VectorCopy( verts, v );
f = b = 0;
v = verts;
for( j = 0; j < numverts; j++, v += 3 )
{
if( dist[j] >= 0 )
{
VectorCopy( v, front[f] );
f++;
}
if( dist[j] <= 0 )
{
VectorCopy (v, back[b]);
b++;
}
if( dist[j] == 0 || dist[j+1] == 0 )
continue;
if(( dist[j] > 0 ) != ( dist[j+1] > 0 ))
{
// clip point
frac = dist[j] / ( dist[j] - dist[j+1] );
for( k = 0; k < 3; k++ )
front[f][k] = back[b][k] = v[k] + frac * (v[3+k] - v[k]);
f++;
b++;
}
}
SubdividePolygon_r( warpface, f, front[0] );
SubdividePolygon_r( warpface, b, back[0] );
return;
}
if( numverts != 4 )
ClearBits( warpface->flags, SURF_DRAWTURB_QUADS );
// add a point in the center to help keep warp valid
poly = Mem_Calloc( loadmodel->mempool, sizeof( glpoly_t ) + (numverts - 4) * VERTEXSIZE * sizeof( float ));
poly->next = warpface->polys;
poly->flags = warpface->flags;
warpface->polys = poly;
poly->numverts = numverts;
for( i = 0; i < numverts; i++, verts += 3 )
{
VectorCopy( verts, poly->verts[i] );
if( FBitSet( warpface->flags, SURF_DRAWTURB ))
{
s = DotProduct( verts, warpface->texinfo->vecs[0] );
t = DotProduct( verts, warpface->texinfo->vecs[1] );
}
else
{
s = DotProduct( verts, warpface->texinfo->vecs[0] ) + warpface->texinfo->vecs[0][3];
t = DotProduct( verts, warpface->texinfo->vecs[1] ) + warpface->texinfo->vecs[1][3];
s /= warpface->texinfo->texture->width;
t /= warpface->texinfo->texture->height;
}
poly->verts[i][3] = s;
poly->verts[i][4] = t;
// for speed reasons
if( !FBitSet( warpface->flags, SURF_DRAWTURB ))
{
// lightmap texture coordinates
s = DotProduct( verts, warpinfo->lmvecs[0] ) + warpinfo->lmvecs[0][3];
s -= warpinfo->lightmapmins[0];
s += warpface->light_s * sample_size;
s += sample_size * 0.5f;
s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;
t = DotProduct( verts, warpinfo->lmvecs[1] ) + warpinfo->lmvecs[1][3];
t -= warpinfo->lightmapmins[1];
t += warpface->light_t * sample_size;
t += sample_size * 0.5f;
t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height;
poly->verts[i][5] = s;
poly->verts[i][6] = t;
}
}
}
void GL_SetupFogColorForSurfaces( void )
{
vec3_t fogColor;
float factor, div;
if( !glState.isFogEnabled)
return;
if( RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture )
{
pglFogfv( GL_FOG_COLOR, RI.fogColor );
return;
}
div = (r_detailtextures->value) ? 2.0f : 1.0f;
factor = (r_detailtextures->value) ? 3.0f : 2.0f;
fogColor[0] = pow( RI.fogColor[0] / div, ( 1.0f / factor ));
fogColor[1] = pow( RI.fogColor[1] / div, ( 1.0f / factor ));
fogColor[2] = pow( RI.fogColor[2] / div, ( 1.0f / factor ));
pglFogfv( GL_FOG_COLOR, fogColor );
}
void GL_ResetFogColor( void )
{
// restore fog here
if( glState.isFogEnabled )
pglFogfv( GL_FOG_COLOR, RI.fogColor );
}
/*
================
GL_SubdivideSurface
Breaks a polygon up along axial 64 unit
boundaries so that turbulent and sky warps
can be done reasonably.
================
*/
void GL_SubdivideSurface( msurface_t *fa )
{
vec3_t verts[SUBDIVIDE_SIZE];
int numverts;
int i, lindex;
float *vec;
model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel();
// convert edges back to a normal polygon
numverts = 0;
for( i = 0; i < fa->numedges; i++ )
{
lindex = loadmodel->surfedges[fa->firstedge + i];
if( lindex > 0 ) vec = loadmodel->vertexes[loadmodel->edges[lindex].v[0]].position;
else vec = loadmodel->vertexes[loadmodel->edges[-lindex].v[1]].position;
VectorCopy( vec, verts[numverts] );
numverts++;
}
SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state
// do subdivide
SubdividePolygon_r( fa, numverts, verts[0] );
}
/*
================
GL_BuildPolygonFromSurface
================
*/
void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa )
{
int i, lindex, lnumverts;
medge_t *pedges, *r_pedge;
mextrasurf_t *info = fa->info;
float sample_size;
texture_t *tex;
gl_texture_t *glt;
float *vec;
float s, t;
glpoly_t *poly;
if( !mod || !fa->texinfo || !fa->texinfo->texture )
return; // bad polygon ?
if( FBitSet( fa->flags, SURF_CONVEYOR ) && fa->texinfo->texture->gl_texturenum != 0 )
{
glt = R_GetTexture( fa->texinfo->texture->gl_texturenum );
tex = fa->texinfo->texture;
Assert( glt != NULL && tex != NULL );
// update conveyor widths for keep properly speed of scrolling
glt->srcWidth = tex->width;
glt->srcHeight = tex->height;
}
sample_size = gEngfuncs.Mod_SampleSizeForFace( fa );
// reconstruct the polygon
pedges = mod->edges;
lnumverts = fa->numedges;
// detach if already created, reconstruct again
poly = fa->polys;
fa->polys = NULL;
// quake simple models (healthkits etc) need to be reconstructed their polys because LM coords has changed after the map change
poly = Mem_Realloc( mod->mempool, poly, sizeof( glpoly_t ) + ( lnumverts - 4 ) * VERTEXSIZE * sizeof( float ));
poly->next = fa->polys;
poly->flags = fa->flags;
fa->polys = poly;
poly->numverts = lnumverts;
for( i = 0; i < lnumverts; i++ )
{
lindex = mod->surfedges[fa->firstedge + i];
if( lindex > 0 )
{
r_pedge = &pedges[lindex];
vec = mod->vertexes[r_pedge->v[0]].position;
}
else
{
r_pedge = &pedges[-lindex];
vec = mod->vertexes[r_pedge->v[1]].position;
}
s = DotProduct( vec, fa->texinfo->vecs[0] ) + fa->texinfo->vecs[0][3];
s /= fa->texinfo->texture->width;
t = DotProduct( vec, fa->texinfo->vecs[1] ) + fa->texinfo->vecs[1][3];
t /= fa->texinfo->texture->height;
VectorCopy( vec, poly->verts[i] );
poly->verts[i][3] = s;
poly->verts[i][4] = t;
// lightmap texture coordinates
s = DotProduct( vec, info->lmvecs[0] ) + info->lmvecs[0][3];
s -= info->lightmapmins[0];
s += fa->light_s * sample_size;
s += sample_size * 0.5f;
s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width;
t = DotProduct( vec, info->lmvecs[1] ) + info->lmvecs[1][3];
t -= info->lightmapmins[1];
t += fa->light_t * sample_size;
t += sample_size * 0.5f;
t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->height;
poly->verts[i][5] = s;
poly->verts[i][6] = t;
}
// remove co-linear points - Ed
if( !CVAR_TO_BOOL( gl_keeptjunctions ) && !FBitSet( fa->flags, SURF_UNDERWATER ))
{
for( i = 0; i < lnumverts; i++ )
{
vec3_t v1, v2;
float *prev, *this, *next;
prev = poly->verts[(i + lnumverts - 1) % lnumverts];
next = poly->verts[(i + 1) % lnumverts];
this = poly->verts[i];
VectorSubtract( this, prev, v1 );
VectorNormalize( v1 );
VectorSubtract( next, prev, v2 );
VectorNormalize( v2 );
// skip co-linear points
if(( fabs( v1[0] - v2[0] ) <= 0.001f) && (fabs( v1[1] - v2[1] ) <= 0.001f) && (fabs( v1[2] - v2[2] ) <= 0.001f))
{
int j, k;
for( j = i + 1; j < lnumverts; j++ )
{
for( k = 0; k < VERTEXSIZE; k++ )
poly->verts[j-1][k] = poly->verts[j][k];
}
// retry next vertex next time, which is now current vertex
lnumverts--;
nColinElim++;
i--;
}
}
}
poly->numverts = lnumverts;
}
/*
===============
R_TextureAnim
Returns the proper texture for a given time and base texture, do not process random tiling
===============
*/
texture_t *R_TextureAnim( texture_t *b )
{
texture_t *base = b;
int count, reletive;
if( RI.currententity->curstate.frame )
{
if( base->alternate_anims )
base = base->alternate_anims;
}
if( !base->anim_total )
return base;
if( base->name[0] == '-' )
{
return b; // already tiled
}
else
{
int speed;
// Quake1 textures uses 10 frames per second
if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL ))
speed = 10;
else speed = 20;
reletive = (int)(gpGlobals->time * speed) % base->anim_total;
}
count = 0;
while( base->anim_min > reletive || base->anim_max <= reletive )
{
base = base->anim_next;
if( !base || ++count > MOD_FRAMES )
return b;
}
return base;
}
/*
===============
R_TextureAnimation
Returns the proper texture for a given time and surface
===============
*/
texture_t *R_TextureAnimation( msurface_t *s )
{
texture_t *base = s->texinfo->texture;
int count, reletive;
if( RI.currententity && RI.currententity->curstate.frame )
{
if( base->alternate_anims )
base = base->alternate_anims;
}
if( !base->anim_total )
return base;
if( base->name[0] == '-' )
{
int tx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES;
int ty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES;
reletive = rtable[tx][ty] % base->anim_total;
}
else
{
int speed;
// Quake1 textures uses 10 frames per second
if( FBitSet( R_GetTexture( base->gl_texturenum )->flags, TF_QUAKEPAL ))
speed = 10;
else speed = 20;
reletive = (int)(gpGlobals->time * speed) % base->anim_total;
}
count = 0;
while( base->anim_min > reletive || base->anim_max <= reletive )
{
base = base->anim_next;
if( !base || ++count > MOD_FRAMES )
return s->texinfo->texture;
}
return base;
}
/*
===============
R_AddDynamicLights
===============
*/
void R_AddDynamicLights( msurface_t *surf )
{
float dist, rad, minlight;
int lnum, s, t, sd, td, smax, tmax;
float sl, tl, sacc, tacc;
vec3_t impact, origin_l;
mextrasurf_t *info = surf->info;
int sample_frac = 1.0;
float sample_size;
mtexinfo_t *tex;
dlight_t *dl;
uint *bl;
// no dlighted surfaces here
if( !R_CountSurfaceDlights( surf )) return;
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
smax = (info->lightextents[0] / sample_size) + 1;
tmax = (info->lightextents[1] / sample_size) + 1;
tex = surf->texinfo;
if( FBitSet( tex->flags, TEX_WORLD_LUXELS ))
{
if( surf->texinfo->faceinfo )
sample_frac = surf->texinfo->faceinfo->texture_step;
else if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP ))
sample_frac = LM_SAMPLE_EXTRASIZE;
else sample_frac = LM_SAMPLE_SIZE;
}
for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )
{
if( !FBitSet( surf->dlightbits, BIT( lnum )))
continue; // not lit by this light
dl = gEngfuncs.GetDynamicLight( lnum );
// transform light origin to local bmodel space
if( !tr.modelviewIdentity )
Matrix4x4_VectorITransform( RI.objectMatrix, dl->origin, origin_l );
else VectorCopy( dl->origin, origin_l );
rad = dl->radius;
dist = PlaneDiff( origin_l, surf->plane );
rad -= fabs( dist );
// rad is now the highest intensity on the plane
minlight = dl->minlight;
if( rad < minlight )
continue;
minlight = rad - minlight;
if( surf->plane->type < 3 )
{
VectorCopy( origin_l, impact );
impact[surf->plane->type] -= dist;
}
else VectorMA( origin_l, -dist, surf->plane->normal, impact );
sl = DotProduct( impact, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0];
tl = DotProduct( impact, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1];
bl = r_blocklights;
for( t = 0, tacc = 0; t < tmax; t++, tacc += sample_size )
{
td = (tl - tacc) * sample_frac;
if( td < 0 ) td = -td;
for( s = 0, sacc = 0; s < smax; s++, sacc += sample_size, bl += 3 )
{
sd = (sl - sacc) * sample_frac;
if( sd < 0 ) sd = -sd;
if( sd > td ) dist = sd + (td >> 1);
else dist = td + (sd >> 1);
if( dist < minlight )
{
bl[0] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.r )) / 256;
bl[1] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.g )) / 256;
bl[2] += ((int)((rad - dist) * 256) * gEngfuncs.LightToTexGamma( dl->color.b )) / 256;
}
}
}
}
}
/*
================
R_SetCacheState
================
*/
void R_SetCacheState( msurface_t *surf )
{
int maps;
for( maps = 0; maps < MAXLIGHTMAPS && surf->styles[maps] != 255; maps++ )
{
surf->cached_light[maps] = tr.lightstylevalue[surf->styles[maps]];
}
}
/*
=============================================================================
LIGHTMAP ALLOCATION
=============================================================================
*/
static void LM_InitBlock( void )
{
memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated ));
}
static int LM_AllocBlock( int w, int h, int *x, int *y )
{
int i, j;
int best, best2;
best = BLOCK_SIZE;
for( i = 0; i < BLOCK_SIZE - w; i++ )
{
best2 = 0;
for( j = 0; j < w; j++ )
{
if( gl_lms.allocated[i+j] >= best )
break;
if( gl_lms.allocated[i+j] > best2 )
best2 = gl_lms.allocated[i+j];
}
if( j == w )
{
// this is a valid spot
*x = i;
*y = best = best2;
}
}
if( best + h > BLOCK_SIZE )
return false;
for( i = 0; i < w; i++ )
gl_lms.allocated[*x + i] = best + h;
return true;
}
static void LM_UploadDynamicBlock( void )
{
int height = 0, i;
for( i = 0; i < BLOCK_SIZE; i++ )
{
if( gl_lms.allocated[i] > height )
height = gl_lms.allocated[i];
}
pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer );
}
static void LM_UploadBlock( qboolean dynamic )
{
int i;
if( dynamic )
{
int height = 0;
for( i = 0; i < BLOCK_SIZE; i++ )
{
if( gl_lms.allocated[i] > height )
height = gl_lms.allocated[i];
}
GL_Bind( XASH_TEXTURE0, tr.dlightTexture );
pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, BLOCK_SIZE, height, GL_RGBA, GL_UNSIGNED_BYTE, gl_lms.lightmap_buffer );
}
else
{
rgbdata_t r_lightmap;
char lmName[16];
i = gl_lms.current_lightmap_texture;
// upload static lightmaps only during loading
memset( &r_lightmap, 0, sizeof( r_lightmap ));
Q_snprintf( lmName, sizeof( lmName ), "*lightmap%i", i );
r_lightmap.width = BLOCK_SIZE;
r_lightmap.height = BLOCK_SIZE;
r_lightmap.type = PF_RGBA_32;
r_lightmap.size = r_lightmap.width * r_lightmap.height * 4;
r_lightmap.flags = IMAGE_HAS_COLOR;
r_lightmap.buffer = gl_lms.lightmap_buffer;
tr.lightmapTextures[i] = GL_LoadTextureInternal( lmName, &r_lightmap, TF_FONT|TF_ATLAS_PAGE );
if( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS )
gEngfuncs.Host_Error( "AllocBlock: full\n" );
}
}
/*
=================
R_BuildLightmap
Combine and scale multiple lightmaps into the floating
format in r_blocklights
=================
*/
static void R_BuildLightMap( msurface_t *surf, byte *dest, int stride, qboolean dynamic )
{
int smax, tmax;
uint *bl, scale;
int i, map, size, s, t;
int sample_size;
mextrasurf_t *info = surf->info;
color24 *lm;
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
smax = ( info->lightextents[0] / sample_size ) + 1;
tmax = ( info->lightextents[1] / sample_size ) + 1;
size = smax * tmax;
lm = surf->samples;
memset( r_blocklights, 0, sizeof( uint ) * size * 3 );
// add all the lightmaps
for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255 && lm; map++ )
{
scale = tr.lightstylevalue[surf->styles[map]];
for( i = 0, bl = r_blocklights; i < size; i++, bl += 3, lm++ )
{
bl[0] += gEngfuncs.LightToTexGamma( lm->r ) * scale;
bl[1] += gEngfuncs.LightToTexGamma( lm->g ) * scale;
bl[2] += gEngfuncs.LightToTexGamma( lm->b ) * scale;
}
}
// add all the dynamic lights
if( surf->dlightframe == tr.framecount && dynamic )
R_AddDynamicLights( surf );
// Put into texture format
stride -= (smax << 2);
bl = r_blocklights;
for( t = 0; t < tmax; t++, dest += stride )
{
for( s = 0; s < smax; s++ )
{
dest[0] = Q_min((bl[0] >> 7), 255 );
dest[1] = Q_min((bl[1] >> 7), 255 );
dest[2] = Q_min((bl[2] >> 7), 255 );
dest[3] = 255;
bl += 3;
dest += 4;
}
}
}
/*
================
DrawGLPoly
================
*/
void DrawGLPoly( glpoly_t *p, float xScale, float yScale )
{
float *v;
float sOffset, sy;
float tOffset, cy;
cl_entity_t *e = RI.currententity;
int i, hasScale = false;
if( !p ) return;
if( FBitSet( p->flags, SURF_DRAWTILED ))
GL_ResetFogColor();
if( p->flags & SURF_CONVEYOR )
{
float flConveyorSpeed = 0.0f;
float flRate, flAngle;
gl_texture_t *texture;
if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && RI.currententity == gEngfuncs.GetEntityByIndex( 0 ) )
{
// same as doom speed
flConveyorSpeed = -35.0f;
}
else
{
flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f;
if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed;
}
texture = R_GetTexture( glState.currentTextures[glState.activeTMU] );
flRate = fabs( flConveyorSpeed ) / (float)texture->srcWidth;
flAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0;
SinCos( flAngle * ( M_PI_F / 180.0f ), &sy, &cy );
sOffset = gpGlobals->time * cy * flRate;
tOffset = gpGlobals->time * sy * flRate;
// make sure that we are positive
if( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset;
if( tOffset < 0.0f ) tOffset += 1.0f + -(int)tOffset;
// make sure that we are in a [0,1] range
sOffset = sOffset - (int)sOffset;
tOffset = tOffset - (int)tOffset;
}
else
{
sOffset = tOffset = 0.0f;
}
if( xScale != 0.0f && yScale != 0.0f )
hasScale = true;
pglBegin( GL_POLYGON );
for( i = 0, v = p->verts[0]; i < p->numverts; i++, v += VERTEXSIZE )
{
if( hasScale )
pglTexCoord2f(( v[3] + sOffset ) * xScale, ( v[4] + tOffset ) * yScale );
else pglTexCoord2f( v[3] + sOffset, v[4] + tOffset );
pglVertex3fv( v );
}
pglEnd();
if( FBitSet( p->flags, SURF_DRAWTILED ))
GL_SetupFogColorForSurfaces();
}
/*
================
DrawGLPolyChain
Render lightmaps
================
*/
void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset )
{
qboolean dynamic = true;
if( soffset == 0.0f && toffset == 0.0f )
dynamic = false;
for( ; p != NULL; p = p->chain )
{
float *v;
int i;
pglBegin( GL_POLYGON );
v = p->verts[0];
for( i = 0; i < p->numverts; i++, v += VERTEXSIZE )
{
if( !dynamic ) pglTexCoord2f( v[5], v[6] );
else pglTexCoord2f( v[5] - soffset, v[6] - toffset );
pglVertex3fv( v );
}
pglEnd ();
}
}
_inline qboolean R_HasLightmap( void )
{
if( CVAR_TO_BOOL( r_fullbright ) || !WORLDMODEL->lightdata )
return false;
if( RI.currententity )
{
if( RI.currententity->curstate.effects & EF_FULLBRIGHT )
return false; // disabled by user
// check for rendermode
switch( RI.currententity->curstate.rendermode )
{
case kRenderTransTexture:
case kRenderTransColor:
case kRenderTransAdd:
case kRenderGlow:
return false; // no lightmaps
}
}
return true;
}
/*
================
R_BlendLightmaps
================
*/
void R_BlendLightmaps( void )
{
msurface_t *surf, *newsurf = NULL;
int i;
if( !R_HasLightmap() )
return;
GL_SetupFogColorForSurfaces ();
if( !CVAR_TO_BOOL( r_lightmap ))
pglEnable( GL_BLEND );
else pglDisable( GL_BLEND );
// lightmapped solid surfaces
pglDepthMask( GL_FALSE );
pglDepthFunc( GL_EQUAL );
pglDisable( GL_ALPHA_TEST );
pglBlendFunc( GL_ZERO, GL_SRC_COLOR );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
// render static lightmaps first
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
if( gl_lms.lightmap_surfaces[i] )
{
GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[i] );
for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain )
{
if( surf->polys ) DrawGLPolyChain( surf->polys, 0.0f, 0.0f );
}
}
}
// render dynamic lightmaps
if( CVAR_TO_BOOL( r_dynamic ))
{
LM_InitBlock();
GL_Bind( XASH_TEXTURE0, tr.dlightTexture );
newsurf = gl_lms.dynamic_surfaces;
for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain )
{
int smax, tmax;
int sample_size;
mextrasurf_t *info = surf->info;
byte *base;
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
smax = ( info->lightextents[0] / sample_size ) + 1;
tmax = ( info->lightextents[1] / sample_size ) + 1;
if( LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t ))
{
base = gl_lms.lightmap_buffer;
base += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * 4;
R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );
}
else
{
msurface_t *drawsurf;
// upload what we have so far
LM_UploadBlock( true );
// draw all surfaces that use this lightmap
for( drawsurf = newsurf; drawsurf != surf; drawsurf = drawsurf->info->lightmapchain )
{
if( drawsurf->polys )
{
DrawGLPolyChain( drawsurf->polys,
( drawsurf->light_s - drawsurf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ),
( drawsurf->light_t - drawsurf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ));
}
}
newsurf = drawsurf;
// clear the block
LM_InitBlock();
// try uploading the block now
if( !LM_AllocBlock( smax, tmax, &surf->info->dlight_s, &surf->info->dlight_t ))
gEngfuncs.Host_Error( "AllocBlock: full\n" );
base = gl_lms.lightmap_buffer;
base += ( surf->info->dlight_t * BLOCK_SIZE + surf->info->dlight_s ) * 4;
R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );
}
}
// draw remainder of dynamic lightmaps that haven't been uploaded yet
if( newsurf ) LM_UploadBlock( true );
for( surf = newsurf; surf != NULL; surf = surf->info->lightmapchain )
{
if( surf->polys )
{
DrawGLPolyChain( surf->polys,
( surf->light_s - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ),
( surf->light_t - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ));
}
}
}
pglDisable( GL_BLEND );
pglDepthMask( GL_TRUE );
pglDepthFunc( GL_LEQUAL );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
// restore fog here
GL_ResetFogColor();
}
/*
================
R_RenderFullbrights
================
*/
void R_RenderFullbrights( void )
{
mextrasurf_t *es, *p;
int i;
if( !draw_fullbrights )
return;
R_AllowFog( false );
pglEnable( GL_BLEND );
pglDepthMask( GL_FALSE );
pglDisable( GL_ALPHA_TEST );
pglBlendFunc( GL_ONE, GL_ONE );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
for( i = 1; i < MAX_TEXTURES; i++ )
{
es = fullbright_surfaces[i];
if( !es ) continue;
GL_Bind( XASH_TEXTURE0, i );
for( p = es; p; p = p->lumachain )
DrawGLPoly( p->surf->polys, 0.0f, 0.0f );
fullbright_surfaces[i] = NULL;
es->lumachain = NULL;
}
pglDisable( GL_BLEND );
pglDepthMask( GL_TRUE );
pglDisable( GL_ALPHA_TEST );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
draw_fullbrights = false;
R_AllowFog( true );
}
/*
================
R_RenderDetails
================
*/
void R_RenderDetails( void )
{
gl_texture_t *glt;
mextrasurf_t *es, *p;
msurface_t *fa;
int i;
if( !draw_details )
return;
GL_SetupFogColorForSurfaces();
pglEnable( GL_BLEND );
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
pglDepthFunc( GL_EQUAL );
for( i = 1; i < MAX_TEXTURES; i++ )
{
es = detail_surfaces[i];
if( !es ) continue;
GL_Bind( XASH_TEXTURE0, i );
for( p = es; p; p = p->detailchain )
{
fa = p->surf;
glt = R_GetTexture( fa->texinfo->texture->gl_texturenum ); // get texture scale
DrawGLPoly( fa->polys, glt->xscale, glt->yscale );
}
detail_surfaces[i] = NULL;
es->detailchain = NULL;
}
pglDisable( GL_BLEND );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglDepthFunc( GL_LEQUAL );
draw_details = false;
// restore fog here
GL_ResetFogColor();
}
/*
================
R_RenderBrushPoly
================
*/
void R_RenderBrushPoly( msurface_t *fa, int cull_type )
{
qboolean is_dynamic = false;
int maps;
texture_t *t;
r_stats.c_world_polys++;
if( fa->flags & SURF_DRAWSKY )
return; // already handled
t = R_TextureAnimation( fa );
GL_Bind( XASH_TEXTURE0, t->gl_texturenum );
if( FBitSet( fa->flags, SURF_DRAWTURB ))
{
// warp texture, no lightmaps
EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE));
return;
}
if( t->fb_texturenum )
{
fa->info->lumachain = fullbright_surfaces[t->fb_texturenum];
fullbright_surfaces[t->fb_texturenum] = fa->info;
draw_fullbrights = true;
}
if( CVAR_TO_BOOL( r_detailtextures ))
{
if( glState.isFogEnabled )
{
// don't apply detail textures for windows in the fog
if( RI.currententity->curstate.rendermode != kRenderTransTexture )
{
if( t->dt_texturenum )
{
fa->info->detailchain = detail_surfaces[t->dt_texturenum];
detail_surfaces[t->dt_texturenum] = fa->info;
}
else
{
// draw stub detail texture for underwater surfaces
fa->info->detailchain = detail_surfaces[tr.grayTexture];
detail_surfaces[tr.grayTexture] = fa->info;
}
draw_details = true;
}
}
else if( t->dt_texturenum )
{
fa->info->detailchain = detail_surfaces[t->dt_texturenum];
detail_surfaces[t->dt_texturenum] = fa->info;
draw_details = true;
}
}
DrawGLPoly( fa->polys, 0.0f, 0.0f );
if( RI.currententity->curstate.rendermode == kRenderNormal )
{
// batch decals to draw later
if( tr.num_draw_decals < MAX_DECAL_SURFS && fa->pdecals )
tr.draw_decals[tr.num_draw_decals++] = fa;
}
else
{
// if rendermode != kRenderNormal draw decals sequentially
DrawSurfaceDecals( fa, true, (cull_type == CULL_BACKSIDE));
}
if( FBitSet( fa->flags, SURF_DRAWTILED ))
return; // no lightmaps anyway
// check for lightmap modification
for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ )
{
if( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] )
goto dynamic;
}
// dynamic this frame or dynamic previously
if( fa->dlightframe == tr.framecount )
{
dynamic:
// NOTE: at this point we have only valid textures
if( r_dynamic->value ) is_dynamic = true;
}
if( is_dynamic )
{
if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 || fa->styles[maps] == 20 ) && ( fa->dlightframe != tr.framecount ))
{
byte temp[132*132*4];
mextrasurf_t *info = fa->info;
int sample_size;
int smax, tmax;
sample_size = gEngfuncs.Mod_SampleSizeForFace( fa );
smax = ( info->lightextents[0] / sample_size ) + 1;
tmax = ( info->lightextents[1] / sample_size ) + 1;
R_BuildLightMap( fa, temp, smax * 4, true );
R_SetCacheState( fa );
GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] );
pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax,
GL_RGBA, GL_UNSIGNED_BYTE, temp );
fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum];
gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa;
}
else
{
fa->info->lightmapchain = gl_lms.dynamic_surfaces;
gl_lms.dynamic_surfaces = fa;
}
}
else
{
fa->info->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum];
gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa;
}
}
/*
================
R_DrawTextureChains
================
*/
void R_DrawTextureChains( void )
{
int i;
msurface_t *s;
texture_t *t;
// make sure what color is reset
pglColor4ub( 255, 255, 255, 255 );
R_LoadIdentity(); // set identity matrix
GL_SetupFogColorForSurfaces();
// restore worldmodel
RI.currententity = gEngfuncs.GetEntityByIndex( 0 );
RI.currentmodel = RI.currententity->model;
if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) )
{
pglDisable( GL_TEXTURE_2D );
pglColor3f( 1.0f, 1.0f, 1.0f );
}
// clip skybox surfaces
for( s = skychain; s != NULL; s = s->texturechain )
R_AddSkyBoxSurface( s );
if( ENGINE_GET_PARM( PARM_SKY_SPHERE ) )
{
pglEnable( GL_TEXTURE_2D );
if( skychain )
R_DrawClouds();
skychain = NULL;
}
for( i = 0; i < WORLDMODEL->numtextures; i++ )
{
t = WORLDMODEL->textures[i];
if( !t ) continue;
s = t->texturechain;
if( !s || ( i == tr.skytexturenum ))
continue;
if(( s->flags & SURF_DRAWTURB ) && MOVEVARS->wateralpha < 1.0f )
continue; // draw translucent water later
if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( s->flags, SURF_TRANSPARENT ))
{
draw_alpha_surfaces = true;
continue; // draw transparent surfaces later
}
for( ; s != NULL; s = s->texturechain )
R_RenderBrushPoly( s, CULL_VISIBLE );
t->texturechain = NULL;
}
}
/*
================
R_DrawAlphaTextureChains
================
*/
void R_DrawAlphaTextureChains( void )
{
int i;
msurface_t *s;
texture_t *t;
if( !draw_alpha_surfaces )
return;
memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces ));
gl_lms.dynamic_surfaces = NULL;
// make sure what color is reset
pglColor4ub( 255, 255, 255, 255 );
R_LoadIdentity(); // set identity matrix
pglDisable( GL_BLEND );
pglEnable( GL_ALPHA_TEST );
pglAlphaFunc( GL_GREATER, 0.25f );
GL_SetupFogColorForSurfaces();
// restore worldmodel
RI.currententity = gEngfuncs.GetEntityByIndex( 0 );
RI.currentmodel = RI.currententity->model;
RI.currententity->curstate.rendermode = kRenderTransAlpha;
draw_alpha_surfaces = false;
for( i = 0; i < WORLDMODEL->numtextures; i++ )
{
t = WORLDMODEL->textures[i];
if( !t ) continue;
s = t->texturechain;
if( !s || !FBitSet( s->flags, SURF_TRANSPARENT ))
continue;
for( ; s != NULL; s = s->texturechain )
R_RenderBrushPoly( s, CULL_VISIBLE );
t->texturechain = NULL;
}
GL_ResetFogColor();
R_BlendLightmaps();
RI.currententity->curstate.rendermode = kRenderNormal; // restore world rendermode
pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );
}
/*
================
R_DrawWaterSurfaces
================
*/
void R_DrawWaterSurfaces( void )
{
int i;
msurface_t *s;
texture_t *t;
if( !RI.drawWorld || RI.onlyClientDraw )
return;
// non-transparent water is already drawed
if( MOVEVARS->wateralpha >= 1.0f )
return;
// restore worldmodel
RI.currententity = gEngfuncs.GetEntityByIndex( 0 );
RI.currentmodel = RI.currententity->model;
// go back to the world matrix
R_LoadIdentity();
pglEnable( GL_BLEND );
pglDepthMask( GL_FALSE );
pglDisable( GL_ALPHA_TEST );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
pglColor4f( 1.0f, 1.0f, 1.0f, MOVEVARS->wateralpha );
for( i = 0; i < WORLDMODEL->numtextures; i++ )
{
t = WORLDMODEL->textures[i];
if( !t ) continue;
s = t->texturechain;
if( !s ) continue;
if( !FBitSet( s->flags, SURF_DRAWTURB ))
continue;
// set modulate mode explicitly
GL_Bind( XASH_TEXTURE0, t->gl_texturenum );
for( ; s; s = s->texturechain )
EmitWaterPolys( s, false );
t->texturechain = NULL;
}
pglDisable( GL_BLEND );
pglDepthMask( GL_TRUE );
pglDisable( GL_ALPHA_TEST );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglColor4ub( 255, 255, 255, 255 );
}
/*
=================
R_SurfaceCompare
compare translucent surfaces
=================
*/
static int R_SurfaceCompare( const void *a, const void *b )
{
msurface_t *surf1, *surf2;
vec3_t org1, org2;
float len1, len2;
surf1 = (msurface_t *)((sortedface_t *)a)->surf;
surf2 = (msurface_t *)((sortedface_t *)b)->surf;
VectorAdd( RI.currententity->origin, surf1->info->origin, org1 );
VectorAdd( RI.currententity->origin, surf2->info->origin, org2 );
// compare by plane dists
len1 = DotProduct( org1, RI.vforward ) - RI.viewplanedist;
len2 = DotProduct( org2, RI.vforward ) - RI.viewplanedist;
if( len1 > len2 )
return -1;
if( len1 < len2 )
return 1;
return 0;
}
void R_SetRenderMode( cl_entity_t *e )
{
switch( e->curstate.rendermode )
{
case kRenderNormal:
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
break;
case kRenderTransColor:
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
pglColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, e->curstate.renderamt );
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
pglDisable( GL_TEXTURE_2D );
pglEnable( GL_BLEND );
break;
case kRenderTransAdd:
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
pglColor4f( tr.blend, tr.blend, tr.blend, 1.0f );
pglBlendFunc( GL_ONE, GL_ONE );
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
break;
case kRenderTransAlpha:
pglEnable( GL_ALPHA_TEST );
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))
{
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend );
pglEnable( GL_BLEND );
}
else
{
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
pglDisable( GL_BLEND );
}
pglAlphaFunc( GL_GREATER, 0.25f );
break;
default:
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
pglColor4f( 1.0f, 1.0f, 1.0f, tr.blend );
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
break;
}
}
/*
=================
R_DrawBrushModel
=================
*/
void R_DrawBrushModel( cl_entity_t *e )
{
int i, k, num_sorted;
vec3_t origin_l, oldorigin;
int old_rendermode;
vec3_t mins, maxs;
int cull_type;
msurface_t *psurf;
model_t *clmodel;
qboolean rotated;
dlight_t *l;
qboolean allow_vbo = CVAR_TO_BOOL( r_vbo );
if( !RI.drawWorld ) return;
clmodel = e->model;
// external models not loaded to VBO
if( clmodel->surfaces != WORLDMODEL->surfaces )
allow_vbo = false;
if( !VectorIsNull( e->angles ))
{
for( i = 0; i < 3; i++ )
{
mins[i] = e->origin[i] - clmodel->radius;
maxs[i] = e->origin[i] + clmodel->radius;
}
rotated = true;
}
else
{
VectorAdd( e->origin, clmodel->mins, mins );
VectorAdd( e->origin, clmodel->maxs, maxs );
rotated = false;
}
if( R_CullBox( mins, maxs ))
return;
memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces ));
old_rendermode = e->curstate.rendermode;
gl_lms.dynamic_surfaces = NULL;
if( rotated ) R_RotateForEntity( e );
else R_TranslateForEntity( e );
if( ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ) && FBitSet( clmodel->flags, MODEL_TRANSPARENT ))
e->curstate.rendermode = kRenderTransAlpha;
e->visframe = tr.realframecount; // visible
if( rotated ) Matrix4x4_VectorITransform( RI.objectMatrix, RI.cullorigin, tr.modelorg );
else VectorSubtract( RI.cullorigin, e->origin, tr.modelorg );
// calculate dynamic lighting for bmodel
for( k = 0; k < MAX_DLIGHTS; k++ )
{
l = gEngfuncs.GetDynamicLight( k );
if( l->die < gpGlobals->time || !l->radius )
continue;
VectorCopy( l->origin, oldorigin ); // save lightorigin
Matrix4x4_VectorITransform( RI.objectMatrix, l->origin, origin_l );
VectorCopy( origin_l, l->origin ); // move light in bmodel space
R_MarkLights( l, 1<<k, clmodel->nodes + clmodel->hulls[0].firstclipnode );
VectorCopy( oldorigin, l->origin ); // restore lightorigin
}
// setup the rendermode
R_SetRenderMode( e );
GL_SetupFogColorForSurfaces ();
if( e->curstate.rendermode == kRenderTransAdd )
{
R_AllowFog( false );
allow_vbo = false;
}
if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture )
allow_vbo = false;
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
num_sorted = 0;
for( i = 0; i < clmodel->nummodelsurfaces; i++, psurf++ )
{
if( FBitSet( psurf->flags, SURF_DRAWTURB ) && !ENGINE_GET_PARM( PARM_QUAKE_COMPATIBLE ))
{
if( psurf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES ))
continue;
if( mins[2] + 1.0f >= psurf->plane->dist )
continue;
}
cull_type = R_CullSurface( psurf, &RI.frustum, RI.frustum.clipFlags );
if( cull_type >= CULL_FRUSTUM )
continue;
if( cull_type == CULL_BACKSIDE )
{
if( !FBitSet( psurf->flags, SURF_DRAWTURB ) && !( psurf->pdecals && e->curstate.rendermode == kRenderTransTexture ))
continue;
}
if( num_sorted < gpGlobals->max_surfaces )
{
gpGlobals->draw_surfaces[num_sorted].surf = psurf;
gpGlobals->draw_surfaces[num_sorted].cull = cull_type;
num_sorted++;
}
}
// sort faces if needs
if( !FBitSet( clmodel->flags, MODEL_LIQUID ) && e->curstate.rendermode == kRenderTransTexture && !CVAR_TO_BOOL( gl_nosort ))
qsort( gpGlobals->draw_surfaces, num_sorted, sizeof( sortedface_t ), R_SurfaceCompare );
// draw sorted translucent surfaces
for( i = 0; i < num_sorted; i++ )
if( !allow_vbo || !R_AddSurfToVBO( gpGlobals->draw_surfaces[i].surf, true ) )
R_RenderBrushPoly( gpGlobals->draw_surfaces[i].surf, gpGlobals->draw_surfaces[i].cull );
R_DrawVBO( R_HasLightmap(), true );
if( e->curstate.rendermode == kRenderTransColor )
pglEnable( GL_TEXTURE_2D );
DrawDecalsBatch();
GL_ResetFogColor();
R_BlendLightmaps();
R_RenderFullbrights();
R_RenderDetails();
// restore fog here
if( e->curstate.rendermode == kRenderTransAdd )
R_AllowFog( true );
e->curstate.rendermode = old_rendermode;
pglDisable( GL_ALPHA_TEST );
pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );
pglDisable( GL_BLEND );
pglDepthMask( GL_TRUE );
R_DrawModelHull(); // draw before restore
R_LoadIdentity(); // restore worldmatrix
}
/*
==============================
VBO
==============================
*/
/*
Bulld arrays (vboarray_t) for all map geometry on map load.
Store index base for every surface (vbosurfdata_t) to build index arrays
For each texture build index arrays (vbotexture_t) every frame.
*/
// vertex attribs
//#define NO_TEXTURE_MATRIX // need debug
typedef struct vbovertex_s
{
vec3_t pos;
vec2_t gl_tc;
vec2_t lm_tc;
#ifdef NO_TEXTURE_MATRIX
vec2_t dt_tc;
#endif
} vbovertex_t;
// store indexes for each texture
typedef struct vbotexture_s
{
unsigned short *indexarray; // index array (generated instead of texture chains)
uint curindex; // counter for index array
uint len; // maximum index array length
struct vbotexture_s *next; // if cannot fit into one array, allocate new one, as every array has own index space
msurface_t *dlightchain; // list of dlight surfaces
struct vboarray_s *vboarray; // debug
uint lightmaptexturenum;
} vbotexture_t;
// array list
typedef struct vboarray_s
{
uint glindex; // glGenBuffers
int array_len; // allocation length
vbovertex_t *array; // vertex attrib array
struct vboarray_s *next; // split by 65536 vertices
} vboarray_t;
// every surface is linked to vbo texture
typedef struct vbosurfdata_s
{
vbotexture_t *vbotexture;
uint texturenum;
uint startindex;
} vbosurfdata_t;
typedef struct vbodecal_s
{
int numVerts;
} vbodecal_t;
#define DECAL_VERTS_MAX 32
#define DECAL_VERTS_CUT 8
typedef struct vbodecaldata_s
{
vbodecal_t decals[MAX_RENDER_DECALS];
vbovertex_t decalarray[MAX_RENDER_DECALS * DECAL_VERTS_CUT];
uint decalvbo;
msurface_t **lm;
} vbodecaldata_t;
// gl_decals.c
extern decal_t gDecalPool[MAX_RENDER_DECALS];
struct vbo_static_s
{
// quickly free all allocations on map change
poolhandle_t mempool;
// arays
vbodecaldata_t *decaldata; // array
vbotexture_t *textures; // array
vbosurfdata_t *surfdata; // array
vboarray_t *arraylist; // linked list
// separate areay for dlights (build during draw)
unsigned short *dlight_index; // array
vec2_t *dlight_tc; // array
unsigned int dlight_vbo;
vbovertex_t decal_dlight[MAX_RENDER_DECALS * DECAL_VERTS_MAX];
unsigned int decal_dlight_vbo;
int decal_numverts[MAX_RENDER_DECALS * DECAL_VERTS_MAX];
// prevent draining cpu on empty cycles
int minlightmap;
int maxlightmap;
int mintexture;
int maxtexture;
// never skip array splits
int minarraysplit_tex;
int maxarraysplit_tex;
int minarraysplit_lm;
int maxarraysplit_lm;
} vbos;
struct multitexturestate_s
{
int tmu_gl; // texture tmu
int tmu_dt; // detail tmu
int tmu_lm; // lightmap tmu
qboolean details_enabled; // current texture has details
int lm; // current lightmap texture
qboolean skiptexture;
gl_texture_t *glt; // details scale
} mtst;
/*
===================
R_GenerateVBO
Allocate memory for arrays, fill it with vertex attribs and upload to GPU
===================
*/
void R_GenerateVBO( void )
{
int numtextures = WORLDMODEL->numtextures;
int numlightmaps = gl_lms.current_lightmap_texture;
int k, len = 0;
vboarray_t *vbo;
uint maxindex = 0;
R_ClearVBO();
// we do not want to write vbo code that does not use multitexture
if( !GL_Support( GL_ARB_VERTEX_BUFFER_OBJECT_EXT ) || !GL_Support( GL_ARB_MULTITEXTURE ) || glConfig.max_texture_units < 2 )
{
gEngfuncs.Cvar_FullSet( "gl_vbo", "0", FCVAR_READ_ONLY );
return;
}
// save in config if enabled manually
if( CVAR_TO_BOOL( r_vbo ) )
r_vbo->flags |= FCVAR_ARCHIVE;
vbos.mempool = Mem_AllocPool("Render VBO Zone");
vbos.minarraysplit_tex = INT_MAX;
vbos.maxarraysplit_tex = 0;
vbos.minarraysplit_lm = MAXLIGHTMAPS;
vbos.maxarraysplit_lm = 0;
vbos.minlightmap = MAX_LIGHTMAPS;
vbos.maxlightmap = 0;
vbos.mintexture = INT_MAX;
vbos.maxtexture = 0;
vbos.textures = Mem_Calloc( vbos.mempool, numtextures * numlightmaps * sizeof( vbotexture_t ) );
vbos.surfdata = Mem_Calloc( vbos.mempool, WORLDMODEL->numsurfaces * sizeof( vbosurfdata_t ) );
vbos.arraylist = vbo = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) );
vbos.decaldata = Mem_Calloc( vbos.mempool, sizeof( vbodecaldata_t ) );
vbos.decaldata->lm = Mem_Calloc( vbos.mempool, sizeof( msurface_t* ) * numlightmaps );
// count array lengths
for( k = 0; k < numlightmaps; k++ )
{
int j;
for( j = 0; j < numtextures; j++ )
{
int i;
vbotexture_t *vbotex = &vbos.textures[k * numtextures + j];
for( i = 0; i < WORLDMODEL->numsurfaces; i++ )
{
msurface_t *surf = &WORLDMODEL->surfaces[i];
if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) )
continue;
if( surf->lightmaptexturenum != k )
continue;
if( R_TextureAnimation( surf ) != WORLDMODEL->textures[j] )
continue;
if( vbo->array_len + surf->polys->numverts > USHRT_MAX )
{
// generate new array and new vbotexture node
vbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len );
gEngfuncs.Con_Printf( "R_GenerateVBOs: allocated array of %d verts, texture %d\n", vbo->array_len, j );
vbo->next = Mem_Calloc( vbos.mempool, sizeof( vboarray_t ) );
vbo = vbo->next;
vbotex->next = Mem_Calloc( vbos.mempool, sizeof( vbotexture_t ) );
vbotex = vbotex->next;
// never skip this textures and lightmaps
if( vbos.minarraysplit_tex > j )
vbos.minarraysplit_tex = j;
if( vbos.minarraysplit_lm > k )
vbos.minarraysplit_lm = k;
if( vbos.maxarraysplit_tex < j + 1 )
vbos.maxarraysplit_tex = j + 1;
if( vbos.maxarraysplit_lm < k + 1 )
vbos.maxarraysplit_lm = k + 1;
}
vbos.surfdata[i].vbotexture = vbotex;
vbos.surfdata[i].startindex = vbo->array_len;
vbos.surfdata[i].texturenum = j;
vbo->array_len += surf->polys->numverts;
vbotex->len += surf->polys->numverts;
vbotex->vboarray = vbo;
}
}
}
// allocate last array
vbo->array = Mem_Calloc( vbos.mempool, sizeof( vbovertex_t ) * vbo->array_len );
gEngfuncs.Con_Printf( "R_GenerateVBOs: allocated array of %d verts\n", vbo->array_len );
// switch to list begin
vbo = vbos.arraylist;
// fill and upload
for( k = 0; k < numlightmaps; k++ )
{
int j;
for( j = 0; j < numtextures; j++ )
{
int i;
vbotexture_t *vbotex = &vbos.textures[k * numtextures + j];
// preallocate index arrays
vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( unsigned short ) * 6 * vbotex->len );
vbotex->lightmaptexturenum = k;
if( maxindex < vbotex->len )
maxindex = vbotex->len;
for( i = 0; i < WORLDMODEL->numsurfaces; i++ )
{
msurface_t *surf = &WORLDMODEL->surfaces[i];
int l;
if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) )
continue;
if( surf->lightmaptexturenum != k )
continue;
if( R_TextureAnimation( surf ) != WORLDMODEL->textures[j] )
continue;
// switch to next array
if( len + surf->polys->numverts > USHRT_MAX )
{
// upload last generated array
pglGenBuffersARB( 1, &vbo->glindex );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB );
ASSERT( len == vbo->array_len );
vbo = vbo->next;
vbotex = vbotex->next;
vbotex->indexarray = Mem_Calloc( vbos.mempool, sizeof( unsigned short ) * 6 * vbotex->len );
vbotex->lightmaptexturenum = k;
// calculate limits for dlights
if( maxindex < vbotex->len )
maxindex = vbotex->len;
len = 0;
}
// fill vbovertex_t
for( l = 0; l < surf->polys->numverts; l++ )
{
float *v = surf->polys->verts[l];
VectorCopy( v, vbo->array[len + l].pos );
vbo->array[len + l].gl_tc[0] = v[3];
vbo->array[len + l].gl_tc[1] = v[4];
vbo->array[len + l].lm_tc[0] = v[5];
vbo->array[len + l].lm_tc[1] = v[6];
#ifdef NO_TEXTURE_MATRIX
if( WORLDMODEL->textures[j]->dt_texturenum )
{
gl_texture_t *glt = R_GetTexture( WORLDMODEL->textures[j]->gl_texturenum );
vbo->array[len + l].dt_tc[0] = v[3] * glt->xscale;
vbo->array[len + l].dt_tc[1] = v[4] * glt->yscale;
}
#endif
}
len += surf->polys->numverts;
}
}
}
ASSERT( len == vbo->array_len );
// upload last array
pglGenBuffersARB( 1, &vbo->glindex );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, vbo->array_len * sizeof( vbovertex_t ), vbo->array, GL_STATIC_DRAW_ARB );
// prepare decal array
pglGenBuffersARB( 1, &vbos.decaldata->decalvbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_CUT * MAX_RENDER_DECALS, vbos.decaldata->decalarray, GL_DYNAMIC_DRAW_ARB );
// preallocate dlight arrays
vbos.dlight_index = Mem_Calloc( vbos.mempool, maxindex * sizeof( unsigned short ) * 6 );
// select maximum possible length for dlight
vbos.dlight_tc = Mem_Calloc( vbos.mempool, sizeof( vec2_t ) * (int)(vbos.arraylist->next?USHRT_MAX + 1:vbos.arraylist->array_len + 1) );
if( CVAR_TO_BOOL(r_vbo_dlightmode) )
{
pglGenBuffersARB( 1, &vbos.dlight_vbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (int)(vbos.arraylist->next?USHRT_MAX + 1:vbos.arraylist->array_len + 1) , vbos.dlight_tc, GL_STREAM_DRAW_ARB );
pglGenBuffersARB( 1, &vbos.decal_dlight_vbo );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), vbos.decal_dlight, GL_STREAM_DRAW_ARB );
}
// reset state
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
mtst.tmu_gl = XASH_TEXTURE0;
}
/*
==============
R_AddDecalVBO
generate decal mesh and put it to array
==============
*/
void R_AddDecalVBO( decal_t *pdecal, msurface_t *surf )
{
int numVerts, i;
float *v;
int decalindex = pdecal - &gDecalPool[0];
if( !vbos.decaldata )
return;
v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts );
if( numVerts > DECAL_VERTS_CUT )
{
// use client arrays
vbos.decaldata->decals[decalindex].numVerts = -1;
return;
}
for( i = 0; i < numVerts; i++ )
memcpy( &vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i], v + i * VERTEXSIZE, VERTEXSIZE * 4 );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );
pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, decalindex * sizeof( vbovertex_t ) * DECAL_VERTS_CUT, sizeof( vbovertex_t ) * numVerts, &vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT] );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
vbos.decaldata->decals[decalindex].numVerts = numVerts;
}
/*
=============
R_ClearVBO
free all vbo data
=============
*/
void R_ClearVBO( void )
{
vboarray_t *vbo;
for( vbo = vbos.arraylist; vbo; vbo = vbo->next )
pglDeleteBuffersARB( 1, &vbo->glindex );
vbos.arraylist = NULL;
if( vbos.decaldata )
pglDeleteBuffersARB( 1, &vbos.decaldata->decalvbo );
if( vbos.dlight_vbo )
pglDeleteBuffersARB( 1, &vbos.dlight_vbo );
if( vbos.decal_dlight_vbo )
pglDeleteBuffersARB( 1, &vbos.decal_dlight_vbo );
vbos.decal_dlight_vbo = vbos.dlight_vbo = 0;
vbos.decaldata = NULL;
Mem_FreePool( &vbos.mempool );
}
/*
===================
R_DisableDetail
disable detail tmu
===================
*/
static void R_DisableDetail( void )
{
if( mtst.details_enabled && mtst.tmu_dt != -1 )
{
GL_SelectTexture( mtst.tmu_dt );
pglDisableClientState( GL_TEXTURE_COORD_ARRAY );
pglDisable( GL_TEXTURE_2D );
pglLoadIdentity();
}
}
/*
===================
R_EnableDetail
enable detail tmu if availiable
===================
*/
static void R_EnableDetail( void )
{
if( mtst.details_enabled && mtst.tmu_dt != -1 )
{
GL_SelectTexture( mtst.tmu_dt );
pglEnableClientState( GL_TEXTURE_COORD_ARRAY );
pglEnable( GL_TEXTURE_2D );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );
pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE );
pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );
pglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 );
// use transform matrix for details (undone)
#ifndef NO_TEXTURE_MATRIX
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
pglMatrixMode( GL_TEXTURE );
pglLoadIdentity();
pglScalef( mtst.glt->xscale, mtst.glt->yscale, 1 );
#else
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, dt_tc ) );
#endif
}
}
/*
==============
R_SetLightmap
enable lightmap on current tmu
==============
*/
static void R_SetLightmap( void )
{
if( mtst.skiptexture )
return;
/*if( gl_overbright->integer )
{
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_ARB );
pglTexEnvi( GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE );
pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS_ARB );
pglTexEnvi( GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE );
pglTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE_ARB, 2 );
}
else*/
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );
}
/*
==============
R_SetDecalMode
When drawing decal, disable or restore bump and details
set tmu to lightmap when enabled
==============
*/
static void R_SetDecalMode( qboolean enable )
{
// order is important to correctly rearrange TMUs
if( enable )
{
// disable detail texture if enabled
R_DisableDetail();
}
else
{
R_EnableDetail();
}
}
/*
==============
R_SetupVBOTexture
setup multitexture mode before drawing VBOs
if tex is NULL, load texture by number
==============
*/
static texture_t *R_SetupVBOTexture( texture_t *tex, int number )
{
if( mtst.skiptexture )
return tex;
if( !tex )
tex = R_TextureAnim( WORLDMODEL->textures[number] );
if( CVAR_TO_BOOL( r_detailtextures ) && tex->dt_texturenum && mtst.tmu_dt != -1 )
{
mtst.details_enabled = true;
GL_Bind( mtst.tmu_dt, tex->dt_texturenum );
mtst.glt = R_GetTexture( tex->gl_texturenum );
R_EnableDetail();
}
else R_DisableDetail();
GL_Bind( mtst.tmu_gl, CVAR_TO_BOOL( r_lightmap )?tr.whiteTexture:tex->gl_texturenum );
return tex;
}
/*
===================
R_AdditionalPasses
draw details when not enough tmus
===================
*/
static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, texture_t *tex, qboolean resetvbo )
{
// draw details in additional pass
if( r_detailtextures->value && mtst.tmu_dt == -1 && tex->dt_texturenum )
{
gl_texture_t *glt = R_GetTexture( tex->gl_texturenum );
GL_SelectTexture( XASH_TEXTURE1 );
pglDisable( GL_TEXTURE_2D );
// setup detail
GL_Bind( XASH_TEXTURE0, tex->dt_texturenum );
pglEnable( GL_BLEND );
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL );
// when drawing dlights, we need to bind array and unbind it again
if( resetvbo )
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) );
// apply scale
pglMatrixMode( GL_TEXTURE );
pglLoadIdentity();
pglScalef( glt->xscale, glt->yscale, 1 );
// draw
#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes
if( pglDrawRangeElements )
pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, indexlen, GL_UNSIGNED_SHORT, indexarray );
else
#endif
pglDrawElements( GL_TRIANGLES, indexlen, GL_UNSIGNED_SHORT, indexarray );
// restore state
pglLoadIdentity();
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglDisable( GL_BLEND );
GL_Bind( XASH_TEXTURE1, mtst.lm );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) );
GL_SelectTexture( XASH_TEXTURE1 );
pglEnable( GL_TEXTURE_2D );
if( resetvbo )
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
}
/*
=====================
R_DrawLightmappedVBO
Draw array for given vbotexture_t. build and draw dynamic lightmaps if present
=====================
*/
static void R_DrawLightmappedVBO( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap, qboolean skiplighting )
{
#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes
if( pglDrawRangeElements )
pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray );
else
#endif
pglDrawElements( GL_TRIANGLES, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray );
R_AdditionalPasses( vbo, vbotex->curindex, vbotex->indexarray, texture, false );
// draw debug lines
if( CVAR_TO_BOOL(gl_wireframe) && !skiplighting )
{
R_SetDecalMode( true );
pglDisable( GL_TEXTURE_2D );
GL_SelectTexture( XASH_TEXTURE0 );
pglDisable( GL_TEXTURE_2D );
pglDisable( GL_DEPTH_TEST );
#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes
if( pglDrawRangeElements )
pglDrawRangeElements( GL_LINES, 0, vbo->array_len, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray );
else
#endif
pglDrawElements( GL_LINES, vbotex->curindex, GL_UNSIGNED_SHORT, vbotex->indexarray );
pglEnable( GL_DEPTH_TEST );
pglEnable( GL_TEXTURE_2D );
GL_SelectTexture( XASH_TEXTURE1 );
pglEnable( GL_TEXTURE_2D );
R_SetDecalMode( false );
}
//Msg( "%d %d %d\n", vbo->array_len, vbotex->len, lightmap );
if( skiplighting )
{
vbotex->curindex = 0;
vbotex->dlightchain = NULL;
return;
}
// draw dlights and dlighted decals
if( vbotex->dlightchain )
{
unsigned short *dlightarray = vbos.dlight_index; // preallocated array
unsigned int dlightindex = 0;
msurface_t *surf, *newsurf;
int decalcount = 0;
GL_Bind( mtst.tmu_lm, tr.dlightTexture );
// replace lightmap texcoord array by dlight array
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );
if( vbos.dlight_vbo )
pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 );
else
pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc );
// clear the block
LM_InitBlock();
// accumulate indexes for every dlighted surface until dlight block full
for( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain )
{
int smax, tmax;
byte *base;
uint indexbase = vbos.surfdata[((char*)surf - (char*)WORLDMODEL->surfaces) / sizeof( *surf )].startindex;
uint index;
mextrasurf_t *info; // this stores current dlight offset
decal_t *pdecal;
int sample_size;
info = surf->info;
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
smax = ( info->lightextents[0] / sample_size ) + 1;
tmax = ( info->lightextents[1] / sample_size ) + 1;
// find space for this surface and get offsets
if( LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t ))
{
base = gl_lms.lightmap_buffer;
base += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4;
R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );
}
else
{
// out of free block space. Draw all generated index array and clear it
// upload already generated block
LM_UploadDynamicBlock();
#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes
if( pglDrawRangeElements )
pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, dlightindex, GL_UNSIGNED_SHORT, dlightarray );
else
#endif
pglDrawElements( GL_TRIANGLES, dlightindex, GL_UNSIGNED_SHORT, dlightarray );
// draw decals that lighted with this lightmap
if( decalcount )
{
msurface_t *decalsurf;
int decali = 0;
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
pglEnable( GL_POLYGON_OFFSET_FILL );
if( RI.currententity->curstate.rendermode == kRenderTransAlpha )
pglDisable( GL_ALPHA_TEST );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );
R_SetDecalMode( true );
if( vbos.decal_dlight_vbo )
{
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) );
GL_SelectTexture( mtst.tmu_gl );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, pos ) );
}
else
{
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc );
GL_SelectTexture( mtst.tmu_gl );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos);
}
for( decalsurf = newsurf; ( decali < decalcount ) && ( decalsurf != surf ); decalsurf = decalsurf->info->lightmapchain )
{
for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext )
{
gl_texture_t *glt;
if( !pdecal->texture )
continue;
glt = R_GetTexture( pdecal->texture );
GL_Bind( mtst.tmu_gl, pdecal->texture );
// normal HL decal with alpha-channel
if( glt->flags & TF_HAS_ALPHA )
{
// draw transparent decals with GL_MODULATE
if( glt->fogParams[3] > 230 )
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
else
{
// color decal like detail texture. Base color is 127 127 127
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
}
pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] );
decali++;
}
newsurf = surf;
}
// restore states pointers for next dynamic lightmap
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglDepthMask( GL_TRUE );
pglDisable( GL_BLEND );
pglDisable( GL_POLYGON_OFFSET_FILL );
if( RI.currententity->curstate.rendermode == kRenderTransAlpha )
pglEnable( GL_ALPHA_TEST );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );
R_SetDecalMode( false );
GL_SelectTexture( mtst.tmu_lm );
if( vbos.dlight_vbo )
pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, 0 );
else
pglTexCoordPointer( 2, GL_FLOAT, sizeof( float ) * 2, vbos.dlight_tc );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );
R_SetupVBOTexture( texture, 0 );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
decalcount = 0;
}
// clear the block
LM_InitBlock();
dlightindex = 0;
// try upload the block now
if( !LM_AllocBlock( smax, tmax, &info->dlight_s, &info->dlight_t ))
gEngfuncs.Host_Error( "AllocBlock: full\n" );
base = gl_lms.lightmap_buffer;
base += ( info->dlight_t * BLOCK_SIZE + info->dlight_s ) * 4;
R_BuildLightMap( surf, base, BLOCK_SIZE * 4, true );
}
// build index and texcoords arrays
vbos.dlight_tc[indexbase][0] = surf->polys->verts[0][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );
vbos.dlight_tc[indexbase][1] = surf->polys->verts[0][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );
vbos.dlight_tc[indexbase + 1][0] = surf->polys->verts[1][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );
vbos.dlight_tc[indexbase + 1][1] = surf->polys->verts[1][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );
for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ )
{
dlightarray[dlightindex++] = indexbase;
dlightarray[dlightindex++] = index - 1;
dlightarray[dlightindex++] = index;
vbos.dlight_tc[index][0] = surf->polys->verts[index - indexbase][5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );
vbos.dlight_tc[index][1] = surf->polys->verts[index - indexbase][6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );
}
if( vbos.dlight_vbo )
{
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo );
pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * indexbase, sizeof( vec2_t )* surf->polys->numverts, vbos.dlight_tc + indexbase );
}
// if surface has decals, build decal array
for( pdecal = surf->pdecals; pdecal; pdecal = pdecal->pnext )
{
int decalindex = pdecal - &gDecalPool[0];
int numVerts = vbos.decaldata->decals[decalindex].numVerts;
int i;
if( numVerts == -1 )
{
// build decal array
float *v = R_DecalSetupVerts( pdecal, surf, pdecal->texture, &numVerts );
for( i = 0; i < numVerts; i++, v += VERTEXSIZE )
{
VectorCopy( v, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos );
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = v[3];
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = v[4];
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = v[5] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = v[6] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );
}
}
else
{
// copy from vbo
for( i = 0; i < numVerts; i++ )
{
VectorCopy( vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].pos, vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].pos );
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[0];
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].gl_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].gl_tc[1];
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[0] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[0] - ( surf->light_s - info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE );
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = vbos.decaldata->decalarray[decalindex * DECAL_VERTS_CUT + i].lm_tc[1] - ( surf->light_t - info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE );
}
}
if( vbos.dlight_vbo )
{
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );
pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * decalcount * DECAL_VERTS_MAX, sizeof( vbovertex_t )* numVerts, vbos.decal_dlight + decalcount * DECAL_VERTS_MAX );
}
vbos.decal_numverts[decalcount] = numVerts;
decalcount++;
}
//info->dlight_s = info->dlight_t = 0;
}
if( dlightindex )
{
// update block
LM_UploadDynamicBlock();
// draw remaining array
#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes
if( pglDrawRangeElements )
pglDrawRangeElements( GL_TRIANGLES, 0, vbo->array_len, dlightindex, GL_UNSIGNED_SHORT, dlightarray );
else
#endif
pglDrawElements( GL_TRIANGLES, dlightindex, GL_UNSIGNED_SHORT, dlightarray );
R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true );
// draw remaining decals
if( decalcount )
{
msurface_t *decalsurf;
int decali = 0;
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
pglEnable( GL_POLYGON_OFFSET_FILL );
if( RI.currententity->curstate.rendermode == kRenderTransAlpha )
pglDisable( GL_ALPHA_TEST );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo );
R_SetDecalMode( true );
if( vbos.decal_dlight_vbo )
{
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, lm_tc ) );
GL_SelectTexture( mtst.tmu_gl );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, gl_tc ) );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof( vbovertex_t, pos ) );
}
else
{
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].lm_tc );
GL_SelectTexture( mtst.tmu_gl );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].gl_tc );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), &vbos.decal_dlight[0].pos);
}
for( decalsurf = newsurf; decali < decalcount && decalsurf; decalsurf = decalsurf->info->lightmapchain )
{
decal_t *pdecal;
for( pdecal = decalsurf->pdecals; pdecal; pdecal = pdecal->pnext )
{
gl_texture_t *glt;
if( !pdecal->texture )
continue;
glt = R_GetTexture( pdecal->texture );
GL_Bind( mtst.tmu_gl, pdecal->texture );
// normal HL decal with alpha-channel
if( glt->flags & TF_HAS_ALPHA )
{
// draw transparent decals with GL_MODULATE
if( glt->fogParams[3] > 230 )
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
else
{
// color decal like detail texture. Base color is 127 127 127
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
}
pglDrawArrays( GL_TRIANGLE_FAN, decali * DECAL_VERTS_MAX, vbos.decal_numverts[decali] );
decali++;
}
}
// reset states
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglDepthMask( GL_TRUE );
pglDisable( GL_BLEND );
pglDisable( GL_POLYGON_OFFSET_FILL );
if( RI.currententity->curstate.rendermode == kRenderTransAlpha )
pglEnable( GL_ALPHA_TEST );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
R_SetDecalMode( false );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );
R_SetupVBOTexture( texture, 0 );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
}
}
// restore static lightmap
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
GL_Bind( mtst.tmu_lm, tr.lightmapTextures[lightmap] );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );
// prepare to next frame
vbotex->dlightchain = NULL;
}
// prepare to next frame
vbotex->curindex = 0;
}
/*
=====================
R_DrawVBO
Draw generated index arrays
=====================
*/
void R_DrawVBO( qboolean drawlightmap, qboolean drawtextures )
{
int numtextures = WORLDMODEL->numtextures;
int numlightmaps = gl_lms.current_lightmap_texture;
int k;
vboarray_t *vbo = vbos.arraylist;
if( !CVAR_TO_BOOL( r_vbo ) )
return;
// bind array
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglEnableClientState( GL_VERTEX_ARRAY );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );
// setup multitexture
if( drawtextures )
{
GL_SelectTexture( mtst.tmu_gl = XASH_TEXTURE0 );
pglEnable( GL_TEXTURE_2D );
pglEnableClientState( GL_TEXTURE_COORD_ARRAY );
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
}
if( drawlightmap )
{
// set lightmap texenv
GL_SelectTexture( mtst.tmu_lm = XASH_TEXTURE1 );
pglEnable( GL_TEXTURE_2D );
pglEnableClientState( GL_TEXTURE_COORD_ARRAY );
R_SetLightmap();
}
mtst.skiptexture = !drawtextures;
mtst.tmu_dt = glConfig.max_texture_units > 2? XASH_TEXTURE2:-1;
// setup limits
if( vbos.minlightmap > vbos.minarraysplit_lm )
vbos.minlightmap = vbos.minarraysplit_lm;
if( vbos.maxlightmap < vbos.maxarraysplit_lm )
vbos.maxlightmap = vbos.maxarraysplit_lm;
if( vbos.maxlightmap > numlightmaps )
vbos.maxlightmap = numlightmaps;
if( vbos.mintexture > vbos.minarraysplit_tex )
vbos.mintexture = vbos.minarraysplit_tex;
if( vbos.maxtexture < vbos.maxarraysplit_tex )
vbos.maxtexture = vbos.maxarraysplit_tex;
if( vbos.maxtexture > numtextures )
vbos.maxtexture = numtextures;
for( k = vbos.minlightmap; k < vbos.maxlightmap; k++ )
{
int j;
msurface_t *lightmapchain;
if( drawlightmap )
{
GL_Bind( mtst.tmu_lm, mtst.lm = tr.lightmapTextures[k] );
}
for( j = vbos.mintexture; j < vbos.maxtexture; j++ )
{
vbotexture_t *vbotex = &vbos.textures[k * numtextures + j];
texture_t *tex = NULL;
if( !vbotex->vboarray )
continue;
// ASSERT( vbotex->vboarray == vbo );
if( vbotex->vboarray != vbo )
continue;
if( vbotex->curindex || vbotex->dlightchain )
{
// draw textures static lightmap first
if( drawtextures )
tex = R_SetupVBOTexture( NULL, j );
R_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap );
}
// if we need to switch to next array (only if map has >65536 vertices)
while( vbotex->next )
{
vbotex = vbotex->next;
vbo = vbo->next;
// bind new vertex and index arrays
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );
// update texcoord pointers
if( drawtextures )
{
tex = R_SetupVBOTexture( tex, 0 );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
}
if( drawlightmap )
{
GL_Bind( mtst.tmu_lm, tr.lightmapTextures[k] );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );
}
// draw new array
if( (vbotex->curindex || vbotex->dlightchain) )
R_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap );
}
}
if( drawtextures && drawlightmap && vbos.decaldata->lm[k] )
{
// prepare for decal draw
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
pglEnable( GL_POLYGON_OFFSET_FILL );
if( RI.currententity->curstate.rendermode == kRenderTransAlpha )
pglDisable( GL_ALPHA_TEST );
R_SetDecalMode( true );
// Set pointers to vbodecaldata->decalvbo
if( drawlightmap )
{
GL_SelectTexture( mtst.tmu_lm );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );
GL_SelectTexture( mtst.tmu_gl );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) );
}
// all surfaces having decals and this lightmap
for( lightmapchain = vbos.decaldata->lm[k]; lightmapchain; lightmapchain = lightmapchain->info->lightmapchain )
{
decal_t *pdecal;
// all decals of surface
for( pdecal = lightmapchain->pdecals; pdecal; pdecal = pdecal->pnext )
{
gl_texture_t *glt;
int decalindex = pdecal - &gDecalPool[0];
if( !pdecal->texture )
continue;
glt = R_GetTexture( pdecal->texture );
GL_Bind( mtst.tmu_gl, pdecal->texture );
// normal HL decal with alpha-channel
if( glt->flags & TF_HAS_ALPHA )
{
// draw transparent decals with GL_MODULATE
if( glt->fogParams[3] > 230 )
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
else pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
else
{
// color decal like detail texture. Base color is 127 127 127
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
}
if( vbos.decaldata->decals[decalindex].numVerts == -1 )
{
int numVerts;
float *v;
v = R_DecalSetupVerts( pdecal, lightmapchain, pdecal->texture, &numVerts );
// to many verts to keep in sparse array, so build it now
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
pglVertexPointer( 3, GL_FLOAT, VERTEXSIZE * 4, v );
pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 3 );
if( drawlightmap )
{
GL_SelectTexture( mtst.tmu_lm );
pglTexCoordPointer( 2, GL_FLOAT, VERTEXSIZE * 4, v + 5 );
}
pglDrawArrays( GL_TRIANGLE_FAN, 0, numVerts );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );
GL_SelectTexture( mtst.tmu_gl );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, pos ) );
}
else // just draw VBO
pglDrawArrays( GL_TRIANGLE_FAN, decalindex * DECAL_VERTS_CUT, vbos.decaldata->decals[decalindex].numVerts );
}
}
// prepare for next frame
vbos.decaldata->lm[k] = NULL;
// prepare for next texture
pglDepthMask( GL_TRUE );
pglDisable( GL_BLEND );
pglDisable( GL_POLYGON_OFFSET_FILL );
// restore vbo
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex );
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) );
// restore bump if needed
R_SetDecalMode( false );
// restore texture
GL_SelectTexture( mtst.tmu_gl );
pglEnableClientState( GL_TEXTURE_COORD_ARRAY );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) );
// restore lightmap
GL_SelectTexture( mtst.tmu_lm );
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, lm_tc ) );
if( RI.currententity->curstate.rendermode == kRenderTransAlpha )
pglEnable( GL_ALPHA_TEST );
}
if( !drawtextures || !drawlightmap )
vbos.decaldata->lm[k] = NULL;
}
// ASSERT( !vbo->next );
// restore states
R_DisableDetail();
if( drawlightmap )
{
// reset states
GL_SelectTexture( XASH_TEXTURE1 );
pglDisableClientState( GL_TEXTURE_COORD_ARRAY );
pglDisable( GL_TEXTURE_2D );
if( drawtextures )
{
GL_SelectTexture( XASH_TEXTURE0 );
pglEnable( GL_TEXTURE_2D );
}
}
if( drawtextures )
pglDisableClientState( GL_TEXTURE_COORD_ARRAY );
mtst.details_enabled = false;
vbos.minlightmap = MAX_LIGHTMAPS;
vbos.maxlightmap = 0;
vbos.mintexture = INT_MAX;
vbos.maxtexture = 0;
pglDisableClientState( GL_VERTEX_ARRAY );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
}
/*
================
R_CheckLightMap
update surface's lightmap if needed and return true if it is dynamic
================
*/
static qboolean R_CheckLightMap( msurface_t *fa )
{
int maps;
qboolean is_dynamic = false;
// check for lightmap modification
for( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ )
{
if( tr.lightstylevalue[fa->styles[maps]] != fa->cached_light[maps] )
{
is_dynamic = true;
break;
}
}
// already up to date
if( !is_dynamic && ( fa->dlightframe != tr.framecount || maps == MAXLIGHTMAPS ) )
return false;
// build lightmap
if(( fa->styles[maps] >= 32 || fa->styles[maps] == 0 ) && ( fa->dlightframe != tr.framecount ))
{
byte temp[132*132*4];
int smax, tmax;
int sample_size;
mextrasurf_t *info;
info = fa->info;
sample_size = gEngfuncs.Mod_SampleSizeForFace( fa );
smax = ( info->lightextents[0] / sample_size ) + 1;
tmax = ( info->lightextents[1] / sample_size ) + 1;
if( smax < 132 && tmax < 132 )
{
R_BuildLightMap( fa, temp, smax * 4, true );
}
else
{
smax = Q_min( smax, 132 );
tmax = Q_min( tmax, 132 );
//Host_MapDesignError( "R_RenderBrushPoly: bad surface extents: %d %d", fa->extents[0], fa->extents[1] );
memset( temp, 255, sizeof( temp ) );
}
R_SetCacheState( fa );
#ifdef XASH_WES
GL_Bind( XASH_TEXTURE1, tr.lightmapTextures[fa->lightmaptexturenum] );
pglTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE );
#else
GL_Bind( XASH_TEXTURE0, tr.lightmapTextures[fa->lightmaptexturenum] );
#endif
pglTexSubImage2D( GL_TEXTURE_2D, 0, fa->light_s, fa->light_t, smax, tmax,
GL_RGBA, GL_UNSIGNED_BYTE, temp );
#ifdef XASH_WES
GL_SelectTexture( XASH_TEXTURE0 );
#endif
}
// add to dynamic chain
else
return true;
// updated
return false;
}
qboolean R_AddSurfToVBO( msurface_t *surf, qboolean buildlightmap )
{
if( CVAR_TO_BOOL(r_vbo) && vbos.surfdata[surf - WORLDMODEL->surfaces].vbotexture )
{
// find vbotexture_t assotiated with this surface
int idx = surf - WORLDMODEL->surfaces;
vbotexture_t *vbotex = vbos.surfdata[idx].vbotexture;
int texturenum = vbos.surfdata[idx].texturenum;
if( !surf->polys )
return true;
if( vbos.maxlightmap < surf->lightmaptexturenum + 1 )
vbos.maxlightmap = surf->lightmaptexturenum + 1;
if( vbos.minlightmap > surf->lightmaptexturenum )
vbos.minlightmap = surf->lightmaptexturenum;
if( vbos.maxtexture < texturenum + 1 )
vbos.maxtexture = texturenum + 1;
if( vbos.mintexture > texturenum )
vbos.mintexture = texturenum;
buildlightmap &= !CVAR_TO_BOOL( r_fullbright ) && !!WORLDMODEL->lightdata;
if( buildlightmap && R_CheckLightMap( surf ) )
{
// every vbotex has own lightmap chain (as we sorted if by textures to use multitexture)
surf->info->lightmapchain = vbotex->dlightchain;
vbotex->dlightchain = surf;
}
else
{
uint indexbase = vbos.surfdata[idx].startindex;
uint index;
// GL_TRIANGLE_FAN: 0 1 2 0 2 3 0 3 4 ...
for( index = indexbase + 2; index < indexbase + surf->polys->numverts; index++ )
{
vbotex->indexarray[vbotex->curindex++] = indexbase;
vbotex->indexarray[vbotex->curindex++] = index - 1;
vbotex->indexarray[vbotex->curindex++] = index;
}
// if surface has decals, add it to decal lightmapchain
if( surf->pdecals )
{
surf->info->lightmapchain = vbos.decaldata->lm[vbotex->lightmaptexturenum];
vbos.decaldata->lm[vbotex->lightmaptexturenum] = surf;
}
}
return true;
}
return false;
}
/*
=============================================================
WORLD MODEL
=============================================================
*/
/*
================
R_RecursiveWorldNode
================
*/
void R_RecursiveWorldNode( mnode_t *node, uint clipflags )
{
int i, clipped;
msurface_t *surf, **mark;
mleaf_t *pleaf;
int c, side;
float dot;
loc0:
if( node->contents == CONTENTS_SOLID )
return; // hit a solid leaf
if( node->visframe != tr.visframecount )
return;
if( clipflags && !CVAR_TO_BOOL( r_nocull ))
{
for( i = 0; i < 6; i++ )
{
const mplane_t *p = &RI.frustum.planes[i];
if( !FBitSet( clipflags, BIT( i )))
continue;
clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p );
if( clipped == 2 ) return;
if( clipped == 1 ) ClearBits( clipflags, BIT( i ));
}
}
// if a leaf node, draw stuff
if( node->contents < 0 )
{
pleaf = (mleaf_t *)node;
mark = pleaf->firstmarksurface;
c = pleaf->nummarksurfaces;
if( c )
{
do
{
(*mark)->visframe = tr.framecount;
mark++;
} while( --c );
}
// deal with model fragments in this leaf
if( pleaf->efrags )
gEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount );
r_stats.c_world_leafs++;
return;
}
// node is just a decision point, so go down the apropriate sides
// find which side of the node we are on
dot = PlaneDiff( tr.modelorg, node->plane );
side = (dot >= 0.0f) ? 0 : 1;
// recurse down the children, front side first
R_RecursiveWorldNode( node->children[side], clipflags );
// draw stuff
for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ )
{
if( R_CullSurface( surf, &RI.frustum, clipflags ))
continue;
if( surf->flags & SURF_DRAWSKY )
{
// make sky chain to right clip the skybox
surf->texturechain = skychain;
skychain = surf;
}
else if( !R_AddSurfToVBO( surf, true ) )
{
surf->texturechain = surf->texinfo->texture->texturechain;
surf->texinfo->texture->texturechain = surf;
}
}
// recurse down the back side
node = node->children[!side];
goto loc0;
}
/*
================
R_CullNodeTopView
cull node by user rectangle (simple scissor)
================
*/
qboolean R_CullNodeTopView( mnode_t *node )
{
vec2_t delta, size;
vec3_t center, half;
// build the node center and half-diagonal
VectorAverage( node->minmaxs, node->minmaxs + 3, center );
VectorSubtract( node->minmaxs + 3, center, half );
// cull against the screen frustum or the appropriate area's frustum.
Vector2Subtract( center, world_orthocenter, delta );
Vector2Add( half, world_orthohalf, size );
return ( fabs( delta[0] ) > size[0] ) || ( fabs( delta[1] ) > size[1] );
}
/*
================
R_DrawTopViewLeaf
================
*/
static void R_DrawTopViewLeaf( mleaf_t *pleaf, uint clipflags )
{
msurface_t **mark, *surf;
int i;
for( i = 0, mark = pleaf->firstmarksurface; i < pleaf->nummarksurfaces; i++, mark++ )
{
surf = *mark;
// don't process the same surface twice
if( surf->visframe == tr.framecount )
continue;
surf->visframe = tr.framecount;
if( R_CullSurface( surf, &RI.frustum, clipflags ))
continue;
if(!( surf->flags & SURF_DRAWSKY ))
{
surf->texturechain = surf->texinfo->texture->texturechain;
surf->texinfo->texture->texturechain = surf;
}
}
// deal with model fragments in this leaf
if( pleaf->efrags )
gEngfuncs.R_StoreEfrags( &pleaf->efrags, tr.realframecount );
r_stats.c_world_leafs++;
}
/*
================
R_DrawWorldTopView
================
*/
void R_DrawWorldTopView( mnode_t *node, uint clipflags )
{
int i, c, clipped;
msurface_t *surf;
do
{
if( node->contents == CONTENTS_SOLID )
return; // hit a solid leaf
if( node->visframe != tr.visframecount )
return;
if( clipflags && !r_nocull->value )
{
for( i = 0; i < 6; i++ )
{
const mplane_t *p = &RI.frustum.planes[i];
if( !FBitSet( clipflags, BIT( i )))
continue;
clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p );
if( clipped == 2 ) return;
if( clipped == 1 ) ClearBits( clipflags, BIT( i ));
}
}
// cull against the screen frustum or the appropriate area's frustum.
if( R_CullNodeTopView( node ))
return;
// if a leaf node, draw stuff
if( node->contents < 0 )
{
R_DrawTopViewLeaf( (mleaf_t *)node, clipflags );
return;
}
// draw stuff
for( c = node->numsurfaces, surf = WORLDMODEL->surfaces + node->firstsurface; c; c--, surf++ )
{
// don't process the same surface twice
if( surf->visframe == tr.framecount )
continue;
surf->visframe = tr.framecount;
if( R_CullSurface( surf, &RI.frustum, clipflags ))
continue;
if(!( surf->flags & SURF_DRAWSKY ))
{
surf->texturechain = surf->texinfo->texture->texturechain;
surf->texinfo->texture->texturechain = surf;
}
}
// recurse down both children, we don't care the order...
R_DrawWorldTopView( node->children[0], clipflags );
node = node->children[1];
} while( node );
}
/*
=============
R_DrawTriangleOutlines
=============
*/
void R_DrawTriangleOutlines( void )
{
int i, j;
msurface_t *surf;
glpoly_t *p;
float *v;
if( !gl_wireframe->value )
return;
pglDisable( GL_TEXTURE_2D );
pglDisable( GL_DEPTH_TEST );
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
// render static surfaces first
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
for( surf = gl_lms.lightmap_surfaces[i]; surf != NULL; surf = surf->info->lightmapchain )
{
p = surf->polys;
for( ; p != NULL; p = p->chain )
{
pglBegin( GL_POLYGON );
v = p->verts[0];
for( j = 0; j < p->numverts; j++, v += VERTEXSIZE )
pglVertex3fv( v );
pglEnd ();
}
}
}
// render surfaces with dynamic lightmaps
for( surf = gl_lms.dynamic_surfaces; surf != NULL; surf = surf->info->lightmapchain )
{
p = surf->polys;
for( ; p != NULL; p = p->chain )
{
pglBegin( GL_POLYGON );
v = p->verts[0];
for( j = 0; j < p->numverts; j++, v += VERTEXSIZE )
pglVertex3fv( v );
pglEnd ();
}
}
pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
pglEnable( GL_DEPTH_TEST );
pglEnable( GL_TEXTURE_2D );
}
/*
=============
R_DrawWorld
=============
*/
void R_DrawWorld( void )
{
double start, end;
// paranoia issues: when gl_renderer is "0" we need have something valid for currententity
// to prevent crashing until HeadShield drawing.
RI.currententity = gEngfuncs.GetEntityByIndex( 0 );
RI.currentmodel = RI.currententity->model;
if( !RI.drawWorld || RI.onlyClientDraw )
return;
VectorCopy( RI.cullorigin, tr.modelorg );
memset( gl_lms.lightmap_surfaces, 0, sizeof( gl_lms.lightmap_surfaces ));
memset( fullbright_surfaces, 0, sizeof( fullbright_surfaces ));
memset( detail_surfaces, 0, sizeof( detail_surfaces ));
gl_lms.dynamic_surfaces = NULL;
pglDisable( GL_ALPHA_TEST );
pglDisable( GL_BLEND );
tr.blend = 1.0f;
R_ClearSkyBox ();
start = gEngfuncs.pfnTime();
if( RI.drawOrtho )
R_DrawWorldTopView( WORLDMODEL->nodes, RI.frustum.clipFlags );
else R_RecursiveWorldNode( WORLDMODEL->nodes, RI.frustum.clipFlags );
end = gEngfuncs.pfnTime();
r_stats.t_world_node = end - start;
start = gEngfuncs.pfnTime();
R_DrawVBO( !CVAR_TO_BOOL(r_fullbright) && !!WORLDMODEL->lightdata, true );
R_DrawTextureChains();
if( !ENGINE_GET_PARM( PARM_DEV_OVERVIEW ))
{
DrawDecalsBatch();
GL_ResetFogColor();
R_BlendLightmaps();
R_RenderFullbrights();
R_RenderDetails();
if( skychain )
R_DrawSkyBox();
}
end = gEngfuncs.pfnTime();
r_stats.t_world_draw = end - start;
tr.num_draw_decals = 0;
skychain = NULL;
R_DrawTriangleOutlines ();
R_DrawWorldHull();
}
/*
===============
R_MarkLeaves
Mark the leaves and nodes that are in the PVS for the current leaf
===============
*/
void R_MarkLeaves( void )
{
qboolean novis = false;
qboolean force = false;
mleaf_t *leaf = NULL;
mnode_t *node;
vec3_t test;
int i;
if( !RI.drawWorld ) return;
if( FBitSet( r_novis->flags, FCVAR_CHANGED ) || tr.fResetVis )
{
// force recalc viewleaf
ClearBits( r_novis->flags, FCVAR_CHANGED );
tr.fResetVis = false;
RI.viewleaf = NULL;
}
VectorCopy( RI.pvsorigin, test );
if( RI.viewleaf != NULL )
{
// merge two leafs that can be a crossed-line contents
if( RI.viewleaf->contents == CONTENTS_EMPTY )
{
VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] - 16.0f );
leaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes );
}
else
{
VectorSet( test, RI.pvsorigin[0], RI.pvsorigin[1], RI.pvsorigin[2] + 16.0f );
leaf = gEngfuncs.Mod_PointInLeaf( test, WORLDMODEL->nodes );
}
if(( leaf->contents != CONTENTS_SOLID ) && ( RI.viewleaf != leaf ))
force = true;
}
if( RI.viewleaf == RI.oldviewleaf && RI.viewleaf != NULL && !force )
return;
// development aid to let you run around
// and see exactly where the pvs ends
if( r_lockpvs->value ) return;
RI.oldviewleaf = RI.viewleaf;
tr.visframecount++;
if( r_novis->value || RI.drawOrtho || !RI.viewleaf || !WORLDMODEL->visdata )
novis = true;
gEngfuncs.R_FatPVS( RI.pvsorigin, REFPVS_RADIUS, RI.visbytes, FBitSet( RI.params, RP_OLDVIEWLEAF ), novis );
if( force && !novis ) gEngfuncs.R_FatPVS( test, REFPVS_RADIUS, RI.visbytes, true, novis );
for( i = 0; i < WORLDMODEL->numleafs; i++ )
{
if( CHECKVISBIT( RI.visbytes, i ))
{
node = (mnode_t *)&WORLDMODEL->leafs[i+1];
do
{
if( node->visframe == tr.visframecount )
break;
node->visframe = tr.visframecount;
node = node->parent;
} while( node );
}
}
}
/*
========================
GL_CreateSurfaceLightmap
========================
*/
void GL_CreateSurfaceLightmap( msurface_t *surf, model_t *loadmodel )
{
int smax, tmax;
int sample_size;
mextrasurf_t *info = surf->info;
byte *base;
if( !loadmodel->lightdata )
return;
if( FBitSet( surf->flags, SURF_DRAWTILED ))
return;
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf );
smax = ( info->lightextents[0] / sample_size ) + 1;
tmax = ( info->lightextents[1] / sample_size ) + 1;
if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ))
{
LM_UploadBlock( false );
LM_InitBlock();
if( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ))
gEngfuncs.Host_Error( "AllocBlock: full\n" );
}
surf->lightmaptexturenum = gl_lms.current_lightmap_texture;
base = gl_lms.lightmap_buffer;
base += ( surf->light_t * BLOCK_SIZE + surf->light_s ) * 4;
R_SetCacheState( surf );
R_BuildLightMap( surf, base, BLOCK_SIZE * 4, false );
}
/*
==================
GL_RebuildLightmaps
Rebuilds the lightmap texture
when gamma is changed
==================
*/
void GL_RebuildLightmaps( void )
{
int i, j;
model_t *m;
if( !ENGINE_GET_PARM( PARM_CLIENT_ACTIVE ) )
return; // wait for worldmodel
ClearBits( vid_brightness->flags, FCVAR_CHANGED );
ClearBits( vid_gamma->flags, FCVAR_CHANGED );
// release old lightmaps
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
if( !tr.lightmapTextures[i] ) break;
GL_FreeTexture( tr.lightmapTextures[i] );
}
memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));
gl_lms.current_lightmap_texture = 0;
// setup all the lightstyles
CL_RunLightStyles();
LM_InitBlock();
for( i = 0; i < ENGINE_GET_PARM( PARM_NUMMODELS ); i++ )
{
if(( m = gEngfuncs.pfnGetModelByIndex( i + 1 )) == NULL )
continue;
if( m->name[0] == '*' || m->type != mod_brush )
continue;
for( j = 0; j < m->numsurfaces; j++ )
GL_CreateSurfaceLightmap( m->surfaces + j, m );
}
LM_UploadBlock( false );
if( gEngfuncs.drawFuncs->GL_BuildLightmaps )
{
// build lightmaps on the client-side
gEngfuncs.drawFuncs->GL_BuildLightmaps( );
}
}
/*
==================
GL_BuildLightmaps
Builds the lightmap texture
with all the surfaces from all brush models
==================
*/
void GL_BuildLightmaps( void )
{
int i, j;
model_t *m;
// release old lightmaps
for( i = 0; i < MAX_LIGHTMAPS; i++ )
{
if( !tr.lightmapTextures[i] ) break;
GL_FreeTexture( tr.lightmapTextures[i] );
}
memset( tr.lightmapTextures, 0, sizeof( tr.lightmapTextures ));
memset( &RI, 0, sizeof( RI ));
// update the lightmap blocksize
if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_LARGE_LIGHTMAPS ))
tr.block_size = BLOCK_SIZE_MAX;
else tr.block_size = BLOCK_SIZE_DEFAULT;
skychain = NULL;
tr.framecount = tr.visframecount = 1; // no dlight cache
gl_lms.current_lightmap_texture = 0;
tr.modelviewIdentity = false;
tr.realframecount = 1;
nColinElim = 0;
// setup the texture for dlights
R_InitDlightTexture();
// setup all the lightstyles
CL_RunLightStyles();
LM_InitBlock();
for( i = 0; i < ENGINE_GET_PARM( PARM_NUMMODELS ); i++ )
{
if(( m = gEngfuncs.pfnGetModelByIndex( i + 1 )) == NULL )
continue;
if( m->name[0] == '*' || m->type != mod_brush )
continue;
for( j = 0; j < m->numsurfaces; j++ )
{
// clearing all decal chains
m->surfaces[j].pdecals = NULL;
m->surfaces[j].visframe = 0;
GL_CreateSurfaceLightmap( m->surfaces + j, m );
if( m->surfaces[j].flags & SURF_DRAWTURB )
continue;
GL_BuildPolygonFromSurface( m, m->surfaces + j );
}
// clearing visframe
for( j = 0; j < m->numleafs; j++ )
m->leafs[j+1].visframe = 0;
for( j = 0; j < m->numnodes; j++ )
m->nodes[j].visframe = 0;
}
LM_UploadBlock( false );
if( gEngfuncs.drawFuncs->GL_BuildLightmaps )
{
// build lightmaps on the client-side
gEngfuncs.drawFuncs->GL_BuildLightmaps( );
}
// now gamma and brightness are valid
ClearBits( vid_brightness->flags, FCVAR_CHANGED );
ClearBits( vid_gamma->flags, FCVAR_CHANGED );
}
void GL_InitRandomTable( void )
{
int tu, tv;
for( tu = 0; tu < MOD_FRAMES; tu++ )
{
for( tv = 0; tv < MOD_FRAMES; tv++ )
{
rtable[tu][tv] = gEngfuncs.COM_RandomLong( 0, 0x7FFF );
}
}
gEngfuncs.COM_SetRandomSeed( 0 );
}