#include "../plugin.h" #include "shader.h" static plugfsfuncs_t *fsfuncs; typedef struct shaderparsestate_s parsestate_t; typedef struct { char **savefile; char *sourcefile; char type[MAX_QPATH]; char normalmap[MAX_QPATH]; struct { char name[MAX_QPATH]; } tex[5]; char envmap[MAX_QPATH]; char envmapmask[MAX_QPATH]; char envfrombase; char halflambert; float alphatestref; char *blendfunc; qboolean alphatest; qboolean culldisable; qboolean ignorez; char *replaceblock; } vmtstate_t; static void VARGS Q_strlcatfz (char *dest, size_t *offset, size_t size, const char *fmt, ...) LIKEPRINTF(4); static void VARGS Q_strlcatfz (char *dest, size_t *offset, size_t size, const char *fmt, ...) { va_list argptr; dest += *offset; size -= *offset; va_start (argptr, fmt); Q_vsnprintfz(dest, size, fmt, argptr); va_end (argptr); *offset += strlen(dest); } static void Q_StrCat(char **ptr, const char *append) { size_t oldlen = *ptr?strlen(*ptr):0; size_t newlen = strlen(append); char *newptr = plugfuncs->Malloc(oldlen+newlen+1); memcpy(newptr, *ptr, oldlen); memcpy(newptr+oldlen, append, newlen); newptr[oldlen+newlen] = 0; plugfuncs->Free(*ptr); *ptr = newptr; } //case comparisons are specific to ascii only, so this should be 'safe' for utf-8 strings too. int Q_strncasecmp (const char *s1, const char *s2, int n) { int c1, c2; while (1) { c1 = *s1++; c2 = *s2++; if (!n--) return 0; // strings are equal until end point if (c1 != c2) { if (c1 >= 'a' && c1 <= 'z') c1 -= ('a' - 'A'); if (c2 >= 'a' && c2 <= 'z') c2 -= ('a' - 'A'); if (c1 != c2) { // strings not equal if (c1 > c2) return 1; // strings not equal return -1; } } if (!c1) return 0; // strings are equal // s1++; // s2++; } return -1; } int Q_strcasecmp (const char *s1, const char *s2) { return Q_strncasecmp (s1, s2, 0x7fffffff); } static qboolean VMT_ReadVMT(const char *materialname, vmtstate_t *st); //this is made more complicated on account of includes allowing recursion static char *VMT_ParseBlock(const char *fname, vmtstate_t *st, char *line) { //assumes the open { was already parsed, but will parse the close. char *replace = NULL; com_tokentype_t ttype; char key[MAX_OSPATH]; char value[MAX_OSPATH]; char *qmark; qboolean cond; for(;line;) { line = cmdfuncs->ParseToken(line, key, sizeof(key), &ttype); if (ttype == TTP_RAWTOKEN && !strcmp(key, "}")) break; //end-of-block line = cmdfuncs->ParseToken(line, value, sizeof(value), &ttype); if (ttype == TTP_RAWTOKEN && !strcmp(value, "{")) { //sub block. we don't go into details here. if (!Q_strcasecmp(key, "replace")) replace = line; else Con_DPrintf("%s: Unknown block \"%s\"\n", fname, key); line = VMT_ParseBlock(fname, NULL, line); continue; } while ((qmark = strchr(key, '?'))) { *qmark++ = 0; if (!Q_strcasecmp(key, "srgb")) cond = false;//!!(vid.flags & VID_SRGBAWARE); else { Con_DPrintf("%s: Unknown vmt conditional \"%s\"\n", fname, key); cond = false; } if (!cond) { *key = 0; break; } else memmove(key, qmark, strlen(qmark)+1); } if (!*key || !st) ; else if (!Q_strcasecmp(key, "include")) { if (!VMT_ReadVMT(value, st)) return NULL; } else if (!Q_strcasecmp(key, "$basetexture") || !Q_strcasecmp(key, "$hdrbasetexture")) //fixme: hdr version should probably override the other. order matters. Q_strlcpy(st->tex[0].name, value, sizeof(st->tex[0].name)); else if (!Q_strcasecmp(key, "$hdrcompressedtexture")) //named texture is R8G8B8E8 and needs to be decompressed manually... should probably just use e5bgr9 but we don't have a way to transcode it here. ; else if (!Q_strcasecmp(key, "$basetexturetransform")) ; else if (!Q_strcasecmp(key, "$bumpmap")) // same as normalmap ~eukara { Q_strlcpy(st->normalmap, value, sizeof(st->normalmap)); Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); } else if (!Q_strcasecmp(key, "$ssbump")) ; else if (!Q_strcasecmp(key, "$ssbumpmathfix")) ; else if (!Q_strcasecmp(key, "$basetexture2") || !strcmp(key, "$texture2")) Q_strlcpy(st->tex[1].name, value, sizeof(st->tex[1].name)); else if (!Q_strcasecmp(key, "$basetexturetransform2")) ; else if (!Q_strcasecmp(key, "$surfaceprop")) ; else if (!Q_strcasecmp(key, "$ignorez")) st->ignorez = !!atoi(value); else if (!Q_strcasecmp(key, "$nocull") && (!strcmp(value, "1")||!strcmp(value, "0"))) st->culldisable = atoi(value); else if (!Q_strcasecmp(key, "$alphatest") && (!strcmp(value, "1")||!strcmp(value, "0"))) st->alphatest = atoi(value); else if (!Q_strcasecmp(key, "$alphatestreference")) st->alphatestref = atof(value); else if (!Q_strcasecmp(key, "$alphafunc")) { Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); } else if (!Q_strcasecmp(key, "$alpha")) { Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); } else if (!Q_strcasecmp(key, "$translucent")) { if (atoi(value)) st->blendfunc = "src_alpha one_minus_src_alpha\n"; } else if (!Q_strcasecmp(key, "$additive")) { if (atoi(value)) st->blendfunc = "src_one one_minus_src_alpha\n"; Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); } else if (!Q_strcasecmp(key, "$halflambert")) st->halflambert = 1; else if (!Q_strcasecmp(key, "$color")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$vertexcolor")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$vertexalpha")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$decal")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$decalscale")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$decalsize")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$envmap")) Q_strlcpy(st->envmap, value, sizeof(st->envmap)); else if (!Q_strcasecmp(key, "$envmapmask")) Q_strlcpy(st->envmapmask, value, sizeof(st->envmapmask)); else if (!Q_strcasecmp(key, "$envmapcontrast")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$envmaptint")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$envmapsaturation")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$basealphaenvmapmask")) st->envfrombase=1; else if (!Q_strcasecmp(key, "$normalmapalphaenvmapmask")) st->envfrombase=0; else if (!Q_strcasecmp(key, "$crackmaterial")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$selfillum")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$selfillummask")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$selfillumtint")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$nofog")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$nomip")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$nodecal")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$detail")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$detailscale")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$detailtint")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$detailblendfactor")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$detailblendmode")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$surfaceprop2")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$AllowAlphaToCoverage")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$blendmodulatetexture")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); //water/reflection stuff else if (!Q_strcasecmp(key, "$refracttinttexture")) Q_strlcpy(st->tex[0].name, value, sizeof(st->tex[0].name)); else if (!Q_strcasecmp(key, "$refracttexture")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$refractamount")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$refracttint")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$reflecttexture")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$reflectamount")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$reflecttint")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$fresnelpower")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$minreflectivity")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$maxreflectivity")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$normalmap")) { Q_strlcpy(st->normalmap, value, sizeof(st->normalmap)); Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); } else if (!Q_strcasecmp(key, "$bumpframe")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$fogenable")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$fogcolor")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$fogstart")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$fogend")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$abovewater")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$underwateroverlay")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$reflectentities")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$scale")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$bottommaterial")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$scroll1")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (!Q_strcasecmp(key, "$scroll2")) Con_DPrintf("%s: %s \"%s\"\n", fname, key, value); else if (*key == '%') ; //editor lines else Con_DPrintf("%s: Unknown field \"%s\"\n", fname, key); } if (replace) VMT_ParseBlock(fname, st, replace); return line; } static void Shader_GenerateFromVMT(parsestate_t *ps, vmtstate_t *st, const char *shortname, void (*LoadMaterialString)(parsestate_t *ps, const char *script)) { size_t offset = 0; char script[8192]; char *progargs = ""; if (!*st->tex[0].name) //fill in a default... Q_strlcpy(st->tex[0].name, shortname, sizeof(st->tex[0].name)); if (st->alphatest) progargs = "#MASK=0.5#MASKLT"; //alphamask has to be handled by glsl (when glsl is used) Q_strlcatfz(script, &offset, sizeof(script), "\n"); if (!Q_strcasecmp(st->type, "WorldVertexTransition")) { //attempt to do terrain blending Q_strlcpy(st->type, "vmt/transition#TWOWAY", sizeof(st->type)); Q_strlcatfz(script, &offset, sizeof(script), "\tprogram \"%s%s\"\n", st->type, progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); Q_strlcatfz(script, &offset, sizeof(script), "\tuppermap \"%s%s.vtf\"\n", strcmp(st->tex[1].name, "materials/")?"materials/":"", st->tex[1].name); } else if (!Q_strcasecmp(st->type, "Decal")) { Q_strlcpy(st->type, "vmt/vertexlit", sizeof(st->type)); Q_strlcatfz(script, &offset, sizeof(script), "\tprogram \"%s%s\"\n", st->type, progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); Q_strlcatfz(script, &offset, sizeof(script), "\tpolygonOffset 1\n"); } else if (!Q_strcasecmp(st->type, "DecalModulate")) { Q_strlcatfz(script, &offset, sizeof(script), "\t{\n" "\t\tprogram \"vmt/vertexlit%s\"\n" "\t\tblendFunc gl_dst_color gl_one_minus_src_alpha\n" "\t}\n", progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); Q_strlcatfz(script, &offset, sizeof(script), "\tpolygonOffset 1\n"); } else if (!Q_strcasecmp(st->type, "Water")) { Q_strlcatfz(script, &offset, sizeof(script), "\t{\n" "\t\tprogram \"vmt/water%s\"\n" "\t\tmap $refraction\n" "\t\tmap $reflection\n" "\t}\n", progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); Q_strlcatfz(script, &offset, sizeof(script), "\tnormalmap \"%s%s.vtf\"\n", strcmp(st->normalmap, "materials/")?"materials/":"", st->normalmap); } else if (!Q_strcasecmp(st->type, "Refract")) { Q_strlcatfz(script, &offset, sizeof(script), "\t{\n" "\t\tprogram \"vmt/refract%s\"\n" "\t\tmap $refraction\n" "\t}\n", progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); Q_strlcatfz(script, &offset, sizeof(script), "\tnormalmap \"%s%s.vtf\"\n", strcmp(st->normalmap, "materials/")?"materials/":"", st->normalmap); } else if (!Q_strcasecmp(st->type, "VertexlitGeneric")) { if (*st->envmap && st->envfrombase) { if (st->halflambert) Q_strlcpy(st->type, "vmt/vertexlit#ENVFROMBASE#HALFLAMBERT", sizeof(st->type)); else Q_strlcpy(st->type, "vmt/vertexlit#ENVFROMBASE", sizeof(st->type)); } else { if (st->halflambert) Q_strlcpy(st->type, "vmt/vertexlit#HALFLAMBERT", sizeof(st->type)); else Q_strlcpy(st->type, "vmt/vertexlit", sizeof(st->type)); } Q_strlcatfz(script, &offset, sizeof(script), "\tprogram \"%s%s\"\n", st->type, progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); if (*st->normalmap) Q_strlcatfz(script, &offset, sizeof(script), "\tnormalmap \"%s%s.vtf\"\n", strcmp(st->normalmap, "materials/")?"materials/":"", st->normalmap); } else if (!Q_strcasecmp(st->type, "LightmappedGeneric")) { /* reflectmask from diffuse map alpha */ if (*st->envmap && st->envfrombase) Q_strlcpy(st->type, "vmt/lightmapped#ENVFROMBASE", sizeof(st->type)); else if (*st->envmap && *st->envmapmask) /* dedicated reflectmask */ Q_strlcpy(st->type, "vmt/lightmapped#ENVFROMMASK", sizeof(st->type)); else /* take from normalmap */ Q_strlcpy(st->type, "vmt/lightmapped", sizeof(st->type)); Q_strlcatfz(script, &offset, sizeof(script), "\tprogram \"%s%s\"\n", st->type, progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); if (*st->normalmap) Q_strlcatfz(script, &offset, sizeof(script), "\tnormalmap \"%s%s.vtf\"\n", strcmp(st->normalmap, "materials/")?"materials/":"", st->normalmap); } else { /* render-target camera/monitor - eukara*/ if (!Q_strcasecmp(st->tex[0].name, "_rt_Camera")) Q_strlcatfz(script, &offset, sizeof(script), "\t{\n" "\t\tmap $rt:base\n" "\t}\n"/*, progargs*/); else { /* the default should just be unlit, let's not make any assumptions - eukara*/ Q_strlcpy(st->type, "vmt/unlit", sizeof(st->type)); Q_strlcatfz(script, &offset, sizeof(script), "\tprogram \"%s%s\"\n", st->type, progargs); Q_strlcatfz(script, &offset, sizeof(script), "\tdiffusemap \"%s%s.vtf\"\n", strcmp(st->tex[0].name, "materials/")?"materials/":"", st->tex[0].name); } } if (*st->envmapmask) Q_strlcatfz(script, &offset, sizeof(script), "\treflectmask \"%s%s.vtf\"\n", strcmp(st->envmapmask, "materials/")?"materials/":"", st->envmapmask); if (*st->envmap && strcmp(st->envmap, "env_cubemap")) Q_strlcatfz(script, &offset, sizeof(script), "\treflectcube \"%s%s.vtf\"\n", strcmp(st->envmap, "materials/")?"materials/":"", st->envmap); if (st->alphatest) Q_strlcatfz(script, &offset, sizeof(script), "\talphatest ge128\n"); if (st->culldisable) Q_strlcatfz(script, &offset, sizeof(script), "\tcull disable\n"); if (st->ignorez) Q_strlcatfz(script, &offset, sizeof(script), "\tnodepth\n"); if (st->blendfunc) Q_strlcatfz(script, &offset, sizeof(script), "\tprogblendfunc %s\n", st->blendfunc); Q_strlcatfz(script, &offset, sizeof(script), "}\n"); LoadMaterialString(ps, script); if (st->sourcefile) { //cat the original file on there... if (st->savefile) { char *winsucks; //strip any '\r' chars in there that like to show as ugly glyphs. for (winsucks = st->sourcefile; *winsucks; winsucks++) if (*winsucks=='\r') *winsucks = ' '; Q_StrCat(st->savefile, "\n/*\n"); Q_StrCat(st->savefile, st->sourcefile); Q_StrCat(st->savefile, "*/"); } plugfuncs->Free(st->sourcefile); } } static qboolean VMT_ReadVMT(const char *fname, vmtstate_t *st) { char *line, *file = NULL; com_tokentype_t ttype; char token[MAX_QPATH]; char *prefix="", *postfix=""; if (strstr(fname, "://")) return false; //don't try to handle urls. //don't dupe the mandatory materials/ prefix if (strncmp(fname, "materials/", 10)) prefix = "materials/"; if (strcmp(fsfuncs->GetExtension(fname, NULL), ".vmt")) postfix = ".vmt"; Q_snprintfz(token, sizeof(token), "%s%s%s", prefix, fname, postfix); file = fsfuncs->LoadFile(token, NULL); if (file) { if (st->savefile) { if (st->sourcefile) { Q_StrCat(&st->sourcefile, fname); Q_StrCat(&st->sourcefile, ":\n"); Q_StrCat(&st->sourcefile, file); } else Q_StrCat(&st->sourcefile, file); } line = file; line = cmdfuncs->ParseToken(line, st->type, sizeof(st->type), &ttype); line = cmdfuncs->ParseToken(line, token, sizeof(token), &ttype); if (!strcmp(token, "{")) { line = VMT_ParseBlock(fname, st, line); } plugfuncs->Free(file); return !!line; } return false; } static qboolean Shader_LoadVMT(parsestate_t *ps, const char *filename, void (*LoadMaterialString)(parsestate_t *ps, const char *script)) { vmtstate_t st; memset(&st, 0, sizeof(st)); st.savefile = NULL;//ps->saveshaderbody; if (!VMT_ReadVMT(filename, &st)) { if (st.sourcefile) plugfuncs->Free(st.sourcefile); return false; } Shader_GenerateFromVMT(ps, &st, filename, LoadMaterialString); return true; } static struct sbuiltin_s vmtprograms[] = { //we don't know what renderer the engine will need... #ifdef FTEPLUGIN #ifndef GLQUAKE #define GLQUAKE #endif #ifndef VKQUAKE #define VKQUAKE #endif #ifndef D3DQUAKE #define D3DQUAKE #endif #endif #include "mat_vmt_progs.h" {QR_NONE} }; static plugmaterialloaderfuncs_t vmtfuncs = { "HL2 VMT", Shader_LoadVMT, vmtprograms, }; qboolean VMT_Init(void) { fsfuncs = plugfuncs->GetEngineInterface(plugfsfuncs_name, sizeof(*fsfuncs)); if (!fsfuncs) return false; return plugfuncs->ExportInterface(plugmaterialloaderfuncs_name, &vmtfuncs, sizeof(vmtfuncs)); }