OctaCore/src/engine/renderlights.cc

5355 lines
182 KiB
C++

#include "renderlights.hh"
#include <climits>
#include <algorithm>
#include <shared/command.hh>
#include <shared/glemu.hh>
#include <shared/igame.hh>
#include "aa.hh"
#include "dynlight.hh"
#include "light.hh"
#include "main.hh" // initwarning, fatal, timings
#include "material.hh"
#include "octaedit.hh" // editmode
#include "octarender.hh"
#include "pvs.hh"
#include "rendergl.hh"
#include "rendermodel.hh"
#include "renderparticles.hh"
#include "rendersky.hh"
#include "renderva.hh"
#include "stain.hh"
#include "texture.hh"
#include "world.hh"
#define CHANGE_SHADERS 0
int gw = -1, gh = -1, bloomw = -1, bloomh = -1, lasthdraccum = 0;
GLuint gfbo = 0, gdepthtex = 0, gcolortex = 0, gnormaltex = 0, gglowtex = 0;
static GLuint gdepthrb = 0, gstencilrb = 0;
bool gdepthinit = false;
int scalew = -1, scaleh = -1;
GLuint scalefbo[2] = { 0, 0 }, scaletex[2] = { 0, 0 };
GLuint hdrfbo = 0, hdrtex = 0, bloompbo = 0, bloomfbo[6] = { 0, 0, 0, 0, 0, 0 }, bloomtex[6] = { 0, 0, 0, 0, 0, 0 };
int hdrclear = 0;
GLuint refractfbo = 0, refracttex = 0;
GLenum bloomformat = 0, hdrformat = 0, stencilformat = 0;
bool hdrfloat = false;
GLuint msfbo = 0, msdepthtex = 0, mscolortex = 0, msnormaltex = 0, msglowtex = 0;
static GLuint msdepthrb = 0, msstencilrb = 0, mshdrfbo = 0, mshdrtex = 0, msrefractfbo = 0, msrefracttex = 0;
vector<vec2> msaapositions;
int aow = -1, aoh = -1;
GLuint aofbo[4] = { 0, 0, 0, 0 }, aotex[4] = { 0, 0, 0, 0 }, aonoisetex = 0;
matrix4 eyematrix, worldmatrix, linearworldmatrix, screenmatrix;
extern int amd_pf_bug;
int gethdrformat(int prec, int fallback = GL_RGB)
{
if(prec >= 3 && hasTF) return GL_RGB16F;
if(prec >= 2 && hasPF && !amd_pf_bug) return GL_R11F_G11F_B10F;
if(prec >= 1) return GL_RGB10;
return fallback;
}
extern int bloomsize, bloomprec;
void setupbloom(int w, int h)
{
int maxsize = ((1<<bloomsize)*5)/4;
while(w >= maxsize || h >= maxsize)
{
w /= 2;
h /= 2;
}
w = max(w, 1);
h = max(h, 1);
if(w == bloomw && h == bloomh) return;
bloomw = w;
bloomh = h;
loopi(5) if(!bloomtex[i]) glGenTextures(1, &bloomtex[i]);
loopi(5) if(!bloomfbo[i]) glGenFramebuffers_(1, &bloomfbo[i]);
bloomformat = gethdrformat(bloomprec);
createtexture(bloomtex[0], max(gw/2, bloomw), max(gh/2, bloomh), nullptr, 3, 1, bloomformat, GL_TEXTURE_RECTANGLE);
createtexture(bloomtex[1], max(gw/4, bloomw), max(gh/4, bloomh), nullptr, 3, 1, bloomformat, GL_TEXTURE_RECTANGLE);
createtexture(bloomtex[2], bloomw, bloomh, nullptr, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE);
createtexture(bloomtex[3], bloomw, bloomh, nullptr, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE);
if(bloomformat != GL_RGB)
{
if(!bloomtex[5]) glGenTextures(1, &bloomtex[5]);
if(!bloomfbo[5]) glGenFramebuffers_(1, &bloomfbo[5]);
createtexture(bloomtex[5], bloomw, bloomh, nullptr, 3, 1, bloomformat, GL_TEXTURE_RECTANGLE);
}
if(hwvtexunits < 4)
{
glGenBuffers_(1, &bloompbo);
glBindBuffer_(GL_PIXEL_PACK_BUFFER, bloompbo);
glBufferData_(GL_PIXEL_PACK_BUFFER, 4*(hasTF ? sizeof(GLfloat) : sizeof(GLushort))*(hasTRG ? 1 : 3), nullptr, GL_DYNAMIC_COPY);
glBindBuffer_(GL_PIXEL_PACK_BUFFER, 0);
}
static const uchar gray[12] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32 };
static const float grayf[12] = { 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f, 0.125f };
createtexture(bloomtex[4], bloompbo ? 4 : 1, 1, hasTF ? (const void *)grayf : (const void *)gray, 3, 1, hasTF ? (hasTRG ? GL_R16F : GL_RGB16F) : (hasTRG ? GL_R16 : GL_RGB16));
loopi(5 + (bloomformat != GL_RGB ? 1 : 0))
{
glBindFramebuffer_(GL_FRAMEBUFFER, bloomfbo[i]);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, i==4 ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE, bloomtex[i], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating bloom buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
}
void cleanupbloom()
{
if(bloompbo) { glDeleteBuffers_(1, &bloompbo); bloompbo = 0; }
loopi(6) if(bloomfbo[i]) { glDeleteFramebuffers_(1, &bloomfbo[i]); bloomfbo[i] = 0; }
loopi(6) if(bloomtex[i]) { glDeleteTextures(1, &bloomtex[i]); bloomtex[i] = 0; }
bloomw = bloomh = -1;
lasthdraccum = 0;
}
extern int ao, aotaps, aoreduce, aoreducedepth, aonoise, aobilateral, aobilateralupscale, aopackdepth, aodepthformat, aoprec, aoderivnormal;
static Shader *bilateralshader[2] = { nullptr, nullptr };
Shader *loadbilateralshader(int pass)
{
if(!aobilateral) return nullshader;
string opts;
int optslen = 0;
bool linear = aoreducedepth && (aoreduce || aoreducedepth > 1),
upscale = aoreduce && aobilateralupscale,
reduce = aoreduce && (upscale || (!linear && !aopackdepth));
if(reduce)
{
opts[optslen++] = 'r';
opts[optslen++] = '0' + aoreduce;
}
if(upscale) opts[optslen++] = 'u';
else if(linear) opts[optslen++] = 'l';
if(aopackdepth) opts[optslen++] = 'p';
opts[optslen] = '\0';
defformatstring(name, "bilateral%c%s%d", 'x' + pass, opts, aobilateral);
return generateshader(name, "bilateralshader \"%s\" %d %d", opts, aobilateral, reduce ? aoreduce : 0);
}
void loadbilateralshaders()
{
loopk(2) bilateralshader[k] = loadbilateralshader(k);
}
void clearbilateralshaders()
{
loopk(2) bilateralshader[k] = nullptr;
}
void setbilateralparams(int radius, float depth)
{
float sigma = blursigma*2*radius;
LOCALPARAMF(bilateralparams, 1.0f/(M_LN2*2*sigma*sigma), 1.0f/(M_LN2*depth*depth));
}
void setbilateralshader(int radius, int pass, float depth)
{
bilateralshader[pass]->set();
setbilateralparams(radius, depth);
}
static Shader *ambientobscuranceshader = nullptr;
Shader *loadambientobscuranceshader()
{
string opts;
int optslen = 0;
bool linear = aoreducedepth && (aoreduce || aoreducedepth > 1);
if(linear) opts[optslen++] = 'l';
if(aoderivnormal) opts[optslen++] = 'd';
if(aobilateral && aopackdepth) opts[optslen++] = 'p';
opts[optslen] = '\0';
defformatstring(name, "ambientobscurance%s%d", opts, aotaps);
return generateshader(name, "ambientobscuranceshader \"%s\" %d", opts, aotaps);
}
void loadaoshaders()
{
ambientobscuranceshader = loadambientobscuranceshader();
}
void clearaoshaders()
{
ambientobscuranceshader = nullptr;
}
void setupao(int w, int h)
{
int sw = w>>aoreduce, sh = h>>aoreduce;
if(sw == aow && sh == aoh) return;
aow = sw;
aoh = sh;
if(!aonoisetex) glGenTextures(1, &aonoisetex);
bvec *noise = new bvec[(1<<aonoise)*(1<<aonoise)];
loopk((1<<aonoise)*(1<<aonoise)) noise[k] = bvec(vec(rndscale(2)-1, rndscale(2)-1, 0).normalize());
createtexture(aonoisetex, 1<<aonoise, 1<<aonoise, noise, 0, 0, GL_RGB, GL_TEXTURE_2D);
delete[] noise;
bool upscale = aoreduce && aobilateral && aobilateralupscale;
GLenum format = aoprec && hasTRG ? GL_R8 : GL_RGBA8,
packformat = aobilateral && aopackdepth ? (aodepthformat ? GL_RG16F : GL_RGBA8) : format;
int packfilter = upscale && aopackdepth && !aodepthformat ? 0 : 1;
loopi(upscale ? 3 : 2)
{
if(!aotex[i]) glGenTextures(1, &aotex[i]);
if(!aofbo[i]) glGenFramebuffers_(1, &aofbo[i]);
createtexture(aotex[i], upscale && i ? w : aow, upscale && i >= 2 ? h : aoh, nullptr, 3, i < 2 ? packfilter : 1, i < 2 ? packformat : format, GL_TEXTURE_RECTANGLE);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[i]);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, aotex[i], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating AO buffer!");
if(!upscale && packformat == GL_RG16F)
{
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
}
if(aoreducedepth && (aoreduce || aoreducedepth > 1))
{
if(!aotex[3]) glGenTextures(1, &aotex[3]);
if(!aofbo[3]) glGenFramebuffers_(1, &aofbo[3]);
createtexture(aotex[3], aow, aoh, nullptr, 3, 0, aodepthformat > 1 ? GL_R32F : (aodepthformat ? GL_R16F : GL_RGBA8), GL_TEXTURE_RECTANGLE);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[3]);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, aotex[3], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating AO buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadaoshaders();
loadbilateralshaders();
}
void cleanupao()
{
loopi(4) if(aofbo[i]) { glDeleteFramebuffers_(1, &aofbo[i]); aofbo[i] = 0; }
loopi(4) if(aotex[i]) { glDeleteTextures(1, &aotex[i]); aotex[i] = 0; }
if(aonoisetex) { glDeleteTextures(1, &aonoisetex); aonoisetex = 0; }
aow = aoh = -1;
clearaoshaders();
clearbilateralshaders();
}
VARFP(ao, 0, 1, 1, { cleanupao(); cleardeferredlightshaders(); });
FVARR(aoradius, 0, 5, 256);
FVAR(aocutoff, 0, 2.0f, 1e3f);
FVARR(aodark, 1e-3f, 11.0f, 1e3f);
FVARR(aosharp, 1e-3f, 1, 1e3f);
FVAR(aoprefilterdepth, 0, 1, 1e3f);
FVARR(aomin, 0, 0.25f, 1);
VARFR(aosun, 0, 1, 1, cleardeferredlightshaders());
FVARR(aosunmin, 0, 0.5f, 1);
VARP(aoblur, 0, 4, 7);
VARP(aoiter, 0, 0, 4);
VARFP(aoreduce, 0, 1, 2, cleanupao());
VARF(aoreducedepth, 0, 1, 2, cleanupao());
VARFP(aofloatdepth, 0, 1, 2, initwarning("AO setup", INIT_LOAD, CHANGE_SHADERS));
VARFP(aoprec, 0, 1, 1, cleanupao());
VAR(aodepthformat, 1, 0, 0);
VARF(aonoise, 0, 5, 8, cleanupao());
VARFP(aobilateral, 0, 3, 10, cleanupao());
FVARP(aobilateraldepth, 0, 4, 1e3f);
VARFP(aobilateralupscale, 0, 0, 1, cleanupao());
VARF(aopackdepth, 0, 1, 1, cleanupao());
VARFP(aotaps, 1, 5, 12, cleanupao());
VARF(aoderivnormal, 0, 0, 1, cleanupao());
VAR(aoderiv, -1, 1, 1);
VAR(debugao, 0, 0, 1);
void initao()
{
aodepthformat = aofloatdepth && hasTRG && hasTF ? aofloatdepth : 0;
}
void viewao()
{
if(!ao) return;
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[2] ? aotex[2] : aotex[0]);
int tw = aotex[2] ? gw : aow, th = aotex[2] ? gh : aoh;
debugquad(0, 0, w, h, 0, 0, tw, th);
}
void renderao()
{
if(!ao) return;
timer *aotimer = begintimer("ambient obscurance");
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
bool linear = aoreducedepth && (aoreduce || aoreducedepth > 1);
float xscale = eyematrix.a.x, yscale = eyematrix.b.y;
if(linear)
{
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[3]);
glViewport(0, 0, aow, aoh);
SETSHADER(linearizedepth);
screenquad(vieww, viewh);
xscale *= float(vieww)/aow;
yscale *= float(viewh)/aoh;
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[3]);
}
ambientobscuranceshader->set();
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[0]);
glViewport(0, 0, aow, aoh);
glActiveTexture_(GL_TEXTURE1);
if(aoderivnormal)
{
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
}
else
{
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msnormaltex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gnormaltex);
LOCALPARAM(normalmatrix, matrix3(cammatrix));
}
glActiveTexture_(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, aonoisetex);
glActiveTexture_(GL_TEXTURE0);
LOCALPARAMF(tapparams, aoradius*eyematrix.d.z/xscale, aoradius*eyematrix.d.z/yscale, aoradius*aoradius*aocutoff*aocutoff);
LOCALPARAMF(contrastparams, (2.0f*aodark)/aotaps, aosharp);
LOCALPARAMF(offsetscale, xscale/eyematrix.d.z, yscale/eyematrix.d.z, eyematrix.d.x/eyematrix.d.z, eyematrix.d.y/eyematrix.d.z);
LOCALPARAMF(prefilterdepth, aoprefilterdepth);
if(aoderiv >= 0) glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, aoderiv ? GL_NICEST : GL_FASTEST);
screenquad(vieww, viewh, aow/float(1<<aonoise), aoh/float(1<<aonoise));
if(aoderiv >= 0) glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_DONT_CARE);
if(aobilateral)
{
if(aoreduce && aobilateralupscale) loopi(2)
{
setbilateralshader(aobilateral, i, aobilateraldepth);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[i+1]);
glViewport(0, 0, vieww, i ? viewh : aoh);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[i]);
glActiveTexture_(GL_TEXTURE1);
if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE0);
screenquad(vieww, viewh, i ? vieww : aow, aoh);
}
else loopi(2 + 2*aoiter)
{
setbilateralshader(aobilateral, i%2, aobilateraldepth);
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[(i+1)%2]);
glViewport(0, 0, aow, aoh);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[i%2]);
glActiveTexture_(GL_TEXTURE1);
if(linear) glBindTexture(GL_TEXTURE_RECTANGLE, aotex[3]);
else if(msaasamples) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE0);
screenquad(vieww, viewh);
}
}
else if(aoblur)
{
float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1];
setupblurkernel(aoblur, blurweights, bluroffsets);
loopi(2 + 2*aoiter)
{
glBindFramebuffer_(GL_FRAMEBUFFER, aofbo[(i+1)%2]);
glViewport(0, 0, aow, aoh);
setblurshader(i%2, 1, aoblur, blurweights, bluroffsets, GL_TEXTURE_RECTANGLE);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[i%2]);
screenquad(aow, aoh);
}
}
glBindFramebuffer_(GL_FRAMEBUFFER, msaasamples ? msfbo : gfbo);
glViewport(0, 0, vieww, viewh);
endtimer(aotimer);
}
void cleanupscale()
{
loopi(2) if(scalefbo[i]) { glDeleteFramebuffers_(1, &scalefbo[i]); scalefbo[i] = 0; }
loopi(2) if(scaletex[i]) { glDeleteTextures(1, &scaletex[i]); scaletex[i] = 0; }
scalew = scaleh = -1;
}
extern int gscalecubic, gscalenearest;
void setupscale(int sw, int sh, int w, int h)
{
scalew = w;
scaleh = h;
loopi(gscalecubic ? 2 : 1)
{
if(!scaletex[i]) glGenTextures(1, &scaletex[i]);
if(!scalefbo[i]) glGenFramebuffers_(1, &scalefbo[i]);
glBindFramebuffer_(GL_FRAMEBUFFER, scalefbo[i]);
createtexture(scaletex[i], sw, i ? h : sh, nullptr, 3, gscalecubic || !gscalenearest ? 1 : 0, GL_RGB, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, scaletex[i], 0);
if(!i) bindgdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating scale buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
if(gscalecubic)
{
useshaderbyname("scalecubicx");
useshaderbyname("scalecubicy");
}
}
GLuint shouldscale()
{
return scalefbo[0];
}
void doscale(GLuint outfbo)
{
if(!scaletex[0]) return;
timer *scaletimer = begintimer("scaling");
if(gscalecubic)
{
glBindFramebuffer_(GL_FRAMEBUFFER, scalefbo[1]);
glViewport(0, 0, gw, hudh);
glBindTexture(GL_TEXTURE_RECTANGLE, scaletex[0]);
SETSHADER(scalecubicy);
screenquad(gw, gh);
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, hudw, hudh);
glBindTexture(GL_TEXTURE_RECTANGLE, scaletex[1]);
SETSHADER(scalecubicx);
screenquad(gw, hudh);
}
else
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, hudw, hudh);
glBindTexture(GL_TEXTURE_RECTANGLE, scaletex[0]);
SETSHADER(scalelinear);
screenquad(gw, gh);
}
endtimer(scaletimer);
}
VARFP(glineardepth, 0, 0, 3, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
VAR(gdepthformat, 1, 0, 0);
VARF(gstencil, 0, 0, 1, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
VARF(gdepthstencil, 0, 2, 2, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
VAR(ghasstencil, 1, 0, 0);
VARFP(msaa, 0, 0, 16, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARF(msaadepthstencil, 0, 2, 2, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARF(msaastencil, 0, 0, 1, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARF(msaaedgedetect, 0, 1, 1, cleanupgbuffer());
VARFP(msaalineardepth, -1, -1, 3, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
VARFP(msaatonemap, 0, 0, 1, cleanupgbuffer());
VARF(msaatonemapblit, 0, 0, 1, cleanupgbuffer());
VAR(msaamaxsamples, 1, 0, 0);
VAR(msaamaxdepthtexsamples, 1, 0, 0);
VAR(msaamaxcolortexsamples, 1, 0, 0);
VAR(msaaminsamples, 1, 0, 0);
VAR(msaasamples, 1, 0, 0);
VAR(msaalight, 1, 0, 0);
VARF(msaapreserve, -1, 0, 1, initwarning("MSAA setup", INIT_LOAD, CHANGE_SHADERS));
void checkmsaasamples()
{
GLuint tex;
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
GLint samples;
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaaminsamples, GL_RGBA8, 1, 1, GL_TRUE);
glGetTexLevelParameteriv(GL_TEXTURE_2D_MULTISAMPLE, 0, GL_TEXTURE_SAMPLES, &samples);
msaasamples = samples;
glDeleteTextures(1, &tex);
}
void initgbuffer()
{
msaamaxsamples = msaamaxdepthtexsamples = msaamaxcolortexsamples = msaaminsamples = msaasamples = msaalight = 0;
msaapositions.setsize(0);
if(hasFBMS && hasFBB && hasTMS)
{
GLint val;
glGetIntegerv(GL_MAX_SAMPLES, &val);
msaamaxsamples = val;
glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &val);
msaamaxdepthtexsamples = val;
glGetIntegerv(GL_MAX_COLOR_TEXTURE_SAMPLES, &val);
msaamaxcolortexsamples = val;
}
int maxsamples = min(msaamaxsamples, msaamaxcolortexsamples), reqsamples = min(msaa, maxsamples);
if(reqsamples >= 2)
{
msaaminsamples = 2;
while(msaaminsamples*2 <= reqsamples) msaaminsamples *= 2;
}
int lineardepth = glineardepth;
if(msaaminsamples)
{
if(msaamaxdepthtexsamples < msaaminsamples)
{
if(msaalineardepth > 0) lineardepth = msaalineardepth;
else if(!lineardepth) lineardepth = 1;
}
else if(msaalineardepth >= 0) lineardepth = msaalineardepth;
}
if(lineardepth > 1 && (!hasAFBO || !hasTF || !hasTRG)) gdepthformat = 1;
else gdepthformat = lineardepth;
if(msaaminsamples)
{
ghasstencil = (msaadepthstencil > 1 || (msaadepthstencil && gdepthformat)) && hasDS ? 2 : (msaastencil ? 1 : 0);
checkmsaasamples();
if(msaapreserve >= 0) msaalight = hasMSS ? 3 : (msaasamples==2 ? 2 : msaapreserve);
}
else ghasstencil = (gdepthstencil > 1 || (gdepthstencil && gdepthformat)) && hasDS ? 2 : (gstencil ? 1 : 0);
initao();
}
VARF(forcepacknorm, 0, 0, 1, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
bool usepacknorm() { return forcepacknorm || msaasamples || !useavatarmask(); }
ICOMMAND(usepacknorm, "", (), intret(usepacknorm() ? 1 : 0));
void maskgbuffer(const char *mask)
{
GLenum drawbufs[4];
int numbufs = 0;
while(*mask) switch(*mask++)
{
case 'c': drawbufs[numbufs++] = GL_COLOR_ATTACHMENT0; break;
case 'n': drawbufs[numbufs++] = GL_COLOR_ATTACHMENT1; break;
case 'd': if(gdepthformat) drawbufs[numbufs++] = GL_COLOR_ATTACHMENT3; break;
case 'g': drawbufs[numbufs++] = GL_COLOR_ATTACHMENT2; break;
}
glDrawBuffers_(numbufs, drawbufs);
}
extern int hdrprec, gscale;
void cleanupmsbuffer()
{
if(msfbo) { glDeleteFramebuffers_(1, &msfbo); msfbo = 0; }
if(msdepthtex) { glDeleteTextures(1, &msdepthtex); msdepthtex = 0; }
if(mscolortex) { glDeleteTextures(1, &mscolortex); mscolortex = 0; }
if(msnormaltex) { glDeleteTextures(1, &msnormaltex); msnormaltex = 0; }
if(msglowtex) { glDeleteTextures(1, &msglowtex); msglowtex = 0; }
if(msstencilrb) { glDeleteRenderbuffers_(1, &msstencilrb); msstencilrb = 0; }
if(msdepthrb) { glDeleteRenderbuffers_(1, &msdepthrb); msdepthrb = 0; }
if(mshdrfbo) { glDeleteFramebuffers_(1, &mshdrfbo); mshdrfbo = 0; }
if(mshdrtex) { glDeleteTextures(1, &mshdrtex); mshdrtex = 0; }
if(msrefractfbo) { glDeleteFramebuffers_(1, &msrefractfbo); msrefractfbo = 0; }
if(msrefracttex) { glDeleteTextures(1, &msrefracttex); msrefracttex = 0; }
}
void bindmsdepth()
{
if(gdepthformat)
{
glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, msdepthrb);
if(ghasstencil > 1) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msdepthrb);
else if(msaalight && ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msstencilrb);
}
else
{
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, msdepthtex, 0);
if(ghasstencil > 1) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D_MULTISAMPLE, msdepthtex, 0);
else if(msaalight && ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, msstencilrb);
}
}
void setupmsbuffer(int w, int h)
{
if(!msfbo) glGenFramebuffers_(1, &msfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, msfbo);
stencilformat = ghasstencil > 1 ? GL_DEPTH24_STENCIL8 : (ghasstencil ? GL_STENCIL_INDEX8 : 0);
if(gdepthformat)
{
if(!msdepthrb) glGenRenderbuffers_(1, &msdepthrb);
glBindRenderbuffer_(GL_RENDERBUFFER, msdepthrb);
glRenderbufferStorageMultisample_(GL_RENDERBUFFER, msaasamples, ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24, w, h);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(msaalight && ghasstencil == 1)
{
if(!msstencilrb) glGenRenderbuffers_(1, &msstencilrb);
glBindRenderbuffer_(GL_RENDERBUFFER, msstencilrb);
glRenderbufferStorageMultisample_(GL_RENDERBUFFER, msaasamples, GL_STENCIL_INDEX8, w, h);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(!msdepthtex) glGenTextures(1, &msdepthtex);
if(!mscolortex) glGenTextures(1, &mscolortex);
if(!msnormaltex) glGenTextures(1, &msnormaltex);
maskgbuffer(msaalight ? "cndg" : "cnd");
static const GLenum depthformats[] = { GL_RGBA8, GL_R16F, GL_R32F };
GLenum depthformat = gdepthformat ? depthformats[gdepthformat-1] : (ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, depthformat, w, h, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mscolortex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGBA8, w, h, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msnormaltex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGBA8, w, h, GL_TRUE);
if(msaalight)
{
if(!msglowtex) glGenTextures(1, &msglowtex);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msglowtex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, hasAFBO ? hdrformat : GL_RGBA8, w, h, GL_TRUE);
}
bindmsdepth();
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mscolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D_MULTISAMPLE, msnormaltex, 0);
if(msaalight) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D_MULTISAMPLE, msglowtex, 0);
if(gdepthformat) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_2D_MULTISAMPLE, msdepthtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
if(msaalight && hasAFBO)
{
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msglowtex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGBA8, w, h, GL_TRUE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D_MULTISAMPLE, msglowtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating MSAA g-buffer!");
}
else fatal("failed allocating MSAA g-buffer!");
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (ghasstencil ? GL_STENCIL_BUFFER_BIT : 0));
msaapositions.setsize(0);
loopi(msaasamples)
{
GLfloat vals[2];
glGetMultisamplefv_(GL_SAMPLE_POSITION, i, vals);
msaapositions.add(vec2(vals[0], vals[1]));
}
if(msaalight)
{
if(!mshdrtex) glGenTextures(1, &mshdrtex);
if(!mshdrfbo) glGenFramebuffers_(1, &mshdrfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, mshdrfbo);
bindmsdepth();
hdrformat = 0;
for(int prec = hdrprec; prec >= 0; prec--)
{
GLenum format = gethdrformat(prec);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
glGetError();
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, format, w, h, GL_TRUE);
if(glGetError() == GL_NO_ERROR)
{
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, mshdrtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
hdrformat = format;
break;
}
}
}
if(!hdrformat || glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating MSAA HDR buffer!");
if(!msrefracttex) glGenTextures(1, &msrefracttex);
if(!msrefractfbo) glGenFramebuffers_(1, &msrefractfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, msrefractfbo);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msrefracttex);
glTexImage2DMultisample_(GL_TEXTURE_2D_MULTISAMPLE, msaasamples, GL_RGB, w, h, GL_TRUE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msrefracttex, 0);
bindmsdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating MSAA refraction buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
useshaderbyname("msaaedgedetect");
useshaderbyname("msaaresolve");
useshaderbyname("msaareducew");
useshaderbyname("msaareduce");
if(!msaalight) useshaderbyname("msaaresolvedepth");
if(msaalight > 1 && msaatonemap)
{
useshaderbyname("msaatonemap");
if(msaalight > 2) useshaderbyname("msaatonemapsample");
}
}
void bindgdepth()
{
if(gdepthformat || msaalight)
{
glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gdepthrb);
if(ghasstencil > 1) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gdepthrb);
else if(!msaalight || ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gstencilrb);
}
else
{
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_RECTANGLE, gdepthtex, 0);
if(ghasstencil > 1) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_RECTANGLE, gdepthtex, 0);
else if(ghasstencil) glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, gstencilrb);
}
}
void setupgbuffer()
{
int sw = renderw, sh = renderh;
if(gscale != 100)
{
sw = max((renderw*gscale + 99)/100, 1);
sh = max((renderh*gscale + 99)/100, 1);
}
if(gw == sw && gh == sh && ((sw >= hudw && sh >= hudh && !scalefbo[0]) || (scalew == hudw && scaleh == hudh))) return;
cleanupscale();
cleanupbloom();
cleanupao();
cleanupvolumetric();
cleanupaa();
cleanuppostfx();
gw = sw;
gh = sh;
hdrformat = gethdrformat(hdrprec);
stencilformat = ghasstencil > 1 ? GL_DEPTH24_STENCIL8 : (ghasstencil ? GL_STENCIL_INDEX8 : 0);
if(msaasamples) setupmsbuffer(gw, gh);
hdrfloat = floatformat(hdrformat);
hdrclear = 3;
gdepthinit = false;
if(gdepthformat || msaalight)
{
if(!gdepthrb) glGenRenderbuffers_(1, &gdepthrb);
glBindRenderbuffer_(GL_RENDERBUFFER, gdepthrb);
glRenderbufferStorage_(GL_RENDERBUFFER, ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24, gw, gh);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(!msaalight && ghasstencil == 1)
{
if(!gstencilrb) glGenRenderbuffers_(1, &gstencilrb);
glBindRenderbuffer_(GL_RENDERBUFFER, gstencilrb);
glRenderbufferStorage_(GL_RENDERBUFFER, GL_STENCIL_INDEX8, gw, gh);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
}
if(!msaalight)
{
if(!gdepthtex) glGenTextures(1, &gdepthtex);
if(!gcolortex) glGenTextures(1, &gcolortex);
if(!gnormaltex) glGenTextures(1, &gnormaltex);
if(!gglowtex) glGenTextures(1, &gglowtex);
if(!gfbo) glGenFramebuffers_(1, &gfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, gfbo);
maskgbuffer("cndg");
static const GLenum depthformats[] = { GL_RGBA8, GL_R16F, GL_R32F };
GLenum depthformat = gdepthformat ? depthformats[gdepthformat-1] : (ghasstencil > 1 ? stencilformat : GL_DEPTH_COMPONENT24);
createtexture(gdepthtex, gw, gh, nullptr, 3, 0, depthformat, GL_TEXTURE_RECTANGLE);
createtexture(gcolortex, gw, gh, nullptr, 3, 0, GL_RGBA8, GL_TEXTURE_RECTANGLE);
createtexture(gnormaltex, gw, gh, nullptr, 3, 0, GL_RGBA8, GL_TEXTURE_RECTANGLE);
createtexture(gglowtex, gw, gh, nullptr, 3, 0, hasAFBO ? hdrformat : GL_RGBA8, GL_TEXTURE_RECTANGLE);
bindgdepth();
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, gcolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE, gnormaltex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_RECTANGLE, gglowtex, 0);
if(gdepthformat) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_RECTANGLE, gdepthtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
if(hasAFBO)
{
createtexture(gglowtex, gw, gh, nullptr, 3, 0, GL_RGBA8, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_RECTANGLE, gglowtex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating g-buffer!");
}
else fatal("failed allocating g-buffer!");
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | (ghasstencil ? GL_STENCIL_BUFFER_BIT : 0));
}
if(!hdrtex) glGenTextures(1, &hdrtex);
if(!hdrfbo) glGenFramebuffers_(1, &hdrfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
createtexture(hdrtex, gw, gh, nullptr, 3, 1, hdrformat, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, hdrtex, 0);
bindgdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating HDR buffer!");
if(!msaalight || (msaalight > 2 && msaatonemap && msaatonemapblit))
{
if(!refracttex) glGenTextures(1, &refracttex);
if(!refractfbo) glGenFramebuffers_(1, &refractfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, refractfbo);
createtexture(refracttex, gw, gh, nullptr, 3, 0, GL_RGB, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, refracttex, 0);
bindgdepth();
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating refraction buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
if(gw < hudw || gh < hudh) setupscale(gw, gh, hudw, hudh);
}
void cleanupgbuffer()
{
if(gfbo) { glDeleteFramebuffers_(1, &gfbo); gfbo = 0; }
if(gdepthtex) { glDeleteTextures(1, &gdepthtex); gdepthtex = 0; }
if(gcolortex) { glDeleteTextures(1, &gcolortex); gcolortex = 0; }
if(gnormaltex) { glDeleteTextures(1, &gnormaltex); gnormaltex = 0; }
if(gglowtex) { glDeleteTextures(1, &gglowtex); gglowtex = 0; }
if(gstencilrb) { glDeleteRenderbuffers_(1, &gstencilrb); gstencilrb = 0; }
if(gdepthrb) { glDeleteRenderbuffers_(1, &gdepthrb); gdepthrb = 0; }
if(hdrfbo) { glDeleteFramebuffers_(1, &hdrfbo); hdrfbo = 0; }
if(hdrtex) { glDeleteTextures(1, &hdrtex); hdrtex = 0; }
if(refractfbo) { glDeleteFramebuffers_(1, &refractfbo); refractfbo = 0; }
if(refracttex) { glDeleteTextures(1, &refracttex); refracttex = 0; }
gw = gh = -1;
cleanupscale();
cleanupmsbuffer();
cleardeferredlightshaders();
}
VAR(msaadepthblit, 0, 0, 1);
void resolvemsaadepth(int w = vieww, int h = viewh)
{
if(!msaasamples || msaalight) return;
timer *resolvetimer = drawtex ? nullptr : begintimer("msaa depth resolve");
if(msaadepthblit)
{
glBindFramebuffer_(GL_READ_FRAMEBUFFER, msfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, gfbo);
if(ghasstencil) glClear(GL_STENCIL_BUFFER_BIT);
glBlitFramebuffer_(0, 0, w, h, 0, 0, w, h, GL_DEPTH_BUFFER_BIT, GL_NEAREST);
}
if(!msaadepthblit || gdepthformat)
{
glBindFramebuffer_(GL_FRAMEBUFFER, gfbo);
glViewport(0, 0, w, h);
maskgbuffer("d");
if(!msaadepthblit)
{
if(ghasstencil)
{
glStencilFunc(GL_ALWAYS, 0, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_STENCIL_TEST);
}
glDepthFunc(GL_ALWAYS);
SETSHADER(msaaresolvedepth);
}
else
{
glDisable(GL_DEPTH_TEST);
SETSHADER(msaaresolve);
}
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
screenquad();
maskgbuffer("cnd");
if(!msaadepthblit)
{
if(ghasstencil) glDisable(GL_STENCIL_TEST);
glDepthFunc(GL_LESS);
}
else glEnable(GL_DEPTH_TEST);
}
endtimer(resolvetimer);
}
void resolvemsaacolor(int w = vieww, int h = viewh)
{
if(!msaalight) return;
timer *resolvetimer = drawtex ? nullptr : begintimer("msaa resolve");
glBindFramebuffer_(GL_READ_FRAMEBUFFER, mshdrfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, hdrfbo);
glBlitFramebuffer_(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
endtimer(resolvetimer);
}
FVAR(bloomthreshold, 1e-3f, 0.85f, 1e3f);
FVARP(bloomscale, 0, 1.0f, 1e3f);
VARP(bloomblur, 0, 7, 7);
VARP(bloomiter, 0, 0, 4);
VARFP(bloomsize, 6, 9, 11, cleanupbloom());
VARFP(bloomprec, 0, 2, 3, cleanupbloom());
FVAR(hdraccumscale, 0, 0.98f, 1);
VAR(hdraccummillis, 1, 33, 1000);
VAR(hdrreduce, 0, 2, 2);
VARFP(hdrprec, 0, 2, 3, cleanupgbuffer());
FVARFP(hdrgamma, 1e-3f, 2, 1e3f, initwarning("HDR setup", INIT_LOAD, CHANGE_SHADERS));
FVARR(hdrbright, 1e-4f, 1.0f, 1e4f);
FVAR(hdrsaturate, 1e-3f, 0.85f, 1e3f);
FVAR(hdrminexposure, 0, 0.03f, 1);
FVAR(hdrmaxexposure, 0, 0.3f, 1);
VARFP(gscale, 25, 100, 100, cleanupgbuffer());
VARFP(gscalecubic, 0, 0, 1, cleanupgbuffer());
VARFP(gscalenearest, 0, 0, 1, cleanupgbuffer());
FVARFP(gscalecubicsoft, 0, 0, 1, initwarning("scaling setup", INIT_LOAD, CHANGE_SHADERS));
float ldrscale = 1.0f, ldrscaleb = 1.0f/255;
void copyhdr(int sw, int sh, GLuint fbo, int dw, int dh, bool flipx, bool flipy, bool swapxy)
{
if(!dw) dw = sw;
if(!dh) dh = sh;
if(msaalight) resolvemsaacolor(sw, sh);
GLERROR;
glBindFramebuffer_(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, dw, dh);
SETSHADER(reorient);
vec reorientx(flipx ? -0.5f : 0.5f, 0, 0.5f), reorienty(0, flipy ? -0.5f : 0.5f, 0.5f);
if(swapxy) swap(reorientx, reorienty);
reorientx.mul(sw);
reorienty.mul(sh);
LOCALPARAM(reorientx, reorientx);
LOCALPARAM(reorienty, reorienty);
glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
screenquad();
GLERROR;
hdrclear = 3;
}
void loadhdrshaders(int aa)
{
switch(aa)
{
case AA_LUMA:
useshaderbyname("hdrtonemapluma");
useshaderbyname("hdrnopluma");
if(msaalight > 1 && msaatonemap) useshaderbyname("msaatonemapluma");
break;
case AA_MASKED:
if(!msaasamples && ghasstencil) useshaderbyname("hdrtonemapstencil");
else
{
useshaderbyname("hdrtonemapmasked");
useshaderbyname("hdrnopmasked");
if(msaalight > 1 && msaatonemap) useshaderbyname("msaatonemapmasked");
}
break;
case AA_SPLIT:
useshaderbyname("msaatonemapsplit");
break;
case AA_SPLIT_LUMA:
useshaderbyname("msaatonemapsplitluma");
break;
case AA_SPLIT_MASKED:
useshaderbyname("msaatonemapsplitmasked");
break;
default:
break;
}
}
void processhdr(GLuint outfbo, int aa)
{
timer *hdrtimer = begintimer("hdr processing");
GLOBALPARAMF(hdrparams, hdrbright, hdrsaturate, bloomthreshold, bloomscale);
GLuint b0fbo = bloomfbo[1], b0tex = bloomtex[1], b1fbo = bloomfbo[0], b1tex = bloomtex[0], ptex = hdrtex;
int b0w = max(vieww/4, bloomw), b0h = max(viewh/4, bloomh), b1w = max(vieww/2, bloomw), b1h = max(viewh/2, bloomh),
pw = vieww, ph = viewh;
if(msaalight)
{
if(aa < AA_SPLIT && (msaalight <= 1 || !msaatonemap))
{
glBindFramebuffer_(GL_READ_FRAMEBUFFER, mshdrfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, hdrfbo);
glBlitFramebuffer_(0, 0, vieww, viewh, 0, 0, vieww, viewh, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
else if(hasFBMSBS && (vieww > bloomw || viewh > bloomh))
{
int cw = max(vieww/2, bloomw), ch = max(viewh/2, bloomh);
glBindFramebuffer_(GL_READ_FRAMEBUFFER, mshdrfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, hdrfbo);
glBlitFramebuffer_(0, 0, vieww, viewh, 0, 0, cw, ch, GL_COLOR_BUFFER_BIT, GL_SCALED_RESOLVE_FASTEST_EXT);
pw = cw;
ph = ch;
}
else
{
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
if(vieww/2 >= bloomw)
{
pw = vieww/2;
if(viewh/2 >= bloomh)
{
ph = viewh/2;
glViewport(0, 0, pw, ph);
SETSHADER(msaareduce);
}
else
{
glViewport(0, 0, pw, viewh);
SETSHADER(msaareducew);
}
}
else
{
glViewport(0, 0, vieww, viewh);
SETSHADER(msaaresolve);
}
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
screenquad(vieww, viewh);
}
}
if(hdrreduce) while(pw > bloomw || ph > bloomh)
{
GLuint cfbo = b1fbo, ctex = b1tex;
int cw = max(pw/2, bloomw), ch = max(ph/2, bloomh);
if(hdrreduce > 1 && cw/2 >= bloomw)
{
cw /= 2;
if(ch/2 >= bloomh)
{
ch /= 2;
SETSHADER(hdrreduce2);
}
else SETSHADER(hdrreduce2w);
}
else SETSHADER(hdrreduce);
if(cw == bloomw && ch == bloomh) { if(bloomfbo[5]) { cfbo = bloomfbo[5]; ctex = bloomtex[5]; } else { cfbo = bloomfbo[2]; ctex = bloomtex[2]; } }
glBindFramebuffer_(GL_FRAMEBUFFER, cfbo);
glViewport(0, 0, cw, ch);
glBindTexture(GL_TEXTURE_RECTANGLE, ptex);
screenquad(pw, ph);
ptex = ctex;
pw = cw;
ph = ch;
swap(b0fbo, b1fbo);
swap(b0tex, b1tex);
swap(b0w, b1w);
swap(b0h, b1h);
}
if(!lasthdraccum || lastmillis - lasthdraccum >= hdraccummillis)
{
GLuint ltex = ptex;
int lw = pw, lh = ph;
for(int i = 0; lw > 2 || lh > 2; i++)
{
int cw = max(lw/2, 2), ch = max(lh/2, 2);
if(hdrreduce > 1 && cw/2 >= 2)
{
cw /= 2;
if(ch/2 >= 2)
{
ch /= 2;
if(i) SETSHADER(hdrreduce2); else SETSHADER(hdrluminance2);
}
else if(i) SETSHADER(hdrreduce2w); else SETSHADER(hdrluminance2w);
}
else if(i) SETSHADER(hdrreduce); else SETSHADER(hdrluminance);
glBindFramebuffer_(GL_FRAMEBUFFER, b1fbo);
glViewport(0, 0, cw, ch);
glBindTexture(GL_TEXTURE_RECTANGLE, ltex);
screenquad(lw, lh);
ltex = b1tex;
lw = cw;
lh = ch;
swap(b0fbo, b1fbo);
swap(b0tex, b1tex);
swap(b0w, b1w);
swap(b0h, b1h);
}
glBindFramebuffer_(GL_FRAMEBUFFER, bloomfbo[4]);
glViewport(0, 0, bloompbo ? 4 : 1, 1);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA);
SETSHADER(hdraccum);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
LOCALPARAMF(accumscale, lasthdraccum ? pow(hdraccumscale, float(lastmillis - lasthdraccum)/hdraccummillis) : 0);
screenquad(2, 2);
glDisable(GL_BLEND);
if(bloompbo)
{
glBindBuffer_(GL_PIXEL_PACK_BUFFER, bloompbo);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, 4, 1, hasTRG ? GL_RED : GL_RGB, hasTF ? GL_FLOAT : GL_UNSIGNED_SHORT, nullptr);
glBindBuffer_(GL_PIXEL_PACK_BUFFER, 0);
}
lasthdraccum = lastmillis;
}
if(bloompbo)
{
gle::bindvbo(bloompbo);
gle::enablecolor();
gle::colorpointer(hasTF ? sizeof(GLfloat) : sizeof(GLushort), (const void *)0, hasTF ? GL_FLOAT : GL_UNSIGNED_SHORT, 1);
gle::clearvbo();
}
b0fbo = bloomfbo[3];
b0tex = bloomtex[3];
b1fbo = bloomfbo[2];
b1tex = bloomtex[2];
b0w = b1w = bloomw;
b0h = b1h = bloomh;
glActiveTexture_(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, bloomtex[4]);
glActiveTexture_(GL_TEXTURE0);
glBindFramebuffer_(GL_FRAMEBUFFER, b0fbo);
glViewport(0, 0, b0w, b0h);
SETSHADER(hdrbloom);
glBindTexture(GL_TEXTURE_RECTANGLE, ptex);
screenquad(pw, ph);
if(bloomblur)
{
float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1];
setupblurkernel(bloomblur, blurweights, bluroffsets);
loopi(2 + 2*bloomiter)
{
glBindFramebuffer_(GL_FRAMEBUFFER, b1fbo);
glViewport(0, 0, b1w, b1h);
setblurshader(i%2, 1, bloomblur, blurweights, bluroffsets, GL_TEXTURE_RECTANGLE);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
screenquad(b0w, b0h);
swap(b0w, b1w);
swap(b0h, b1h);
swap(b0tex, b1tex);
swap(b0fbo, b1fbo);
}
}
if(aa >= AA_SPLIT)
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, vieww, viewh);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
glActiveTexture_(GL_TEXTURE0);
switch(aa)
{
case AA_SPLIT_LUMA: SETSHADER(msaatonemapsplitluma); break;
case AA_SPLIT_MASKED:
SETSHADER(msaatonemapsplitmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(msaatonemapsplit); break;
}
screenquad(vieww, viewh, b0w, b0h);
}
else if(msaalight <= 1 || !msaatonemap)
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, vieww, viewh);
glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
glActiveTexture_(GL_TEXTURE0);
switch(aa)
{
case AA_LUMA: SETSHADER(hdrtonemapluma); break;
case AA_MASKED:
if(!msaasamples && ghasstencil)
{
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glStencilFunc(GL_EQUAL, 0, 0x80);
glEnable(GL_STENCIL_TEST);
SETSHADER(hdrtonemap);
screenquad(vieww, viewh, b0w, b0h);
glStencilFunc(GL_EQUAL, 0x80, 0x80);
SETSHADER(hdrtonemapstencil);
screenquad(vieww, viewh, b0w, b0h);
glDisable(GL_STENCIL_TEST);
goto done;
}
SETSHADER(hdrtonemapmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(hdrtonemap); break;
}
screenquad(vieww, viewh, b0w, b0h);
}
else
{
bool blit = msaalight > 2 && msaatonemapblit && (!aa || !outfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, blit ? msrefractfbo : outfbo);
glViewport(0, 0, vieww, viewh);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, b0tex);
glActiveTexture_(GL_TEXTURE0);
if(blit) SETSHADER(msaatonemapsample);
else switch(aa)
{
case AA_LUMA: SETSHADER(msaatonemapluma); break;
case AA_MASKED:
SETSHADER(msaatonemapmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(msaatonemap); break;
}
screenquad(vieww, viewh, b0w, b0h);
if(blit)
{
glBindFramebuffer_(GL_READ_FRAMEBUFFER, msrefractfbo);
glBindFramebuffer_(GL_DRAW_FRAMEBUFFER, aa || !outfbo ? refractfbo : outfbo);
glBlitFramebuffer_(0, 0, vieww, viewh, 0, 0, vieww, viewh, GL_COLOR_BUFFER_BIT, GL_NEAREST);
if(!outfbo)
{
glBindFramebuffer_(GL_FRAMEBUFFER, outfbo);
glViewport(0, 0, vieww, viewh);
if(!blit) SETSHADER(hdrnop);
else switch(aa)
{
case AA_LUMA: SETSHADER(hdrnopluma); break;
case AA_MASKED:
SETSHADER(hdrnopmasked);
setaavelocityparams(GL_TEXTURE3);
break;
default: SETSHADER(hdrnop); break;
}
glBindTexture(GL_TEXTURE_RECTANGLE, refracttex);
screenquad(vieww, viewh);
}
}
}
done:
if(bloompbo) gle::disablecolor();
endtimer(hdrtimer);
}
static void getavglum(int *numargs, ident *id)
{
if(!bloomfbo[4]) return;
glBindFramebuffer_(GL_FRAMEBUFFER, bloomfbo[4]);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
float avglum = -1;
glReadPixels(0, 0, 1, 1, GL_RED, GL_FLOAT, &avglum);
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
if(avglum < 0) return;
avglum *= 4;
if(*numargs < 0) floatret(avglum);
else printfvar(id, avglum);
}
COMMANDN(avglum, getavglum, "N$");
VAR(debugbloom, 0, 0, 1);
static void viewbloom()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, bloomtex[3]);
debugquad(0, 0, w, h, 0, 0, bloomw, bloomh);
}
VAR(debugdepth, 0, 0, 1);
void viewdepth()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
debugquad(0, 0, w, h, 0, 0, gw, gh);
}
VAR(debugstencil, 0, 0, 0xFF);
void viewstencil()
{
if(!ghasstencil || !hdrfbo) return;
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
glViewport(0, 0, gw, gh);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glStencilFunc(GL_NOTEQUAL, 0, debugstencil);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glEnable(GL_STENCIL_TEST);
SETSHADER(hudnotexture);
gle::colorf(1, 1, 1);
debugquad(0, 0, hudw, hudh, 0, 0, gw, gh);
glDisable(GL_STENCIL_TEST);
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
glViewport(0, 0, hudw, hudh);
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
debugquad(0, 0, w, h, 0, 0, gw, gh);
}
VAR(debugrefract, 0, 0, 1);
void viewrefract()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, refracttex);
debugquad(0, 0, w, h, 0, 0, gw, gh);
}
#define RH_MAXSPLITS 4
#define RH_MAXGRID 64
GLuint rhtex[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }, rhrb[4] = { 0, 0, 0, 0 }, rhfbo = 0;
uint rhclearmasks[2][RH_MAXSPLITS][(RH_MAXGRID+2+31)/32];
GLuint rsmdepthtex = 0, rsmcolortex = 0, rsmnormaltex = 0, rsmfbo = 0;
extern int rhrect, rhgrid, rhsplits, rhborder, rhprec, rhtaps, rhcache, rhforce, rsmprec, rsmdepthprec, rsmsize;
static Shader *radiancehintsshader = nullptr;
Shader *rsmworldshader = nullptr;
Shader *loadradiancehintsshader()
{
defformatstring(name, "radiancehints%d", rhtaps);
return generateshader(name, "radiancehintsshader %d", rhtaps);
}
void loadrhshaders()
{
if(rhborder) useshaderbyname("radiancehintsborder");
if(rhcache) useshaderbyname("radiancehintscached");
useshaderbyname("radiancehintsdisable");
radiancehintsshader = loadradiancehintsshader();
rsmworldshader = useshaderbyname("rsmworld");
useshaderbyname("rsmsky");
}
void clearrhshaders()
{
radiancehintsshader = nullptr;
rsmworldshader = nullptr;
}
void setupradiancehints()
{
GLenum rhformat = hasTF && rhprec >= 1 ? GL_RGBA16F : GL_RGBA8;
loopi(!rhrect && rhcache ? 8 : 4)
{
if(!rhtex[i]) glGenTextures(1, &rhtex[i]);
create3dtexture(rhtex[i], rhgrid+2*rhborder, rhgrid+2*rhborder, (rhgrid+2*rhborder)*rhsplits, nullptr, 7, 1, rhformat);
if(rhborder)
{
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
GLfloat border[4] = { 0.5f, 0.5f, 0.5f, 0 };
glTexParameterfv(GL_TEXTURE_3D, GL_TEXTURE_BORDER_COLOR, border);
}
}
if(!rhfbo) glGenFramebuffers_(1, &rhfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, rhfbo);
if(rhrect) loopi(4)
{
if(!rhrb[i]) glGenRenderbuffers_(1, &rhrb[i]);
glBindRenderbuffer_(GL_RENDERBUFFER, rhrb[i]);
glRenderbufferStorage_(GL_RENDERBUFFER, rhformat, (rhgrid + 2*rhborder)*(rhgrid + 2*rhborder), (rhgrid + 2*rhborder)*rhsplits);
glBindRenderbuffer_(GL_RENDERBUFFER, 0);
glFramebufferRenderbuffer_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_RENDERBUFFER, rhrb[i]);
}
else loopi(4) glFramebufferTexture3D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_3D, rhtex[i], 0, 0);
static const GLenum drawbufs[4] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3 };
glDrawBuffers_(4, drawbufs);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating radiance hints buffer!");
if(!rsmdepthtex) glGenTextures(1, &rsmdepthtex);
if(!rsmcolortex) glGenTextures(1, &rsmcolortex);
if(!rsmnormaltex) glGenTextures(1, &rsmnormaltex);
if(!rsmfbo) glGenFramebuffers_(1, &rsmfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, rsmfbo);
GLenum rsmformat = gethdrformat(rsmprec, GL_RGBA8);
createtexture(rsmdepthtex, rsmsize, rsmsize, nullptr, 3, 0, rsmdepthprec > 1 ? GL_DEPTH_COMPONENT32 : (rsmdepthprec ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16), GL_TEXTURE_RECTANGLE);
createtexture(rsmcolortex, rsmsize, rsmsize, nullptr, 3, 0, rsmformat, GL_TEXTURE_RECTANGLE);
createtexture(rsmnormaltex, rsmsize, rsmsize, nullptr, 3, 0, rsmformat, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_RECTANGLE, rsmdepthtex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, rsmcolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_RECTANGLE, rsmnormaltex, 0);
glDrawBuffers_(2, drawbufs);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating RSM buffer!");
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadrhshaders();
clearradiancehintscache();
}
void cleanupradiancehints()
{
clearradiancehintscache();
loopi(8) if(rhtex[i]) { glDeleteTextures(1, &rhtex[i]); rhtex[i] = 0; }
loopi(4) if(rhrb[i]) { glDeleteRenderbuffers_(1, &rhrb[i]); rhrb[i] = 0; }
if(rhfbo) { glDeleteFramebuffers_(1, &rhfbo); rhfbo = 0; }
if(rsmdepthtex) { glDeleteTextures(1, &rsmdepthtex); rsmdepthtex = 0; }
if(rsmcolortex) { glDeleteTextures(1, &rsmcolortex); rsmcolortex = 0; }
if(rsmnormaltex) { glDeleteTextures(1, &rsmnormaltex); rsmnormaltex = 0; }
if(rsmfbo) { glDeleteFramebuffers_(1, &rsmfbo); rsmfbo = 0; }
clearrhshaders();
}
VARF(rhrect, 0, 0, 1, cleanupradiancehints());
VARF(rhsplits, 1, 2, RH_MAXSPLITS, { cleardeferredlightshaders(); cleanupradiancehints(); });
VARF(rhborder, 0, 1, 1, cleanupradiancehints());
VARF(rsmsize, 64, 384, 2048, cleanupradiancehints());
VARF(rhnearplane, 1, 1, 16, clearradiancehintscache());
VARF(rhfarplane, 64, 1024, 16384, clearradiancehintscache());
FVARF(rsmpradiustweak, 1e-3f, 1, 1e3f, clearradiancehintscache());
FVARF(rhpradiustweak, 1e-3f, 1, 1e3f, clearradiancehintscache());
FVARF(rsmdepthrange, 0, 1024, 1e6f, clearradiancehintscache());
FVARF(rsmdepthmargin, 0, 0.1f, 1e3f, clearradiancehintscache());
VARFP(rhprec, 0, 0, 1, cleanupradiancehints());
VARFP(rsmprec, 0, 0, 3, cleanupradiancehints());
VARFP(rsmdepthprec, 0, 0, 2, cleanupradiancehints());
FVAR(rhnudge, 0, 0.5f, 4);
FVARF(rhworldbias, 0, 0.5f, 10, clearradiancehintscache());
FVARF(rhsplitweight, 0.20f, 0.6f, 0.95f, clearradiancehintscache());
VARF(rhgrid, 3, 27, RH_MAXGRID, cleanupradiancehints());
FVARF(rsmspread, 0, 0.15f, 1, clearradiancehintscache());
VAR(rhclipgrid, 0, 1, 1);
VARF(rhcache, 0, 1, 1, cleanupradiancehints());
VARF(rhforce, 0, 0, 1, cleanupradiancehints());
VAR(rsmcull, 0, 1, 1);
VARFP(rhtaps, 0, 20, 32, cleanupradiancehints());
VAR(rhdyntex, 0, 0, 1);
VAR(rhdynmm, 0, 0, 1);
VARFR(gidist, 0, 384, 1024, { clearradiancehintscache(); cleardeferredlightshaders(); if(!gidist) cleanupradiancehints(); });
FVARFR(giscale, 0, 1.5f, 1e3f, { cleardeferredlightshaders(); if(!giscale) cleanupradiancehints(); });
FVARR(giaoscale, 0, 3, 1e3f);
VARFP(gi, 0, 1, 1, { cleardeferredlightshaders(); cleanupradiancehints(); });
VAR(debugrsm, 0, 0, 2);
void viewrsm()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw, x = hudw-w, y = hudh-h;
SETSHADER(hudrect);
gle::colorf(1, 1, 1);
glBindTexture(GL_TEXTURE_RECTANGLE, debugrsm == 2 ? rsmnormaltex : rsmcolortex);
debugquad(x, y, w, h, 0, 0, rsmsize, rsmsize);
}
VAR(debugrh, -1, 0, RH_MAXSPLITS*(RH_MAXGRID + 2));
void viewrh()
{
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw, x = hudw-w, y = hudh-h;
gle::colorf(1, 1, 1);
if(debugrh < 0 && rhrect)
{
SETSHADER(hudrect);
glBindTexture(GL_TEXTURE_RECTANGLE, rhtex[5]);
float tw = (rhgrid+2*rhborder)*(rhgrid+2*rhborder), th = (rhgrid+2*rhborder)*rhsplits;
gle::defvertex(2);
gle::deftexcoord0(2);
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(x, y); gle::attribf(0, 0);
gle::attribf(x+w, y); gle::attribf(tw, 0);
gle::attribf(x, y+h); gle::attribf(0, th);
gle::attribf(x+w, y+h); gle::attribf(tw, th);
gle::end();
}
else
{
SETSHADER(hud3d);
glBindTexture(GL_TEXTURE_3D, rhtex[1]);
float z = (max(debugrh, 1)-1+0.5f)/float((rhgrid+2*rhborder)*rhsplits);
gle::defvertex(2);
gle::deftexcoord0(3);
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(x, y); gle::attribf(0, 0, z);
gle::attribf(x+w, y); gle::attribf(1, 0, z);
gle::attribf(x, y+h); gle::attribf(0, 1, z);
gle::attribf(x+w, y+h); gle::attribf(1, 1, z);
gle::end();
}
}
#define SHADOWATLAS_SIZE 4096
PackNode shadowatlaspacker(0, 0, SHADOWATLAS_SIZE, SHADOWATLAS_SIZE);
extern int smminradius;
struct lightinfo
{
int ent, shadowmap;
ushort flags, batched;
vec o, color;
float radius, dist;
vec dir, spotx, spoty;
int spot;
float sx1, sy1, sx2, sy2, sz1, sz2;
occludequery *query;
lightinfo() {}
lightinfo(const vec &o, const vec &color, float radius, ushort flags = 0, const vec &dir = vec(0, 0, 0), int spot = 0)
: ent(-1), shadowmap(-1), flags(flags), batched(~0),
o(o), color(color), radius(radius), dist(camera1->o.dist(o)),
dir(dir), spot(spot), query(nullptr)
{
if(spot > 0) calcspot();
calcscissor();
}
lightinfo(int i, const extentity &e)
: ent(i), shadowmap(-1), flags(e.attr5), batched(~0),
o(e.o), color(vec(e.attr2, e.attr3, e.attr4).max(0)), radius(e.attr1), dist(camera1->o.dist(e.o)),
dir(0, 0, 0), spot(0), query(nullptr)
{
if(e.attached && e.attached->type == ET_SPOTLIGHT)
{
dir = vec(e.attached->o).sub(e.o).normalize();
spot = std::clamp(int(e.attached->attr1), 1, 89);
calcspot();
}
calcscissor();
}
void calcspot()
{
quat orient(dir, vec(0, 0, dir.z < 0 ? -1 : 1));
spotx = orient.invertedrotate(vec(1, 0, 0));
spoty = orient.invertedrotate(vec(0, 1, 0));
}
bool noshadow() const { return flags&L_NOSHADOW || radius <= smminradius; }
bool nospec() const { return (flags&L_NOSPEC) != 0; }
bool volumetric() const { return (flags&L_VOLUMETRIC) != 0; }
bool colorshadow() const { return (flags&L_SMALPHA) != 0; }
void addscissor(float &dx1, float &dy1, float &dx2, float &dy2) const
{
dx1 = min(dx1, sx1);
dy1 = min(dy1, sy1);
dx2 = max(dx2, sx2);
dy2 = max(dy2, sy2);
}
void addscissor(float &dx1, float &dy1, float &dx2, float &dy2, float &dz1, float &dz2) const
{
addscissor(dx1, dy1, dx2, dy2);
dz1 = min(dz1, sz1);
dz2 = max(dz2, sz2);
}
bool validscissor() const { return sx1 < sx2 && sy1 < sy2 && sz1 < sz2; }
void calcscissor()
{
sx1 = sy1 = sz1 = -1;
sx2 = sy2 = sz2 = 1;
if(spot > 0) calcspotscissor(o, radius, dir, spot, spotx, spoty, sx1, sy1, sx2, sy2, sz1, sz2);
else calcspherescissor(o, radius, sx1, sy1, sx2, sy2, sz1, sz2);
}
bool checkquery() const { return query && query->owner == this && ::checkquery(query); }
void calcbb(vec &bbmin, vec &bbmax)
{
if(spot > 0)
{
float spotscale = radius * tan360(spot);
vec up = vec(spotx).mul(spotscale).abs(), right = vec(spoty).mul(spotscale).abs(), center = vec(dir).mul(radius).add(o);
bbmin = bbmax = center;
bbmin.sub(up).sub(right);
bbmax.add(up).add(right);
bbmin.min(o);
bbmax.max(o);
}
else
{
bbmin = vec(o).sub(radius);
bbmax = vec(o).add(radius);
}
}
};
struct shadowcachekey
{
vec o;
float radius;
vec dir;
int spot;
shadowcachekey() {}
shadowcachekey(const lightinfo &l) : o(l.o), radius(l.radius), dir(l.dir), spot(l.spot) {}
};
static inline uint hthash(const shadowcachekey &k)
{
return hthash(k.o);
}
static inline bool htcmp(const shadowcachekey &x, const shadowcachekey &y)
{
return x.o == y.o && x.radius == y.radius && x.dir == y.dir && x.spot == y.spot;
}
struct shadowcacheval;
struct shadowmapinfo
{
ushort x, y, size;
uchar sidemask, transparent;
int light;
shadowcacheval *cached;
};
struct shadowcacheval
{
ushort x, y, size;
uchar sidemask, transparent;
shadowcacheval() {}
shadowcacheval(const shadowmapinfo &sm) : x(sm.x), y(sm.y), size(sm.size), sidemask(sm.sidemask), transparent(sm.transparent) {}
};
struct shadowcache : hashtable<shadowcachekey, shadowcacheval>
{
shadowcache() : hashtable<shadowcachekey, shadowcacheval>(256) {}
void reset()
{
clear();
}
};
extern int smcache, smfilter, smgather, smalpha, smalphaprec, alphashadow;
#define SHADOWCACHE_EVICT 2
GLuint shadowatlastex = 0, shadowatlasfbo = 0;
GLuint shadowcolortex = 0, shadowblanktex = 0;
GLuint shadowfiltertex = 0, shadowfilterfbo = 0;
GLenum shadowatlastarget = GL_NONE;
vector<uint> shadowcolorclears, shadowcolorblurs;
int smalign = 0;
shadowcache shadowcache;
bool shadowcachefull = false;
int evictshadowcache = 0;
Shader *smalphaworldshader = nullptr;
extern int usetexgather;
static inline bool usegatherforsm() { return smfilter > 1 && smgather && hasTG && usetexgather; }
static inline bool usesmcomparemode() { return !usegatherforsm() || (hasTG && hasGPU5 && usetexgather > 1); }
static void loadsmshaders()
{
if(smalpha && alphashadow)
{
smalphaworldshader = useshaderbyname("smalphaworld");
if(smfilter)
{
useshaderbyname("smalphaclear");
useshaderbyname(usegatherforsm() ? "smalphablur2d" : "smalphablurrect");
}
}
}
static void clearsmshaders()
{
smalphaworldshader = nullptr;
}
static inline void setsmnoncomparemode() // use texture gather
{
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_MODE, GL_NONE);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
}
static inline void setsmcomparemode() // use embedded shadow cmp
{
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(shadowatlastarget, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
VAR(debugshadowatlas, 0, 0, 3);
void viewshadowatlas()
{
bool color = debugshadowatlas > 1 && shadowcolortex && (debugshadowatlas <= 2 || shadowfiltertex);
int w = min(hudw, hudh)/2, h = (w*hudh)/hudw, x = hudw-w, y = hudh-h;
float tw = 1, th = 1;
if((color && debugshadowatlas > 2) || shadowatlastarget == GL_TEXTURE_RECTANGLE)
{
tw = shadowatlaspacker.w;
th = shadowatlaspacker.h;
if(debugshadowatlas > 2) { tw /= 2; th /= 2; }
SETSHADER(hudrect);
}
else hudshader->set();
gle::colorf(1, 1, 1);
if(color)
{
if(debugshadowatlas > 2) glBindTexture(GL_TEXTURE_RECTANGLE, shadowcolortex);
else glBindTexture(shadowatlastarget, shadowcolortex);
}
else
{
glBindTexture(shadowatlastarget, shadowatlastex);
if(usesmcomparemode()) setsmnoncomparemode();
}
debugquad(x, y, w, h, 0, 0, tw, th);
if(!color && usesmcomparemode()) setsmcomparemode();
}
extern int smdepthprec, smsize;
void setupshadowatlas()
{
int size = min((1<<smsize), hwtexsize);
shadowatlaspacker.resize(size, size);
if(!shadowatlastex) glGenTextures(1, &shadowatlastex);
shadowatlastarget = usegatherforsm() ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE;
createtexture(shadowatlastex, shadowatlaspacker.w, shadowatlaspacker.h, nullptr, 3, 1, smdepthprec > 1 ? GL_DEPTH_COMPONENT32 : (smdepthprec ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16), shadowatlastarget);
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(shadowatlastarget, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
smalign = 0;
if(smalpha && alphashadow)
{
if(!shadowcolortex) glGenTextures(1, &shadowcolortex);
GLenum colcomp = smalphaprec > 1 ? GL_RGB10 : (smalphaprec ? (hasES2 ? GL_RGB565 : GL_RGB5) : GL_R3_G3_B2);
createtexture(shadowcolortex, shadowatlaspacker.w, shadowatlaspacker.h, nullptr, 3, 1, colcomp, shadowatlastarget);
if(!shadowblanktex) glGenTextures(1, &shadowblanktex);
static const uchar blank[4] = {255, 255, 255, 255};
createtexture(shadowblanktex, 1, 1, blank, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE);
if(smfilter)
{
smalign = 1;
if(!shadowfiltertex) glGenTextures(1, &shadowfiltertex);
createtexture(shadowfiltertex, shadowatlaspacker.w/2, shadowatlaspacker.h/2, nullptr, 3, 1, colcomp, GL_TEXTURE_RECTANGLE);
if(!shadowfilterfbo) glGenFramebuffers_(1, &shadowfilterfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, shadowfilterfbo);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, shadowfiltertex, 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("Failed allocating shadow filter buffer!");
}
}
if(!shadowatlasfbo) glGenFramebuffers_(1, &shadowatlasfbo);
glBindFramebuffer_(GL_FRAMEBUFFER, shadowatlasfbo);
if(shadowcolortex) glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, shadowatlastarget, shadowcolortex, 0);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowatlastarget, shadowatlastex, 0);
if(!shadowcolortex) glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating shadow atlas!");
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadsmshaders();
}
void cleanupshadowatlas()
{
if(shadowatlastex) { glDeleteTextures(1, &shadowatlastex); shadowatlastex = 0; }
if(shadowcolortex) { glDeleteTextures(1, &shadowcolortex); shadowcolortex = 0; }
if(shadowblanktex) { glDeleteTextures(1, &shadowblanktex); shadowblanktex = 0; }
if(shadowatlasfbo) { glDeleteFramebuffers_(1, &shadowatlasfbo); shadowatlasfbo = 0; }
if(shadowfiltertex) { glDeleteTextures(1, &shadowfiltertex); shadowfiltertex = 0; }
if(shadowfilterfbo) { glDeleteFramebuffers_(1, &shadowfilterfbo); shadowfilterfbo = 0; }
clearshadowcache();
clearsmshaders();
}
const matrix4 cubeshadowviewmatrix[6] =
{
// sign-preserving cubemap projections
matrix4(vec(0, 0, 1), vec(0, 1, 0), vec(-1, 0, 0)), // +X
matrix4(vec(0, 0, 1), vec(0, 1, 0), vec( 1, 0, 0)), // -X
matrix4(vec(1, 0, 0), vec(0, 0, 1), vec(0, -1, 0)), // +Y
matrix4(vec(1, 0, 0), vec(0, 0, 1), vec(0, 1, 0)), // -Y
matrix4(vec(1, 0, 0), vec(0, 1, 0), vec(0, 0, -1)), // +Z
matrix4(vec(1, 0, 0), vec(0, 1, 0), vec(0, 0, 1)) // -Z
};
FVAR(smpolyfactor, -1e3f, 1, 1e3f);
FVAR(smpolyoffset, -1e3f, 0, 1e3f);
FVAR(smbias, -1e6f, 0.01f, 1e6f);
FVAR(smpolyfactor2, -1e3f, 1.5f, 1e3f);
FVAR(smpolyoffset2, -1e3f, 0, 1e3f);
FVAR(smbias2, -1e6f, 0.02f, 1e6f);
FVAR(smprec, 1e-3f, 1, 1e3f);
FVAR(smcubeprec, 1e-3f, 1, 1e3f);
FVAR(smspotprec, 1e-3f, 1, 1e3f);
VARFP(smsize, 10, 12, 14, cleanupshadowatlas());
VARFP(smdepthprec, 0, 0, 2, cleanupshadowatlas());
VAR(smsidecull, 0, 1, 1);
VAR(smviscull, 0, 1, 1);
VAR(smborder, 0, 3, 16);
VAR(smborder2, 0, 4, 16);
VAR(smminradius, 0, 16, 10000);
VAR(smminsize, 1, 96, 1024);
VAR(smmaxsize, 1, 384, 1024);
//VAR(smmaxsize, 1, 4096, 4096);
VAR(smused, 1, 0, 0);
VAR(smquery, 0, 1, 1);
VARF(smcullside, 0, 1, 1, cleanupshadowatlas());
VARF(smcache, 0, 1, 2, cleanupshadowatlas());
VARFP(smfilter, 0, 2, 3, { cleardeferredlightshaders(); cleanupshadowatlas(); cleanupvolumetric(); });
VARFP(smgather, 0, 0, 1, { cleardeferredlightshaders(); cleanupshadowatlas(); cleanupvolumetric(); });
VAR(smnoshadow, 0, 0, 1);
VAR(smdynshadow, 0, 1, 1);
VARF(smalpha, 0, 2, 2, { cleardeferredlightshaders(); cleanupshadowatlas(); });
VARFP(smalphaprec, 0, 0, 2, cleanupshadowatlas());
VAR(lightpassesused, 1, 0, 0);
VAR(lightsvisible, 1, 0, 0);
VAR(lightsoccluded, 1, 0, 0);
VARN(lightbatches, lightbatchesused, 1, 0, 0);
VARN(lightbatchrects, lightbatchrectsused, 1, 0, 0);
VARN(lightbatchstacks, lightbatchstacksused, 1, 0, 0);
VARFR(alphashadow, 0, 0, 2, { cleardeferredlightshaders(); cleanupshadowatlas(); });
FVARFR(alphashadowscale, 0, 1, 2, clearshadowcache());
enum
{
MAXLIGHTTILEBATCH = 8
};
VARF(lighttilebatch, 0, MAXLIGHTTILEBATCH, MAXLIGHTTILEBATCH, cleardeferredlightshaders());
VARF(batchsunlight, 0, 2, 2, cleardeferredlightshaders());
int shadowmapping = 0;
struct lightrect
{
uchar x1, y1, x2, y2;
lightrect() {}
lightrect(uchar x1, uchar y1, uchar x2, uchar y2) : x1(x1), y1(y1), x2(x2), y2(y2) {}
lightrect(const lightinfo &l)
{
calctilebounds(l.sx1, l.sy1, l.sx2, l.sy2, x1, y1, x2, y2);
}
bool outside(const lightrect &o) const
{
return x1 >= o.x2 || x2 <= o.x1 || y1 >= o.y2 || y2 <= o.y1;
}
bool inside(const lightrect &o) const
{
return x1 >= o.x1 && x2 <= o.x2 && y1 >= o.y1 && y2 <= o.y2;
}
void intersect(const lightrect &o)
{
x1 = max(x1, o.x1);
y1 = max(y1, o.y1);
x2 = min(x2, o.x2);
y2 = min(y2, o.y2);
}
bool overlaps(int tx1, int ty1, int tx2, int ty2, const uint *tilemask) const
{
if(int(x2) <= tx1 || int(x1) >= tx2 || int(y2) <= ty1 || int(y1) >= ty2) return false;
if(!tilemask) return true;
uint xmask = (1<<x2) - (1<<x1);
for(int y = max(int(y1), ty1), end = min(int(y2), ty2); y < end; y++) if(tilemask[y] & xmask) return true;
return false;
}
};
enum
{
BF_SPOTLIGHT = 1<<0,
BF_NOSHADOW = 1<<1,
BF_SMALPHA = 1<<2,
BF_NOSUN = 1<<3
};
struct lightbatchkey
{
uchar flags, numlights;
ushort lights[MAXLIGHTTILEBATCH];
};
struct lightbatch : lightbatchkey
{
vector<lightrect> rects;
void reset()
{
rects.setsize(0);
}
bool overlaps(int tx1, int ty1, int tx2, int ty2, const uint *tilemask) const
{
if(!tx1 && !ty1 && tx2 >= lighttilew && ty2 >= lighttileh && !tilemask) return true;
loopv(rects) if(rects[i].overlaps(tx1, ty1, tx2, ty2, tilemask)) return true;
return false;
}
};
static inline void htrecycle(lightbatch &l)
{
l.reset();
}
static inline uint hthash(const lightbatchkey &l)
{
uint h = 0;
loopi(l.numlights) h = ((h<<8)+h)^l.lights[i];
return h;
}
static inline bool htcmp(const lightbatchkey &x, const lightbatchkey &y)
{
return x.flags == y.flags &&
x.numlights == y.numlights &&
(!x.numlights || !memcmp(x.lights, y.lights, x.numlights*sizeof(x.lights[0])));
}
vector<lightinfo> lights;
vector<int> lightorder;
hashset<lightbatch> lightbatcher(128);
vector<lightbatch *> lightbatches;
vector<shadowmapinfo> shadowmaps;
void clearshadowcache()
{
shadowmaps.setsize(0);
clearradiancehintscache();
clearshadowmeshes();
}
static shadowmapinfo *addshadowmap(ushort x, ushort y, int size, int &idx, int light = -1, shadowcacheval *cached = nullptr)
{
idx = shadowmaps.length();
shadowmapinfo *sm = &shadowmaps.add();
sm->x = x;
sm->y = y;
sm->size = size;
sm->light = light;
sm->sidemask = 0;
sm->transparent = 0;
sm->cached = cached;
return sm;
}
#define CSM_MAXSPLITS 8
VARF(csmmaxsize, 256, 768, 2048, clearshadowcache());
VARF(csmsplits, 1, 3, CSM_MAXSPLITS, { cleardeferredlightshaders(); clearshadowcache(); });
FVAR(csmsplitweight, 0.20f, 0.75f, 0.95f);
VARF(csmshadowmap, 0, 1, 1, { cleardeferredlightshaders(); clearshadowcache(); });
// cascaded shadow maps
struct cascadedshadowmap
{
struct splitinfo
{
float nearplane; // split distance to near plane
float farplane; // split distance to farplane
matrix4 proj; // one projection per split
vec scale, offset; // scale and offset of the projection
int idx; // shadowmapinfo indices
vec center, bounds; // max extents of shadowmap in sunlight model space
plane cull[4]; // world space culling planes of the split's projected sides
};
matrix4 model; // model view is shared by all splits
splitinfo splits[CSM_MAXSPLITS]; // per-split parameters
vec lightview; // view vector for light
int rendered;
void setup(); // insert shadowmaps for each split frustum if there is sunlight
void updatesplitdist(); // compute split frustum distances
void getmodelmatrix(); // compute the shared model matrix
void getprojmatrix(); // compute each cropped projection matrix
void gencullplanes(); // generate culling planes for the mvp matrix
void bindparams(); // bind any shader params necessary for lighting
};
void cascadedshadowmap::setup()
{
int size = ((csmmaxsize * shadowatlaspacker.w) / SHADOWATLAS_SIZE + smalign)&~smalign;
loopi(csmsplits)
{
ushort smx = USHRT_MAX, smy = USHRT_MAX;
splits[i].idx = -1;
if(shadowatlaspacker.insert(smx, smy, size, size))
addshadowmap(smx, smy, size, splits[i].idx);
}
getmodelmatrix();
getprojmatrix();
gencullplanes();
}
VAR(csmnearplane, 1, 1, 16);
VAR(csmfarplane, 64, 1024, 16384);
FVAR(csmpradiustweak, 1e-3f, 1, 1e3f);
FVAR(csmdepthrange, 0, 1024, 1e6f);
FVAR(csmdepthmargin, 0, 0.1f, 1e3f);
FVAR(csmpolyfactor, -1e3f, 2, 1e3f);
FVAR(csmpolyoffset, -1e4f, 0, 1e4f);
FVAR(csmbias, -1e6f, 1e-4f, 1e6f);
FVAR(csmpolyfactor2, -1e3f, 3, 1e3f);
FVAR(csmpolyoffset2, -1e4f, 0, 1e4f);
FVAR(csmbias2, -1e16f, 2e-4f, 1e6f);
VAR(csmcull, 0, 1, 1);
void cascadedshadowmap::updatesplitdist()
{
float lambda = csmsplitweight, nd = csmnearplane, fd = csmfarplane, ratio = fd/nd;
splits[0].nearplane = nd;
for(int i = 1; i < csmsplits; ++i)
{
float si = i / float(csmsplits);
splits[i].nearplane = lambda*(nd*pow(ratio, si)) + (1-lambda)*(nd + (fd - nd)*si);
splits[i-1].farplane = splits[i].nearplane * 1.005f;
}
splits[csmsplits-1].farplane = fd;
}
void cascadedshadowmap::getmodelmatrix()
{
model = viewmatrix;
model.rotate_around_x(sunlightpitch*RAD);
model.rotate_around_z((180-sunlightyaw)*RAD);
}
void cascadedshadowmap::getprojmatrix()
{
lightview = vec(sunlightdir).neg();
// compute the split frustums
updatesplitdist();
// find z extent
float minz = lightview.project_bb(worldmin, worldmax), maxz = lightview.project_bb(worldmax, worldmin),
zmargin = max((maxz - minz)*csmdepthmargin, 0.5f*(csmdepthrange - (maxz - minz)));
minz -= zmargin;
maxz += zmargin;
// compute each split projection matrix
loopi(csmsplits)
{
splitinfo &split = splits[i];
if(split.idx < 0) continue;
const shadowmapinfo &sm = shadowmaps[split.idx];
vec c;
float radius = calcfrustumboundsphere(split.nearplane, split.farplane, camera1->o, camdir, c);
// compute the projected bounding box of the sphere
vec tc;
model.transform(c, tc);
int border = smfilter > 2 ? smborder2 : smborder;
const float pradius = ceil(radius * csmpradiustweak) + smalign, step = (2*pradius) / (sm.size - 2*border);
vec2 offset = vec2(tc).sub(pradius).div(step*(1+smalign));
offset.x = floor(offset.x)*(1+smalign);
offset.y = floor(offset.y)*(1+smalign);
split.center = vec(vec2(offset).mul(step).add(pradius), -0.5f*(minz + maxz));
split.bounds = vec(pradius, pradius, 0.5f*(maxz - minz));
// modify mvp with a scale and offset
// now compute the update model view matrix for this split
split.scale = vec(1/step, 1/step, -1/(maxz - minz));
split.offset = vec(border - offset.x, border - offset.y, -minz/(maxz - minz));
split.proj.identity();
split.proj.settranslation(2*split.offset.x/sm.size - 1, 2*split.offset.y/sm.size - 1, 2*split.offset.z - 1);
split.proj.setscale(2*split.scale.x/sm.size, 2*split.scale.y/sm.size, 2*split.scale.z);
}
}
void cascadedshadowmap::gencullplanes()
{
loopi(csmsplits)
{
splitinfo &split = splits[i];
matrix4 mvp;
mvp.mul(split.proj, model);
vec4 px = mvp.rowx(), py = mvp.rowy(), pw = mvp.roww();
split.cull[0] = plane(vec4(pw).add(px)).normalize(); // left plane
split.cull[1] = plane(vec4(pw).sub(px)).normalize(); // right plane
split.cull[2] = plane(vec4(pw).add(py)).normalize(); // bottom plane
split.cull[3] = plane(vec4(pw).sub(py)).normalize(); // top plane
}
}
void cascadedshadowmap::bindparams()
{
GLOBALPARAM(csmmatrix, matrix3(model));
static GlobalShaderParam csmtc("csmtc"), csmoffset("csmoffset");
vec4 *csmtcv = csmtc.reserve<vec4>(csmsplits);
vec *csmoffsetv = csmoffset.reserve<vec>(csmsplits);
loopi(csmsplits)
{
cascadedshadowmap::splitinfo &split = splits[i];
if(split.idx < 0) continue;
const shadowmapinfo &sm = shadowmaps[split.idx];
csmtcv[i] = vec4(vec2(split.center).mul(-split.scale.x), split.scale.x, split.bounds.x*split.scale.x);
const float bias = (smfilter > 2 ? csmbias2 : csmbias) * (-512.0f / sm.size) * (split.farplane - split.nearplane) / (splits[0].farplane - splits[0].nearplane);
csmoffsetv[i] = vec(sm.x, sm.y, 0.5f + bias).add2(0.5f*sm.size);
}
GLOBALPARAMF(csmz, splits[0].center.z*-splits[0].scale.z, splits[0].scale.z);
}
cascadedshadowmap csm;
int calcbbcsmsplits(const ivec &bbmin, const ivec &bbmax)
{
int mask = (1<<csmsplits)-1;
if(!csmcull) return mask;
loopi(csmsplits)
{
const cascadedshadowmap::splitinfo &split = csm.splits[i];
int k;
for(k = 0; k < 4; k++)
{
const plane &p = split.cull[k];
ivec omin, omax;
if(p.x > 0) { omin.x = bbmin.x; omax.x = bbmax.x; } else { omin.x = bbmax.x; omax.x = bbmin.x; }
if(p.y > 0) { omin.y = bbmin.y; omax.y = bbmax.y; } else { omin.y = bbmax.y; omax.y = bbmin.y; }
if(p.z > 0) { omin.z = bbmin.z; omax.z = bbmax.z; } else { omin.z = bbmax.z; omax.z = bbmin.z; }
if(omax.dist(p) < 0) { mask &= ~(1<<i); goto nextsplit; }
if(omin.dist(p) < 0) goto notinside;
}
mask &= (2<<i)-1;
break;
notinside:
while(++k < 4)
{
const plane &p = split.cull[k];
ivec omax(p.x > 0 ? bbmax.x : bbmin.x, p.y > 0 ? bbmax.y : bbmin.y, p.z > 0 ? bbmax.z : bbmin.z);
if(omax.dist(p) < 0) { mask &= ~(1<<i); break; }
}
nextsplit:;
}
return mask;
}
int calcspherecsmsplits(const vec &center, float radius)
{
int mask = (1<<csmsplits)-1;
if(!csmcull) return mask;
loopi(csmsplits)
{
const cascadedshadowmap::splitinfo &split = csm.splits[i];
int k;
for(k = 0; k < 4; k++)
{
const plane &p = split.cull[k];
float dist = p.dist(center);
if(dist < -radius) { mask &= ~(1<<i); goto nextsplit; }
if(dist < radius) goto notinside;
}
mask &= (2<<i)-1;
break;
notinside:
while(++k < 4)
{
const plane &p = split.cull[k];
if(p.dist(center) < -radius) { mask &= ~(1<<i); break; }
}
nextsplit:;
}
return mask;
}
struct reflectiveshadowmap
{
matrix4 model, proj;
vec lightview;
plane cull[4];
vec scale, offset;
vec center, bounds;
void setup();
void getmodelmatrix();
void getprojmatrix();
void gencullplanes();
} rsm;
void reflectiveshadowmap::setup()
{
getmodelmatrix();
getprojmatrix();
gencullplanes();
}
void reflectiveshadowmap::getmodelmatrix()
{
model = viewmatrix;
model.rotate_around_x(sunlightpitch*RAD);
model.rotate_around_z((180-sunlightyaw)*RAD);
}
void reflectiveshadowmap::getprojmatrix()
{
lightview = vec(sunlightdir).neg();
// find z extent
float minz = lightview.project_bb(worldmin, worldmax), maxz = lightview.project_bb(worldmax, worldmin),
zmargin = max((maxz - minz)*rsmdepthmargin, 0.5f*(rsmdepthrange - (maxz - minz)));
minz -= zmargin;
maxz += zmargin;
vec c;
float radius = calcfrustumboundsphere(rhnearplane, rhfarplane, camera1->o, camdir, c);
// compute the projected bounding box of the sphere
vec tc;
model.transform(c, tc);
const float pradius = ceil((radius + gidist) * rsmpradiustweak), step = (2*pradius) / rsmsize;
vec2 tcoff = vec2(tc).sub(pradius).div(step);
tcoff.x = floor(tcoff.x);
tcoff.y = floor(tcoff.y);
center = vec(vec2(tcoff).mul(step).add(pradius), -0.5f*(minz + maxz));
bounds = vec(pradius, pradius, 0.5f*(maxz - minz));
scale = vec(1/step, 1/step, -1/(maxz - minz));
offset = vec(-tcoff.x, -tcoff.y, -minz/(maxz - minz));
proj.identity();
proj.settranslation(2*offset.x/rsmsize - 1, 2*offset.y/rsmsize - 1, 2*offset.z - 1);
proj.setscale(2*scale.x/rsmsize, 2*scale.y/rsmsize, 2*scale.z);
}
void reflectiveshadowmap::gencullplanes()
{
matrix4 mvp;
mvp.mul(proj, model);
vec4 px = mvp.rowx(), py = mvp.rowy(), pw = mvp.roww();
cull[0] = plane(vec4(pw).add(px)).normalize(); // left plane
cull[1] = plane(vec4(pw).sub(px)).normalize(); // right plane
cull[2] = plane(vec4(pw).add(py)).normalize(); // bottom plane
cull[3] = plane(vec4(pw).sub(py)).normalize(); // top plane
}
int calcbbrsmsplits(const ivec &bbmin, const ivec &bbmax)
{
if(!rsmcull) return 1;
loopk(4)
{
const plane &p = rsm.cull[k];
ivec omin, omax;
if(p.x > 0) { omin.x = bbmin.x; omax.x = bbmax.x; } else { omin.x = bbmax.x; omax.x = bbmin.x; }
if(p.y > 0) { omin.y = bbmin.y; omax.y = bbmax.y; } else { omin.y = bbmax.y; omax.y = bbmin.y; }
if(p.z > 0) { omin.z = bbmin.z; omax.z = bbmax.z; } else { omin.z = bbmax.z; omax.z = bbmin.z; }
if(omax.dist(p) < 0) return 0;
if(omin.dist(p) < 0) while(++k < 4)
{
const plane &p = rsm.cull[k];
ivec omax(p.x > 0 ? bbmax.x : bbmin.x, p.y > 0 ? bbmax.y : bbmin.y, p.z > 0 ? bbmax.z : bbmin.z);
if(omax.dist(p) < 0) return 0;
}
}
return 1;
}
int calcspherersmsplits(const vec &center, float radius)
{
if(!rsmcull) return 1;
loopk(4)
{
const plane &p = rsm.cull[k];
float dist = p.dist(center);
if(dist < -radius) return 0;
if(dist < radius) while(++k < 4)
{
const plane &p = rsm.cull[k];
if(p.dist(center) < -radius) return 0;
}
}
return 1;
}
struct radiancehints
{
struct splitinfo
{
float nearplane, farplane;
vec offset, scale;
vec center; float bounds;
vec cached; bool copied;
splitinfo() : center(-1e16f, -1e16f, -1e16f), bounds(-1e16f), cached(-1e16f, -1e16f, -1e16f), copied(false) {}
void clearcache() { bounds = -1e16f; }
} splits[RH_MAXSPLITS];
vec dynmin, dynmax, prevdynmin, prevdynmax;
radiancehints() : dynmin(1e16f, 1e16f, 1e16f), dynmax(-1e16f, -1e16f, -1e16f), prevdynmin(1e16f, 1e16f, 1e16f), prevdynmax(-1e16f, -1e16f, -1e16f) {}
void setup();
void updatesplitdist();
void bindparams();
void renderslices();
void clearcache() { loopi(RH_MAXSPLITS) splits[i].clearcache(); }
bool allcached() const { loopi(rhsplits) if(splits[i].cached != splits[i].center) return false; return true; }
} rh;
void clearradiancehintscache()
{
rh.clearcache();
memset(rhclearmasks, 0, sizeof(rhclearmasks));
}
void radiancehints::updatesplitdist()
{
float lambda = rhsplitweight, nd = rhnearplane, fd = rhfarplane, ratio = fd/nd;
splits[0].nearplane = nd;
for(int i = 1; i < rhsplits; ++i)
{
float si = i / float(rhsplits);
splits[i].nearplane = lambda*(nd*pow(ratio, si)) + (1-lambda)*(nd + (fd - nd)*si);
splits[i-1].farplane = splits[i].nearplane * 1.005f;
}
splits[rhsplits-1].farplane = fd;
}
void radiancehints::setup()
{
updatesplitdist();
loopi(rhsplits)
{
splitinfo &split = splits[i];
vec c;
float radius = calcfrustumboundsphere(split.nearplane, split.farplane, camera1->o, camdir, c);
// compute the projected bounding box of the sphere
const float pradius = ceil(radius * rhpradiustweak), step = (2*pradius) / rhgrid;
vec offset = vec(c).sub(pradius).div(step);
offset.x = floor(offset.x);
offset.y = floor(offset.y);
offset.z = floor(offset.z);
split.cached = split.bounds == pradius ? split.center : vec(-1e16f, -1e16f, -1e16f);
split.center = vec(offset).mul(step).add(pradius);
split.bounds = pradius;
// modify mvp with a scale and offset
// now compute the update model view matrix for this split
split.scale = vec(1/(step*(rhgrid+2*rhborder)), 1/(step*(rhgrid+2*rhborder)), 1/(step*(rhgrid+2*rhborder)*rhsplits));
split.offset = vec(-(offset.x-rhborder)/(rhgrid+2*rhborder), -(offset.y-rhborder)/(rhgrid+2*rhborder), (i - (offset.z-rhborder)/(rhgrid+2*rhborder))/float(rhsplits));
}
}
void radiancehints::bindparams()
{
float step = 2*splits[0].bounds/rhgrid;
GLOBALPARAMF(rhnudge, rhnudge*step);
static GlobalShaderParam rhtc("rhtc");
vec4 *rhtcv = rhtc.reserve<vec4>(rhsplits);
loopi(rhsplits)
{
splitinfo &split = splits[i];
rhtcv[i] = vec4(vec(split.center).mul(-split.scale.x), split.scale.x);//split.bounds*(1 + rhborder*2*0.5f/rhgrid));
}
GLOBALPARAMF(rhbounds, 0.5f*(rhgrid + rhborder)/float(rhgrid + 2*rhborder));
}
bool useradiancehints()
{
return !sunlight.iszero() && csmshadowmap && gi && giscale && gidist;
}
FVAR(avatarshadowdist, 0, 12, 100);
FVAR(avatarshadowbias, 0, 8, 100);
VARF(avatarshadowstencil, 0, 1, 2, initwarning("g-buffer setup", INIT_LOAD, CHANGE_SHADERS));
int avatarmask = 0;
bool useavatarmask() { return avatarshadowstencil && ghasstencil && (!msaasamples || (msaalight && avatarshadowstencil > 1)); }
void enableavatarmask()
{
if(useavatarmask())
{
avatarmask = 0x40;
glStencilFunc(GL_ALWAYS, avatarmask, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glEnable(GL_STENCIL_TEST);
}
}
void disableavatarmask()
{
if(avatarmask)
{
avatarmask = 0;
glDisable(GL_STENCIL_TEST);
}
}
VAR(forcespotlights, 1, 0, 0);
extern int spotlights, volumetricsmalphalights;
static Shader *volumetricshader = nullptr, *volumetricbilateralshader[2] = { nullptr, nullptr };
void clearvolumetricshaders()
{
volumetricshader = nullptr;
loopi(2) volumetricbilateralshader[i] = nullptr;
}
extern int volsteps, volbilateral, volblur, volreduce;
Shader *loadvolumetricshader()
{
string common, shadow;
int commonlen = 0, shadowlen = 0;
if(usegatherforsm()) common[commonlen++] = smfilter > 2 ? 'G' : 'g';
else if(smfilter) common[commonlen++] = smfilter > 2 ? 'E' : (smfilter > 1 ? 'F' : 'f');
else common[commonlen++] = 'N';
if(spotlights || forcespotlights) common[commonlen++] = 's';
common[commonlen] = '\0';
shadow[shadowlen++] = 'p';
if(smalpha > 1 && alphashadow && volumetricsmalphalights) shadow[shadowlen++] = 'P';
shadow[shadowlen] = '\0';
defformatstring(name, "volumetric%s%s%d", common, shadow, volsteps);
return generateshader(name, "volumetricshader \"%s\" \"%s\" %d", common, shadow, volsteps);
}
void loadvolumetricshaders()
{
volumetricshader = loadvolumetricshader();
if(volbilateral) loopi(2)
{
defformatstring(name, "volumetricbilateral%c%d%d", 'x' + i, volbilateral, volreduce);
volumetricbilateralshader[i] = generateshader(name, "volumetricbilateralshader %d %d", volbilateral, volreduce);
}
}
static int volw = -1, volh = -1;
static GLuint volfbo[2] = { 0, 0 }, voltex[2] = { 0, 0 };
void setupvolumetric(int w, int h)
{
volw = w>>volreduce;
volh = h>>volreduce;
loopi(volbilateral || volblur ? 2 : 1)
{
if(!voltex[i]) glGenTextures(1, &voltex[i]);
if(!volfbo[i]) glGenFramebuffers_(1, &volfbo[i]);
glBindFramebuffer_(GL_FRAMEBUFFER, volfbo[i]);
createtexture(voltex[i], volw, volh, nullptr, 3, 1, hdrformat, GL_TEXTURE_RECTANGLE);
glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, voltex[i], 0);
if(glCheckFramebufferStatus_(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
fatal("failed allocating volumetric buffer!");
}
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
loadvolumetricshaders();
}
void cleanupvolumetric()
{
loopi(2) if(volfbo[i]) { glDeleteFramebuffers_(1, &volfbo[i]); volfbo[i] = 0; }
loopi(2) if(voltex[i]) { glDeleteTextures(1, &voltex[i]); voltex[i] = 0; }
volw = volh = -1;
clearvolumetricshaders();
}
VARFP(volumetric, 0, 1, 1, cleanupvolumetric());
VARFP(volreduce, 0, 1, 2, cleanupvolumetric());
VARFP(volbilateral, 0, 1, 3, cleanupvolumetric());
FVAR(volbilateraldepth, 0, 4, 1e3f);
VARFP(volblur, 0, 1, 3, cleanupvolumetric());
VARFP(volsteps, 1, 12, 64, cleanupvolumetric());
FVAR(volminstep, 0, 0.0625f, 1e3f);
FVAR(volprefilter, 0, 4, 1e3f);
FVAR(voldistclamp, 0, 0.99f, 2);
CVAR1R(volcolour, 0x808080);
FVARR(volscale, 0, 1, 16);
VAR(volderiv, -1, 1, 1);
static Shader *deferredlightshader = nullptr, *deferredminimapshader = nullptr, *deferredmsaapixelshader = nullptr, *deferredmsaasampleshader = nullptr;
void cleardeferredlightshaders()
{
deferredlightshader = nullptr;
deferredminimapshader = nullptr;
deferredmsaapixelshader = nullptr;
deferredmsaasampleshader = nullptr;
}
extern int nospeclights, smalphalights;
Shader *loaddeferredlightshader(const char *type = nullptr)
{
string common, shadow, sun;
int commonlen = 0, shadowlen = 0, sunlen = 0;
bool minimap = false, multisample = false, avatar = true;
if(type)
{
if(strchr(type, 'm')) minimap = true;
if(strchr(type, 'M')) multisample = true;
if(strchr(type, 'D')) avatar = false;
copystring(common, type);
commonlen = strlen(common);
}
if(!minimap)
{
if(!multisample || msaalight) common[commonlen++] = 't';
if(avatar && useavatarmask()) common[commonlen++] = 'd';
if(lighttilebatch)
{
common[commonlen++] = 'n';
common[commonlen++] = '0' + lighttilebatch;
}
}
if(usegatherforsm()) common[commonlen++] = smfilter > 2 ? 'G' : 'g';
else if(smfilter) common[commonlen++] = smfilter > 2 ? 'E' : (smfilter > 1 ? 'F' : 'f');
else common[commonlen++] = 'N';
if(spotlights || forcespotlights) common[commonlen++] = 's';
if(nospeclights) common[commonlen++] = 'z';
common[commonlen] = '\0';
shadow[shadowlen++] = 'p';
if(smalpha > 1 && alphashadow > (smalphalights ? 0 : 1)) shadow[shadowlen++] = 'P';
shadow[shadowlen] = '\0';
int usecsm = 0, userh = 0;
if(!sunlight.iszero() && csmshadowmap)
{
usecsm = csmsplits;
sun[sunlen++] = 'c';
if(smalpha && alphashadow) sun[sunlen++] = 'C';
sun[sunlen++] = '0' + csmsplits;
if(!minimap)
{
if(avatar && ao && aosun) sun[sunlen++] = 'A';
if(gi && giscale && gidist)
{
userh = rhsplits;
sun[sunlen++] = 'r';
sun[sunlen++] = '0' + rhsplits;
}
}
}
if(!minimap)
{
if(avatar && ao) sun[sunlen++] = 'a';
if(lighttilebatch && (!usecsm || batchsunlight > (userh ? 1 : 0))) sun[sunlen++] = 'b';
}
sun[sunlen] = '\0';
defformatstring(name, "deferredlight%s%s%s", common, shadow, sun);
return generateshader(name, "deferredlightshader \"%s\" \"%s\" \"%s\" %d %d %d", common, shadow, sun, usecsm, userh, !minimap ? lighttilebatch : 0);
}
void loaddeferredlightshaders()
{
if(msaasamples)
{
string opts;
if(msaalight > 2) copystring(opts, "MS");
else if(msaalight==2) copystring(opts, ghasstencil || !msaaedgedetect ? "MO" : "MOT");
else formatstring(opts, ghasstencil || !msaaedgedetect ? "MR%d" : "MRT%d", msaasamples);
deferredmsaasampleshader = loaddeferredlightshader(opts);
deferredmsaapixelshader = loaddeferredlightshader("M");
deferredlightshader = msaalight ? deferredmsaapixelshader : loaddeferredlightshader("D");
}
else deferredlightshader = loaddeferredlightshader();
}
static inline bool sortlights(int x, int y)
{
const lightinfo &xl = lights[x], &yl = lights[y];
if(!xl.spot) { if(yl.spot) return true; }
else if(!yl.spot) return false;
if(!xl.noshadow()) { if(yl.noshadow()) return true; }
else if(!yl.noshadow()) return false;
if(xl.sz1 < yl.sz1) return true;
else if(xl.sz1 > yl.sz1) return false;
return xl.dist - xl.radius < yl.dist - yl.radius;
}
VAR(lighttilealignw, 1, 16, 256);
VAR(lighttilealignh, 1, 16, 256);
VARN(lighttilew, lighttilemaxw, 1, 10, LIGHTTILE_MAXW);
VARN(lighttileh, lighttilemaxh, 1, 10, LIGHTTILE_MAXH);
int lighttilew = 0, lighttileh = 0, lighttilevieww = 0, lighttileviewh = 0;
void calctilesize()
{
lighttilevieww = (vieww + lighttilealignw - 1)/lighttilealignw;
lighttileviewh = (viewh + lighttilealignh - 1)/lighttilealignh;
lighttilew = min(lighttilevieww, lighttilemaxw);
lighttileh = min(lighttileviewh, lighttilemaxh);
}
void resetlights()
{
shadowcache.reset();
if(smcache)
{
int evictx = ((evictshadowcache%SHADOWCACHE_EVICT)*shadowatlaspacker.w)/SHADOWCACHE_EVICT,
evicty = ((evictshadowcache/SHADOWCACHE_EVICT)*shadowatlaspacker.h)/SHADOWCACHE_EVICT,
evictx2 = (((evictshadowcache%SHADOWCACHE_EVICT)+1)*shadowatlaspacker.w)/SHADOWCACHE_EVICT,
evicty2 = (((evictshadowcache/SHADOWCACHE_EVICT)+1)*shadowatlaspacker.h)/SHADOWCACHE_EVICT;
loopv(shadowmaps)
{
shadowmapinfo &sm = shadowmaps[i];
if(sm.light < 0) continue;
lightinfo &l = lights[sm.light];
if(sm.cached && shadowcachefull)
{
int w = l.spot ? sm.size : sm.size*3, h = l.spot ? sm.size : sm.size*2;
if(sm.x < evictx2 && sm.x + w > evictx && sm.y < evicty2 && sm.y + h > evicty) continue;
}
shadowcache[l] = sm;
}
if(shadowcachefull)
{
evictshadowcache = (evictshadowcache + 1)%(SHADOWCACHE_EVICT*SHADOWCACHE_EVICT);
shadowcachefull = false;
}
}
lights.setsize(0);
lightorder.setsize(0);
shadowmaps.setsize(0);
shadowatlaspacker.reset();
calctilesize();
}
namespace lightsphere
{
vec *verts = nullptr;
GLushort *indices = nullptr;
int numverts = 0, numindices = 0;
GLuint vbuf = 0, ebuf = 0;
void init(int slices, int stacks)
{
numverts = (stacks+1)*(slices+1);
verts = new vec[numverts];
float ds = 1.0f/slices, dt = 1.0f/stacks, t = 1.0f;
loopi(stacks+1)
{
float rho = M_PI*(1-t), s = 0.0f, sinrho = i && i < stacks ? sin(rho) : 0, cosrho = !i ? 1 : (i < stacks ? cos(rho) : -1);
loopj(slices+1)
{
float theta = j==slices ? 0 : 2*M_PI*s;
verts[i*(slices+1) + j] = vec(-sin(theta)*sinrho, -cos(theta)*sinrho, cosrho);
s += ds;
}
t -= dt;
}
numindices = (stacks-1)*slices*3*2;
indices = new ushort[numindices];
GLushort *curindex = indices;
loopi(stacks)
{
loopk(slices)
{
int j = i%2 ? slices-k-1 : k;
if(i)
{
*curindex++ = i*(slices+1)+j;
*curindex++ = i*(slices+1)+j+1;
*curindex++ = (i+1)*(slices+1)+j;
}
if(i+1 < stacks)
{
*curindex++ = i*(slices+1)+j+1;
*curindex++ = (i+1)*(slices+1)+j+1;
*curindex++ = (i+1)*(slices+1)+j;
}
}
}
if(!vbuf) glGenBuffers_(1, &vbuf);
gle::bindvbo(vbuf);
glBufferData_(GL_ARRAY_BUFFER, numverts*sizeof(vec), verts, GL_STATIC_DRAW);
DELETEA(verts);
if(!ebuf) glGenBuffers_(1, &ebuf);
gle::bindebo(ebuf);
glBufferData_(GL_ELEMENT_ARRAY_BUFFER, numindices*sizeof(GLushort), indices, GL_STATIC_DRAW);
DELETEA(indices);
}
void cleanup()
{
if(vbuf) { glDeleteBuffers_(1, &vbuf); vbuf = 0; }
if(ebuf) { glDeleteBuffers_(1, &ebuf); ebuf = 0; }
}
void enable()
{
if(!vbuf) init(8, 4);
gle::bindvbo(vbuf);
gle::bindebo(ebuf);
gle::vertexpointer(sizeof(vec), verts);
gle::enablevertex();
}
void draw()
{
glDrawRangeElements_(GL_TRIANGLES, 0, numverts-1, numindices, GL_UNSIGNED_SHORT, indices);
xtraverts += numindices;
glde++;
}
void disable()
{
gle::disablevertex();
gle::clearvbo();
gle::clearebo();
}
}
VAR(depthtestlights, 0, 2, 2);
FVAR(depthtestlightsclamp, 0, 0.999995f, 1);
VAR(depthfaillights, 0, 1, 1);
FVAR(lightradiustweak, 1, 1.11f, 2);
static inline void lightquad(float z = -1, float sx1 = -1, float sy1 = -1, float sx2 = 1, float sy2 = 1)
{
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(sx2, sy1, z);
gle::attribf(sx1, sy1, z);
gle::attribf(sx2, sy2, z);
gle::attribf(sx1, sy2, z);
gle::end();
}
static inline void lightquads(float z, float sx1, float sy1, float sx2, float sy2)
{
gle::attribf(sx1, sy2, z);
gle::attribf(sx2, sy2, z);
gle::attribf(sx2, sy1, z);
gle::attribf(sx1, sy1, z);
}
static inline void lightquads(float z, float sx1, float sy1, float sx2, float sy2, int tx1, int ty1, int tx2, int ty2)
{
int vx1 = max(int(floor((sx1*0.5f+0.5f)*vieww)), ((tx1*lighttilevieww)/lighttilew)*lighttilealignw),
vy1 = max(int(floor((sy1*0.5f+0.5f)*viewh)), ((ty1*lighttileviewh)/lighttileh)*lighttilealignh),
vx2 = min(int(ceil((sx2*0.5f+0.5f)*vieww)), min(((tx2*lighttilevieww)/lighttilew)*lighttilealignw, vieww)),
vy2 = min(int(ceil((sy2*0.5f+0.5f)*viewh)), min(((ty2*lighttileviewh)/lighttileh)*lighttilealignh, viewh));
lightquads(z, (vx1*2.0f)/vieww-1.0f, (vy1*2.0f)/viewh-1.0f, (vx2*2.0f)/vieww-1.0f, (vy2*2.0f)/viewh-1.0f);
}
static inline void lightquads(float z, float sx1, float sy1, float sx2, float sy2, int x1, int y1, int x2, int y2, const uint *tilemask)
{
if(!tilemask) lightquads(z, sx1, sy1, sx2, sy2, x1, y1, x2, y2);
else for(int y = y1; y < y2;)
{
int starty = y;
uint xmask = (1<<x2) - (1<<x1), startmask = tilemask[y] & xmask;
do ++y; while(y < y2 && (tilemask[y]&xmask) == startmask);
for(int x = x1; x < x2;)
{
while(x < x2 && !(startmask&(1<<x))) ++x;
if(x >= x2) break;
int startx = x;
do ++x; while(x < x2 && startmask&(1<<x));
lightquads(z, sx1, sy1, sx2, sy2, startx, starty, x, y);
}
}
}
static void lightquad(float sz1, float bsx1, float bsy1, float bsx2, float bsy2, const uint *tilemask)
{
int btx1, bty1, btx2, bty2;
calctilebounds(bsx1, bsy1, bsx2, bsy2, btx1, bty1, btx2, bty2);
gle::begin(GL_QUADS);
lightquads(sz1, bsx1, bsy1, bsx2, bsy2, btx1, bty1, btx2, bty2, tilemask);
gle::end();
}
static void bindlighttexs(int msaapass = 0, bool transparent = false)
{
if(msaapass) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mscolortex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gcolortex);
glActiveTexture_(GL_TEXTURE1);
if(msaapass) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msnormaltex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gnormaltex);
if(transparent)
{
glActiveTexture_(GL_TEXTURE2);
if(msaapass) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msglowtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gglowtex);
}
glActiveTexture_(GL_TEXTURE3);
if(msaapass) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE4);
glBindTexture(shadowatlastarget, shadowatlastex);
if(usesmcomparemode()) setsmcomparemode(); else setsmnoncomparemode();
if(ao)
{
glActiveTexture_(GL_TEXTURE5);
glBindTexture(GL_TEXTURE_RECTANGLE, aotex[2] ? aotex[2] : aotex[0]);
}
if(useradiancehints()) loopi(4)
{
glActiveTexture_(GL_TEXTURE6 + i);
glBindTexture(GL_TEXTURE_3D, rhtex[i]);
}
if(smalpha && alphashadow)
{
glActiveTexture_(GL_TEXTURE10);
glBindTexture(GL_TEXTURE_RECTANGLE, csm.rendered > 1 ? (smfilter ? shadowfiltertex : shadowcolortex) : shadowblanktex);
glActiveTexture_(GL_TEXTURE11);
glBindTexture(GL_TEXTURE_RECTANGLE, smfilter ? shadowfiltertex : shadowcolortex);
}
glActiveTexture_(GL_TEXTURE0);
}
static inline void setlightglobals(bool transparent = false)
{
GLOBALPARAMF(shadowatlasscale, 1.0f/shadowatlaspacker.w, 1.0f/shadowatlaspacker.h);
if(ao)
{
if(transparent || drawtex || (editmode && fullbright))
{
GLOBALPARAMF(aoscale, 0.0f, 0.0f);
GLOBALPARAMF(aoparams, 1.0f, 0.0f, 1.0f, 0.0f);
}
else
{
GLOBALPARAM(aoscale, aotex[2] ? vec2(1, 1) : vec2(float(aow)/vieww, float(aoh)/viewh));
GLOBALPARAMF(aoparams, aomin, 1.0f-aomin, aosunmin, 1.0f-aosunmin);
}
}
float lightscale = 2.0f*ldrscaleb;
if(!drawtex && editmode && fullbright)
GLOBALPARAMF(lightscale, fullbrightlevel*lightscale, fullbrightlevel*lightscale, fullbrightlevel*lightscale, 255*lightscale);
else
GLOBALPARAMF(lightscale, ambient.x*lightscale*ambientscale, ambient.y*lightscale*ambientscale, ambient.z*lightscale*ambientscale, 255*lightscale);
if(csm.rendered)
{
csm.bindparams();
rh.bindparams();
if(!drawtex && editmode && fullbright)
{
GLOBALPARAMF(sunlightdir, 0, 0, 0);
GLOBALPARAMF(sunlightcolor, 0, 0, 0);
GLOBALPARAMF(giscale, 0);
GLOBALPARAMF(skylightcolor, 0, 0, 0);
}
else
{
GLOBALPARAM(sunlightdir, sunlightdir);
GLOBALPARAMF(sunlightcolor, sunlight.x*lightscale*sunlightscale, sunlight.y*lightscale*sunlightscale, sunlight.z*lightscale*sunlightscale);
GLOBALPARAMF(giscale, 2*giscale);
GLOBALPARAMF(skylightcolor, 2*giaoscale*skylight.x*lightscale*skylightscale, 2*giaoscale*skylight.y*lightscale*skylightscale, 2*giaoscale*skylight.z*lightscale*skylightscale);
}
}
matrix4 lightmatrix;
lightmatrix.identity();
GLOBALPARAM(lightmatrix, lightmatrix);
}
static LocalShaderParam lightpos("lightpos"), lightcolor("lightcolor"), spotparams("spotparams"), shadowparams("shadowparams"), shadowoffset("shadowoffset");
static vec4 lightposv[8], lightcolorv[8], spotparamsv[8], shadowparamsv[8];
static vec2 shadowoffsetv[8];
static inline void setlightparams(int i, const lightinfo &l)
{
lightposv[i] = vec4(l.o, 1).div(l.radius);
lightcolorv[i] = vec4(vec(l.color).mul(2*ldrscaleb), l.nospec() ? 0 : 1);
if(l.spot > 0) spotparamsv[i] = vec4(vec(l.dir).neg(), 1/(1 - cos360(l.spot)));
if(l.shadowmap >= 0)
{
shadowmapinfo &sm = shadowmaps[l.shadowmap];
float smnearclip = SQRT3 / l.radius, smfarclip = SQRT3,
bias = (smfilter > 2 || shadowatlaspacker.w > SHADOWATLAS_SIZE ? smbias2 : smbias) * (smcullside ? 1 : -1) * smnearclip * (1024.0f / sm.size);
int border = smfilter > 2 ? smborder2 : smborder;
if(l.spot > 0)
{
shadowparamsv[i] = vec4(
-0.5f * sm.size * cotan360(l.spot),
(-smnearclip * smfarclip / (smfarclip - smnearclip) - 0.5f*bias),
1 / (1 + fabs(l.dir.z)),
0.5f + 0.5f * (smfarclip + smnearclip) / (smfarclip - smnearclip));
}
else
{
shadowparamsv[i] = vec4(
-0.5f * (sm.size - border),
-smnearclip * smfarclip / (smfarclip - smnearclip) - 0.5f*bias,
sm.size,
0.5f + 0.5f * (smfarclip + smnearclip) / (smfarclip - smnearclip));
}
shadowoffsetv[i] = vec2(sm.x + 0.5f*sm.size, sm.y + 0.5f*sm.size);
}
}
static inline void setlightshader(Shader *s, int n, bool baselight, bool shadowmap, bool spotlight, bool transparent = false, bool colorshadow = false, bool avatar = false)
{
int variant = (shadowmap ? 1 : 0) + (baselight ? 0 : 2) + (spotlight ? 4 : 0) + (transparent ? 8 : (avatar ? 24 : (colorshadow ? 16 : 0)));
s->setvariant(n - (variant&7 ? 1 : 0), variant);
lightpos.setv(lightposv, n);
lightcolor.setv(lightcolorv, n);
if(spotlight) spotparams.setv(spotparamsv, n);
if(shadowmap)
{
shadowparams.setv(shadowparamsv, n);
shadowoffset.setv(shadowoffsetv, n);
}
}
static inline void setavatarstencil(int stencilref, bool on)
{
glStencilFunc(GL_EQUAL, (on ? 0x40 : 0) | stencilref, !(stencilref&0x08) && msaalight==2 ? 0x47 : 0x4F);
}
static void rendersunpass(Shader *s, int stencilref, bool transparent, float bsx1, float bsy1, float bsx2, float bsy2, const uint *tilemask)
{
if(hasDBT && depthtestlights > 1) glDepthBounds_(0, depthtestlightsclamp);
int tx1 = max(int(floor((bsx1*0.5f+0.5f)*vieww)), 0), ty1 = max(int(floor((bsy1*0.5f+0.5f)*viewh)), 0),
tx2 = min(int(ceil((bsx2*0.5f+0.5f)*vieww)), vieww), ty2 = min(int(ceil((bsy2*0.5f+0.5f)*viewh)), viewh);
s->setvariant(0, transparent ? 8 : 0);
lightquad(-1, (tx1*2.0f)/vieww-1.0f, (ty1*2.0f)/viewh-1.0f, (tx2*2.0f)/vieww-1.0f, (ty2*2.0f)/viewh-1.0f, tilemask);
lightpassesused++;
if(stencilref >= 0)
{
setavatarstencil(stencilref, true);
s->setvariant(0, 24);
lightquad(-1, (tx1*2.0f)/vieww-1.0f, (ty1*2.0f)/viewh-1.0f, (tx2*2.0f)/vieww-1.0f, (ty2*2.0f)/viewh-1.0f, tilemask);
lightpassesused++;
setavatarstencil(stencilref, false);
}
}
static void renderlightsnobatch(Shader *s, int stencilref, bool transparent, float bsx1, float bsy1, float bsx2, float bsy2)
{
lightsphere::enable();
glEnable(GL_SCISSOR_TEST);
bool outside = true;
loop(avatarpass, stencilref >= 0 ? 2 : 1)
{
if(avatarpass) setavatarstencil(stencilref, true);
loopv(lightorder)
{
const lightinfo &l = lights[lightorder[i]];
float sx1 = max(bsx1, l.sx1), sy1 = max(bsy1, l.sy1),
sx2 = min(bsx2, l.sx2), sy2 = min(bsy2, l.sy2);
if(sx1 >= sx2 || sy1 >= sy2 || l.sz1 >= l.sz2 || (avatarpass && l.dist - l.radius > avatarshadowdist)) continue;
matrix4 lightmatrix = camprojmatrix;
lightmatrix.translate(l.o);
lightmatrix.scale(l.radius*lightradiustweak);
GLOBALPARAM(lightmatrix, lightmatrix);
setlightparams(0, l);
bool colorshadow = shadowmaps.inrange(l.shadowmap) && shadowmaps[l.shadowmap].transparent;
setlightshader(s, 1, false, l.shadowmap >= 0, l.spot > 0, transparent, colorshadow, avatarpass > 0);
int tx1 = int(floor((sx1*0.5f+0.5f)*vieww)), ty1 = int(floor((sy1*0.5f+0.5f)*viewh)),
tx2 = int(ceil((sx2*0.5f+0.5f)*vieww)), ty2 = int(ceil((sy2*0.5f+0.5f)*viewh));
glScissor(tx1, ty1, tx2-tx1, ty2-ty1);
if(hasDBT && depthtestlights > 1) glDepthBounds_(l.sz1*0.5f + 0.5f, min(l.sz2*0.5f + 0.5f, depthtestlightsclamp));
if(camera1->o.dist(l.o) <= l.radius*lightradiustweak + nearplane + 1 && depthfaillights)
{
if(outside)
{
outside = false;
glDepthFunc(GL_GEQUAL);
glCullFace(GL_FRONT);
}
}
else if(!outside)
{
outside = true;
glDepthFunc(GL_LESS);
glCullFace(GL_BACK);
}
lightsphere::draw();
lightpassesused++;
}
if(avatarpass) setavatarstencil(stencilref, false);
}
if(!outside)
{
outside = true;
glDepthFunc(GL_LESS);
glCullFace(GL_BACK);
}
glDisable(GL_SCISSOR_TEST);
lightsphere::disable();
}
static void renderlightbatches(Shader *s, int stencilref, bool transparent, float bsx1, float bsy1, float bsx2, float bsy2, const uint *tilemask, bool sunpass)
{
int btx1, bty1, btx2, bty2;
calctilebounds(bsx1, bsy1, bsx2, bsy2, btx1, bty1, btx2, bty2);
loopv(lightbatches)
{
lightbatch &batch = *lightbatches[i];
if(!batch.overlaps(btx1, bty1, btx2, bty2, tilemask)) continue;
int n = batch.numlights;
float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1, sz1 = 1, sz2 = -1;
loopj(n)
{
const lightinfo &l = lights[batch.lights[j]];
setlightparams(j, l);
l.addscissor(sx1, sy1, sx2, sy2, sz1, sz2);
}
bool baselight = !(batch.flags & BF_NOSUN) && !sunpass;
if(baselight) { sx1 = bsx1; sy1 = bsy1; sx2 = bsx2; sy2 = bsy2; sz1 = -1; sz2 = 1; }
else
{
sx1 = max(sx1, bsx1); sy1 = max(sy1, bsy1); sx2 = min(sx2, bsx2); sy2 = min(sy2, bsy2);
if(sx1 >= sx2 || sy1 >= sy2 || sz1 >= sz2) continue;
}
if(n)
{
bool shadowmap = !(batch.flags & BF_NOSHADOW), spotlight = (batch.flags & BF_SPOTLIGHT) != 0, colorshadow = (batch.flags & BF_SMALPHA) != 0;
setlightshader(s, n, baselight, shadowmap, spotlight, transparent, colorshadow);
}
else s->setvariant(0, transparent ? 8 : 0);
lightpassesused++;
if(hasDBT && depthtestlights > 1) glDepthBounds_(sz1*0.5f + 0.5f, min(sz2*0.5f + 0.5f, depthtestlightsclamp));
gle::begin(GL_QUADS);
loopvj(batch.rects)
{
const lightrect &r = batch.rects[j];
int x1 = max(int(r.x1), btx1), y1 = max(int(r.y1), bty1),
x2 = min(int(r.x2), btx2), y2 = min(int(r.y2), bty2);
if(x1 < x2 && y1 < y2) lightquads(sz1, sx1, sy1, sx2, sy2, x1, y1, x2, y2, tilemask);
}
gle::end();
}
if(stencilref >= 0)
{
setavatarstencil(stencilref, true);
bool baselight = !sunpass;
for(int offset = 0; baselight || offset < lightorder.length(); baselight = false)
{
int n = 0;
bool shadowmap = false, spotlight = false;
float sx1 = 1, sy1 = 1, sx2 = -1, sy2 = -1, sz1 = 1, sz2 = -1;
for(; offset < lightorder.length(); offset++)
{
const lightinfo &l = lights[lightorder[offset]];
if(l.dist - l.radius > avatarshadowdist) continue;
if(!n)
{
shadowmap = l.shadowmap >= 0;
spotlight = l.spot > 0;
}
else if(n >= lighttilebatch || (l.shadowmap >= 0) != shadowmap || (l.spot > 0) != spotlight) break;
setlightparams(n++, l);
l.addscissor(sx1, sy1, sx2, sy2, sz1, sz2);
}
if(baselight) { sx1 = bsx1; sy1 = bsy1; sx2 = bsx2; sy2 = bsy2; sz1 = -1; sz2 = 1; }
else
{
if(!n) break;
sx1 = max(sx1, bsx1); sy1 = max(sy1, bsy1); sx2 = min(sx2, bsx2); sy2 = min(sy2, bsy2);
if(sx1 >= sx2 || sy1 >= sy2 || sz1 >= sz2) continue;
}
if(n) setlightshader(s, n, baselight, shadowmap, spotlight, false, false, true);
else s->setvariant(0, 24);
if(hasDBT && depthtestlights > 1) glDepthBounds_(sz1*0.5f + 0.5f, min(sz2*0.5f + 0.5f, depthtestlightsclamp));
lightquad(sz1, sx1, sy1, sx2, sy2, tilemask);
lightpassesused++;
}
setavatarstencil(stencilref, false);
}
}
void renderlights(float bsx1 = -1, float bsy1 = -1, float bsx2 = 1, float bsy2 = 1, const uint *tilemask = nullptr, int stencilmask = 0, int msaapass = 0, bool transparent = false)
{
Shader *s = drawtex == DRAWTEX_MINIMAP ? deferredminimapshader : (msaapass <= 0 ? deferredlightshader : (msaapass > 1 ? deferredmsaasampleshader : deferredmsaapixelshader));
if(!s || s == nullshader) return;
bool depth = true;
if(!depthtestlights) { glDisable(GL_DEPTH_TEST); depth = false; }
else glDepthMask(GL_FALSE);
bindlighttexs(msaapass, transparent);
setlightglobals(transparent);
gle::defvertex(3);
bool avatar = useavatarmask() && !transparent && !drawtex;
int stencilref = -1;
if(msaapass == 1 && ghasstencil)
{
int tx1 = max(int(floor((bsx1*0.5f+0.5f)*vieww)), 0), ty1 = max(int(floor((bsy1*0.5f+0.5f)*viewh)), 0),
tx2 = min(int(ceil((bsx2*0.5f+0.5f)*vieww)), vieww), ty2 = min(int(ceil((bsy2*0.5f+0.5f)*viewh)), viewh);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
if(stencilmask) glStencilFunc(GL_EQUAL, stencilmask|0x08, 0x07);
else
{
glStencilFunc(GL_ALWAYS, 0x08, ~0);
glEnable(GL_STENCIL_TEST);
}
if(avatar) glStencilMask(~0x40);
if(depthtestlights && depth) { glDisable(GL_DEPTH_TEST); depth = false; }
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
SETSHADER(msaaedgedetect);
lightquad(-1, (tx1*2.0f)/vieww-1.0f, (ty1*2.0f)/viewh-1.0f, (tx2*2.0f)/vieww-1.0f, (ty2*2.0f)/viewh-1.0f, tilemask);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_EQUAL, stencilref = stencilmask, (avatar ? 0x40 : 0) | (msaalight==2 ? 0x07 : 0x0F));
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
if(avatar) glStencilMask(~0);
else if(msaalight==2 && !stencilmask) glDisable(GL_STENCIL_TEST);
}
else if(msaapass == 2)
{
if(ghasstencil) glStencilFunc(GL_EQUAL, stencilref = stencilmask|0x08, avatar ? 0x4F : 0x0F);
if(msaalight==2) { glSampleMaski_(0, 2); glEnable(GL_SAMPLE_MASK); }
}
else if(ghasstencil && (stencilmask || avatar))
{
if(!stencilmask) glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_EQUAL, stencilref = stencilmask, avatar ? 0x4F : 0x0F);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}
if(!avatar) stencilref = -1;
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
if(hasDBT && depthtestlights > 1) glEnable(GL_DEPTH_BOUNDS_TEST_EXT);
bool sunpass = !lighttilebatch || drawtex == DRAWTEX_MINIMAP || (csm.rendered && batchsunlight <= (gi && giscale && gidist ? 1 : 0));
if(sunpass)
{
if(depthtestlights && depth) { glDisable(GL_DEPTH_TEST); depth = false; }
rendersunpass(s, stencilref, transparent, bsx1, bsy1, bsx2, bsy2, tilemask);
}
if(depthtestlights && !depth) { glEnable(GL_DEPTH_TEST); depth = true; }
if(!lighttilebatch || drawtex == DRAWTEX_MINIMAP)
{
renderlightsnobatch(s, stencilref, transparent, bsx1, bsy1, bsx2, bsy2);
}
else
{
renderlightbatches(s, stencilref, transparent, bsx1, bsy1, bsx2, bsy2, tilemask, sunpass);
}
if(msaapass == 1 && ghasstencil)
{
if(msaalight==2 && !stencilmask && !avatar) glEnable(GL_STENCIL_TEST);
}
else if(msaapass == 2)
{
if(ghasstencil && !stencilmask) glDisable(GL_STENCIL_TEST);
if(msaalight==2) glDisable(GL_SAMPLE_MASK);
}
else if(avatar && !stencilmask) glDisable(GL_STENCIL_TEST);
glDisable(GL_BLEND);
if(!depthtestlights) glEnable(GL_DEPTH_TEST);
else
{
glDepthMask(GL_TRUE);
if(hasDBT && depthtestlights > 1) glDisable(GL_DEPTH_BOUNDS_TEST_EXT);
}
}
extern int volumetriclights;
void rendervolumetric()
{
if(!volumetric || !volumetriclights || !volscale) return;
float bsx1 = 1, bsy1 = 1, bsx2 = -1, bsy2 = -1;
loopv(lightorder)
{
const lightinfo &l = lights[lightorder[i]];
if(!l.volumetric() || l.checkquery()) continue;
l.addscissor(bsx1, bsy1, bsx2, bsy2);
}
if(bsx1 >= bsx2 || bsy1 >= bsy2) return;
timer *voltimer = begintimer("volumetric lights");
glBindFramebuffer_(GL_FRAMEBUFFER, volfbo[0]);
glViewport(0, 0, volw, volh);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture_(GL_TEXTURE3);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE4);
glBindTexture(shadowatlastarget, shadowatlastex);
if(usesmcomparemode()) setsmcomparemode(); else setsmnoncomparemode();
if(smalpha > 1 && alphashadow && volumetricsmalphalights)
{
glActiveTexture_(GL_TEXTURE11);
glBindTexture(GL_TEXTURE_RECTANGLE, smfilter ? shadowfiltertex : shadowcolortex);
}
glActiveTexture_(GL_TEXTURE0);
GLOBALPARAMF(shadowatlasscale, 1.0f/shadowatlaspacker.w, 1.0f/shadowatlaspacker.h);
GLOBALPARAMF(volscale, float(vieww)/volw, float(viewh)/volh, float(volw)/vieww, float(volh)/viewh);
GLOBALPARAMF(volminstep, volminstep);
GLOBALPARAMF(volprefilter, volprefilter);
GLOBALPARAMF(voldistclamp, farplane*voldistclamp);
glBlendFunc(GL_ONE, GL_ONE);
glEnable(GL_BLEND);
if(!depthtestlights) glDisable(GL_DEPTH_TEST);
else glDepthMask(GL_FALSE);
lightsphere::enable();
glEnable(GL_SCISSOR_TEST);
if(volderiv >= 0) glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, volderiv ? GL_NICEST : GL_FASTEST);
bool outside = true;
loopv(lightorder)
{
const lightinfo &l = lights[lightorder[i]];
if(!l.volumetric() || l.checkquery()) continue;
matrix4 lightmatrix = camprojmatrix;
lightmatrix.translate(l.o);
lightmatrix.scale(l.radius*lightradiustweak);
GLOBALPARAM(lightmatrix, lightmatrix);
bool colorshadow = l.colorshadow() && l.shadowmap >= 0 && shadowmaps[l.shadowmap].transparent;
if(l.spot > 0)
{
volumetricshader->setvariant(0, l.shadowmap >= 0 ? (colorshadow ? 4 : 3) : 2);
LOCALPARAM(spotparams, vec4(l.dir, 1/(1 - cos360(l.spot))));
}
else if(l.shadowmap >= 0) volumetricshader->setvariant(0, colorshadow ? 1 : 0);
else volumetricshader->set();
LOCALPARAM(lightpos, vec4(l.o, 1).div(l.radius));
vec color = vec(l.color).mul(ldrscaleb).mul(volcolour.tocolor().mul(volscale));
LOCALPARAM(lightcolor, color);
if(l.shadowmap >= 0)
{
shadowmapinfo &sm = shadowmaps[l.shadowmap];
float smnearclip = SQRT3 / l.radius, smfarclip = SQRT3,
bias = (smfilter > 2 ? smbias2 : smbias) * (smcullside ? 1 : -1) * smnearclip * (1024.0f / sm.size);
int border = smfilter > 2 ? smborder2 : smborder;
if(l.spot > 0)
{
LOCALPARAMF(shadowparams,
0.5f * sm.size * cotan360(l.spot),
(-smnearclip * smfarclip / (smfarclip - smnearclip) - 0.5f*bias),
1 / (1 + fabs(l.dir.z)),
0.5f + 0.5f * (smfarclip + smnearclip) / (smfarclip - smnearclip));
}
else
{
LOCALPARAMF(shadowparams,
0.5f * (sm.size - border),
-smnearclip * smfarclip / (smfarclip - smnearclip) - 0.5f*bias,
sm.size,
0.5f + 0.5f * (smfarclip + smnearclip) / (smfarclip - smnearclip));
}
LOCALPARAMF(shadowoffset, sm.x + 0.5f*sm.size, sm.y + 0.5f*sm.size);
}
int tx1 = int(floor((l.sx1*0.5f+0.5f)*volw)), ty1 = int(floor((l.sy1*0.5f+0.5f)*volh)),
tx2 = int(ceil((l.sx2*0.5f+0.5f)*volw)), ty2 = int(ceil((l.sy2*0.5f+0.5f)*volh));
glScissor(tx1, ty1, tx2-tx1, ty2-ty1);
if(camera1->o.dist(l.o) <= l.radius*lightradiustweak + nearplane + 1 && depthfaillights)
{
if(outside)
{
outside = false;
if(depthtestlights) glDisable(GL_DEPTH_TEST);
glCullFace(GL_FRONT);
}
}
else if(!outside)
{
outside = true;
if(depthtestlights) glEnable(GL_DEPTH_TEST);
glCullFace(GL_BACK);
}
lightsphere::draw();
}
if(!outside)
{
outside = true;
glCullFace(GL_BACK);
}
lightsphere::disable();
if(volderiv >= 0) glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_DONT_CARE);
if(depthtestlights)
{
glDepthMask(GL_TRUE);
glDisable(GL_DEPTH_TEST);
}
int cx1 = int(floor((bsx1*0.5f+0.5f)*volw))&~1,
cy1 = int(floor((bsy1*0.5f+0.5f)*volh))&~1,
cx2 = (int(ceil((bsx2*0.5f+0.5f)*volw))&~1) + 2,
cy2 = (int(ceil((bsy2*0.5f+0.5f)*volh))&~1) + 2;
if(volbilateral || volblur)
{
int radius = (volbilateral ? volbilateral : volblur)*2;
cx1 = max(cx1 - radius, 0);
cy1 = max(cy1 - radius, 0);
cx2 = min(cx2 + radius, volw);
cy2 = min(cy2 + radius, volh);
glScissor(cx1, cy1, cx2-cx1, cy2-cy1);
glDisable(GL_BLEND);
if(volbilateral) loopi(2)
{
glBindFramebuffer_(GL_FRAMEBUFFER, volfbo[(i+1)%2]);
glViewport(0, 0, volw, volh);
volumetricbilateralshader[i]->set();
setbilateralparams(volbilateral, volbilateraldepth);
glBindTexture(GL_TEXTURE_RECTANGLE, voltex[i%2]);
screenquadoffset(0.25f, 0.25f, vieww, viewh);
}
else
{
float blurweights[MAXBLURRADIUS+1], bluroffsets[MAXBLURRADIUS+1];
setupblurkernel(volblur, blurweights, bluroffsets);
loopi(2)
{
glBindFramebuffer_(GL_FRAMEBUFFER, volfbo[(i+1)%2]);
glViewport(0, 0, volw, volh);
setblurshader(i%2, 1, volblur, blurweights, bluroffsets, GL_TEXTURE_RECTANGLE);
glBindTexture(GL_TEXTURE_RECTANGLE, voltex[i%2]);
screenquad(volw, volh);
}
}
glEnable(GL_BLEND);
}
glBindFramebuffer_(GL_FRAMEBUFFER, msaalight ? mshdrfbo : hdrfbo);
glViewport(0, 0, vieww, viewh);
int margin = (1<<volreduce) - 1;
cx1 = max((cx1 * vieww) / volw - margin, 0);
cy1 = max((cy1 * viewh) / volh - margin, 0);
cx2 = min((cx2 * vieww + margin + volw - 1) / volw, vieww);
cy2 = min((cy2 * viewh + margin + volh - 1) / volh, viewh);
glScissor(cx1, cy1, cx2-cx1, cy2-cy1);
bool avatar = useavatarmask();
if(avatar)
{
glStencilFunc(GL_EQUAL, 0, 0x40);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glEnable(GL_STENCIL_TEST);
}
SETSHADER(scalelinear);
glBindTexture(GL_TEXTURE_RECTANGLE, voltex[0]);
screenquad(volw, volh);
if(volbilateral || volblur)
{
swap(volfbo[0], volfbo[1]);
swap(voltex[0], voltex[1]);
}
if(avatar) glDisable(GL_STENCIL_TEST);
glDisable(GL_SCISSOR_TEST);
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
endtimer(voltimer);
}
VAR(oqvol, 0, 1, 1);
VAR(oqlights, 0, 1, 1);
VAR(debuglightscissor, 0, 0, 1);
void viewlightscissor()
{
vector<extentity *> &ents = entities::getents();
gle::defvertex(2);
loopv(entgroup)
{
int idx = entgroup[i];
if(ents.inrange(idx) && ents[idx]->type == ET_LIGHT)
{
extentity &e = *ents[idx];
loopvj(lights) if(lights[j].o == e.o)
{
lightinfo &l = lights[j];
if(!l.validscissor()) break;
gle::colorf(l.color.x/255, l.color.y/255, l.color.z/255);
float x1 = (l.sx1+1)/2*hudw, x2 = (l.sx2+1)/2*hudw,
y1 = (1-l.sy1)/2*hudh, y2 = (1-l.sy2)/2*hudh;
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(x1, y1);
gle::attribf(x2, y1);
gle::attribf(x1, y2);
gle::attribf(x2, y2);
gle::end();
}
}
}
}
void collectlights()
{
if(lights.length()) return;
// point lights processed here
const vector<extentity *> &ents = entities::getents();
if(!editmode || !fullbright) loopv(ents)
{
const extentity *e = ents[i];
if(e->type != ET_LIGHT || e->attr1 <= 0) continue;
if(smviscull)
{
if(isfoggedsphere(e->attr1, e->o)) continue;
if(pvsoccludedsphere(e->o, e->attr1)) continue;
}
lightinfo &l = lights.add(lightinfo(i, *e));
if(l.validscissor()) lightorder.add(lights.length()-1);
}
int numdynlights = 0;
if(!drawtex)
{
updatedynlights();
numdynlights = finddynlights();
}
loopi(numdynlights)
{
vec o, color, dir;
float radius;
int spot, flags;
if(!getdynlight(i, o, radius, color, dir, spot, flags)) continue;
lightinfo &l = lights.add(lightinfo(o, vec(color).mul(255).max(0), radius, flags, dir, spot));
if(l.validscissor()) lightorder.add(lights.length()-1);
}
lightorder.sort(sortlights);
bool queried = false;
if(!drawtex && smquery && oqfrags && oqlights) loopv(lightorder)
{
int idx = lightorder[i];
lightinfo &l = lights[idx];
if((l.noshadow() && (!oqvol || !l.volumetric())) || l.radius >= worldsize) continue;
vec bbmin, bbmax;
l.calcbb(bbmin, bbmax);
if(!camera1->o.insidebb(bbmin, bbmax, 2))
{
l.query = newquery(&l);
if(l.query)
{
if(!queried)
{
startbb(false);
queried = true;
}
startquery(l.query);
ivec bo(bbmin), br = ivec(bbmax).sub(bo).add(1);
drawbb(bo, br);
endquery(l.query);
}
}
}
if(queried)
{
endbb(false);
glFlush();
}
smused = 0;
if(smcache && !smnoshadow && shadowcache.numelems) loop(mismatched, 2) loopv(lightorder)
{
int idx = lightorder[i];
lightinfo &l = lights[idx];
if(l.noshadow()) continue;
shadowcacheval *cached = shadowcache.access(l);
if(!cached) continue;
float prec = smprec, lod;
int w, h;
if(l.spot) { w = 1; h = 1; prec *= tan360(l.spot); lod = smspotprec; }
else { w = 3; h = 2; lod = smcubeprec; }
lod *= std::clamp(l.radius * prec / sqrtf(max(1.0f, l.dist/l.radius)), float(smminsize), float(smmaxsize));
int size = (std::clamp(int(ceil((lod * shadowatlaspacker.w) / SHADOWATLAS_SIZE)), 1, shadowatlaspacker.w / w) + smalign)&~smalign;
w *= size;
h *= size;
if(mismatched)
{
if(cached->size == size) continue;
ushort x = USHRT_MAX, y = USHRT_MAX;
if(!shadowatlaspacker.insert(x, y, w, h)) continue;
addshadowmap(x, y, size, l.shadowmap, idx);
}
else
{
if(cached->size != size) continue;
ushort x = cached->x, y = cached->y;
shadowatlaspacker.reserve(x, y, w, h);
addshadowmap(x, y, size, l.shadowmap, idx, cached);
}
smused += w*h;
}
}
static bool inoq = false;
VAR(csminoq, 0, 1, 1);
VAR(sminoq, 0, 1, 1);
VAR(rhinoq, 0, 1, 1);
static inline bool shouldworkinoq()
{
return !drawtex && oqfrags && (!wireframe || !editmode);
}
struct batchrect : lightrect
{
uchar group;
ushort idx;
batchrect() {}
batchrect(const lightinfo &l, ushort idx)
: lightrect(l),
group((l.shadowmap < 0 ? BF_NOSHADOW : (shadowmaps[l.shadowmap].transparent ? BF_SMALPHA : 0)) | (l.spot > 0 ? BF_SPOTLIGHT : 0)),
idx(idx)
{}
};
struct batchstack : lightrect
{
ushort offset, numrects;
uchar flags;
batchstack() {}
batchstack(uchar x1, uchar y1, uchar x2, uchar y2, ushort offset, ushort numrects, uchar flags = 0) : lightrect(x1, y1, x2, y2), offset(offset), numrects(numrects), flags(flags) {}
};
static vector<batchrect> batchrects;
static void batchlights(const batchstack &initstack)
{
batchstack stack[32];
size_t numstack = 1;
stack[0] = initstack;
while(numstack > 0)
{
batchstack s = stack[--numstack];
if(numstack + 5 > sizeof(stack)/sizeof(stack[0])) { batchlights(s); continue; }
++lightbatchstacksused;
int groups[BF_NOSUN] = { 0 };
lightrect split(s);
ushort splitidx = USHRT_MAX;
int outside = s.offset, inside = s.offset + s.numrects;
for(int i = outside; i < inside; ++i)
{
const batchrect &r = batchrects[i];
if(r.outside(s))
{
if(i != outside) swap(batchrects[i], batchrects[outside]);
++outside;
}
else if(s.inside(r))
{
++groups[r.group];
swap(batchrects[i--], batchrects[--inside]);
}
else if(r.idx < splitidx) { split = r; splitidx = r.idx; }
}
uchar flags = s.flags;
int batched = s.offset + s.numrects;
loop(g, BF_NOSUN) while(groups[g] >= lighttilebatch || (inside == outside && (groups[g] || !(flags & BF_NOSUN))))
{
lightbatchkey key;
key.flags = flags | g;
flags |= BF_NOSUN;
int n = min(groups[g], lighttilebatch);
groups[g] -= n;
key.numlights = n;
loopi(n)
{
int best = -1;
ushort bestidx = USHRT_MAX;
for(int j = inside; j < batched; ++j) { const batchrect &r = batchrects[j]; if(r.group == g && r.idx < bestidx) { best = j; bestidx = r.idx; } }
key.lights[i] = lightorder[bestidx];
swap(batchrects[best], batchrects[--batched]);
}
lightbatch &batch = lightbatcher[key];
if(batch.rects.empty())
{
(lightbatchkey &)batch = key;
lightbatches.add(&batch);
}
batch.rects.add(s);
++lightbatchrectsused;
}
if(splitidx != USHRT_MAX)
{
int numoverlap = batched - outside;
split.intersect(s);
if(split.y1 > s.y1) stack[numstack++] = batchstack(s.x1, s.y1, s.x2, split.y1, outside, numoverlap, flags);
if(split.x1 > s.x1) stack[numstack++] = batchstack(s.x1, split.y1, split.x1, split.y2, outside, numoverlap, flags);
stack[numstack++] = batchstack(split.x1, split.y1, split.x2, split.y2, outside, numoverlap, flags);
if(split.x2 < s.x2) stack[numstack++] = batchstack(split.x2, split.y1, s.x2, split.y2, outside, numoverlap, flags);
if(split.y2 < s.y2) stack[numstack++] = batchstack(s.x1, split.y2, s.x2, s.y2, outside, numoverlap, flags);
}
}
}
static inline bool sortlightbatches(const lightbatch *x, const lightbatch *y)
{
if(x->flags < y->flags) return true;
if(x->flags > y->flags) return false;
return x->numlights > y->numlights;
}
static void batchlights()
{
lightbatches.setsize(0);
lightbatchstacksused = 0;
lightbatchrectsused = 0;
if(lighttilebatch && drawtex != DRAWTEX_MINIMAP)
{
lightbatcher.recycle();
batchlights(batchstack(0, 0, lighttilew, lighttileh, 0, batchrects.length()));
lightbatches.sort(sortlightbatches);
}
lightbatchesused = lightbatches.length();
}
void packlights()
{
lightsvisible = lightsoccluded = 0;
lightpassesused = 0;
batchrects.setsize(0);
loopv(lightorder)
{
int idx = lightorder[i];
lightinfo &l = lights[idx];
if(l.checkquery())
{
if(l.shadowmap >= 0)
{
shadowmaps[l.shadowmap].light = -1;
l.shadowmap = -1;
}
l.batched = ~0;
lightsoccluded++;
continue;
}
if(!l.noshadow() && !smnoshadow && l.shadowmap < 0)
{
float prec = smprec, lod;
int w, h;
if(l.spot) { w = 1; h = 1; prec *= tan360(l.spot); lod = smspotprec; }
else { w = 3; h = 2; lod = smcubeprec; }
lod *= std::clamp(l.radius * prec / sqrtf(max(1.0f, l.dist/l.radius)), float(smminsize), float(smmaxsize));
int size = (std::clamp(int(ceil((lod * shadowatlaspacker.w) / SHADOWATLAS_SIZE)), 1, shadowatlaspacker.w / w) + smalign)&~smalign;
w *= size;
h *= size;
ushort x = USHRT_MAX, y = USHRT_MAX;
if(shadowatlaspacker.insert(x, y, w, h))
{
addshadowmap(x, y, size, l.shadowmap, idx);
smused += w*h;
}
else if(smcache) shadowcachefull = true;
}
l.batched = batchrects.length();
batchrects.add(batchrect(l, i));
}
lightsvisible = lightorder.length() - lightsoccluded;
}
static inline void nogiquad(int x, int y, int w, int h)
{
gle::attribf(x, y+h);
gle::attribf(x+w, y+h);
gle::attribf(x+w, y);
gle::attribf(x, y);
}
static inline bool rendernogi(cube *c, const ivec &o, int size, const ivec &bbmin, const ivec &bbmax, int minsize)
{
ivec mid = ivec(o).add(size);
uchar overlap = 0;
if(bbmin.y < mid.y)
{
if(bbmin.x < mid.x)
{
if((bbmin.z < mid.z && (c[0].children ? rendernogi(c[0].children, ivec(o.x, o.y, o.z), size>>1, bbmin, bbmax, minsize) : c[0].material&MAT_NOGI)) ||
(bbmax.z > mid.z && (c[4].children ? rendernogi(c[4].children, ivec(o.x, o.y, mid.z), size>>1, bbmin, bbmax, minsize) : c[4].material&MAT_NOGI)))
overlap |= 1;
}
if(bbmax.x > mid.x)
{
if((bbmin.z < mid.z && (c[1].children ? rendernogi(c[1].children, ivec(mid.x, o.y, o.z), size>>1, bbmin, bbmax, minsize) : c[1].material&MAT_NOGI)) ||
(bbmax.z > mid.z && (c[5].children ? rendernogi(c[5].children, ivec(mid.x, o.y, mid.z), size>>1, bbmin, bbmax, minsize) : c[5].material&MAT_NOGI)))
overlap |= 2;
}
}
if(bbmax.y > mid.y)
{
if(bbmin.x < mid.x)
{
if((bbmin.z < mid.z && (c[2].children ? rendernogi(c[2].children, ivec(o.x, mid.y, o.z), size>>1, bbmin, bbmax, minsize) : c[2].material&MAT_NOGI)) ||
(bbmax.z > mid.z && (c[6].children ? rendernogi(c[6].children, ivec(o.x, mid.y, mid.z), size>>1, bbmin, bbmax, minsize) : c[6].material&MAT_NOGI)))
overlap |= 4;
}
if(bbmax.x > mid.x)
{
if((bbmin.z < mid.z && (c[3].children ? rendernogi(c[3].children, ivec(mid.x, mid.y, o.z), size>>1, bbmin, bbmax, minsize) : c[3].material&MAT_NOGI)) ||
(bbmax.z > mid.z && (c[7].children ? rendernogi(c[7].children, ivec(mid.x, mid.y, mid.z), size>>1, bbmin, bbmax, minsize) : c[7].material&MAT_NOGI)))
overlap |= 8;
}
}
if(!overlap) return false;
if(overlap == 0xF || size <= minsize) return true;
if(overlap&1)
{
if(overlap&2) nogiquad(o.x, o.y, 2*size, size);
else nogiquad(o.x, o.y, size, size);
}
else if(overlap&2) nogiquad(o.x+size, o.y, size, size);
if(overlap&4)
{
if(overlap&8) nogiquad(o.x, o.y+size, 2*size, size);
else nogiquad(o.x, o.y+size, size, size);
}
else if(overlap&8) nogiquad(o.x+size, o.y+size, size, size);
return false;
}
static inline void rendernogi(const ivec &bbmin, const ivec &bbmax, int minsize)
{
if(rendernogi(worldroot, ivec(0, 0, 0), worldsize>>1, ivec(bbmin).max(nogimin), ivec(bbmax).min(nogimax), minsize))
nogiquad(0, 0, worldsize, worldsize);
}
static inline void rhquad(float x1, float y1, float x2, float y2, float tx1, float ty1, float tx2, float ty2, float tz)
{
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(x2, y1); gle::attribf(tx2, ty1, tz);
gle::attribf(x1, y1); gle::attribf(tx1, ty1, tz);
gle::attribf(x2, y2); gle::attribf(tx2, ty2, tz);
gle::attribf(x1, y2); gle::attribf(tx1, ty2, tz);
gle::end();
}
static inline void rhquad(float dx1, float dy1, float dx2, float dy2, float dtx1, float dty1, float dtx2, float dty2, float dtz,
float px1, float py1, float px2, float py2, float ptx1, float pty1, float ptx2, float pty2, float ptz)
{
gle::begin(GL_TRIANGLE_STRIP);
gle::attribf(dx2, dy1); gle::attribf(dtx2, dty1, dtz);
gle::attribf(px2, py1); gle::attribf(ptx2, pty1, ptz);
gle::attribf(dx1, dy1); gle::attribf(dtx1, dty1, dtz);
gle::attribf(px1, py1); gle::attribf(ptx1, pty1, ptz);
gle::attribf(dx1, dy2); gle::attribf(dtx1, dty2, dtz);
gle::attribf(px1, py2); gle::attribf(ptx1, pty2, ptz);
gle::attribf(dx2, dy2); gle::attribf(dtx2, dty2, dtz);
gle::attribf(px2, py2); gle::attribf(ptx2, pty2, ptz);
gle::attribf(dx2, dy1); gle::attribf(dtx2, dty1, dtz);
gle::attribf(px2, py1); gle::attribf(ptx2, pty1, ptz);
gle::end();
}
void radiancehints::renderslices()
{
int sw = rhgrid+2*rhborder, sh = rhgrid+2*rhborder;
glBindFramebuffer_(GL_FRAMEBUFFER, rhfbo);
if(!rhrect)
{
glViewport(0, 0, sw, sh);
if(rhcache)
{
loopi(4) swap(rhtex[i], rhtex[i+4]);
uint clearmasks[RH_MAXSPLITS][(RH_MAXGRID+2+31)/32];
memcpy(clearmasks, rhclearmasks[0], sizeof(clearmasks));
memcpy(rhclearmasks[0], rhclearmasks[1], sizeof(clearmasks));
memcpy(rhclearmasks[1], clearmasks, sizeof(clearmasks));
}
}
GLOBALPARAMF(rhatten, 1.0f/(gidist*gidist));
GLOBALPARAMF(rsmspread, gidist*rsmspread*rsm.scale.x, gidist*rsmspread*rsm.scale.y);
GLOBALPARAMF(rhaothreshold, splits[0].bounds/rhgrid);
GLOBALPARAMF(rhaoatten, 1.0f/(gidist*rsmspread));
GLOBALPARAMF(rhaoheight, gidist*rsmspread);
matrix4 rsmtcmatrix;
rsmtcmatrix.identity();
rsmtcmatrix.settranslation(rsm.offset);
rsmtcmatrix.setscale(rsm.scale);
rsmtcmatrix.mul(rsm.model);
GLOBALPARAM(rsmtcmatrix, rsmtcmatrix);
matrix4 rsmworldmatrix;
rsmworldmatrix.invert(rsmtcmatrix);
GLOBALPARAM(rsmworldmatrix, rsmworldmatrix);
glBindTexture(GL_TEXTURE_RECTANGLE, rsmdepthtex);
glActiveTexture_(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_RECTANGLE, rsmcolortex);
glActiveTexture_(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_RECTANGLE, rsmnormaltex);
if(rhborder) loopi(4)
{
glActiveTexture_(GL_TEXTURE3 + i);
glBindTexture(GL_TEXTURE_3D, rhtex[i]);
}
if(rhcache) loopi(4)
{
glActiveTexture_(GL_TEXTURE7 + i);
glBindTexture(GL_TEXTURE_3D, rhtex[rhrect ? i : 4+i]);
}
glActiveTexture_(GL_TEXTURE0);
glClearColor(0.5f, 0.5f, 0.5f, 0);
if(rhrect) glEnable(GL_SCISSOR_TEST);
gle::defvertex(2);
gle::deftexcoord0(3);
bool prevcached = true;
int cx = -1, cy = -1;
loopirev(rhsplits)
{
splitinfo &split = splits[i];
float cellradius = split.bounds/rhgrid, step = 2*cellradius, nudge = rhnudge*2*splits[0].bounds/rhgrid + rhworldbias*step;
vec cmin, cmax, dmin(1e16f, 1e16f, 1e16f), dmax(-1e16f, -1e16f, -1e16f), bmin(1e16f, 1e16f, 1e16f), bmax(-1e16f, -1e16f, -1e16f);
loopk(3)
{
cmin[k] = floor((worldmin[k] - nudge - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds;
cmax[k] = ceil((worldmax[k] + nudge - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds;
}
if(prevdynmin.z < prevdynmax.z) loopk(3)
{
dmin[k] = min(dmin[k], (float)floor((prevdynmin[k] - gidist - cellradius - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds);
dmax[k] = max(dmax[k], (float)ceil((prevdynmax[k] + gidist + cellradius - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds);
}
if(dynmin.z < dynmax.z) loopk(3)
{
dmin[k] = min(dmin[k], (float)floor((dynmin[k] - gidist - cellradius - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds);
dmax[k] = max(dmax[k], (float)ceil((dynmax[k] + gidist + cellradius - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds);
}
if((rhrect || !rhcache || hasCI) && split.cached == split.center && (!rhborder || prevcached) && !rhforce &&
(dmin.x > split.center.x + split.bounds || dmax.x < split.center.x - split.bounds ||
dmin.y > split.center.y + split.bounds || dmax.y < split.center.y - split.bounds ||
dmin.z > split.center.z + split.bounds || dmax.z < split.center.z - split.bounds))
{
if(rhrect || !rhcache || split.copied) continue;
split.copied = true;
loopk(4) glCopyImageSubData_(rhtex[4+k], GL_TEXTURE_3D, 0, 0, 0, i*sh, rhtex[k], GL_TEXTURE_3D, 0, 0, 0, i*sh, sw, sh, sh);
continue;
}
prevcached = false;
split.copied = false;
GLOBALPARAM(rhcenter, split.center);
GLOBALPARAMF(rhbounds, split.bounds);
GLOBALPARAMF(rhspread, cellradius);
if(rhborder && i + 1 < rhsplits)
{
GLOBALPARAMF(bordercenter, 0.5f, 0.5f, float(i+1 + 0.5f)/rhsplits);
GLOBALPARAMF(borderrange, 0.5f - 0.5f/(rhgrid+2), 0.5f - 0.5f/(rhgrid+2), (0.5f - 0.5f/(rhgrid+2))/rhsplits);
GLOBALPARAMF(borderscale, rhgrid+2, rhgrid+2, (rhgrid+2)*rhsplits);
splitinfo &next = splits[i+1];
loopk(3)
{
bmin[k] = floor((max(float(worldmin[k] - nudge), next.center[k] - next.bounds) - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds;
bmax[k] = ceil((min(float(worldmax[k] + nudge), next.center[k] + next.bounds) - (split.center[k] - split.bounds))/step)*step + split.center[k] - split.bounds;
}
}
uint clearmasks[(RH_MAXGRID+2+31)/32];
memset(clearmasks, 0xFF, sizeof(clearmasks));
int sy = rhrect ? i*sh : 0;
loopjrev(sh)
{
int sx = rhrect ? j*sw : 0;
#define BIND_SLICE do { \
if(rhrect) \
{ \
glViewport(sx, sy, sw, sh); \
glScissor(sx, sy, sw, sh); \
} \
else \
{ \
glFramebufferTexture3D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_3D, rhtex[0], 0, i*sh + j); \
glFramebufferTexture3D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_3D, rhtex[1], 0, i*sh + j); \
glFramebufferTexture3D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_3D, rhtex[2], 0, i*sh + j); \
glFramebufferTexture3D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT3, GL_TEXTURE_3D, rhtex[3], 0, i*sh + j); \
} \
} while(0)
float x1 = split.center.x - split.bounds, x2 = split.center.x + split.bounds,
y1 = split.center.y - split.bounds, y2 = split.center.y + split.bounds,
z = split.center.z - split.bounds + (j-rhborder+0.5f)*step,
vx1 = -1 + rhborder*2.0f/(rhgrid+2), vx2 = 1 - rhborder*2.0f/(rhgrid+2), vy1 = -1 + rhborder*2.0f/(rhgrid+2), vy2 = 1 - rhborder*2.0f/(rhgrid+2),
tx1 = x1, tx2 = x2, ty1 = y1, ty2 = y2;
bool clipped = false;
if(rhborder && i + 1 < rhsplits)
{
splitinfo &next = splits[i+1];
float bx1 = x1-step, bx2 = x2+step, by1 = y1-step, by2 = y2+step, bz = z,
bvx1 = -1, bvx2 = 1, bvy1 = -1, bvy2 = 1,
btx1 = bx1, btx2 = bx2, bty1 = by1, bty2 = by2;
if(rhclipgrid)
{
if(bz < bmin.z || bz > bmax.z) goto noborder;
if(bx1 < bmin.x || bx2 > bmax.x || by1 < bmin.y || by2 > bmax.y)
{
btx1 = max(bx1, bmin.x);
btx2 = min(bx2, bmax.x);
bty1 = max(by1, bmin.y);
bty2 = min(by2, bmax.y);
if(btx1 > tx2 || bty1 > bty2) goto noborder;
bvx1 += 2*(btx1 - bx1)/(bx2 - bx1);
bvx2 += 2*(btx2 - bx2)/(bx2 - bx1);
bvy1 += 2*(bty1 - by1)/(by2 - by1);
bvy2 += 2*(bty2 - by2)/(by2 - by1);
clipped = true;
}
}
btx1 = btx1*next.scale.x + next.offset.x;
btx2 = btx2*next.scale.x + next.offset.x;
bty1 = bty1*next.scale.y + next.offset.y;
bty2 = bty2*next.scale.y + next.offset.y;
bz = bz*next.scale.z + next.offset.z;
BIND_SLICE;
if(clipped) glClear(GL_COLOR_BUFFER_BIT);
SETSHADER(radiancehintsborder);
rhquad(bvx1, bvy1, bvx2, bvy2, btx1, bty1, btx2, bty2, bz);
clearmasks[j/32] &= ~(1 << (j%32));
}
noborder:
if(j < rhborder || j >= rhgrid + rhborder)
{
skipped:
if(clearmasks[j/32] & (1 << (j%32)) && (!rhrect || cx < 0) && !(rhclearmasks[0][i][j/32] & (1 << (j%32))))
{
BIND_SLICE;
glClear(GL_COLOR_BUFFER_BIT);
cx = sx;
cy = sy;
}
continue;
}
if(rhclipgrid)
{
if(z < cmin.z || z > cmax.z) goto skipped;
if(x1 < cmin.x || x2 > cmax.x || y1 < cmin.y || y2 > cmax.y)
{
tx1 = max(x1, cmin.x);
tx2 = min(x2, cmax.x);
ty1 = max(y1, cmin.y);
ty2 = min(y2, cmax.y);
if(tx1 > tx2 || ty1 > ty2) goto skipped;
vx1 += 2*rhgrid/float(sw)*(tx1 - x1)/(x2 - x1);
vx2 += 2*rhgrid/float(sw)*(tx2 - x2)/(x2 - x1);
vy1 += 2*rhgrid/float(sh)*(ty1 - y1)/(y2 - y1);
vy2 += 2*rhgrid/float(sh)*(ty2 - y2)/(y2 - y1);
clipped = true;
}
}
if(clearmasks[j/32] & (1 << (j%32)))
{
BIND_SLICE;
if(clipped || (rhborder && i + 1 >= rhsplits)) glClear(GL_COLOR_BUFFER_BIT);
clearmasks[j/32] &= ~(1 << (j%32));
}
if(rhcache && z > split.cached.z - split.bounds && z < split.cached.z + split.bounds)
{
float px1 = max(tx1, split.cached.x - split.bounds), px2 = min(tx2, split.cached.x + split.bounds),
py1 = max(ty1, split.cached.y - split.bounds), py2 = min(ty2, split.cached.y + split.bounds);
if(px1 < px2 && py1 < py2)
{
float pvx1 = -1 + rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sw)*(px1 - x1)/(x2 - x1),
pvx2 = 1 - rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sw)*(px2 - x2)/(x2 - x1),
pvy1 = -1 + rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sh)*(py1 - y1)/(y2 - y1),
pvy2 = 1 - rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sh)*(py2 - y2)/(y2 - y1),
ptx1 = (px1 + split.center.x - split.cached.x)*split.scale.x + split.offset.x,
ptx2 = (px2 + split.center.x - split.cached.x)*split.scale.x + split.offset.x,
pty1 = (py1 + split.center.y - split.cached.y)*split.scale.y + split.offset.y,
pty2 = (py2 + split.center.y - split.cached.y)*split.scale.y + split.offset.y,
pz = (z + split.center.z - split.cached.z)*split.scale.z + split.offset.z;
if(px1 != tx1 || px2 != tx2 || py1 != ty1 || py2 != ty2)
{
radiancehintsshader->set();
rhquad(pvx1, pvy1, pvx2, pvy2, px1, py1, px2, py2, z,
vx1, vy1, vx2, vy2, tx1, ty1, tx2, ty2, z);
}
if(z > dmin.z && z < dmax.z)
{
float dx1 = max(px1, dmin.x), dx2 = min(px2, dmax.x),
dy1 = max(py1, dmin.y), dy2 = min(py2, dmax.y);
if(dx1 < dx2 && dy1 < dy2)
{
float dvx1 = -1 + rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sw)*(dx1 - x1)/(x2 - x1),
dvx2 = 1 - rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sw)*(dx2 - x2)/(x2 - x1),
dvy1 = -1 + rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sh)*(dy1 - y1)/(y2 - y1),
dvy2 = 1 - rhborder*2.0f/(rhgrid+2) + 2*rhgrid/float(sh)*(dy2 - y2)/(y2 - y1),
dtx1 = (dx1 + split.center.x - split.cached.x)*split.scale.x + split.offset.x,
dtx2 = (dx2 + split.center.x - split.cached.x)*split.scale.x + split.offset.x,
dty1 = (dy1 + split.center.y - split.cached.y)*split.scale.y + split.offset.y,
dty2 = (dy2 + split.center.y - split.cached.y)*split.scale.y + split.offset.y,
dz = (z + split.center.z - split.cached.z)*split.scale.z + split.offset.z;
if(dx1 != px1 || dx2 != px2 || dy1 != py1 || dy2 != py2)
{
SETSHADER(radiancehintscached);
rhquad(dvx1, dvy1, dvx2, dvy2, dtx1, dty1, dtx2, dty2, dz,
pvx1, pvy1, pvx2, pvy2, ptx1, pty1, ptx2, pty2, pz);
}
radiancehintsshader->set();
rhquad(dvx1, dvy1, dvx2, dvy2, dx1, dy1, dx2, dy2, z);
goto maskslice;
}
}
SETSHADER(radiancehintscached);
rhquad(pvx1, pvy1, pvx2, pvy2, ptx1, pty1, ptx2, pty2, pz);
goto maskslice;
}
}
radiancehintsshader->set();
rhquad(vx1, vy1, vx2, vy2, tx1, ty1, tx2, ty2, z);
maskslice:
if(i) continue;
rendernogi(ivec::floor(vec(x1, y1, z - 0.5f*step)), ivec::ceil(vec(x2, y2, z + 0.5f*step)), int(step));
if(gle::attribbuf.empty()) continue;
SETSHADER(radiancehintsdisable);
if(rhborder)
{
glScissor(sx + rhborder, sy + rhborder, sw - 2*rhborder, sh - 2*rhborder);
if(!rhrect) glEnable(GL_SCISSOR_TEST);
}
gle::defvertex(2);
gle::begin(GL_QUADS);
gle::end();
if(rhborder && !rhrect) glDisable(GL_SCISSOR_TEST);
gle::defvertex(2);
gle::deftexcoord0(3);
}
if(rhrect) loopk(4)
{
glReadBuffer(GL_COLOR_ATTACHMENT0+k);
glBindTexture(GL_TEXTURE_3D, rhtex[k]);
loopj(sh)
{
if(clearmasks[j/32] & (1 << (j%32)))
{
if(!(rhclearmasks[0][i][j/32] & (1 << (j%32)))) glCopyTexSubImage3D_(GL_TEXTURE_3D, 0, 0, 0, sy+j, cx, cy, sw, sh);
continue;
}
glCopyTexSubImage3D_(GL_TEXTURE_3D, 0, 0, 0, sy+j, j*sw, sy, sw, sh);
}
}
memcpy(rhclearmasks[0][i], clearmasks, sizeof(clearmasks));
}
if(rhrect) glDisable(GL_SCISSOR_TEST);
}
void renderradiancehints()
{
if(rhinoq && !inoq && shouldworkinoq()) return;
if(!useradiancehints()) return;
timer *rhcputimer = begintimer("radiance hints", false);
timer *rhtimer = begintimer("radiance hints");
rh.setup();
rsm.setup();
shadowmapping = SM_REFLECT;
shadowside = 0;
shadoworigin = vec(0, 0, 0);
shadowdir = rsm.lightview;
shadowbias = rsm.lightview.project_bb(worldmin, worldmax);
shadowradius = fabs(rsm.lightview.project_bb(worldmax, worldmin));
findshadowvas();
findshadowmms();
shadowmaskbatchedmodels(false);
batchshadowmapmodels();
rh.prevdynmin = rh.dynmin;
rh.prevdynmax = rh.dynmax;
rh.dynmin = vec(1e16f, 1e16f, 1e16f);
rh.dynmax = vec(-1e16f, -1e16f, -1e16f);
if(rhdyntex) dynamicshadowvabounds(1<<shadowside, rh.dynmin, rh.dynmax);
if(rhdynmm) batcheddynamicmodelbounds(1<<shadowside, rh.dynmin, rh.dynmax);
if(rhforce || rh.prevdynmin.z < rh.prevdynmax.z || rh.dynmin.z < rh.dynmax.z || !rh.allcached())
{
if(inoq)
{
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glDepthMask(GL_TRUE);
}
glBindFramebuffer_(GL_FRAMEBUFFER, rsmfbo);
shadowmatrix.mul(rsm.proj, rsm.model);
GLOBALPARAM(rsmmatrix, shadowmatrix);
GLOBALPARAMF(rsmdir, -rsm.lightview.x, -rsm.lightview.y, -rsm.lightview.z);
glViewport(0, 0, rsmsize, rsmsize);
glClearColor(0, 0, 0, 0);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
renderrsmgeom(rhdyntex!=0);
rendershadowmodelbatches(rhdynmm!=0);
rh.renderslices();
if(inoq)
{
glBindFramebuffer_(GL_FRAMEBUFFER, msaasamples ? msfbo : gfbo);
glViewport(0, 0, vieww, viewh);
glFlush();
}
}
clearbatchedmapmodels();
shadowmapping = 0;
endtimer(rhtimer);
endtimer(rhcputimer);
}
static void setupshadowtransparent()
{
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(1, 1, 1, 1);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_ZERO, GL_SRC_COLOR);
}
static void cleanupshadowtransparent()
{
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
}
static inline bool clearshadowtransparent(int idx, int side)
{
if(shadowtransparent&(1<<side)) return false;
shadowcolorclears.add(idx * 6 + side);
return true;
}
static void rendershadowtransparent(int idx, int side, bool cullside = false)
{
const shadowmapinfo &sm = shadowmaps[idx];
int sidex = 0, sidey = 0;
if(sm.light >= 0 && side > 0)
{
sidex = (side>>1)*sm.size;
sidey = (side&1)*sm.size;
}
glViewport(sm.x + sidex, sm.y + sidey, sm.size, sm.size);
glScissor(sm.x + sidex, sm.y + sidey, sm.size, sm.size);
glClear(GL_COLOR_BUFFER_BIT);
shadowside = side;
renderalphashadow(cullside);
if(smfilter) shadowcolorblurs.add(idx * 6 + side);
}
void rendercsmshadowmaps()
{
if(csminoq && !debugshadowatlas && !inoq && shouldworkinoq()) return;
csm.rendered = 0;
if(sunlight.iszero() || !csmshadowmap) return;
csm.rendered = 1;
if(inoq)
{
glBindFramebuffer_(GL_FRAMEBUFFER, shadowatlasfbo);
glDepthMask(GL_TRUE);
}
csm.setup();
shadowmapping = SM_CASCADE;
shadoworigin = vec(0, 0, 0);
shadowdir = csm.lightview;
shadowbias = csm.lightview.project_bb(worldmin, worldmax);
shadowradius = fabs(csm.lightview.project_bb(worldmax, worldmin));
float polyfactor = csmpolyfactor, polyoffset = csmpolyoffset;
if(smfilter > 2) { polyfactor = csmpolyfactor2; polyoffset = csmpolyoffset2; }
if(polyfactor || polyoffset)
{
glPolygonOffset(polyfactor, polyoffset);
glEnable(GL_POLYGON_OFFSET_FILL);
}
glEnable(GL_SCISSOR_TEST);
findshadowvas(smalpha && alphashadow);
if(shadowtransparent) csm.rendered = 2;
findshadowmms();
shadowmaskbatchedmodels(smdynshadow!=0);
batchshadowmapmodels();
loopi(csmsplits) if(csm.splits[i].idx >= 0)
{
const cascadedshadowmap::splitinfo &split = csm.splits[i];
const shadowmapinfo &sm = shadowmaps[split.idx];
shadowmatrix.mul(split.proj, csm.model);
GLOBALPARAM(shadowmatrix, shadowmatrix);
glViewport(sm.x, sm.y, sm.size, sm.size);
glScissor(sm.x, sm.y, sm.size, sm.size);
glClear(GL_DEPTH_BUFFER_BIT);
shadowside = i;
rendershadowmapworld();
rendershadowmodelbatches();
}
if(shadowtransparent)
{
setupshadowtransparent();
loopi(csmsplits) if(csm.splits[i].idx >= 0)
{
const cascadedshadowmap::splitinfo &split = csm.splits[i];
if(clearshadowtransparent(split.idx, i)) continue;
shadowmatrix.mul(split.proj, csm.model);
GLOBALPARAM(shadowmatrix, shadowmatrix);
rendershadowtransparent(split.idx, i);
}
cleanupshadowtransparent();
}
clearbatchedmapmodels();
glDisable(GL_SCISSOR_TEST);
if(polyfactor || polyoffset) glDisable(GL_POLYGON_OFFSET_FILL);
shadowmapping = 0;
if(inoq)
{
glBindFramebuffer_(GL_FRAMEBUFFER, msaasamples ? msfbo : gfbo);
glViewport(0, 0, vieww, viewh);
glFlush();
}
}
int calcshadowinfo(const extentity &e, vec &origin, float &radius, vec &spotloc, int &spotangle, float &bias)
{
if(e.attr5&L_NOSHADOW || e.attr1 <= smminradius) return SM_NONE;
origin = e.o;
radius = e.attr1;
int type, w, border;
float lod;
if(e.attached && e.attached->type == ET_SPOTLIGHT)
{
type = SM_SPOT;
w = 1;
border = 0;
lod = smspotprec;
spotloc = e.attached->o;
spotangle = std::clamp(int(e.attached->attr1), 1, 89);
}
else
{
type = SM_CUBEMAP;
w = 3;
lod = smcubeprec;
border = smfilter > 2 ? smborder2 : smborder;
spotloc = e.o;
spotangle = 0;
}
lod *= smminsize;
int size = (std::clamp(int(ceil((lod * shadowatlaspacker.w) / SHADOWATLAS_SIZE)), 1, shadowatlaspacker.w / w) + smalign)&~smalign;
bias = border / float(size - border);
return type;
}
matrix4 shadowmatrix;
void rendershadowmaps(int offset = 0)
{
if(!(sminoq && !debugshadowatlas && !inoq && shouldworkinoq())) offset = 0;
for(; offset < shadowmaps.length(); offset++) if(shadowmaps[offset].light >= 0) break;
if(offset >= shadowmaps.length()) return;
if(inoq)
{
glBindFramebuffer_(GL_FRAMEBUFFER, shadowatlasfbo);
glDepthMask(GL_TRUE);
}
float polyfactor = smpolyfactor, polyoffset = smpolyoffset;
if(smfilter > 2) { polyfactor = smpolyfactor2; polyoffset = smpolyoffset2; }
if(polyfactor || polyoffset)
{
glPolygonOffset(polyfactor, polyoffset);
glEnable(GL_POLYGON_OFFSET_FILL);
}
glEnable(GL_SCISSOR_TEST);
const vector<extentity *> &ents = entities::getents();
for(int i = offset; i < shadowmaps.length(); i++)
{
shadowmapinfo &sm = shadowmaps[i];
if(sm.light < 0) continue;
lightinfo &l = lights[sm.light];
extentity *e = l.ent >= 0 ? ents[l.ent] : nullptr;
int border, sidemask;
if(l.spot)
{
shadowmapping = SM_SPOT;
border = 0;
sidemask = 1;
}
else
{
shadowmapping = SM_CUBEMAP;
border = smfilter > 2 ? smborder2 : smborder;
sidemask = drawtex == DRAWTEX_MINIMAP ? 0x2F : (smsidecull ? cullfrustumsides(l.o, l.radius, sm.size, border) : 0x3F);
}
sm.sidemask = sidemask;
shadoworigin = l.o;
shadowradius = l.radius;
shadowbias = border / float(sm.size - border);
shadowdir = l.dir;
shadowspot = l.spot;
shadowmesh *mesh = e ? findshadowmesh(l.ent, *e) : nullptr;
findshadowvas(smalpha > 1 && alphashadow > (l.colorshadow() ? 0 : 1));
if(shadowtransparent)
{
sm.transparent = shadowtransparent;
if(batchrects.inrange(l.batched)) batchrects[l.batched].group |= BF_SMALPHA;
}
findshadowmms();
shadowmaskbatchedmodels(!(l.flags&L_NODYNSHADOW) && smdynshadow);
batchshadowmapmodels(mesh != nullptr);
shadowcacheval *cached = nullptr;
int cachemask = 0;
if(smcache)
{
int dynmask = smcache <= 1 ? (batcheddynamicmodels() | dynamicshadowvas()) : 0;
cached = sm.cached;
if(cached)
{
if(!debugshadowatlas) cachemask = cached->sidemask & ~dynmask;
sm.sidemask |= cachemask;
}
sm.sidemask &= ~dynmask;
sidemask &= ~cachemask;
if(!sidemask) { clearbatchedmapmodels(); continue; }
}
float smnearclip = SQRT3 / l.radius, smfarclip = SQRT3;
matrix4 smprojmatrix(vec4(float(sm.size - border) / sm.size, 0, 0, 0),
vec4(0, float(sm.size - border) / sm.size, 0, 0),
vec4(0, 0, -(smfarclip + smnearclip) / (smfarclip - smnearclip), -1),
vec4(0, 0, -2*smnearclip*smfarclip / (smfarclip - smnearclip), 0));
if(shadowmapping == SM_SPOT)
{
glViewport(sm.x, sm.y, sm.size, sm.size);
glScissor(sm.x, sm.y, sm.size, sm.size);
glClear(GL_DEPTH_BUFFER_BIT);
float invradius = 1.0f / l.radius, spotscale = invradius * cotan360(l.spot);
matrix4 spotmatrix(vec(l.spotx).mul(spotscale), vec(l.spoty).mul(spotscale), vec(l.dir).mul(-invradius));
spotmatrix.translate(vec(l.o).neg());
shadowmatrix.mul(smprojmatrix, spotmatrix);
GLOBALPARAM(shadowmatrix, shadowmatrix);
glCullFace((l.dir.z >= 0) == !smcullside ? GL_FRONT : GL_BACK);
shadowside = 0;
if(mesh) rendershadowmesh(mesh); else rendershadowmapworld();
rendershadowmodelbatches();
if(shadowtransparent)
{
setupshadowtransparent();
rendershadowtransparent(i, 0, (l.dir.z >= 0) == !smcullside);
cleanupshadowtransparent();
}
}
else
{
if(!cachemask)
{
int cx1 = sidemask & 0x03 ? 0 : (sidemask & 0xC ? sm.size : 2 * sm.size),
cx2 = sidemask & 0x30 ? 3 * sm.size : (sidemask & 0xC ? 2 * sm.size : sm.size),
cy1 = sidemask & 0x15 ? 0 : sm.size,
cy2 = sidemask & 0x2A ? 2 * sm.size : sm.size;
glScissor(sm.x + cx1, sm.y + cy1, cx2 - cx1, cy2 - cy1);
glClear(GL_DEPTH_BUFFER_BIT);
}
loop(side, 6) if(sidemask&(1<<side))
{
int sidex = (side>>1)*sm.size, sidey = (side&1)*sm.size;
glViewport(sm.x + sidex, sm.y + sidey, sm.size, sm.size);
glScissor(sm.x + sidex, sm.y + sidey, sm.size, sm.size);
if(cachemask) glClear(GL_DEPTH_BUFFER_BIT);
matrix4 cubematrix(cubeshadowviewmatrix[side]);
cubematrix.scale(1.0f/l.radius);
cubematrix.translate(vec(l.o).neg());
shadowmatrix.mul(smprojmatrix, cubematrix);
GLOBALPARAM(shadowmatrix, shadowmatrix);
glCullFace((side & 1) ^ (side >> 2) ^ smcullside ? GL_FRONT : GL_BACK);
shadowside = side;
if(mesh) rendershadowmesh(mesh); else rendershadowmapworld();
rendershadowmodelbatches();
}
if(shadowtransparent)
{
setupshadowtransparent();
loop(side, 6) if(sidemask&(1<<side))
{
if(clearshadowtransparent(i, side)) continue;
matrix4 cubematrix(cubeshadowviewmatrix[side]);
cubematrix.scale(1.0f/l.radius);
cubematrix.translate(vec(l.o).neg());
shadowmatrix.mul(smprojmatrix, cubematrix);
GLOBALPARAM(shadowmatrix, shadowmatrix);
rendershadowtransparent(i, side, (side & 1) ^ (side >> 2) ^ smcullside);
}
cleanupshadowtransparent();
}
}
clearbatchedmapmodels();
}
glCullFace(GL_BACK);
glDisable(GL_SCISSOR_TEST);
if(polyfactor || polyoffset) glDisable(GL_POLYGON_OFFSET_FILL);
shadowmapping = 0;
if(inoq)
{
glBindFramebuffer_(GL_FRAMEBUFFER, msaasamples ? msfbo : gfbo);
glViewport(0, 0, vieww, viewh);
glFlush();
}
}
static void filtershadowcolors()
{
if(shadowfilterfbo)
{
glBindFramebuffer_(GL_FRAMEBUFFER, shadowfilterfbo);
glViewport(0, 0, shadowatlaspacker.w/2, shadowatlaspacker.h/2);
}
else glViewport(0, 0, shadowatlaspacker.w, shadowatlaspacker.h);
glDepthMask(GL_FALSE);
GLOBALPARAMF(shadowatlasscale, 1.0f/shadowatlaspacker.w, 1.0f/shadowatlaspacker.h);
if(shadowcolorclears.length())
{
SETSHADER(smalphaclear);
gle::defvertex(2);
gle::begin(GL_QUADS);
loopv(shadowcolorclears)
{
shadowmapinfo &sm = shadowmaps[shadowcolorclears[i] / 6];
int side = shadowcolorclears[i] % 6;
int x = sm.x, y = sm.y;
if(sm.light >= 0 && side > 0)
{
x += (side>>1)*sm.size;
y += (side&1)*sm.size;
}
gle::attribf(x, y);
gle::attribf(x, y+sm.size);
gle::attribf(x+sm.size, y+sm.size);
gle::attribf(x+sm.size, y);
}
shadowcolorclears.setsize(0);
xtraverts += gle::end();
}
if(shadowcolorblurs.length())
{
if(usegatherforsm())
{
SETSHADER(smalphablur2d);
glBindTexture(GL_TEXTURE_2D, shadowcolortex);
}
else
{
SETSHADER(smalphablurrect);
glBindTexture(GL_TEXTURE_RECTANGLE, shadowcolortex);
}
gle::defvertex(2);
gle::deftexcoord0(4);
gle::begin(GL_QUADS);
loopv(shadowcolorblurs)
{
shadowmapinfo &sm = shadowmaps[shadowcolorblurs[i] / 6];
int side = shadowcolorblurs[i] % 6;
int x = sm.x, y = sm.y;
if(sm.light >= 0 && side > 0)
{
x += (side>>1)*sm.size;
y += (side&1)*sm.size;
}
gle::attribf(x, y); gle::attribf(x, y, x+sm.size, y+sm.size);
gle::attribf(x, y+sm.size); gle::attribf(x, y, x+sm.size, y+sm.size);
gle::attribf(x+sm.size, y+sm.size); gle::attribf(x, y, x+sm.size, y+sm.size);
gle::attribf(x+sm.size, y); gle::attribf(x, y, x+sm.size, y+sm.size);
}
shadowcolorblurs.setsize(0);
xtraverts += gle::end();
}
glDepthMask(GL_TRUE);
}
void rendershadowatlas()
{
timer *smcputimer = begintimer("shadow map", false);
timer *smtimer = begintimer("shadow map");
glBindFramebuffer_(GL_FRAMEBUFFER, shadowatlasfbo);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
if(debugshadowatlas)
{
glClearDepth(0);
if(shadowcolortex)
{
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(0, 0, 0, 0);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
}
else glClear(GL_DEPTH_BUFFER_BIT);
glClearDepth(1);
}
// sun light
rendercsmshadowmaps();
int smoffset = shadowmaps.length();
packlights();
// point lights
rendershadowmaps(smoffset);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
if(shadowcolorclears.length() || shadowcolorblurs.length()) filtershadowcolors();
batchlights();
endtimer(smtimer);
endtimer(smcputimer);
}
void workinoq()
{
collectlights();
if(drawtex) return;
game::rendergame();
if(shouldworkinoq())
{
inoq = true;
if(csminoq && !debugshadowatlas) rendercsmshadowmaps();
if(sminoq && !debugshadowatlas) rendershadowmaps();
if(rhinoq) renderradiancehints();
inoq = false;
}
}
FVAR(refractmargin, 0, 0.1f, 1);
FVAR(refractdepth, 1e-3f, 16, 1e3f);
int transparentlayer = 0;
void rendertransparent()
{
int hasalphavas = findalphavas();
int hasmats = findmaterials();
bool hasmodels = transmdlsx1 < transmdlsx2 && transmdlsy1 < transmdlsy2;
if(!hasalphavas && !hasmats && !hasmodels)
{
if(!editmode) renderparticles();
return;
}
if(!editmode && particlelayers && ghasstencil) renderparticles(PL_UNDER);
timer *transtimer = begintimer("transparent");
if(hasalphavas&4 || hasmats&4)
{
glBindFramebuffer_(GL_FRAMEBUFFER, msaalight ? msrefractfbo : refractfbo);
glDepthMask(GL_FALSE);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
float sx1 = min(alpharefractsx1, matrefractsx1), sy1 = min(alpharefractsy1, matrefractsy1),
sx2 = max(alpharefractsx2, matrefractsx2), sy2 = max(alpharefractsy2, matrefractsy2);
bool scissor = sx1 > -1 || sy1 > -1 || sx2 < 1 || sy2 < 1;
if(scissor)
{
int x1 = int(floor(max(sx1*0.5f+0.5f-refractmargin*viewh/vieww, 0.0f)*vieww)),
y1 = int(floor(max(sy1*0.5f+0.5f-refractmargin, 0.0f)*viewh)),
x2 = int(ceil(min(sx2*0.5f+0.5f+refractmargin*viewh/vieww, 1.0f)*vieww)),
y2 = int(ceil(min(sy2*0.5f+0.5f+refractmargin, 1.0f)*viewh));
glEnable(GL_SCISSOR_TEST);
glScissor(x1, y1, x2 - x1, y2 - y1);
}
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
if(scissor) glDisable(GL_SCISSOR_TEST);
GLOBALPARAMF(refractdepth, 1.0f/refractdepth);
SETSHADER(refractmask);
if(hasalphavas&4) renderrefractmask();
if(hasmats&4) rendermaterialmask();
glDepthMask(GL_TRUE);
}
glActiveTexture_(GL_TEXTURE7);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msrefracttex);
else glBindTexture(GL_TEXTURE_RECTANGLE, refracttex);
glActiveTexture_(GL_TEXTURE8);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mshdrtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, hdrtex);
glActiveTexture_(GL_TEXTURE9);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE0);
if(ghasstencil) glEnable(GL_STENCIL_TEST);
matrix4 raymatrix(vec(-0.5f*vieww*projmatrix.a.x, 0, 0.5f*vieww - 0.5f*vieww*projmatrix.c.x),
vec(0, -0.5f*viewh*projmatrix.b.y, 0.5f*viewh - 0.5f*viewh*projmatrix.c.y));
raymatrix.muld(cammatrix);
GLOBALPARAM(raymatrix, raymatrix);
GLOBALPARAM(linearworldmatrix, linearworldmatrix);
uint tiles[LIGHTTILE_MAXH];
float allsx1 = 1, allsy1 = 1, allsx2 = -1, allsy2 = -1, sx1, sy1, sx2, sy2;
loop(layer, 4)
{
switch(layer)
{
case 0:
if(!(hasmats&1)) continue;
sx1 = matliquidsx1; sy1 = matliquidsy1; sx2 = matliquidsx2; sy2 = matliquidsy2;
memcpy(tiles, matliquidtiles, sizeof(tiles));
break;
case 1:
if(!(hasalphavas&1)) continue;
sx1 = alphabacksx1; sy1 = alphabacksy1; sx2 = alphabacksx2; sy2 = alphabacksy2;
memcpy(tiles, alphatiles, sizeof(tiles));
break;
case 2:
if(!(hasalphavas&2) && !(hasmats&2)) continue;
sx1 = alphafrontsx1; sy1 = alphafrontsy1; sx2 = alphafrontsx2; sy2 = alphafrontsy2;
memcpy(tiles, alphatiles, sizeof(tiles));
if(hasmats&2)
{
sx1 = min(sx1, matsolidsx1);
sy1 = min(sy1, matsolidsy1);
sx2 = max(sx2, matsolidsx2);
sy2 = max(sy2, matsolidsy2);
loopj(LIGHTTILE_MAXH) tiles[j] |= matsolidtiles[j];
}
break;
case 3:
if(!hasmodels) continue;
sx1 = transmdlsx1; sy1 = transmdlsy1; sx2 = transmdlsx2; sy2 = transmdlsy2;
memcpy(tiles, transmdltiles, sizeof(tiles));
break;
default:
continue;
}
transparentlayer = layer+1;
allsx1 = min(allsx1, sx1);
allsy1 = min(allsy1, sy1);
allsx2 = max(allsx2, sx2);
allsy2 = max(allsy2, sy2);
glBindFramebuffer_(GL_FRAMEBUFFER, msaalight ? msfbo : gfbo);
if(ghasstencil)
{
glStencilFunc(GL_ALWAYS, layer+1, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
}
else
{
bool scissor = sx1 > -1 || sy1 > -1 || sx2 < 1 || sy2 < 1;
if(scissor)
{
int x1 = int(floor((sx1*0.5f+0.5f)*vieww)), y1 = int(floor((sy1*0.5f+0.5f)*viewh)),
x2 = int(ceil((sx2*0.5f+0.5f)*vieww)), y2 = int(ceil((sy2*0.5f+0.5f)*viewh));
glEnable(GL_SCISSOR_TEST);
glScissor(x1, y1, x2 - x1, y2 - y1);
}
maskgbuffer("n");
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
if(scissor) glDisable(GL_SCISSOR_TEST);
}
maskgbuffer("cndg");
if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
switch(layer)
{
case 0:
renderliquidmaterials();
break;
case 1:
renderalphageom(1);
break;
case 2:
if(hasalphavas&2) renderalphageom(2);
if(hasmats&2) rendersolidmaterials();
renderstains(STAINBUF_TRANSPARENT, true, layer+1);
break;
case 3:
rendertransparentmodelbatches(layer+1);
break;
}
if(wireframe && editmode) glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
if(msaalight)
{
glBindFramebuffer_(GL_FRAMEBUFFER, mshdrfbo);
if((ghasstencil && msaaedgedetect) || msaalight==2) loopi(2) renderlights(sx1, sy1, sx2, sy2, tiles, layer+1, i+1, true);
else renderlights(sx1, sy1, sx2, sy2, tiles, layer+1, 3, true);
}
else
{
glBindFramebuffer_(GL_FRAMEBUFFER, hdrfbo);
renderlights(sx1, sy1, sx2, sy2, tiles, layer+1, 0, true);
}
switch(layer)
{
case 2:
renderstains(STAINBUF_TRANSPARENT, false, layer+1);
break;
}
}
transparentlayer = 0;
if(ghasstencil) glDisable(GL_STENCIL_TEST);
endtimer(transtimer);
if(editmode) return;
if(particlelayers && ghasstencil)
{
bool scissor = allsx1 > -1 || allsy1 > -1 || allsx2 < 1 || allsy2 < 1;
if(scissor)
{
int x1 = int(floor((allsx1*0.5f+0.5f)*vieww)), y1 = int(floor((allsy1*0.5f+0.5f)*viewh)),
x2 = int(ceil((allsx2*0.5f+0.5f)*vieww)), y2 = int(ceil((allsy2*0.5f+0.5f)*viewh));
glEnable(GL_SCISSOR_TEST);
glScissor(x1, y1, x2 - x1, y2 - y1);
}
glStencilFunc(GL_NOTEQUAL, 0, 0x07);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
glEnable(GL_STENCIL_TEST);
renderparticles(PL_OVER);
glDisable(GL_STENCIL_TEST);
if(scissor) glDisable(GL_SCISSOR_TEST);
renderparticles(PL_NOLAYER);
}
else renderparticles();
}
VAR(gdepthclear, 0, 1, 1);
VAR(gcolorclear, 0, 1, 1);
void preparegbuffer(bool depthclear)
{
glBindFramebuffer_(GL_FRAMEBUFFER, msaasamples && (msaalight || !drawtex) ? msfbo : gfbo);
glViewport(0, 0, vieww, viewh);
if(drawtex && gdepthinit)
{
glEnable(GL_SCISSOR_TEST);
glScissor(0, 0, vieww, viewh);
}
if(gdepthformat && gdepthclear)
{
maskgbuffer("d");
if(gdepthformat == 1) glClearColor(1, 1, 1, 1);
else glClearColor(-farplane, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
maskgbuffer("cn");
}
else maskgbuffer("cnd");
if(gcolorclear) glClearColor(0, 0, 0, 0);
glClear((depthclear ? GL_DEPTH_BUFFER_BIT : 0)|(gcolorclear ? GL_COLOR_BUFFER_BIT : 0)|(depthclear && ghasstencil && (!msaasamples || msaalight || ghasstencil > 1) ? GL_STENCIL_BUFFER_BIT : 0));
if(gdepthformat && gdepthclear) maskgbuffer("cnd");
if(drawtex && gdepthinit) glDisable(GL_SCISSOR_TEST);
gdepthinit = true;
matrix4 invscreenmatrix;
invscreenmatrix.identity();
invscreenmatrix.settranslation(-1.0f, -1.0f, -1.0f);
invscreenmatrix.setscale(2.0f/vieww, 2.0f/viewh, 2.0f);
eyematrix.muld(invprojmatrix, invscreenmatrix);
if(drawtex == DRAWTEX_MINIMAP)
{
linearworldmatrix.muld(invcamprojmatrix, invscreenmatrix);
if(!gdepthformat) worldmatrix = linearworldmatrix;
linearworldmatrix.a.z = invcammatrix.a.z;
linearworldmatrix.b.z = invcammatrix.b.z;
linearworldmatrix.c.z = invcammatrix.c.z;
linearworldmatrix.d.z = invcammatrix.d.z;
if(gdepthformat) worldmatrix = linearworldmatrix;
GLOBALPARAMF(radialfogscale, 0, 0, 0, 0);
}
else
{
float xscale = eyematrix.a.x, yscale = eyematrix.b.y, xoffset = eyematrix.d.x, yoffset = eyematrix.d.y, zscale = eyematrix.d.z;
matrix4 depthmatrix(vec(xscale/zscale, 0, xoffset/zscale), vec(0, yscale/zscale, yoffset/zscale));
linearworldmatrix.muld(invcammatrix, depthmatrix);
if(gdepthformat) worldmatrix = linearworldmatrix;
else worldmatrix.muld(invcamprojmatrix, invscreenmatrix);
GLOBALPARAMF(radialfogscale, xscale/zscale, yscale/zscale, xoffset/zscale, yoffset/zscale);
}
screenmatrix.identity();
screenmatrix.settranslation(0.5f*vieww, 0.5f*viewh, 0.5f);
screenmatrix.setscale(0.5f*vieww, 0.5f*viewh, 0.5f);
screenmatrix.muld(camprojmatrix);
GLOBALPARAMF(viewsize, vieww, viewh, 1.0f/vieww, 1.0f/viewh);
GLOBALPARAMF(gdepthscale, eyematrix.d.z, eyematrix.c.w, eyematrix.d.w);
GLOBALPARAMF(gdepthinvscale, eyematrix.d.z / eyematrix.c.w, eyematrix.d.w / eyematrix.c.w);
GLOBALPARAMF(gdepthpackparams, -1.0f/farplane, -255.0f/farplane, -(255.0f*255.0f)/farplane);
GLOBALPARAMF(gdepthunpackparams, -farplane, -farplane/255.0f, -farplane/(255.0f*255.0f));
GLOBALPARAM(worldmatrix, worldmatrix);
GLOBALPARAMF(ldrscale, ldrscale);
GLOBALPARAMF(hdrgamma, hdrgamma, 1.0f/hdrgamma);
GLOBALPARAM(camera, camera1->o);
GLOBALPARAMF(millis, lastmillis/1000.0f);
GLERROR;
if(depthclear) resetlights();
resetmodelbatches();
}
void rendergbuffer(bool depthclear)
{
timer *gcputimer = drawtex ? nullptr : begintimer("g-buffer", false);
timer *gtimer = drawtex ? nullptr : begintimer("g-buffer");
preparegbuffer(depthclear);
if(limitsky())
{
renderexplicitsky();
GLERROR;
}
rendergeom();
GLERROR;
renderdecals();
GLERROR;
rendermapmodels();
GLERROR;
if(drawtex == DRAWTEX_MINIMAP)
{
if(depthclear) findmaterials();
renderminimapmaterials();
GLERROR;
}
else if(!drawtex)
{
rendermodelbatches();
GLERROR;
renderstains(STAINBUF_OPAQUE, true);
renderstains(STAINBUF_MAPMODEL, true);
GLERROR;
//renderavatar();
//GLERROR;
}
endtimer(gtimer);
endtimer(gcputimer);
}
void shademinimap(const vec &color)
{
GLERROR;
glBindFramebuffer_(GL_FRAMEBUFFER, msaalight ? mshdrfbo : hdrfbo);
glViewport(0, 0, vieww, viewh);
if(color.x >= 0)
{
glClearColor(color.x, color.y, color.z, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
renderlights(-1, -1, 1, 1, nullptr, 0, msaalight ? -1 : 0);
GLERROR;
}
void shademodelpreview(int x, int y, int w, int h, bool background, bool scissor)
{
GLERROR;
glBindFramebuffer_(GL_FRAMEBUFFER, 0);
glViewport(0, 0, hudw, hudh);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, mscolortex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gcolortex);
glActiveTexture_(GL_TEXTURE1);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msnormaltex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gnormaltex);
glActiveTexture_(GL_TEXTURE3);
if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
glActiveTexture_(GL_TEXTURE0);
float lightscale = 2.0f*ldrscale;
GLOBALPARAMF(lightscale, 0.1f*lightscale, 0.1f*lightscale, 0.1f*lightscale, lightscale);
GLOBALPARAM(sunlightdir, vec(0, -1, 2).normalize());
GLOBALPARAMF(sunlightcolor, 0.6f*lightscale, 0.6f*lightscale, 0.6f*lightscale);
SETSHADER(modelpreview);
LOCALPARAMF(cutout, background ? -1 : 0);
if(scissor) glEnable(GL_SCISSOR_TEST);
int sx = std::clamp(x, 0, hudw), sy = std::clamp(y, 0, hudh),
sw = std::clamp(x + w, 0, hudw) - sx, sh = std::clamp(y + h, 0, hudh) - sy;
float sxk = 2.0f/hudw, syk = 2.0f/hudh, txk = vieww/float(w), tyk = viewh/float(h);
hudquad(sx*sxk - 1, sy*syk - 1, sw*sxk, sh*syk, (sx-x)*txk, (sy-y)*tyk, sw*txk, sh*tyk);
if(scissor) glDisable(GL_SCISSOR_TEST);
GLERROR;
}
void shadesky()
{
glBindFramebuffer_(GL_FRAMEBUFFER, msaalight ? mshdrfbo : hdrfbo);
glViewport(0, 0, vieww, viewh);
drawskybox((hdrclear > 0 ? hdrclear-- : msaalight) > 0);
}
void shadegbuffer()
{
if(msaasamples && !msaalight && !drawtex) resolvemsaadepth();
GLERROR;
timer *shcputimer = begintimer("deferred shading", false);
timer *shtimer = begintimer("deferred shading");
shadesky();
if(msaasamples && (msaalight || !drawtex))
{
if((ghasstencil && msaaedgedetect) || msaalight==2) loopi(2) renderlights(-1, -1, 1, 1, nullptr, 0, i+1);
else renderlights(-1, -1, 1, 1, nullptr, 0, drawtex ? -1 : 3);
}
else renderlights();
GLERROR;
if(!drawtex)
{
renderstains(STAINBUF_OPAQUE, false);
renderstains(STAINBUF_MAPMODEL, false);
}
endtimer(shtimer);
endtimer(shcputimer);
}
void setuplights()
{
GLERROR;
setupgbuffer();
if(bloomw < 0 || bloomh < 0) setupbloom(gw, gh);
if(ao && (aow < 0 || aoh < 0)) setupao(gw, gh);
if(volumetriclights && volumetric && (volw < 0 || volh < 0)) setupvolumetric(gw, gh);
if(!shadowatlasfbo) setupshadowatlas();
if(useradiancehints() && !rhfbo) setupradiancehints();
if(!deferredlightshader) loaddeferredlightshaders();
if(drawtex == DRAWTEX_MINIMAP && !deferredminimapshader) deferredminimapshader = loaddeferredlightshader(msaalight ? "mM" : "m");
setupaa(gw, gh);
GLERROR;
}
bool debuglights()
{
if(debugshadowatlas) viewshadowatlas();
else if(debugao) viewao();
else if(debugbloom) viewbloom();
else if(debugdepth) viewdepth();
else if(debugstencil) viewstencil();
else if(debugrefract) viewrefract();
else if(debuglightscissor) viewlightscissor();
else if(debugrsm) viewrsm();
else if(debugrh) viewrh();
else if(!debugaa()) return false;
return true;
}
void cleanuplights()
{
cleanupgbuffer();
cleanupbloom();
cleanupao();
cleanupvolumetric();
cleanupshadowatlas();
cleanupradiancehints();
lightsphere::cleanup();
cleanupaa();
}