OctaCore/src/engine/renderva.cc

2845 lines
85 KiB
C++

// renderva.cpp: handles the occlusion and rendering of vertex arrays
#include "renderva.hh"
#include <climits>
#include <algorithm>
#include <shared/command.hh>
#include <shared/glemu.hh>
#include <shared/igame.hh>
#include "blend.hh"
#include "grass.hh"
#include "main.hh" // renderprogress, timings
#include "octaedit.hh" // editmode
#include "octarender.hh"
#include "pvs.hh"
#include "rendergl.hh"
#include "renderlights.hh"
#include "rendermodel.hh"
#include "rendersky.hh"
#include "texture.hh"
#include "world.hh"
static inline void drawtris(GLsizei numindices, const GLvoid *indices, ushort minvert, ushort maxvert)
{
glDrawRangeElements_(GL_TRIANGLES, minvert, maxvert, numindices, GL_UNSIGNED_SHORT, indices);
glde++;
}
static inline void drawvatris(vtxarray *va, GLsizei numindices, int offset)
{
drawtris(numindices, (ushort *)0 + va->eoffset + offset, va->minvert, va->maxvert);
}
static inline void drawvaskytris(vtxarray *va)
{
drawtris(va->sky, (ushort *)0 + va->skyoffset, va->minvert, va->maxvert);
}
///////// view frustrum culling ///////////////////////
static plane vfcP[5]; // perpindictular vectors to view frustrum bounding planes
static float vfcDfog; // far plane culling distance (fog limit).
static float vfcDnear[5], vfcDfar[5];
vtxarray *visibleva = nullptr;
bool isfoggedsphere(float rad, const vec &cv)
{
loopi(4) if(vfcP[i].dist(cv) < -rad) return true;
float dist = vfcP[4].dist(cv);
return dist < -rad || dist > vfcDfog + rad;
}
int isvisiblesphere(float rad, const vec &cv)
{
int v = VFC_FULL_VISIBLE;
float dist;
loopi(5)
{
dist = vfcP[i].dist(cv);
if(dist < -rad) return VFC_NOT_VISIBLE;
if(dist < rad) v = VFC_PART_VISIBLE;
}
dist -= vfcDfog;
if(dist > rad) return VFC_FOGGED; //VFC_NOT_VISIBLE; // culling when fog is closer than size of world results in HOM
if(dist > -rad) v = VFC_PART_VISIBLE;
return v;
}
static inline int ishiddencube(const ivec &o, int size)
{
loopi(5) if(o.dist(vfcP[i]) < -vfcDfar[i]*size) return true;
return false;
}
static inline int isfoggedcube(const ivec &o, int size)
{
loopi(4) if(o.dist(vfcP[i]) < -vfcDfar[i]*size) return true;
float dist = o.dist(vfcP[4]);
return dist < -vfcDfar[4]*size || dist > vfcDfog - vfcDnear[4]*size;
}
static int isvisiblecube(const ivec &o, int size)
{
int v = VFC_FULL_VISIBLE;
float dist;
loopi(5)
{
dist = o.dist(vfcP[i]);
if(dist < -vfcDfar[i]*size) return VFC_NOT_VISIBLE;
if(dist < -vfcDnear[i]*size) v = VFC_PART_VISIBLE;
}
dist -= vfcDfog;
if(dist > -vfcDnear[4]*size) return VFC_FOGGED;
if(dist > -vfcDfar[4]*size) v = VFC_PART_VISIBLE;
return v;
}
int isvisiblebb(const ivec &bo, const ivec &br)
{
int v = VFC_FULL_VISIBLE;
float dnear, dfar;
loopi(5)
{
const plane &p = vfcP[i];
dnear = dfar = bo.dist(p);
if(p.x > 0) dfar += p.x*br.x; else dnear += p.x*br.x;
if(p.y > 0) dfar += p.y*br.y; else dnear += p.y*br.y;
if(p.z > 0) dfar += p.z*br.z; else dnear += p.z*br.z;
if(dfar < 0) return VFC_NOT_VISIBLE;
if(dnear < 0) v = VFC_PART_VISIBLE;
}
if(dnear > vfcDfog) return VFC_FOGGED;
if(dfar > vfcDfog) v = VFC_PART_VISIBLE;
return v;
}
static inline float vadist(vtxarray *va, const vec &p)
{
return p.dist_to_bb(va->bbmin, va->bbmax);
}
#define VASORTSIZE 64
static vtxarray *vasort[VASORTSIZE];
static inline void addvisibleva(vtxarray *va)
{
float dist = vadist(va, camera1->o);
va->distance = int(dist); /*cv.dist(camera1->o) - va->size*SQRT3/2*/
int hash = std::clamp(int(dist*VASORTSIZE/worldsize), 0, VASORTSIZE-1);
vtxarray **prev = &vasort[hash], *cur = vasort[hash];
while(cur && va->distance >= cur->distance)
{
prev = &cur->next;
cur = cur->next;
}
va->next = cur;
*prev = va;
}
static void sortvisiblevas()
{
visibleva = nullptr;
vtxarray **last = &visibleva;
loopi(VASORTSIZE) if(vasort[i])
{
vtxarray *va = vasort[i];
*last = va;
while(va->next) va = va->next;
last = &va->next;
}
}
template<bool fullvis, bool resetocclude>
static inline void findvisiblevas(vector<vtxarray *> &vas)
{
loopv(vas)
{
vtxarray &v = *vas[i];
int prevvfc = v.curvfc;
v.curvfc = fullvis ? VFC_FULL_VISIBLE : isvisiblecube(v.o, v.size);
if(v.curvfc != VFC_NOT_VISIBLE)
{
if(pvsoccluded(v.o, v.size))
{
v.curvfc += PVS_FULL_VISIBLE - VFC_FULL_VISIBLE;
continue;
}
bool resetchildren = prevvfc >= VFC_NOT_VISIBLE || resetocclude;
if(resetchildren)
{
v.occluded = !v.texs ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
v.query = nullptr;
}
addvisibleva(&v);
if(v.children.length())
{
if(fullvis || v.curvfc == VFC_FULL_VISIBLE)
{
if(resetchildren) findvisiblevas<true, true>(v.children);
else findvisiblevas<true, false>(v.children);
}
else if(resetchildren) findvisiblevas<false, true>(v.children);
else findvisiblevas<false, false>(v.children);
}
}
}
}
static void findvisiblevas()
{
memset(vasort, 0, sizeof(vasort));
findvisiblevas<false, false>(varoot);
sortvisiblevas();
}
static void calcvfcD()
{
loopi(5)
{
plane &p = vfcP[i];
vfcDnear[i] = vfcDfar[i] = 0;
loopk(3) if(p[k] > 0) vfcDfar[i] += p[k];
else vfcDnear[i] += p[k];
}
}
static void setvfcP(const vec &bbmin = vec(-1, -1, -1), const vec &bbmax = vec(1, 1, 1))
{
vec4 px = camprojmatrix.rowx(), py = camprojmatrix.rowy(), pz = camprojmatrix.rowz(), pw = camprojmatrix.roww();
vfcP[0] = plane(vec4(pw).mul(-bbmin.x).add(px)).normalize(); // left plane
vfcP[1] = plane(vec4(pw).mul(bbmax.x).sub(px)).normalize(); // right plane
vfcP[2] = plane(vec4(pw).mul(-bbmin.y).add(py)).normalize(); // bottom plane
vfcP[3] = plane(vec4(pw).mul(bbmax.y).sub(py)).normalize(); // top plane
vfcP[4] = plane(vec4(pw).add(pz)).normalize(); // near/far planes
vfcDfog = min(calcfogcull(), float(farplane));
calcvfcD();
}
static plane oldvfcP[5];
#if 0
static void savevfcP()
{
memcpy(oldvfcP, vfcP, sizeof(vfcP));
}
static void restorevfcP()
{
memcpy(vfcP, oldvfcP, sizeof(vfcP));
calcvfcD();
}
#endif
void visiblecubes(bool cull)
{
if(cull)
{
setvfcP();
findvisiblevas();
}
else
{
memset(vfcP, 0, sizeof(vfcP));
vfcDfog = farplane;
memset(vfcDnear, 0, sizeof(vfcDnear));
memset(vfcDfar, 0, sizeof(vfcDfar));
visibleva = nullptr;
loopv(valist)
{
vtxarray *va = valist[i];
va->distance = 0;
va->curvfc = VFC_FULL_VISIBLE;
va->occluded = !va->texs ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
va->query = nullptr;
va->next = visibleva;
visibleva = va;
}
}
}
///////// occlusion queries /////////////
#define MAXQUERY 2048
#define MAXQUERYFRAMES 2
int deferquery = 0;
struct queryframe
{
int cur, max, defer;
occludequery queries[MAXQUERY];
queryframe() : cur(0), max(0), defer(0) {}
void flip()
{
loopi(cur) queries[i].owner = nullptr;
for(; defer > 0 && max < MAXQUERY; defer--)
{
queries[max].owner = nullptr;
queries[max].fragments = -1;
glGenQueries_(1, &queries[max++].id);
}
cur = defer = 0;
}
occludequery *newquery(void *owner)
{
if(cur >= max)
{
if(max >= MAXQUERY) return nullptr;
if(deferquery)
{
if(max + defer < MAXQUERY) defer++;
return nullptr;
}
glGenQueries_(1, &queries[max++].id);
}
occludequery *query = &queries[cur++];
query->owner = owner;
query->fragments = -1;
return query;
}
void reset() { loopi(max) queries[i].owner = nullptr; }
void cleanup()
{
loopi(max)
{
glDeleteQueries_(1, &queries[i].id);
queries[i].owner = nullptr;
}
cur = max = defer = 0;
}
};
static queryframe queryframes[MAXQUERYFRAMES];
static uint flipquery = 0;
int getnumqueries()
{
return queryframes[flipquery].cur;
}
void flipqueries()
{
flipquery = (flipquery + 1) % MAXQUERYFRAMES;
queryframes[flipquery].flip();
}
occludequery *newquery(void *owner)
{
return queryframes[flipquery].newquery(owner);
}
void resetqueries()
{
loopi(MAXQUERYFRAMES) queryframes[i].reset();
}
static void clearqueries()
{
loopi(MAXQUERYFRAMES) queryframes[i].cleanup();
}
VARF(oqany, 0, 0, 2, clearqueries());
VAR(oqfrags, 0, 8, 64);
VAR(oqwait, 0, 1, 1);
static inline GLenum querytarget()
{
return oqany && hasOQ2 ? (oqany > 1 && hasES3 ? GL_ANY_SAMPLES_PASSED_CONSERVATIVE : GL_ANY_SAMPLES_PASSED) : GL_SAMPLES_PASSED;
}
void startquery(occludequery *query)
{
glBeginQuery_(querytarget(), query->id);
}
void endquery(occludequery *query)
{
glEndQuery_(querytarget());
}
bool checkquery(occludequery *query, bool nowait)
{
if(query->fragments < 0)
{
if(nowait || !oqwait)
{
GLint avail;
glGetQueryObjectiv_(query->id, GL_QUERY_RESULT_AVAILABLE, &avail);
if(!avail) return false;
}
GLuint fragments;
glGetQueryObjectuiv_(query->id, GL_QUERY_RESULT, &fragments);
query->fragments = querytarget() == GL_SAMPLES_PASSED || !fragments ? int(fragments) : oqfrags;
}
return query->fragments < oqfrags;
}
static GLuint bbvbo = 0, bbebo = 0;
static void setupbb()
{
if(!bbvbo)
{
glGenBuffers_(1, &bbvbo);
gle::bindvbo(bbvbo);
vec verts[8];
loopi(8) verts[i] = vec(i&1, (i>>1)&1, (i>>2)&1);
glBufferData_(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);
gle::clearvbo();
}
if(!bbebo)
{
glGenBuffers_(1, &bbebo);
gle::bindebo(bbebo);
GLushort tris[3*2*6];
#define GENFACEORIENT(orient, v0, v1, v2, v3) do { \
int offset = orient*3*2; \
tris[offset + 0] = v0; \
tris[offset + 1] = v1; \
tris[offset + 2] = v2; \
tris[offset + 3] = v0; \
tris[offset + 4] = v2; \
tris[offset + 5] = v3; \
} while(0);
#define GENFACEVERT(orient, vert, ox,oy,oz, rx,ry,rz) (ox | oy | oz)
GENFACEVERTS(0, 1, 0, 2, 0, 4, , , , , , )
#undef GENFACEORIENT
#undef GENFACEVERT
glBufferData_(GL_ELEMENT_ARRAY_BUFFER, sizeof(tris), tris, GL_STATIC_DRAW);
gle::clearebo();
}
}
static void cleanupbb()
{
if(bbvbo) { glDeleteBuffers_(1, &bbvbo); bbvbo = 0; }
if(bbebo) { glDeleteBuffers_(1, &bbebo); bbebo = 0; }
}
void startbb(bool mask)
{
setupbb();
gle::bindvbo(bbvbo);
gle::bindebo(bbebo);
gle::vertexpointer(sizeof(vec), (const vec *)0);
gle::enablevertex();
SETSHADER(bbquery);
if(mask)
{
glDepthMask(GL_FALSE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
}
}
void endbb(bool mask)
{
gle::disablevertex();
gle::clearvbo();
gle::clearebo();
if(mask)
{
glDepthMask(GL_TRUE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
}
void drawbb(const ivec &bo, const ivec &br)
{
LOCALPARAMF(bborigin, bo.x, bo.y, bo.z);
LOCALPARAMF(bbsize, br.x, br.y, br.z);
glDrawRangeElements_(GL_TRIANGLES, 0, 8-1, 3*2*6, GL_UNSIGNED_SHORT, (ushort *)0);
xtraverts += 8;
}
extern int octaentsize;
static octaentities *visiblemms, **lastvisiblemms;
static void findvisiblemms(const vector<extentity *> &ents, bool doquery)
{
visiblemms = nullptr;
lastvisiblemms = &visiblemms;
for(vtxarray *va = visibleva; va; va = va->next) if(va->occluded < OCCLUDE_BB && va->curvfc < VFC_FOGGED) loopv(va->mapmodels)
{
octaentities *oe = va->mapmodels[i];
if(isfoggedcube(oe->o, oe->size) || pvsoccluded(oe->bbmin, oe->bbmax)) continue;
bool occluded = doquery && oe->query && oe->query->owner == oe && checkquery(oe->query);
if(occluded)
{
oe->distance = -1;
oe->next = nullptr;
*lastvisiblemms = oe;
lastvisiblemms = &oe->next;
}
else
{
int visible = 0;
loopv(oe->mapmodels)
{
extentity &e = *ents[oe->mapmodels[i]];
if(e.flags&EF_NOVIS) continue;
e.flags |= EF_RENDER;
++visible;
}
if(!visible) continue;
oe->distance = int(camera1->o.dist_to_bb(oe->o, oe->size));
octaentities **prev = &visiblemms, *cur = visiblemms;
while(cur && cur->distance >= 0 && oe->distance > cur->distance)
{
prev = &cur->next;
cur = cur->next;
}
if(*prev == nullptr) lastvisiblemms = &oe->next;
oe->next = *prev;
*prev = oe;
}
}
}
VAR(oqmm, 0, 4, 8);
static inline void rendermapmodel(extentity &e)
{
int anim = ANIM_MAPMODEL|ANIM_LOOP, basetime = 0;
if(e.flags&EF_ANIM) entities::animatemapmodel(e, anim, basetime);
rendermapmodel(e.attr1, anim, e.o, e.attr2, e.attr3, e.attr4, MDL_CULL_VFC | MDL_CULL_DIST, basetime, e.attr5 > 0 ? e.attr5/100.0f : 1.0f);
}
void rendermapmodels()
{
static int skipoq = 0;
bool doquery = !drawtex && oqfrags && oqmm;
const vector<extentity *> &ents = entities::getents();
findvisiblemms(ents, doquery);
for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance>=0)
{
bool rendered = false;
loopv(oe->mapmodels)
{
extentity &e = *ents[oe->mapmodels[i]];
if(!(e.flags&EF_RENDER)) continue;
if(!rendered)
{
rendered = true;
oe->query = doquery && oe->distance>0 && !(++skipoq%oqmm) ? newquery(oe) : nullptr;
if(oe->query) startmodelquery(oe->query);
}
rendermapmodel(e);
e.flags &= ~EF_RENDER;
}
if(rendered && oe->query) endmodelquery();
}
rendermapmodelbatches();
clearbatchedmapmodels();
bool queried = false;
for(octaentities *oe = visiblemms; oe; oe = oe->next) if(oe->distance<0)
{
oe->query = doquery && !camera1->o.insidebb(oe->bbmin, oe->bbmax, 1) ? newquery(oe) : nullptr;
if(!oe->query) continue;
if(!queried)
{
startbb();
queried = true;
}
startquery(oe->query);
drawbb(oe->bbmin, ivec(oe->bbmax).sub(oe->bbmin));
endquery(oe->query);
}
if(queried)
{
endbb();
}
}
static inline bool bbinsideva(const ivec &bo, const ivec &br, vtxarray *va)
{
return bo.x >= va->bbmin.x && bo.y >= va->bbmin.y && bo.z >= va->bbmin.z &&
br.x <= va->bbmax.x && br.y <= va->bbmax.y && br.z <= va->bbmax.z;
}
static inline bool bboccluded(const ivec &bo, const ivec &br, cube *c, const ivec &o, int size)
{
loopoctabox(o, size, bo, br)
{
ivec co(i, o, size);
if(c[i].ext && c[i].ext->va)
{
vtxarray *va = c[i].ext->va;
if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) continue;
}
if(c[i].children && bboccluded(bo, br, c[i].children, co, size>>1)) continue;
return false;
}
return true;
}
bool bboccluded(const ivec &bo, const ivec &br)
{
int diff = (bo.x^br.x) | (bo.y^br.y) | (bo.z^br.z);
if(diff&~((1<<worldscale)-1)) return false;
int scale = worldscale-1;
if(diff&(1<<scale)) return bboccluded(bo, br, worldroot, ivec(0, 0, 0), 1<<scale);
cube *c = &worldroot[octastep(bo.x, bo.y, bo.z, scale)];
if(c->ext && c->ext->va)
{
vtxarray *va = c->ext->va;
if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true;
}
scale--;
while(c->children && !(diff&(1<<scale)))
{
c = &c->children[octastep(bo.x, bo.y, bo.z, scale)];
if(c->ext && c->ext->va)
{
vtxarray *va = c->ext->va;
if(va->curvfc >= VFC_FOGGED || (va->occluded >= OCCLUDE_BB && bbinsideva(bo, br, va))) return true;
}
scale--;
}
if(c->children) return bboccluded(bo, br, c->children, ivec(bo).mask(~((2<<scale)-1)), 1<<scale);
return false;
}
VAR(outline, 0, 0, 1);
CVARP(outlinecolour, 0);
VAR(dtoutline, 0, 1, 1);
void renderoutline()
{
ldrnotextureshader->set();
gle::enablevertex();
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
gle::color(outlinecolour);
enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
if(!dtoutline) glDisable(GL_DEPTH_TEST);
vtxarray *prev = nullptr;
for(vtxarray *va = visibleva; va; va = va->next) if(va->occluded < OCCLUDE_BB)
{
if((!va->texs || va->occluded >= OCCLUDE_GEOM) && !va->alphaback && !va->alphafront && !va->refracttris) continue;
if(!prev || va->vbuf != prev->vbuf)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->ebuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
if(va->texs && va->occluded < OCCLUDE_GEOM)
{
drawvatris(va, 3*va->tris, 0);
xtravertsva += va->verts;
}
if(va->alphatris)
{
drawvatris(va, 3*va->alphatris, 3*(va->tris + va->blendtris));
xtravertsva += 3*va->alphatris;
}
prev = va;
}
if(!dtoutline) glEnable(GL_DEPTH_TEST);
disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
gle::clearvbo();
gle::clearebo();
gle::disablevertex();
}
CVARP(blendbrushcolour, 0x0000C0);
void renderblendbrush(GLuint tex, float x, float y, float w, float h)
{
SETSHADER(blendbrush);
gle::enablevertex();
glDepthFunc(GL_LEQUAL);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, tex);
gle::color(blendbrushcolour, 0x40);
LOCALPARAMF(texgenS, 1.0f/w, 0, 0, -x/w);
LOCALPARAMF(texgenT, 0, 1.0f/h, 0, -y/h);
vtxarray *prev = nullptr;
for(vtxarray *va = visibleva; va; va = va->next) if(va->texs && va->occluded < OCCLUDE_GEOM)
{
if(va->o.x + va->size <= x || va->o.y + va->size <= y || va->o.x >= x + w || va->o.y >= y + h) continue;
if(!prev || va->vbuf != prev->vbuf)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->ebuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
drawvatris(va, 3*va->tris, 0);
xtravertsva += va->verts;
prev = va;
}
glDisable(GL_BLEND);
glDepthFunc(GL_LESS);
gle::clearvbo();
gle::clearebo();
gle::disablevertex();
}
static int calcbbsidemask(const ivec &bbmin, const ivec &bbmax, const vec &lightpos, float lightradius, float bias)
{
vec pmin = vec(bbmin).sub(lightpos).div(lightradius), pmax = vec(bbmax).sub(lightpos).div(lightradius);
int mask = 0x3F;
float dp1 = pmax.x + pmax.y, dn1 = pmax.x - pmin.y, ap1 = fabs(dp1), an1 = fabs(dn1),
dp2 = pmin.x + pmin.y, dn2 = pmin.x - pmax.y, ap2 = fabs(dp2), an2 = fabs(dn2);
if(ap1 > bias*an1 && ap2 > bias*an2)
mask &= (3<<4)
| (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2))
| (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2));
if(an1 > bias*ap1 && an2 > bias*ap2)
mask &= (3<<4)
| (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2))
| (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2));
dp1 = pmax.y + pmax.z, dn1 = pmax.y - pmin.z, ap1 = fabs(dp1), an1 = fabs(dn1),
dp2 = pmin.y + pmin.z, dn2 = pmin.y - pmax.z, ap2 = fabs(dp2), an2 = fabs(dn2);
if(ap1 > bias*an1 && ap2 > bias*an2)
mask &= (3<<0)
| (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4))
| (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4));
if(an1 > bias*ap1 && an2 > bias*ap2)
mask &= (3<<0)
| (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4))
| (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4));
dp1 = pmax.z + pmax.x, dn1 = pmax.z - pmin.x, ap1 = fabs(dp1), an1 = fabs(dn1),
dp2 = pmin.z + pmin.x, dn2 = pmin.z - pmax.x, ap2 = fabs(dp2), an2 = fabs(dn2);
if(ap1 > bias*an1 && ap2 > bias*an2)
mask &= (3<<2)
| (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0))
| (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0));
if(an1 > bias*ap1 && an2 > bias*ap2)
mask &= (3<<2)
| (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0))
| (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0));
return mask;
}
int calcspheresidemask(const vec &p, float radius, float bias)
{
// p is in the cubemap's local coordinate system
// bias = border/(size - border)
float dxyp = p.x + p.y, dxyn = p.x - p.y, axyp = fabs(dxyp), axyn = fabs(dxyn),
dyzp = p.y + p.z, dyzn = p.y - p.z, ayzp = fabs(dyzp), ayzn = fabs(dyzn),
dzxp = p.z + p.x, dzxn = p.z - p.x, azxp = fabs(dzxp), azxn = fabs(dzxn);
int mask = 0x3F;
radius *= SQRT2;
if(axyp > bias*axyn + radius) mask &= dxyp < 0 ? ~((1<<0)|(1<<2)) : ~((2<<0)|(2<<2));
if(axyn > bias*axyp + radius) mask &= dxyn < 0 ? ~((1<<0)|(2<<2)) : ~((2<<0)|(1<<2));
if(ayzp > bias*ayzn + radius) mask &= dyzp < 0 ? ~((1<<2)|(1<<4)) : ~((2<<2)|(2<<4));
if(ayzn > bias*ayzp + radius) mask &= dyzn < 0 ? ~((1<<2)|(2<<4)) : ~((2<<2)|(1<<4));
if(azxp > bias*azxn + radius) mask &= dzxp < 0 ? ~((1<<4)|(1<<0)) : ~((2<<4)|(2<<0));
if(azxn > bias*azxp + radius) mask &= dzxn < 0 ? ~((1<<4)|(2<<0)) : ~((2<<4)|(1<<0));
return mask;
}
static int calctrisidemask(const vec &p1, const vec &p2, const vec &p3, float bias)
{
// p1, p2, p3 are in the cubemap's local coordinate system
// bias = border/(size - border)
int mask = 0x3F;
float dp1 = p1.x + p1.y, dn1 = p1.x - p1.y, ap1 = fabs(dp1), an1 = fabs(dn1),
dp2 = p2.x + p2.y, dn2 = p2.x - p2.y, ap2 = fabs(dp2), an2 = fabs(dn2),
dp3 = p3.x + p3.y, dn3 = p3.x - p3.y, ap3 = fabs(dp3), an3 = fabs(dn3);
if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3)
mask &= (3<<4)
| (dp1 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2))
| (dp2 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2))
| (dp3 >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2));
if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3)
mask &= (3<<4)
| (dn1 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2))
| (dn2 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2))
| (dn3 >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2));
dp1 = p1.y + p1.z, dn1 = p1.y - p1.z, ap1 = fabs(dp1), an1 = fabs(dn1),
dp2 = p2.y + p2.z, dn2 = p2.y - p2.z, ap2 = fabs(dp2), an2 = fabs(dn2),
dp3 = p3.y + p3.z, dn3 = p3.y - p3.z, ap3 = fabs(dp3), an3 = fabs(dn3);
if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3)
mask &= (3<<0)
| (dp1 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4))
| (dp2 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4))
| (dp3 >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4));
if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3)
mask &= (3<<0)
| (dn1 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4))
| (dn2 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4))
| (dn3 >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4));
dp1 = p1.z + p1.x, dn1 = p1.z - p1.x, ap1 = fabs(dp1), an1 = fabs(dn1),
dp2 = p2.z + p2.x, dn2 = p2.z - p2.x, ap2 = fabs(dp2), an2 = fabs(dn2),
dp3 = p3.z + p3.x, dn3 = p3.z - p3.x, ap3 = fabs(dp3), an3 = fabs(dn3);
if(ap1 > bias*an1 && ap2 > bias*an2 && ap3 > bias*an3)
mask &= (3<<2)
| (dp1 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0))
| (dp2 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0))
| (dp3 >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0));
if(an1 > bias*ap1 && an2 > bias*ap2 && an3 > bias*ap3)
mask &= (3<<2)
| (dn1 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0))
| (dn2 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0))
| (dn3 >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0));
return mask;
}
int cullfrustumsides(const vec &lightpos, float lightradius, float size, float border)
{
int sides = 0x3F, masks[6] = { 3<<4, 3<<4, 3<<0, 3<<0, 3<<2, 3<<2 };
float scale = (size - 2*border)/size, bias = border / (float)(size - border);
// check if cone enclosing side would cross frustum plane
scale = 2 / (scale*scale + 2);
loopi(5) if(vfcP[i].dist(lightpos) <= -0.03125f)
{
vec n = vec(vfcP[i]).div(lightradius);
float len = scale*n.squaredlen();
if(n.x*n.x > len) sides &= n.x < 0 ? ~(1<<0) : ~(2 << 0);
if(n.y*n.y > len) sides &= n.y < 0 ? ~(1<<2) : ~(2 << 2);
if(n.z*n.z > len) sides &= n.z < 0 ? ~(1<<4) : ~(2 << 4);
}
if (vfcP[4].dist(lightpos) >= vfcDfog + 0.03125f)
{
vec n = vec(vfcP[4]).div(lightradius);
float len = scale*n.squaredlen();
if(n.x*n.x > len) sides &= n.x >= 0 ? ~(1<<0) : ~(2 << 0);
if(n.y*n.y > len) sides &= n.y >= 0 ? ~(1<<2) : ~(2 << 2);
if(n.z*n.z > len) sides &= n.z >= 0 ? ~(1<<4) : ~(2 << 4);
}
// this next test usually clips off more sides than the former, but occasionally clips fewer/different ones, so do both and combine results
// check if frustum corners/origin cross plane sides
// infinite version, assumes frustum corners merely give direction and extend to infinite distance
vec p = vec(camera1->o).sub(lightpos).div(lightradius);
float dp = p.x + p.y, dn = p.x - p.y, ap = fabs(dp), an = fabs(dn);
masks[0] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2));
masks[1] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2));
dp = p.y + p.z, dn = p.y - p.z, ap = fabs(dp), an = fabs(dn);
masks[2] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4));
masks[3] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4));
dp = p.z + p.x, dn = p.z - p.x, ap = fabs(dp), an = fabs(dn);
masks[4] |= ap <= bias*an ? 0x3F : (dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0));
masks[5] |= an <= bias*ap ? 0x3F : (dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0));
loopi(4)
{
vec n;
switch(i)
{
case 0: n.cross(vfcP[0], vfcP[2]); break;
case 1: n.cross(vfcP[3], vfcP[0]); break;
case 2: n.cross(vfcP[2], vfcP[1]); break;
case 3: n.cross(vfcP[1], vfcP[3]); break;
}
dp = n.x + n.y, dn = n.x - n.y, ap = fabs(dp), an = fabs(dn);
if(ap > 0) masks[0] |= dp >= 0 ? (1<<0)|(1<<2) : (2<<0)|(2<<2);
if(an > 0) masks[1] |= dn >= 0 ? (1<<0)|(2<<2) : (2<<0)|(1<<2);
dp = n.y + n.z, dn = n.y - n.z, ap = fabs(dp), an = fabs(dn);
if(ap > 0) masks[2] |= dp >= 0 ? (1<<2)|(1<<4) : (2<<2)|(2<<4);
if(an > 0) masks[3] |= dn >= 0 ? (1<<2)|(2<<4) : (2<<2)|(1<<4);
dp = n.z + n.x, dn = n.z - n.x, ap = fabs(dp), an = fabs(dn);
if(ap > 0) masks[4] |= dp >= 0 ? (1<<4)|(1<<0) : (2<<4)|(2<<0);
if(an > 0) masks[5] |= dn >= 0 ? (1<<4)|(2<<0) : (2<<4)|(1<<0);
}
return sides & masks[0] & masks[1] & masks[2] & masks[3] & masks[4] & masks[5];
}
VAR(smbbcull, 0, 1, 1);
VAR(smdistcull, 0, 1, 1);
VAR(smnodraw, 0, 0, 1);
vec shadoworigin(0, 0, 0), shadowdir(0, 0, 0);
float shadowradius = 0, shadowbias = 0;
int shadowside = 0, shadowspot = 0, shadowtransparent = 0;
static vtxarray *shadowva = nullptr;
static inline void addshadowva(vtxarray *va, float dist)
{
va->rdistance = int(dist);
int hash = std::clamp(int(dist*VASORTSIZE/shadowradius), 0, VASORTSIZE-1);
vtxarray **prev = &vasort[hash], *cur = vasort[hash];
while(cur && va->rdistance > cur->rdistance)
{
prev = &cur->rnext;
cur = cur->rnext;
}
va->rnext = cur;
*prev = va;
}
static void sortshadowvas()
{
shadowva = nullptr;
vtxarray **last = &shadowva;
loopi(VASORTSIZE) if(vasort[i])
{
vtxarray *va = vasort[i];
*last = va;
while(va->rnext) va = va->rnext;
last = &va->rnext;
}
}
static inline void getshadowvabb(vtxarray &v, ivec &bbmin, ivec &bbmax, bool transparent = false)
{
if(v.children.length() || v.mapmodels.length()) { bbmin = v.bbmin; bbmax = v.bbmax; }
else { bbmin = v.geommin; bbmax = v.geommax; }
if(transparent && v.alphatris) { bbmin.min(v.alphamin); bbmax.max(v.alphamax); }
}
static void findshadowvas(vector<vtxarray *> &vas, bool transparent)
{
loopv(vas)
{
vtxarray &v = *vas[i];
float dist = vadist(&v, shadoworigin);
if(dist < shadowradius || !smdistcull)
{
ivec bbmin, bbmax;
getshadowvabb(v, bbmin, bbmax, transparent);
v.shadowmask = smbbcull ? 0x3F : calcbbsidemask(bbmin, bbmax, shadoworigin, shadowradius, shadowbias);
if(transparent)
{
if(v.alphatris)
{
v.shadowtransparent = v.shadowmask & calcbbsidemask(v.alphamin, v.alphamax, shadoworigin, shadowradius, shadowbias);
shadowtransparent |= v.shadowtransparent;
}
else v.shadowtransparent = 0;
}
addshadowva(&v, dist);
if(v.children.length()) findshadowvas(v.children, transparent);
}
}
}
static void findcsmshadowvas(vector<vtxarray *> &vas, bool transparent)
{
loopv(vas)
{
vtxarray &v = *vas[i];
ivec bbmin, bbmax;
getshadowvabb(v, bbmin, bbmax, transparent);
v.shadowmask = calcbbcsmsplits(bbmin, bbmax);
if(v.shadowmask)
{
if(transparent)
{
if(v.alphatris)
{
v.shadowtransparent = v.shadowmask & calcbbcsmsplits(v.alphamin, v.alphamax);
shadowtransparent |= v.shadowtransparent;
}
else v.shadowtransparent = 0;
}
float dist = shadowdir.project_bb(bbmin, bbmax) - shadowbias;
addshadowva(&v, dist);
if(v.children.length()) findcsmshadowvas(v.children, transparent);
}
}
}
static void findrsmshadowvas(vector<vtxarray *> &vas)
{
loopv(vas)
{
vtxarray &v = *vas[i];
ivec bbmin, bbmax;
getshadowvabb(v, bbmin, bbmax);
v.shadowmask = calcbbrsmsplits(bbmin, bbmax);
if(v.shadowmask)
{
float dist = shadowdir.project_bb(bbmin, bbmax) - shadowbias;
addshadowva(&v, dist);
if(v.children.length()) findrsmshadowvas(v.children);
}
}
}
static void findspotshadowvas(vector<vtxarray *> &vas, bool transparent)
{
loopv(vas)
{
vtxarray &v = *vas[i];
float dist = vadist(&v, shadoworigin);
if(dist < shadowradius || !smdistcull)
{
ivec bbmin, bbmax;
getshadowvabb(v, bbmin, bbmax, transparent);
bool insidespot = bbinsidespot(shadoworigin, shadowdir, shadowspot, bbmin, bbmax);
v.shadowmask = !smbbcull || insidespot ? 1 : 0;
if(transparent)
{
if(v.alphatris)
{
v.shadowtransparent = v.shadowmask && bbinsidespot(shadoworigin, shadowdir, shadowspot, v.alphamin, v.alphamax) ? 1 : 0;
shadowtransparent |= v.shadowtransparent;
}
else v.shadowtransparent = 0;
}
addshadowva(&v, dist);
if(v.children.length()) findspotshadowvas(v.children, transparent);
}
}
}
void findshadowvas(bool transparent)
{
shadowtransparent = 0;
memset(vasort, 0, sizeof(vasort));
switch(shadowmapping)
{
case SM_REFLECT: findrsmshadowvas(varoot); break;
case SM_CUBEMAP: findshadowvas(varoot, transparent); break;
case SM_CASCADE: findcsmshadowvas(varoot, transparent); break;
case SM_SPOT: findspotshadowvas(varoot, transparent); break;
}
sortshadowvas();
}
void rendershadowmapworld()
{
SETSHADER(smworld);
gle::enablevertex();
vtxarray *prev = nullptr;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->tris && va->shadowmask&(1<<shadowside))
{
if(!prev || va->vbuf != prev->vbuf)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->ebuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
if(!smnodraw) drawvatris(va, 3*va->tris, 0);
xtravertsva += va->verts;
prev = va;
}
if(skyshadow)
{
prev = nullptr;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->sky && va->shadowmask&(1<<shadowside))
{
if(!prev || va->vbuf != prev->vbuf)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->skybuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
if(!smnodraw) drawvaskytris(va);
xtravertsva += va->sky/3;
prev = va;
}
}
gle::clearvbo();
gle::clearebo();
gle::disablevertex();
}
static octaentities *shadowmms = nullptr;
void findshadowmms()
{
shadowmms = nullptr;
octaentities **lastmms = &shadowmms;
for(vtxarray *va = shadowva; va; va = va->rnext) loopvj(va->mapmodels)
{
octaentities *oe = va->mapmodels[j];
switch(shadowmapping)
{
case SM_REFLECT:
break;
case SM_CASCADE:
if(!calcbbcsmsplits(oe->bbmin, oe->bbmax))
continue;
break;
case SM_CUBEMAP:
if(smdistcull && shadoworigin.dist_to_bb(oe->bbmin, oe->bbmax) >= shadowradius)
continue;
break;
case SM_SPOT:
if(smdistcull && shadoworigin.dist_to_bb(oe->bbmin, oe->bbmax) >= shadowradius)
continue;
if(smbbcull && !bbinsidespot(shadoworigin, shadowdir, shadowspot, oe->bbmin, oe->bbmax))
continue;
break;
}
oe->rnext = nullptr;
*lastmms = oe;
lastmms = &oe->rnext;
}
}
void batchshadowmapmodels(bool skipmesh)
{
if(!shadowmms) return;
int nflags = EF_NOVIS|EF_NOSHADOW;
if(skipmesh) nflags |= EF_SHADOWMESH;
const vector<extentity *> &ents = entities::getents();
for(octaentities *oe = shadowmms; oe; oe = oe->rnext) loopvk(oe->mapmodels)
{
extentity &e = *ents[oe->mapmodels[k]];
if(e.flags&nflags) continue;
e.flags |= EF_RENDER;
}
for(octaentities *oe = shadowmms; oe; oe = oe->rnext) loopvj(oe->mapmodels)
{
extentity &e = *ents[oe->mapmodels[j]];
if(!(e.flags&EF_RENDER)) continue;
rendermapmodel(e);
e.flags &= ~EF_RENDER;
}
}
VAR(oqdist, 0, 256, 1024);
struct renderstate
{
bool colormask, depthmask;
int alphaing;
bool shadowing;
GLuint vbuf;
bool vattribs, vquery;
vec colorscale;
float alphascale;
float refractscale;
vec refractcolor;
bool blend;
int blendx, blendy;
int globals, tmu;
GLuint textures[7];
Slot *slot, *texgenslot;
VSlot *vslot, *texgenvslot;
vec2 texgenscroll;
int texgenorient, texgenmillis;
renderstate() : colormask(true), depthmask(true), alphaing(0), vbuf(0), vattribs(false), vquery(false), colorscale(1, 1, 1), alphascale(0), refractscale(0), refractcolor(1, 1, 1), blend(false), blendx(-1), blendy(-1), globals(-1), tmu(-1), slot(nullptr), texgenslot(nullptr), vslot(nullptr), texgenvslot(nullptr), texgenscroll(0, 0), texgenorient(-1), texgenmillis(lastmillis)
{
loopk(7) textures[k] = 0;
}
};
static inline void disablevbuf(renderstate &cur)
{
gle::clearvbo();
gle::clearebo();
cur.vbuf = 0;
}
static inline void enablevquery(renderstate &cur)
{
if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); }
if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); }
startbb(false);
cur.vquery = true;
}
static inline void disablevquery(renderstate &cur)
{
endbb(false);
cur.vquery = false;
}
static void renderquery(renderstate &cur, occludequery *query, vtxarray *va, bool full = true)
{
if(!cur.vquery) enablevquery(cur);
startquery(query);
if(full) drawbb(ivec(va->bbmin).sub(1), ivec(va->bbmax).sub(va->bbmin).add(2));
else drawbb(va->geommin, ivec(va->geommax).sub(va->geommin));
endquery(query);
}
enum
{
RENDERPASS_GBUFFER = 0,
RENDERPASS_Z,
RENDERPASS_CAUSTICS,
RENDERPASS_GBUFFER_BLEND,
RENDERPASS_SMALPHA,
RENDERPASS_RSM,
RENDERPASS_RSM_BLEND
};
struct geombatch
{
const elementset &es;
VSlot &vslot;
int offset;
vtxarray *va;
int next, batch;
geombatch(const elementset &es, int offset, vtxarray *va)
: es(es), vslot(lookupvslot(es.texture)), offset(offset), va(va),
next(-1), batch(-1)
{}
int compare(const geombatch &b) const
{
if(va->vbuf < b.va->vbuf) return -1;
if(va->vbuf > b.va->vbuf) return 1;
if(es.layer&LAYER_BOTTOM)
{
if(!(b.es.layer&LAYER_BOTTOM)) return 1;
int x1 = va->o.x&~0xFFF, x2 = b.va->o.x&~0xFFF;
if(x1 < x2) return -1;
if(x1 > x2) return 1;
int y1 = va->o.y&~0xFFF, y2 = b.va->o.y&~0xFFF;
if(y1 < y2) return -1;
if(y1 > y2) return 1;
}
else if(b.es.layer&LAYER_BOTTOM) return -1;
if(vslot.slot->shader < b.vslot.slot->shader) return -1;
if(vslot.slot->shader > b.vslot.slot->shader) return 1;
if(es.texture < b.es.texture) return -1;
if(es.texture > b.es.texture) return 1;
if(es.envmap < b.es.envmap) return -1;
if(es.envmap > b.es.envmap) return 1;
if(vslot.slot->params.length() < b.vslot.slot->params.length()) return -1;
if(vslot.slot->params.length() > b.vslot.slot->params.length()) return 1;
if(es.orient < b.es.orient) return -1;
if(es.orient > b.es.orient) return 1;
return 0;
}
};
static vector<geombatch> geombatches;
static int firstbatch = -1, numbatches = 0;
static void mergetexs(renderstate &cur, vtxarray *va, elementset *texs = nullptr, int numtexs = 0, int offset = 0)
{
if(!texs)
{
texs = va->texelems;
numtexs = va->texs;
if(cur.alphaing)
{
texs += va->texs + va->blends;
offset += 3*(va->tris + va->blendtris);
numtexs = va->alphaback;
if(cur.alphaing > 1) numtexs += va->alphafront + va->refract;
}
}
if(firstbatch < 0)
{
firstbatch = geombatches.length();
numbatches = numtexs;
loopi(numtexs-1)
{
geombatches.add(geombatch(texs[i], offset, va)).next = i+1;
offset += texs[i].length;
}
geombatches.add(geombatch(texs[numtexs-1], offset, va));
return;
}
int prevbatch = -1, curbatch = firstbatch, curtex = 0;
do
{
geombatch &b = geombatches.add(geombatch(texs[curtex], offset, va));
offset += texs[curtex].length;
int dir = -1;
while(curbatch >= 0)
{
dir = b.compare(geombatches[curbatch]);
if(dir <= 0) break;
prevbatch = curbatch;
curbatch = geombatches[curbatch].next;
}
if(!dir)
{
int last = curbatch, next;
for(;;)
{
next = geombatches[last].batch;
if(next < 0) break;
last = next;
}
if(last==curbatch)
{
b.batch = curbatch;
b.next = geombatches[curbatch].next;
if(prevbatch < 0) firstbatch = geombatches.length()-1;
else geombatches[prevbatch].next = geombatches.length()-1;
curbatch = geombatches.length()-1;
}
else
{
b.batch = next;
geombatches[last].batch = geombatches.length()-1;
}
}
else
{
numbatches++;
b.next = curbatch;
if(prevbatch < 0) firstbatch = geombatches.length()-1;
else geombatches[prevbatch].next = geombatches.length()-1;
prevbatch = geombatches.length()-1;
}
}
while(++curtex < numtexs);
}
static inline void enablevattribs(renderstate &cur, bool all = true)
{
gle::enablevertex();
if(all)
{
gle::enabletexcoord0();
gle::enablenormal();
gle::enabletangent();
}
cur.vattribs = true;
}
static inline void disablevattribs(renderstate &cur, bool all = true)
{
gle::disablevertex();
if(all)
{
gle::disabletexcoord0();
gle::disablenormal();
gle::disabletangent();
}
cur.vattribs = false;
}
static void changevbuf(renderstate &cur, int pass, vtxarray *va)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->ebuf);
cur.vbuf = va->vbuf;
vertex *vdata = (vertex *)0;
gle::vertexpointer(sizeof(vertex), vdata->pos.v);
if(pass==RENDERPASS_GBUFFER || pass==RENDERPASS_RSM || pass==RENDERPASS_SMALPHA)
{
gle::normalpointer(sizeof(vertex), vdata->norm.v, GL_BYTE);
gle::texcoord0pointer(sizeof(vertex), vdata->tc.v);
gle::tangentpointer(sizeof(vertex), vdata->tangent.v, GL_BYTE);
}
}
static void changebatchtmus(renderstate &cur, int pass, geombatch &b)
{
if(b.vslot.slot->shader && b.vslot.slot->shader->type&SHADER_ENVMAP && b.es.envmap!=EMID_CUSTOM)
{
GLuint emtex = lookupenvmap(b.es.envmap);
if(cur.textures[TEX_ENVMAP]!=emtex)
{
cur.tmu = TEX_ENVMAP;
glActiveTexture_(GL_TEXTURE0 + TEX_ENVMAP);
glBindTexture(GL_TEXTURE_CUBE_MAP, cur.textures[TEX_ENVMAP] = emtex);
}
}
if(b.es.layer&LAYER_BOTTOM)
{
if(!cur.blend)
{
cur.blend = true;
cur.vslot = nullptr;
}
if((cur.blendx != (b.va->o.x&~0xFFF) || cur.blendy != (b.va->o.y&~0xFFF)))
{
cur.tmu = 7;
glActiveTexture_(GL_TEXTURE7);
bindblendtexture(b.va->o);
cur.blendx = b.va->o.x&~0xFFF;
cur.blendy = b.va->o.y&~0xFFF;
}
}
else if(cur.blend)
{
cur.blend = false;
cur.vslot = nullptr;
}
if(cur.tmu != 0)
{
cur.tmu = 0;
glActiveTexture_(GL_TEXTURE0);
}
}
static inline void bindslottex(renderstate &cur, int type, Texture *tex, GLenum target = GL_TEXTURE_2D)
{
if(cur.textures[type] != tex->id)
{
if(cur.tmu != type)
{
cur.tmu = type;
glActiveTexture_(GL_TEXTURE0 + type);
}
glBindTexture(target, cur.textures[type] = tex->id);
}
}
static void changeslottmus(renderstate &cur, int pass, Slot &slot, VSlot &vslot)
{
if(pass==RENDERPASS_GBUFFER || pass==RENDERPASS_RSM || pass==RENDERPASS_SMALPHA)
{
Texture *diffuse = slot.sts.empty() ? notexture : slot.sts[0].t;
bindslottex(cur, TEX_DIFFUSE, diffuse);
if(pass == RENDERPASS_GBUFFER)
{
if(msaasamples) GLOBALPARAMF(hashid, vslot.index);
if(slot.shader->type&SHADER_TRIPLANAR)
{
float scale = TEX_SCALE/vslot.scale;
GLOBALPARAMF(texgenscale, scale/diffuse->xs, scale/diffuse->ys);
}
}
}
if(cur.alphaing)
{
float alpha = cur.alphaing > 1 ? vslot.alphafront : vslot.alphaback;
if(cur.colorscale != vslot.colorscale || cur.alphascale != alpha)
{
cur.colorscale = vslot.colorscale;
cur.alphascale = alpha;
if(pass == RENDERPASS_SMALPHA)
{
extern float alphashadowscale;
GLOBALPARAMF(colorparams, alphashadowscale*vslot.colorscale.x, alphashadowscale*vslot.colorscale.y, alphashadowscale*vslot.colorscale.z, alpha);
}
else GLOBALPARAMF(colorparams, alpha*vslot.colorscale.x, alpha*vslot.colorscale.y, alpha*vslot.colorscale.z, alpha);
}
if(cur.alphaing > 1 && vslot.refractscale > 0 && (cur.refractscale != vslot.refractscale || cur.refractcolor != vslot.refractcolor))
{
cur.refractscale = vslot.refractscale;
cur.refractcolor = vslot.refractcolor;
float refractscale = 0.5f/ldrscale;
GLOBALPARAMF(refractparams, vslot.refractcolor.x*refractscale, vslot.refractcolor.y*refractscale, vslot.refractcolor.z*refractscale, vslot.refractscale*viewh);
}
}
else if(cur.colorscale != vslot.colorscale)
{
cur.colorscale = vslot.colorscale;
GLOBALPARAMF(colorparams, vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z, 1);
}
loopvj(slot.sts)
{
Slot::Tex &t = slot.sts[j];
switch(t.type)
{
case TEX_ENVMAP:
if(t.t) bindslottex(cur, t.type, t.t, GL_TEXTURE_CUBE_MAP);
break;
case TEX_NORMAL:
case TEX_GLOW:
bindslottex(cur, t.type, t.t);
break;
}
}
if(pass == RENDERPASS_GBUFFER && vslot.detail)
{
VSlot &detail = lookupvslot(vslot.detail);
loopvj(detail.slot->sts)
{
Slot::Tex &t = detail.slot->sts[j];
switch(t.type)
{
case TEX_DIFFUSE:
if(slot.shader->type&SHADER_TRIPLANAR)
{
float scale = TEX_SCALE/detail.scale;
GLOBALPARAMF(detailscale, scale/t.t->xs, scale/t.t->ys);
}
// fall-through
case TEX_NORMAL:
bindslottex(cur, TEX_DETAIL + t.type, t.t);
break;
}
}
}
if(cur.tmu != 0)
{
cur.tmu = 0;
glActiveTexture_(GL_TEXTURE0);
}
cur.slot = &slot;
cur.vslot = &vslot;
}
static void changetexgen(renderstate &cur, int orient, Slot &slot, VSlot &vslot)
{
if(cur.texgenslot != &slot || cur.texgenvslot != &vslot)
{
Texture *curtex = !cur.texgenslot || cur.texgenslot->sts.empty() ? notexture : cur.texgenslot->sts[0].t,
*tex = slot.sts.empty() ? notexture : slot.sts[0].t;
if(!cur.texgenvslot || slot.sts.empty() ||
(curtex->xs != tex->xs || curtex->ys != tex->ys ||
cur.texgenvslot->rotation != vslot.rotation || cur.texgenvslot->scale != vslot.scale ||
cur.texgenvslot->offset != vslot.offset || cur.texgenvslot->scroll != vslot.scroll))
{
const texrotation &r = texrotations[vslot.rotation];
float xs = r.flipx ? -tex->xs : tex->xs,
ys = r.flipy ? -tex->ys : tex->ys;
vec2 scroll(vslot.scroll);
if(r.swapxy) swap(scroll.x, scroll.y);
scroll.x *= cur.texgenmillis*tex->xs/xs;
scroll.y *= cur.texgenmillis*tex->ys/ys;
if(cur.texgenscroll != scroll)
{
cur.texgenscroll = scroll;
cur.texgenorient = -1;
}
}
cur.texgenslot = &slot;
cur.texgenvslot = &vslot;
}
if(cur.texgenorient == orient) return;
GLOBALPARAM(texgenscroll, cur.texgenscroll);
cur.texgenorient = orient;
}
static inline void changeshader(renderstate &cur, int pass, geombatch &b)
{
VSlot &vslot = b.vslot;
Slot &slot = *vslot.slot;
if(pass == RENDERPASS_SMALPHA)
{
extern Shader *smalphaworldshader;
if(slot.texmask&(1<<TEX_ALPHA)) smalphaworldshader->setvariant(0, slot.texmask&(1<<TEX_NORMAL) ? 1 : 0, slot, vslot);
else smalphaworldshader->set(slot, vslot);
}
else if(pass == RENDERPASS_RSM)
{
extern Shader *rsmworldshader;
if(b.es.layer&LAYER_BOTTOM) rsmworldshader->setvariant(0, 0, slot, vslot);
else rsmworldshader->set(slot, vslot);
}
else if(cur.alphaing) slot.shader->setvariant(cur.alphaing > 1 && vslot.refractscale > 0 ? 1 : 0, 1, slot, vslot);
else if(b.es.layer&LAYER_BOTTOM) slot.shader->setvariant(0, 0, slot, vslot);
else slot.shader->set(slot, vslot);
cur.globals = GlobalShaderParamState::nextversion;
}
template<class T>
static inline void updateshader(T &cur)
{
if(cur.globals != GlobalShaderParamState::nextversion)
{
if(Shader::lastshader) Shader::lastshader->flushparams();
cur.globals = GlobalShaderParamState::nextversion;
}
}
static void renderbatch(renderstate &cur, int pass, geombatch &b)
{
gbatches++;
for(geombatch *curbatch = &b;; curbatch = &geombatches[curbatch->batch])
{
ushort len = curbatch->es.length;
if(len)
{
drawtris(len, (ushort *)0 + curbatch->va->eoffset + curbatch->offset, curbatch->es.minvert, curbatch->es.maxvert);
vtris += len/3;
}
if(curbatch->batch < 0) break;
}
}
static void resetbatches()
{
geombatches.setsize(0);
firstbatch = -1;
numbatches = 0;
}
static void renderbatches(renderstate &cur, int pass)
{
cur.slot = nullptr;
cur.vslot = nullptr;
int curbatch = firstbatch;
if(curbatch >= 0)
{
if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); }
if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); }
if(!cur.vattribs)
{
if(cur.vquery) disablevquery(cur);
enablevattribs(cur);
}
}
while(curbatch >= 0)
{
geombatch &b = geombatches[curbatch];
curbatch = b.next;
if(cur.vbuf != b.va->vbuf) changevbuf(cur, pass, b.va);
if(pass == RENDERPASS_GBUFFER || pass == RENDERPASS_RSM || pass == RENDERPASS_SMALPHA)
changebatchtmus(cur, pass, b);
if(cur.vslot != &b.vslot)
{
changeslottmus(cur, pass, *b.vslot.slot, b.vslot);
if(cur.texgenorient != b.es.orient || (cur.texgenorient < O_ANY && cur.texgenvslot != &b.vslot)) changetexgen(cur, b.es.orient, *b.vslot.slot, b.vslot);
changeshader(cur, pass, b);
}
else
{
if(cur.texgenorient != b.es.orient) changetexgen(cur, b.es.orient, *b.vslot.slot, b.vslot);
updateshader(cur);
}
renderbatch(cur, pass, b);
}
resetbatches();
}
static void renderzpass(renderstate &cur, vtxarray *va)
{
if(!cur.vattribs)
{
if(cur.vquery) disablevquery(cur);
enablevattribs(cur, false);
}
if(cur.vbuf!=va->vbuf) changevbuf(cur, RENDERPASS_Z, va);
if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); }
if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); }
int firsttex = 0, numtris = va->tris, offset = 0;
if(cur.alphaing)
{
firsttex += va->texs + va->blends;
offset += 3*(va->tris + va->blendtris);
numtris = va->alphatris;
xtravertsva += 3*numtris;
}
else xtravertsva += va->verts;
nocolorshader->set();
drawvatris(va, 3*numtris, offset);
}
#define startvaquery(va, flush) \
do { \
if(va->query) \
{ \
flush; \
startquery(va->query); \
} \
} while(0)
#define endvaquery(va, flush) \
do { \
if(va->query) \
{ \
flush; \
endquery(va->query); \
} \
} while(0)
VAR(batchgeom, 0, 1, 1);
static void renderva(renderstate &cur, vtxarray *va, int pass = RENDERPASS_GBUFFER, bool doquery = false)
{
switch(pass)
{
case RENDERPASS_GBUFFER:
if(!cur.alphaing) vverts += va->verts;
if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, pass); });
mergetexs(cur, va);
if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, pass); });
else if(!batchgeom && geombatches.length()) renderbatches(cur, pass);
break;
case RENDERPASS_GBUFFER_BLEND:
if(doquery) startvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER); });
mergetexs(cur, va, &va->texelems[va->texs], va->blends, 3*va->tris);
if(doquery) endvaquery(va, { if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER); });
else if(!batchgeom && geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER);
break;
case RENDERPASS_CAUSTICS:
if(!cur.vattribs) enablevattribs(cur, false);
if(cur.vbuf!=va->vbuf) changevbuf(cur, pass, va);
drawvatris(va, 3*va->tris, 0);
xtravertsva += va->verts;
break;
case RENDERPASS_Z:
if(doquery) startvaquery(va, );
renderzpass(cur, va);
if(doquery) endvaquery(va, );
break;
case RENDERPASS_SMALPHA:
mergetexs(cur, va);
if(!batchgeom && geombatches.length()) renderbatches(cur, pass);
break;
case RENDERPASS_RSM:
mergetexs(cur, va);
if(!batchgeom && geombatches.length()) renderbatches(cur, pass);
break;
case RENDERPASS_RSM_BLEND:
mergetexs(cur, va, &va->texelems[va->texs], va->blends, 3*va->tris);
if(!batchgeom && geombatches.length()) renderbatches(cur, RENDERPASS_RSM);
break;
}
}
void cleanupva()
{
clearvas(worldroot);
clearqueries();
cleanupbb();
cleanupgrass();
}
static void setupgeom(renderstate &cur)
{
glActiveTexture_(GL_TEXTURE0);
GLOBALPARAMF(colorparams, 1, 1, 1, 1);
GLOBALPARAMF(blendlayer, 1.0f);
}
static void cleanupgeom(renderstate &cur)
{
if(cur.vattribs) disablevattribs(cur);
if(cur.vbuf) disablevbuf(cur);
}
VAR(oqgeom, 0, 1, 1);
void rendergeom()
{
bool doOQ = oqfrags && oqgeom && !drawtex, multipassing = false;
renderstate cur;
int blends = 0;
if(doOQ)
{
for(vtxarray *va = visibleva; va; va = va->next) if(va->texs)
{
if(!camera1->o.insidebb(va->o, va->size, 2))
{
if(va->parent && va->parent->occluded >= OCCLUDE_BB)
{
va->query = nullptr;
va->occluded = OCCLUDE_PARENT;
continue;
}
va->occluded = va->query && va->query->owner == va && checkquery(va->query) ? min(va->occluded+1, int(OCCLUDE_BB)) : OCCLUDE_NOTHING;
va->query = newquery(va);
if(!va->query || !va->occluded)
va->occluded = pvsoccluded(va->geommin, va->geommax) ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
if(va->occluded >= OCCLUDE_GEOM)
{
if(va->query)
{
if(cur.vattribs) disablevattribs(cur, false);
if(cur.vbuf) disablevbuf(cur);
renderquery(cur, va->query, va);
}
continue;
}
}
else
{
va->query = nullptr;
va->occluded = pvsoccluded(va->geommin, va->geommax) ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
if(va->occluded >= OCCLUDE_GEOM) continue;
}
renderva(cur, va, RENDERPASS_Z, true);
}
if(cur.vquery) disablevquery(cur);
if(cur.vattribs) disablevattribs(cur, false);
if(cur.vbuf) disablevbuf(cur);
glFlush();
if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); }
if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); }
workinoq();
if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); }
if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); }
if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); }
cur.texgenorient = -1;
setupgeom(cur);
resetbatches();
for(vtxarray *va = visibleva; va; va = va->next) if(va->texs && va->occluded < OCCLUDE_GEOM)
{
blends += va->blends;
renderva(cur, va, RENDERPASS_GBUFFER);
}
if(geombatches.length()) { renderbatches(cur, RENDERPASS_GBUFFER); glFlush(); }
for(vtxarray *va = visibleva; va; va = va->next) if(va->texs && va->occluded >= OCCLUDE_GEOM)
{
if((va->parent && va->parent->occluded >= OCCLUDE_BB) || (va->query && checkquery(va->query)))
{
va->occluded = OCCLUDE_BB;
continue;
}
else
{
va->occluded = pvsoccluded(va->geommin, va->geommax) ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
if(va->occluded >= OCCLUDE_GEOM) continue;
}
blends += va->blends;
renderva(cur, va, RENDERPASS_GBUFFER);
}
if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER);
}
else
{
setupgeom(cur);
resetbatches();
for(vtxarray *va = visibleva; va; va = va->next) if(va->texs)
{
va->query = nullptr;
va->occluded = pvsoccluded(va->geommin, va->geommax) ? OCCLUDE_GEOM : OCCLUDE_NOTHING;
if(va->occluded >= OCCLUDE_GEOM) continue;
blends += va->blends;
renderva(cur, va, RENDERPASS_GBUFFER);
}
if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER);
}
if(blends)
{
if(cur.vbuf) disablevbuf(cur);
if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); }
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
maskgbuffer("cn");
GLOBALPARAMF(blendlayer, 0.0f);
cur.texgenorient = -1;
for(vtxarray *va = visibleva; va; va = va->next) if(va->blends && va->occluded < OCCLUDE_GEOM && va->curvfc != VFC_FOGGED)
{
renderva(cur, va, RENDERPASS_GBUFFER_BLEND);
}
if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER);
maskgbuffer("cnd");
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
if(multipassing) glDepthFunc(GL_LESS);
cleanupgeom(cur);
if(!doOQ)
{
glFlush();
if(cur.colormask) { cur.colormask = false; glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); }
if(cur.depthmask) { cur.depthmask = false; glDepthMask(GL_FALSE); }
workinoq();
if(!cur.colormask) { cur.colormask = true; glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); }
if(!cur.depthmask) { cur.depthmask = true; glDepthMask(GL_TRUE); }
}
}
int dynamicshadowvas()
{
int vis = 0;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->dynalphatexs) vis |= va->shadowtransparent;
return vis;
}
int dynamicshadowvabounds(int mask, vec &bbmin, vec &bbmax)
{
int vis = 0;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->shadowmask&mask && va->dyntexs)
{
bbmin.min(vec(va->geommin));
bbmax.max(vec(va->geommax));
vis++;
}
return vis;
}
void renderrsmgeom(bool dyntex)
{
renderstate cur;
if(!dyntex) cur.texgenmillis = 0;
setupgeom(cur);
if(skyshadow)
{
enablevattribs(cur, false);
SETSHADER(rsmsky);
vtxarray *prev = nullptr;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->sky)
{
if(!prev || va->vbuf != prev->vbuf)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->skybuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
drawvaskytris(va);
xtravertsva += va->sky/3;
prev = va;
}
if(cur.vattribs) disablevattribs(cur, false);
}
resetbatches();
int blends = 0;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->texs)
{
blends += va->blends;
renderva(cur, va, RENDERPASS_RSM);
}
if(geombatches.length()) renderbatches(cur, RENDERPASS_RSM);
bool multipassing = false;
if(blends)
{
if(cur.vbuf) disablevbuf(cur);
if(!multipassing) { multipassing = true; glDepthFunc(GL_LEQUAL); }
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
GLOBALPARAMF(blendlayer, 0.0f);
cur.texgenorient = -1;
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->blends)
{
renderva(cur, va, RENDERPASS_RSM_BLEND);
}
if(geombatches.length()) renderbatches(cur, RENDERPASS_RSM);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
}
if(multipassing) glDepthFunc(GL_LESS);
cleanupgeom(cur);
}
static vector<vtxarray *> alphavas;
static int alphabackvas = 0, alpharefractvas = 0;
float alphafrontsx1 = -1, alphafrontsx2 = 1, alphafrontsy1 = -1, alphafrontsy2 = -1,
alphabacksx1 = -1, alphabacksx2 = 1, alphabacksy1 = -1, alphabacksy2 = -1,
alpharefractsx1 = -1, alpharefractsx2 = 1, alpharefractsy1 = -1, alpharefractsy2 = 1;
uint alphatiles[LIGHTTILE_MAXH];
int findalphavas()
{
alphavas.setsize(0);
alphafrontsx1 = alphafrontsy1 = alphabacksx1 = alphabacksy1 = alpharefractsx1 = alpharefractsy1 = 1;
alphafrontsx2 = alphafrontsy2 = alphabacksx2 = alphabacksy2 = alpharefractsx2 = alpharefractsy2 = -1;
alphabackvas = alpharefractvas = 0;
memset(alphatiles, 0, sizeof(alphatiles));
for(vtxarray *va = visibleva; va; va = va->next) if(va->alphatris)
{
if(va->occluded >= OCCLUDE_BB) continue;
if(va->occluded >= OCCLUDE_GEOM && pvsoccluded(va->alphamin, va->alphamax)) continue;
if(va->curvfc==VFC_FOGGED) continue;
float sx1 = -1, sx2 = 1, sy1 = -1, sy2 = 1;
if(!calcbbscissor(va->alphamin, va->alphamax, sx1, sy1, sx2, sy2)) continue;
alphavas.add(va);
masktiles(alphatiles, sx1, sy1, sx2, sy2);
alphafrontsx1 = min(alphafrontsx1, sx1);
alphafrontsy1 = min(alphafrontsy1, sy1);
alphafrontsx2 = max(alphafrontsx2, sx2);
alphafrontsy2 = max(alphafrontsy2, sy2);
if(va->alphabacktris)
{
alphabackvas++;
alphabacksx1 = min(alphabacksx1, sx1);
alphabacksy1 = min(alphabacksy1, sy1);
alphabacksx2 = max(alphabacksx2, sx2);
alphabacksy2 = max(alphabacksy2, sy2);
}
if(va->refracttris)
{
if(!calcbbscissor(va->refractmin, va->refractmax, sx1, sy1, sx2, sy2)) continue;
alpharefractvas++;
alpharefractsx1 = min(alpharefractsx1, sx1);
alpharefractsy1 = min(alpharefractsy1, sy1);
alpharefractsx2 = max(alpharefractsx2, sx2);
alpharefractsy2 = max(alpharefractsy2, sy2);
}
}
return (alpharefractvas ? 4 : 0) | (alphavas.length() ? 2 : 0) | (alphabackvas ? 1 : 0);
}
void renderrefractmask()
{
gle::enablevertex();
vtxarray *prev = nullptr;
loopv(alphavas)
{
vtxarray *va = alphavas[i];
if(!va->refracttris) continue;
if(!prev || va->vbuf != prev->vbuf)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->ebuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
drawvatris(va, 3*va->refracttris, 3*(va->tris + va->blendtris + va->alphabacktris + va->alphafronttris));
xtravertsva += 3*va->refracttris;
prev = va;
}
gle::clearvbo();
gle::clearebo();
gle::disablevertex();
}
void renderalphageom(int side)
{
resetbatches();
renderstate cur;
cur.alphaing = side;
cur.alphascale = -1;
setupgeom(cur);
if(side == 2)
{
loopv(alphavas) renderva(cur, alphavas[i], RENDERPASS_GBUFFER);
if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER);
}
else
{
glCullFace(GL_FRONT);
loopv(alphavas) if(alphavas[i]->alphabacktris) renderva(cur, alphavas[i], RENDERPASS_GBUFFER);
if(geombatches.length()) renderbatches(cur, RENDERPASS_GBUFFER);
glCullFace(GL_BACK);
}
cleanupgeom(cur);
}
void renderalphashadow(bool cullside)
{
resetbatches();
renderstate cur;
cur.alphascale = -1;
setupgeom(cur);
glCullFace(cullside ? GL_FRONT : GL_BACK);
cur.alphaing = 1;
for(vtxarray *va = shadowva; va; va = va->rnext)
if(va->shadowtransparent&(1<<shadowside) && va->alphabacktris)
renderva(cur, va, RENDERPASS_SMALPHA);
if(geombatches.length()) renderbatches(cur, RENDERPASS_SMALPHA);
glCullFace(cullside ? GL_BACK : GL_FRONT);
cur.alphaing = 2;
for(vtxarray *va = shadowva; va; va = va->rnext)
if(va->shadowtransparent&(1<<shadowside) && va->alphatris)
renderva(cur, va, RENDERPASS_SMALPHA);
if(geombatches.length()) renderbatches(cur, RENDERPASS_SMALPHA);
glCullFace(GL_BACK);
cleanupgeom(cur);
}
CVARP(explicitskycolour, 0x800080);
bool renderexplicitsky(bool outline)
{
vtxarray *prev = nullptr;
for(vtxarray *va = visibleva; va; va = va->next) if(va->sky && va->occluded < OCCLUDE_BB &&
((va->skymax.x >= 0 && isvisiblebb(va->skymin, ivec(va->skymax).sub(va->skymin)) != VFC_NOT_VISIBLE) ||
!insideworld(camera1->o)))
{
if(!prev || va->vbuf != prev->vbuf)
{
if(!prev)
{
gle::enablevertex();
if(outline)
{
ldrnotextureshader->set();
gle::color(explicitskycolour);
glDepthMask(GL_FALSE);
enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else if(editmode)
{
maskgbuffer("d");
SETSHADER(depth);
}
else
{
nocolorshader->set();
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
}
}
gle::bindvbo(va->vbuf);
gle::bindebo(va->skybuf);
const vertex *ptr = 0;
gle::vertexpointer(sizeof(vertex), ptr->pos.v);
}
drawvaskytris(va);
xtraverts += va->sky;
prev = va;
}
if(!prev) return false;
if(outline)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
glDepthMask(GL_TRUE);
}
else if(editmode)
{
maskgbuffer("cnd");
}
else
{
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
gle::disablevertex();
gle::clearvbo();
gle::clearebo();
return true;
}
struct decalrenderer
{
GLuint vbuf;
vec colorscale;
int globals, tmu;
GLuint textures[7];
DecalSlot *slot;
decalrenderer() : vbuf(0), colorscale(1, 1, 1), globals(-1), tmu(-1), slot(nullptr)
{
loopi(7) textures[i] = 0;
}
};
struct decalbatch
{
const elementset &es;
DecalSlot &slot;
int offset;
vtxarray *va;
int next, batch;
decalbatch(const elementset &es, int offset, vtxarray *va)
: es(es), slot(lookupdecalslot(es.texture)), offset(offset), va(va),
next(-1), batch(-1)
{}
int compare(const decalbatch &b) const
{
if(va->vbuf < b.va->vbuf) return -1;
if(va->vbuf > b.va->vbuf) return 1;
if(slot.shader < b.slot.shader) return -1;
if(slot.shader > b.slot.shader) return 1;
if(es.texture < b.es.texture) return -1;
if(es.texture > b.es.texture) return 1;
if(es.envmap < b.es.envmap) return -1;
if(es.envmap > b.es.envmap) return 1;
if(slot.Slot::params.length() < b.slot.Slot::params.length()) return -1;
if(slot.Slot::params.length() > b.slot.Slot::params.length()) return 1;
if(es.reuse < b.es.reuse) return -1;
if(es.reuse > b.es.reuse) return 1;
return 0;
}
};
static vector<decalbatch> decalbatches;
static void mergedecals(decalrenderer &cur, vtxarray *va)
{
elementset *texs = va->decalelems;
int numtexs = va->decaltexs, offset = 0;
if(firstbatch < 0)
{
firstbatch = decalbatches.length();
numbatches = numtexs;
loopi(numtexs-1)
{
decalbatches.add(decalbatch(texs[i], offset, va)).next = i+1;
offset += texs[i].length;
}
decalbatches.add(decalbatch(texs[numtexs-1], offset, va));
return;
}
int prevbatch = -1, curbatch = firstbatch, curtex = 0;
do
{
decalbatch &b = decalbatches.add(decalbatch(texs[curtex], offset, va));
offset += texs[curtex].length;
int dir = -1;
while(curbatch >= 0)
{
dir = b.compare(decalbatches[curbatch]);
if(dir <= 0) break;
prevbatch = curbatch;
curbatch = decalbatches[curbatch].next;
}
if(!dir)
{
int last = curbatch, next;
for(;;)
{
next = decalbatches[last].batch;
if(next < 0) break;
last = next;
}
if(last==curbatch)
{
b.batch = curbatch;
b.next = decalbatches[curbatch].next;
if(prevbatch < 0) firstbatch = decalbatches.length()-1;
else decalbatches[prevbatch].next = decalbatches.length()-1;
curbatch = decalbatches.length()-1;
}
else
{
b.batch = next;
decalbatches[last].batch = decalbatches.length()-1;
}
}
else
{
numbatches++;
b.next = curbatch;
if(prevbatch < 0) firstbatch = decalbatches.length()-1;
else decalbatches[prevbatch].next = decalbatches.length()-1;
prevbatch = decalbatches.length()-1;
}
}
while(++curtex < numtexs);
}
static void resetdecalbatches()
{
decalbatches.setsize(0);
firstbatch = -1;
numbatches = 0;
}
static void changevbuf(decalrenderer &cur, int pass, vtxarray *va)
{
gle::bindvbo(va->vbuf);
gle::bindebo(va->decalbuf);
cur.vbuf = va->vbuf;
vertex *vdata = (vertex *)0;
gle::vertexpointer(sizeof(vertex), vdata->pos.v);
gle::normalpointer(sizeof(vertex), vdata->norm.v, GL_BYTE, 4);
gle::texcoord0pointer(sizeof(vertex), vdata->tc.v, GL_FLOAT, 3);
gle::tangentpointer(sizeof(vertex), vdata->tangent.v, GL_BYTE);
}
static void changebatchtmus(decalrenderer &cur, int pass, decalbatch &b)
{
if(b.slot.shader->type&SHADER_ENVMAP && b.es.envmap!=EMID_CUSTOM)
{
GLuint emtex = lookupenvmap(b.es.envmap);
if(cur.textures[TEX_ENVMAP]!=emtex)
{
cur.tmu = TEX_ENVMAP;
glActiveTexture_(GL_TEXTURE0 + TEX_ENVMAP);
glBindTexture(GL_TEXTURE_CUBE_MAP, cur.textures[TEX_ENVMAP] = emtex);
}
}
if(cur.tmu != 0)
{
cur.tmu = 0;
glActiveTexture_(GL_TEXTURE0);
}
}
static inline void bindslottex(decalrenderer &cur, int type, Texture *tex, GLenum target = GL_TEXTURE_2D)
{
if(cur.textures[type] != tex->id)
{
if(cur.tmu != type)
{
cur.tmu = type;
glActiveTexture_(GL_TEXTURE0 + type);
}
glBindTexture(target, cur.textures[type] = tex->id);
}
}
static void changeslottmus(decalrenderer &cur, int pass, DecalSlot &slot)
{
Texture *diffuse = slot.sts.empty() ? notexture : slot.sts[0].t;
bindslottex(cur, TEX_DIFFUSE, diffuse);
loopv(slot.sts)
{
Slot::Tex &t = slot.sts[i];
switch(t.type)
{
case TEX_ENVMAP:
if(t.t) bindslottex(cur, t.type, t.t, GL_TEXTURE_CUBE_MAP);
break;
case TEX_NORMAL:
case TEX_GLOW:
bindslottex(cur, t.type, t.t);
break;
case TEX_SPEC:
if(t.combined < 0) bindslottex(cur, TEX_GLOW, t.t);
break;
}
}
if(cur.tmu != 0)
{
cur.tmu = 0;
glActiveTexture_(GL_TEXTURE0);
}
if(cur.colorscale != slot.colorscale)
{
cur.colorscale = slot.colorscale;
GLOBALPARAMF(colorparams, slot.colorscale.x, slot.colorscale.y, slot.colorscale.z, 1);
}
cur.slot = &slot;
}
static inline void changeshader(decalrenderer &cur, int pass, decalbatch &b)
{
DecalSlot &slot = b.slot;
if(b.es.reuse)
{
VSlot &reuse = lookupvslot(b.es.reuse);
if(pass) slot.shader->setvariant(0, 0, slot, reuse);
else slot.shader->set(slot, reuse);
}
else if(pass) slot.shader->setvariant(0, 0, slot);
else slot.shader->set(slot);
cur.globals = GlobalShaderParamState::nextversion;
}
static void renderdecalbatch(decalrenderer &cur, int pass, decalbatch &b)
{
gbatches++;
for(decalbatch *curbatch = &b;; curbatch = &decalbatches[curbatch->batch])
{
ushort len = curbatch->es.length;
if(len)
{
drawtris(len, (ushort *)0 + curbatch->va->decaloffset + curbatch->offset, curbatch->es.minvert, curbatch->es.maxvert);
vtris += len/3;
}
if(curbatch->batch < 0) break;
}
}
static void renderdecalbatches(decalrenderer &cur, int pass)
{
cur.slot = nullptr;
int curbatch = firstbatch;
while(curbatch >= 0)
{
decalbatch &b = decalbatches[curbatch];
curbatch = b.next;
if(pass && !b.slot.shader->numvariants(0)) continue;
if(cur.vbuf != b.va->vbuf) changevbuf(cur, pass, b.va);
changebatchtmus(cur, pass, b);
if(cur.slot != &b.slot)
{
changeslottmus(cur, pass, b.slot);
changeshader(cur, pass, b);
}
else
{
updateshader(cur);
}
renderdecalbatch(cur, pass, b);
}
resetdecalbatches();
}
static void setupdecals(decalrenderer &cur)
{
gle::enablevertex();
gle::enablenormal();
gle::enabletexcoord0();
gle::enabletangent();
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
GLOBALPARAMF(colorparams, 1, 1, 1, 1);
}
static void cleanupdecals(decalrenderer &cur)
{
disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
glDisable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
maskgbuffer("cnd");
gle::disablevertex();
gle::disablenormal();
gle::disabletexcoord0();
gle::disabletangent();
gle::clearvbo();
gle::clearebo();
}
VAR(batchdecals, 0, 1, 1);
void renderdecals()
{
vtxarray *decalva;
for(decalva = visibleva; decalva; decalva = decalva->next) if(decalva->decaltris && decalva->occluded < OCCLUDE_BB) break;
if(!decalva) return;
decalrenderer cur;
setupdecals(cur);
resetdecalbatches();
if(maxdualdrawbufs)
{
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC1_ALPHA);
maskgbuffer("c");
for(vtxarray *va = decalva; va; va = va->next) if(va->decaltris && va->occluded < OCCLUDE_BB)
{
mergedecals(cur, va);
if(!batchdecals && decalbatches.length()) renderdecalbatches(cur, 0);
}
if(decalbatches.length()) renderdecalbatches(cur, 0);
if(usepacknorm())
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
}
else glBlendFunc(GL_SRC1_ALPHA, GL_ONE_MINUS_SRC1_ALPHA);
maskgbuffer("n");
cur.vbuf = 0;
for(vtxarray *va = decalva; va; va = va->next) if(va->decaltris && va->occluded < OCCLUDE_BB)
{
mergedecals(cur, va);
if(!batchdecals && decalbatches.length()) renderdecalbatches(cur, 1);
}
if(decalbatches.length()) renderdecalbatches(cur, 1);
}
else
{
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
maskgbuffer("cn");
for(vtxarray *va = decalva; va; va = va->next) if(va->decaltris && va->occluded < OCCLUDE_BB)
{
mergedecals(cur, va);
if(!batchdecals && decalbatches.length()) renderdecalbatches(cur, 0);
}
if(decalbatches.length()) renderdecalbatches(cur, 0);
}
cleanupdecals(cur);
}
struct shadowmesh
{
vec origin;
float radius;
vec spotloc;
int spotangle;
int type;
int draws[6];
};
struct shadowdraw
{
GLuint ebuf, vbuf;
int offset, tris, next;
ushort minvert, maxvert;
};
struct shadowverts
{
static const int SIZE = 1<<13;
int table[SIZE];
vector<vec> verts;
vector<int> chain;
shadowverts() { clear(); }
void clear()
{
memset(table, -1, sizeof(table));
chain.setsize(0);
verts.setsize(0);
}
int add(const vec &v)
{
uint h = hthash(v)&(SIZE-1);
for(int i = table[h]; i>=0; i = chain[i]) if(verts[i] == v) return i;
if(verts.length() >= USHRT_MAX) return -1;
verts.add(v);
chain.add(table[h]);
return table[h] = verts.length()-1;
}
} shadowverts;
static vector<ushort> shadowtris[6];
static vector<GLuint> shadowvbos;
static hashtable<int, shadowmesh> shadowmeshes;
static vector<shadowdraw> shadowdraws;
struct shadowdrawinfo
{
int last;
ushort minvert, maxvert;
shadowdrawinfo() : last(-1) { reset(); }
void reset() { minvert = USHRT_MAX; maxvert = 0; }
};
static void flushshadowmeshdraws(shadowmesh &m, int sides, shadowdrawinfo draws[6])
{
int numindexes = 0;
loopi(sides) numindexes += shadowtris[i].length();
if(!numindexes) return;
GLuint ebuf = 0, vbuf = 0;
glGenBuffers_(1, &ebuf);
glGenBuffers_(1, &vbuf);
ushort *indexes = new ushort[numindexes];
int offset = 0;
loopi(sides) if(shadowtris[i].length())
{
if(draws[i].last < 0) m.draws[i] = shadowdraws.length();
else shadowdraws[draws[i].last].next = shadowdraws.length();
draws[i].last = shadowdraws.length();
shadowdraw &d = shadowdraws.add();
d.ebuf = ebuf;
d.vbuf = vbuf;
d.offset = offset;
d.tris = shadowtris[i].length()/3;
d.minvert = draws[i].minvert;
d.maxvert = draws[i].maxvert;
d.next = -1;
memcpy(indexes + offset, shadowtris[i].getbuf(), shadowtris[i].length()*sizeof(ushort));
offset += shadowtris[i].length();
shadowtris[i].setsize(0);
draws[i].reset();
}
gle::bindebo(ebuf);
glBufferData_(GL_ELEMENT_ARRAY_BUFFER, numindexes*sizeof(ushort), indexes, GL_STATIC_DRAW);
gle::clearebo();
delete[] indexes;
gle::bindvbo(vbuf);
glBufferData_(GL_ARRAY_BUFFER, shadowverts.verts.length()*sizeof(vec), shadowverts.verts.getbuf(), GL_STATIC_DRAW);
gle::clearvbo();
shadowverts.clear();
shadowvbos.add(ebuf);
shadowvbos.add(vbuf);
}
static inline void addshadowmeshtri(shadowmesh &m, int sides, shadowdrawinfo draws[6], const vec &v0, const vec &v1, const vec &v2)
{
extern int smcullside;
vec l0 = vec(v0).sub(shadoworigin);
float side = l0.scalartriple(vec(v1).sub(v0), vec(v2).sub(v0));
if(smcullside ? side > 0 : side < 0) return;
vec l1 = vec(v1).sub(shadoworigin), l2 = vec(v2).sub(shadoworigin);
if(l0.squaredlen() > shadowradius*shadowradius && l1.squaredlen() > shadowradius*shadowradius && l2.squaredlen() > shadowradius*shadowradius)
return;
int sidemask = 0;
switch(m.type)
{
case SM_SPOT: sidemask = bbinsidespot(shadoworigin, shadowdir, shadowspot, ivec(vec(v0).min(v1).min(v2)), ivec(vec(v0).max(v1).max(v2).add(1))) ? 1 : 0; break;
case SM_CUBEMAP: sidemask = calctrisidemask(l0.div(shadowradius), l1.div(shadowradius), l2.div(shadowradius), shadowbias); break;
}
if(!sidemask) return;
if(shadowverts.verts.length() + 3 >= USHRT_MAX) flushshadowmeshdraws(m, sides, draws);
int i0 = shadowverts.add(v0), i1 = shadowverts.add(v1), i2 = shadowverts.add(v2);
ushort minvert = min(i0, min(i1, i2)), maxvert = max(i0, max(i1, i2));
loopk(sides) if(sidemask&(1<<k))
{
shadowdrawinfo &d = draws[k];
d.minvert = min(d.minvert, minvert);
d.maxvert = max(d.maxvert, maxvert);
shadowtris[k].add(i0);
shadowtris[k].add(i1);
shadowtris[k].add(i2);
}
}
static void genshadowmeshtris(shadowmesh &m, int sides, shadowdrawinfo draws[6], ushort *edata, int numtris, vertex *vdata)
{
for(int j = 0; j < 3*numtris; j += 3) addshadowmeshtri(m, sides, draws, vdata[edata[j]].pos, vdata[edata[j+1]].pos, vdata[edata[j+2]].pos);
}
static void genshadowmeshmapmodels(shadowmesh &m, int sides, shadowdrawinfo draws[6])
{
const vector<extentity *> &ents = entities::getents();
for(octaentities *oe = shadowmms; oe; oe = oe->rnext) loopvk(oe->mapmodels)
{
extentity &e = *ents[oe->mapmodels[k]];
if(e.flags&(EF_NOVIS|EF_NOSHADOW)) continue;
e.flags |= EF_RENDER;
}
vector<triangle> tris;
for(octaentities *oe = shadowmms; oe; oe = oe->rnext) loopvj(oe->mapmodels)
{
extentity &e = *ents[oe->mapmodels[j]];
if(!(e.flags&EF_RENDER)) continue;
e.flags &= ~EF_RENDER;
model *mm = loadmapmodel(e.attr1);
if(!mm || !mm->shadow || mm->animated() || (mm->alphashadow && mm->alphatested())) continue;
matrix4x3 orient;
orient.identity();
if(e.attr2) orient.rotate_around_z(sincosmod360(e.attr2));
if(e.attr3) orient.rotate_around_x(sincosmod360(e.attr3));
if(e.attr4) orient.rotate_around_y(sincosmod360(-e.attr4));
if(e.attr5 > 0) orient.scale(e.attr5/100.0f);
orient.settranslation(e.o);
tris.setsize(0);
mm->genshadowmesh(tris, orient);
loopv(tris)
{
triangle &t = tris[i];
addshadowmeshtri(m, sides, draws, t.a, t.b, t.c);
}
e.flags |= EF_SHADOWMESH;
}
}
static void genshadowmesh(int idx, extentity &e)
{
shadowmesh m;
m.type = calcshadowinfo(e, m.origin, m.radius, m.spotloc, m.spotangle, shadowbias);
if(!m.type) return;
memset(m.draws, -1, sizeof(m.draws));
shadowmapping = m.type;
shadoworigin = m.origin;
shadowradius = m.radius;
shadowdir = m.type == SM_SPOT ? vec(m.spotloc).sub(m.origin).normalize() : vec(0, 0, 0);
shadowspot = m.spotangle;
findshadowvas();
findshadowmms();
int sides = m.type == SM_SPOT ? 1 : 6;
shadowdrawinfo draws[6];
for(vtxarray *va = shadowva; va; va = va->rnext) if(va->shadowmask)
{
if(va->tris) genshadowmeshtris(m, sides, draws, va->edata + va->eoffset, va->tris, va->vdata);
if(skyshadow && va->sky) genshadowmeshtris(m, sides, draws, va->skydata + va->skyoffset, va->sky/3, va->vdata);
}
if(shadowmms) genshadowmeshmapmodels(m, sides, draws);
flushshadowmeshdraws(m, sides, draws);
shadowmeshes[idx] = m;
shadowmapping = 0;
}
void clearshadowmeshes()
{
if(shadowvbos.length()) { glDeleteBuffers_(shadowvbos.length(), shadowvbos.getbuf()); shadowvbos.setsize(0); }
if(shadowmeshes.numelems)
{
vector<extentity *> &ents = entities::getents();
loopv(ents)
{
extentity &e = *ents[i];
if(e.flags&EF_SHADOWMESH) e.flags &= ~EF_SHADOWMESH;
}
}
shadowmeshes.clear();
shadowdraws.setsize(0);
}
VARF(smmesh, 0, 1, 1, { if(!smmesh) clearshadowmeshes(); });
void genshadowmeshes()
{
clearshadowmeshes();
if(!smmesh) return;
renderprogress(0, "generating shadow meshes..");
vector<extentity *> &ents = entities::getents();
loopv(ents)
{
extentity &e = *ents[i];
if(e.type != ET_LIGHT) continue;
genshadowmesh(i, e);
}
}
shadowmesh *findshadowmesh(int idx, extentity &e)
{
shadowmesh *m = shadowmeshes.access(idx);
if(!m || m->type != shadowmapping || m->origin != shadoworigin || m->radius < shadowradius) return nullptr;
switch(m->type)
{
case SM_SPOT:
if(!e.attached || e.attached->type != ET_SPOTLIGHT || m->spotloc != e.attached->o || m->spotangle < std::clamp(int(e.attached->attr1), 1, 89))
return nullptr;
break;
}
return m;
}
void rendershadowmesh(shadowmesh *m)
{
int draw = m->draws[shadowside];
if(draw < 0) return;
SETSHADER(smworld);
gle::enablevertex();
GLuint ebuf = 0, vbuf = 0;
while(draw >= 0)
{
shadowdraw &d = shadowdraws[draw];
if(ebuf != d.ebuf) { gle::bindebo(d.ebuf); ebuf = d.ebuf; }
if(vbuf != d.vbuf) { gle::bindvbo(d.vbuf); vbuf = d.vbuf; gle::vertexpointer(sizeof(vec), 0); }
drawtris(3*d.tris, (ushort *)0 + d.offset, d.minvert, d.maxvert);
xtravertsva += 3*d.tris;
draw = d.next;
}
gle::disablevertex();
gle::clearebo();
gle::clearvbo();
}