// shader.cpp: OpenGL GLSL shader management #include "engine.h" Shader *Shader::lastshader = NULL; Shader *nullshader = NULL, *hudshader = NULL, *hudtextshader = NULL, *hudnotextureshader = NULL, *nocolorshader = NULL, *foggedshader = NULL, *foggednotextureshader = NULL, *ldrshader = NULL, *ldrnotextureshader = NULL, *stdworldshader = NULL; static hashnameset globalparams(256); static hashtable localparams(256); static hashnameset shaders(256); static Shader *slotshader = NULL; static vector slotparams; static bool standardshaders = false, forceshaders = true, loadedshaders = false; VAR(maxvsuniforms, 1, 0, 0); VAR(maxfsuniforms, 1, 0, 0); VAR(mintexoffset, 1, 0, 0); VAR(maxtexoffset, 1, 0, 0); VAR(mintexrectoffset, 1, 0, 0); VAR(maxtexrectoffset, 1, 0, 0); //VAR(dbgshader, 0, 0, 2); VAR(dbgshader, 0, 1, 2); void loadshaders() { standardshaders = true; execfile("config/glsl.cfg"); standardshaders = false; nullshader = lookupshaderbyname("null"); hudshader = lookupshaderbyname("hud"); hudtextshader = lookupshaderbyname("hudtext"); hudnotextureshader = lookupshaderbyname("hudnotexture"); stdworldshader = lookupshaderbyname("stdworld"); if(!nullshader || !hudshader || !hudtextshader || !hudnotextureshader || !stdworldshader) fatal("cannot find shader definitions"); dummyslot.shader = stdworldshader; dummydecalslot.shader = nullshader; nocolorshader = lookupshaderbyname("nocolor"); foggedshader = lookupshaderbyname("fogged"); foggednotextureshader = lookupshaderbyname("foggednotexture"); ldrshader = lookupshaderbyname("ldr"); ldrnotextureshader = lookupshaderbyname("ldrnotexture"); nullshader->set(); loadedshaders = true; } Shader *lookupshaderbyname(const char *name) { Shader *s = shaders.access(name); return s && s->loaded() ? s : NULL; } Shader *generateshader(const char *name, const char *fmt, ...) { if(!loadedshaders) return NULL; Shader *s = name ? lookupshaderbyname(name) : NULL; if(!s) { defvformatstring(cmd, fmt, fmt); bool wasstandard = standardshaders; standardshaders = true; execute(cmd); standardshaders = wasstandard; s = name ? lookupshaderbyname(name) : NULL; if(!s) s = nullshader; } return s; } static void showglslinfo(GLenum type, GLuint obj, const char *name, const char **parts = NULL, int numparts = 0) { GLint length = 0; if(type) glGetShaderiv_(obj, GL_INFO_LOG_LENGTH, &length); else glGetProgramiv_(obj, GL_INFO_LOG_LENGTH, &length); if(length > 1) { conoutf(CON_ERROR, "GLSL ERROR (%s:%s)", type == GL_VERTEX_SHADER ? "VS" : (type == GL_FRAGMENT_SHADER ? "FS" : "PROG"), name); FILE *l = getlogfile(); if(l) { GLchar *log = new GLchar[length]; if(type) glGetShaderInfoLog_(obj, length, &length, log); else glGetProgramInfoLog_(obj, length, &length, log); fprintf(l, "%s\n", log); bool partlines = log[0] != '0'; int line = 0; loopi(numparts) { const char *part = parts[i]; int startline = line; while(*part) { const char *next = strchr(part, '\n'); if(++line > 1000) goto done; if(partlines) fprintf(l, "%d(%d): ", i, line - startline); else fprintf(l, "%d: ", line); fwrite(part, 1, next ? next - part + 1 : strlen(part), l); if(!next) { fputc('\n', l); break; } part = next + 1; } } done: delete[] log; } } } static void compileglslshader(Shader &s, GLenum type, GLuint &obj, const char *def, const char *name, bool msg = true) { const char *source = def + strspn(def, " \t\r\n"); const char *parts[] = { "#version 400\n", "#define textureRect(sampler, coords) texture(sampler, coords)\n" "#define textureRectProj(sampler, coords) textureProj(sampler, coords)\n" "#define textureRectOffset(sampler, coords, offset) textureOffset(sampler, coords, offset)\n", source }; GLsizei numparts = sizeof(parts) / sizeof(void *); obj = glCreateShader_(type); glShaderSource_(obj, numparts, (const GLchar **)parts, NULL); glCompileShader_(obj); GLint success; glGetShaderiv_(obj, GL_COMPILE_STATUS, &success); if(!success) { if(msg) showglslinfo(type, obj, name, parts, numparts); glDeleteShader_(obj); obj = 0; } else if(dbgshader > 1 && msg) showglslinfo(type, obj, name, parts, numparts); } VAR(dbgubo, 0, 0, 1); static void bindglsluniform(Shader &s, UniformLoc &u) { u.loc = glGetUniformLocation_(s.program, u.name); if(!u.blockname) return; GLuint bidx = glGetUniformBlockIndex_(s.program, u.blockname); GLuint uidx = GL_INVALID_INDEX; glGetUniformIndices_(s.program, 1, &u.name, &uidx); if(bidx != GL_INVALID_INDEX && uidx != GL_INVALID_INDEX) { GLint sizeval = 0, offsetval = 0, strideval = 0; glGetActiveUniformBlockiv_(s.program, bidx, GL_UNIFORM_BLOCK_DATA_SIZE, &sizeval); if(sizeval <= 0) return; glGetActiveUniformsiv_(s.program, 1, &uidx, GL_UNIFORM_OFFSET, &offsetval); if(u.stride > 0) { glGetActiveUniformsiv_(s.program, 1, &uidx, GL_UNIFORM_ARRAY_STRIDE, &strideval); if(strideval > u.stride) return; } u.offset = offsetval; u.size = sizeval; glUniformBlockBinding_(s.program, bidx, u.binding); if(dbgubo) conoutf(CON_DEBUG, "UBO: %s:%s:%d, offset: %d, size: %d, stride: %d", u.name, u.blockname, u.binding, offsetval, sizeval, strideval); } } static void bindworldtexlocs(Shader &s) { #define UNIFORMTEX(name, tmu) \ do { \ int loc = glGetUniformLocation_(s.program, name); \ if(loc != -1) { glUniform1i_(loc, tmu); } \ } while(0) UNIFORMTEX("diffusemap", TEX_DIFFUSE); UNIFORMTEX("normalmap", TEX_NORMAL); UNIFORMTEX("glowmap", TEX_GLOW); UNIFORMTEX("envmap", TEX_ENVMAP); UNIFORMTEX("detaildiffusemap", TEX_DETAIL+TEX_DIFFUSE); UNIFORMTEX("detailnormalmap", TEX_DETAIL+TEX_NORMAL); UNIFORMTEX("blendmap", 7); UNIFORMTEX("refractmask", 7); UNIFORMTEX("refractlight", 8); } static void linkglslprogram(Shader &s, bool msg = true) { s.program = s.vsobj && s.psobj ? glCreateProgram_() : 0; GLint success = 0; if(s.program) { glAttachShader_(s.program, s.vsobj); glAttachShader_(s.program, s.psobj); uint attribs = 0; loopv(s.attriblocs) { AttribLoc &a = s.attriblocs[i]; glBindAttribLocation_(s.program, a.loc, a.name); attribs |= 1<= s.localparamremap.length()) { int n = idx + 1 - s.localparamremap.length(); memset(s.localparamremap.pad(n), 0xFF, n); } s.localparamremap[idx] = s.localparams.length(); LocalShaderParamState &l = s.localparams.add(); l.name = name; l.loc = loc; l.size = size; l.format = format; return idx; } GlobalShaderParamState *getglobalparam(const char *name) { GlobalShaderParamState *param = globalparams.access(name); if(!param) { param = &globalparams[name]; param->name = name; memset(param->buf, -1, sizeof(param->buf)); param->version = -1; } return param; } static GlobalShaderParamUse *addglobalparam(Shader &s, GlobalShaderParamState *param, int loc, int size, GLenum format) { GlobalShaderParamUse &g = s.globalparams.add(); g.param = param; g.version = -2; g.loc = loc; g.size = size; g.format = format; return &g; } static void setglsluniformformat(Shader &s, const char *name, GLenum format, int size) { switch(format) { case GL_FLOAT: case GL_FLOAT_VEC2: case GL_FLOAT_VEC3: case GL_FLOAT_VEC4: case GL_INT: case GL_INT_VEC2: case GL_INT_VEC3: case GL_INT_VEC4: case GL_UNSIGNED_INT: case GL_UNSIGNED_INT_VEC2: case GL_UNSIGNED_INT_VEC3: case GL_UNSIGNED_INT_VEC4: case GL_BOOL: case GL_BOOL_VEC2: case GL_BOOL_VEC3: case GL_BOOL_VEC4: case GL_FLOAT_MAT2: case GL_FLOAT_MAT3: case GL_FLOAT_MAT4: break; default: return; } if(!strncmp(name, "gl_", 3)) return; int loc = glGetUniformLocation_(s.program, name); if(loc < 0) return; loopvj(s.defaultparams) if(s.defaultparams[j].loc == loc) { s.defaultparams[j].format = format; return; } loopvj(s.uniformlocs) if(s.uniformlocs[j].loc == loc) return; loopvj(s.globalparams) if(s.globalparams[j].loc == loc) return; loopvj(s.localparams) if(s.localparams[j].loc == loc) return; name = getshaderparamname(name); GlobalShaderParamState *param = globalparams.access(name); if(param) addglobalparam(s, param, loc, size, format); else addlocalparam(s, name, loc, size, format); } static void allocglslactiveuniforms(Shader &s) { GLint numactive = 0; glGetProgramiv_(s.program, GL_ACTIVE_UNIFORMS, &numactive); string name; loopi(numactive) { GLsizei namelen = 0; GLint size = 0; GLenum format = GL_FLOAT_VEC4; name[0] = '\0'; glGetActiveUniform_(s.program, i, sizeof(name)-1, &namelen, &size, &format, name); if(namelen <= 0 || size <= 0) continue; name[clamp(int(namelen), 0, (int)sizeof(name)-2)] = '\0'; char *brak = strchr(name, '['); if(brak) *brak = '\0'; setglsluniformformat(s, name, format, size); } } void Shader::allocparams(Slot *slot) { allocglslactiveuniforms(*this); } int GlobalShaderParamState::nextversion = 0; void GlobalShaderParamState::resetversions() { enumerate(shaders, Shader, s, { loopv(s.globalparams) { GlobalShaderParamUse &u = s.globalparams[i]; if(u.version != u.param->version) u.version = -2; } }); nextversion = 0; enumerate(globalparams, GlobalShaderParamState, g, { g.version = ++nextversion; }); enumerate(shaders, Shader, s, { loopv(s.globalparams) { GlobalShaderParamUse &u = s.globalparams[i]; if(u.version >= 0) u.version = u.param->version; } }); } static float *findslotparam(Slot &s, const char *name, float *noval = NULL) { loopv(s.params) { SlotShaderParam ¶m = s.params[i]; if(name == param.name) return param.val; } loopv(s.shader->defaultparams) { SlotShaderParamState ¶m = s.shader->defaultparams[i]; if(name == param.name) return param.val; } return noval; } static float *findslotparam(VSlot &s, const char *name, float *noval = NULL) { loopv(s.params) { SlotShaderParam ¶m = s.params[i]; if(name == param.name) return param.val; } return findslotparam(*s.slot, name, noval); } static inline void setslotparam(SlotShaderParamState &l, const float *val) { switch(l.format) { case GL_BOOL: case GL_FLOAT: glUniform1fv_(l.loc, 1, val); break; case GL_BOOL_VEC2: case GL_FLOAT_VEC2: glUniform2fv_(l.loc, 1, val); break; case GL_BOOL_VEC3: case GL_FLOAT_VEC3: glUniform3fv_(l.loc, 1, val); break; case GL_BOOL_VEC4: case GL_FLOAT_VEC4: glUniform4fv_(l.loc, 1, val); break; case GL_INT: glUniform1i_(l.loc, int(val[0])); break; case GL_INT_VEC2: glUniform2i_(l.loc, int(val[0]), int(val[1])); break; case GL_INT_VEC3: glUniform3i_(l.loc, int(val[0]), int(val[1]), int(val[2])); break; case GL_INT_VEC4: glUniform4i_(l.loc, int(val[0]), int(val[1]), int(val[2]), int(val[3])); break; case GL_UNSIGNED_INT: glUniform1ui_(l.loc, uint(val[0])); break; case GL_UNSIGNED_INT_VEC2: glUniform2ui_(l.loc, uint(val[0]), uint(val[1])); break; case GL_UNSIGNED_INT_VEC3: glUniform3ui_(l.loc, uint(val[0]), uint(val[1]), uint(val[2])); break; case GL_UNSIGNED_INT_VEC4: glUniform4ui_(l.loc, uint(val[0]), uint(val[1]), uint(val[2]), uint(val[3])); break; } } #define SETSLOTPARAM(l, mask, i, val) do { \ if(!(mask&(1<invalid() ? 0 : reusevs->vsobj; else compileglslshader(*this, GL_VERTEX_SHADER, vsobj, vsstr, name, dbgshader || !variantshader); if(!psstr) psobj = !reuseps || reuseps->invalid() ? 0 : reuseps->psobj; else compileglslshader(*this, GL_FRAGMENT_SHADER, psobj, psstr, name, dbgshader || !variantshader); linkglslprogram(*this, !variantshader); return program!=0; } void Shader::cleanup(bool full) { used = false; if(vsobj) { if(!reusevs) glDeleteShader_(vsobj); vsobj = 0; } if(psobj) { if(!reuseps) glDeleteShader_(psobj); psobj = 0; } if(program) { glDeleteProgram_(program); program = 0; } localparams.setsize(0); localparamremap.setsize(0); globalparams.setsize(0); if(standard || full) { type = SHADER_INVALID; DELETEA(vsstr); DELETEA(psstr); DELETEA(defer); variants.setsize(0); DELETEA(variantrows); defaultparams.setsize(0); attriblocs.setsize(0); fragdatalocs.setsize(0); uniformlocs.setsize(0); reusevs = reuseps = NULL; } else loopv(defaultparams) defaultparams[i].loc = -1; } static void genattriblocs(Shader &s, const char *vs, const char *ps, Shader *reusevs, Shader *reuseps) { static int len = strlen("//:attrib"); string name; int loc; if(reusevs) s.attriblocs = reusevs->attriblocs; else while((vs = strstr(vs, "//:attrib"))) { if(sscanf(vs, "//:attrib %100s %d", name, &loc) == 2) s.attriblocs.add(AttribLoc(getshaderparamname(name), loc)); vs += len; } } static void genuniformlocs(Shader &s, const char *vs, const char *ps, Shader *reusevs, Shader *reuseps) { static int len = strlen("//:uniform"); string name, blockname; int binding, stride; if(reusevs) s.uniformlocs = reusevs->uniformlocs; else while((vs = strstr(vs, "//:uniform"))) { int numargs = sscanf(vs, "//:uniform %100s %100s %d %d", name, blockname, &binding, &stride); if(numargs >= 3) s.uniformlocs.add(UniformLoc(getshaderparamname(name), getshaderparamname(blockname), binding, numargs >= 4 ? stride : 0)); else if(numargs >= 1) s.uniformlocs.add(UniformLoc(getshaderparamname(name))); vs += len; } } Shader *newshader(int type, const char *name, const char *vs, const char *ps, Shader *variant = NULL, int row = 0) { if(Shader::lastshader) { glUseProgram_(0); Shader::lastshader = NULL; } Shader *exists = shaders.access(name); char *rname = exists ? exists->name : newstring(name); Shader &s = shaders[rname]; s.name = rname; s.vsstr = newstring(vs); s.psstr = newstring(ps); DELETEA(s.defer); s.type = type & ~(SHADER_INVALID | SHADER_DEFERRED); s.variantshader = variant; s.standard = standardshaders; if(forceshaders) s.forced = true; s.reusevs = s.reuseps = NULL; if(variant) { int row = 0, col = 0; if(!vs[0] || sscanf(vs, "%d , %d", &row, &col) >= 1) { DELETEA(s.vsstr); s.reusevs = !vs[0] ? variant : variant->getvariant(col, row); } row = col = 0; if(!ps[0] || sscanf(ps, "%d , %d", &row, &col) >= 1) { DELETEA(s.psstr); s.reuseps = !ps[0] ? variant : variant->getvariant(col, row); } } if(variant) loopv(variant->defaultparams) s.defaultparams.add(variant->defaultparams[i]); else loopv(slotparams) s.defaultparams.add(slotparams[i]); s.attriblocs.setsize(0); s.uniformlocs.setsize(0); genattriblocs(s, vs, ps, s.reusevs, s.reuseps); genuniformlocs(s, vs, ps, s.reusevs, s.reuseps); s.fragdatalocs.setsize(0); if(s.reuseps) s.fragdatalocs = s.reuseps->fragdatalocs; if(!s.compile()) { s.cleanup(true); if(variant) shaders.remove(rname); return NULL; } if(variant) variant->addvariant(row, &s); return &s; } static const char *findglslmain(const char *s) { const char *main = strstr(s, "main"); if(!main) return NULL; for(; main >= s; main--) switch(*main) { case '\r': case '\n': case ';': return main + 1; } return s; } static void gengenericvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 0) { int rowoffset = 0; bool vschanged = false, pschanged = false; vector vsv, psv; vsv.put(vs, strlen(vs)+1); psv.put(ps, strlen(ps)+1); static const int len = strlen("//:variant"), olen = strlen("override"); for(char *vspragma = vsv.getbuf();; vschanged = true) { vspragma = strstr(vspragma, "//:variant"); if(!vspragma) break; if(sscanf(vspragma + len, "row %d", &rowoffset) == 1) continue; memset(vspragma, ' ', len); vspragma += len; if(!strncmp(vspragma, "override", olen)) { memset(vspragma, ' ', olen); vspragma += olen; char *end = vspragma + strcspn(vspragma, "\n\r"); end += strspn(end, "\n\r"); int endlen = strcspn(end, "\n\r"); memset(end, ' ', endlen); } } for(char *pspragma = psv.getbuf();; pschanged = true) { pspragma = strstr(pspragma, "//:variant"); if(!pspragma) break; if(sscanf(pspragma + len, "row %d", &rowoffset) == 1) continue; memset(pspragma, ' ', len); pspragma += len; if(!strncmp(pspragma, "override", olen)) { memset(pspragma, ' ', olen); pspragma += olen; char *end = pspragma + strcspn(pspragma, "\n\r"); end += strspn(end, "\n\r"); int endlen = strcspn(end, "\n\r"); memset(end, ' ', endlen); } } row += rowoffset; if(row < 0 || row >= MAXVARIANTROWS) return; int col = s.numvariants(row); defformatstring(varname, "%s", col, row, sname); string reuse; if(col) formatstring(reuse, "%d", row); else copystring(reuse, ""); newshader(s.type, varname, vschanged ? vsv.getbuf() : reuse, pschanged ? psv.getbuf() : reuse, &s, row); } static void genfogshader(vector &vsbuf, vector &psbuf, const char *vs, const char *ps) { const char *vspragma = strstr(vs, "//:fog"), *pspragma = strstr(ps, "//:fog"); if(!vspragma && !pspragma) return; static const int pragmalen = strlen("//:fog"); const char *vsmain = findglslmain(vs), *vsend = strrchr(vs, '}'); if(vsmain && vsend) { if(!strstr(vs, "lineardepth")) { vsbuf.put(vs, vsmain - vs); const char *fogparams = "\nuniform vec2 lineardepthscale;\nout float lineardepth;\n"; vsbuf.put(fogparams, strlen(fogparams)); vsbuf.put(vsmain, vsend - vsmain); const char *vsfog = "\nlineardepth = dot(lineardepthscale, gl_Position.zw);\n"; vsbuf.put(vsfog, strlen(vsfog)); vsbuf.put(vsend, strlen(vsend)+1); } } const char *psmain = findglslmain(ps), *psend = strrchr(ps, '}'); if(psmain && psend) { psbuf.put(ps, psmain - ps); if(!strstr(ps, "lineardepth")) { const char *foginterp = "\nin float lineardepth;\n"; psbuf.put(foginterp, strlen(foginterp)); } const char *fogparams = "\nuniform vec3 fogcolor;\n" "uniform vec2 fogdensity;\n" "uniform vec4 radialfogscale;\n" "#define fogcoord lineardepth*length(vec3(gl_FragCoord.xy*radialfogscale.xy + radialfogscale.zw, 1.0))\n"; psbuf.put(fogparams, strlen(fogparams)); psbuf.put(psmain, psend - psmain); const char *psdef = "\n#define FOG_COLOR "; const char *psfog = pspragma && !strncmp(pspragma+pragmalen, "rgba", 4) ? "\nfragcolor = mix((FOG_COLOR), fragcolor, clamp(exp2(fogcoord*-fogdensity.x)*fogdensity.y, 0.0, 1.0));\n" : "\nfragcolor.rgb = mix((FOG_COLOR).rgb, fragcolor.rgb, clamp(exp2(fogcoord*-fogdensity.x)*fogdensity.y, 0.0, 1.0));\n"; int clen = 0; if(pspragma) { pspragma += pragmalen; while(iscubealpha(*pspragma)) pspragma++; while(*pspragma && !iscubespace(*pspragma)) pspragma++; pspragma += strspn(pspragma, " \t\v\f"); clen = strcspn(pspragma, "\r\n"); } if(clen <= 0) { pspragma = "fogcolor"; clen = strlen(pspragma); } psbuf.put(psdef, strlen(psdef)); psbuf.put(pspragma, clen); psbuf.put(psfog, strlen(psfog)); psbuf.put(psend, strlen(psend)+1); } } static void genuniformdefs(vector &vsbuf, vector &psbuf, const char *vs, const char *ps, Shader *variant = NULL) { if(variant ? variant->defaultparams.empty() : slotparams.empty()) return; const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); if(!vsmain || !psmain) return; vsbuf.put(vs, vsmain - vs); psbuf.put(ps, psmain - ps); if(variant) loopv(variant->defaultparams) { defformatstring(uni, "\nuniform vec4 %s;\n", variant->defaultparams[i].name); vsbuf.put(uni, strlen(uni)); psbuf.put(uni, strlen(uni)); } else loopv(slotparams) { defformatstring(uni, "\nuniform vec4 %s;\n", slotparams[i].name); vsbuf.put(uni, strlen(uni)); psbuf.put(uni, strlen(uni)); } vsbuf.put(vsmain, strlen(vsmain)+1); psbuf.put(psmain, strlen(psmain)+1); } void setupshaders() { GLint val; glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &val); maxvsuniforms = val/4; glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &val); maxfsuniforms = val/4; glGetIntegerv(GL_MIN_PROGRAM_TEXEL_OFFSET, &val); mintexoffset = val; glGetIntegerv(GL_MAX_PROGRAM_TEXEL_OFFSET, &val); maxtexoffset = val; mintexrectoffset = mintexoffset; maxtexrectoffset = maxtexoffset; standardshaders = true; nullshader = newshader(0, "null", "in vec4 vvertex;\n" "void main(void) {\n" " gl_Position = vvertex;\n" "}\n", "layout(location = 0) out vec4 fragcolor;\n" "void main(void) {\n" " fragcolor = vec4(1.0, 0.0, 1.0, 1.0);\n" "}\n"); hudshader = newshader(0, "hud", "in vec4 vvertex, vcolor;\n" "in vec2 vtexcoord0;\n" "uniform mat4 hudmatrix;\n" "out vec2 texcoord0;\n" "out vec4 colorscale;\n" "void main(void) {\n" " gl_Position = hudmatrix * vvertex;\n" " texcoord0 = vtexcoord0;\n" " colorscale = vcolor;\n" "}\n", "uniform sampler2D tex0;\n" "in vec2 texcoord0;\n" "in vec4 colorscale;\n" "layout(location = 0) out vec4 fragcolor;\n" "void main(void) {\n" " vec4 color = texture(tex0, texcoord0);\n" " fragcolor = colorscale * color;\n" "}\n"); hudtextshader = newshader(0, "hudtext", "in vec4 vvertex, vcolor;\n" "in vec2 vtexcoord0;\n" "uniform mat4 hudmatrix;\n" "out vec2 texcoord0;\n" "out vec4 colorscale;\n" "void main(void) {\n" " gl_Position = hudmatrix * vvertex;\n" " texcoord0 = vtexcoord0;\n" " colorscale = vcolor;\n" "}\n", "uniform sampler2D tex0;\n" "uniform vec4 textparams;\n" "in vec2 texcoord0;\n" "in vec4 colorscale;\n" "layout(location = 0) out vec4 fragcolor;\n" "void main(void) {\n" " float dist = texture(tex0, texcoord0).r;\n" " float border = smoothstep(textparams.x, textparams.y, dist);\n" " float outline = smoothstep(textparams.z, textparams.w, dist);\n" " fragcolor = vec4(colorscale.rgb * outline, colorscale.a * border);\n" "}\n"); hudnotextureshader = newshader(0, "hudnotexture", "in vec4 vvertex, vcolor;\n" "uniform mat4 hudmatrix;" "out vec4 color;\n" "void main(void) {\n" " gl_Position = hudmatrix * vvertex;\n" " color = vcolor;\n" "}\n", "in vec4 color;\n" "layout(location = 0) out vec4 fragcolor;\n" "void main(void) {\n" " fragcolor = color;\n" "}\n"); standardshaders = false; if(!nullshader || !hudshader || !hudtextshader || !hudnotextureshader) fatal("failed to setup shaders"); dummyslot.shader = nullshader; } VAR(defershaders, 0, 1, 1); void defershader(int *type, const char *name, const char *contents) { Shader *exists = shaders.access(name); if(exists && !exists->invalid()) return; if(!defershaders) { execute(contents); return; } char *rname = exists ? exists->name : newstring(name); Shader &s = shaders[rname]; s.name = rname; DELETEA(s.defer); s.defer = newstring(contents); s.type = SHADER_DEFERRED | (*type & ~SHADER_INVALID); s.standard = standardshaders; } COMMAND(defershader, "iss"); void Shader::force() { if(!deferred() || !defer) return; char *cmd = defer; defer = NULL; bool wasstandard = standardshaders, wasforcing = forceshaders; int oldflags = identflags; standardshaders = standard; forceshaders = false; identflags &= ~IDF_PERSIST; slotparams.shrink(0); execute(cmd); identflags = oldflags; forceshaders = wasforcing; standardshaders = wasstandard; delete[] cmd; if(deferred()) { DELETEA(defer); type = SHADER_INVALID; } } int Shader::uniformlocversion() { static int version = 0; if(++version >= 0) return version; version = 0; enumerate(shaders, Shader, s, { loopvj(s.uniformlocs) s.uniformlocs[j].version = -1; }); return version; } Shader *useshaderbyname(const char *name) { Shader *s = shaders.access(name); if(!s) return NULL; if(s->deferred()) s->force(); s->forced = true; return s; } ICOMMAND(forceshader, "s", (const char *name), useshaderbyname(name)); void shader(int *type, char *name, char *vs, char *ps) { if(lookupshaderbyname(name)) return; defformatstring(info, "shader %s", name); renderprogress(loadprogress, info); vector vsbuf, psbuf, vsbak, psbak; #define GENSHADER(cond, body) \ if(cond) \ { \ if(vsbuf.length()) { vsbak.setsize(0); vsbak.put(vs, strlen(vs)+1); vs = vsbak.getbuf(); vsbuf.setsize(0); } \ if(psbuf.length()) { psbak.setsize(0); psbak.put(ps, strlen(ps)+1); ps = psbak.getbuf(); psbuf.setsize(0); } \ body; \ if(vsbuf.length()) vs = vsbuf.getbuf(); \ if(psbuf.length()) ps = psbuf.getbuf(); \ } GENSHADER(slotparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps)); GENSHADER(strstr(vs, "//:fog") || strstr(ps, "//:fog"), genfogshader(vsbuf, psbuf, vs, ps)); Shader *s = newshader(*type, name, vs, ps); if(s) { if(strstr(ps, "//:variant") || strstr(vs, "//:variant")) gengenericvariant(*s, name, vs, ps); } slotparams.shrink(0); } COMMAND(shader, "isss"); void variantshader(int *type, char *name, int *row, char *vs, char *ps, int *maxvariants) { if(*row < 0) { shader(type, name, vs, ps); return; } else if(*row >= MAXVARIANTROWS) return; Shader *s = lookupshaderbyname(name); if(!s) return; defformatstring(varname, "%s", s->numvariants(*row), *row, name); if(*maxvariants > 0) { defformatstring(info, "shader %s", name); renderprogress(min(s->variants.length() / float(*maxvariants), 1.0f), info); } vector vsbuf, psbuf, vsbak, psbak; GENSHADER(s->defaultparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps, s)); GENSHADER(strstr(vs, "//:fog") || strstr(ps, "//:fog"), genfogshader(vsbuf, psbuf, vs, ps)); Shader *v = newshader(*type, varname, vs, ps, s, *row); if(v) { if(strstr(ps, "//:variant") || strstr(vs, "//:variant")) gengenericvariant(*s, varname, vs, ps, *row); } } COMMAND(variantshader, "isissi"); void setshader(char *name) { slotparams.shrink(0); Shader *s = shaders.access(name); if(!s) { conoutf(CON_ERROR, "no such shader: %s", name); } else slotshader = s; } COMMAND(setshader, "s"); void resetslotshader() { slotshader = NULL; slotparams.shrink(0); } void setslotshader(Slot &s) { s.shader = slotshader; if(!s.shader) { s.shader = stdworldshader; return; } loopv(slotparams) s.params.add(slotparams[i]); } static void linkslotshaderparams(vector ¶ms, Shader *sh, bool load) { if(sh->loaded()) loopv(params) { int loc = -1; SlotShaderParam ¶m = params[i]; loopv(sh->defaultparams) { SlotShaderParamState &dparam = sh->defaultparams[i]; if(dparam.name==param.name) { if(memcmp(param.val, dparam.val, sizeof(param.val))) loc = i; break; } } param.loc = loc; } else if(load) loopv(params) params[i].loc = -1; } void linkslotshader(Slot &s, bool load) { if(!s.shader) return; if(load && s.shader->deferred()) s.shader->force(); linkslotshaderparams(s.params, s.shader, load); } void linkvslotshader(VSlot &s, bool load) { if(!s.slot->shader) return; linkslotshaderparams(s.params, s.slot->shader, load); if(!s.slot->shader->loaded()) return; if(s.slot->texmask&(1<= 0) { s = s->getvariant(*col, *row); if(!s) return; } if(s->vsstr) fprintf(l, "%s:%s\n%s\n", s->name, "VS", s->vsstr); if(s->psstr) fprintf(l, "%s:%s\n%s\n", s->name, "FS", s->psstr); }); ICOMMAND(isshaderdefined, "s", (char *name), intret(lookupshaderbyname(name) ? 1 : 0)); static hashset shaderparamnames(256); const char *getshaderparamname(const char *name, bool insert) { const char *exists = shaderparamnames.find(name, NULL); if(exists || !insert) return exists; return shaderparamnames.add(newstring(name)); } void addslotparam(const char *name, float x, float y, float z, float w, int flags = 0) { if(name) name = getshaderparamname(name); loopv(slotparams) { SlotShaderParam ¶m = slotparams[i]; if(param.name==name) { param.val[0] = x; param.val[1] = y; param.val[2] = z; param.val[3] = w; param.flags |= flags; return; } } SlotShaderParam param = {name, -1, flags, {x, y, z, w}}; slotparams.add(param); } ICOMMAND(setuniformparam, "sfFFf", (char *name, float *x, float *y, float *z, float *w), addslotparam(name, *x, *y, *z, *w)); ICOMMAND(setshaderparam, "sfFFf", (char *name, float *x, float *y, float *z, float *w), addslotparam(name, *x, *y, *z, *w)); ICOMMAND(defuniformparam, "sfFFf", (char *name, float *x, float *y, float *z, float *w), addslotparam(name, *x, *y, *z, *w)); ICOMMAND(reuseuniformparam, "sfFFf", (char *name, float *x, float *y, float *z, float *w), addslotparam(name, *x, *y, *z, *w, SlotShaderParam::REUSE)); #define NUMPOSTFXBINDS 10 struct postfxtex { GLuint id; int scale, used; postfxtex() : id(0), scale(0), used(-1) {} }; vector postfxtexs; int postfxbinds[NUMPOSTFXBINDS]; GLuint postfxfb = 0; int postfxw = 0, postfxh = 0; struct postfxpass { Shader *shader; vec4 params; uint inputs, freeinputs; int outputbind, outputscale; postfxpass() : shader(NULL), inputs(1), freeinputs(1), outputbind(0), outputscale(0) {} }; vector postfxpasses; static int allocatepostfxtex(int scale) { loopv(postfxtexs) { postfxtex &t = postfxtexs[i]; if(t.scale==scale && t.used < 0) return i; } postfxtex &t = postfxtexs.add(); t.scale = scale; glGenTextures(1, &t.id); createtexture(t.id, max(postfxw>>scale, 1), max(postfxh>>scale, 1), NULL, 3, 1, GL_RGB, GL_TEXTURE_RECTANGLE); return postfxtexs.length()-1; } void cleanuppostfx(bool fullclean) { if(fullclean && postfxfb) { glDeleteFramebuffers_(1, &postfxfb); postfxfb = 0; } loopv(postfxtexs) glDeleteTextures(1, &postfxtexs[i].id); postfxtexs.shrink(0); postfxw = 0; postfxh = 0; } GLuint setuppostfx(int w, int h, GLuint outfbo) { if(postfxpasses.empty()) return outfbo; if(postfxw != w || postfxh != h) { cleanuppostfx(false); postfxw = w; postfxh = h; } loopi(NUMPOSTFXBINDS) postfxbinds[i] = -1; loopv(postfxtexs) postfxtexs[i].used = -1; if(!postfxfb) glGenFramebuffers_(1, &postfxfb); glBindFramebuffer_(GL_FRAMEBUFFER, postfxfb); int tex = allocatepostfxtex(0); glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, postfxtexs[tex].id, 0); bindgdepth(); postfxbinds[0] = tex; postfxtexs[tex].used = 0; return postfxfb; } void renderpostfx(GLuint outfbo) { if(postfxpasses.empty()) return; timer *postfxtimer = begintimer("postfx"); loopv(postfxpasses) { postfxpass &p = postfxpasses[i]; int tex = -1; if(!postfxpasses.inrange(i+1)) { glBindFramebuffer_(GL_FRAMEBUFFER, outfbo); } else { tex = allocatepostfxtex(p.outputscale); glFramebufferTexture2D_(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, postfxtexs[tex].id, 0); } int w = tex >= 0 ? max(postfxw>>postfxtexs[tex].scale, 1) : postfxw, h = tex >= 0 ? max(postfxh>>postfxtexs[tex].scale, 1) : postfxh; glViewport(0, 0, w, h); p.shader->set(); LOCALPARAM(params, p.params); int tw = w, th = h, tmu = 0; loopj(NUMPOSTFXBINDS) if(p.inputs&(1<= 0) { if(!tmu) { tw = max(postfxw>>postfxtexs[postfxbinds[j]].scale, 1); th = max(postfxh>>postfxtexs[postfxbinds[j]].scale, 1); } else glActiveTexture_(GL_TEXTURE0 + tmu); glBindTexture(GL_TEXTURE_RECTANGLE, postfxtexs[postfxbinds[j]].id); ++tmu; } if(tmu) glActiveTexture_(GL_TEXTURE0); screenquad(tw, th); loopj(NUMPOSTFXBINDS) if(p.freeinputs&(1<= 0) { postfxtexs[postfxbinds[j]].used = -1; postfxbinds[j] = -1; } if(tex >= 0) { if(postfxbinds[p.outputbind] >= 0) postfxtexs[postfxbinds[p.outputbind]].used = -1; postfxbinds[p.outputbind] = tex; postfxtexs[tex].used = p.outputbind; } } endtimer(postfxtimer); } static bool addpostfx(const char *name, int outputbind, int outputscale, uint inputs, uint freeinputs, const vec4 ¶ms) { if(!*name) return false; Shader *s = useshaderbyname(name); if(!s) { conoutf(CON_ERROR, "no such postfx shader: %s", name); return false; } postfxpass &p = postfxpasses.add(); p.shader = s; p.outputbind = outputbind; p.outputscale = outputscale; p.inputs = inputs; p.freeinputs = freeinputs; p.params = params; return true; } void clearpostfx() { postfxpasses.shrink(0); cleanuppostfx(false); } COMMAND(clearpostfx, ""); ICOMMAND(addpostfx, "siisffff", (char *name, int *bind, int *scale, char *inputs, float *x, float *y, float *z, float *w), { int inputmask = inputs[0] ? 0 : 1; int freemask = inputs[0] ? 0 : 1; bool freeinputs = true; for(; *inputs; inputs++) { if(isdigit(*inputs)) { inputmask |= 1<<(*inputs-'0'); if(freeinputs) freemask |= 1<<(*inputs-'0'); } else if(*inputs=='+') freeinputs = false; else if(*inputs=='-') freeinputs = true; } inputmask &= (1<reusevs && v->reusevs->invalid()) || (v->reuseps && v->reuseps->invalid()) || !v->compile()) v->cleanup(true); } } if(s.forced && s.deferred()) s.force(); }); } void resetshaders() { clearchanges(CHANGE_SHADERS); cleanuplights(); cleanupmodels(); cleanupshaders(); setupshaders(); initgbuffer(); reloadshaders(); allchanged(true); GLERROR; } COMMAND(resetshaders, ""); FVAR(blursigma, 0.005f, 0.5f, 2.0f); void setupblurkernel(int radius, float *weights, float *offsets) { if(radius<1 || radius>MAXBLURRADIUS) return; float sigma = blursigma*2*radius, total = 1.0f/sigma; weights[0] = total; offsets[0] = 0; // rely on bilinear filtering to sample 2 pixels at once // transforms a*X + b*Y into (u+v)*[X*u/(u+v) + Y*(1 - u/(u+v))] loopi(radius) { float weight1 = exp(-((2*i)*(2*i)) / (2*sigma*sigma)) / sigma, weight2 = exp(-((2*i+1)*(2*i+1)) / (2*sigma*sigma)) / sigma, scale = weight1 + weight2, offset = 2*i+1 + weight2 / scale; weights[i+1] = scale; offsets[i+1] = offset; total += 2*scale; } loopi(radius+1) weights[i] /= total; for(int i = radius+1; i <= MAXBLURRADIUS; i++) weights[i] = offsets[i] = 0; } void setblurshader(int pass, int size, int radius, float *weights, float *offsets, GLenum target) { if(radius<1 || radius>MAXBLURRADIUS) return; static Shader *blurshader[7][2] = { { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL } }, *blurrectshader[7][2] = { { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL }, { NULL, NULL } }; Shader *&s = (target == GL_TEXTURE_RECTANGLE ? blurrectshader : blurshader)[radius-1][pass]; if(!s) { defformatstring(name, "blur%c%d%s", 'x'+pass, radius, target == GL_TEXTURE_RECTANGLE ? "rect" : ""); s = lookupshaderbyname(name); } s->set(); LOCALPARAMV(weights, weights, 8); float scaledoffsets[8]; loopk(8) scaledoffsets[k] = offsets[k]/size; LOCALPARAMV(offsets, scaledoffsets, 8); }