mirror of
https://github.com/Shpoike/Quakespasm.git
synced 2024-11-09 23:11:39 +00:00
2415 lines
69 KiB
C
2415 lines
69 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_world.c: world model rendering
|
|
|
|
#include "quakedef.h"
|
|
|
|
extern cvar_t gl_fullbrights, r_drawflat, gl_overbright, r_oldskyleaf, r_showtris; //johnfitz
|
|
cvar_t r_scenecache = {"r_scenecache",""}; //spike, an attempt to cope with abusive maps a bit better.
|
|
|
|
byte *SV_FatPVS (vec3_t org, qmodel_t *worldmodel);
|
|
static qboolean RSceneCache_Queue(byte *vis);
|
|
static void RSceneCache_Draw(qboolean water);
|
|
void RSceneCache_Shutdown(void);
|
|
extern qboolean lightmaps_skipupdates;
|
|
|
|
//==============================================================================
|
|
//
|
|
// SETUP CHAINS
|
|
//
|
|
//==============================================================================
|
|
|
|
/*
|
|
================
|
|
R_ClearTextureChains -- ericw
|
|
|
|
clears texture chains for all textures used by the given model, and also
|
|
clears the lightmap chains
|
|
================
|
|
*/
|
|
void R_ClearTextureChains (qmodel_t *mod, texchain_t chain)
|
|
{
|
|
int i;
|
|
|
|
// set all chains to null
|
|
for (i=0 ; i<mod->numtextures ; i++)
|
|
if (mod->textures[i])
|
|
mod->textures[i]->texturechains[chain] = NULL;
|
|
|
|
// clear lightmap chains
|
|
for (i=0 ; i<lightmap_count ; i++)
|
|
lightmaps[i].polys = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_ChainSurface -- ericw -- adds the given surface to its texture chain
|
|
================
|
|
*/
|
|
void R_ChainSurface (msurface_t *surf, texchain_t chain)
|
|
{
|
|
surf->texturechain = surf->texinfo->texture->texturechains[chain];
|
|
surf->texinfo->texture->texturechains[chain] = surf;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_BackFaceCull -- johnfitz -- returns true if the surface is facing away from vieworg
|
|
================
|
|
*/
|
|
qboolean R_BackFaceCull (msurface_t *surf)
|
|
{
|
|
double dot;
|
|
|
|
if (surf->plane->type < 3)
|
|
dot = r_refdef.vieworg[surf->plane->type] - surf->plane->dist;
|
|
else
|
|
dot = DotProduct (r_refdef.vieworg, surf->plane->normal) - surf->plane->dist;
|
|
|
|
if ((dot < 0) ^ !!(surf->flags & SURF_PLANEBACK))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
R_MarkSurfaces -- johnfitz -- mark surfaces based on PVS and rebuild texture chains
|
|
===============
|
|
*/
|
|
void R_MarkSurfaces (void)
|
|
{
|
|
byte *vis;
|
|
mleaf_t *leaf;
|
|
msurface_t *surf, **mark;
|
|
int i, j;
|
|
qboolean nearwaterportal;
|
|
|
|
// clear lightmap chains
|
|
for (i=0 ; i<lightmap_count ; i++)
|
|
lightmaps[i].polys = NULL;
|
|
|
|
// check this leaf for water portals
|
|
// TODO: loop through all water surfs and use distance to leaf cullbox
|
|
nearwaterportal = r_scenecache.value!=0;
|
|
for (i=0, mark = r_viewleaf->firstmarksurface; i < r_viewleaf->nummarksurfaces; i++, mark++)
|
|
if ((*mark)->flags & SURF_DRAWTURB)
|
|
nearwaterportal = true;
|
|
|
|
// choose vis data
|
|
if (r_novis.value || r_viewleaf->contents == CONTENTS_SOLID || r_viewleaf->contents == CONTENTS_SKY)
|
|
vis = Mod_NoVisPVS (cl.worldmodel);
|
|
else if (nearwaterportal)
|
|
vis = SV_FatPVS (r_origin, cl.worldmodel);
|
|
else
|
|
vis = Mod_LeafPVS (r_viewleaf, cl.worldmodel);
|
|
|
|
r_visframecount++;
|
|
|
|
// set all chains to null
|
|
for (i=0 ; i<cl.worldmodel->numtextures ; i++)
|
|
if (cl.worldmodel->textures[i])
|
|
cl.worldmodel->textures[i]->texturechains[chain_world] = NULL;
|
|
|
|
#ifndef SDL_THREADS_DISABLED
|
|
if (RSceneCache_Queue(vis))
|
|
return;
|
|
lightmaps_skipupdates = false;
|
|
#endif
|
|
|
|
//need to do this somewhere...
|
|
R_PushDlights ();
|
|
|
|
// iterate through leaves, marking surfaces
|
|
leaf = &cl.worldmodel->leafs[1];
|
|
for (i=0 ; i<cl.worldmodel->numleafs ; i++, leaf++)
|
|
{
|
|
if (vis[i>>3] & (1<<(i&7)))
|
|
{
|
|
if (R_CullBox(leaf->minmaxs, leaf->minmaxs + 3))
|
|
continue;
|
|
|
|
if (leaf->contents != CONTENTS_SKY || r_oldskyleaf.value)
|
|
for (j=0, mark = leaf->firstmarksurface; j<leaf->nummarksurfaces; j++, mark++)
|
|
{
|
|
surf = *mark;
|
|
if (surf->visframe != r_visframecount)
|
|
{
|
|
surf->visframe = r_visframecount;
|
|
if (!R_CullBox(surf->mins, surf->maxs) && !R_BackFaceCull (surf))
|
|
{
|
|
rs_brushpolys++; //count wpolys here
|
|
R_ChainSurface(surf, chain_world);
|
|
R_RenderDynamicLightmaps(cl.worldmodel, surf);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add static models
|
|
if (leaf->efrags)
|
|
R_StoreEfrags (&leaf->efrags);
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
//
|
|
// DRAW CHAINS
|
|
//
|
|
//==============================================================================
|
|
|
|
/*
|
|
=============
|
|
R_BeginTransparentDrawing -- ericw
|
|
=============
|
|
*/
|
|
static void R_BeginTransparentDrawing (float entalpha)
|
|
{
|
|
if (entalpha < 1.0f)
|
|
{
|
|
glDepthMask (GL_FALSE);
|
|
glEnable (GL_BLEND);
|
|
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glColor4f (1,1,1,entalpha);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_EndTransparentDrawing -- ericw
|
|
=============
|
|
*/
|
|
static void R_EndTransparentDrawing (float entalpha)
|
|
{
|
|
if (entalpha < 1.0f)
|
|
{
|
|
glDepthMask (GL_TRUE);
|
|
glDisable (GL_BLEND);
|
|
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
glColor3f (1, 1, 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_ShowTris -- johnfitz
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_ShowTris (qmodel_t *model, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
glpoly_t *p;
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
if (!t)
|
|
continue;
|
|
|
|
if (!gl_glsl_water_able && t->texturechains[chain] && (t->texturechains[chain]->flags & SURF_DRAWTURB))
|
|
{
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
for (p = s->polys->next; p; p = p->next)
|
|
{
|
|
DrawGLTriangleFan (p);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
DrawGLTriangleFan (s->polys);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_Drawflat -- johnfitz
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_Drawflat (qmodel_t *model, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
glpoly_t *p;
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
if (!t)
|
|
continue;
|
|
|
|
if (!gl_glsl_water_able && t->texturechains[chain] && (t->texturechains[chain]->flags & SURF_DRAWTURB))
|
|
{
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
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++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
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++;
|
|
}
|
|
}
|
|
}
|
|
glColor3f (1,1,1);
|
|
srand ((int) (cl.time * 1000));
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_Glow -- johnfitz
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_Glow (qmodel_t *model, entity_t *ent, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
gltexture_t *glt;
|
|
qboolean bound;
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
|
|
if (!t || !t->texturechains[chain] || !(glt = R_TextureAnimation(t, ent != NULL ? ent->frame : 0)->fullbright))
|
|
continue;
|
|
|
|
bound = false;
|
|
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
if (!bound) //only bind once we are sure we need this texture
|
|
{
|
|
GL_Bind (glt);
|
|
bound = true;
|
|
}
|
|
DrawGLPoly (s->polys);
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
//
|
|
// VBO SUPPORT
|
|
//
|
|
//==============================================================================
|
|
|
|
static unsigned int R_NumTriangleIndicesForSurf (msurface_t *s)
|
|
{
|
|
return 3 * (s->numedges - 2);
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_TriangleIndicesForSurf
|
|
|
|
Writes out the triangle indices needed to draw s as a triangle list.
|
|
The number of indices it will write is given by R_NumTriangleIndicesForSurf.
|
|
================
|
|
*/
|
|
static void R_TriangleIndicesForSurf (msurface_t *s, unsigned int *dest)
|
|
{
|
|
int i;
|
|
for (i=2; i<s->numedges; i++)
|
|
{
|
|
*dest++ = s->vbo_firstvert;
|
|
*dest++ = s->vbo_firstvert + i - 1;
|
|
*dest++ = s->vbo_firstvert + i;
|
|
}
|
|
}
|
|
|
|
#define MAX_BATCH_SIZE 65536
|
|
|
|
static unsigned int vbo_indices[MAX_BATCH_SIZE];
|
|
static unsigned int num_vbo_indices;
|
|
|
|
/*
|
|
================
|
|
R_ClearBatch
|
|
================
|
|
*/
|
|
static void R_ClearBatch ()
|
|
{
|
|
num_vbo_indices = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_FlushBatch
|
|
|
|
Draw the current batch if non-empty and clears it, ready for more R_BatchSurface calls.
|
|
================
|
|
*/
|
|
static void R_FlushBatch ()
|
|
{
|
|
if (num_vbo_indices > 0)
|
|
{
|
|
glDrawElements (GL_TRIANGLES, num_vbo_indices, GL_UNSIGNED_INT, vbo_indices);
|
|
num_vbo_indices = 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_BatchSurface
|
|
|
|
Add the surface to the current batch, or just draw it immediately if we're not
|
|
using VBOs.
|
|
================
|
|
*/
|
|
static void R_BatchSurface (msurface_t *s)
|
|
{
|
|
unsigned int num_surf_indices;
|
|
|
|
num_surf_indices = R_NumTriangleIndicesForSurf (s);
|
|
if (num_surf_indices-1u<=MAX_BATCH_SIZE) //ericw's qbsp bugs out sometimes. don't crash.
|
|
{
|
|
if (num_vbo_indices + num_surf_indices > MAX_BATCH_SIZE)
|
|
R_FlushBatch();
|
|
|
|
R_TriangleIndicesForSurf (s, &vbo_indices[num_vbo_indices]);
|
|
num_vbo_indices += num_surf_indices;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_Multitexture -- johnfitz
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_Multitexture (qmodel_t *model, entity_t *ent, texchain_t chain)
|
|
{
|
|
int i, j;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
float *v;
|
|
qboolean bound;
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
|
|
if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWTILED | SURF_NOTEXTURE))
|
|
continue;
|
|
|
|
bound = false;
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
if (!bound) //only bind once we are sure we need this texture
|
|
{
|
|
GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture);
|
|
|
|
if (t->texturechains[chain]->flags & SURF_DRAWFENCE)
|
|
glEnable (GL_ALPHA_TEST); // Flip alpha test back on
|
|
|
|
GL_EnableMultitexture(); // selects TEXTURE1
|
|
bound = true;
|
|
}
|
|
GL_Bind (lightmaps[s->lightmaptexturenum].texture);
|
|
glBegin(GL_POLYGON);
|
|
v = s->polys->verts[0];
|
|
for (j=0 ; j<s->polys->numverts ; j++, v+= VERTEXSIZE)
|
|
{
|
|
GL_MTexCoord2fFunc (GL_TEXTURE0_ARB, v[3], v[4]);
|
|
GL_MTexCoord2fFunc (GL_TEXTURE1_ARB, v[5], v[6]);
|
|
glVertex3fv (v);
|
|
}
|
|
glEnd ();
|
|
rs_brushpasses++;
|
|
}
|
|
GL_DisableMultitexture(); // selects TEXTURE0
|
|
|
|
if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE)
|
|
glDisable (GL_ALPHA_TEST); // Flip alpha test back off
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_NoTexture -- johnfitz
|
|
|
|
draws surfs whose textures were missing from the BSP
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_NoTexture (qmodel_t *model, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
qboolean bound;
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
|
|
if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_NOTEXTURE))
|
|
continue;
|
|
|
|
bound = false;
|
|
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
if (!bound) //only bind once we are sure we need this texture
|
|
{
|
|
GL_Bind (t->gltexture);
|
|
bound = true;
|
|
}
|
|
DrawGLPoly (s->polys);
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_TextureOnly -- johnfitz
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_TextureOnly (qmodel_t *model, entity_t *ent, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
qboolean bound;
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
|
|
if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWSKY))
|
|
continue;
|
|
|
|
bound = false;
|
|
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
if (!bound) //only bind once we are sure we need this texture
|
|
{
|
|
GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture);
|
|
|
|
if (t->texturechains[chain]->flags & SURF_DRAWFENCE)
|
|
glEnable (GL_ALPHA_TEST); // Flip alpha test back on
|
|
|
|
bound = true;
|
|
}
|
|
DrawGLPoly (s->polys);
|
|
rs_brushpasses++;
|
|
}
|
|
|
|
if (bound && t->texturechains[chain]->flags & SURF_DRAWFENCE)
|
|
glDisable (GL_ALPHA_TEST); // Flip alpha test back off
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
GL_WaterAlphaForEntitySurface -- ericw
|
|
|
|
Returns the water alpha to use for the entity and surface combination.
|
|
================
|
|
*/
|
|
float GL_WaterAlphaForEntitySurface (entity_t *ent, msurface_t *s)
|
|
{
|
|
float entalpha;
|
|
if (ent == NULL || ent->alpha == ENTALPHA_DEFAULT)
|
|
entalpha = GL_WaterAlphaForSurface(s);
|
|
else
|
|
entalpha = ENTALPHA_DECODE(ent->alpha);
|
|
return entalpha;
|
|
}
|
|
|
|
|
|
static GLuint r_world_program;
|
|
extern GLuint gl_bmodel_vbo;
|
|
|
|
// uniforms used in frag shader
|
|
static GLuint texLoc;
|
|
static GLuint LMTexLoc;
|
|
static GLuint fullbrightTexLoc;
|
|
static GLuint useFullbrightTexLoc;
|
|
static GLuint useOverbrightLoc;
|
|
static GLuint useAlphaTestLoc;
|
|
static GLuint alphaLoc;
|
|
|
|
|
|
static struct
|
|
{
|
|
GLuint program;
|
|
|
|
GLuint light_scale;
|
|
GLuint alpha_scale;
|
|
GLuint time;
|
|
GLuint eyepos;
|
|
GLuint fogalpha;
|
|
GLuint colour;
|
|
} r_water[4]; //
|
|
|
|
#define vertAttrIndex 0
|
|
#define texCoordsAttrIndex 1
|
|
#define LMCoordsAttrIndex 2
|
|
|
|
/*
|
|
=============
|
|
GLWorld_CreateShaders
|
|
=============
|
|
*/
|
|
static void GLWater_CreateShaders (void)
|
|
{
|
|
const char *modedefines[countof(r_water)] = {
|
|
"",
|
|
"#define LIT\n"
|
|
};
|
|
const glsl_attrib_binding_t bindings[] = {
|
|
{ "Vert", vertAttrIndex },
|
|
{ "TexCoords", texCoordsAttrIndex },
|
|
{ "LMCoords", LMCoordsAttrIndex }
|
|
};
|
|
|
|
// Driver bug workarounds:
|
|
// - "Intel(R) UHD Graphics 600" version "4.6.0 - Build 26.20.100.7263"
|
|
// crashing on glUseProgram with `vec3 Vert` and
|
|
// `gl_ModelViewProjectionMatrix * vec4(Vert, 1.0);`. Work around with
|
|
// making Vert a vec4. (https://sourceforge.net/p/quakespasm/bugs/39/)
|
|
const GLchar *vertSource = \
|
|
"#version 110\n"
|
|
"%s"
|
|
"\n"
|
|
"attribute vec4 Vert;\n"
|
|
"attribute vec2 TexCoords;\n"
|
|
"#ifdef LIT\n"
|
|
"attribute vec2 LMCoords;\n"
|
|
"varying vec2 tc_lm;\n"
|
|
"#endif\n"
|
|
"\n"
|
|
"varying float FogFragCoord;\n"
|
|
"varying vec2 tc_tex;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" tc_tex = TexCoords;\n"
|
|
"#ifdef LIT\n"
|
|
" tc_lm = LMCoords;\n"
|
|
"#endif\n"
|
|
" gl_Position = gl_ModelViewProjectionMatrix * Vert;\n"
|
|
" FogFragCoord = gl_Position.w;\n"
|
|
"}\n";
|
|
|
|
const GLchar *fragSource = \
|
|
"#version 110\n"
|
|
"%s"
|
|
"\n"
|
|
"uniform sampler2D Tex;\n"
|
|
"#ifdef LIT\n"
|
|
"uniform sampler2D LMTex;\n"
|
|
"uniform float LightScale;\n"
|
|
"varying vec2 tc_lm;\n"
|
|
"#endif\n"
|
|
"uniform float Alpha;\n"
|
|
"uniform float WarpTime;\n"
|
|
"\n"
|
|
"varying float FogFragCoord;\n"
|
|
"varying vec2 tc_tex;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec2 ntc = tc_tex;\n"
|
|
//CYCLE 128
|
|
//AMP 8*0x10000
|
|
//SPEED 20
|
|
// sintable[i] = AMP + sin(i*3.14159*2/CYCLE)*AMP;
|
|
//
|
|
// r_turb_turb = sintable + ((int)(cl.time*SPEED)&(CYCLE-1));
|
|
//
|
|
// sturb = ((r_turb_s + r_turb_turb[(r_turb_t>>16)&(CYCLE-1)])>>16)&63;
|
|
// tturb = ((r_turb_t + r_turb_turb[(r_turb_s>>16)&(CYCLE-1)])>>16)&63;
|
|
//The following 4 lines SHOULD match the software renderer, except normalised coords rather than snapped texels
|
|
"#define M_PI 3.14159\n"
|
|
"#define TIMEBIAS (((WarpTime*20.0)*M_PI*2.0)/128.0)\n"
|
|
" ntc += 0.125 + sin(tc_tex.ts*M_PI + TIMEBIAS)*0.125;\n"
|
|
" vec4 result = texture2D(Tex, ntc.st);\n"
|
|
"#ifdef LIT\n"
|
|
" result *= texture2D(LMTex, tc_lm.xy);\n"
|
|
" result.rgb *= LightScale;\n"
|
|
"#endif\n"
|
|
" result.a *= Alpha;\n"
|
|
" result = clamp(result, 0.0, 1.0);\n"
|
|
" float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n"
|
|
" fog = clamp(fog, 0.0, 1.0);\n"
|
|
" result.rgb = mix(gl_Fog.color.rgb, result.rgb, fog);\n"
|
|
" gl_FragColor = result;\n"
|
|
"}\n";
|
|
|
|
const GLchar *vertSource_sky =
|
|
"#version 110\n"
|
|
"\n"
|
|
"uniform vec3 EyePos;\n"
|
|
"\n"
|
|
"attribute vec4 Vert;\n"
|
|
"attribute vec2 TexCoords;\n"
|
|
"\n"
|
|
"varying float FogFragCoord;\n"
|
|
"varying vec3 SkyDir;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
"SkyDir = Vert.xyz - EyePos;\n"
|
|
"gl_Position = gl_ModelViewProjectionMatrix * Vert;\n"
|
|
"FogFragCoord = gl_Position.w;\n"
|
|
"}\n";
|
|
const GLchar *fragSource_sky =
|
|
"#version 110\n"
|
|
"\n"
|
|
"uniform sampler2D Tex;\n"
|
|
"uniform sampler2D CloudTex;\n"
|
|
"uniform float WarpTime;\n"
|
|
"uniform float Alpha, FogAlpha;\n"
|
|
"varying float FogFragCoord;\n"
|
|
"varying vec3 SkyDir;\n"
|
|
"void main ()\n"
|
|
"{\n"
|
|
"vec2 tccoord;\n"
|
|
"vec3 dir = SkyDir;\n"
|
|
"dir.z *= 3.0;\n"
|
|
"dir.xy *= 2.953125/length(dir);\n"
|
|
"tccoord = (dir.xy + WarpTime*0.0625);\n"
|
|
"vec3 sky = vec3(texture2D(Tex, tccoord));\n"
|
|
"tccoord = (dir.xy + WarpTime*0.125);\n"
|
|
"vec4 clouds = texture2D(CloudTex, tccoord);\n"
|
|
"clouds.a *= Alpha;\n"
|
|
"sky = (sky.rgb*(1.0-clouds.a)) + (clouds.a*clouds.rgb);\n"
|
|
|
|
#if 1 //sky is logically an infinite distance away, so fog is just an alpha blend with the colour, no distance calcs needed.
|
|
"if (gl_Fog.density > 0.0)\n"
|
|
"sky.rgb = mix(sky.rgb, gl_Fog.color.rgb, FogAlpha);\n"
|
|
#else //do fog as normal. we actually have distance values.
|
|
"float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n"
|
|
"fog = clamp(fog, 0.0, 1.0) * FogAlpha + (1.0-FogAlpha);\n"
|
|
"sky.rgb = mix(gl_Fog.color.rgb, sky.rgb, fog);\n"
|
|
#endif
|
|
|
|
"gl_FragColor = vec4(sky, 1.0);\n"
|
|
"}\n";
|
|
|
|
const GLchar *vertSource_fastsky =
|
|
"#version 110\n"
|
|
"attribute vec4 Vert;\n"
|
|
"varying float FogFragCoord;\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
"gl_Position = gl_ModelViewProjectionMatrix * Vert;\n"
|
|
"FogFragCoord = gl_Position.w;\n"
|
|
"}\n";
|
|
const GLchar *fragSource_fastsky =
|
|
"#version 110\n"
|
|
"\n"
|
|
"uniform float Alpha, FogAlpha;\n"
|
|
"uniform vec3 SkyColour;\n"
|
|
"varying float FogFragCoord;\n"
|
|
"void main ()\n"
|
|
"{\n"
|
|
"vec3 sky = SkyColour.rgb;\n"
|
|
|
|
#if 1 //sky is logically an infinite distance away, so fog is just an alpha blend with the colour, no distance calcs needed.
|
|
"if (gl_Fog.density > 0.0)\n"
|
|
"sky.rgb = mix(sky.rgb, gl_Fog.color.rgb, FogAlpha);\n"
|
|
#else //do fog as normal. we actually have distance values.
|
|
"float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n"
|
|
"fog = clamp(fog, 0.0, 1.0) * FogAlpha + (1.0-FogAlpha);\n"
|
|
"sky.rgb = mix(gl_Fog.color.rgb, sky.rgb, fog);\n"
|
|
#endif
|
|
"gl_FragColor = vec4(sky, 1.0);\n"
|
|
"}\n";
|
|
|
|
size_t i;
|
|
char vtext[1024];
|
|
char ftext[1024];
|
|
gl_glsl_water_able = false;
|
|
|
|
if (!gl_glsl_able)
|
|
return;
|
|
|
|
for (i = 0; i < countof(r_water); i++)
|
|
{
|
|
if (i == 3)
|
|
r_water[i].program = GL_CreateProgram (vertSource_fastsky, fragSource_fastsky, sizeof(bindings)/sizeof(bindings[0]), bindings);
|
|
else if (i == 2)
|
|
r_water[i].program = GL_CreateProgram (vertSource_sky, fragSource_sky, sizeof(bindings)/sizeof(bindings[0]), bindings);
|
|
else
|
|
{
|
|
snprintf(vtext, sizeof(vtext), vertSource, modedefines[i]);
|
|
snprintf(ftext, sizeof(ftext), fragSource, modedefines[i]);
|
|
r_water[i].program = GL_CreateProgram (vtext, ftext, sizeof(bindings)/sizeof(bindings[0]), bindings);
|
|
}
|
|
|
|
if (r_water[i].program != 0)
|
|
{
|
|
// get uniform locations
|
|
GLuint texLoc = ((i!=3)?GL_GetUniformLocation (&r_water[i].program, "Tex"):-1);
|
|
GLuint LMTexLoc = ((i==1)?GL_GetUniformLocation (&r_water[i].program, "LMTex"):-1);
|
|
GLuint CloudTexLoc = ((i==2)?GL_GetUniformLocation (&r_water[i].program, "CloudTex"):-1);
|
|
r_water[i].light_scale = ((i==1)?GL_GetUniformLocation (&r_water[i].program, "LightScale"):-1);
|
|
r_water[i].alpha_scale = ((i!=3)?GL_GetUniformLocation (&r_water[i].program, "Alpha"):-1);
|
|
r_water[i].time = ((i!=3)?GL_GetUniformLocation (&r_water[i].program, "WarpTime"):-1);
|
|
r_water[i].eyepos = ((i==2)?GL_GetUniformLocation (&r_water[i].program, "EyePos"):-1);
|
|
r_water[i].fogalpha = ((i>=2)?GL_GetUniformLocation (&r_water[i].program, "FogAlpha"):-1);
|
|
r_water[i].colour = ((i==3)?GL_GetUniformLocation (&r_water[i].program, "SkyColour"):-1);
|
|
|
|
if (!r_water[i].program)
|
|
return;
|
|
|
|
//bake constants here.
|
|
GL_UseProgramFunc (r_water[i].program);
|
|
GL_Uniform1iFunc (texLoc, 0);
|
|
if (LMTexLoc != -1)
|
|
GL_Uniform1iFunc (LMTexLoc, 1);
|
|
if (CloudTexLoc != -1)
|
|
GL_Uniform1iFunc (CloudTexLoc, 2);
|
|
GL_UseProgramFunc (0);
|
|
}
|
|
else
|
|
return; //erk?
|
|
}
|
|
gl_glsl_water_able = true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_Water -- johnfitz
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_Water (qmodel_t *model, entity_t *ent, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
glpoly_t *p;
|
|
qboolean bound;
|
|
float entalpha;
|
|
|
|
if (r_drawflat_cheatsafe || r_lightmap_cheatsafe) // ericw -- !r_drawworld_cheatsafe check moved to R_DrawWorld_Water ()
|
|
return;
|
|
|
|
if (gl_glsl_water_able)
|
|
{
|
|
int lastlightmap = -2;
|
|
int mode = -1;
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB))
|
|
continue;
|
|
s = t->texturechains[chain];
|
|
|
|
entalpha = GL_WaterAlphaForEntitySurface (ent, s);
|
|
if (entalpha < 1.0f)
|
|
{
|
|
glDepthMask (GL_FALSE);
|
|
glEnable (GL_BLEND);
|
|
}
|
|
|
|
// Bind the buffers
|
|
GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); // indices come from client memory!
|
|
GL_VertexAttribPointerFunc (vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0));
|
|
GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 3);
|
|
GL_VertexAttribPointerFunc (LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 5);
|
|
|
|
//actually use the buffers...
|
|
GL_EnableVertexAttribArrayFunc (vertAttrIndex);
|
|
GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex);
|
|
|
|
GL_SelectTexture (GL_TEXTURE0);
|
|
GL_Bind (t->gltexture);
|
|
GL_SelectTexture (GL_TEXTURE1);
|
|
for (; s; s = s->texturechain)
|
|
{
|
|
if (s->lightmaptexturenum != lastlightmap)
|
|
{
|
|
R_FlushBatch ();
|
|
|
|
mode = s->lightmaptexturenum>=0 && !r_fullbright_cheatsafe;
|
|
if (mode)
|
|
{ //lit
|
|
GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
GL_Bind (lightmaps[s->lightmaptexturenum].texture);
|
|
}
|
|
else //unlit
|
|
GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
|
|
GL_UseProgramFunc (r_water[mode].program);
|
|
GL_Uniform1fFunc (r_water[mode].time, cl.time);
|
|
if (r_water[mode].light_scale != -1)
|
|
GL_Uniform1fFunc (r_water[mode].light_scale, gl_overbright.value?2:1);
|
|
GL_Uniform1fFunc (r_water[mode].alpha_scale, entalpha);
|
|
lastlightmap = s->lightmaptexturenum;
|
|
}
|
|
R_BatchSurface (s);
|
|
|
|
rs_brushpasses++;
|
|
}
|
|
|
|
R_FlushBatch ();
|
|
GL_UseProgramFunc (0);
|
|
GL_DisableVertexAttribArrayFunc (vertAttrIndex);
|
|
GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex);
|
|
GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
GL_SelectTexture (GL_TEXTURE0);
|
|
lastlightmap = -2;
|
|
|
|
if (entalpha < 1.0f)
|
|
{
|
|
glDepthMask (GL_TRUE);
|
|
glDisable (GL_BLEND);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// legacy water for people with such old gpus that they can't even use glsl.
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTURB))
|
|
continue;
|
|
bound = false;
|
|
entalpha = 1.0f;
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
if (!bound) //only bind once we are sure we need this texture
|
|
{
|
|
entalpha = GL_WaterAlphaForEntitySurface (ent, s);
|
|
R_BeginTransparentDrawing (entalpha);
|
|
GL_Bind (t->gltexture);
|
|
bound = true;
|
|
}
|
|
for (p = s->polys->next; p; p = p->next)
|
|
{
|
|
DrawWaterPoly (p);
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
R_EndTransparentDrawing (entalpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_White -- johnfitz -- draw sky and water as white polys when r_lightmap is 1
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_White (qmodel_t *model, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
|
|
glDisable (GL_TEXTURE_2D);
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
|
|
if (!t || !t->texturechains[chain] || !(t->texturechains[chain]->flags & SURF_DRAWTILED))
|
|
continue;
|
|
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
DrawGLPoly (s->polys);
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
glEnable (GL_TEXTURE_2D);
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawLightmapChains -- johnfitz -- R_BlendLightmaps stripped down to almost nothing
|
|
================
|
|
*/
|
|
void R_DrawLightmapChains (void)
|
|
{
|
|
int i, j;
|
|
glpoly_t *p;
|
|
float *v;
|
|
|
|
for (i=0 ; i<lightmap_count ; i++)
|
|
{
|
|
if (!lightmaps[i].polys)
|
|
continue;
|
|
|
|
GL_Bind (lightmaps[i].texture);
|
|
for (p = lightmaps[i].polys; p; p=p->chain)
|
|
{
|
|
glBegin (GL_POLYGON);
|
|
v = p->verts[0];
|
|
for (j=0 ; j<p->numverts ; j++, v+= VERTEXSIZE)
|
|
{
|
|
glTexCoord2f (v[5], v[6]);
|
|
glVertex3fv (v);
|
|
}
|
|
glEnd ();
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
GLWorld_CreateShaders
|
|
=============
|
|
*/
|
|
void GLWorld_CreateShaders (void)
|
|
{
|
|
const glsl_attrib_binding_t bindings[] = {
|
|
{ "Vert", vertAttrIndex },
|
|
{ "TexCoords", texCoordsAttrIndex },
|
|
{ "LMCoords", LMCoordsAttrIndex }
|
|
};
|
|
|
|
// Driver bug workarounds:
|
|
// - "Intel(R) UHD Graphics 600" version "4.6.0 - Build 26.20.100.7263"
|
|
// crashing on glUseProgram with `vec3 Vert` and
|
|
// `gl_ModelViewProjectionMatrix * vec4(Vert, 1.0);`. Work around with
|
|
// making Vert a vec4. (https://sourceforge.net/p/quakespasm/bugs/39/)
|
|
const GLchar *vertSource = \
|
|
"#version 110\n"
|
|
"\n"
|
|
"attribute vec4 Vert;\n"
|
|
"attribute vec2 TexCoords;\n"
|
|
"attribute vec2 LMCoords;\n"
|
|
"\n"
|
|
"varying float FogFragCoord;\n"
|
|
"varying vec2 tc_tex;\n"
|
|
"varying vec2 tc_lm;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" tc_tex = TexCoords;\n"
|
|
" tc_lm = LMCoords;\n"
|
|
" gl_Position = gl_ModelViewProjectionMatrix * Vert;\n"
|
|
" FogFragCoord = gl_Position.w;\n"
|
|
"}\n";
|
|
|
|
const GLchar *fragSource = \
|
|
"#version 110\n"
|
|
"\n"
|
|
"uniform sampler2D Tex;\n"
|
|
"uniform sampler2D LMTex;\n"
|
|
"uniform sampler2D FullbrightTex;\n"
|
|
"uniform bool UseFullbrightTex;\n"
|
|
"uniform bool UseOverbright;\n"
|
|
"uniform bool UseAlphaTest;\n"
|
|
"uniform float Alpha;\n"
|
|
"\n"
|
|
"varying float FogFragCoord;\n"
|
|
"varying vec2 tc_tex;\n"
|
|
"varying vec2 tc_lm;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec4 result = texture2D(Tex, tc_tex.xy);\n"
|
|
" if (UseAlphaTest && (result.a < 0.666))\n"
|
|
" discard;\n"
|
|
" result *= texture2D(LMTex, tc_lm.xy);\n"
|
|
" if (UseOverbright)\n"
|
|
" result.rgb *= 2.0;\n"
|
|
" if (UseFullbrightTex)\n"
|
|
" result += texture2D(FullbrightTex, tc_tex.xy);\n"
|
|
" result = clamp(result, 0.0, 1.0);\n"
|
|
" float fog = exp(-gl_Fog.density * gl_Fog.density * FogFragCoord * FogFragCoord);\n"
|
|
" fog = clamp(fog, 0.0, 1.0);\n"
|
|
" result = mix(gl_Fog.color, result, fog);\n"
|
|
" result.a = Alpha;\n" // FIXME: This will make almost transparent things cut holes though heavy fog
|
|
" gl_FragColor = result;\n"
|
|
"}\n";
|
|
|
|
if (!gl_glsl_alias_able)
|
|
return;
|
|
|
|
r_world_program = GL_CreateProgram (vertSource, fragSource, sizeof(bindings)/sizeof(bindings[0]), bindings);
|
|
|
|
if (r_world_program != 0)
|
|
{
|
|
// get uniform locations
|
|
texLoc = GL_GetUniformLocation (&r_world_program, "Tex");
|
|
LMTexLoc = GL_GetUniformLocation (&r_world_program, "LMTex");
|
|
fullbrightTexLoc = GL_GetUniformLocation (&r_world_program, "FullbrightTex");
|
|
useFullbrightTexLoc = GL_GetUniformLocation (&r_world_program, "UseFullbrightTex");
|
|
useOverbrightLoc = GL_GetUniformLocation (&r_world_program, "UseOverbright");
|
|
useAlphaTestLoc = GL_GetUniformLocation (&r_world_program, "UseAlphaTest");
|
|
alphaLoc = GL_GetUniformLocation (&r_world_program, "Alpha");
|
|
|
|
GL_UseProgramFunc (r_world_program);
|
|
GL_Uniform1iFunc (texLoc, 0);
|
|
GL_Uniform1iFunc (LMTexLoc, 1);
|
|
GL_Uniform1iFunc (fullbrightTexLoc, 2);
|
|
GL_UseProgramFunc (0);
|
|
}
|
|
|
|
GLWater_CreateShaders();
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_DrawTextureChains_GLSL -- ericw
|
|
|
|
Draw lightmapped surfaces with fulbrights in one pass, using VBO.
|
|
Requires 3 TMUs, OpenGL 2.0
|
|
================
|
|
*/
|
|
void R_DrawTextureChains_GLSL (qmodel_t *model, entity_t *ent, texchain_t chain)
|
|
{
|
|
int i;
|
|
msurface_t *s;
|
|
texture_t *t;
|
|
//qboolean bound; //removed this cos it was pointless anyway
|
|
int lastlightmap;
|
|
gltexture_t *fullbright = NULL;
|
|
float entalpha;
|
|
unsigned int enteffects;
|
|
|
|
entalpha = (ent != NULL) ? ENTALPHA_DECODE(ent->alpha) : 1.0f;
|
|
enteffects = (ent != NULL) ? ent->effects : 0;
|
|
|
|
// enable blending / disable depth writes
|
|
if (enteffects & EF_ADDITIVE)
|
|
{
|
|
glDepthMask (GL_FALSE);
|
|
glBlendFunc (GL_SRC_ALPHA, GL_ONE);
|
|
glEnable (GL_BLEND);
|
|
}
|
|
else if (entalpha < 1)
|
|
{
|
|
glDepthMask (GL_FALSE);
|
|
glEnable (GL_BLEND);
|
|
}
|
|
|
|
GL_UseProgramFunc (r_world_program);
|
|
|
|
// Bind the buffers
|
|
GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0); // indices come from client memory!
|
|
|
|
GL_EnableVertexAttribArrayFunc (vertAttrIndex);
|
|
GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex);
|
|
GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
|
|
GL_VertexAttribPointerFunc (vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0));
|
|
GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 3);
|
|
GL_VertexAttribPointerFunc (LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 5);
|
|
|
|
// set uniforms
|
|
GL_Uniform1iFunc (texLoc, 0);
|
|
GL_Uniform1iFunc (LMTexLoc, 1);
|
|
GL_Uniform1iFunc (fullbrightTexLoc, 2);
|
|
GL_Uniform1iFunc (useFullbrightTexLoc, 0);
|
|
GL_Uniform1iFunc (useOverbrightLoc, (int)gl_overbright.value);
|
|
GL_Uniform1iFunc (useAlphaTestLoc, 0);
|
|
GL_Uniform1fFunc (alphaLoc, entalpha);
|
|
|
|
for (i=0 ; i<model->numtextures ; i++)
|
|
{
|
|
t = model->textures[i];
|
|
|
|
if (!t || !t->texturechains[chain] || t->texturechains[chain]->flags & (SURF_DRAWTURB | SURF_DRAWTILED | SURF_NOTEXTURE))
|
|
continue;
|
|
|
|
// Enable/disable TMU 2 (fullbrights)
|
|
// FIXME: Move below to where we bind GL_TEXTURE0
|
|
if (gl_fullbrights.value && (fullbright = R_TextureAnimation(t, ent != NULL ? ent->frame : 0)->fullbright))
|
|
{
|
|
GL_SelectTexture (GL_TEXTURE2);
|
|
GL_Bind (fullbright);
|
|
GL_Uniform1iFunc (useFullbrightTexLoc, 1);
|
|
}
|
|
else
|
|
GL_Uniform1iFunc (useFullbrightTexLoc, 0);
|
|
|
|
R_ClearBatch ();
|
|
|
|
//bind the appropriate diffuse
|
|
GL_SelectTexture (GL_TEXTURE0);
|
|
GL_Bind ((R_TextureAnimation(t, ent != NULL ? ent->frame : 0))->gltexture);
|
|
if (t->texturechains[chain]->flags & SURF_DRAWFENCE)
|
|
GL_Uniform1iFunc (useAlphaTestLoc, 1); // Flip alpha test back on
|
|
|
|
GL_SelectTexture (GL_TEXTURE1);
|
|
lastlightmap = -1; //we're checking anyway, so w/e
|
|
for (s = t->texturechains[chain]; s; s = s->texturechain)
|
|
{
|
|
if (s->lightmaptexturenum != lastlightmap)
|
|
{ //lightmap changed... flush and bind. FIXME: this ain't sorted... FIXME: use a texture2DArray
|
|
R_FlushBatch ();
|
|
GL_Bind (lightmaps[s->lightmaptexturenum].texture);
|
|
lastlightmap = s->lightmaptexturenum;
|
|
}
|
|
|
|
R_BatchSurface (s);
|
|
rs_brushpasses++;
|
|
}
|
|
|
|
R_FlushBatch ();
|
|
|
|
if (t->texturechains[chain]->flags & SURF_DRAWFENCE)
|
|
GL_Uniform1iFunc (useAlphaTestLoc, 0); // Flip alpha test back off
|
|
}
|
|
|
|
// clean up
|
|
GL_DisableVertexAttribArrayFunc (vertAttrIndex);
|
|
GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex);
|
|
GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
|
|
GL_UseProgramFunc (0);
|
|
GL_SelectTexture (GL_TEXTURE0);
|
|
|
|
if (enteffects & EF_ADDITIVE)
|
|
{
|
|
glDepthMask (GL_TRUE);
|
|
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //our normal alpha setting.
|
|
glDisable (GL_BLEND);
|
|
}
|
|
else if (entalpha < 1)
|
|
{
|
|
glDepthMask (GL_TRUE);
|
|
glDisable (GL_BLEND);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_DrawWorld -- johnfitz -- rewritten
|
|
=============
|
|
*/
|
|
void R_DrawTextureChains (qmodel_t *model, entity_t *ent, texchain_t chain)
|
|
{
|
|
float entalpha;
|
|
|
|
if (ent != NULL)
|
|
entalpha = ENTALPHA_DECODE(ent->alpha);
|
|
else
|
|
entalpha = 1;
|
|
|
|
R_UploadLightmaps ();
|
|
|
|
if (r_drawflat_cheatsafe)
|
|
{
|
|
glDisable (GL_TEXTURE_2D);
|
|
R_DrawTextureChains_Drawflat (model, chain);
|
|
glEnable (GL_TEXTURE_2D);
|
|
return;
|
|
}
|
|
|
|
if (r_fullbright_cheatsafe)
|
|
{
|
|
R_BeginTransparentDrawing (entalpha);
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
R_EndTransparentDrawing (entalpha);
|
|
goto fullbrights;
|
|
}
|
|
|
|
if (r_lightmap_cheatsafe)
|
|
{
|
|
if (!gl_overbright.value)
|
|
{
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glColor3f(0.5, 0.5, 0.5);
|
|
}
|
|
R_DrawLightmapChains ();
|
|
if (!gl_overbright.value)
|
|
{
|
|
glColor3f(1,1,1);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
}
|
|
R_DrawTextureChains_White (model, chain);
|
|
return;
|
|
}
|
|
|
|
R_BeginTransparentDrawing (entalpha);
|
|
|
|
R_DrawTextureChains_NoTexture (model, chain);
|
|
|
|
// OpenGL 2 fast path
|
|
if (r_world_program != 0)
|
|
{
|
|
R_EndTransparentDrawing (entalpha);
|
|
|
|
R_DrawTextureChains_GLSL (model, ent, chain);
|
|
return;
|
|
}
|
|
|
|
if (gl_overbright.value)
|
|
{
|
|
if (gl_texture_env_combine && gl_mtexable) //case 1: texture and lightmap in one pass, overbright using texture combiners
|
|
{
|
|
GL_EnableMultitexture ();
|
|
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);
|
|
GL_DisableMultitexture ();
|
|
R_DrawTextureChains_Multitexture (model, ent, chain);
|
|
GL_EnableMultitexture ();
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_RGB_SCALE_EXT, 1.0f);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
GL_DisableMultitexture ();
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
}
|
|
else if (entalpha < 1) //case 2: can't do multipass if entity has alpha, so just draw the texture
|
|
{
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
}
|
|
else //case 3: texture in one pass, lightmap in second pass using 2x modulation blend func, fog in third pass
|
|
{
|
|
//to make fog work with multipass lightmapping, need to do one pass
|
|
//with no fog, one modulate pass with black fog, and one additive
|
|
//pass with black geometry and normal fog
|
|
Fog_DisableGFog ();
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
Fog_EnableGFog ();
|
|
glDepthMask (GL_FALSE);
|
|
glEnable (GL_BLEND);
|
|
glBlendFunc (GL_DST_COLOR, GL_SRC_COLOR); //2x modulate
|
|
Fog_StartAdditive ();
|
|
R_DrawLightmapChains ();
|
|
Fog_StopAdditive ();
|
|
if (Fog_GetDensity() > 0)
|
|
{
|
|
glBlendFunc(GL_ONE, GL_ONE); //add
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glColor3f(0,0,0);
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
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);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (gl_mtexable) //case 4: texture and lightmap in one pass, regular modulation
|
|
{
|
|
GL_EnableMultitexture ();
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
GL_DisableMultitexture ();
|
|
R_DrawTextureChains_Multitexture (model, ent, chain);
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
|
|
}
|
|
else if (entalpha < 1) //case 5: can't do multipass if entity has alpha, so just draw the texture
|
|
{
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
}
|
|
else //case 6: texture in one pass, lightmap in a second pass, fog in third pass
|
|
{
|
|
//to make fog work with multipass lightmapping, need to do one pass
|
|
//with no fog, one modulate pass with black fog, and one additive
|
|
//pass with black geometry and normal fog
|
|
Fog_DisableGFog ();
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
Fog_EnableGFog ();
|
|
glDepthMask (GL_FALSE);
|
|
glEnable (GL_BLEND);
|
|
glBlendFunc(GL_ZERO, GL_SRC_COLOR); //modulate
|
|
Fog_StartAdditive ();
|
|
R_DrawLightmapChains ();
|
|
Fog_StopAdditive ();
|
|
if (Fog_GetDensity() > 0)
|
|
{
|
|
glBlendFunc(GL_ONE, GL_ONE); //add
|
|
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
|
|
glColor3f(0,0,0);
|
|
R_DrawTextureChains_TextureOnly (model, ent, chain);
|
|
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);
|
|
}
|
|
}
|
|
|
|
R_EndTransparentDrawing (entalpha);
|
|
|
|
fullbrights:
|
|
if (gl_fullbrights.value)
|
|
{
|
|
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);
|
|
Fog_StartAdditive ();
|
|
R_DrawTextureChains_Glow (model, ent, chain);
|
|
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);
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_DrawWorld -- ericw -- moved from R_DrawTextureChains, which is no longer specific to the world.
|
|
=============
|
|
*/
|
|
void R_DrawWorld (void)
|
|
{
|
|
if (!r_drawworld_cheatsafe)
|
|
return;
|
|
|
|
R_DrawTextureChains (cl.worldmodel, NULL, chain_world);
|
|
#ifndef SDL_THREADS_DISABLED
|
|
RSceneCache_Draw(false);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_DrawWorld_Water -- ericw -- moved from R_DrawTextureChains_Water, which is no longer specific to the world.
|
|
=============
|
|
*/
|
|
void R_DrawWorld_Water (void)
|
|
{
|
|
if (!r_drawworld_cheatsafe)
|
|
return;
|
|
|
|
R_DrawTextureChains_Water (cl.worldmodel, NULL, chain_world);
|
|
#ifndef SDL_THREADS_DISABLED
|
|
RSceneCache_Draw(true);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=============
|
|
R_DrawWorld_ShowTris -- ericw -- moved from R_DrawTextureChains_ShowTris, which is no longer specific to the world.
|
|
=============
|
|
*/
|
|
void R_DrawWorld_ShowTris (void)
|
|
{
|
|
if (!r_drawworld_cheatsafe)
|
|
return;
|
|
|
|
R_DrawTextureChains_ShowTris (cl.worldmodel, chain_world);
|
|
}
|
|
|
|
|
|
|
|
#ifndef SDL_THREADS_DISABLED
|
|
/*
|
|
================
|
|
Scenecache stuff -- spike
|
|
Uses a worker thread to build an index buffer that can be thrown at the gpu.
|
|
Ignores frustum checks - the gpu can generally cull this faster than the main thread anyway.
|
|
Forces fatpvs on, to invisible walls popin/stutter.
|
|
Doesn't walk any leafs (per-frame), so can't use efrags. We instead just do a pvs check on each individually (should at least avoid poisoning the cache).
|
|
|
|
================
|
|
*/
|
|
static struct
|
|
{ //I'm tagging things as commented-volatile to mark the things that we depend upon before the sdl lock/unlock/wait calls.
|
|
SDL_Thread *thread;
|
|
SDL_mutex *mutex;
|
|
SDL_cond *wt_cond;
|
|
SDL_cond *rt_cond;
|
|
|
|
/*volatile*/ qboolean die;
|
|
/*volatile*/ struct rscenecache_s *processing;
|
|
/*volatile*/ qboolean processed; //lightmaps need updating
|
|
|
|
struct rscenecache_s *drawing;
|
|
qboolean doingskybox;
|
|
|
|
struct rscenecache_s
|
|
{
|
|
struct rscenecache_s *next;
|
|
|
|
vec3_t pos;
|
|
int hostframe; //forget them if they get too old.
|
|
qmodel_t *worldmodel;
|
|
byte *pvs;
|
|
|
|
byte *cachedsubmodels; //one bit for each.
|
|
unsigned int numcachedsubmodels;
|
|
|
|
unsigned int brushpolys;
|
|
unsigned int lightmaps;
|
|
unsigned int numtextures;
|
|
|
|
/*volatile*/ enum
|
|
{
|
|
SCS_BUILDING,
|
|
SCS_COMPUTED,
|
|
SCS_FINISHED, //has an ebo.
|
|
SCS_DISCARDED,
|
|
} status;
|
|
GLuint ebo;
|
|
dlight_t dlights[countof(cl_dlights)]; //added this here so the cache at least gets consistent lighting without having to fight the main thread.
|
|
double time; //for killing old lights...
|
|
struct rscenecachebath_s
|
|
{
|
|
unsigned int *idx;
|
|
unsigned int *eboidx;
|
|
size_t numidx;
|
|
size_t maxidx;
|
|
} batches[1]; //one per texturelm...
|
|
} *cache; //remember a few, for skyrooms or multiple-csqc-renderscenes etc. we need at least two - previous and pending
|
|
} rscenecache;
|
|
byte *skipsubmodels;
|
|
|
|
|
|
static void RSceneCache_RenderDynamicLightmaps (struct rscenecache_s *cache, msurface_t *fa, int dlightframecount)
|
|
{
|
|
static entity_t r_worldentity; //so the dlight stuff doesn't bug out.
|
|
byte *base;
|
|
int maps;
|
|
glRect_t *theRect;
|
|
int smax, tmax;
|
|
|
|
if (fa->flags & SURF_DRAWTILED) //johnfitz -- not a lightmapped surface
|
|
return;
|
|
|
|
// check for lightmap modification
|
|
for (maps=0; maps < MAXLIGHTMAPS && fa->styles[maps] != INVALID_LIGHTSTYLE; maps++)
|
|
if (d_lightstylevalue[fa->styles[maps]] != fa->cached_light[maps])
|
|
goto dynamic;
|
|
|
|
if (fa->dlightframe == dlightframecount // dynamic this frame
|
|
|| fa->cached_dlight) // dynamic previously
|
|
{
|
|
dynamic:
|
|
if (r_dynamic.value)
|
|
{
|
|
struct lightmap_s *lm = &lightmaps[fa->lightmaptexturenum];
|
|
lm->modified = true;
|
|
theRect = &lm->rectchange;
|
|
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]+1;
|
|
tmax = fa->extents[1]+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 = lm->pbodata;
|
|
base += fa->light_t * LMBLOCK_WIDTH * lightmap_bytes + fa->light_s * lightmap_bytes;
|
|
R_BuildLightMap (cache->worldmodel, fa, base, LMBLOCK_WIDTH*lightmap_bytes, &r_worldentity, dlightframecount, cache->dlights);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int RSceneCache_Thread(void *ctx)
|
|
{
|
|
int i, j, e;
|
|
mleaf_t *leaf;
|
|
msurface_t **mark, *surf;
|
|
struct rscenecache_s *cache;
|
|
byte *vis;
|
|
unsigned int bpolys;
|
|
unsigned int clusters;
|
|
unsigned int *idx;
|
|
size_t numidx;
|
|
struct rscenecachebath_s *batch;
|
|
mmodel_t *sub;
|
|
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
SDL_CondSignal(rscenecache.rt_cond); //wake the parent thread. its waiting for us.
|
|
while (!rscenecache.die)
|
|
{
|
|
if (!rscenecache.processing) //might have been posted+signaled to us while we were busy on the last one.
|
|
SDL_CondWait(rscenecache.wt_cond, rscenecache.mutex);
|
|
cache = rscenecache.processing;
|
|
rscenecache.processing = NULL; //accepted!
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
if (cache)
|
|
{
|
|
int visframecount = r_visframecount;
|
|
int dlightframecount = r_framecount;
|
|
|
|
if (!gl_flashblend.value)
|
|
for (j = 0; j < countof(cache->dlights); j++)
|
|
{
|
|
if ((cache->dlights[j].die < cache->time) ||
|
|
(!cache->dlights[j].radius))
|
|
continue;
|
|
//FIXME: no model context passed
|
|
R_MarkLights (&cache->dlights[j], cache->dlights[j].origin, dlightframecount, j, cache->worldmodel->nodes);
|
|
}
|
|
|
|
bpolys = 0;
|
|
vis = cache->pvs;
|
|
leaf = &cache->worldmodel->leafs[1];
|
|
clusters = cache->worldmodel->numleafs;
|
|
for (i=0 ; i<clusters ; i++, leaf++)
|
|
{
|
|
if (vis[i>>3] & (1<<(i&7)))
|
|
{
|
|
if (leaf->contents != CONTENTS_SKY || r_oldskyleaf.value)
|
|
for (j=0, mark = leaf->firstmarksurface; j<leaf->nummarksurfaces; j++, mark++)
|
|
{
|
|
surf = *mark;
|
|
if (surf->visframe != visframecount)
|
|
{
|
|
surf->visframe = visframecount;
|
|
|
|
bpolys++;
|
|
if (surf->numedges < 3)
|
|
continue; //ignore any buggy degenerate ones.
|
|
if ((unsigned)(surf->lightmaptexturenum+1) >= cache->lightmaps)
|
|
continue; //wtf
|
|
if (surf->texinfo->materialidx >= cache->numtextures)
|
|
continue; //should have been sanitised at load.
|
|
numidx = (surf->numedges-2)*3;
|
|
batch = &cache->batches[surf->texinfo->materialidx*cache->lightmaps + 1+surf->lightmaptexturenum];
|
|
if (batch->numidx+numidx > batch->maxidx)
|
|
{
|
|
batch->maxidx = batch->numidx+numidx + 4096; //overestimate, because why not
|
|
batch->idx = realloc(batch->idx, sizeof(*batch->idx)*batch->maxidx);
|
|
}
|
|
idx = &batch->idx[batch->numidx];
|
|
batch->numidx += numidx;
|
|
for (e = 2; e < surf->numedges; e++)
|
|
{
|
|
*idx++ = surf->vbo_firstvert;
|
|
*idx++ = surf->vbo_firstvert + e-1;
|
|
*idx++ = surf->vbo_firstvert + e;
|
|
}
|
|
|
|
RSceneCache_RenderDynamicLightmaps(cache, surf, dlightframecount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < cache->numcachedsubmodels; i++)
|
|
{
|
|
if (!(cache->cachedsubmodels[i>>3]&(1u<<(i&7))))
|
|
continue; //not needed.
|
|
sub = &cache->worldmodel->submodels[i];
|
|
|
|
if (!gl_flashblend.value)
|
|
for (j = 0; j < countof(cache->dlights); j++)
|
|
{
|
|
if ((cache->dlights[j].die < cache->time) ||
|
|
(!cache->dlights[j].radius))
|
|
continue;
|
|
//FIXME: no model context passed
|
|
R_MarkLights (&cache->dlights[j], cache->dlights[j].origin, dlightframecount, j, cache->worldmodel->nodes + sub->headnode[0]);
|
|
}
|
|
|
|
//FIXME: these should really use MultiDrawIndirect, so we can add/remove them more cheaply.
|
|
for (j=0, surf = cache->worldmodel->surfaces+sub->firstface; j<sub->numfaces; j++, surf++)
|
|
{ //don't bother with visframe checks here. a) we shouldn't be getting dupes anyway. b) we don't want to trip up the regular rendering if its rendering a moving copy while we're generating a new cache.
|
|
bpolys++;
|
|
if (surf->numedges < 3)
|
|
continue; //ignore any buggy degenerate ones.
|
|
if ((unsigned)(surf->lightmaptexturenum+1) >= cache->lightmaps)
|
|
continue; //wtf
|
|
if (surf->texinfo->materialidx >= cache->numtextures)
|
|
continue; //should have been sanitised at load.
|
|
numidx = (surf->numedges-2)*3;
|
|
batch = &cache->batches[surf->texinfo->materialidx*cache->lightmaps + 1+surf->lightmaptexturenum];
|
|
if (batch->numidx+numidx > batch->maxidx)
|
|
{
|
|
batch->maxidx = batch->numidx+numidx + 4096; //overestimate, because why not
|
|
batch->idx = realloc(batch->idx, sizeof(*batch->idx)*batch->maxidx);
|
|
}
|
|
idx = &batch->idx[batch->numidx];
|
|
batch->numidx += numidx;
|
|
for (e = 2; e < surf->numedges; e++)
|
|
{
|
|
*idx++ = surf->vbo_firstvert;
|
|
*idx++ = surf->vbo_firstvert + e-1;
|
|
*idx++ = surf->vbo_firstvert + e;
|
|
}
|
|
|
|
RSceneCache_RenderDynamicLightmaps(cache, surf, dlightframecount);
|
|
}
|
|
}
|
|
|
|
cache->brushpolys = bpolys;
|
|
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
rscenecache.processed = true;
|
|
cache->status = SCS_COMPUTED;
|
|
SDL_CondSignal(rscenecache.rt_cond);
|
|
}
|
|
else
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
}
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
return 0;
|
|
}
|
|
static qboolean RSceneCache_Queue(byte *vis)
|
|
{
|
|
extern GLuint gl_bmodel_vbo;
|
|
// int type = 0;
|
|
struct rscenecache_s *cache, *best = NULL, *building;
|
|
float bdist=FLT_MAX, d; //bdist should match fatpvs size, so we don't have invisible walls.
|
|
vec3_t offset;
|
|
unsigned int rowbytes = (cl.worldmodel->numleafs+7)>>3;
|
|
int e;
|
|
static int settingconflict;
|
|
|
|
static int old_lightstylevalue[countof(d_lightstylevalue)];
|
|
byte *bakesubmodels;
|
|
|
|
skipsubmodels = NULL;
|
|
rscenecache.drawing = NULL; //still need to figure out which cache to use.
|
|
if (!*r_scenecache.string)
|
|
r_scenecache.value = 1; //consistency with FTE's 'auto' seting.
|
|
if (!r_scenecache.value)
|
|
{
|
|
settingconflict = -1;
|
|
|
|
if (rscenecache.thread)
|
|
RSceneCache_Shutdown();
|
|
return false;
|
|
}
|
|
else if (r_fullbright_cheatsafe || r_lightmap_cheatsafe || r_drawflat_cheatsafe)
|
|
{ //r_drawflat cannot possibly work with this. we do not track how many tris there were per surface so you'd be colouring tris rather than surfs, but maybe that's whats actually important... anyway, debug features don't need to be fast. NOTE: QuakeWorld engines have a different interpretation of drawflat - showing block colours based on surface angles, which could be done via glsl, but its not really an nq/qs thing so just use the legacy path.
|
|
//r_fullbright could just use a white texture, or glsl, but its ugly and doesn't deserve to be fast!..
|
|
//r_lightmap would want to force the glsl, could be generic, but its a debug feature that we don't really care about.
|
|
if (settingconflict!=true)
|
|
settingconflict=true, Con_Printf("r_scenecache: Disabling due to conflicting settings\n");
|
|
|
|
if (rscenecache.thread)
|
|
RSceneCache_Shutdown();
|
|
return false;
|
|
}
|
|
//Note: r_dynamic is meant to work, but doesn't update as fast as you'd like (eg dlights).
|
|
else if (settingconflict!=false)
|
|
settingconflict=false, Con_DPrintf("r_scenecache: Enabled\n");
|
|
|
|
//we're not walking leafs here, so we need to handle static ents specially. and before the following loop...
|
|
for (e = 0; e < cl.num_statics; e++)
|
|
{
|
|
struct cl_static_entities_s *test = &cl.static_entities[e];
|
|
entity_t *pent = test->ent;
|
|
int i;
|
|
|
|
if (pent->model && cl_numvisedicts < cl_maxvisedicts)
|
|
{
|
|
if (test->num_clusters<=MAX_ENT_LEAFS)
|
|
{
|
|
for (i=0 ; i < test->num_clusters ; i++)
|
|
if (vis[test->clusternums[i] >> 3] & (1 << (test->clusternums[i]&7) ))
|
|
break;
|
|
if (i == test->num_clusters)
|
|
continue; //not visible.
|
|
}//else too many clusters, we were not tracking this ent properly. assume its visible and hope frustum checks later will stop it... they ARE frustum checked, right?
|
|
|
|
if (R_CullBox(test->absmin, test->absmax))
|
|
continue;
|
|
|
|
#ifdef PSET_SCRIPT
|
|
if (pent->netstate.emiteffectnum > 0)
|
|
{
|
|
float t = cl.time-cl.oldtime;
|
|
vec3_t axis[3];
|
|
if (t < 0) t = 0; else if (t > 0.1) t= 0.1;
|
|
AngleVectors(pent->angles, axis[0], axis[1], axis[2]);
|
|
if (pent->model->type == mod_alias)
|
|
axis[0][2] *= -1; //stupid vanilla bug
|
|
PScript_RunParticleEffectState(pent->origin, axis[0], t, cl.particle_precache[pent->netstate.emiteffectnum].index, &pent->emitstate);
|
|
}
|
|
else if (pent->model->emiteffect >= 0)
|
|
{
|
|
float t = cl.time-cl.oldtime;
|
|
vec3_t axis[3];
|
|
if (t < 0) t = 0; else if (t > 0.1) t= 0.1;
|
|
AngleVectors(pent->angles, axis[0], axis[1], axis[2]);
|
|
if (pent->model->flags & MOD_EMITFORWARDS)
|
|
{
|
|
if (pent->model->type == mod_alias)
|
|
axis[0][2] *= -1; //stupid vanilla bug
|
|
}
|
|
else
|
|
VectorScale(axis[2], -1, axis[0]);
|
|
PScript_RunParticleEffectState(pent->origin, axis[0], t, pent->model->emiteffect, &pent->emitstate);
|
|
if (pent->model->flags & MOD_EMITREPLACE)
|
|
continue;
|
|
}
|
|
#endif
|
|
cl_visedicts[cl_numvisedicts++] = pent;
|
|
}
|
|
}
|
|
|
|
//okay, now figure out which bmodels we can bake into the cache
|
|
bakesubmodels = alloca((cl.worldmodel->numsubmodels+7)>>3);
|
|
memset(bakesubmodels, 0, (cl.worldmodel->numsubmodels+7)>>3);
|
|
if (r_scenecache.value != 2 && r_drawentities.value)
|
|
for (e = 0; e < cl_numvisedicts; e++)
|
|
{
|
|
entity_t *ent = cl_visedicts[e];
|
|
size_t m;
|
|
if (!ent->model || ent->model->submodelof != cl.worldmodel || //we only want submodels of the world here.
|
|
ent->origin[0]||ent->origin[1]||ent->origin[2] || //can only bake them if they're in the identity position. :(
|
|
ent->angles[0]||ent->angles[1]||ent->angles[2] || //and not rotated
|
|
(ent->eflags&EFLAGS_VIEWMODEL) || //viewmodel etc screws with origins.
|
|
ent->frame || //don't bother tracking toggled textures here.
|
|
ent->alpha!=0 || //transparent stuff would need extra batches, which gets awkward and misordered.
|
|
ent->effects) //weird stuff like EF_ADDITIVE/EF_FULLBRIGHT. probably not used on submodels anyway.
|
|
continue; //nope, can't bake it.
|
|
//okay, we want to bake this one.
|
|
m = ent->model->submodelidx;
|
|
bakesubmodels[m>>3] |= (1u<<(m&7));
|
|
}
|
|
|
|
for (building = NULL, cache = rscenecache.cache; cache; cache = cache->next)
|
|
{
|
|
if (cache->worldmodel != cl.worldmodel)
|
|
{ //this cache is completely unsuitable.
|
|
if (cache->status == SCS_BUILDING)
|
|
building = cache;
|
|
continue;
|
|
}
|
|
|
|
if (!memcmp(cache->pvs, vis, rowbytes))
|
|
{ //pvs matches. yay. we *could* check leaf, but that wouldn't handle detail brushes properly.
|
|
VectorCopy(r_origin, cache->pos); //might as well keep its origin updated, so we don't block needlessly, but only when its actually valid.
|
|
if (cache->status == SCS_BUILDING)
|
|
{ //its perfect so there's no point building it, but we still can't use it yet, so keep looking for one we CAN use.
|
|
building = cache;
|
|
if (!best)
|
|
best = cache;
|
|
continue;
|
|
}
|
|
else
|
|
{ //we're in the right leaf, so yay?
|
|
if (!memcmp(cache->cachedsubmodels, bakesubmodels, (cl.worldmodel->numsubmodels+7)>>3))
|
|
{ //this one's perfect.
|
|
best = cache;
|
|
bdist = 0;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (bdist > 100)
|
|
{
|
|
best = cache;
|
|
bdist = 100;
|
|
}
|
|
continue; //might have one with the correct submodels...
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cache->status == SCS_BUILDING)
|
|
{
|
|
building = cache;
|
|
continue; //can't be better if we're not able to use it yet... we'll block building a new one though.
|
|
}
|
|
|
|
VectorSubtract(r_origin, cache->pos, offset);
|
|
d = DotProduct(offset,offset);
|
|
if (memcmp(cache->cachedsubmodels, bakesubmodels, (cl.worldmodel->numsubmodels+7)>>3))
|
|
d += 100;
|
|
if (d < bdist)
|
|
bdist = d, best = cache;
|
|
}
|
|
|
|
//check if there's one building already (don't want to queue too many)
|
|
if (!building && best)
|
|
for (building = best; building && building->status != SCS_BUILDING; building = building->next)
|
|
;
|
|
|
|
if (!r_dynamic.value)
|
|
old_lightstylevalue[0] = INT_MIN; //something that'll force a regen pretty soon...
|
|
else
|
|
{
|
|
if (!building)
|
|
{ //if the lighting is changing then keep rebuilding,
|
|
if (memcmp(old_lightstylevalue, d_lightstylevalue, sizeof(old_lightstylevalue))) //FIXME: only check lightstyles that are actually used! deathmatch maps can save the resulting expense.
|
|
{
|
|
old_lightstylevalue[0] = INT_MIN; //something that'll force a regen pretty soon...
|
|
cache = NULL; //make sure its rebuilt (can still use the best while it computes).
|
|
}
|
|
else if (r_dynamic.value)
|
|
{ //check if there's active dlights. FIXME: they may take a little longer to disappear
|
|
dlight_t *l = cl_dlights;
|
|
size_t i;
|
|
|
|
for (i=0 ; i<MAX_DLIGHTS ; i++, l++)
|
|
{
|
|
if (l->die < cl.time || !l->radius)
|
|
continue;
|
|
cache = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!best || (!cache && !building))
|
|
{ //no perfect matches. build a new one.
|
|
struct rscenecache_s *oldest = NULL;
|
|
unsigned int oldestage = 3, a;
|
|
|
|
memcpy(old_lightstylevalue, d_lightstylevalue, sizeof(old_lightstylevalue));
|
|
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
if(rscenecache.processing)
|
|
{ //we already had one queued? don't wait for TWO frames!
|
|
rscenecache.processing->status = SCS_DISCARDED;
|
|
rscenecache.processing = NULL;
|
|
}
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
|
|
for (cache = rscenecache.cache; cache; cache = cache->next)
|
|
{
|
|
if (cache->status == SCS_BUILDING || //worker still has it.
|
|
cache == best) //we're falling back on it...
|
|
continue;
|
|
|
|
if (cache->lightmaps != lightmap_count+1 ||
|
|
cache->numtextures != cl.worldmodel->numtextures)
|
|
continue; //allocation sizes changed...
|
|
|
|
if (cache->status == SCS_DISCARDED)
|
|
{ //this one is fine.
|
|
oldest = cache;
|
|
break;
|
|
}
|
|
|
|
a = host_framecount-cache->hostframe; //keep it current
|
|
if (a >= oldestage)
|
|
a = oldestage, oldest = cache;
|
|
}
|
|
|
|
if (oldest)
|
|
{ //we found an old one, yay us.
|
|
struct rscenecache_s **link;
|
|
cache = oldest;
|
|
for (link = &rscenecache.cache; *link; )
|
|
{
|
|
if (*link == cache)
|
|
{
|
|
*link = cache->next; //unlink it...
|
|
cache->next = rscenecache.cache; //and relink at head so its favoured.
|
|
rscenecache.cache = cache;
|
|
break;
|
|
}
|
|
link = &(*link)->next;
|
|
}
|
|
|
|
for (e = 0; e < cache->numtextures*cache->lightmaps; e++)
|
|
cache->batches[e].numidx = 0;
|
|
}
|
|
else
|
|
{ //allocate some new memory for it.
|
|
cache = calloc(1,
|
|
sizeof(*cache)-sizeof(cache->batches) + //base structure
|
|
sizeof(*cache->batches)*cl.worldmodel->numtextures*(lightmap_count+1) + //trailing batch count...
|
|
rowbytes + //pvs info thrown onto the end of the allocation because why not.
|
|
((cl.worldmodel->numsubmodels+7)>>3));
|
|
//link it, cos we might as well.
|
|
cache->next = rscenecache.cache;
|
|
rscenecache.cache = cache;
|
|
|
|
cache->lightmaps = lightmap_count+1; //FIXME use texture arrays for the lightmaps, keep this at 2.
|
|
cache->numtextures = cl.worldmodel->numtextures; //FIXME: merge textures into same-dimensions arrays
|
|
cache->pvs = (byte*)&cache->batches[cache->numtextures*cache->lightmaps];
|
|
cache->worldmodel = cl.worldmodel;
|
|
cache->cachedsubmodels = cache->pvs + rowbytes;
|
|
cache->numcachedsubmodels = cl.worldmodel->numsubmodels;
|
|
}
|
|
|
|
cache->status = SCS_BUILDING;
|
|
VectorCopy(r_origin, cache->pos); //might as well overwrite its origin
|
|
cache->hostframe = host_framecount;
|
|
memcpy(cache->pvs, vis, rowbytes);
|
|
memcpy(cache->cachedsubmodels, bakesubmodels, ((cl.worldmodel->numsubmodels+7)>>3));
|
|
memcpy(cache->dlights, cl_dlights, sizeof(cache->dlights));
|
|
cache->time = cl.time;
|
|
|
|
//create the worker if it doesn't exist...
|
|
if (!rscenecache.thread)
|
|
{
|
|
rscenecache.die = false; //just in case...
|
|
rscenecache.mutex = SDL_CreateMutex();
|
|
rscenecache.wt_cond = SDL_CreateCond();
|
|
rscenecache.rt_cond = SDL_CreateCond();
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
rscenecache.thread = SDL_CreateThread(RSceneCache_Thread, "scenecache", NULL);
|
|
if (!rscenecache.thread)
|
|
{
|
|
r_scenecache.value = 0; //force it off...
|
|
RSceneCache_Shutdown();
|
|
return false;
|
|
}
|
|
SDL_CondWait(rscenecache.rt_cond, rscenecache.mutex);
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
//the thread is now at a known position.
|
|
}
|
|
|
|
//get the worker to start processing it
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
//oh noes! its processing something else and we have no other queue!
|
|
while(rscenecache.processing)
|
|
{
|
|
// double t = Sys_DoubleTime();
|
|
SDL_CondWait(rscenecache.rt_cond, rscenecache.mutex);
|
|
// t = Sys_DoubleTime()-t;
|
|
// Con_Printf("Scenecache prewait (%f)\n", t*1000);
|
|
}
|
|
rscenecache.processing = cache;
|
|
SDL_CondSignal(rscenecache.wt_cond);
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
}
|
|
if (best)
|
|
{
|
|
// if (best->status == SCS_BUILDING)
|
|
// Con_Printf("Scenecache is gonna wait\n");
|
|
cache = best;
|
|
}
|
|
if (!cache)
|
|
{ //this should be unreachable...
|
|
if (rscenecache.thread)
|
|
RSceneCache_Shutdown();
|
|
}
|
|
else
|
|
cache->hostframe = host_framecount; //keep it current
|
|
|
|
rscenecache.drawing = cache;
|
|
rscenecache.doingskybox = false;
|
|
|
|
return !!cache;
|
|
}
|
|
static void RSceneCache_Uncache(struct rscenecache_s *cache)
|
|
{
|
|
size_t i;
|
|
if (cache->status == SCS_BUILDING)
|
|
{
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
while(cache->status == SCS_BUILDING) //thread still has it...
|
|
SDL_CondWait(rscenecache.rt_cond, rscenecache.mutex);
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
}
|
|
if (rscenecache.drawing == cache)
|
|
rscenecache.drawing = NULL;
|
|
for (i = 0; i < cache->numtextures*cache->lightmaps; i++)
|
|
if (cache->batches[i].idx)
|
|
free(cache->batches[i].idx);
|
|
GL_DeleteBuffersFunc(1, &cache->ebo);
|
|
free(cache);
|
|
}
|
|
void RSceneCache_Cleanup(qmodel_t *mod)
|
|
{
|
|
struct rscenecache_s **link, *cache;
|
|
for (link = &rscenecache.cache; (cache=*link); )
|
|
{
|
|
if (cache->worldmodel == mod)
|
|
{
|
|
*link = cache->next;
|
|
RSceneCache_Uncache(cache);
|
|
}
|
|
else
|
|
link = &cache->next;
|
|
}
|
|
}
|
|
static void RSceneCache_Finish(struct rscenecache_s *cache)
|
|
{
|
|
#define USEMAPBUFFER
|
|
int i;
|
|
size_t numidx;
|
|
#ifdef USEMAPBUFFER
|
|
byte *ebomem = NULL;
|
|
#endif
|
|
switch(cache->status)
|
|
{
|
|
case SCS_BUILDING:
|
|
//worker is still computing it... block while waiting for it.
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
while(cache->status == SCS_BUILDING)
|
|
{
|
|
// double t = Sys_DoubleTime();
|
|
SDL_CondWait(rscenecache.rt_cond, rscenecache.mutex);
|
|
// t = Sys_DoubleTime()-t;
|
|
// Con_Printf("Scenecache postwait (%f)\n", t*1000);
|
|
}
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
//fallthrough
|
|
case SCS_COMPUTED:
|
|
//worker thread finished, but GL threading issues mean it didn't build our EBO (which can be a significant boost)
|
|
if (gl_vbo_able)
|
|
{
|
|
for (i = 0, numidx = 0; i < cache->numtextures*cache->lightmaps; i++)
|
|
numidx += cache->batches[i].numidx;
|
|
|
|
if (!cache->ebo)
|
|
GL_GenBuffersFunc(1, &cache->ebo);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, cache->ebo); // indices come from client memory!
|
|
GL_BufferDataFunc(GL_ELEMENT_ARRAY_BUFFER, numidx*sizeof(unsigned int), NULL, GL_STATIC_DRAW);
|
|
#ifdef USEMAPBUFFER
|
|
ebomem = GL_MapBufferFunc(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
|
|
#endif
|
|
}
|
|
for (i = 0, numidx = 0; i < cache->numtextures*cache->lightmaps; i++)
|
|
{
|
|
if (gl_vbo_able)
|
|
{
|
|
cache->batches[i].eboidx = (unsigned int*)(numidx*sizeof(*cache->batches[i].idx));
|
|
#ifdef USEMAPBUFFER
|
|
memcpy(ebomem+(uintptr_t)cache->batches[i].eboidx, cache->batches[i].idx, cache->batches[i].numidx*sizeof(*cache->batches[i].idx));
|
|
#else
|
|
GL_BufferSubDataFunc(GL_ELEMENT_ARRAY_BUFFER, numidx*sizeof(*cache->batches[i].idx), cache->batches[i].numidx*sizeof(*cache->batches[i].idx), cache->batches[i].idx);
|
|
#endif
|
|
//leave the memory allocated to avoid all the reallocs if it gets reused. the cache will still need freeing later anyway.
|
|
}
|
|
else
|
|
cache->batches[i].eboidx = cache->batches[i].idx; //lame
|
|
numidx += cache->batches[i].numidx;
|
|
}
|
|
#ifdef USEMAPBUFFER
|
|
if (gl_vbo_able)
|
|
GL_UnmapBufferFunc(GL_ELEMENT_ARRAY_BUFFER);
|
|
#endif
|
|
cache->status = SCS_FINISHED;
|
|
|
|
for (i=0, cache = rscenecache.cache; cache; cache = cache->next)
|
|
i++;
|
|
break;
|
|
case SCS_FINISHED:
|
|
case SCS_DISCARDED: //shouldn't be here...
|
|
break;
|
|
}
|
|
|
|
if (rscenecache.processed)
|
|
{ //make sure lightmaps are updated when we can.
|
|
rscenecache.processed = false;
|
|
lightmaps_skipupdates = false;
|
|
R_UploadLightmaps();
|
|
lightmaps_skipupdates = true;
|
|
}
|
|
}
|
|
static void RSceneCache_Draw(qboolean water)
|
|
{
|
|
extern GLuint gl_bmodel_vbo;
|
|
struct rscenecache_s *cache = rscenecache.drawing;
|
|
int i, j;
|
|
texture_t *tex;
|
|
int b;
|
|
int mode;
|
|
int lastprog = -1;
|
|
float alpha = 0;
|
|
|
|
if (!cache)
|
|
{
|
|
skipsubmodels = NULL;
|
|
return;
|
|
}
|
|
RSceneCache_Finish(cache);
|
|
skipsubmodels = cache->cachedsubmodels;
|
|
|
|
glDepthMask(GL_TRUE);
|
|
glDisable (GL_BLEND);
|
|
if (skyroom_drawn)
|
|
{ //draw skies first, so we don't end up drawing overlapping non-skies behind
|
|
glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
|
|
RSceneCache_DrawSkySurfDepth();
|
|
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
|
|
}
|
|
|
|
GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, cache->ebo); // indices come from client memory!
|
|
|
|
GL_EnableVertexAttribArrayFunc (vertAttrIndex);
|
|
GL_EnableVertexAttribArrayFunc (texCoordsAttrIndex);
|
|
GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
|
|
GL_VertexAttribPointerFunc (vertAttrIndex, 3, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0));
|
|
GL_VertexAttribPointerFunc (texCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 3);
|
|
GL_VertexAttribPointerFunc (LMCoordsAttrIndex, 2, GL_FLOAT, GL_FALSE, VERTEXSIZE * sizeof(float), ((float *)0) + 5);
|
|
|
|
rs_brushpolys += cache->brushpolys; //for r_speeds.;
|
|
|
|
for (i = 0; i < cache->numtextures; i++)
|
|
{
|
|
if (!cache->worldmodel->textures[i])
|
|
continue; //stupid buggy shite.
|
|
if ((cache->worldmodel->textures[i]->name[0] == '*') != water)
|
|
continue;
|
|
b = false;
|
|
for (j = 0; j < cache->lightmaps; j++)
|
|
{
|
|
if (!cache->batches[i*cache->lightmaps+j].numidx)
|
|
continue; //don't waste time on it.
|
|
|
|
if (!b)
|
|
{
|
|
b = true;
|
|
tex = R_TextureAnimation (cache->worldmodel->textures[i], 0);
|
|
GL_SelectTexture (GL_TEXTURE0);
|
|
GL_Bind(tex->gltexture);
|
|
|
|
//its annoying how we don't know any surface flags here
|
|
if (*tex->name == '*')
|
|
{
|
|
if (j>0)
|
|
{ //lit
|
|
GL_EnableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
mode = 1;
|
|
}
|
|
else //unlit
|
|
{
|
|
GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
mode = 0;
|
|
}
|
|
|
|
// detect special liquid types. stoopid lack of surface flag info. :(
|
|
if (!strncmp (cache->worldmodel->textures[i]->name+1, "lava", 4))
|
|
alpha = map_lavaalpha > 0 ? map_lavaalpha : map_fallbackalpha;
|
|
else if (!strncmp (cache->worldmodel->textures[i]->name+1, "slime", 5))
|
|
alpha = map_slimealpha > 0 ? map_slimealpha : map_fallbackalpha;
|
|
else if (!strncmp (cache->worldmodel->textures[i]->name+1, "tele", 4))
|
|
alpha = map_telealpha > 0 ? map_telealpha : map_fallbackalpha;
|
|
else
|
|
alpha = map_wateralpha;// > 0 ? map_wateralpha : map_fallbackalpha;
|
|
|
|
if (alpha < 1.0f)
|
|
{
|
|
glDepthMask (GL_FALSE);
|
|
glEnable (GL_BLEND);
|
|
}
|
|
else
|
|
{
|
|
glDepthMask (GL_TRUE);
|
|
glDisable (GL_BLEND);
|
|
}
|
|
|
|
if (lastprog != r_water[mode].program)
|
|
{
|
|
lastprog = r_water[mode].program;
|
|
GL_UseProgramFunc (r_water[mode].program);
|
|
GL_Uniform1fFunc (r_water[mode].time, cl.time);
|
|
if (r_water[mode].light_scale != -1)
|
|
GL_Uniform1fFunc (r_water[mode].light_scale, gl_overbright.value?2:1);
|
|
}
|
|
GL_Uniform1fFunc (r_water[mode].alpha_scale, alpha);
|
|
}
|
|
else if (tex->name[0]=='s'&&tex->name[1]=='k'&&tex->name[2]=='y')
|
|
{
|
|
//sky. because why not.
|
|
extern cvar_t r_skyalpha, r_skyfog, r_fastsky;
|
|
extern float skyflatcolor[3];
|
|
mode = r_fastsky.value?3:2;
|
|
|
|
if (rscenecache.doingskybox)
|
|
break; //we're doing skies weirdly. FIXME: replace with cubemap skies, where possible.
|
|
|
|
GL_SelectTexture (GL_TEXTURE2);
|
|
GL_Bind(tex->fullbright);
|
|
|
|
if (skyroom_drawn)
|
|
continue; //already drew them
|
|
else if (lastprog != r_water[mode].program)
|
|
{
|
|
lastprog = r_water[mode].program;
|
|
GL_UseProgramFunc (r_water[mode].program);
|
|
GL_Uniform1fFunc (r_water[mode].time, cl.time);
|
|
|
|
GL_Uniform1fFunc (r_water[mode].alpha_scale, r_skyalpha.value);
|
|
GL_Uniform3fFunc (r_water[mode].eyepos, r_origin[0], r_origin[1], r_origin[2]);
|
|
GL_Uniform1fFunc (r_water[mode].fogalpha, r_skyfog.value);
|
|
if (r_water[mode].colour != (GLuint)-1)
|
|
GL_Uniform3fFunc (r_water[mode].colour, skyflatcolor[0], skyflatcolor[1], skyflatcolor[2]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (lastprog != r_world_program)
|
|
{
|
|
lastprog = r_world_program;
|
|
GL_UseProgramFunc (r_world_program);
|
|
GL_Uniform1iFunc (useOverbrightLoc, (int)gl_overbright.value);
|
|
GL_Uniform1fFunc (alphaLoc, 1); //worldmodel is never translucent.
|
|
}
|
|
GL_Uniform1iFunc (useAlphaTestLoc, *tex->name == '{'); //update alphatest. some future qbsps might actually support it properly on the worldmodel. plus there's lots of buggy bsps where it was used anyway.
|
|
|
|
if (tex->fullbright && gl_fullbrights.value)
|
|
{
|
|
GL_Uniform1iFunc (useFullbrightTexLoc, 1);
|
|
GL_SelectTexture (GL_TEXTURE2);
|
|
GL_Bind(tex->fullbright);
|
|
}
|
|
else
|
|
{
|
|
GL_Uniform1iFunc (useFullbrightTexLoc, 0);
|
|
//don't bother unbinding. the glsl won't use it anyway.
|
|
}
|
|
}
|
|
}
|
|
|
|
GL_SelectTexture (GL_TEXTURE1);
|
|
if (!j)
|
|
GL_Bind(NULL);
|
|
else
|
|
GL_Bind(lightmaps[j-1].texture);
|
|
|
|
glDrawElements(GL_TRIANGLES, cache->batches[i*cache->lightmaps+j].numidx, GL_UNSIGNED_INT, cache->batches[i*cache->lightmaps+j].eboidx);
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
|
|
if (alpha < 1.0f)
|
|
{ //go back to a known state
|
|
glDepthMask (GL_TRUE);
|
|
glDisable (GL_BLEND);
|
|
}
|
|
|
|
GL_UseProgramFunc (0);
|
|
|
|
GL_DisableVertexAttribArrayFunc (vertAttrIndex);
|
|
GL_DisableVertexAttribArrayFunc (texCoordsAttrIndex);
|
|
GL_DisableVertexAttribArrayFunc (LMCoordsAttrIndex);
|
|
GL_SelectTexture (GL_TEXTURE0);
|
|
|
|
GL_BindBuffer (GL_ARRAY_BUFFER, 0);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
}
|
|
qboolean RSceneCache_HasSky(void)
|
|
{
|
|
struct rscenecache_s *cache = rscenecache.drawing;
|
|
int i;
|
|
texture_t *tex;
|
|
|
|
if (cache)
|
|
{
|
|
for (i = 0; i < cache->numtextures; i++)
|
|
{
|
|
tex = cache->worldmodel->textures[i];
|
|
if (!tex || !(tex->name[0]=='s'&&tex->name[1]=='k'&&tex->name[2]=='y'))
|
|
continue; //we only want sky textures.
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
qboolean RSceneCache_DrawSkySurfDepth(void)
|
|
{ //legacy skyboxes are a serious pain, but oh well...
|
|
//if we draw anything here then its JUST depth values. we don't need glsl nor even textures for this.
|
|
struct rscenecache_s *cache = rscenecache.drawing;
|
|
|
|
extern GLuint gl_bmodel_vbo;
|
|
int i, j;
|
|
texture_t *tex;
|
|
qboolean ret = false;
|
|
|
|
if (!cache)
|
|
return false;
|
|
rscenecache.doingskybox = true;
|
|
|
|
RSceneCache_Finish(cache);
|
|
|
|
for (i = 0; i < cache->numtextures; i++)
|
|
{
|
|
tex = cache->worldmodel->textures[i];
|
|
if (!tex || !(tex->name[0]=='s'&&tex->name[1]=='k'&&tex->name[2]=='y'))
|
|
continue; //we only want sky textures.
|
|
for (j = 0; j < cache->lightmaps; j++)
|
|
{
|
|
if (!cache->batches[i*cache->lightmaps+j].numidx)
|
|
continue; //don't waste time on it.
|
|
|
|
if (!ret)
|
|
{ //first batch of sky, set up the vertex array stuff.
|
|
ret = true;
|
|
GL_BindBuffer (GL_ARRAY_BUFFER, gl_bmodel_vbo);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, cache->ebo); // indices come from client memory!
|
|
|
|
glVertexPointer(3, GL_FLOAT, VERTEXSIZE * sizeof(float), ((float *)0));
|
|
glEnableClientState(GL_VERTEX_ARRAY);
|
|
}
|
|
//then draw it
|
|
glDrawElements(GL_TRIANGLES, cache->batches[i*cache->lightmaps+j].numidx, GL_UNSIGNED_INT, cache->batches[i*cache->lightmaps+j].eboidx);
|
|
rs_brushpasses++;
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
glDisableClientState(GL_VERTEX_ARRAY);
|
|
GL_BindBuffer (GL_ARRAY_BUFFER, 0);
|
|
GL_BindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
|
|
}
|
|
return ret;
|
|
}
|
|
void RSceneCache_Shutdown(void)
|
|
{ //clean up the scene cache stuff.
|
|
struct rscenecache_s *cache;
|
|
while ((cache=rscenecache.cache))
|
|
{
|
|
rscenecache.cache = cache->next;
|
|
RSceneCache_Uncache(cache);
|
|
}
|
|
|
|
if (rscenecache.thread)
|
|
{
|
|
SDL_LockMutex(rscenecache.mutex);
|
|
rscenecache.die = true;
|
|
SDL_CondSignal(rscenecache.wt_cond); //make sure it wakes up so it knows it needs to die.
|
|
SDL_UnlockMutex(rscenecache.mutex);
|
|
|
|
SDL_WaitThread(rscenecache.thread, NULL);
|
|
SDL_DestroyCond(rscenecache.wt_cond);
|
|
SDL_DestroyCond(rscenecache.rt_cond);
|
|
SDL_DestroyMutex(rscenecache.mutex);
|
|
|
|
rscenecache.thread = NULL;
|
|
rscenecache.wt_cond = NULL;
|
|
rscenecache.rt_cond = NULL;
|
|
rscenecache.mutex = NULL;
|
|
}
|
|
rscenecache.drawing = NULL;
|
|
skipsubmodels = NULL;
|
|
}
|
|
#endif
|