// shader.cpp: OpenGL GLSL shader management #include "engine.h" Shader *Shader::lastshader = NULL; Shader *nullshader = NULL, *hudshader = NULL, *hudnotextureshader = NULL, *textureshader = NULL, *notextureshader = NULL, *nocolorshader = 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(reservevpparams, 1, 16, 0); VAR(maxvsuniforms, 1, 0, 0); VAR(maxfsuniforms, 1, 0, 0); VAR(maxvaryings, 1, 0, 0); void loadshaders() { standardshaders = true; execfile("data/glsl.cfg"); standardshaders = false; nullshader = lookupshaderbyname("null"); hudshader = lookupshaderbyname("hud"); hudnotextureshader = lookupshaderbyname("hudnotexture"); stdworldshader = lookupshaderbyname("stdworld"); if(!nullshader || !hudshader || !hudnotextureshader || !stdworldshader) fatal("cannot find shader definitions"); dummyslot.shader = stdworldshader; textureshader = lookupshaderbyname("texture"); notextureshader = lookupshaderbyname("notexture"); nocolorshader = lookupshaderbyname("nocolor"); 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(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[16]; int numparts = 0; static const struct { int version; const char * const header; } glslversions[] = { { 330, "#version 330\n" }, { 150, "#version 150\n" }, { 130, "#version 130\n" }, { 120, "#version 120\n" } }; loopi(sizeof(glslversions)/sizeof(glslversions[0])) if(glslversion >= glslversions[i].version) { parts[numparts++] = glslversions[i].header; break; } if(glslversion >= 130) { if(type == GL_VERTEX_SHADER) parts[numparts++] = "#define attribute in\n" "#define varying out\n"; else if(type == GL_FRAGMENT_SHADER) { parts[numparts++] = "#define varying in\n"; if(glslversion < 150) parts[numparts++] = "precision highp float;\n"; if(glversion >= 300) parts[numparts++] = "out vec4 cube2_FragColor;\n" "#define gl_FragColor cube2_FragColor\n"; } parts[numparts++] = "#define texture2D(sampler, coords) texture(sampler, coords)\n" "#define texture2DProj(sampler, coords) textureProj(sampler, coords)\n" "#define textureCube(sampler, coords) texture(sampler, coords)\n"; } parts[numparts++] = source; 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; } } static void bindglsluniform(Shader &s, UniformLoc &u) { u.loc = glGetUniformLocation_(s.program, u.name); if(!u.blockname || !hasUBO) 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); } } 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<= 300) { glBindFragDataLocation_(s.program, 0, "cube2_FragColor"); } glLinkProgram_(s.program); glGetProgramiv_(s.program, GL_LINK_STATUS, &success); } if(success) { glUseProgram_(s.program); loopi(8) { static const char * const texnames[8] = { "tex0", "tex1", "tex2", "tex3", "tex4", "tex5", "tex6", "tex7" }; GLint loc = glGetUniformLocation_(s.program, texnames[i]); if(loc != -1) glUniform1i_(loc, i); } loopv(s.defaultparams) { SlotShaderParamState ¶m = s.defaultparams[i]; param.loc = glGetUniformLocation_(s.program, param.name); } loopv(s.uniformlocs) bindglsluniform(s, s.uniformlocs[i]); glUseProgram_(0); } else if(s.program) { if(msg) showglslinfo(GL_FALSE, s.program, s.name); glDeleteProgram_(s.program); s.program = 0; } } int getlocalparam(const char *name) { return localparams.access(name, int(localparams.numelems)); } static int addlocalparam(Shader &s, const char *name, int loc, int size, GLenum format) { int idx = getlocalparam(name); if(idx >= 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_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) { if(slot) { #define UNIFORMTEX(name, tmu) \ { \ loc = glGetUniformLocation_(program, name); \ int val = tmu; \ if(loc != -1) glUniform1i_(loc, val); \ } int loc, tmu = 2; if(type & SHADER_NORMALSLMS) { UNIFORMTEX("lmcolor", 1); UNIFORMTEX("lmdir", 2); tmu++; } else UNIFORMTEX("lightmap", 1); UNIFORMTEX("shadowmap", 7); int stex = 0; loopv(slot->sts) { Slot::Tex &t = slot->sts[i]; switch(t.type) { case TEX_DIFFUSE: UNIFORMTEX("diffusemap", 0); break; case TEX_NORMAL: UNIFORMTEX("normalmap", tmu++); break; case TEX_GLOW: UNIFORMTEX("glowmap", tmu++); break; case TEX_DECAL: UNIFORMTEX("decal", tmu++); break; case TEX_SPEC: if(t.combined<0) UNIFORMTEX("specmap", tmu++); break; case TEX_DEPTH: if(t.combined<0) UNIFORMTEX("depthmap", tmu++); break; case TEX_ALPHA: if(t.combined<0) UNIFORMTEX("alphamap", tmu++); break; case TEX_UNKNOWN: { defformatstring(sname, "stex%d", stex++); UNIFORMTEX(sname, tmu++); break; } } } } 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; } } #define SETSLOTPARAM(l, mask, i, val) do { \ if(!(mask&(1<invalid() ? 0 : reusevs->vsobj; else compileglslshader(GL_VERTEX_SHADER, vsobj, vsstr, name, !variantshader); if(!psstr) psobj = !reuseps || reuseps->invalid() ? 0 : reuseps->psobj; else compileglslshader(GL_FRAGMENT_SHADER, psobj, psstr, name, !variantshader); linkglslprogram(*this, !variantshader); return program!=0; } void Shader::cleanup(bool invalid) { detailshader = NULL; 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 || invalid) { type = SHADER_INVALID; DELETEA(vsstr); DELETEA(psstr); DELETEA(defer); variants.setsize(0); DELETEA(variantrows); defaultparams.setsize(0); attriblocs.setsize(0); uniformlocs.setsize(0); altshader = NULL; loopi(MAXSHADERDETAIL) fastshader[i] = this; reusevs = reuseps = NULL; } else loopv(defaultparams) defaultparams[i].loc = -1; owner = NULL; } bool Shader::isnull(const Shader *s) { return !s; } 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; 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); if(!s.compile()) { s.cleanup(true); if(variant) shaders.remove(rname); return NULL; } if(variant) variant->addvariant(row, &s); s.fixdetailshader(); return &s; } void setupshaders() { GLint val; glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS, &val); maxvsuniforms = val/4; glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS, &val); maxfsuniforms = val/4; if(glversion < 300) { glGetIntegerv(GL_MAX_VARYING_COMPONENTS, &val); maxvaryings = val; } standardshaders = true; nullshader = newshader(0, "null", "attribute vec4 vvertex;\n" "void main(void) {\n" " gl_Position = vvertex;\n" "}\n", "void main(void) {\n" " gl_FragColor = vec4(1.0, 0.0, 1.0, 0.0);\n" "}\n"); hudshader = newshader(0, "hud", "attribute vec4 vvertex, vcolor;\n" "attribute vec2 vtexcoord0;\n" "uniform mat4 hudmatrix;\n" "varying vec2 texcoord0;\n" "varying vec4 color;\n" "void main(void) {\n" " gl_Position = hudmatrix * vvertex;\n" " texcoord0 = vtexcoord0;\n" " color = vcolor;\n" "}\n", "varying vec2 texcoord0;\n" "varying vec4 color;\n" "uniform sampler2D tex0;\n" "void main(void) {\n" " gl_FragColor = color * texture2D(tex0, texcoord0);\n" "}\n"); hudnotextureshader = newshader(0, "hudnotexture", "attribute vec4 vvertex, vcolor;\n" "uniform mat4 hudmatrix;\n" "varying vec4 color;\n" "void main(void) {\n" " gl_Position = hudmatrix * vvertex;\n" " color = vcolor;\n" "}\n", "varying vec4 color;\n" "void main(void) {\n" " gl_FragColor = color;\n" "}\n"); standardshaders = false; if(!nullshader || !hudshader || !hudnotextureshader) fatal("failed to setup shaders"); dummyslot.shader = nullshader; } 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); } bool minimizedynlighttcusage() { return glversion < 300 && maxvaryings < 48; } static void gendynlightvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 0) { int numlights = minimizedynlighttcusage() ? 1 : MAXDYNLIGHTS; const char *vspragma = strstr(vs, "//:dynlight"), *pspragma = strstr(ps, "//:dynlight"); if(!vspragma || !pspragma) return; string pslight; vspragma += strcspn(vspragma, "\n"); if(*vspragma) vspragma++; if(sscanf(pspragma, "//:dynlight %100s", pslight)!=1) return; pspragma += strcspn(pspragma, "\n"); if(*pspragma) pspragma++; const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); if(vsmain > vspragma) vsmain = vs; if(psmain > pspragma) psmain = ps; vector vsdl, psdl; loopi(MAXDYNLIGHTS) { vsdl.setsize(0); psdl.setsize(0); if(vsmain >= vs) vsdl.put(vs, vsmain - vs); if(psmain >= ps) psdl.put(ps, psmain - ps); defformatstring(pos, "uniform vec4 dynlightpos[%d];\n", i+1); vsdl.put(pos, strlen(pos)); psdl.put(pos, strlen(pos)); defformatstring(color, "uniform vec3 dynlightcolor[%d];\n", i+1); psdl.put(color, strlen(color)); loopk(min(i+1, numlights)) { defformatstring(dir, "%sdynlight%ddir%s", !k ? "varying vec3 " : " ", k, k==i || k+1==numlights ? ";\n" : ","); vsdl.put(dir, strlen(dir)); psdl.put(dir, strlen(dir)); } vsdl.put(vsmain, vspragma-vsmain); psdl.put(psmain, pspragma-psmain); loopk(i+1) { defformatstring(tc, k%s", i+1, sname); Shader *variant = newshader(s.type, name, vsdl.getbuf(), psdl.getbuf(), &s, row); if(!variant) return; } } static void genshadowmapvariant(Shader &s, const char *sname, const char *vs, const char *ps, int row = 1) { const char *vspragma = strstr(vs, "//:shadowmap"), *pspragma = strstr(ps, "//:shadowmap"); if(!vspragma || !pspragma) return; string pslight; vspragma += strcspn(vspragma, "\n"); if(*vspragma) vspragma++; if(sscanf(pspragma, "//:shadowmap %100s", pslight)!=1) return; pspragma += strcspn(pspragma, "\n"); if(*pspragma) pspragma++; const char *vsmain = findglslmain(vs), *psmain = findglslmain(ps); if(vsmain > vspragma) vsmain = vs; if(psmain > pspragma) psmain = ps; vector vssm, pssm; if(vsmain >= vs) vssm.put(vs, vsmain - vs); if(psmain >= ps) pssm.put(ps, psmain - ps); const char *vsdecl = "uniform mat4 shadowmapproject;\n" "varying vec3 shadowmaptc;\n"; vssm.put(vsdecl, strlen(vsdecl)); const char *psdecl = "uniform sampler2D shadowmap;\n" "uniform vec4 shadowmapambient;\n" "varying vec3 shadowmaptc;\n"; pssm.put(psdecl, strlen(psdecl)); vssm.put(vsmain, vspragma-vsmain); pssm.put(psmain, pspragma-psmain); extern int smoothshadowmappeel; const char *tcgen = "shadowmaptc = vec3(shadowmapproject * vvertex);\n"; vssm.put(tcgen, strlen(tcgen)); const char *sm = smoothshadowmappeel ? "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n" "vec2 smdiff = clamp(smvals.xz - shadowmaptc.zz*smvals.y, 0.0, 1.0);\n" "float shadowed = clamp((smdiff.x > 0.0 ? smvals.w : 0.0) - 8.0*smdiff.y, 0.0, 1.0);\n" : "vec4 smvals = texture2D(shadowmap, shadowmaptc.xy);\n" "float smtest = shadowmaptc.z*smvals.y;\n" "float shadowed = smtest < smvals.x && smtest > smvals.z ? smvals.w : 0.0;\n"; pssm.put(sm, strlen(sm)); defformatstring(smlight, "%s.rgb -= shadowed*clamp(%s.rgb - shadowmapambient.rgb, 0.0, 1.0);\n", pslight, pslight); pssm.put(smlight, strlen(smlight)); vssm.put(vspragma, strlen(vspragma)+1); pssm.put(pspragma, strlen(pspragma)+1); defformatstring(name, "%s", sname); Shader *variant = newshader(s.type, name, vssm.getbuf(), pssm.getbuf(), &s, row); if(!variant) return; if(strstr(vs, "//:dynlight")) gendynlightvariant(s, name, vssm.getbuf(), pssm.getbuf(), row); } 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); } 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; s.standard = standardshaders; } 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; } } void fixshaderdetail() { // must null out separately because fixdetailshader can recursively set it enumerate(shaders, Shader, s, { if(!s.forced) s.detailshader = NULL; }); enumerate(shaders, Shader, s, { if(s.forced) s.fixdetailshader(); }); linkslotshaders(); } 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; } VARFP(shaderdetail, 0, MAXSHADERDETAIL, MAXSHADERDETAIL, fixshaderdetail()); void Shader::fixdetailshader(bool shouldforce, bool recurse) { Shader *alt = this; detailshader = NULL; do { Shader *cur = shaderdetail < MAXSHADERDETAIL ? alt->fastshader[shaderdetail] : alt; if(cur->deferred() && shouldforce) cur->force(); if(!cur->invalid()) { if(cur->deferred()) break; detailshader = cur; break; } alt = alt->altshader; } while(alt && alt!=this); if(recurse && detailshader) loopv(detailshader->variants) detailshader->variants[i]->fixdetailshader(shouldforce, false); } Shader *useshaderbyname(const char *name) { Shader *s = shaders.access(name); if(!s) return NULL; if(!s->detailshader) s->fixdetailshader(); s->forced = true; return s; } 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)); Shader *s = newshader(*type, name, vs, ps); if(s) { if(strstr(vs, "//:shadowmap")) genshadowmapvariant(*s, s->name, vs, ps); if(strstr(vs, "//:dynlight")) gendynlightvariant(*s, s->name, vs, ps); } slotparams.shrink(0); } void variantshader(int *type, char *name, int *row, char *vs, char *ps) { 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); //defformatstring(info, "shader %s", varname); //renderprogress(loadprogress, info); vector vsbuf, psbuf, vsbak, psbak; GENSHADER(s->defaultparams.length(), genuniformdefs(vsbuf, psbuf, vs, ps, s)); Shader *v = newshader(*type, varname, vs, ps, s, *row); if(v) { if(strstr(vs, "//:dynlight")) gendynlightvariant(*s, varname, vs, ps, *row); if(strstr(ps, "//:variant") || strstr(vs, "//:variant")) gengenericvariant(*s, varname, vs, ps, *row); } } 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; } 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) 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->detailshader) s.shader->fixdetailshader(); linkslotshaderparams(s.params, s.shader->detailshader, load); } void linkvslotshader(VSlot &s, bool load) { if(!s.slot->shader) return; Shader *sh = s.slot->shader->detailshader; linkslotshaderparams(s.params, sh, load); if(!sh) return; if(s.slot->texmask&(1<altshader = alt; orig->fixdetailshader(false); } void fastshader(char *nice, char *fast, int *detail) { Shader *ns = shaders.access(nice), *fs = shaders.access(fast); if(!ns || !fs) return; loopi(min(*detail+1, MAXSHADERDETAIL)) ns->fastshader[i] = fs; ns->fixdetailshader(false); } COMMAND(shader, "isss"); COMMAND(variantshader, "isiss"); COMMAND(setshader, "s"); COMMAND(altshader, "ss"); COMMAND(fastshader, "ssi"); COMMAND(defershader, "iss"); ICOMMAND(forceshader, "s", (const char *name), useshaderbyname(name)); 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) { 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; return; } } SlotShaderParam param = {name, -1, {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)); void cleanupshaders() { loadedshaders = false; nullshader = hudshader = hudnotextureshader = textureshader = notextureshader = nocolorshader = stdworldshader = NULL; enumerate(shaders, Shader, s, s.cleanup()); Shader::lastshader = NULL; glUseProgram_(0); } void reloadshaders() { identflags &= ~IDF_PERSIST; loadshaders(); identflags |= IDF_PERSIST; linkslotshaders(); enumerate(shaders, Shader, s, { if(!s.standard && !(s.type&(SHADER_DEFERRED|SHADER_INVALID)) && !s.variantshader) { defformatstring(info, "shader %s", s.name); renderprogress(0.0, info); if(!s.compile()) s.cleanup(true); loopv(s.variants) { Shader *v = s.variants[i]; if((v->reusevs && v->reusevs->invalid()) || (v->reuseps && v->reuseps->invalid()) || !v->compile()) v->cleanup(true); } } if(s.forced && !s.detailshader) s.fixdetailshader(); }); } void setupblurkernel(int radius, float sigma, float *weights, float *offsets) { if(radius<1 || radius>MAXBLURRADIUS) return; sigma *= 2*radius; float 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) { 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 } }; Shader *&s = blurshader[radius-1][pass]; if(!s) { defformatstring(name, "blur%c%d", 'x'+pass, radius); s = lookupshaderbyname(name); } s->set(); LOCALPARAMV(weights, weights, 8); float scaledoffsets[8]; loopk(8) scaledoffsets[k] = offsets[k]/size; LOCALPARAMV(offsets, scaledoffsets, 8); }