quakespasm/Quake/r_brush.c
Eric Wasylishen 112c8b68b8 Alias model rendering fast-path using OpenGL 2.
In GL_MakeAliasModelDisplayLists, after saving the standerd triangle strips and fans onto the hunk, if the system is GL2 capable, we also save a second version of the mdl on the hunk designed to be loaded into a VBO (GL_MakeAliasModelDisplayLists_VBO). In R_NewMap, and on video mode changes, we call GLMesh_LoadVertexBuffers which loops over all precached mdl's and loads the data into a pair of VBO's (vertices and vertex indices).

Finally, in R_DrawAliasModel, assuming no rendering options are disabling the fast-path (r_drawflat 1, r_lightmap 1, or r_fullbright 1 would disable it), we call GL_DrawAliasFrame_GLSL, which sets up all of the bindings and draws the (possibly lerped) mdl in one glDrawElements call.

Special thanks to MH for some of the code from RMQEngine and the general concept.

git-svn-id: svn://svn.code.sf.net/p/quakespasm/code/trunk/quakespasm@1151 af15c1b1-3010-417e-b628-4374ebc0bcbd
2015-01-20 18:59:15 +00:00

1314 lines
31 KiB
C

/*
Copyright (C) 1996-2001 Id Software, Inc.
Copyright (C) 2002-2009 John Fitzgibbons and others
Copyright (C) 2007-2008 Kristian Duske
Copyright (C) 2010-2014 QuakeSpasm developers
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 2
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// r_brush.c: brush model rendering. renamed from r_surf.c
#include "quakedef.h"
extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldwater; //johnfitz
extern cvar_t gl_zfix; // QuakeSpasm z-fighting fix
int gl_lightmap_format;
int lightmap_bytes;
#define BLOCK_WIDTH 128
#define BLOCK_HEIGHT 128
gltexture_t *lightmap_textures[MAX_LIGHTMAPS]; //johnfitz -- changed to an array
unsigned blocklights[BLOCK_WIDTH*BLOCK_HEIGHT*3]; //johnfitz -- was 18*18, added lit support (*3) and loosened surface extents maximum (BLOCK_WIDTH*BLOCK_HEIGHT)
typedef struct glRect_s {
unsigned char l,t,w,h;
} glRect_t;
glpoly_t *lightmap_polys[MAX_LIGHTMAPS];
qboolean lightmap_modified[MAX_LIGHTMAPS];
glRect_t lightmap_rectchange[MAX_LIGHTMAPS];
int allocated[MAX_LIGHTMAPS][BLOCK_WIDTH];
int last_lightmap_allocated; //ericw -- optimization: remember the index of the last lightmap AllocBlock stored a surf in
// the lightmap texture data needs to be kept in
// main memory so texsubimage can update properly
byte lightmaps[4*MAX_LIGHTMAPS*BLOCK_WIDTH*BLOCK_HEIGHT];
/*
===============
R_TextureAnimation -- johnfitz -- added "frame" param to eliminate use of "currententity" global
Returns the proper texture for a given time and base texture
===============
*/
texture_t *R_TextureAnimation (texture_t *base, int frame)
{
int relative;
int count;
if (frame)
if (base->alternate_anims)
base = base->alternate_anims;
if (!base->anim_total)
return base;
relative = (int)(cl.time*10) % base->anim_total;
count = 0;
while (base->anim_min > relative || base->anim_max <= relative)
{
base = base->anim_next;
if (!base)
Sys_Error ("R_TextureAnimation: broken cycle");
if (++count > 100)
Sys_Error ("R_TextureAnimation: infinite cycle");
}
return base;
}
/*
================
DrawGLPoly
================
*/
void DrawGLPoly (glpoly_t *p)
{
float *v;
int i;
glBegin (GL_POLYGON);
v = p->verts[0];
for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[3], v[4]);
glVertex3fv (v);
}
glEnd ();
}
/*
================
DrawGLTriangleFan -- johnfitz -- like DrawGLPoly but for r_showtris
================
*/
void DrawGLTriangleFan (glpoly_t *p)
{
float *v;
int i;
glBegin (GL_TRIANGLE_FAN);
v = p->verts[0];
for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
{
glVertex3fv (v);
}
glEnd ();
}
/*
=============================================================
BRUSH MODELS
=============================================================
*/
#if 0
/*
================
R_DrawSequentialPoly -- johnfitz -- rewritten
================
*/
void R_DrawSequentialPoly (msurface_t *s)
{
glpoly_t *p;
texture_t *t;
float *v;
float entalpha;
int i;
t = R_TextureAnimation (s->texinfo->texture, currententity->frame);
entalpha = ENTALPHA_DECODE(currententity->alpha);
// drawflat
if (r_drawflat_cheatsafe)
{
if ((s->flags & SURF_DRAWTURB) && r_oldwater.value)
{
for (p = s->polys->next; p; p = p->next)
{
srand((unsigned int) (uintptr_t) p);
glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0);
DrawGLPoly (p);
rs_brushpasses++;
}
return;
}
srand((unsigned int) (uintptr_t) s->polys);
glColor3f (rand()%256/255.0, rand()%256/255.0, rand()%256/255.0);
DrawGLPoly (s->polys);
rs_brushpasses++;
return;
}
// fullbright
if ((r_fullbright_cheatsafe) && !(s->flags & SURF_DRAWTILED))
{
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
if (s->flags & SURF_DRAWFENCE)
glEnable (GL_ALPHA_TEST); // Flip on alpha test
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
if (s->flags & SURF_DRAWFENCE)
glDisable (GL_ALPHA_TEST); // Flip alpha test back off
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
goto fullbrights;
}
// r_lightmap
if (r_lightmap_cheatsafe)
{
if (s->flags & SURF_DRAWTILED)
{
glDisable (GL_TEXTURE_2D);
DrawGLPoly (s->polys);
glEnable (GL_TEXTURE_2D);
rs_brushpasses++;
return;
}
R_RenderDynamicLightmaps (s);
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
if (!gl_overbright.value)
{
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f(0.5, 0.5, 0.5);
}
glBegin (GL_POLYGON);
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
if (!gl_overbright.value)
{
glColor3f(1,1,1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
}
rs_brushpasses++;
return;
}
// sky poly -- skip it, already handled in gl_sky.c
if (s->flags & SURF_DRAWSKY)
return;
// water poly
if (s->flags & SURF_DRAWTURB)
{
if (currententity->alpha == ENTALPHA_DEFAULT)
entalpha = CLAMP(0.0,r_wateralpha.value,1.0);
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
if (r_oldwater.value)
{
GL_Bind (s->texinfo->texture->gltexture);
for (p = s->polys->next; p; p = p->next)
{
DrawWaterPoly (p);
rs_brushpasses++;
}
rs_brushpasses++;
}
else
{
GL_Bind (s->texinfo->texture->warpimage);
s->texinfo->texture->update_warp = true; // FIXME: one frame too late!
DrawGLPoly (s->polys);
rs_brushpasses++;
}
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
return;
}
// missing texture
if (s->flags & SURF_NOTEXTURE)
{
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
return;
}
// lightmapped poly
if (entalpha < 1)
{
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor4f(1, 1, 1, entalpha);
}
else
glColor3f(1, 1, 1);
if (s->flags & SURF_DRAWFENCE)
glEnable (GL_ALPHA_TEST); // Flip on alpha test
if (gl_overbright.value)
{
if (gl_texture_env_combine && gl_mtexable) //case 1: texture and lightmap in one pass, overbright using texture combiners
{
GL_DisableMultitexture(); // selects TEXTURE0
GL_Bind (t->gltexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GL_EnableMultitexture(); // selects TEXTURE1
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
R_RenderDynamicLightmaps (s);
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE_EXT);
glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_EXT, GL_MODULATE);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_EXT, GL_PREVIOUS_EXT);
glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_EXT, GL_TEXTURE);
glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 2.0f);
glBegin(GL_POLYGON);
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]);
GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GL_DisableMultitexture ();
rs_brushpasses++;
}
else if (entalpha < 1 || (s->flags & SURF_DRAWFENCE)) //case 2: can't do multipass if entity has alpha, so just draw the texture
{
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
}
else //case 3: texture in one pass, lightmap in second pass using 2x modulation blend func, fog in third pass
{
//first pass -- texture with no fog
Fog_DisableGFog ();
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
Fog_EnableGFog ();
rs_brushpasses++;
//second pass -- lightmap with black fog, modulate blended
R_RenderDynamicLightmaps (s);
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
glDepthMask (GL_FALSE);
glEnable (GL_BLEND);
glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); //2x modulate
Fog_StartAdditive ();
glBegin (GL_POLYGON);
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
Fog_StopAdditive ();
rs_brushpasses++;
//third pass -- black geo with normal fog, additive blended
if (Fog_GetDensity() > 0)
{
glBlendFunc(GL_ONE, GL_ONE); //add
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f(0,0,0);
DrawGLPoly (s->polys);
glColor3f(1,1,1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
rs_brushpasses++;
}
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable (GL_BLEND);
glDepthMask (GL_TRUE);
}
}
else
{
if (gl_mtexable) //case 4: texture and lightmap in one pass, regular modulation
{
GL_DisableMultitexture(); // selects TEXTURE0
GL_Bind (t->gltexture);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
GL_EnableMultitexture(); // selects TEXTURE1
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
R_RenderDynamicLightmaps (s);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glBegin(GL_POLYGON);
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]);
GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
GL_DisableMultitexture ();
rs_brushpasses++;
}
else if (entalpha < 1 || (s->flags & SURF_DRAWFENCE)) //case 5: can't do multipass if entity has alpha, so just draw the texture
{
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
rs_brushpasses++;
}
else //case 6: texture in one pass, lightmap in a second pass, fog in third pass
{
//first pass -- texture with no fog
Fog_DisableGFog ();
GL_Bind (t->gltexture);
DrawGLPoly (s->polys);
Fog_EnableGFog ();
rs_brushpasses++;
//second pass -- lightmap with black fog, modulate blended
R_RenderDynamicLightmaps (s);
GL_Bind (lightmap_textures[s->lightmaptexturenum]);
glDepthMask (GL_FALSE);
glEnable (GL_BLEND);
glBlendFunc (GL_ZERO, GL_SRC_COLOR); //modulate
Fog_StartAdditive ();
glBegin (GL_POLYGON);
v = s->polys->verts[0];
for (i=0 ; i<s->polys->numverts ; i++, v+= VERTEXSIZE)
{
glTexCoord2f (v[5], v[6]);
glVertex3fv (v);
}
glEnd ();
Fog_StopAdditive ();
rs_brushpasses++;
//third pass -- black geo with normal fog, additive blended
if (Fog_GetDensity() > 0)
{
glBlendFunc(GL_ONE, GL_ONE); //add
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f(0,0,0);
DrawGLPoly (s->polys);
glColor3f(1,1,1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
rs_brushpasses++;
}
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable (GL_BLEND);
glDepthMask (GL_TRUE);
}
}
if (entalpha < 1)
{
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glColor3f(1, 1, 1);
}
if (s->flags & SURF_DRAWFENCE)
glDisable (GL_ALPHA_TEST); // Flip alpha test back off
fullbrights:
if (gl_fullbrights.value && t->fullbright)
{
glDepthMask (GL_FALSE);
glEnable (GL_BLEND);
glBlendFunc (GL_ONE, GL_ONE);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
glColor3f (entalpha, entalpha, entalpha);
GL_Bind (t->fullbright);
Fog_StartAdditive ();
DrawGLPoly (s->polys);
Fog_StopAdditive ();
glColor3f(1, 1, 1);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable (GL_BLEND);
glDepthMask (GL_TRUE);
rs_brushpasses++;
}
}
#endif
/*
=================
R_DrawBrushModel
=================
*/
void R_DrawBrushModel (entity_t *e)
{
int i, k;
msurface_t *psurf;
float dot;
mplane_t *pplane;
qmodel_t *clmodel;
if (R_CullModelForEntity(e))
return;
currententity = e;
clmodel = e->model;
VectorSubtract (r_refdef.vieworg, e->origin, modelorg);
if (e->angles[0] || e->angles[1] || e->angles[2])
{
vec3_t temp;
vec3_t forward, right, up;
VectorCopy (modelorg, temp);
AngleVectors (e->angles, forward, right, up);
modelorg[0] = DotProduct (temp, forward);
modelorg[1] = -DotProduct (temp, right);
modelorg[2] = DotProduct (temp, up);
}
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
// calculate dynamic lighting for bmodel if it's not an
// instanced model
if (clmodel->firstmodelsurface != 0 && !gl_flashblend.value)
{
for (k=0 ; k<MAX_DLIGHTS ; k++)
{
if ((cl_dlights[k].die < cl.time) ||
(!cl_dlights[k].radius))
continue;
R_MarkLights (&cl_dlights[k], k,
clmodel->nodes + clmodel->hulls[0].firstclipnode);
}
}
glPushMatrix ();
e->angles[0] = -e->angles[0]; // stupid quake bug
if (gl_zfix.value)
{
e->origin[0] -= DIST_EPSILON;
e->origin[1] -= DIST_EPSILON;
e->origin[2] -= DIST_EPSILON;
}
R_RotateForEntity (e->origin, e->angles);
if (gl_zfix.value)
{
e->origin[0] += DIST_EPSILON;
e->origin[1] += DIST_EPSILON;
e->origin[2] += DIST_EPSILON;
}
e->angles[0] = -e->angles[0]; // stupid quake bug
R_ClearTextureChains (clmodel, chain_model);
for (i=0 ; i<clmodel->nummodelsurfaces ; i++, psurf++)
{
pplane = psurf->plane;
dot = DotProduct (modelorg, pplane->normal) - pplane->dist;
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
{
R_ChainSurface (psurf, chain_model);
rs_brushpolys++;
}
}
R_DrawTextureChains (clmodel, e, chain_model);
R_DrawTextureChains_Water (clmodel, e, chain_model);
glPopMatrix ();
}
/*
=================
R_DrawBrushModel_ShowTris -- johnfitz
=================
*/
void R_DrawBrushModel_ShowTris (entity_t *e)
{
int i;
msurface_t *psurf;
float dot;
mplane_t *pplane;
qmodel_t *clmodel;
glpoly_t *p;
if (R_CullModelForEntity(e))
return;
currententity = e;
clmodel = e->model;
VectorSubtract (r_refdef.vieworg, e->origin, modelorg);
if (e->angles[0] || e->angles[1] || e->angles[2])
{
vec3_t temp;
vec3_t forward, right, up;
VectorCopy (modelorg, temp);
AngleVectors (e->angles, forward, right, up);
modelorg[0] = DotProduct (temp, forward);
modelorg[1] = -DotProduct (temp, right);
modelorg[2] = DotProduct (temp, up);
}
psurf = &clmodel->surfaces[clmodel->firstmodelsurface];
glPushMatrix ();
e->angles[0] = -e->angles[0]; // stupid quake bug
R_RotateForEntity (e->origin, e->angles);
e->angles[0] = -e->angles[0]; // stupid quake bug
//
// draw it
//
for (i=0 ; i<clmodel->nummodelsurfaces ; i++, psurf++)
{
pplane = psurf->plane;
dot = DotProduct (modelorg, pplane->normal) - pplane->dist;
if (((psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
{
if ((psurf->flags & SURF_DRAWTURB) && r_oldwater.value)
for (p = psurf->polys->next; p; p = p->next)
DrawGLTriangleFan (p);
else
DrawGLTriangleFan (psurf->polys);
}
}
glPopMatrix ();
}
/*
=============================================================
LIGHTMAPS
=============================================================
*/
/*
================
R_RenderDynamicLightmaps
called during rendering
================
*/
void R_RenderDynamicLightmaps (msurface_t *fa)
{
byte *base;
int maps;
glRect_t *theRect;
int smax, tmax;
if (fa->flags & SURF_DRAWTILED) //johnfitz -- not a lightmapped surface
return;
// add to lightmap chain
fa->polys->chain = lightmap_polys[fa->lightmaptexturenum];
lightmap_polys[fa->lightmaptexturenum] = fa->polys;
// check for lightmap modification
for (maps=0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++)
if (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps])
goto dynamic;
if (fa->dlightframe == r_framecount // dynamic this frame
|| fa->cached_dlight) // dynamic previously
{
dynamic:
if (r_dynamic.value)
{
lightmap_modified[fa->lightmaptexturenum] = true;
theRect = &lightmap_rectchange[fa->lightmaptexturenum];
if (fa->light_t < theRect->t) {
if (theRect->h)
theRect->h += theRect->t - fa->light_t;
theRect->t = fa->light_t;
}
if (fa->light_s < theRect->l) {
if (theRect->w)
theRect->w += theRect->l - fa->light_s;
theRect->l = fa->light_s;
}
smax = (fa->extents[0]>>4)+1;
tmax = (fa->extents[1]>>4)+1;
if ((theRect->w + theRect->l) < (fa->light_s + smax))
theRect->w = (fa->light_s-theRect->l)+smax;
if ((theRect->h + theRect->t) < (fa->light_t + tmax))
theRect->h = (fa->light_t-theRect->t)+tmax;
base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes;
R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes);
}
}
}
/*
========================
AllocBlock -- returns a texture number and the position inside it
========================
*/
int AllocBlock (int w, int h, int *x, int *y)
{
int i, j;
int best, best2;
int texnum;
// ericw -- rather than searching starting at lightmap 0 every time,
// start at the last lightmap we allocated a surface in.
// This makes AllocBlock much faster on large levels (can shave off 3+ seconds
// of load time on a level with 180 lightmaps), at a cost of not quite packing
// lightmaps as tightly vs. not doing this (uses ~5% more lightmaps)
for (texnum=last_lightmap_allocated ; texnum<MAX_LIGHTMAPS ; texnum++, last_lightmap_allocated++)
{
best = BLOCK_HEIGHT;
for (i=0 ; i<BLOCK_WIDTH-w ; i++)
{
best2 = 0;
for (j=0 ; j<w ; j++)
{
if (allocated[texnum][i+j] >= best)
break;
if (allocated[texnum][i+j] > best2)
best2 = allocated[texnum][i+j];
}
if (j == w)
{ // this is a valid spot
*x = i;
*y = best = best2;
}
}
if (best + h > BLOCK_HEIGHT)
continue;
for (i=0 ; i<w ; i++)
allocated[texnum][*x + i] = best + h;
return texnum;
}
Sys_Error ("AllocBlock: full");
return 0; //johnfitz -- shut up compiler
}
mvertex_t *r_pcurrentvertbase;
qmodel_t *currentmodel;
int nColinElim;
/*
========================
GL_CreateSurfaceLightmap
========================
*/
void GL_CreateSurfaceLightmap (msurface_t *surf)
{
int smax, tmax;
byte *base;
smax = (surf->extents[0]>>4)+1;
tmax = (surf->extents[1]>>4)+1;
surf->lightmaptexturenum = AllocBlock (smax, tmax, &surf->light_s, &surf->light_t);
base = lightmaps + surf->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
base += (surf->light_t * BLOCK_WIDTH + surf->light_s) * lightmap_bytes;
R_BuildLightMap (surf, base, BLOCK_WIDTH*lightmap_bytes);
}
/*
================
BuildSurfaceDisplayList -- called at level load time
================
*/
void BuildSurfaceDisplayList (msurface_t *fa)
{
int i, lindex, lnumverts;
medge_t *pedges, *r_pedge;
float *vec;
float s, t;
glpoly_t *poly;
// reconstruct the polygon
pedges = currentmodel->edges;
lnumverts = fa->numedges;
//
// draw texture
//
poly = (glpoly_t *) Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float));
poly->next = fa->polys;
fa->polys = poly;
poly->numverts = lnumverts;
for (i=0 ; i<lnumverts ; i++)
{
lindex = currentmodel->surfedges[fa->firstedge + i];
if (lindex > 0)
{
r_pedge = &pedges[lindex];
vec = r_pcurrentvertbase[r_pedge->v[0]].position;
}
else
{
r_pedge = &pedges[-lindex];
vec = r_pcurrentvertbase[r_pedge->v[1]].position;
}
s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
s /= fa->texinfo->texture->width;
t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
t /= fa->texinfo->texture->height;
VectorCopy (vec, poly->verts[i]);
poly->verts[i][3] = s;
poly->verts[i][4] = t;
//
// lightmap texture coordinates
//
s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
s -= fa->texturemins[0];
s += fa->light_s*16;
s += 8;
s /= BLOCK_WIDTH*16; //fa->texinfo->texture->width;
t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
t -= fa->texturemins[1];
t += fa->light_t*16;
t += 8;
t /= BLOCK_HEIGHT*16; //fa->texinfo->texture->height;
poly->verts[i][5] = s;
poly->verts[i][6] = t;
}
//johnfitz -- removed gl_keeptjunctions code
poly->numverts = lnumverts;
}
/*
==================
GL_BuildLightmaps -- called at level load time
Builds the lightmap texture
with all the surfaces from all brush models
==================
*/
void GL_BuildLightmaps (void)
{
char name[16];
byte *data;
int i, j;
qmodel_t *m;
memset (allocated, 0, sizeof(allocated));
last_lightmap_allocated = 0;
r_framecount = 1; // no dlightcache
//johnfitz -- null out array (the gltexture objects themselves were already freed by Mod_ClearAll)
for (i=0; i < MAX_LIGHTMAPS; i++)
lightmap_textures[i] = NULL;
//johnfitz
gl_lightmap_format = GL_RGBA;//FIXME: hardcoded for now!
switch (gl_lightmap_format)
{
case GL_RGBA:
lightmap_bytes = 4;
break;
case GL_BGRA:
lightmap_bytes = 4;
break;
default:
Sys_Error ("GL_BuildLightmaps: bad lightmap format");
}
for (j=1 ; j<MAX_MODELS ; j++)
{
m = cl.model_precache[j];
if (!m)
break;
if (m->name[0] == '*')
continue;
r_pcurrentvertbase = m->vertexes;
currentmodel = m;
for (i=0 ; i<m->numsurfaces ; i++)
{
//johnfitz -- rewritten to use SURF_DRAWTILED instead of the sky/water flags
if (m->surfaces[i].flags & SURF_DRAWTILED)
continue;
GL_CreateSurfaceLightmap (m->surfaces + i);
BuildSurfaceDisplayList (m->surfaces + i);
//johnfitz
}
}
//
// upload all lightmaps that were filled
//
for (i=0; i<MAX_LIGHTMAPS; i++)
{
if (!allocated[i][0])
break; // no more used
lightmap_modified[i] = false;
lightmap_rectchange[i].l = BLOCK_WIDTH;
lightmap_rectchange[i].t = BLOCK_HEIGHT;
lightmap_rectchange[i].w = 0;
lightmap_rectchange[i].h = 0;
//johnfitz -- use texture manager
sprintf(name, "lightmap%03i",i);
data = lightmaps+i*BLOCK_WIDTH*BLOCK_HEIGHT*lightmap_bytes;
lightmap_textures[i] = TexMgr_LoadImage (cl.worldmodel, name, BLOCK_WIDTH, BLOCK_HEIGHT,
SRC_LIGHTMAP, data, "", (src_offset_t)data, TEXPREF_LINEAR | TEXPREF_NOPICMIP);
//johnfitz
}
//johnfitz -- warn about exceeding old limits
if (i >= 64)
Con_Warning ("%i lightmaps exceeds standard limit of 64.\n", i);
//johnfitz
}
/*
=============================================================
VBO support
=============================================================
*/
GLuint gl_bmodel_vbo = 0;
/*
==================
GL_BuildVBOs
Deletes gl_bmodel_vbo if it already exists, then rebuilds it with all
surfaces from world + all brush models
==================
*/
void GL_BuildVBOs (void)
{
unsigned int numverts, varray_bytes, varray_index;
int i, j;
qmodel_t *m;
float *varray;
if (!(gl_vbo_able && gl_mtexable && gl_max_texture_units >= 3))
return;
// ask GL for a name for our VBO
GL_DeleteBuffersFunc (1, &gl_bmodel_vbo);
GL_GenBuffersFunc (1, &gl_bmodel_vbo);
// count all verts in all models
numverts = 0;
for (j=1 ; j<MAX_MODELS ; j++)
{
m = cl.model_precache[j];
if (!m || m->name[0] == '*' || m->type != mod_brush)
continue;
for (i=0 ; i<m->numsurfaces ; i++)
{
numverts += m->surfaces[i].numedges;
}
}
// build vertex array
varray_bytes = VERTEXSIZE * sizeof(float) * numverts;
varray = (float *) malloc (varray_bytes);
varray_index = 0;
for (j=1 ; j<MAX_MODELS ; j++)
{
m = cl.model_precache[j];
if (!m || m->name[0] == '*' || m->type != mod_brush)
continue;
for (i=0 ; i<m->numsurfaces ; i++)
{
msurface_t *s = &m->surfaces[i];
s->vbo_firstvert = varray_index;
memcpy (&varray[VERTEXSIZE * varray_index], s->polys->verts, VERTEXSIZE * sizeof(float) * s->numedges);
varray_index += s->numedges;
}
}
// upload to GPU
GL_BindBufferFunc (GL_ARRAY_BUFFER, gl_bmodel_vbo);
GL_BufferDataFunc (GL_ARRAY_BUFFER, varray_bytes, varray, GL_STATIC_DRAW);
free (varray);
// invalidate the cached bindings
GL_ClearBufferBindings ();
}
/*
===============
R_AddDynamicLights
===============
*/
void R_AddDynamicLights (msurface_t *surf)
{
int lnum;
int sd, td;
float dist, rad, minlight;
vec3_t impact, local;
int s, t;
int i;
int smax, tmax;
mtexinfo_t *tex;
//johnfitz -- lit support via lordhavoc
float cred, cgreen, cblue, brightness;
unsigned *bl;
//johnfitz
smax = (surf->extents[0]>>4)+1;
tmax = (surf->extents[1]>>4)+1;
tex = surf->texinfo;
for (lnum=0 ; lnum<MAX_DLIGHTS ; lnum++)
{
if (! (surf->dlightbits[lnum >> 5] & (1U << (lnum & 31))))
continue; // not lit by this light
rad = cl_dlights[lnum].radius;
dist = DotProduct (cl_dlights[lnum].origin, surf->plane->normal) -
surf->plane->dist;
rad -= fabs(dist);
minlight = cl_dlights[lnum].minlight;
if (rad < minlight)
continue;
minlight = rad - minlight;
for (i=0 ; i<3 ; i++)
{
impact[i] = cl_dlights[lnum].origin[i] -
surf->plane->normal[i]*dist;
}
local[0] = DotProduct (impact, tex->vecs[0]) + tex->vecs[0][3];
local[1] = DotProduct (impact, tex->vecs[1]) + tex->vecs[1][3];
local[0] -= surf->texturemins[0];
local[1] -= surf->texturemins[1];
//johnfitz -- lit support via lordhavoc
bl = blocklights;
cred = cl_dlights[lnum].color[0] * 256.0f;
cgreen = cl_dlights[lnum].color[1] * 256.0f;
cblue = cl_dlights[lnum].color[2] * 256.0f;
//johnfitz
for (t = 0 ; t<tmax ; t++)
{
td = local[1] - t*16;
if (td < 0)
td = -td;
for (s=0 ; s<smax ; s++)
{
sd = local[0] - s*16;
if (sd < 0)
sd = -sd;
if (sd > td)
dist = sd + (td>>1);
else
dist = td + (sd>>1);
if (dist < minlight)
//johnfitz -- lit support via lordhavoc
{
brightness = rad - dist;
bl[0] += (int) (brightness * cred);
bl[1] += (int) (brightness * cgreen);
bl[2] += (int) (brightness * cblue);
}
bl += 3;
//johnfitz
}
}
}
}
/*
===============
R_BuildLightMap -- johnfitz -- revised for lit support via lordhavoc
Combine and scale multiple lightmaps into the 8.8 format in blocklights
===============
*/
void R_BuildLightMap (msurface_t *surf, byte *dest, int stride)
{
int smax, tmax;
int r,g,b;
int i, j, size;
byte *lightmap;
unsigned scale;
int maps;
unsigned *bl;
surf->cached_dlight = (surf->dlightframe == r_framecount);
smax = (surf->extents[0]>>4)+1;
tmax = (surf->extents[1]>>4)+1;
size = smax*tmax;
lightmap = surf->samples;
if (cl.worldmodel->lightdata)
{
// clear to no light
memset (&blocklights[0], 0, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc
// add all the lightmaps
if (lightmap)
for (maps = 0 ; maps < MAXLIGHTMAPS && surf->styles[maps] != 255 ;
maps++)
{
scale = d_lightstylevalue[surf->styles[maps]];
surf->cached_light[maps] = scale; // 8.8 fraction
//johnfitz -- lit support via lordhavoc
bl = blocklights;
for (i=0 ; i<size ; i++)
{
*bl++ += *lightmap++ * scale;
*bl++ += *lightmap++ * scale;
*bl++ += *lightmap++ * scale;
}
//johnfitz
}
// add all the dynamic lights
if (surf->dlightframe == r_framecount)
R_AddDynamicLights (surf);
}
else
{
// set to full bright if no light data
memset (&blocklights[0], 255, size * 3 * sizeof (unsigned int)); //johnfitz -- lit support via lordhavoc
}
// bound, invert, and shift
// store:
switch (gl_lightmap_format)
{
case GL_RGBA:
stride -= smax * 4;
bl = blocklights;
for (i=0 ; i<tmax ; i++, dest += stride)
{
for (j=0 ; j<smax ; j++)
{
if (gl_overbright.value)
{
r = *bl++ >> 8;
g = *bl++ >> 8;
b = *bl++ >> 8;
}
else
{
r = *bl++ >> 7;
g = *bl++ >> 7;
b = *bl++ >> 7;
}
if (r > 255) r = 255; *dest++ = r;
if (g > 255) g = 255; *dest++ = g;
if (b > 255) b = 255; *dest++ = b;
*dest++ = 255;
}
}
break;
case GL_BGRA:
stride -= smax * 4;
bl = blocklights;
for (i=0 ; i<tmax ; i++, dest += stride)
{
for (j=0 ; j<smax ; j++)
{
if (gl_overbright.value)
{
r = *bl++ >> 8;
g = *bl++ >> 8;
b = *bl++ >> 8;
}
else
{
r = *bl++ >> 7;
g = *bl++ >> 7;
b = *bl++ >> 7;
}
if (b > 255) b = 255; *dest++ = b;
if (g > 255) g = 255; *dest++ = g;
if (r > 255) r = 255; *dest++ = r;
*dest++ = 255;
}
}
break;
default:
Sys_Error ("R_BuildLightMap: bad lightmap format");
}
}
/*
===============
R_UploadLightmap -- johnfitz -- uploads the modified lightmap to opengl if necessary
assumes lightmap texture is already bound
===============
*/
static void R_UploadLightmap(int lmap)
{
glRect_t *theRect;
if (!lightmap_modified[lmap])
return;
lightmap_modified[lmap] = false;
theRect = &lightmap_rectchange[lmap];
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, theRect->t, BLOCK_WIDTH, theRect->h, gl_lightmap_format,
GL_UNSIGNED_BYTE, lightmaps+(lmap* BLOCK_HEIGHT + theRect->t) *BLOCK_WIDTH*lightmap_bytes);
theRect->l = BLOCK_WIDTH;
theRect->t = BLOCK_HEIGHT;
theRect->h = 0;
theRect->w = 0;
rs_dynamiclightmaps++;
}
void R_UploadLightmaps (void)
{
int lmap;
for (lmap = 0; lmap < MAX_LIGHTMAPS; lmap++)
{
if (!lightmap_modified[lmap])
continue;
GL_Bind (lightmap_textures[lmap]);
R_UploadLightmap(lmap);
}
}
/*
================
R_RebuildAllLightmaps -- johnfitz -- called when gl_overbright gets toggled
================
*/
void R_RebuildAllLightmaps (void)
{
int i, j;
qmodel_t *mod;
msurface_t *fa;
byte *base;
if (!cl.worldmodel) // is this the correct test?
return;
//for each surface in each model, rebuild lightmap with new scale
for (i=1; i<MAX_MODELS; i++)
{
if (!(mod = cl.model_precache[i]))
continue;
fa = &mod->surfaces[mod->firstmodelsurface];
for (j=0; j<mod->nummodelsurfaces; j++, fa++)
{
if (fa->flags & SURF_DRAWTILED)
continue;
base = lightmaps + fa->lightmaptexturenum*lightmap_bytes*BLOCK_WIDTH*BLOCK_HEIGHT;
base += fa->light_t * BLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes;
R_BuildLightMap (fa, base, BLOCK_WIDTH*lightmap_bytes);
}
}
//for each lightmap, upload it
for (i=0; i<MAX_LIGHTMAPS; i++)
{
if (!allocated[i][0])
break;
GL_Bind (lightmap_textures[i]);
glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, BLOCK_WIDTH, BLOCK_HEIGHT, gl_lightmap_format,
GL_UNSIGNED_BYTE, lightmaps+i*BLOCK_WIDTH*BLOCK_HEIGHT*lightmap_bytes);
}
}