You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3908 lines
99 KiB
3908 lines
99 KiB
/* |
|
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 ); |
|
} |
|
|
|
void R_LightmapCoord( const vec3_t v, const msurface_t *surf, const float sample_size, vec2_t coords ) |
|
{ |
|
const mextrasurf_t *info = surf->info; |
|
float s, t; |
|
|
|
s = DotProduct( v, info->lmvecs[0] ) + info->lmvecs[0][3] - info->lightmapmins[0]; |
|
s += surf->light_s * sample_size; |
|
s += sample_size * 0.5f; |
|
s /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; |
|
|
|
t = DotProduct( v, info->lmvecs[1] ) + info->lmvecs[1][3] - info->lightmapmins[1]; |
|
t += surf->light_t * sample_size; |
|
t += sample_size * 0.5f; |
|
t /= BLOCK_SIZE * sample_size; //fa->texinfo->texture->width; |
|
|
|
Vector2Set( coords, s, t ); |
|
} |
|
|
|
static void R_TextureCoord( const vec3_t v, const msurface_t *surf, vec2_t coords ) |
|
{ |
|
const mtexinfo_t *info = surf->texinfo; |
|
float s, t; |
|
|
|
s = DotProduct( v, info->vecs[0] ); |
|
t = DotProduct( v, info->vecs[1] ); |
|
|
|
if( !FBitSet( surf->flags, SURF_DRAWTURB )) |
|
{ |
|
s = ( s + info->vecs[0][3] ) / info->texture->width; |
|
t = ( t + info->vecs[1][3] ) / info->texture->height; |
|
} |
|
|
|
Vector2Set( coords, s, t ); |
|
} |
|
|
|
static void R_GetEdgePosition( const model_t *mod, const msurface_t *fa, int i, vec3_t vec ) |
|
{ |
|
const int lindex = mod->surfedges[fa->firstedge + i]; |
|
const medge_t *pedges = mod->edges; |
|
|
|
if( lindex > 0 ) |
|
VectorCopy( mod->vertexes[pedges[lindex].v[0]].position, vec ); |
|
else |
|
VectorCopy( mod->vertexes[pedges[-lindex].v[1]].position, vec ); |
|
} |
|
|
|
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( model_t *loadmodel, msurface_t *warpface, int numverts, float *verts ) |
|
{ |
|
vec3_t front[SUBDIVIDE_SIZE], back[SUBDIVIDE_SIZE]; |
|
float dist[SUBDIVIDE_SIZE]; |
|
float m, frac, *v; |
|
int i, j, k, f, b; |
|
float sample_size; |
|
vec3_t mins, maxs; |
|
glpoly_t *poly; |
|
|
|
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( loadmodel, warpface, f, front[0] ); |
|
SubdividePolygon_r( loadmodel, 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] ); |
|
R_TextureCoord( verts, warpface, &poly->verts[i][3] ); |
|
|
|
// for speed reasons |
|
if( !FBitSet( warpface->flags, SURF_DRAWTURB )) |
|
{ |
|
// lightmap texture coordinates |
|
R_LightmapCoord( verts, warpface, sample_size, &poly->verts[i][5] ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
=============================== |
|
GL_SetupFogColorForSurfaces |
|
|
|
every render pass applies new fog layer, resulting in wrong fog color |
|
recalculate fog color for current pass count |
|
=============================== |
|
*/ |
|
|
|
void GL_SetupFogColorForSurfacesEx( int passes, float density ) |
|
{ |
|
vec3_t fogColor; |
|
float factor, div; |
|
|
|
if( !glState.isFogEnabled ) |
|
return; |
|
|
|
if(( passes < 2 ) || (RI.currententity && RI.currententity->curstate.rendermode == kRenderTransTexture )) |
|
{ |
|
pglFogfv( GL_FOG_COLOR, RI.fogColor ); |
|
return; |
|
} |
|
|
|
div = passes - 1; |
|
factor = passes; |
|
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 ); |
|
pglFogf( GL_FOG_DENSITY, RI.fogDensity * density ); |
|
} |
|
|
|
|
|
void GL_SetupFogColorForSurfaces( void ) |
|
{ |
|
GL_SetupFogColorForSurfacesEx( r_detailtextures.value ? 3 : 2, 1.0f ); |
|
} |
|
|
|
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( model_t *loadmodel, msurface_t *fa ) |
|
{ |
|
vec3_t verts[SUBDIVIDE_SIZE]; |
|
int i; |
|
|
|
// convert edges back to a normal polygon |
|
for( i = 0; i < fa->numedges; i++ ) |
|
R_GetEdgePosition( loadmodel, fa, i, verts[i] ); |
|
|
|
SetBits( fa->flags, SURF_DRAWTURB_QUADS ); // predict state |
|
|
|
// do subdivide |
|
SubdividePolygon_r( loadmodel, fa, fa->numedges, verts[0] ); |
|
} |
|
|
|
/* |
|
================ |
|
GL_BuildPolygonFromSurface |
|
================ |
|
*/ |
|
void GL_BuildPolygonFromSurface( model_t *mod, msurface_t *fa ) |
|
{ |
|
int i, lnumverts; |
|
float sample_size; |
|
texture_t *tex; |
|
gl_texture_t *glt; |
|
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 |
|
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++ ) |
|
{ |
|
R_GetEdgePosition( mod, fa, i, poly->verts[i] ); |
|
R_TextureCoord( poly->verts[i], fa, &poly->verts[i][3] ); |
|
R_LightmapCoord( poly->verts[i], fa, sample_size, &poly->verts[i][5] ); |
|
} |
|
|
|
// remove co-linear points - Ed |
|
if( !gl_keeptjunctions.value && !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)(gp_cl->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_TextureRandomTiling |
|
|
|
Returns the proper texture for a given surface without animation |
|
=============== |
|
*/ |
|
texture_t *R_TextureRandomTiling( msurface_t *s ) |
|
{ |
|
texture_t *base = s->texinfo->texture; |
|
int count, reletive; |
|
|
|
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; |
|
} |
|
|
|
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_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)(gp_cl->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) * dl->color.r ) / 256; |
|
bl[1] += ((int)((rad - dist) * 256) * dl->color.g ) / 256; |
|
bl[2] += ((int)((rad - dist) * 256) * 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_NOMIPMAP|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; |
|
int lightscale; |
|
|
|
sample_size = gEngfuncs.Mod_SampleSizeForFace( surf ); |
|
smax = ( info->lightextents[0] / sample_size ) + 1; |
|
tmax = ( info->lightextents[1] / sample_size ) + 1; |
|
size = smax * tmax; |
|
if( gl_overbright.value ) |
|
lightscale = 256; |
|
else lightscale = ( pow( 2.0f, 1.0f / v_lightgamma->value ) * 256 ) + 0.5; |
|
|
|
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] += lm->r * scale; |
|
bl[1] += lm->g * scale; |
|
bl[2] += 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++ ) |
|
{ |
|
int i; |
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
int t = bl[i] * lightscale >> 14; |
|
|
|
if( t > 1023 ) |
|
t = 1023; |
|
|
|
dest[i] = gEngfuncs.LightToTexGammaEx( t ) >> 2; |
|
} |
|
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 == CL_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.currentTexturesIndex[glState.activeTMU] ); |
|
|
|
flRate = fabs( flConveyorSpeed ) / (float)texture->srcWidth; |
|
flAngle = ( flConveyorSpeed >= 0 ) ? 180 : 0; |
|
|
|
SinCos( flAngle * ( M_PI_F / 180.0f ), &sy, &cy ); |
|
sOffset = gp_cl->time * cy * flRate; |
|
tOffset = gp_cl->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 (); |
|
} |
|
} |
|
|
|
static qboolean R_HasLightmap( void ) |
|
{ |
|
if( r_fullbright->value || !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( !r_lightmap->value ) |
|
pglEnable( GL_BLEND ); |
|
else pglDisable( GL_BLEND ); |
|
|
|
// lightmapped solid surfaces |
|
pglDepthMask( GL_FALSE ); |
|
pglDepthFunc( GL_EQUAL ); |
|
|
|
pglDisable( GL_ALPHA_TEST ); |
|
if( gl_overbright.value ) |
|
{ |
|
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); |
|
pglColor4f( 128.0f / 192.0f, 128.0f / 192.0f, 128.0f / 192.0f, 1.0f ); |
|
} |
|
else |
|
{ |
|
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( r_dynamic->value ) |
|
{ |
|
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( int passes ) |
|
{ |
|
gl_texture_t *glt; |
|
mextrasurf_t *es, *p; |
|
msurface_t *fa; |
|
int i; |
|
|
|
if( !draw_details ) |
|
return; |
|
|
|
GL_SetupFogColorForSurfacesEx( passes, passes == 2 ? 0.5f : 1.0f ); |
|
|
|
pglEnable( GL_BLEND ); |
|
pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); |
|
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); |
|
if(passes == 3) |
|
pglDepthFunc( GL_EQUAL ); |
|
else |
|
{ |
|
pglDepthFunc( GL_LEQUAL ); |
|
//pglDepthMask( GL_FALSE ); |
|
pglEnable( GL_POLYGON_OFFSET_FILL ); |
|
} |
|
|
|
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 ); |
|
pglDisable( GL_POLYGON_OFFSET_FILL ); |
|
|
|
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 ); |
|
|
|
if( FBitSet( fa->flags, SURF_DRAWTURB )) |
|
{ |
|
R_UploadRipples( t ); |
|
|
|
// warp texture, no lightmaps |
|
EmitWaterPolys( fa, (cull_type == CULL_BACKSIDE)); |
|
return; |
|
} |
|
else GL_Bind( XASH_TEXTURE0, t->gl_texturenum ); |
|
|
|
if( t->fb_texturenum ) |
|
{ |
|
fa->info->lumachain = fullbright_surfaces[t->fb_texturenum]; |
|
fullbright_surfaces[t->fb_texturenum] = fa->info; |
|
draw_fullbrights = true; |
|
} |
|
|
|
if( r_detailtextures.value ) |
|
{ |
|
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(( maps < MAXLIGHTMAPS ) && ( 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 = CL_GetEntityByIndex( 0 ); |
|
RI.currentmodel = RI.currententity->model; |
|
|
|
if( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX )) |
|
{ |
|
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( FBitSet( tr.world->flags, FWORLD_SKYSPHERE ) && !FBitSet( tr.world->flags, FWORLD_CUSTOM_SKYBOX )) |
|
{ |
|
pglEnable( GL_TEXTURE_2D ); |
|
if( skychain ) |
|
R_DrawClouds(); |
|
skychain = NULL; |
|
} |
|
|
|
R_DrawVBO( !r_fullbright->value && !!WORLDMODEL->lightdata, true ); |
|
|
|
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 ) && tr.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 = CL_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( tr.movevars->wateralpha >= 1.0f ) |
|
return; |
|
|
|
// restore worldmodel |
|
RI.currententity = CL_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, tr.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 |
|
R_UploadRipples( t ); |
|
|
|
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 = r_vbo.value; |
|
|
|
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 < gp_cl->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 ); |
|
if( e->curstate.rendermode == kRenderTransAdd ) |
|
{ |
|
R_AllowFog( false ); |
|
allow_vbo = false; |
|
} |
|
|
|
if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) |
|
allow_vbo = false; |
|
|
|
if( !allow_vbo ) |
|
GL_SetupFogColorForSurfaces (); |
|
|
|
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 && !gl_nosort.value ) |
|
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( allow_vbo? 2: 3 ); |
|
|
|
// 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; |
|
|
|
#ifndef UINT_INDEX |
|
typedef unsigned short vboindex_t; |
|
#define VBOINDEX_MAX USHRT_MAX |
|
#define GL_VBOINDEX_TYPE GL_UNSIGNED_SHORT |
|
#else |
|
typedef unsigned int vboindex_t; |
|
#define VBOINDEX_MAX UINT_MAX |
|
#define GL_VBOINDEX_TYPE GL_UNSIGNED_INT |
|
#endif |
|
|
|
// store indexes for each texture |
|
typedef struct vbotexture_s |
|
{ |
|
vboindex_t *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) |
|
vboindex_t *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; |
|
|
|
enum array_state_e |
|
{ |
|
VBO_ARRAY_NONE, |
|
VBO_ARRAY_STATIC, |
|
VBO_ARRAY_DECAL, |
|
VBO_ARRAY_DLIGHT, |
|
VBO_ARRAY_DECAL_DLIGHT |
|
}; |
|
|
|
enum texture_state_e |
|
{ |
|
VBO_TEXTURE_NONE, |
|
VBO_TEXTURE_MAIN, |
|
VBO_TEXTURE_DECAL |
|
}; |
|
|
|
enum lightmap_state_e |
|
{ |
|
VBO_LIGHTMAP_NONE, |
|
VBO_LIGHTMAP_STATIC, |
|
VBO_LIGHTMAP_DYNAMIC |
|
}; |
|
|
|
|
|
static struct arraystate_s |
|
{ |
|
enum array_state_e astate; |
|
enum texture_state_e tstate; |
|
enum lightmap_state_e lstate; |
|
int itexture; |
|
qboolean decal_mode; |
|
} vboarray; |
|
|
|
|
|
/* |
|
=================== |
|
R_GenerateVBO |
|
|
|
Allocate memory for arrays, fill it with vertex attribs and upload to GPU |
|
=================== |
|
*/ |
|
void R_GenerateVBO( void ) |
|
{ |
|
model_t *world = WORLDMODEL; |
|
msurface_t *surfaces = world->surfaces; |
|
int numsurfaces = world->numsurfaces; |
|
int numtextures = world->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( r_vbo.value ) |
|
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 < numsurfaces; i++ ) |
|
{ |
|
msurface_t *surf = &surfaces[i]; |
|
|
|
if( surf->lightmaptexturenum != k ) |
|
continue; |
|
|
|
if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) |
|
continue; |
|
|
|
if( R_TextureAnimation( surf ) != world->textures[j] ) |
|
continue; |
|
|
|
if( vbo->array_len + surf->polys->numverts > VBOINDEX_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( *vbotex->indexarray ) * 6 * vbotex->len ); |
|
vbotex->lightmaptexturenum = k; |
|
|
|
if( maxindex < vbotex->len ) |
|
maxindex = vbotex->len; |
|
|
|
for( i = 0; i < numsurfaces; i++ ) |
|
{ |
|
msurface_t *surf = &surfaces[i]; |
|
int l; |
|
|
|
if( surf->lightmaptexturenum != k ) |
|
continue; |
|
|
|
if( surf->flags & ( SURF_DRAWSKY | SURF_DRAWTURB | SURF_CONVEYOR | SURF_DRAWTURB_QUADS ) ) |
|
continue; |
|
|
|
if( R_TextureAnimation( surf ) != world->textures[j] ) |
|
continue; |
|
|
|
// switch to next array |
|
if( len + surf->polys->numverts > VBOINDEX_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( *vbotex->indexarray ) * 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( world->textures[j]->dt_texturenum ) |
|
{ |
|
gl_texture_t *glt = R_GetTexture( world->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( *vbos.dlight_index ) * 6 ); |
|
|
|
// select maximum possible length for dlight |
|
vbos.dlight_tc = Mem_Calloc( vbos.mempool, sizeof( vec2_t ) * (int)( vbos.arraylist->next ? VBOINDEX_MAX + 1 : vbos.arraylist->array_len + 1 )); |
|
|
|
if( r_vbo_dlightmode.value ) |
|
{ |
|
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 ? VBOINDEX_MAX + 1 : vbos.arraylist->array_len + 1 ), NULL, 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 ), NULL, 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 ); |
|
pglMatrixMode( GL_TEXTURE ); |
|
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 |
|
============== |
|
*/ |
|
static void R_SetDecalMode( qboolean enable ) |
|
{ |
|
if( vboarray.decal_mode == enable ) |
|
return; |
|
vboarray.decal_mode = 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( r_detailtextures.value && 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, r_lightmap->value ?tr.whiteTexture:tex->gl_texturenum ); |
|
if(number) |
|
vboarray.itexture = number; |
|
|
|
return tex; |
|
} |
|
|
|
|
|
void R_SetupVBOArrayStatic( vboarray_t *vbo, qboolean drawlightmap, qboolean drawtextures ) |
|
{ |
|
if( vboarray.astate != VBO_ARRAY_STATIC ) |
|
{ |
|
// bind array |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); |
|
// dlights use same vertex array |
|
if( vboarray.astate != VBO_ARRAY_DLIGHT ) |
|
{ |
|
|
|
pglEnableClientState( GL_VERTEX_ARRAY ); |
|
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t,pos) ); |
|
} |
|
|
|
|
|
// setup multitexture |
|
if( drawtextures && vboarray.tstate != VBO_TEXTURE_MAIN ) |
|
{ |
|
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 ) ); |
|
vboarray.tstate = VBO_TEXTURE_MAIN; |
|
} |
|
|
|
if( drawlightmap && (vboarray.lstate != VBO_LIGHTMAP_STATIC || vboarray.astate == VBO_ARRAY_DECAL ) ) |
|
{ |
|
// set lightmap texenv |
|
if( mtst.lm ) |
|
GL_Bind( mtst.tmu_lm = XASH_TEXTURE1, mtst.lm ); |
|
else |
|
GL_SelectTexture( mtst.tmu_lm = XASH_TEXTURE1 ); |
|
pglEnable( GL_TEXTURE_2D ); |
|
pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); |
|
R_SetLightmap(); |
|
vboarray.lstate = VBO_LIGHTMAP_STATIC; |
|
} |
|
vboarray.astate = VBO_ARRAY_STATIC; |
|
R_SetDecalMode( false ); |
|
} |
|
} |
|
|
|
|
|
|
|
static void R_SetupVBOArrayDlight( vboarray_t *vbo, texture_t *texture ) |
|
{ |
|
|
|
if( vboarray.astate != VBO_ARRAY_DLIGHT ) |
|
{ |
|
if( vboarray.astate == VBO_ARRAY_DECAL_DLIGHT ) |
|
{ |
|
// 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) ); |
|
} |
|
if( vboarray.tstate != VBO_TEXTURE_MAIN || vboarray.astate == VBO_ARRAY_DECAL_DLIGHT ) |
|
{ |
|
R_SetupVBOTexture( texture, vboarray.itexture ); |
|
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)offsetof(vbovertex_t, gl_tc ) ); |
|
vboarray.tstate = VBO_TEXTURE_MAIN; |
|
} |
|
GL_Bind( mtst.tmu_lm, tr.dlightTexture ); |
|
vboarray.lstate = VBO_LIGHTMAP_DYNAMIC; |
|
|
|
// 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 ); |
|
|
|
vboarray.astate = VBO_ARRAY_DLIGHT; |
|
} |
|
} |
|
|
|
#define SPARSE_DECALS_UPLOAD 0 |
|
|
|
static void R_SetupVBOArrayDecalDlight( int decalcount ) |
|
{ |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); |
|
#if !SPARSE_DECALS_UPLOAD |
|
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbovertex_t ) * DECAL_VERTS_MAX * decalcount, vbos.decal_dlight , GL_STREAM_DRAW_ARB ); |
|
#endif |
|
R_SetDecalMode( true ); |
|
// hack: fix decal dlights on gl_vbo_details == 2 (wrong state??) |
|
/*if( mtst.details_enabled && mtst.tmu_dt != -1 ) |
|
{ |
|
GL_Bind( mtst.tmu_dt, tr.whiteTexture ); |
|
}*/ |
|
GL_SelectTexture( mtst.tmu_lm ); |
|
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); |
|
} |
|
vboarray.astate = VBO_ARRAY_DECAL_DLIGHT; |
|
vboarray.tstate = VBO_TEXTURE_DECAL; |
|
vboarray.lstate = VBO_LIGHTMAP_DYNAMIC; |
|
} |
|
|
|
|
|
/* |
|
=================== |
|
R_AdditionalPasses |
|
|
|
draw details when not enough tmus |
|
=================== |
|
*/ |
|
static void R_AdditionalPasses( vboarray_t *vbo, int indexlen, void *indexarray, texture_t *tex, qboolean resetvbo, size_t offset ) |
|
{ |
|
if( !indexlen ) |
|
return; |
|
|
|
// draw details in additional pass |
|
if( r_detailtextures.value && r_vbo_detail.value == 1 && 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*)(offset + 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_VBOINDEX_TYPE, indexarray ); |
|
else |
|
#endif |
|
pglDrawElements( GL_TRIANGLES, indexlen, GL_VBOINDEX_TYPE, 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 ); |
|
pglDisable( GL_BLEND ); |
|
vboarray.astate = VBO_ARRAY_NONE; |
|
vboarray.tstate = VBO_TEXTURE_NONE; |
|
vboarray.lstate = VBO_LIGHTMAP_NONE; |
|
if( resetvbo ) |
|
R_SetupVBOArrayDlight( vbo, tex ); |
|
else |
|
R_SetupVBOArrayStatic( vbo, true, true ); |
|
} |
|
} |
|
|
|
|
|
#define MINIMIZE_UPLOAD |
|
#define DISCARD_DLIGHTS |
|
|
|
|
|
static void R_DrawDlightedDecals( vboarray_t *vbo, msurface_t *newsurf, msurface_t *surf, int decalcount, texture_t *texture ) |
|
{ |
|
msurface_t *decalsurf; |
|
decal_t *pdecal; |
|
int decali = 0; |
|
|
|
pglDepthMask( GL_FALSE ); |
|
pglEnable( GL_BLEND ); |
|
pglEnable( GL_POLYGON_OFFSET_FILL ); |
|
if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) |
|
pglDisable( GL_ALPHA_TEST ); |
|
|
|
R_SetupVBOArrayDecalDlight( decalcount ); |
|
|
|
for( decalsurf = newsurf; ( decali < decalcount ) && (!surf ||( 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; |
|
|
|
} |
|
|
|
#if SPARSE_DECALS_UPLOAD |
|
if( vbos.decal_dlight_vbo ) |
|
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB ); |
|
#endif |
|
|
|
// 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 ); |
|
R_SetDecalMode( false ); |
|
} |
|
|
|
static void R_FlushDlights( vboarray_t *vbo, int min_index, int max_index, int dlightindex, vboindex_t *dlightarray ) |
|
{ |
|
if( max_index == 0 ) |
|
return; |
|
if( vbos.dlight_vbo ) |
|
{ |
|
#ifndef MINIMIZE_UPLOAD |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); |
|
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), vbos.dlight_tc + min_index, GL_STREAM_DRAW_ARB ); |
|
#endif |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbo->glindex ); |
|
pglVertexPointer( 3, GL_FLOAT, sizeof( vbovertex_t ), (void*)(min_index* sizeof( vbovertex_t ) + offsetof(vbovertex_t,pos)) ); |
|
GL_SelectTexture( mtst.tmu_gl ); |
|
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)(min_index * sizeof( vbovertex_t ) + offsetof(vbovertex_t,gl_tc)) ); |
|
if( mtst.details_enabled && mtst.tmu_dt != -1 ) |
|
{ |
|
GL_SelectTexture( mtst.tmu_dt ); |
|
pglTexCoordPointer( 2, GL_FLOAT, sizeof( vbovertex_t ), (void*)(min_index * sizeof( vbovertex_t ) + offsetof(vbovertex_t,gl_tc)) ); |
|
} |
|
|
|
} |
|
//GL_SelectTexture( mtst.tmu_lm ); |
|
GL_Bind( mtst.tmu_lm, tr.dlightTexture ); |
|
#ifdef DISCARD_DLIGHTS |
|
pglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, BLOCK_SIZE, BLOCK_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0 ); |
|
#endif |
|
LM_UploadDynamicBlock(); |
|
#if !defined XASH_NANOGL || defined XASH_WES && XASH_EMSCRIPTEN // WebGL need to know array sizes |
|
if( pglDrawRangeElements ) |
|
pglDrawRangeElements( GL_TRIANGLES, min_index, max_index, dlightindex, GL_VBOINDEX_TYPE, dlightarray ); |
|
else |
|
#endif |
|
pglDrawElements( GL_TRIANGLES, dlightindex, GL_VBOINDEX_TYPE, dlightarray ); |
|
} |
|
|
|
static void R_AddSurfaceDecalsDlight( msurface_t *surf, int *pdecalcount ) |
|
{ |
|
decal_t *pdecal; |
|
int decalcount = *pdecalcount; |
|
#if SPARSE_DECALS_UPLOAD |
|
if( decalcount == 0 ) |
|
{ |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decal_dlight_vbo ); |
|
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vbos.decal_dlight ), NULL, GL_STREAM_DRAW_ARB ); |
|
} |
|
#endif |
|
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 - surf->info->dlight_s ) * ( 1.0f / (float)BLOCK_SIZE ); |
|
vbos.decal_dlight[decalcount * DECAL_VERTS_MAX + i].lm_tc[1] = v[6] - ( surf->light_t - surf->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 - surf->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 - surf->info->dlight_t ) * ( 1.0f / (float)BLOCK_SIZE ); |
|
} |
|
} |
|
#if SPARSE_DECALS_UPLOAD |
|
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 ); |
|
} |
|
#endif |
|
|
|
vbos.decal_numverts[decalcount] = numVerts; |
|
decalcount++; |
|
} |
|
*pdecalcount = decalcount; |
|
} |
|
|
|
|
|
static void R_DrawVBODlights( vboarray_t *vbo, vbotexture_t *vbotex, texture_t *texture, int lightmap ) |
|
{ |
|
// draw dlights and dlighted decals |
|
if( vbotex->dlightchain ) |
|
{ |
|
vboindex_t *dlightarray = vbos.dlight_index; // preallocated array |
|
unsigned int dlightindex = 0; |
|
msurface_t *surf, *newsurf; |
|
int decalcount = 0; |
|
int min_index = 65536; |
|
int max_index = 0; |
|
|
|
R_SetupVBOArrayDlight( vbo, texture ); |
|
|
|
// clear the block |
|
LM_InitBlock(); |
|
|
|
|
|
if( vbos.dlight_vbo ) |
|
{ |
|
// calculate minimum indexbase |
|
for( surf = newsurf = vbotex->dlightchain; surf; surf = surf->info->lightmapchain ) |
|
{ |
|
uint indexbase = vbos.surfdata[((char*)surf - (char*)WORLDMODEL->surfaces) / sizeof( *surf )].startindex; |
|
if(min_index > indexbase) |
|
min_index = indexbase; |
|
#ifdef MINIMIZE_UPLOAD |
|
if( max_index < indexbase + surf->polys->numverts ) |
|
max_index = indexbase + surf->polys->numverts; |
|
#endif |
|
} |
|
#ifdef MINIMIZE_UPLOAD |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); |
|
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), NULL, GL_STREAM_DRAW_ARB ); |
|
#endif |
|
} |
|
else |
|
{ |
|
min_index = 0; |
|
max_index = vbo->array_len; |
|
} |
|
|
|
// 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 |
|
R_FlushDlights( vbo, min_index, max_index, dlightindex, dlightarray ); |
|
|
|
R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true, min_index * sizeof( vbovertex_t ) ); |
|
#ifdef MINIMIZE_UPLOAD |
|
// invalidate buffer to prevent blocking on SubData |
|
if( vbos.dlight_vbo ) |
|
{ |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); |
|
pglBufferDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t )* (max_index - min_index), NULL, GL_STREAM_DRAW_ARB ); |
|
} |
|
#else |
|
if( vbos.dlight_vbo ) |
|
max_index = 0; |
|
#endif |
|
|
|
// draw decals that lighted with this lightmap |
|
if( decalcount ) |
|
R_DrawDlightedDecals( vbo, newsurf, surf, decalcount, texture ); |
|
decalcount = 0; |
|
R_SetupVBOArrayDlight( vbo, texture ); |
|
|
|
// 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 - min_index; |
|
dlightarray[dlightindex++] = index - 1 - min_index; |
|
dlightarray[dlightindex++] = index - min_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 ); |
|
} |
|
#ifndef MINIMIZE_UPLOAD |
|
if( max_index < indexbase + surf->polys->numverts ) |
|
max_index = indexbase + surf->polys->numverts; |
|
#else |
|
if( vbos.dlight_vbo ) |
|
{ |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.dlight_vbo ); |
|
pglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, sizeof( vec2_t ) * (indexbase - min_index), sizeof( vec2_t )* surf->polys->numverts, vbos.dlight_tc + indexbase ); |
|
} |
|
#endif |
|
|
|
// if surface has decals, build decal array |
|
R_AddSurfaceDecalsDlight( surf, &decalcount ); |
|
//info->dlight_s = info->dlight_t = 0; |
|
} |
|
|
|
if( dlightindex ) |
|
{ |
|
R_FlushDlights( vbo, min_index, max_index, dlightindex, dlightarray ); |
|
R_AdditionalPasses( vbo, dlightindex, dlightarray, texture, true, min_index * sizeof( vbovertex_t ) ); |
|
|
|
// draw remaining decals |
|
if( decalcount ) |
|
{ |
|
R_DrawDlightedDecals( vbo, newsurf, NULL, decalcount, texture ); |
|
} |
|
} |
|
|
|
if( vbos.dlight_vbo ) |
|
{ |
|
// invalidate state to reset vertex array offset |
|
vboarray.astate = VBO_ARRAY_NONE; |
|
vboarray.tstate = VBO_TEXTURE_NONE; |
|
vboarray.lstate = VBO_LIGHTMAP_NONE; |
|
} |
|
R_SetupVBOArrayStatic( vbo, true, true ); |
|
R_SetupVBOTexture( texture, vboarray.itexture ); |
|
|
|
// prepare to next frame |
|
vbotex->dlightchain = NULL; |
|
} |
|
} |
|
|
|
|
|
|
|
/* |
|
===================== |
|
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( vbotex->curindex ) |
|
{ |
|
#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_VBOINDEX_TYPE, vbotex->indexarray ); |
|
else |
|
#endif |
|
pglDrawElements( GL_TRIANGLES, vbotex->curindex, GL_VBOINDEX_TYPE, vbotex->indexarray ); |
|
|
|
// draw debug lines |
|
if( gl_wireframe.value && !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_VBOINDEX_TYPE, vbotex->indexarray ); |
|
else |
|
#endif |
|
pglDrawElements( GL_LINES, vbotex->curindex, GL_VBOINDEX_TYPE, 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; |
|
} |
|
|
|
R_DrawVBODlights( vbo, vbotex, texture, lightmap ); |
|
|
|
R_AdditionalPasses( vbo, vbotex->curindex, vbotex->indexarray, texture, false, 0 ); |
|
// prepare to next frame |
|
vbotex->curindex = 0; |
|
} |
|
|
|
void R_SetupVBOArrayDecal( qboolean drawlightmap ) |
|
{ |
|
// prepare for decal draw |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, vbos.decaldata->decalvbo ); |
|
// 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 ) ); |
|
/*if( mtst.details_enabled && mtst.tmu_dt != -1 ) |
|
{ |
|
GL_Bind( mtst.tmu_dt, tr.whiteTexture ); |
|
}*/ |
|
} |
|
R_SetDecalMode( true ); |
|
vboarray.astate = VBO_ARRAY_DECAL; |
|
vboarray.tstate = VBO_TEXTURE_DECAL; |
|
|
|
} |
|
|
|
void R_SetupVBOArrayDecalDyn( qboolean drawlightmap, float *v ) |
|
{ |
|
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 ); |
|
} |
|
R_SetDecalMode( true ); |
|
vboarray.astate = VBO_ARRAY_DECAL; |
|
vboarray.tstate = VBO_TEXTURE_DECAL; |
|
|
|
} |
|
|
|
static void R_DrawStaticDecals( vboarray_t *vbo, qboolean drawlightmap, int ilightmap ) |
|
{ |
|
int k = ilightmap; |
|
msurface_t *lightmapchain; |
|
|
|
pglDepthMask( GL_FALSE ); |
|
pglEnable( GL_BLEND ); |
|
pglEnable( GL_POLYGON_OFFSET_FILL ); |
|
|
|
if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) |
|
pglDisable( GL_ALPHA_TEST ); |
|
|
|
R_SetupVBOArrayDecal( drawlightmap ); |
|
|
|
// 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 |
|
R_SetupVBOArrayDecalDyn( drawlightmap, v ); |
|
|
|
pglDrawArrays( GL_TRIANGLE_FAN, 0, numVerts ); |
|
|
|
R_SetupVBOArrayDecal( drawlightmap ); |
|
} |
|
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 ); |
|
|
|
R_SetupVBOArrayStatic( vbo, drawlightmap, true ); |
|
|
|
if( RI.currententity->curstate.rendermode == kRenderTransAlpha ) |
|
pglEnable( GL_ALPHA_TEST ); |
|
} |
|
|
|
void R_ClearVBOState( qboolean drawlightmap, qboolean drawtextures ) |
|
{ |
|
// 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 ); |
|
|
|
|
|
pglDisableClientState( GL_VERTEX_ARRAY ); |
|
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); |
|
|
|
vboarray.astate = VBO_ARRAY_NONE; |
|
vboarray.tstate = VBO_TEXTURE_NONE; |
|
vboarray.lstate = VBO_LIGHTMAP_NONE; |
|
vboarray.itexture = 0; |
|
mtst.lm = 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( !r_vbo.value ) |
|
return; |
|
|
|
GL_SetupFogColorForSurfacesEx( 1, 0.5f ); |
|
|
|
R_SetupVBOArrayStatic( vbo, drawlightmap, drawtextures ); |
|
mtst.skiptexture = !drawtextures; |
|
mtst.tmu_dt = glConfig.max_texture_units > 2 && r_vbo_detail.value == 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; |
|
|
|
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; |
|
vboarray.astate = VBO_ARRAY_NONE; // invalidate |
|
vboarray.tstate = VBO_TEXTURE_NONE; |
|
vboarray.lstate = VBO_LIGHTMAP_NONE; |
|
|
|
if( drawtextures ) |
|
{ |
|
tex = R_SetupVBOTexture( tex, j ); |
|
} |
|
|
|
// update texcoord pointers |
|
R_SetupVBOArrayStatic( vbo, drawlightmap, drawtextures ); |
|
|
|
if( drawlightmap ) |
|
{ |
|
GL_Bind( mtst.tmu_lm, tr.lightmapTextures[k] ); |
|
} |
|
|
|
// draw new array |
|
if( (vbotex->curindex || vbotex->dlightchain) ) |
|
R_DrawLightmappedVBO( vbo, vbotex, tex, k, !drawlightmap ); |
|
vboarray.astate = VBO_ARRAY_NONE; // invalidate |
|
vboarray.tstate = VBO_TEXTURE_NONE; |
|
} |
|
} |
|
|
|
if( drawtextures && drawlightmap && vbos.decaldata->lm[k] ) |
|
{ |
|
R_DrawStaticDecals( vbo, drawlightmap, k ); |
|
} |
|
if( !drawtextures || !drawlightmap ) |
|
vbos.decaldata->lm[k] = NULL; |
|
} |
|
// ASSERT( !vbo->next ); |
|
R_ClearVBOState( drawlightmap, drawtextures ); |
|
|
|
mtst.details_enabled = false; |
|
|
|
vbos.minlightmap = MAX_LIGHTMAPS; |
|
vbos.maxlightmap = 0; |
|
vbos.mintexture = INT_MAX; |
|
vbos.maxtexture = 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 )) |
|
return false; |
|
|
|
// build lightmap |
|
if(( maps < MAXLIGHTMAPS ) && ( 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( r_vbo.value && 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 &= !r_fullbright->value && !!WORLDMODEL->lightdata; |
|
|
|
/* draw details in regular way */ |
|
if( r_vbo_detail.value == 0 ) |
|
{ |
|
if( r_detailtextures.value && surf->texinfo && surf->texinfo ) |
|
{ |
|
texture_t *t = surf->texinfo->texture; |
|
|
|
if( glState.isFogEnabled ) |
|
{ |
|
// don't apply detail textures for windows in the fog |
|
if( RI.currententity->curstate.rendermode != kRenderTransTexture ) |
|
{ |
|
if( t->dt_texturenum ) |
|
{ |
|
surf->info->detailchain = detail_surfaces[t->dt_texturenum]; |
|
detail_surfaces[t->dt_texturenum] = surf->info; |
|
} |
|
else |
|
{ |
|
// draw stub detail texture for underwater surfaces |
|
surf->info->detailchain = detail_surfaces[tr.grayTexture]; |
|
detail_surfaces[tr.grayTexture] = surf->info; |
|
} |
|
draw_details = true; |
|
} |
|
} |
|
else if( t->dt_texturenum ) |
|
{ |
|
surf->info->detailchain = detail_surfaces[t->dt_texturenum]; |
|
detail_surfaces[t->dt_texturenum] = surf->info; |
|
draw_details = true; |
|
} |
|
} |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
// now this path does not draw wapred surfaces, so count it as one poly |
|
r_stats.c_world_polys++; |
|
|
|
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 && !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 )); |
|
} |
|
} |
|
|
|
// 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 = CL_GetEntityByIndex( 0 ); |
|
if( !RI.currententity ) |
|
return; |
|
|
|
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_DrawTextureChains(); |
|
|
|
if( !ENGINE_GET_PARM( PARM_DEV_OVERVIEW )) |
|
{ |
|
DrawDecalsBatch(); |
|
GL_ResetFogColor(); |
|
R_BlendLightmaps(); |
|
R_RenderFullbrights(); |
|
R_RenderDetails( r_vbo.value? 2 : 3 ); |
|
|
|
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 |
|
|
|
// 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 < gp_cl->nummodels; i++ ) |
|
{ |
|
if(( m = CL_ModelHandle( 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( gp_host->features, ENGINE_LARGE_LIGHTMAPS ) || tr.world->version == QBSP2_VERSION ) |
|
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 < gp_cl->nummodels; i++ ) |
|
{ |
|
if(( m = CL_ModelHandle( 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( ); |
|
} |
|
} |
|
|
|
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 ); |
|
}
|
|
|