/* Copyright (C) 2002-2003 Victor Luchits This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // r_shader.c - based on code by Stephen C. Taylor // Ported to FTE from qfusion, there are numerous changes since then. #include "quakedef.h" #ifndef SERVERONLY #include "glquake.h" #include "shader.h" #include "hash.h" #include extern texid_t missing_texture; texid_t r_whiteimage, r_blackimage; qboolean shader_reload_needed; static qboolean shader_rescan_needed; static char **saveshaderbody; sh_config_t sh_config; //cvars that affect shader generation cvar_t r_vertexlight = CVARFD("r_vertexlight", "0", CVAR_SHADERSYSTEM, "Hack loaded shaders to remove detail pass and lightmap sampling for faster rendering."); cvar_t r_forceprogramify = CVARAFD("r_forceprogramify", "0", "dpcompat_makeshitup", CVAR_SHADERSYSTEM, "Reduce the shader to a single texture, and then make stuff up about its mother. The resulting fist fight results in more colour when you shine a light upon its face.\nSet to 2 to ignore 'depthfunc equal' and 'tcmod scale' in order to tolerate bizzare shaders made for a bizzare engine.\nBecause most shaders made for DP are by people who _clearly_ have no idea what the heck they're doing, you'll typically need the '2' setting."); cvar_t dpcompat_nopremulpics = CVARFD("dpcompat_nopremulpics", "0", CVAR_SHADERSYSTEM, "By default FTE uses premultiplied alpha for hud/2d images, while DP does not (which results in halos with low-res content). Unfortunately DDS files would need to be recompressed, resulting in visible issues."); extern cvar_t r_glsl_offsetmapping_reliefmapping; extern cvar_t r_fastturb, r_fastsky, r_skyboxname; extern cvar_t r_drawflat; extern cvar_t r_shaderblobs; extern cvar_t r_tessellation; extern cvar_t gl_compress; //backend fills this in to say the max fixed-function pass count (often 1, where its emulated by us, because we're too lazy). int be_maxpasses; #define Q_stricmp stricmp #define Q_strnicmp strnicmp #define clamp(v,min, max) (v) = (((v)<(min))?(min):(((v)>(max))?(max):(v))); typedef union { float f; unsigned int i; } float_int_t; qbyte FloatToByte( float x ) { static float_int_t f2i; // shift float to have 8bit fraction at base of number f2i.f = x + 32768.0f; // then read as integer and kill float bits... return (qbyte) min(f2i.i & 0x7FFFFF, 255); } cvar_t r_detailtextures; #define MAX_TOKEN_CHARS sizeof(com_token) char *COM_ParseExt (char **data_p, qboolean nl, qboolean comma) { int c; int len; char *data; COM_AssertMainThread("COM_ParseExt"); data = *data_p; len = 0; com_token[0] = 0; if (!data) { *data_p = NULL; return ""; } // skip whitespace skipwhite: while ((c = (unsigned char)*data) <= ' ') { if (c == 0) { *data_p = NULL; return ""; } if (c == '\n' && !nl) { *data_p = data; return com_token; } data++; } // skip // comments if (c == '/' && data[1] == '/') { while (*data && *data != '\n') data++; goto skipwhite; } // skip /* comments if (c == '/' && data[1] == '*') { char *start = data; data+=2; for(;data[0];) { if (data[0] == '*' && data[1] == '/') { data+=2; break; } if (*data == '\n' && !nl) { *data_p = start; return com_token; } data++; } goto skipwhite; } // handle quoted strings specially if (c == '\"') { data++; while (1) { c = *data++; if (c=='\"' || !c) { com_token[len] = 0; *data_p = data; return com_token; } if (len < MAX_TOKEN_CHARS) { com_token[len] = c; len++; } } } // parse a regular word do { if (len < MAX_TOKEN_CHARS) { com_token[len] = c; len++; } data++; c = *data; if (c == ',' && len && comma) break; } while (c>32); if (len == MAX_TOKEN_CHARS) { Con_DPrintf ("Token exceeded %i chars, discarded.\n", (int)MAX_TOKEN_CHARS); len = 0; } com_token[len] = 0; *data_p = data; return com_token; } static float Com_FloatArgument(const char *shadername, char *arg, size_t arglen) { const char *var; //grab an argument instead, otherwise 0 var = shadername; while((var = strchr(var, '#'))) { var++; if (!strnicmp(var, arg, arglen)) { if (var[arglen] == '=') return strtod(var+arglen+1, NULL); if (var[arglen] == '#' || !var[arglen]) return 1; //present, but no value } var++; } return 0; //not present. } #define Shader_FloatArgument(s,k) (Com_FloatArgument(s->name,k,strlen(k))) #define HASH_SIZE 128 #define SPF_DEFAULT 0u /*quake3/fte internal*/ #define SPF_PROGRAMIFY (1u<<0) /*quake3/fte internal*/ #define SPF_DOOM3 (1u<<1) /*any commands, args, etc, should be interpretted according to doom3's norms*/ typedef struct { shader_t *s; //the shader we're parsing shaderpass_t *pass; //the pass we're currently parsing char *ptr; //the src file pointer we're at const char *forcedshader; unsigned int parseflags; //SPF_* qboolean droppass; //for dpwater compat, used to generate a program int dpwatertype; float reflectmin; float reflectmax; float reflectfactor; float refractfactor; vec3_t refractcolour; vec3_t reflectcolour; float wateralpha; //FIXME: rtlights can't respond to these int offsetmappingmode; float offsetmappingscale; float offsetmappingbias; float specularexpscale; //*32 ish float specularvalscale; //*1 ish } parsestate_t; static parsestate_t parsestate; //FIXME typedef struct shaderkey_s { char *keyword; void (*func)( shader_t *shader, shaderpass_t *pass, char **ptr ); char *prefix; } shaderkey_t; typedef struct shadercachefile_s { char *data; size_t length; unsigned int parseflags; char forcedshadername[64]; struct shadercachefile_s *next; char name[1]; } shadercachefile_t; typedef struct shadercache_s { shadercachefile_t *source; size_t offset; struct shadercache_s *hash_next; char name[1]; } shadercache_t; static shadercachefile_t *shaderfiles; //contents of a .shader file static shadercache_t **shader_hash; //locations of known inactive shaders. unsigned int r_numshaders; //number of active slots in r_shaders array. unsigned int r_maxshaders; //max length of r_shaders array. resized if exceeded. shader_t **r_shaders; //list of active shaders for a id->shader lookup static hashtable_t shader_active_hash; //list of active shaders for a name->shader lookup void *shader_active_hash_mem; //static char r_skyboxname[MAX_QPATH]; //static float r_skyheight; char *Shader_Skip( char *ptr ); static qboolean Shader_Parsetok(parsestate_t *ps, shaderkey_t *keys, char *token); static void Shader_ParseFunc(shader_t *shader, char **args, shaderfunc_t *func); static void Shader_MakeCache(const char *path, unsigned int parseflags); static qboolean Shader_LocateSource(char *name, char **buf, size_t *bufsize, size_t *offset, shadercachefile_t **sourcefile); static void Shader_ReadShader(shader_t *s, char *shadersource, shadercachefile_t *sourcefile); static qboolean Shader_ParseShader(char *parsename, shader_t *s); //=========================================================================== static qboolean Shader_EvaluateCondition(shader_t *shader, char **ptr) { char *token; cvar_t *cv; float lhs; qboolean conditiontrue = true; token = COM_ParseExt(ptr, false, false); if (*token == '!') { conditiontrue = false; token++; if (!*token) token = COM_ParseExt(ptr, false, false); } if (*token >= '0' && *token <= '9') lhs = strtod(token, NULL); else { if (*token == '$') token++; if (*token == '#') lhs = !!Shader_FloatArgument(shader, token+1); else if (!Q_stricmp(token, "lpp")) lhs = r_lightprepass; else if (!Q_stricmp(token, "lightmap")) lhs = !r_fullbright.value; else if (!Q_stricmp(token, "deluxmap") || !Q_stricmp(token, "deluxe")) lhs = r_deluxemapping; else if (!Q_stricmp(token, "softwarebanding")) lhs = r_softwarebanding; //normalmaps are generated if they're not already known. else if (!Q_stricmp(token, "normalmap")) lhs = r_loadbumpmapping; else if (!Q_stricmp(token, "vulkan")) lhs = (qrenderer == QR_VULKAN); else if (!Q_stricmp(token, "opengl")) lhs = (qrenderer == QR_OPENGL); else if (!Q_stricmp(token, "d3d8")) lhs = (qrenderer == QR_DIRECT3D8); else if (!Q_stricmp(token, "d3d9")) lhs = (qrenderer == QR_DIRECT3D9); else if (!Q_stricmp(token, "d3d11")) lhs = (qrenderer == QR_DIRECT3D11); else if (!Q_stricmp(token, "gles")) { #ifdef GLQUAKE lhs = ((qrenderer == QR_OPENGL) && gl_config.gles); #else lhs = false; #endif } else if (!Q_stricmp(token, "nofixed")) lhs = sh_config.progs_required; else if (!Q_stricmp(token, "glsl")) lhs = ((qrenderer == QR_OPENGL) && sh_config.progs_supported); else if (!Q_stricmp(token, "hlsl")) lhs = ((qrenderer == QR_DIRECT3D9 || qrenderer == QR_DIRECT3D11) && sh_config.progs_supported); else if (!Q_stricmp(token, "haveprogram")) lhs = !!shader->prog; else if (!Q_stricmp(token, "programs")) lhs = sh_config.progs_supported; else if (!Q_stricmp(token, "diffuse")) lhs = true; else if (!Q_stricmp(token, "specular")) lhs = false; else if (!Q_stricmp(token, "fullbright")) lhs = false; else if (!Q_stricmp(token, "topoverlay")) lhs = false; else if (!Q_stricmp(token, "loweroverlay")) lhs = false; //these are for compat/documentation purposes with qfusion/warsow else if (!Q_stricmp(token, "maxTextureSize")) lhs = sh_config.texture2d_maxsize; else if (!Q_stricmp(token, "maxTextureCubemapSize")) lhs = sh_config.texturecube_maxsize; else if (!Q_stricmp(token, "maxTextureUnits")) lhs = 0; else if (!Q_stricmp(token, "textureCubeMap")) lhs = sh_config.havecubemaps; // else if (!Q_stricmp(token, "GLSL")) // lhs = 1; else if (!Q_stricmp(token, "deluxeMaps") || !Q_stricmp(token, "deluxe")) lhs = r_deluxemapping; else if (!Q_stricmp(token, "portalMaps")) lhs = false; //end qfusion else { cv = Cvar_Get(token, "", 0, "Shader Conditions"); if (cv) { cv->flags |= CVAR_SHADERSYSTEM; lhs = cv->value; } else { Con_Printf("Shader_EvaluateCondition: '%s' is not a cvar\n", token); lhs = 0; } } } if (*token) token = COM_ParseExt(ptr, false, false); if (*token) { float rhs; char cmp[4]; memcpy(cmp, token, 4); token = COM_ParseExt(ptr, false, false); rhs = atof(token); if (!strcmp(cmp, "!=")) conditiontrue = lhs != rhs; else if (!strcmp(cmp, "==")) conditiontrue = lhs == rhs; else if (!strcmp(cmp, "<")) conditiontrue = lhs < rhs; else if (!strcmp(cmp, "<=")) conditiontrue = lhs <= rhs; else if (!strcmp(cmp, ">")) conditiontrue = lhs > rhs; else if (!strcmp(cmp, ">=")) conditiontrue = lhs >= rhs; else conditiontrue = false; } else { conditiontrue = conditiontrue == !!lhs; } if (*token) token = COM_ParseExt(ptr, false, false); if (!strcmp(token, "&&")) return Shader_EvaluateCondition(shader, ptr) && conditiontrue; if (!strcmp(token, "||")) return Shader_EvaluateCondition(shader, ptr) || conditiontrue; return conditiontrue; } static char *Shader_ParseExactString(char **ptr) { char *token; if (!ptr || !(*ptr)) return ""; if (!**ptr || **ptr == '}') return ""; token = COM_ParseExt(ptr, false, false); return token; } static char *Shader_ParseString(char **ptr) { char *token; if (!ptr || !(*ptr)) return ""; while(**ptr == ' ' || **ptr == '\t') *ptr+=1; if (!**ptr || **ptr == '}') return ""; token = COM_ParseExt(ptr, false, true); Q_strlwr ( token ); return token; } static char *Shader_ParseSensString(char **ptr) { char *token; if (!ptr || !(*ptr)) return ""; if (!**ptr || **ptr == '}') return ""; token = COM_ParseExt(ptr, false, true); return token; } static float Shader_ParseFloat(shader_t *shader, char **ptr, float defaultval) { char *token; if (!ptr || !(*ptr)) return defaultval; if (!**ptr || **ptr == '}') return defaultval; token = COM_ParseExt(ptr, false, true); if (*token == '$') { if (token[1] == '#') { return Shader_FloatArgument(shader, token+2); } else { cvar_t *var; var = Cvar_FindVar(token+1); if (var) { if (*var->string) return var->value; else return defaultval; } } } if (!*token) return defaultval; return atof(token); } static void Shader_ParseVector(shader_t *shader, char **ptr, vec3_t v) { char *scratch; char *token; qboolean bracket; qboolean fromcvar = false; token = Shader_ParseString(ptr); if (*token == '$') { cvar_t *var; var = Cvar_FindVar(token+1); if (!var) { v[0] = 1; v[1] = 1; v[2] = 1; return; } var->flags |= CVAR_SHADERSYSTEM; ptr = &scratch; scratch = var->string; token = Shader_ParseString( ptr); fromcvar = true; } if (!Q_stricmp (token, "(")) { bracket = true; token = Shader_ParseString(ptr); } else if (token[0] == '(') { bracket = true; token = &token[1]; } else bracket = false; v[0] = atof ( token ); token = Shader_ParseString ( ptr ); if ( !token[0] ) { v[1] = fromcvar?v[0]:0; } else if (bracket && token[strlen(token)-1] == ')' ) { bracket = false; token[strlen(token)-1] = 0; v[1] = atof ( token ); } else { v[1] = atof ( token ); } token = Shader_ParseString ( ptr ); if ( !token[0] ) { v[2] = fromcvar?v[1]:0; } else if (bracket && token[strlen(token)-1] == ')' ) { token[strlen(token)-1] = 0; v[2] = atof ( token ); } else { v[2] = atof ( token ); if ( bracket ) { Shader_ParseString ( ptr ); } } /* if (v[0] > 5 || v[1] > 5 || v[2] > 5) { VectorScale(v, 1.0f/255, v); } */ } qboolean Shader_ParseSkySides (char *shadername, char *texturename, texid_t *images) { //FIXME: use Image_LoadCubemapTextureData to load the faces //if possible directly use a 7th/cubemap texture instead //this requires fixing the sky code to not do the random transforms thing though. qboolean allokay = true; int i, ss, sp; char path[MAX_QPATH]; static char *skyname_suffix[][6] = { {"rt", "bk", "lf", "ft", "up", "dn"}, // {"px", "py", "nx", "ny", "pz", "nz"}, // {"posx", "posy", "negx", "negy", "posz", "negz"}, // {"_px", "_py", "_nx", "_ny", "_pz", "_nz"}, // {"_posx", "_posy", "_negx", "_negy", "_posz", "_negz"}, {"_rt", "_bk", "_lf", "_ft", "_up", "_dn"} }; static char *skyname_pattern[] = { "%s_%s", "%s%s", "env/%s%s", "gfx/env/%s%s" }; if (*texturename == '$') { cvar_t *v; v = Cvar_FindVar(texturename+1); if (v) texturename = v->string; } if (!*texturename) texturename = "-"; for ( i = 0; i < 6; i++ ) { if ( texturename[0] == '-' ) { images[i] = r_nulltex; allokay = true; } else { for (sp = 0; sp < sizeof(skyname_pattern)/sizeof(skyname_pattern[0]); sp++) { for (ss = 0; ss < sizeof(skyname_suffix)/sizeof(skyname_suffix[0]); ss++) { Q_snprintfz ( path, sizeof(path), skyname_pattern[sp], texturename, skyname_suffix[ss][i] ); images[i] = R_LoadHiResTexture ( path, NULL, IF_NOALPHA|IF_CLAMP|IF_LOADNOW); if (images[i]->width) break; } if (images[i]->width) break; } if (!images[i]->width) { Con_Printf("Sky \"%s\" missing texture: %s\n", shadername, path); images[i] = missing_texture; allokay = false; } } } return allokay; } static void Shader_ParseFunc (shader_t *shader, char **ptr, shaderfunc_t *func) { char *token; token = Shader_ParseString (ptr); if (!Q_stricmp (token, "sin")) func->type = SHADER_FUNC_SIN; else if (!Q_stricmp (token, "triangle")) func->type = SHADER_FUNC_TRIANGLE; else if (!Q_stricmp (token, "square")) func->type = SHADER_FUNC_SQUARE; else if (!Q_stricmp (token, "sawtooth")) func->type = SHADER_FUNC_SAWTOOTH; else if (!Q_stricmp (token, "inversesawtooth")) func->type = SHADER_FUNC_INVERSESAWTOOTH; else if (!Q_stricmp (token, "noise")) func->type = SHADER_FUNC_NOISE; else if (!Q_stricmp (token, "distanceramp")) //QFusion { func->type = SHADER_FUNC_CONSTANT; //not supported... Shader_ParseFloat (shader, ptr, 0); Shader_ParseFloat (shader, ptr, 0); Shader_ParseFloat (shader, ptr, 0); Shader_ParseFloat (shader, ptr, 0); Vector4Set(func->args, 255, 255, 255, 255); return; } func->args[0] = Shader_ParseFloat (shader, ptr, 0); func->args[1] = Shader_ParseFloat (shader, ptr, 0); func->args[2] = Shader_ParseFloat (shader, ptr, 0); func->args[3] = Shader_ParseFloat (shader, ptr, 0); } //=========================================================================== static int Shader_SetImageFlags(shader_t *shader, shaderpass_t *pass, char **name) { //fixme: pass flags should be handled elsewhere. int flags = 0; for(;name;) { if (!Q_strnicmp(*name, "$rt:", 4)) { *name += 4; flags |= IF_NOMIPMAP|IF_CLAMP|IF_RENDERTARGET; if (!(flags & (IF_NEAREST|IF_LINEAR))) { flags |= IF_LINEAR; if (pass) pass->flags |= SHADER_PASS_LINEAR; } } else if (!Q_strnicmp(*name, "$clamp:", 7)) { *name += 7; flags |= IF_CLAMP; } else if (!Q_strnicmp(*name, "$3d:", 4)) { *name+=4; flags = (flags&~IF_TEXTYPE) | IF_3DMAP; } else if (!Q_strnicmp(*name, "$cube:", 6)) { *name+=6; flags = (flags&~IF_TEXTYPE) | IF_CUBEMAP; } else if (!Q_strnicmp(*name, "$nearest:", 9)) { *name+=9; flags &= ~IF_LINEAR; flags |= IF_NEAREST; if (pass) { pass->flags &= ~SHADER_PASS_LINEAR; pass->flags |= SHADER_PASS_NEAREST; } } else if (!Q_strnicmp(*name, "$linear:", 8)) { *name+=8; flags &= ~IF_NEAREST; flags |= IF_LINEAR; if (pass) { pass->flags &= ~SHADER_PASS_NEAREST; pass->flags |= SHADER_PASS_LINEAR; } } else break; } // if (shader->flags & SHADER_SKY) // flags |= IF_SKY; if (shader->flags & SHADER_NOMIPMAPS) flags |= IF_NOMIPMAP; if (shader->flags & SHADER_NOPICMIP) flags |= IF_NOPICMIP; flags |= IF_MIPCAP; return flags; } texid_t R_LoadColourmapImage(void) { //FIXME: cache the result, because this is abusive unsigned int w = 256, h = VID_GRADES; unsigned int x; unsigned int data[256*(VID_GRADES)]; qbyte *colourmappal = (qbyte *)FS_LoadMallocFile ("gfx/colormap.lmp", NULL); #if defined(Q2CLIENT) && defined(IMAGEFMT_PCX) if (!colourmappal) { size_t sz; qbyte *pcx = FS_LoadMallocFile("pics/colormap.pcx", &sz); if (pcx) { colourmappal = Z_Malloc(256*VID_GRADES); ReadPCXData(pcx, sz, 256, VID_GRADES, colourmappal); BZ_Free(pcx); } } #endif if (colourmappal) { for (x = 0; x < sizeof(data)/sizeof(data[0]); x++) data[x] = d_8to24rgbtable[colourmappal[x]]; } else { //erk //fixme: generate a proper colourmap for (x = 0; x < sizeof(data)/sizeof(data[0]); x++) { int r, g, b; float l = 1.0-((x/256)/(float)VID_GRADES); r = d_8to24rgbtable[x & 0xff]; g = (r>>16)&0xff; b = (r>>8)&0xff; r = (r>>0)&0xff; data[x] = d_8to24rgbtable[GetPaletteIndex(r*l,g*l,b*l)]; } } BZ_Free(colourmappal); return R_LoadTexture("$colourmap", w, h, TF_RGBA32, data, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA|IF_CLAMP); } static texid_t Shader_FindImage ( char *name, int flags ) { extern texid_t missing_texture_normal; if (parsestate.parseflags & SPF_DOOM3) { if (!Q_stricmp (name, "_default")) return r_whiteimage; /*fixme*/ if (!Q_stricmp (name, "_white")) return r_whiteimage; if (!Q_stricmp (name, "_black")) { int wibuf[16] = {0}; return R_LoadTexture("$blackimage", 4, 4, TF_RGBA32, wibuf, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA); } } else { if (!Q_stricmp (name, "$whiteimage")) return r_whiteimage; if (!Q_stricmp (name, "$blackimage")) { int wibuf[16] = {0}; return R_LoadTexture("$blackimage", 4, 4, TF_RGBA32, wibuf, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA); } if (!Q_stricmp (name, "$identitynormal")) return missing_texture_normal; if (!Q_stricmp (name, "$colourmap")) return R_LoadColourmapImage(); } if (flags & IF_RENDERTARGET) return R2D_RT_Configure(name, 0, 0, TF_INVALID, flags); return R_LoadHiResTexture(name, NULL, flags); } /****************** shader keyword functions ************************/ static void Shader_Cull ( shader_t *shader, shaderpass_t *pass, char **ptr ) { char *token; shader->flags &= ~(SHADER_CULL_FRONT|SHADER_CULL_BACK); token = Shader_ParseString ( ptr ); if ( !Q_stricmp (token, "disable") || !Q_stricmp (token, "none") || !Q_stricmp (token, "twosided") ) { } else if ( !Q_stricmp (token, "front") ) { shader->flags |= SHADER_CULL_FRONT; } else if ( !Q_stricmp (token, "back") || !Q_stricmp (token, "backside") || !Q_stricmp (token, "backsided") ) { shader->flags |= SHADER_CULL_BACK; } else { shader->flags |= SHADER_CULL_FRONT; } } static void Shader_NoMipMaps ( shader_t *shader, shaderpass_t *pass, char **ptr ) { shader->flags |= (SHADER_NOMIPMAPS|SHADER_NOPICMIP); } static void Shader_Affine ( shader_t *shader, shaderpass_t *pass, char **ptr ) { shader->flags |= SBITS_AFFINE; } static void Shader_NoPicMip ( shader_t *shader, shaderpass_t *pass, char **ptr ) { shader->flags |= SHADER_NOPICMIP; } static void Shader_DeformVertexes ( shader_t *shader, shaderpass_t *pass, char **ptr ) { char *token; deformv_t *deformv; if ( shader->numdeforms >= SHADER_DEFORM_MAX ) return; deformv = &shader->deforms[shader->numdeforms]; shader->flags |= SHADER_NOMARKS; //just in case... token = Shader_ParseString ( ptr ); if ( !Q_stricmp (token, "wave") ) { deformv->type = DEFORMV_WAVE; deformv->args[0] = Shader_ParseFloat (shader, ptr, 0); if (deformv->args[0]) deformv->args[0] = 1.0f / deformv->args[0]; Shader_ParseFunc (shader, ptr, &deformv->func ); } else if ( !Q_stricmp (token, "normal") ) { deformv->type = DEFORMV_NORMAL; deformv->args[0] = Shader_ParseFloat (shader, ptr, 0); deformv->args[1] = Shader_ParseFloat (shader, ptr, 0); } else if ( !Q_stricmp (token, "bulge") ) { deformv->type = DEFORMV_BULGE; Shader_ParseVector (shader, ptr, deformv->args); } else if ( !Q_stricmp (token, "move") ) { deformv->type = DEFORMV_MOVE; Shader_ParseVector (shader, ptr, deformv->args ); Shader_ParseFunc (shader, ptr, &deformv->func ); } else if ( !Q_stricmp (token, "autosprite") ) { deformv->type = DEFORMV_AUTOSPRITE; } else if ( !Q_stricmp (token, "autosprite2") ) { deformv->type = DEFORMV_AUTOSPRITE2; } else if ( !Q_stricmp (token, "projectionShadow") ) deformv->type = DEFORMV_PROJECTION_SHADOW; else if ( !Q_strnicmp (token, "text", 4) ) { deformv->type = DEFORMV_TEXT; deformv->args[0] = atoi(token+4); } else return; shader->numdeforms++; } static void Shader_ClutterParms(shader_t *shader, shaderpass_t *pass, char **ptr) { struct shader_clutter_s *clut; char *modelname; modelname = Shader_ParseString(ptr); clut = Z_Malloc(sizeof(*clut) + strlen(modelname)); strcpy(clut->modelname, modelname); clut->spacing = Shader_ParseFloat(shader, ptr, 1000); clut->scalemin = Shader_ParseFloat(shader, ptr, 1); clut->scalemax = Shader_ParseFloat(shader, ptr, 1); clut->zofs = Shader_ParseFloat(shader, ptr, 0); clut->anglemin = Shader_ParseFloat(shader, ptr, 0) * M_PI * 2 / 360.; clut->anglemax = Shader_ParseFloat(shader, ptr, 360) * M_PI * 2 / 360.; clut->next = shader->clutter; shader->clutter = clut; } static void Shader_SkyParms(shader_t *shader, shaderpass_t *pass, char **ptr) { skydome_t *skydome; // float skyheight; char *boxname; if (shader->skydome) { Z_Free(shader->skydome); } skydome = (skydome_t *)Z_Malloc(sizeof(skydome_t)); shader->skydome = skydome; boxname = Shader_ParseString(ptr); Shader_ParseSkySides(shader->name, boxname, skydome->farbox_textures); /*skyheight =*/ Shader_ParseFloat(shader, ptr, 512); boxname = Shader_ParseString(ptr); // Shader_ParseSkySides(shader->name, boxname, skydome->nearbox_textures); shader->flags |= SHADER_SKY; shader->sort = SHADER_SORT_SKY; } static void Shader_FogParms ( shader_t *shader, shaderpass_t *pass, char **ptr ) { float div; vec3_t color, fcolor; // if ( !r_ignorehwgamma->value ) // div = 1.0f / pow(2, max(0, floor(r_overbrightbits->value))); // else div = 1.0f; Shader_ParseVector (shader, ptr, color ); VectorScale ( color, div, color ); ColorNormalize ( color, fcolor ); shader->fog_color[0] = FloatToByte ( fcolor[0] ); shader->fog_color[1] = FloatToByte ( fcolor[1] ); shader->fog_color[2] = FloatToByte ( fcolor[2] ); shader->fog_color[3] = 255; shader->fog_dist = Shader_ParseFloat (shader, ptr, 128); if ( shader->fog_dist <= 0.0f ) { shader->fog_dist = 128.0f; } shader->fog_dist = 1.0f / shader->fog_dist; shader->flags |= SHADER_NODLIGHT|SHADER_NOSHADOWS; } static void Shader_SurfaceParm ( shader_t *shader, shaderpass_t *pass, char **ptr ) { char *token; token = Shader_ParseString ( ptr ); if ( !Q_stricmp( token, "nodraw" ) ) shader->flags |= SHADER_NODRAW; else if ( !Q_stricmp( token, "nodraw2" ) ) shader->flags |= SHADER_NODRAW; //an alternative so that q3map2 won't see+strip it. else if ( !Q_stricmp( token, "nodlight" ) ) shader->flags |= SHADER_NODLIGHT; else if ( !Q_stricmp( token, "noshadows" ) ) shader->flags |= SHADER_NOSHADOWS; else if ( !Q_stricmp( token, "sky" ) ) shader->flags |= SHADER_SKY; else if ( !Q_stricmp( token, "noimpact" ) ) shader->flags |= SHADER_NOMARKS; //wrong, but whatever. else if ( !Q_stricmp( token, "nomarks" ) ) shader->flags |= SHADER_NOMARKS; //forceshader type things inherit certain textures from the original material //however, that original material might not need those textures and thus won't have them loaded, which breaks replacement. //these provide a way to override that. else if (!Q_stricmp( token, "hasdiffuse")) shader->flags |= SHADER_HASDIFFUSE; else if (!Q_stricmp( token, "hasnormalmap")) shader->flags |= SHADER_HASNORMALMAP; else if (!Q_stricmp( token, "hasgloss")) shader->flags |= SHADER_HASGLOSS; else if (!Q_stricmp( token, "hasfullbright")) shader->flags |= SHADER_HASFULLBRIGHT; else if (!Q_stricmp( token, "haspaletted")) shader->flags |= SHADER_HASPALETTED; else if (!Q_stricmp(token, "hastop") || !Q_stricmp(token, "hasbottom") || !Q_stricmp(token, "hastopbottom")) shader->flags |= SHADER_HASTOPBOTTOM; } static void Shader_Sort ( shader_t *shader, shaderpass_t *pass, char **ptr ) { char *token; token = Shader_ParseString ( ptr ); if ( !Q_stricmp( token, "portal" ) ) shader->sort = SHADER_SORT_PORTAL; else if( !Q_stricmp( token, "sky" ) ) shader->sort = SHADER_SORT_SKY; else if( !Q_stricmp( token, "opaque" ) ) shader->sort = SHADER_SORT_OPAQUE; else if( !Q_stricmp( token, "decal" ) || !Q_stricmp( token, "litdecal" ) ) shader->sort = SHADER_SORT_DECAL; else if( !Q_stricmp( token, "seethrough" ) ) shader->sort = SHADER_SORT_SEETHROUGH; else if( !Q_stricmp( token, "unlitdecal" ) ) shader->sort = SHADER_SORT_UNLITDECAL; else if( !Q_stricmp( token, "banner" ) ) shader->sort = SHADER_SORT_BANNER; else if( !Q_stricmp( token, "additive" ) ) shader->sort = SHADER_SORT_ADDITIVE; else if( !Q_stricmp( token, "underwater" ) ) shader->sort = SHADER_SORT_UNDERWATER; else if( !Q_stricmp( token, "nearest" ) ) shader->sort = SHADER_SORT_NEAREST; else if( !Q_stricmp( token, "blend" ) ) shader->sort = SHADER_SORT_BLEND; else if ( !Q_stricmp( token, "deferredlight" ) ) shader->sort = SHADER_SORT_DEFERREDLIGHT; else if ( !Q_stricmp( token, "ripple" ) ) shader->sort = SHADER_SORT_RIPPLE; else { shader->sort = atoi ( token ); clamp ( shader->sort, SHADER_SORT_NONE, SHADER_SORT_NEAREST ); } } static void Shader_Deferredlight ( shader_t *shader, shaderpass_t *pass, char **ptr ) { shader->sort = SHADER_SORT_DEFERREDLIGHT; } static void Shader_Portal ( shader_t *shader, shaderpass_t *pass, char **ptr ) { shader->sort = SHADER_SORT_PORTAL; } static void Shader_PolygonOffset ( shader_t *shader, shaderpass_t *pass, char **ptr ) { /*the q3 defaults*/ shader->polyoffset.factor = -0.05; shader->polyoffset.unit = -25; shader->flags |= SHADER_POLYGONOFFSET; //some backends might be lazy and only allow simple values. } static void Shader_EntityMergable ( shader_t *shader, shaderpass_t *pass, char **ptr ) { shader->flags |= SHADER_ENTITY_MERGABLE; } #if defined(GLQUAKE) || defined(D3DQUAKE) static qboolean Shader_ParseProgramCvar(char *script, cvar_t **cvarrefs, char **cvarnames, int *cvartypes, int cvartype) { char body[MAX_QPATH]; char *out; char *namestart; while (*script == ' ' || *script == '\t') script++; namestart = script; while ((*script >= 'A' && *script <= 'Z') || (*script >= 'a' && *script <= 'z') || (*script >= '0' && *script <= '9') || *script == '_') script++; cvartypes[0] = cvartype; cvarnames[0] = Z_Malloc(script - namestart + 1); memcpy(cvarnames[0], namestart, script - namestart); cvarnames[0][script - namestart] = 0; while (*script == ' ' || *script == '\t') script++; if (*script == '=') { script++; while (*script == ' ' || *script == '\t') script++; out = body; while (out < com_token+countof(body)-1 && *script != '\n' && !(script[0] == '/' && script[1] == '/')) *out++ = *script++; *out++ = 0; cvarrefs[0] = Cvar_Get(cvarnames[0], body, 0, "GLSL Variables"); } else cvarrefs[0] = Cvar_Get(cvarnames[0], "", 0, "GLSL Variables"); return true; } #endif const struct sh_defaultsamplers_s sh_defaultsamplers[] = { {"s_shadowmap", 1u<<0}, {"s_projectionmap", 1u<<1}, {"s_diffuse", 1u<<2}, {"s_normalmap", 1u<<3}, {"s_specular", 1u<<4}, {"s_upper", 1u<<5}, {"s_lower", 1u<<6}, {"s_fullbright", 1u<<7}, {"s_paletted", 1u<<8}, {"s_reflectcube", 1u<<9}, {"s_reflectmask", 1u<<10}, {"s_displacement", 1u<<11}, {"s_lightmap", 1u<<12}, {"s_deluxemap", 1u<<13}, #if MAXRLIGHTMAPS > 1 {"s_lightmap1", 1u<<14}, {"s_lightmap2", 1u<<15}, {"s_lightmap3", 1u<<16}, {"s_deluxemap1", 1u<<17}, {"s_deluxemap2", 1u<<18}, {"s_deluxemap3", 1u<<19}, #else {"s_lightmap1", 0}, {"s_lightmap2", 0}, {"s_lightmap3", 0}, {"s_deluxemap1", 0}, {"s_deluxemap2", 0}, {"s_deluxemap3", 0}, #endif {NULL} }; static struct { char *name; unsigned int bitmask; } permutations[] = { {"BUMP", PERMUTATION_BUMPMAP}, {"FULLBRIGHT", PERMUTATION_FULLBRIGHT}, {"UPPERLOWER", PERMUTATION_UPPERLOWER}, {"REFLECTCUBEMASK", PERMUTATION_REFLECTCUBEMASK}, {"SKELETAL", PERMUTATION_SKELETAL}, {"FOG", PERMUTATION_FOG}, {"FRAMEBLEND", PERMUTATION_FRAMEBLEND}, {"LIGHTSTYLED", PERMUTATION_LIGHTSTYLES} }; #define MAXMODIFIERS 64 void VARGS Q_strlcatfz (char *dest, size_t *offset, size_t size, const char *fmt, ...) LIKEPRINTF(4); 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); } struct programpermu_s *Shader_LoadPermutation(program_t *prog, unsigned int p) { const char *permutationdefines[3]; struct programpermu_s *pp; size_t n, pn = 0; char defines[8192]; size_t offset; extern cvar_t gl_specular, gl_specular_power; if (~prog->supportedpermutations & p) return NULL; //o.O pp = Z_Malloc(sizeof(*pp)); pp->permutation = p; *defines = 0; offset = 0; if (p & PERMUTATION_SKELETAL) Q_strlcatfz(defines, &offset, sizeof(defines), "#define MAX_GPU_BONES %i\n", sh_config.max_gpu_bones); if (gl_specular.value) Q_strlcatfz(defines, &offset, sizeof(defines), "#define SPECULAR\n#define SPECULAR_BASE_MUL %f\n#define SPECULAR_BASE_POW %f\n", 1.0*gl_specular.value, max(1,gl_specular_power.value)); for (n = 0; n < countof(permutations); n++) { if (p & permutations[n].bitmask) Q_strlcatfz(defines, &offset, sizeof(defines), "#define %s\n", permutations[n].name); } if (p & PERMUTATION_UPPERLOWER) Q_strlcatfz(defines, &offset, sizeof(defines), "#define UPPER\n#define LOWER\n"); if (p & PERMUTATION_BUMPMAP) { if (r_glsl_offsetmapping.ival) { Q_strlcatfz(defines, &offset, sizeof(defines), "#define OFFSETMAPPING\n"); if (r_glsl_offsetmapping_reliefmapping.ival && (p & PERMUTATION_BUMPMAP)) Q_strlcatfz(defines, &offset, sizeof(defines), "#define RELIEFMAPPING\n"); } if (r_deluxemapping) //fixme: should be per-model really Q_strlcatfz(defines, &offset, sizeof(defines), "#define DELUXE\n"); } permutationdefines[pn++] = defines; permutationdefines[pn++] = prog->preshade; permutationdefines[pn++] = NULL; if (!sh_config.pCreateProgram(prog, pp, prog->shaderver, permutationdefines, prog->shadertext, prog->tess?prog->shadertext:NULL, prog->tess?prog->shadertext:NULL, prog->geom?prog->shadertext:NULL, prog->shadertext, prog->warned, NULL)) prog->warned = true; //extra loop to validate the programs actually linked properly. //delaying it like this gives certain threaded drivers a chance to compile them all while we're messing around with other junk if (sh_config.pValidateProgram && !sh_config.pValidateProgram(prog, pp, prog->warned, NULL)) prog->warned = true; if (sh_config.pProgAutoFields) { cvar_t *cvarrefs[64]; char *cvarnames[64+1]; int cvartypes[64]; unsigned char *cvardata = prog->cvardata; size_t size = prog->cvardatasize, i; for (i = 0; i < countof(cvartypes) && size; i++) { memcpy(&cvartypes[i], cvardata, sizeof(int)); cvarnames[i] = cvardata+sizeof(int); size -= sizeof(int)+strlen(cvarnames[i])+1; cvardata += sizeof(int)+strlen(cvarnames[i])+1; cvarrefs[i] = Cvar_FindVar(cvarnames[i]); } cvarnames[i] = NULL; //no more sh_config.pProgAutoFields(prog, pp, cvarrefs, cvarnames, cvartypes); } return pp; } static qboolean Shader_LoadPermutations(char *name, program_t *prog, char *script, int qrtype, int ver, char *blobfilename) { #if defined(GLQUAKE) || defined(D3DQUAKE) // const char *permutationdefines[countof(permutations) + MAXMODIFIERS + 1]; unsigned int nopermutation = PERMUTATIONS-1; // int nummodifiers = 0; int p; char *end; cvar_t *cvarrefs[64]; char *cvarnames[64]; int cvartypes[64]; size_t cvarcount = 0, i; extern cvar_t gl_specular, gl_specular_power; qboolean cantess; //not forced. char prescript[8192]; size_t offset = 0; #endif #ifdef VKQUAKE if (qrenderer == QR_VULKAN && (qrtype == QR_VULKAN || qrtype == QR_OPENGL)) { //vulkan can potentially load glsl, f it has the extensions enabled. if (qrtype == QR_VULKAN && VK_LoadBlob(prog, script, name)) return true; } else #endif if (qrenderer != qrtype) { return false; } #if defined(GLQUAKE) || defined(D3DQUAKE) ver = 0; if (!sh_config.pCreateProgram && !sh_config.pLoadBlob) return false; if (prog->name) return false; //o.O *prescript = 0; offset = 0; memset(prog->permu, 0, sizeof(prog->permu)); prog->name = Z_StrDup(name); prog->geom = false; prog->tess = false; prog->nofixedcompat = true; prog->numsamplers = 0; prog->defaulttextures = 0; for(;;) { while (*script == ' ' || *script == '\r' || *script == '\n' || *script == '\t') script++; if (!strncmp(script, "!!fixed", 7)) { prog->nofixedcompat = false; script += 7; } else if (!strncmp(script, "!!geom", 6)) { prog->geom = true; script += 6; } else if (!strncmp(script, "!!tess", 6)) { prog->tess = true; script += 6; } else if (!strncmp(script, "!!samps", 7)) { com_tokentype_t tt; qboolean ignore = false; script += 7; for(;;) { size_t len; int i; char *type, *idx, *next; char *token = com_token; next = COM_ParseTokenOut(script, "", com_token, sizeof(com_token), &tt); if (tt == TTP_LINEENDING || tt == TTP_EOF) break; script = next; if (*token == '=' || *token == '!') { len = strlen(token); if (*token == (Com_FloatArgument(name, token+1, len-1)?'!':'=')) ignore = true; continue; } else if (ignore) continue; #ifndef NOLEGACY else if (!strncmp(token, "deluxmap", 8)) { //FIXME: remove this some time. token = va("deluxemap%s",token+8); } #endif type = strchr(token, ':'); idx = strchr(token, '='); if (type || idx) { //name:type=idx if (type) *type++ = 0; else type = "sampler2D"; if (idx) { *idx++ = 0; i = atoi(idx); } else i = prog->numsamplers; if (prog->numsamplers < i+1) prog->numsamplers = i+1; //I really want to use layout(binding = %i) here, but its specific to the glsl version (which we don't really know yet) Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define s_%s s_t%u\nuniform %s s_%s;\n", token, i, type, token); } else { len = strlen(token); for (i = 0; sh_defaultsamplers[i].name; i++) { if (!strcmp(token, sh_defaultsamplers[i].name+2)) { prog->defaulttextures |= sh_defaultsamplers[i].defaulttexbits; break; } } if (!sh_defaultsamplers[i].name) { //this path is deprecated. i = atoi(token); if (i) { if (qrenderer == QR_OPENGL) { while (prog->numsamplers < i) Q_strlcatfz(prescript, &offset, sizeof(prescript), "uniform sampler2D s_t%u;\n", prog->numsamplers++); } else if (prog->numsamplers < i) prog->numsamplers = i; } else Con_Printf("Unknown texture name in %s\n", name); } } } } else if (!strncmp(script, "!!cvardf", 8) || !strncmp(script, "!!cvard3", 8) || !strncmp(script, "!!cvard4", 8) || !strncmp(script, "!!cvard_srgb", 11)) { qboolean srgb = false; float div = 1; char type = script[7]; script+=8; if (type == '_') { if (*script == 's') { srgb = true; script+=1; } if (!strncmp(script, "rgba", 4)) { type = '4'; script+=4; } else if (!strncmp(script, "rgb", 3)) { type = '3'; script+=3; } else if (!strncmp(script, "rg", 2)) { type = '2'; script+=2; } else if (!strncmp(script, "r", 1) || !strncmp(script, "f", 1)) { type = 'f'; script+=1; } if (!strncmp(script, "_b", 2)) { div = 255; script+=2; } else if (!strncmp(script, "_", 1)) div = strtod(script, &script); } while (*script == ' ' || *script == '\t') script++; end = script; while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_') end++; if (end - script < 64) { cvar_t *var; char namebuf[64]; char valuebuf[64]; memcpy(namebuf, script, end - script); namebuf[end - script] = 0; while (*end == ' ' || *end == '\t') end++; if (*end == '=') { script = ++end; while (*end && *end != '\n' && *end != '\r' && end < script+sizeof(namebuf)-1) end++; memcpy(valuebuf, script, end - script); valuebuf[end - script] = 0; } else strcpy(valuebuf, "0"); var = Cvar_Get(namebuf, valuebuf, CVAR_SHADERSYSTEM, "GLSL Variables"); if (var) { if (srgb) { if (type == '4') Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define %s %s(%g,%g,%g,%g)\n", namebuf, ((qrenderer == QR_OPENGL)?"vec4":"float4"), SRGBf(var->vec4[0]/div), SRGBf(var->vec4[1]/div), SRGBf(var->vec4[2]/div), var->vec4[3]/div); else if (type == '3') Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define %s %s(%g,%g,%g)\n", namebuf, ((qrenderer == QR_OPENGL)?"vec3":"float3"), SRGBf(var->vec4[0]/div), SRGBf(var->vec4[1]/div), SRGBf(var->vec4[2]/div)); else Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define %s %g\n", namebuf, SRGBf(var->value/div)); } else { if (type == '4') Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define %s %s(%g,%g,%g,%g)\n", namebuf, ((qrenderer == QR_OPENGL)?"vec4":"float4"), var->vec4[0]/div, var->vec4[1]/div, var->vec4[2]/div, var->vec4[3]/div); else if (type == '3') Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define %s %s(%g,%g,%g)\n", namebuf, ((qrenderer == QR_OPENGL)?"vec3":"float3"), var->vec4[0]/div, var->vec4[1]/div, var->vec4[2]/div); else Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define %s %g\n", namebuf, var->value/div); } } } script = end; } else if (!strncmp(script, "!!cvarf", 7)) { if (cvarcount != sizeof(cvarnames)/sizeof(cvarnames[0])) cvarcount += Shader_ParseProgramCvar(script+7, &cvarrefs[cvarcount], &cvarnames[cvarcount], &cvartypes[cvarcount], SP_CVARF); } else if (!strncmp(script, "!!cvari", 7)) { if (cvarcount != sizeof(cvarnames)/sizeof(cvarnames[0])) cvarcount += Shader_ParseProgramCvar(script+7, &cvarrefs[cvarcount], &cvarnames[cvarcount], &cvartypes[cvarcount], SP_CVARI); } else if (!strncmp(script, "!!cvarv", 7)) { if (cvarcount != sizeof(cvarnames)/sizeof(cvarnames[0])) cvarcount += Shader_ParseProgramCvar(script+7, &cvarrefs[cvarcount], &cvarnames[cvarcount], &cvartypes[cvarcount], SP_CVAR3F); } else if (!strncmp(script, "!!permu", 7)) { script += 7; while (*script == ' ' || *script == '\t') script++; end = script; while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_') end++; for (p = 0; p < countof(permutations); p++) { if (!strncmp(permutations[p].name, script, end - script) && permutations[p].name[end-script] == '\0') { nopermutation &= ~permutations[p].bitmask; break; } } if (p == countof(permutations)) { //we 'recognise' ones that are force-defined, despite not being actual permutations. if (end - script == 4 && !strncmp("TESS", script, 4)) cantess = true; else if (strncmp("SPECULAR", script, end - script)) if (strncmp("DELUXE", script, end - script)) if (strncmp("DELUX", script, end - script)) if (strncmp("OFFSETMAPPING", script, end - script)) if (strncmp("RELIEFMAPPING", script, end - script)) Con_DPrintf("Unknown pemutation in glsl program %s\n", name); } script = end; } else if (!strncmp(script, "!!ver", 5)) { int minver, maxver; script += 5; while (*script == ' ' || *script == '\t') script++; end = script; while ((*end >= 'A' && *end <= 'Z') || (*end >= 'a' && *end <= 'z') || (*end >= '0' && *end <= '9') || *end == '_') end++; minver = strtol(script, &script, 0); while (*script == ' ' || *script == '\t') script++; maxver = strtol(script, NULL, 0); if (!maxver) maxver = minver; ver = maxver; if (ver > sh_config.maxver) ver = sh_config.maxver; if (ver < minver) ver = minver; //some kind of error. script = end; } else if (!strncmp(script, "!!", 2)) { Con_DPrintf("Unknown directinve in glsl program %s\n", name); script += 2; while (*script == ' ' || *script == '\t') script++; } else if (!strncmp(script, "//", 2)) { script += 2; while (*script == ' ' || *script == '\t') script++; } else break; while (*script && *script != '\n') script++; }; prog->shadertext = Z_StrDup(script); if (qrenderer == qrtype && ver < 150) prog->tess = cantess = false; //GL_ARB_tessellation_shader requires glsl 150(gl3.2) (or glessl 3.1). nvidia complains about layouts if you try anyway if (!r_fog_permutation.ival) nopermutation |= PERMUTATION_BIT_FOG; if (!sh_config.max_gpu_bones) nopermutation |= PERMUTATION_SKELETAL; //multiple lightmaps is kinda hacky. if any are set, all must be. #define ALTLIGHTMAPSAMP 13 if (prog->defaulttextures & ((1u<<(ALTLIGHTMAPSAMP+0)) | (1u<<(ALTLIGHTMAPSAMP+1)) | (1u<<(ALTLIGHTMAPSAMP+2)))) prog->defaulttextures |=((1u<<(ALTLIGHTMAPSAMP+0)) | (1u<<(ALTLIGHTMAPSAMP+1)) | (1u<<(ALTLIGHTMAPSAMP+2))); #define ALTDELUXMAPSAMP 16 if (prog->defaulttextures & ((1u<<(ALTDELUXMAPSAMP+0)) | (1u<<(ALTDELUXMAPSAMP+1)) | (1u<<(ALTDELUXMAPSAMP+2)))) prog->defaulttextures |=((1u<<(ALTDELUXMAPSAMP+0)) | (1u<<(ALTDELUXMAPSAMP+1)) | (1u<<(ALTDELUXMAPSAMP+2))); for (end = strchr(name, '#'); end && *end; ) { char *start = end+1; end = strchr(start, '#'); if (!end) end = start + strlen(start); if (end-start == 7 && !Q_strncasecmp(start, "usemods", 7)) prog->nofixedcompat = false; if (end-start == 4 && !Q_strncasecmp(start, "tess", 4)) prog->tess |= cantess; Q_strlcatfz(prescript, &offset, sizeof(prescript), "#define "); while (offset < sizeof(prescript) && start < end) { if (*start == '=') { start++; prescript[offset++] = ' '; break; } prescript[offset++] = toupper(*start++); } while (offset < sizeof(prescript) && start < end) prescript[offset++] = toupper(*start++); Q_strlcatfz(prescript, &offset, sizeof(prescript), "\n"); } prog->preshade = Z_StrDup(prescript); prog->supportedpermutations = (~nopermutation) & (PERMUTATIONS-1); prog->shaderver = ver; if (cvarcount) { *prescript = 0; offset = 0; for (i = 0; i < cvarcount && offset < sizeof(prescript); i++) { if (cvarrefs[i]) { memcpy(prescript+offset, &cvartypes[i], sizeof(int)); offset+=4; Q_strlcatfz(prescript, &offset, sizeof(prescript), "%s", cvarnames[i]); offset++; } } prog->cvardata = Z_Malloc(offset); prog->cvardatasize = offset; memcpy(prog->cvardata, prescript, prog->cvardatasize); } while(cvarcount) Z_Free((char*)cvarnames[--cvarcount]); //ensure that permutation 0 works correctly as a fallback. //FIXME: add debug mode to compile all. prog->permu[0] = Shader_LoadPermutation(prog, 0); return !!prog->permu[0]; #else return false; #endif } typedef struct sgeneric_s { program_t prog; struct sgeneric_s *next; char *name; qboolean failed; } sgeneric_t; static sgeneric_t *sgenerics; struct sbuiltin_s { int qrtype; int apiver; char name[MAX_QPATH]; char *body; } sbuiltins[] = { #include "r_bishaders.h" {QR_NONE} }; void Shader_UnloadProg(program_t *prog) { if (sh_config.pDeleteProg) sh_config.pDeleteProg(prog); Z_Free(prog->name); Z_Free(prog->preshade); Z_Free(prog->shadertext); Z_Free(prog->cvardata); Z_Free(prog); } static void Shader_FlushGenerics(void) { sgeneric_t *g; while (sgenerics) { g = sgenerics; sgenerics = g->next; if (g->prog.refs == 1) { g->prog.refs--; Shader_UnloadProg(&g->prog); } else Con_Printf("generic shader still used\n"); } } static void Shader_LoadGeneric(sgeneric_t *g, int qrtype) { unsigned int i; void *file; char basicname[MAX_QPATH]; char blobname[MAX_QPATH]; char *h; g->failed = true; basicname[1] = 0; Q_strncpyz(basicname, g->name, sizeof(basicname)); h = strchr(basicname+1, '#'); if (h) *h = '\0'; if (strchr(basicname, '/') || strchr(basicname, '.')) { //explicit path FS_LoadFile(basicname, &file); *blobname = 0; } else if (ruleset_allow_shaders.ival) { //renderer-specific files if (sh_config.progpath) { Q_snprintfz(blobname, sizeof(blobname), sh_config.progpath, basicname); FS_LoadFile(blobname, &file); } else file = NULL; if (sh_config.blobpath && r_shaderblobs.ival) Q_snprintfz(blobname, sizeof(blobname), sh_config.blobpath, basicname); else *blobname = 0; } else { file = NULL; *blobname = 0; } if (sh_config.pDeleteProg) { sh_config.pDeleteProg(&g->prog); } Z_Free(g->prog.name); g->prog.name = NULL; Z_Free(g->prog.preshade); g->prog.preshade = NULL; Z_Free(g->prog.shadertext); g->prog.shadertext = NULL; Z_Free(g->prog.cvardata); g->prog.cvardata = NULL; if (file) { // Con_DPrintf("Loaded %s from disk\n", sh_config.progpath?va(sh_config.progpath, basicname):basicname); g->failed = !Shader_LoadPermutations(g->name, &g->prog, file, qrtype, 0, blobname); FS_FreeFile(file); return; } else { int ver; for (i = 0; *sbuiltins[i].name; i++) { if (sbuiltins[i].qrtype == qrtype && !strcmp(sbuiltins[i].name, basicname)) { ver = sbuiltins[i].apiver; if (ver < sh_config.minver || ver > sh_config.maxver) if (!(qrenderer==QR_OPENGL&&ver==110)) continue; g->failed = !Shader_LoadPermutations(g->name, &g->prog, sbuiltins[i].body, qrtype, ver, blobname); if (g->failed) continue; return; } } } } program_t *Shader_FindGeneric(char *name, int qrtype) { sgeneric_t *g; for (g = sgenerics; g; g = g->next) { if (!strcmp(name, g->name)) { if (g->failed) return NULL; g->prog.refs++; return &g->prog; } } //don't even try if we know it won't work. if (!sh_config.progs_supported) return NULL; g = BZ_Malloc(sizeof(*g) + strlen(name)+1); memset(g, 0, sizeof(*g)); g->name = (char*)(g+1); strcpy(g->name, name); g->next = sgenerics; sgenerics = g; g->prog.refs = 1; Shader_LoadGeneric(g, qrtype); if (g->failed) return NULL; g->prog.refs++; return &g->prog; } static void Shader_ReloadGenerics(void) { sgeneric_t *g; for (g = sgenerics; g; g = g->next) { //this happens if some cvar changed that affects the glsl itself. supposedly. Shader_LoadGeneric(g, qrenderer); } //this shader can take a while to load due to its number of permutations. //because this all happens on the main thread, try to avoid random stalls by pre-loading it. if (sh_config.progs_supported) { program_t *p = Shader_FindGeneric("defaultskin", qrenderer); if (p) //generics get held on to in order to avoid so much churn. so we can just release the reference we just created and it'll be held until shutdown anyway. p->refs--; } } void Shader_WriteOutGenerics_f(void) { int i; char *name; for (i = 0; *sbuiltins[i].name; i++) { name = NULL; if (sbuiltins[i].qrtype == QR_OPENGL) { if (sbuiltins[i].apiver == 100) name = va("gles/eg_%s.glsl", sbuiltins[i].name); else name = va("glsl/eg_%s.glsl", sbuiltins[i].name); } else if (sbuiltins[i].qrtype == QR_DIRECT3D9) name = va("hlsl/eg_%s.hlsl", sbuiltins[i].name); else if (sbuiltins[i].qrtype == QR_DIRECT3D11) name = va("hlsl11/eg_%s.hlsl", sbuiltins[i].name); if (name) { vfsfile_t *f = FS_OpenVFS(name, "rb", FS_GAMEONLY); if (f) { int len = VFS_GETLEN(f); char *buf = Hunk_TempAlloc(len); VFS_READ(f, buf, len); if (len != strlen(sbuiltins[i].body) || memcmp(buf, sbuiltins[i].body, len)) Con_Printf("Not writing %s - modified version in the way\n", name); else Con_Printf("%s is unmodified\n", name); VFS_CLOSE(f); } else { Con_Printf("Writing %s\n", name); FS_WriteFile(name, sbuiltins[i].body, strlen(sbuiltins[i].body), FS_GAMEONLY); } } } } struct shader_field_names_s shader_attr_names[] = { /*vertex attributes*/ {"v_position1", VATTR_VERTEX1}, {"v_position2", VATTR_VERTEX2}, {"v_colour", VATTR_COLOUR}, {"v_texcoord", VATTR_TEXCOORD}, {"v_lmcoord", VATTR_LMCOORD}, {"v_normal", VATTR_NORMALS}, {"v_svector", VATTR_SNORMALS}, {"v_tvector", VATTR_TNORMALS}, {"v_bone", VATTR_BONENUMS}, {"v_weight", VATTR_BONEWEIGHTS}, #if MAXRLIGHTMAPS > 1 {"v_lmcoord1", VATTR_LMCOORD}, {"v_lmcoord2", VATTR_LMCOORD2}, {"v_lmcoord3", VATTR_LMCOORD3}, {"v_lmcoord4", VATTR_LMCOORD4}, {"v_colour1", VATTR_COLOUR}, {"v_colour2", VATTR_COLOUR2}, {"v_colour3", VATTR_COLOUR3}, {"v_colour4", VATTR_COLOUR4}, #endif {NULL} }; struct shader_field_names_s shader_unif_names[] = { /**///tagged names are available to vulkan /*matricies*/ /**/{"m_model", SP_M_MODEL}, //the model matrix {"m_view", SP_M_VIEW}, //the view matrix {"m_modelview", SP_M_MODELVIEW},//the combined modelview matrix {"m_projection", SP_M_PROJECTION},//projection matrix /**/{"m_modelviewprojection", SP_M_MODELVIEWPROJECTION},//fancy mvp matrix. probably has degraded precision. {"m_bones_packed", SP_M_ENTBONES_PACKED}, //bone matrix array. should normally be read via sys/skeletal.h {"m_bones_mat3x4", SP_M_ENTBONES_MAT3X4}, //bone matrix array. should normally be read via sys/skeletal.h {"m_invviewprojection", SP_M_INVVIEWPROJECTION},//inverted vp matrix {"m_invmodelviewprojection",SP_M_INVMODELVIEWPROJECTION},//inverted mvp matrix. /**///m_modelinv /*viewer properties*/ {"v_eyepos", SP_V_EYEPOS}, //eye pos in worldspace /**/{"w_fog", SP_W_FOG}, //read by sys/fog.h {"w_user", SP_W_USER}, //set via VF_USERDATA /*ent properties*/ {"e_vblend", SP_E_VBLEND}, //v_position1+v_position2 scalers /**/{"e_lmscale", SP_E_LMSCALE}, /*overbright shifting*/ {"e_vlscale", SP_E_VLSCALE}, /*no lightmaps, no overbrights*/ {"e_origin", SP_E_ORIGIN}, //the ents origin in worldspace /**/{"e_time", SP_E_TIME}, //r_refdef.time - entity->time /**/{"e_eyepos", SP_E_EYEPOS}, //eye pos in modelspace {"e_colour", SP_E_COLOURS}, //colormod/alpha, even if colormod isn't set /**/{"e_colourident", SP_E_COLOURSIDENT}, //colormod,alpha or 1,1,1,alpha if colormod isn't set /**/{"e_glowmod", SP_E_GLOWMOD}, //fullbright scalers (for hdr mostly) /**/{"e_uppercolour", SP_E_TOPCOLOURS}, //q1 player colours /**/{"e_lowercolour", SP_E_BOTTOMCOLOURS},//q1 player colours /**/{"e_light_dir", SP_E_L_DIR}, //lightgrid light dir. dotproducts should be clamped to 0-1. /**/{"e_light_mul", SP_E_L_MUL}, //lightgrid light scaler. /**/{"e_light_ambient", SP_E_L_AMBIENT}, //lightgrid light value for the unlit side. {"s_colour", SP_S_COLOUR}, //the rgbgen/alphagen stuff. obviously doesn't work with per-vertex ones. /*rtlight properties, use with caution*/ {"l_lightscreen", SP_LIGHTSCREEN}, //screenspace position of the current rtlight /**/{"l_lightradius", SP_LIGHTRADIUS}, //radius of the current rtlight /**/{"l_lightcolour", SP_LIGHTCOLOUR}, //rgb values of the current rtlight /**/{"l_lightposition", SP_LIGHTPOSITION}, //light position in modelspace {"l_lightdirection", SP_LIGHTDIRECTION}, //light direction in modelspace (ortho lights only, instead of position) /**/{"l_lightcolourscale", SP_LIGHTCOLOURSCALE},//ambient/diffuse/specular scalers /**/{"l_cubematrix", SP_LIGHTCUBEMATRIX},//matrix used to control the rtlight's cubemap projection /**/{"l_shadowmapproj", SP_LIGHTSHADOWMAPPROJ}, //compacted projection matrix for shadowmaps /**/{"l_shadowmapscale", SP_LIGHTSHADOWMAPSCALE},//should probably use a samplerRect instead... {"e_sourcesize", SP_SOURCESIZE}, //size of VF_SOURCECOLOUR image {"e_rendertexturescale", SP_RENDERTEXTURESCALE}, //scaler for $currentrender, when npot isn't supported. {NULL} }; static char *Shader_ParseBody(char *debugname, char **ptr) { char *body; char *start, *end; end = *ptr; while (*end == ' ' || *end == '\t' || *end == '\r') end++; if (*end == '\n') { int count; end++; while (*end == ' ' || *end == '\t') end++; if (*end != '{') { Con_Printf("shader \"%s\" missing program string\n", debugname); } else { end++; start = end; for (count = 1; *end; end++) { if (*end == '}') { count--; if (!count) break; } else if (*end == '{') count++; } body = BZ_Malloc(end - start + 1); memcpy(body, start, end-start); body[end-start] = 0; *ptr = end+1;/*skip over it all*/ return body; } } return NULL; } static void Shader_SLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr, int qrtype) { /*accepts: program { BLAH } where BLAH is both vertex+frag with #ifdefs or program fname on one line. */ char *programbody; char *hash; program_t *newprog; programbody = Shader_ParseBody(shader->name, ptr); if (programbody) { newprog = BZ_Malloc(sizeof(*newprog)); memset(newprog, 0, sizeof(*newprog)); newprog->refs = 1; if (!Shader_LoadPermutations(shader->name, newprog, programbody, qrtype, 0, NULL)) { BZ_Free(newprog); newprog = NULL; } BZ_Free(programbody); } else { hash = strchr(shader->name, '#'); if (hash) { //pass the # postfixes from the shader name onto the generic glsl to use char newname[512]; Q_snprintfz(newname, sizeof(newname), "%s%s", Shader_ParseExactString(ptr), hash); newprog = Shader_FindGeneric(newname, qrtype); } else newprog = Shader_FindGeneric(Shader_ParseExactString(ptr), qrtype); } if (pass) { if (pass->numMergedPasses) { Shader_ReleaseGeneric(newprog); Con_DPrintf("shader %s: program defined after first texture map\n", shader->name); } else { Shader_ReleaseGeneric(pass->prog); pass->prog = newprog; } } else { Shader_ReleaseGeneric(shader->prog); shader->prog = newprog; } } static void Shader_GLSLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) { Shader_SLProgramName(shader,pass,ptr,QR_OPENGL); } static void Shader_ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) { Shader_SLProgramName(shader,pass,ptr,qrenderer); } static void Shader_HLSL9ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) { Shader_SLProgramName(shader,pass,ptr,QR_DIRECT3D9); } static void Shader_HLSL11ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) { Shader_SLProgramName(shader,pass,ptr,QR_DIRECT3D11); } static void Shader_ReflectCube(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->reflectcube = Shader_FindImage(token, flags|IF_CUBEMAP); } static void Shader_ReflectMask(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->reflectmask = Shader_FindImage(token, flags); } static void Shader_DiffuseMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->base = Shader_FindImage(token, flags); Q_strncpyz(shader->defaulttextures->mapname, token, sizeof(shader->defaulttextures->mapname)); } static void Shader_SpecularMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->specular = Shader_FindImage(token, flags); } static void Shader_NormalMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->bump = Shader_FindImage(token, flags|IF_TRYBUMP|IF_NOSRGB); } static void Shader_FullbrightMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->fullbright = Shader_FindImage(token, flags); } static void Shader_UpperMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->upperoverlay = Shader_FindImage(token, flags); } static void Shader_LowerMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->loweroverlay = Shader_FindImage(token, flags); } static void Shader_DisplacementMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); unsigned int flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->displacement = Shader_FindImage(token, flags|IF_NOSRGB); } static void Shaderpass_QF_Material(shader_t *shader, shaderpass_t *pass, char **ptr) { unsigned int flags; char *progname = "defaultwall"; char *token; char *hash = strchr(shader->name, '#'); if (hash) { //pass the # postfixes from the shader name onto the generic glsl to use char newname[512]; Q_snprintfz(newname, sizeof(newname), "%s%s", progname, hash); pass->prog = Shader_FindGeneric(newname, qrenderer); } else pass->prog = Shader_FindGeneric(progname, qrenderer); token = Shader_ParseString(ptr); if (*token && strcmp(token, "-")) { flags = Shader_SetImageFlags (shader, NULL, &token); if (*token) shader->defaulttextures->base = Shader_FindImage(token, flags); else { token = shader->name; if (hash) *hash = 0; shader->defaulttextures->base = Shader_FindImage(token, flags); if (hash) *hash = '#'; } } if (*token) token = Shader_ParseString(ptr); if (*token && strcmp(token, "-")) { flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->bump = Shader_FindImage(token, flags); } if (*token) token = Shader_ParseString(ptr); if (*token && strcmp(token, "-")) { flags = Shader_SetImageFlags (shader, NULL, &token); shader->defaulttextures->specular = Shader_FindImage(token, flags); } } static qboolean Shaderpass_MapGen (shader_t *shader, shaderpass_t *pass, char *tname); static void Shader_Translucent(shader_t *shader, shaderpass_t *pass, char **ptr) { shader->flags |= SHADER_BLEND; } static void Shader_PortalFBOScale(shader_t *shader, shaderpass_t *pass, char **ptr) { shader->portalfboscale = Shader_ParseFloat(shader, ptr, 0); shader->portalfboscale = max(shader->portalfboscale, 0); } static void Shader_DP_Camera(shader_t *shader, shaderpass_t *pass, char **ptr) { shader->sort = SHADER_SORT_PORTAL; parsestate.dpwatertype |= 4; } static void Shader_DP_Water(shader_t *shader, shaderpass_t *pass, char **ptr) { parsestate.parseflags |= SPF_PROGRAMIFY; parsestate.dpwatertype |= 3; parsestate.reflectmin = Shader_ParseFloat(shader, ptr, 0); parsestate.reflectmax = Shader_ParseFloat(shader, ptr, 0); parsestate.refractfactor = Shader_ParseFloat(shader, ptr, 0); parsestate.reflectfactor = Shader_ParseFloat(shader, ptr, 0); Shader_ParseVector(shader, ptr, parsestate.refractcolour); Shader_ParseVector(shader, ptr, parsestate.reflectcolour); parsestate.wateralpha = Shader_ParseFloat(shader, ptr, 0); } static void Shader_DP_Reflect(shader_t *shader, shaderpass_t *pass, char **ptr) { parsestate.parseflags |= SPF_PROGRAMIFY; parsestate.dpwatertype |= 1; parsestate.reflectmin = 1; parsestate.reflectmax = 1; parsestate.reflectfactor = Shader_ParseFloat(shader, ptr, 0); Shader_ParseVector(shader, ptr, parsestate.reflectcolour); } static void Shader_DP_Refract(shader_t *shader, shaderpass_t *pass, char **ptr) { parsestate.parseflags |= SPF_PROGRAMIFY; parsestate.dpwatertype |= 2; parsestate.refractfactor = Shader_ParseFloat(shader, ptr, 0); Shader_ParseVector(shader, ptr, parsestate.refractcolour); } static void Shader_DP_OffsetMapping(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); if (!strcmp(token, "disable") || !strcmp(token, "none") || !strcmp(token, "off")) parsestate.offsetmappingmode = 0; else if (!strcmp(token, "default") || !strcmp(token, "normal")) parsestate.offsetmappingmode = -1; else if (!strcmp(token, "linear")) parsestate.offsetmappingmode = 1; else if (!strcmp(token, "relief")) parsestate.offsetmappingmode = 2; parsestate.offsetmappingscale = Shader_ParseFloat(shader, ptr, 1); token = Shader_ParseString(ptr); if (!strcmp(token, "bias")) parsestate.offsetmappingbias = Shader_ParseFloat(shader, ptr, 0.5); else if (!strcmp(token, "match")) parsestate.offsetmappingbias = 1.0 - Shader_ParseFloat(shader, ptr, 0.5); else if (!strcmp(token, "match8")) parsestate.offsetmappingbias = 1.0 - (1.0/255) * Shader_ParseFloat(shader, ptr, 128); else if (!strcmp(token, "match16")) parsestate.offsetmappingbias = 1.0 - (1.0/65535) * Shader_ParseFloat(shader, ptr, 32768); } static void Shader_DP_GlossScale(shader_t *shader, shaderpass_t *pass, char **ptr) { parsestate.specularvalscale = Shader_ParseFloat(shader, ptr, 0); } static void Shader_DP_GlossExponent(shader_t *shader, shaderpass_t *pass, char **ptr) { parsestate.specularexpscale = Shader_ParseFloat(shader, ptr, 0); } static void Shader_BEMode(shader_t *shader, shaderpass_t *pass, char **ptr) { char subname[1024]; int mode; char tokencopy[1024]; char *token; char *embed = NULL; token = Shader_ParseString(ptr); if (!Q_stricmp(token, "rtlight")) mode = -1; //all light types else if (!Q_stricmp(token, "rtlight_only")) mode = LSHADER_STANDARD; else if (!Q_stricmp(token, "rtlight_smap")) mode = LSHADER_SMAP; else if (!Q_stricmp(token, "rtlight_spot")) mode = LSHADER_SPOT; else if (!Q_stricmp(token, "rtlight_cube")) mode = LSHADER_CUBE; else if (!Q_stricmp(token, "rtlight_cube_smap")) mode = LSHADER_CUBE|LSHADER_SMAP; else if (!Q_stricmp(token, "rtlight_cube_spot")) //doesn't make sense. mode = LSHADER_CUBE|LSHADER_SPOT; else if (!Q_stricmp(token, "rtlight_spot_smap")) mode = LSHADER_SMAP|LSHADER_SPOT; else if (!Q_stricmp(token, "rtlight_cube_spot_smap")) //doesn't make sense. mode = LSHADER_CUBE|LSHADER_SPOT|LSHADER_SMAP; else if (!Q_stricmp(token, "crepuscular")) mode = bemoverride_crepuscular; else if (!Q_stricmp(token, "depthonly")) mode = bemoverride_depthonly; else if (!Q_stricmp(token, "depthdark")) mode = bemoverride_depthdark; else if (!Q_stricmp(token, "gbuffer") || !Q_stricmp(token, "prelight")) mode = bemoverride_gbuffer; else if (!Q_stricmp(token, "fog")) mode = bemoverride_fog; else { Con_DPrintf(CON_WARNING "Shader %s specifies unknown bemode %s.\n", shader->name, token); return; //not supported. } embed = Shader_ParseBody(shader->name, ptr); if (embed) { int l = strlen(embed) + 6; char *b = BZ_Malloc(l); Q_snprintfz(b, l, "{\n%s\n}\n", embed); BZ_Free(embed); embed = b; //generate a unique name Q_snprintfz(tokencopy, sizeof(tokencopy), "%s_mode%i", shader->name, mode); } else { token = Shader_ParseString(ptr); Q_strncpyz(tokencopy, token, sizeof(tokencopy)); //make sure things don't go squiff. } if (mode == -1) { //shorthand for rtlights for (mode = 0; mode < LSHADER_MODES; mode++) { if ((mode & LSHADER_CUBE) && (mode & (LSHADER_SPOT|LSHADER_ORTHO))) continue; //cube projections don't make sense when the light isn't projecting a cube if ((mode & LSHADER_ORTHO) && (mode & LSHADER_SPOT)) continue; //ortho+spot are mutually exclusive. Q_snprintfz(subname, sizeof(subname), "%s%s%s%s%s%s", tokencopy, (mode & LSHADER_SMAP)?"#PCF":"", (mode & LSHADER_SPOT)?"#SPOT":"", (mode & LSHADER_CUBE)?"#CUBE":"", (mode & LSHADER_ORTHO)?"#ORTHO":"", #ifdef GLQUAKE (qrenderer == QR_OPENGL && gl_config.arb_shadow && (mode & LSHADER_SMAP))?"#USE_ARB_SHADOW":"" #else "" #endif ); shader->bemoverrides[mode] = R_RegisterCustom(subname, shader->usageflags|(embed?SUR_FORCEFALLBACK:0), embed?Shader_DefaultScript:NULL, embed); } } else { shader->bemoverrides[mode] = R_RegisterCustom(tokencopy, shader->usageflags|(embed?SUR_FORCEFALLBACK:0), embed?Shader_DefaultScript:NULL, embed); } if (embed) BZ_Free(embed); } static shaderkey_t shaderkeys[] = { #define Q3 NULL {"cull", Shader_Cull, Q3}, {"skyparms", Shader_SkyParms, Q3}, {"fogparms", Shader_FogParms, Q3}, {"surfaceparm", Shader_SurfaceParm, Q3}, {"nomipmaps", Shader_NoMipMaps, Q3}, {"nopicmip", Shader_NoPicMip, Q3}, {"polygonoffset", Shader_PolygonOffset, Q3}, {"sort", Shader_Sort, Q3}, {"deformvertexes", Shader_DeformVertexes, Q3}, {"portal", Shader_Portal, Q3}, {"entitymergable", Shader_EntityMergable, Q3}, //fte extensions {"clutter", Shader_ClutterParms, "fte"}, {"deferredlight", Shader_Deferredlight, "fte"}, //(sort = prelight) // {"lpp_light", Shader_Deferredlight, "fte"}, //(sort = prelight) {"affine", Shader_Affine, "fte"}, //some hardware is horribly slow, and can benefit from certain hints. {"bemode", Shader_BEMode, "fte"}, {"diffusemap", Shader_DiffuseMap, "fte"}, {"normalmap", Shader_NormalMap, "fte"}, {"specularmap", Shader_SpecularMap, "fte"}, {"fullbrightmap", Shader_FullbrightMap, "fte"}, {"uppermap", Shader_UpperMap, "fte"}, {"lowermap", Shader_LowerMap, "fte"}, {"reflectmask", Shader_ReflectMask, "fte"}, {"displacementmap", Shader_DisplacementMap, "fte"}, {"portalfboscale", Shader_PortalFBOScale, "fte"}, //portal/mirror/refraction/reflection FBOs are resized by this scale {"basefactor", NULL, "fte"}, //material scalers for glsl {"specularfactor", NULL, "fte"}, //material scalers for glsl {"fullbrightfactor", NULL, "fte"}, //material scalers for glsl //TODO: PBR textures... // {"albedomap", Shader_DiffuseMap, "fte"}, //rgb(a) // {"loweruppermap", Shader_LowerUpperMap, "fte"}, //r=lower, g=upper (team being more important than personal colours, this allows the texture to gracefully revert to red-only) //{"normalmap", Shader_NormalMap, "fte"}, //xy-h // {"omrmap", Shader_SpecularMap, "fte"}, //r=occlusion, g=metalness, b=roughness. //{"glowmap", Shader_FullbrightMap, "fte"}, //rgb /*program stuff at the material level is an outdated practise.*/ {"program", Shader_ProgramName, "fte"}, //usable with any renderer that has a usable shader language... {"glslprogram", Shader_GLSLProgramName, "fte"}, //for renderers that accept embedded glsl {"hlslprogram", Shader_HLSL9ProgramName, "fte"}, //for d3d with embedded hlsl {"hlsl11program", Shader_HLSL11ProgramName, "fte"}, //for d3d with embedded hlsl // {"progblendfunc", Shader_ProgBlendFunc, "fte"}, //specifies the blend mode (actually just overrides the first subpasses' blendmode. // {"progmap", Shader_ProgMap, "fte"}, //avoids needing extra subpasses (actually just inserts an extra pass). //dp compat {"reflectcube", Shader_ReflectCube, "dp"}, {"camera", Shader_DP_Camera, "dp"}, {"water", Shader_DP_Water, "dp"}, {"reflect", Shader_DP_Reflect, "dp"}, {"refract", Shader_DP_Refract, "dp"}, {"offsetmapping", Shader_DP_OffsetMapping, "dp"}, {"noshadow", NULL, "dp"}, {"polygonoffset", NULL, "dp"}, {"glossintensitymod", Shader_DP_GlossScale, "dp"}, //scales r_shadow_glossintensity(=1), aka: gl_specular {"glossexponentmod", Shader_DP_GlossExponent, "dp"}, //scales r_shadow_glossexponent(=32) /*doom3 compat*/ {"diffusemap", Shader_DiffuseMap, "doom3"}, //macro for "{\nstage diffusemap\nmap \n}" {"bumpmap", Shader_NormalMap, "doom3"}, //macro for "{\nstage bumpmap\nmap \n}" {"discrete", NULL, "doom3"}, {"nonsolid", NULL, "doom3"}, {"noimpact", NULL, "doom3"}, {"translucent", Shader_Translucent, "doom3"}, {"noshadows", NULL, "doom3"}, {"nooverlays", NULL, "doom3"}, {"nofragment", NULL, "doom3"}, /*RTCW compat*/ {"nocompress", NULL, "rtcw"}, {"allowcompress", NULL, "rtcw"}, {"nofog", NULL, "rtcw"}, {"skyfogvars", NULL, "rtcw"}, {"sunshader", NULL, "rtcw"}, {"sun", NULL, "q3map2"}, //provides rgb and dir {"sunExt", NULL, "q3map2"}, //treated as an alias {"fogParms", NULL, "rtcw"}, //sets a cvar. *shudder* {"fogvars", NULL, "rtcw"}, //sets a cvar. *shudder* {"waterfogvars", NULL, "rtcw"}, //sets a cvar. *shudder* {"light", NULL, "rtcw"}, //for q3map2, not us {"lightgridmulamb", NULL, "rtcw"}, //urm {"lightgridmuldir", NULL, "rtcw"}, //not really sure how this is useful to us /*qfusion / warsow compat*/ // {"skyparms2", NULL, "qf"}, //skyparms without the underscore. // {"skyparmssides", NULL, "qf"}, //skyparms with explicitly-named faces // {"nocompress", NULL, "qf"}, //disables opportunistic compression (doesn't affect compressed source images, apparently) // {"nofiltering", NULL, "qf"}, //misnomer. there is always 'filtering'. this means to use nearest filtering for min and mag, as well as no mipmaps. // {"smallestmipmapsize", NULL, "qf"}, //mips with a size less than the specified value are dropped. // {"stenciltest", NULL, "qf"}, //enables GL_STENCIL_TEST, which is special-case stuff that I see no reason to support // {"offsetmappingscale", NULL, "qf"}, // {"glossexponent", NULL, "qf"}, // {"glossintensity", NULL, "qf"}, // {"template", NULL, "qf"}, //parses some other shader, with $3 etc arg expansion {"skip", NULL, "qf"}, //just skips the line. acts like a comment. no idea why they can't just use a comment. // {"softparticle", NULL, "qf"}, //uses screen depth, if possible. // {"forceworldoutlines", NULL, "qf"}, //looks like an ugly hack to me. {NULL, NULL} }; static struct { char *name; char *body; } shadermacros[] = { {"decal_macro", "polygonOffset 1\ndiscrete\nsort decal\nnoShadows"}, // {"diffusemap", "{\nblend diffusemap\nmap %1\n}"}, // {"bumpmap", "{\nblend bumpmap\nmap %1\n}"}, // {"specularmap", "{\nblend specularmap\nmap %1\n}"}, {NULL} }; // =============================================================== static qboolean Shaderpass_MapGen (shader_t *shader, shaderpass_t *pass, char *tname) { int tcgen = TC_GEN_BASE; if (!Q_stricmp (tname, "$lightmap")) { tcgen = TC_GEN_LIGHTMAP; pass->flags |= SHADER_PASS_LIGHTMAP | SHADER_PASS_NOMIPMAP; pass->texgen = T_GEN_LIGHTMAP; shader->flags |= SHADER_HASLIGHTMAP; } else if (!Q_stricmp (tname, "$deluxmap")) { tcgen = TC_GEN_LIGHTMAP; pass->flags |= SHADER_PASS_DELUXMAP | SHADER_PASS_NOMIPMAP; pass->texgen = T_GEN_DELUXMAP; } else if (!Q_stricmp (tname, "$diffuse")) { pass->texgen = T_GEN_DIFFUSE; shader->flags |= SHADER_HASDIFFUSE; } else if (!Q_stricmp (tname, "$paletted")) { pass->texgen = T_GEN_PALETTED; shader->flags |= SHADER_HASPALETTED; } else if (!Q_stricmp (tname, "$normalmap")) { pass->texgen = T_GEN_NORMALMAP; shader->flags |= SHADER_HASNORMALMAP; } else if (!Q_stricmp (tname, "$specular")) { pass->texgen = T_GEN_SPECULAR; shader->flags |= SHADER_HASGLOSS; } else if (!Q_stricmp (tname, "$fullbright")) { pass->texgen = T_GEN_FULLBRIGHT; shader->flags |= SHADER_HASFULLBRIGHT; } else if (!Q_stricmp (tname, "$upperoverlay")) { shader->flags |= SHADER_HASTOPBOTTOM; pass->texgen = T_GEN_UPPEROVERLAY; } else if (!Q_stricmp (tname, "$loweroverlay")) { shader->flags |= SHADER_HASTOPBOTTOM; pass->texgen = T_GEN_LOWEROVERLAY; } else if (!Q_stricmp (tname, "$reflectcube")) { pass->texgen = T_GEN_REFLECTCUBE; } else if (!Q_stricmp (tname, "$reflectmask")) { pass->texgen = T_GEN_REFLECTMASK; } else if (!Q_stricmp (tname, "$displacement")) { shader->flags |= SHADER_HASDISPLACEMENT; pass->texgen = T_GEN_DISPLACEMENT; } else if (!Q_stricmp (tname, "$shadowmap")) { pass->texgen = T_GEN_SHADOWMAP; pass->flags |= SHADER_PASS_DEPTHCMP; } else if (!Q_stricmp (tname, "$lightcubemap")) { pass->texgen = T_GEN_LIGHTCUBEMAP; } else if (!Q_stricmp (tname, "$currentrender")) { pass->texgen = T_GEN_CURRENTRENDER; shader->flags |= SHADER_HASCURRENTRENDER; } else if (!Q_stricmp (tname, "$sourcecolour")) { pass->texgen = T_GEN_SOURCECOLOUR; } else if (!Q_stricmp (tname, "$sourcecube")) { pass->texgen = T_GEN_SOURCECUBE; } else if (!Q_stricmp (tname, "$sourcedepth")) { pass->texgen = T_GEN_SOURCEDEPTH; } else if (!Q_strnicmp (tname, "$gbuffer", 8)) { unsigned idx = strtoul(tname+8, &tname, 10); if (*tname || idx >= GBUFFER_COUNT) return false; pass->texgen = T_GEN_GBUFFER0 + idx; } else if (!Q_stricmp (tname, "$reflection")) { shader->flags |= SHADER_HASREFLECT; pass->texgen = T_GEN_REFLECTION; } else if (!Q_stricmp (tname, "$refraction")) { shader->flags |= SHADER_HASREFRACT; pass->texgen = T_GEN_REFRACTION; } else if (!Q_stricmp (tname, "$refractiondepth")) { shader->flags |= SHADER_HASREFRACT; pass->texgen = T_GEN_REFRACTIONDEPTH; } else if (!Q_stricmp (tname, "$ripplemap")) { shader->flags |= SHADER_HASRIPPLEMAP; pass->texgen = T_GEN_RIPPLEMAP; } else if (!Q_stricmp (tname, "$null")) { pass->flags |= SHADER_PASS_NOMIPMAP|SHADER_PASS_DETAIL; pass->texgen = T_GEN_SINGLEMAP; } else return false; if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = tcgen; return true; } shaderpass_t *Shaderpass_DefineMap(shader_t *shader, shaderpass_t *pass) { //'map foo' works a bit differently when there's a program in the same pass. //instead of corrupting the previous one, it collects multiple maps so that {prog foo;map t0;map t1; map t2; blendfunc add} can work as expected if (pass->prog) { if (pass->numMergedPasses==0) pass->numMergedPasses++; else { //FIXME: bounds check! if (shader->numpasses == SHADER_PASS_MAX || shader->numpasses == SHADER_TMU_MAX) { Con_DPrintf (CON_WARNING "Shader %s has too many texture passes.\n", shader->name); parsestate.droppass = true; } // else if (shader->numpasses == be_maxpasses) // parsestate.droppass = true; else { pass->numMergedPasses++; shader->numpasses++; } pass = shader->passes+shader->numpasses-1; memset(pass, 0, sizeof(*pass)); } } else pass->numMergedPasses = 1; return pass; } static void Shaderpass_Map (shader_t *shader, shaderpass_t *pass, char **ptr) { int flags; char *token; pass = Shaderpass_DefineMap(shader, pass); pass->anim_frames[0] = r_nulltex; token = Shader_ParseString (ptr); flags = Shader_SetImageFlags (shader, pass, &token); if (!Shaderpass_MapGen(shader, pass, token)) { switch((flags & IF_TEXTYPE) >> IF_TEXTYPESHIFT) { case 0: pass->texgen = T_GEN_SINGLEMAP; break; case 1: pass->texgen = T_GEN_3DMAP; break; default: pass->texgen = T_GEN_CUBEMAP; break; } if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_BASE; if (!*shader->defaulttextures->mapname && *token != '$' && pass->tcgen == TC_GEN_BASE) Q_strncpyz(shader->defaulttextures->mapname, token, sizeof(shader->defaulttextures->mapname)); pass->anim_frames[0] = Shader_FindImage (token, flags); } } static void Shaderpass_AnimMap_Flag (shader_t *shader, shaderpass_t *pass, char **ptr, unsigned int flags) { char *token; texid_t image; qboolean isdiffuse = false; flags |= Shader_SetImageFlags (shader, pass, NULL); if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_BASE; pass->flags |= SHADER_PASS_ANIMMAP; pass->texgen = T_GEN_ANIMMAP; pass->anim_fps = Shader_ParseFloat (shader, ptr, 0); pass->anim_numframes = 0; for ( ; ; ) { token = Shader_ParseString(ptr); if (!token[0]) { break; } if (!pass->anim_numframes && !*shader->defaulttextures->mapname && *token != '$' && pass->tcgen == TC_GEN_BASE) { isdiffuse = true; shader->defaulttextures_fps = pass->anim_fps; } if (pass->anim_numframes < SHADER_MAX_ANIMFRAMES) { image = Shader_FindImage (token, flags); if (isdiffuse) { if (shader->numdefaulttextures < pass->anim_numframes+1) { int newmax = pass->anim_numframes+1; shader->defaulttextures = BZ_Realloc(shader->defaulttextures, sizeof(*shader->defaulttextures) * (newmax)); memset(shader->defaulttextures+shader->numdefaulttextures, 0, sizeof(*shader->defaulttextures) * (newmax-shader->numdefaulttextures)); shader->numdefaulttextures = newmax; } Q_strncpyz(shader->defaulttextures[pass->anim_numframes].mapname, token, sizeof(shader->defaulttextures[pass->anim_numframes].mapname)); } if (!TEXVALID(image)) { pass->anim_frames[pass->anim_numframes++] = missing_texture; Con_DPrintf (CON_WARNING "Shader %s has an animmap with no image: %s.\n", shader->name, token ); } else { pass->anim_frames[pass->anim_numframes++] = image; } } } } static void Shaderpass_AnimMap (shader_t *shader, shaderpass_t *pass, char **ptr) { Shaderpass_AnimMap_Flag(shader, pass, ptr, 0); } static void Shaderpass_QF_AnimClampMap (shader_t *shader, shaderpass_t *pass, char **ptr) { Shaderpass_AnimMap_Flag(shader, pass, ptr, IF_CLAMP); } static void Shaderpass_ClampMap (shader_t *shader, shaderpass_t *pass, char **ptr) { int flags; char *token; token = Shader_ParseString (ptr); flags = Shader_SetImageFlags (shader, pass, &token); if (!Shaderpass_MapGen(shader, pass, token)) { if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_BASE; pass->anim_frames[0] = Shader_FindImage (token, flags | IF_CLAMP); switch((flags & IF_TEXTYPE) >> IF_TEXTYPESHIFT) { case 0: pass->texgen = T_GEN_SINGLEMAP; break; case 1: pass->texgen = T_GEN_3DMAP; break; default: pass->texgen = T_GEN_CUBEMAP; break; } if (!TEXVALID(pass->anim_frames[0])) { if (flags & (IF_3DMAP | IF_CUBEMAP)) pass->anim_frames[0] = r_nulltex; else pass->anim_frames[0] = missing_texture; Con_DPrintf (CON_WARNING "Shader %s has a stage with no image: %s.\n", shader->name, token); } } pass->flags |= SHADER_PASS_CLAMP; } static void Shaderpass_VideoMap (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseSensString (ptr); #ifndef HAVE_MEDIA_DECODER (void)token; #else if (pass->cin) Z_Free (pass->cin); pass->cin = Media_StartCin(token); if (!pass->cin) { Con_DPrintf (CON_WARNING "(shader %s) Couldn't load video %s\n", shader->name, token); } if (pass->cin) { pass->flags |= SHADER_PASS_VIDEOMAP; shader->flags |= SHADER_VIDEOMAP; pass->texgen = T_GEN_VIDEOMAP; } else { pass->texgen = T_GEN_DIFFUSE; pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.type = SHADER_FUNC_CONSTANT; pass->rgbgen_func.args[0] = pass->rgbgen_func.args[1] = pass->rgbgen_func.args[2] = 0; } #endif } static void Shaderpass_RTCW_Map_16bit (shader_t *shader, shaderpass_t *pass, char **ptr) { if (!gl_load24bit.ival) //urm, not sure if suitable choice of cvar Shaderpass_Map(shader, pass, ptr); } static void Shaderpass_RTCW_Map_32bit (shader_t *shader, shaderpass_t *pass, char **ptr) { if (gl_load24bit.ival) Shaderpass_Map(shader, pass, ptr); } static void Shaderpass_RTCW_Map_s3tc (shader_t *shader, shaderpass_t *pass, char **ptr) { if (sh_config.texfmt[PTI_BC3_RGBA] && gl_compress.ival) Shaderpass_Map(shader, pass, ptr); } static void Shaderpass_RTCW_Map_nos3tc (shader_t *shader, shaderpass_t *pass, char **ptr) { if (!(sh_config.texfmt[PTI_BC3_RGBA] && gl_compress.ival)) Shaderpass_Map(shader, pass, ptr); } static void Shaderpass_RTCW_AnimMap_s3tc (shader_t *shader, shaderpass_t *pass, char **ptr) { if ((sh_config.texfmt[PTI_BC3_RGBA] && gl_compress.ival)) Shaderpass_AnimMap(shader, pass, ptr); } static void Shaderpass_RTCW_AnimMap_nos3tc (shader_t *shader, shaderpass_t *pass, char **ptr) { if (!(sh_config.texfmt[PTI_BC3_RGBA] && gl_compress.ival)) Shaderpass_AnimMap(shader, pass, ptr); } static void Shaderpass_SLProgramName (shader_t *shader, shaderpass_t *pass, char **ptr, int qrtype) { /*accepts: program { BLAH } where BLAH is both vertex+frag with #ifdefs or program fname on one line. */ //char *programbody; char *hash; /*programbody = Shader_ParseBody(shader->name, ptr); if (programbody) { shader->prog = BZ_Malloc(sizeof(*shader->prog)); memset(shader->prog, 0, sizeof(*shader->prog)); shader->prog->refs = 1; if (!Shader_LoadPermutations(shader->name, shader->prog, programbody, qrtype, 0, NULL)) { BZ_Free(shader->prog); shader->prog = NULL; } BZ_Free(programbody); return; }*/ hash = strchr(shader->name, '#'); if (hash) { //pass the # postfixes from the shader name onto the generic glsl to use char newname[512]; Q_snprintfz(newname, sizeof(newname), "%s%s", Shader_ParseExactString(ptr), hash); pass->prog = Shader_FindGeneric(newname, qrtype); } else pass->prog = Shader_FindGeneric(Shader_ParseExactString(ptr), qrtype); } static void Shaderpass_ProgramName (shader_t *shader, shaderpass_t *pass, char **ptr) { Shaderpass_SLProgramName(shader,pass,ptr,qrenderer); } static void Shaderpass_RGBGen (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token; token = Shader_ParseString (ptr); if (!Q_stricmp (token, "identitylighting")) pass->rgbgen = RGB_GEN_IDENTITY_LIGHTING; else if (!Q_stricmp (token, "identity")) pass->rgbgen = RGB_GEN_IDENTITY; else if (!Q_stricmp (token, "wave")) { pass->rgbgen = RGB_GEN_WAVE; Shader_ParseFunc (shader, ptr, &pass->rgbgen_func); } else if (!Q_stricmp(token, "entity")) pass->rgbgen = RGB_GEN_ENTITY; else if (!Q_stricmp (token, "oneMinusEntity")) pass->rgbgen = RGB_GEN_ONE_MINUS_ENTITY; else if (!Q_stricmp (token, "vertex")) pass->rgbgen = RGB_GEN_VERTEX_LIGHTING; else if (!Q_stricmp (token, "oneMinusVertex")) pass->rgbgen = RGB_GEN_ONE_MINUS_VERTEX; else if (!Q_stricmp (token, "lightingDiffuse")) pass->rgbgen = RGB_GEN_LIGHTING_DIFFUSE; else if (!Q_stricmp (token, "entitylighting")) pass->rgbgen = RGB_GEN_ENTITY_LIGHTING_DIFFUSE; else if (!Q_stricmp (token, "exactvertex")) pass->rgbgen = RGB_GEN_VERTEX_EXACT; else if (!Q_stricmp (token, "const") || !Q_stricmp (token, "constant")) { pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.type = SHADER_FUNC_CONSTANT; Shader_ParseVector (shader, ptr, pass->rgbgen_func.args); } else if (!Q_stricmp (token, "srgb") || !Q_stricmp (token, "srgbconst")) { pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.type = SHADER_FUNC_CONSTANT; Shader_ParseVector (shader, ptr, pass->rgbgen_func.args); pass->rgbgen_func.args[0] = SRGBf(pass->rgbgen_func.args[0]); pass->rgbgen_func.args[1] = SRGBf(pass->rgbgen_func.args[1]); pass->rgbgen_func.args[2] = SRGBf(pass->rgbgen_func.args[2]); } else if (!Q_stricmp (token, "topcolor")) pass->rgbgen = RGB_GEN_TOPCOLOR; else if (!Q_stricmp (token, "bottomcolor")) pass->rgbgen = RGB_GEN_BOTTOMCOLOR; } static void Shaderpass_AlphaGen (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token; token = Shader_ParseString(ptr); if (!Q_stricmp (token, "portal")) { pass->alphagen = ALPHA_GEN_PORTAL; shader->portaldist = Shader_ParseFloat(shader, ptr, 256); if (!shader->portaldist) shader->portaldist = 256; shader->flags |= SHADER_AGEN_PORTAL; } else if (!Q_stricmp (token, "vertex")) { pass->alphagen = ALPHA_GEN_VERTEX; } else if (!Q_stricmp (token, "entity")) { pass->alphagen = ALPHA_GEN_ENTITY; } else if (!Q_stricmp (token, "wave")) { pass->alphagen = ALPHA_GEN_WAVE; Shader_ParseFunc (shader, ptr, &pass->alphagen_func); } else if ( !Q_stricmp (token, "lightingspecular")) { pass->alphagen = ALPHA_GEN_SPECULAR; } else if ( !Q_stricmp (token, "const") || !Q_stricmp (token, "constant")) { pass->alphagen = ALPHA_GEN_CONST; pass->alphagen_func.type = SHADER_FUNC_CONSTANT; pass->alphagen_func.args[0] = fabs(Shader_ParseFloat(shader, ptr, 0)); } } static void Shaderpass_AlphaShift (shader_t *shader, shaderpass_t *pass, char **ptr) //for alienarena { float speed; float min, max; pass->alphagen = ALPHA_GEN_WAVE; pass->alphagen_func.type = SHADER_FUNC_SIN; //arg0 = add //arg1 = scale //arg2 = timeshift //arg3 = timescale speed = Shader_ParseFloat(shader, ptr, 0); min = Shader_ParseFloat(shader, ptr, 0); max = Shader_ParseFloat(shader, ptr, 0); pass->alphagen_func.args[0] = min + (max - min)/2; pass->alphagen_func.args[1] = (max - min)/2; pass->alphagen_func.args[2] = 0; pass->alphagen_func.args[3] = 1/speed; } static int Shader_BlendFactor(char *name, qboolean dstnotsrc) { int factor; if (!strnicmp(name, "gl_", 3)) name += 3; if (!Q_stricmp(name, "zero")) factor = SBITS_SRCBLEND_ZERO; else if ( !Q_stricmp(name, "one")) factor = SBITS_SRCBLEND_ONE; else if (!Q_stricmp(name, "dst_color")) factor = SBITS_SRCBLEND_DST_COLOR; else if (!Q_stricmp(name, "one_minus_src_alpha")) factor = SBITS_SRCBLEND_ONE_MINUS_SRC_ALPHA; else if (!Q_stricmp(name, "src_alpha")) factor = SBITS_SRCBLEND_SRC_ALPHA; else if (!Q_stricmp(name, "src_color")) factor = SBITS_SRCBLEND_SRC_COLOR_INVALID; else if (!Q_stricmp(name, "one_minus_dst_color")) factor = SBITS_SRCBLEND_ONE_MINUS_DST_COLOR; else if (!Q_stricmp(name, "one_minus_src_color")) factor = SBITS_SRCBLEND_ONE_MINUS_SRC_COLOR_INVALID; else if (!Q_stricmp(name, "dst_alpha") ) factor = SBITS_SRCBLEND_DST_ALPHA; else if (!Q_stricmp(name, "one_minus_dst_alpha")) factor = SBITS_SRCBLEND_ONE_MINUS_DST_ALPHA; else factor = SBITS_SRCBLEND_NONE; if (dstnotsrc) { //dest factors are shifted factor <<= 4; /*gl doesn't accept dst_color for destinations*/ if (factor == SBITS_DSTBLEND_NONE || factor == SBITS_DSTBLEND_DST_COLOR_INVALID || factor == SBITS_DSTBLEND_ONE_MINUS_DST_COLOR_INVALID || factor == SBITS_DSTBLEND_ALPHA_SATURATE_INVALID) { Con_DPrintf("Invalid shader dst blend \"%s\"\n", name); factor = SBITS_DSTBLEND_ONE; } } else { /*gl doesn't accept src_color for sources*/ if (factor == SBITS_SRCBLEND_NONE || factor == SBITS_SRCBLEND_SRC_COLOR_INVALID || factor == SBITS_SRCBLEND_ONE_MINUS_SRC_COLOR_INVALID) { Con_DPrintf("Unrecognised shader src blend \"%s\"\n", name); factor = SBITS_SRCBLEND_ONE; } } return factor; } static void Shaderpass_BlendFunc (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token; //reset to defaults pass->shaderbits &= ~(SBITS_BLEND_BITS); pass->stagetype = ST_AMBIENT; token = Shader_ParseString (ptr); if ( !Q_stricmp (token, "bumpmap")) //doom3 is awkward... pass->stagetype = ST_BUMPMAP; else if ( !Q_stricmp (token, "specularmap")) //doom3 is awkward... pass->stagetype = ST_SPECULARMAP; else if ( !Q_stricmp (token, "diffusemap")) //doom3 is awkward... pass->stagetype = ST_DIFFUSEMAP; else if ( !Q_stricmp (token, "blend")) pass->shaderbits |= SBITS_SRCBLEND_SRC_ALPHA | SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA; else if ( !Q_stricmp (token, "premul")) //gets rid of feathering. pass->shaderbits |= SBITS_SRCBLEND_ONE | SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA; else if (!Q_stricmp (token, "filter")) pass->shaderbits |= SBITS_SRCBLEND_DST_COLOR | SBITS_DSTBLEND_ZERO; else if (!Q_stricmp (token, "add")) pass->shaderbits |= SBITS_SRCBLEND_ONE | SBITS_DSTBLEND_ONE; else if (!Q_stricmp (token, "replace")) pass->shaderbits |= SBITS_SRCBLEND_NONE | SBITS_DSTBLEND_NONE; else { pass->shaderbits |= Shader_BlendFactor(token, false); token = Shader_ParseString (ptr); if (*token == ',') token = Shader_ParseString (ptr); pass->shaderbits |= Shader_BlendFactor(token, true); } } static void Shaderpass_AlphaFunc (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token; pass->shaderbits &= ~SBITS_ATEST_BITS; token = Shader_ParseString (ptr); if (!Q_stricmp (token, "gt0")) { pass->shaderbits |= SBITS_ATEST_GT0; } else if (!Q_stricmp (token, "lt128")) { pass->shaderbits |= SBITS_ATEST_LT128; } else if (!Q_stricmp (token, "ge128")) { pass->shaderbits |= SBITS_ATEST_GE128; } } static void Shaderpass_DepthFunc (shader_t *shader, shaderpass_t *pass, char **ptr) { char *token; pass->shaderbits &= ~(SBITS_DEPTHFUNC_BITS); token = Shader_ParseString (ptr); if (!Q_stricmp (token, "equal")) pass->shaderbits |= SBITS_DEPTHFUNC_EQUAL; else if (!Q_stricmp (token, "lequal")) pass->shaderbits |= SBITS_DEPTHFUNC_CLOSEREQUAL; //default else if (!Q_stricmp (token, "less")) pass->shaderbits |= SBITS_DEPTHFUNC_CLOSER; else if (!Q_stricmp (token, "greater")) pass->shaderbits |= SBITS_DEPTHFUNC_FURTHER; // else if (!Q_stricmp (token, "gequal")) // pass->shaderbits |= SBITS_DEPTHFUNC_FURTHEREQUAL; // else if (!Q_stricmp (token, "nequal")) // pass->shaderbits |= SBITS_DEPTHFUNC_NOTEQUAL; // else if (!Q_stricmp (token, "never")) // pass->shaderbits |= SBITS_DEPTHFUNC_NEVER; // else if (!Q_stricmp (token, "always")) // pass->shaderbits |= SBITS_DEPTHFUNC_ALWAYS; else Con_DPrintf("Invalid depth func %s\n", token); } static void Shaderpass_DepthWrite (shader_t *shader, shaderpass_t *pass, char **ptr) { shader->flags |= SHADER_DEPTHWRITE; pass->shaderbits |= SBITS_MISC_DEPTHWRITE; } static void Shaderpass_NoDepthTest (shader_t *shader, shaderpass_t *pass, char **ptr) { shader->flags |= SHADER_DEPTHWRITE; pass->shaderbits |= SBITS_MISC_NODEPTHTEST; } static void Shaderpass_NoDepth (shader_t *shader, shaderpass_t *pass, char **ptr) { shader->flags |= SHADER_DEPTHWRITE; } static void Shaderpass_TcMod (shader_t *shader, shaderpass_t *pass, char **ptr) { int i; tcmod_t *tcmod; char *token; if (pass->numtcmods >= SHADER_MAX_TC_MODS) { return; } tcmod = &pass->tcmods[pass->numtcmods]; token = Shader_ParseString (ptr); if (!Q_stricmp (token, "rotate")) { tcmod->args[0] = -Shader_ParseFloat(shader, ptr, 0) / 360.0f; if (!tcmod->args[0]) { return; } tcmod->type = SHADER_TCMOD_ROTATE; } else if ( !Q_stricmp (token, "scale") ) { tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0); tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0); tcmod->type = SHADER_TCMOD_SCALE; } else if ( !Q_stricmp (token, "scroll") ) { tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0); tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0); tcmod->type = SHADER_TCMOD_SCROLL; } else if (!Q_stricmp(token, "stretch")) { shaderfunc_t func; Shader_ParseFunc(shader, ptr, &func); tcmod->args[0] = func.type; for (i = 1; i < 5; ++i) tcmod->args[i] = func.args[i-1]; tcmod->type = SHADER_TCMOD_STRETCH; } else if (!Q_stricmp (token, "transform")) { for (i = 0; i < 6; ++i) tcmod->args[i] = Shader_ParseFloat (shader, ptr, 0); tcmod->type = SHADER_TCMOD_TRANSFORM; } else if (!Q_stricmp (token, "turb")) { for (i = 0; i < 4; i++) tcmod->args[i] = Shader_ParseFloat (shader, ptr, 0); tcmod->type = SHADER_TCMOD_TURB; } else if (!Q_stricmp (token, "page")) { for (i = 0; i < 3; i++) tcmod->args[i] = Shader_ParseFloat (shader, ptr, 0); tcmod->type = SHADER_TCMOD_PAGE; } // else if (!Q_stricmp (token, "entityTranslate")) //RTCW // else if (!Q_stricmp (token, "swap")) //RTCW else { Con_DPrintf("Unknown tcmod %s in %s\n", token, shader->name); return; } pass->numtcmods++; } static void Shaderpass_Scale ( shader_t *shader, shaderpass_t *pass, char **ptr ) { //seperate x and y char *token; tcmod_t *tcmod; tcmod = &pass->tcmods[pass->numtcmods]; tcmod->type = SHADER_TCMOD_SCALE; token = Shader_ParseString (ptr); if (!strcmp(token, "static")) { tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0); } else { tcmod->args[0] = atof(token); } while (**ptr == ' ' || **ptr == '\t') *ptr+=1; if (**ptr == ',') *ptr+=1; token = Shader_ParseString (ptr); if (!strcmp(token, "static")) { tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0); } else { tcmod->args[1] = atof(token); } pass->numtcmods++; } static void Shaderpass_Scroll (shader_t *shader, shaderpass_t *pass, char **ptr) { //seperate x and y char *token; tcmod_t *tcmod; tcmod = &pass->tcmods[pass->numtcmods]; token = Shader_ParseString ( ptr ); if (!strcmp(token, "static")) { tcmod->type = SHADER_TCMOD_SCROLL; tcmod->args[0] = Shader_ParseFloat (shader, ptr, 0); } else { Con_Printf("Bad shader scale\n"); return; } token = Shader_ParseString ( ptr ); if (!strcmp(token, "static")) { tcmod->type = SHADER_TCMOD_SCROLL; tcmod->args[1] = Shader_ParseFloat (shader, ptr, 0); } else { Con_Printf("Bad shader scale\n"); return; } pass->numtcmods++; } static void Shaderpass_TcGen ( shader_t *shader, shaderpass_t *pass, char **ptr ) { char *token; token = Shader_ParseString ( ptr ); if ( !Q_stricmp (token, "base") ) { pass->tcgen = TC_GEN_BASE; } else if ( !Q_stricmp (token, "lightmap") ) { pass->tcgen = TC_GEN_LIGHTMAP; } else if ( !Q_stricmp (token, "environment") ) { pass->tcgen = TC_GEN_ENVIRONMENT; } else if ( !Q_stricmp (token, "fireriseenv") ) { //from RTCW pass->tcgen = TC_GEN_ENVIRONMENT; //FIXME: not supported } else if ( !Q_stricmp (token, "vector") ) { pass->tcgen = TC_GEN_VECTOR; Shader_ParseVector (shader, ptr, pass->tcgenvec[0]); Shader_ParseVector (shader, ptr, pass->tcgenvec[1]); } else if ( !Q_stricmp (token, "normal") ) { pass->tcgen = TC_GEN_NORMAL; } else if ( !Q_stricmp (token, "svector") ) { pass->tcgen = TC_GEN_SVECTOR; } else if ( !Q_stricmp (token, "tvector") ) { pass->tcgen = TC_GEN_TVECTOR; } else if ( !Q_stricmp (token, "skybox") ) { pass->tcgen = TC_GEN_SKYBOX; } } static void Shaderpass_EnvMap ( shader_t *shader, shaderpass_t *pass, char **ptr ) //for alienarena { pass->tcgen = TC_GEN_ENVIRONMENT; } static void Shaderpass_Detail ( shader_t *shader, shaderpass_t *pass, char **ptr ) { pass->flags |= SHADER_PASS_DETAIL; } static void Shaderpass_AlphaMask ( shader_t *shader, shaderpass_t *pass, char **ptr ) { pass->shaderbits &= ~SBITS_ATEST_BITS; pass->shaderbits |= SBITS_ATEST_GE128; } static void Shaderpass_NoLightMap ( shader_t *shader, shaderpass_t *pass, char **ptr ) { pass->rgbgen = RGB_GEN_IDENTITY; } static void Shaderpass_Red(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.args[0] = Shader_ParseFloat(shader, ptr, 0); } static void Shaderpass_Green(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.args[1] = Shader_ParseFloat(shader, ptr, 0); } static void Shaderpass_Blue(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->rgbgen = RGB_GEN_CONST; pass->rgbgen_func.args[2] = Shader_ParseFloat(shader, ptr, 0); } static void Shaderpass_Alpha(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->alphagen = ALPHA_GEN_CONST; pass->alphagen_func.args[0] = Shader_ParseFloat(shader, ptr, 0); } static void Shaderpass_MaskColor(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->shaderbits |= SBITS_MASK_RED|SBITS_MASK_GREEN|SBITS_MASK_BLUE; } static void Shaderpass_MaskRed(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->shaderbits |= SBITS_MASK_RED; } static void Shaderpass_MaskGreen(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->shaderbits |= SBITS_MASK_GREEN; } static void Shaderpass_MaskBlue(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->shaderbits |= SBITS_MASK_BLUE; } static void Shaderpass_MaskAlpha(shader_t *shader, shaderpass_t *pass, char **ptr) { pass->shaderbits |= SBITS_MASK_ALPHA; } static void Shaderpass_AlphaTest(shader_t *shader, shaderpass_t *pass, char **ptr) { if (Shader_ParseFloat(shader, ptr, 0) == 0.5) pass->shaderbits |= SBITS_ATEST_GE128; else Con_Printf("unsupported alphatest value\n"); } static void Shaderpass_TexGen(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); if (!strcmp(token, "normal")) pass->tcgen = TC_GEN_NORMAL; else if (!strcmp(token, "skybox")) pass->tcgen = TC_GEN_SKYBOX; else if (!strcmp(token, "wobblesky")) { pass->tcgen = TC_GEN_WOBBLESKY; token = Shader_ParseString(ptr); token = Shader_ParseString(ptr); token = Shader_ParseString(ptr); } else if (!strcmp(token, "reflect")) pass->tcgen = TC_GEN_REFLECT; else { Con_Printf("texgen token not understood\n"); } } static void Shaderpass_CubeMap(shader_t *shader, shaderpass_t *pass, char **ptr) { char *token = Shader_ParseString(ptr); if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_SKYBOX; pass->texgen = T_GEN_CUBEMAP; pass->anim_frames[0] = Shader_FindImage(token, IF_CUBEMAP); if (!TEXVALID(pass->anim_frames[0])) { pass->texgen = T_GEN_SINGLEMAP; pass->anim_frames[0] = missing_texture; } } static shaderkey_t shaderpasskeys[] = { #define Q3 NULL {"rgbgen", Shaderpass_RGBGen, Q3}, {"alphagen", Shaderpass_AlphaGen, Q3}, {"blendfunc", Shaderpass_BlendFunc, Q3}, {"depthfunc", Shaderpass_DepthFunc, Q3}, {"depthwrite", Shaderpass_DepthWrite, Q3}, {"alphafunc", Shaderpass_AlphaFunc, Q3}, {"tcmod", Shaderpass_TcMod, Q3}, {"map", Shaderpass_Map, Q3}, {"animmap", Shaderpass_AnimMap, Q3}, {"clampmap", Shaderpass_ClampMap, Q3}, {"videomap", Shaderpass_VideoMap, Q3}, {"tcgen", Shaderpass_TcGen, Q3}, {"texgen", Shaderpass_TcGen, Q3}, {"detail", Shaderpass_Detail, Q3}, {"nodepthtest", Shaderpass_NoDepthTest, NULL}, {"nodepth", Shaderpass_NoDepth, NULL}, {"envmap", Shaderpass_EnvMap, "rscript"},//for alienarena {"nolightmap", Shaderpass_NoLightMap, "rscript"},//for alienarena {"scale", Shaderpass_Scale, "rscript"},//for alienarena {"scroll", Shaderpass_Scroll, "rscript"},//for alienarena {"alphashift", Shaderpass_AlphaShift, "rscript"},//for alienarena {"alphamask", Shaderpass_AlphaMask, "rscript"},//for alienarena {"program", Shaderpass_ProgramName, "fte"}, /*doom3 compat*/ {"blend", Shaderpass_BlendFunc, "doom3"}, {"maskcolor", Shaderpass_MaskColor, "doom3"}, {"maskred", Shaderpass_MaskRed, "doom3"}, {"maskgreen", Shaderpass_MaskGreen, "doom3"}, {"maskblue", Shaderpass_MaskBlue, "doom3"}, {"maskalpha", Shaderpass_MaskAlpha, "doom3"}, {"alphatest", Shaderpass_AlphaTest, "doom3"}, {"texgen", Shaderpass_TexGen, "doom3"}, {"cubemap", Shaderpass_CubeMap, "doom3"}, //one of these is wrong {"cameracubemap",Shaderpass_CubeMap, "doom3"}, //one of these is wrong {"red", Shaderpass_Red, "doom3"}, {"green", Shaderpass_Green, "doom3"}, {"blue", Shaderpass_Blue, "doom3"}, {"alpha", Shaderpass_Alpha, "doom3"}, //RTCW //fancy map lines use the map if that mode is active. //FIXME: actually check these to ensure there's no issues with any shaders overriding the pass's previously specified map // (hopefully no shaders would actually do that due to the engine loading both textures, which would be wasteful) {"map16", Shaderpass_RTCW_Map_16bit, "rtcw"}, {"map32", Shaderpass_RTCW_Map_32bit, "rtcw"}, {"mapcomp", Shaderpass_RTCW_Map_s3tc, "rtcw"}, {"mapnocomp", Shaderpass_RTCW_Map_nos3tc, "rtcw"}, {"animcompmap", Shaderpass_RTCW_AnimMap_s3tc, "rtcw"}, {"animnocompmap",Shaderpass_RTCW_AnimMap_nos3tc,"rtcw"}, //qfusion/warsow compat {"material", Shaderpass_QF_Material, "qf"}, {"animclampmap",Shaderpass_QF_AnimClampMap, "qf"}, // {"cubemap", Shaderpass_QF_CubeMap, "qf"}, // {"shadecubemap",Shaderpass_QF_ShadeCubeMap, "qf"}, {"surroundmap", Shaderpass_CubeMap, "qf"}, // {"distortion", Shaderpass_QF_Distortion, "qf"}, // {"celshade", Shaderpass_QF_Celshade, "qf"}, // {"grayscale", Shaderpass_QF_Greyscale, "qf"}, // {"greyscale", Shaderpass_QF_Greyscale, "qf"}, // {"skip", Shaderpass_QF_Skip, "qf"}, {NULL, NULL} }; // =============================================================== void Shader_FreePass (shaderpass_t *pass) { #ifdef HAVE_MEDIA_DECODER if ( pass->flags & SHADER_PASS_VIDEOMAP ) { Media_ShutdownCin(pass->cin); pass->cin = NULL; } #endif if (pass->prog) { Shader_ReleaseGeneric(pass->prog); pass->prog = NULL; } } void Shader_ReleaseGeneric(program_t *prog) { if (prog) if (prog->refs-- == 1) Shader_UnloadProg(prog); } void Shader_Free (shader_t *shader) { int i; shaderpass_t *pass; if (shader->bucket.data == shader) Hash_RemoveData(&shader_active_hash, shader->name, shader); shader->bucket.data = NULL; Shader_ReleaseGeneric(shader->prog); shader->prog = NULL; if (shader->skydome) Z_Free (shader->skydome); shader->skydome = NULL; while (shader->clutter) { void *t = shader->clutter; shader->clutter = shader->clutter->next; Z_Free(t); } pass = shader->passes; for (i = 0; i < shader->numpasses; i++, pass++) { Shader_FreePass (pass); } shader->numpasses = 0; if (shader->genargs) { free(shader->genargs); shader->genargs = NULL; } shader->uses = 0; Z_Free(shader->defaulttextures); shader->defaulttextures = NULL; } int QDECL Shader_InitCallback (const char *name, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath) { Shader_MakeCache(name, SPF_DEFAULT); return true; } int QDECL Shader_InitCallback_Doom3 (const char *name, qofs_t size, time_t mtime, void *param, searchpathfuncs_t *spath) { Shader_MakeCache(name, SPF_DOOM3); return true; } qboolean Shader_Init (void) { int wibuf[16]; if (!r_shaders) { r_numshaders = 0; r_maxshaders = 256; r_shaders = calloc(r_maxshaders, sizeof(*r_shaders)); shader_hash = calloc (HASH_SIZE, sizeof(*shader_hash)); shader_active_hash_mem = malloc(Hash_BytesForBuckets(1024)); memset(shader_active_hash_mem, 0, Hash_BytesForBuckets(1024)); Hash_InitTable(&shader_active_hash, 1024, shader_active_hash_mem); Shader_FlushGenerics(); if (!sh_config.progs_supported) sh_config.max_gpu_bones = 0; else { extern cvar_t r_max_gpu_bones; if (!*r_max_gpu_bones.string) { #ifdef FTE_TARGET_WEB sh_config.max_gpu_bones = 0; //webgl tends to crap out if this is too high, so 32 is a good enough value to play safe. some browsers have really shitty uniform performance too, so lets just default to pure-cpu transforms. in javascript. yes, its that bad. #else sh_config.max_gpu_bones = 64; //ATI drivers bug out and start to crash if you put this at 128. #endif } else sh_config.max_gpu_bones = bound(0, r_max_gpu_bones.ival, MAX_BONES); } } memset(wibuf, 0xff, sizeof(wibuf)); if (!qrenderer) r_whiteimage = r_nulltex; else r_whiteimage = R_LoadTexture("$whiteimage", 4, 4, TF_RGBA32, wibuf, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA); memset(wibuf, 0, sizeof(wibuf)); r_blackimage = R_LoadTexture("$blackimage", 4, 4, TF_RGBA32, wibuf, IF_NOMIPMAP|IF_NOPICMIP|IF_NEAREST|IF_NOGAMMA); Shader_NeedReload(true); Shader_DoReload(); return true; } void Shader_FlushCache(void) { shadercachefile_t *sf; shadercache_t *cache, *cache_next; int i; for (i = 0; i < HASH_SIZE; i++) { cache = shader_hash[i]; shader_hash[i] = NULL; for (; cache; cache = cache_next) { cache_next = cache->hash_next; cache->hash_next = NULL; Z_Free(cache); } } while(shaderfiles) { sf = shaderfiles; shaderfiles = sf->next; if (sf->data) FS_FreeFile(sf->data); Z_Free(sf); } } static void Shader_MakeCache(const char *path, unsigned int parseflags) { unsigned int key; char *buf, *ptr, *token; shadercache_t *cache; shadercachefile_t *cachefile, *filelink = NULL; qofs_t size; for (cachefile = shaderfiles; cachefile; cachefile = cachefile->next) { if (!Q_stricmp(cachefile->name, path)) return; //already loaded. there's no source package or anything. filelink = cachefile; } Con_DPrintf ("...loading '%s'\n", path); cachefile = Z_Malloc(sizeof(*cachefile) + strlen(path)); strcpy(cachefile->name, path); size = FS_LoadFile(path, (void **)&cachefile->data); cachefile->length = size; cachefile->parseflags = parseflags; if (filelink) filelink->next = cachefile; else shaderfiles = cachefile; if (qofs_Error(size)) { Con_Printf("Unable to read %s\n", path); cachefile->length = 0; return; } if (size > 1024*1024*64) //sanity limit { Con_Printf("Refusing to parse %s due to size\n", path); cachefile->length = 0; FS_FreeFile(cachefile->data); cachefile->data = NULL; return; } ptr = buf = cachefile->data; size = cachefile->length; //look for meta comments. while (1) { //parse metas while (*ptr == ' ' || *ptr == '\t') ptr++; if (ptr[0] == '\r' && ptr[1] == '\n') ptr+=2; //blank line with dos ending else if (ptr[0] == '\r' || ptr[0] == '\n') ptr+=1; //blank line with mac or unix ending else if (ptr[0] == '/' && ptr[1] == '/') { char *e = strchr(ptr, '\n'); if (e) e++; else e = ptr + strlen(ptr); ptr += 2; while (*ptr == ' ' || *ptr == '\t') ptr++; if (!strncmp(ptr, "meta:", 5)) { ptr+=5; token = COM_ParseExt (&ptr, false, true); if (!strcmp(token, "forceprogramify")) { cachefile->parseflags |= SPF_PROGRAMIFY; token = COM_ParseExt (&ptr, false, true); if (*token) Q_strncpyz(cachefile->forcedshadername, token, sizeof(cachefile->forcedshadername)); } else Con_DPrintf("unknown shader meta term \"%s\" in %s\n", token, path); while (*ptr == ' ' || *ptr == '\t') ptr++; if (*ptr != '\r' && *ptr != '\n') { while (*ptr && (*ptr != '\r' && *ptr != '\n')) ptr++; Con_DPrintf("junk after shader meta in %s\n", path); } } ptr = e; } else break; //the actual shader started. } //now scan the file looking for each individual shader. do { if ( ptr - buf >= size ) break; token = COM_ParseExt (&ptr, true, true); if ( !token[0] || ptr - buf >= size ) break; COM_CleanUpPath(token); if (Shader_LocateSource(token, NULL, NULL, NULL, NULL)) { ptr = Shader_Skip ( ptr ); continue; } key = Hash_Key ( token, HASH_SIZE ); cache = ( shadercache_t * )Z_Malloc(sizeof(shadercache_t) + strlen(token)); strcpy(cache->name, token); cache->hash_next = shader_hash[key]; cache->source = cachefile; cache->offset = ptr - cachefile->data; shader_hash[key] = cache; ptr = Shader_Skip ( ptr ); } while ( ptr ); } static qboolean Shader_LocateSource(char *name, char **buf, size_t *bufsize, size_t *offset, shadercachefile_t **sourcefile) { unsigned int key; shadercache_t *cache; key = Hash_Key ( name, HASH_SIZE ); cache = shader_hash[key]; for ( ; cache; cache = cache->hash_next ) { if ( !Q_stricmp (cache->name, name) ) { if (buf) { *buf = cache->source->data; *bufsize = cache->source->length; *offset = cache->offset; *sourcefile = cache->source; } return true; } } return false; } char *Shader_Skip ( char *ptr ) { char *tok; int brace_count; // Opening brace tok = COM_ParseExt(&ptr, true, true); if (!ptr) return NULL; if ( tok[0] != '{' ) { tok = COM_ParseExt (&ptr, true, true); } for (brace_count = 1; brace_count > 0; ) { tok = COM_ParseExt (&ptr, true, true); if ( !tok[0] ) return NULL; if (tok[0] == '{') { brace_count++; } else if (tok[0] == '}') { brace_count--; } } return ptr; } void Shader_Reset(shader_t *s) { extern cvar_t r_refractreflect_scale; char name[MAX_QPATH]; int id = s->id; int uses = s->uses; shader_gen_t *defaultgen = s->generator; char *genargs = s->genargs; texnums_t *dt = s->defaulttextures; int dtcount = s->numdefaulttextures; float dtrate = s->defaulttextures_fps; //FIXME! int w = s->width; int h = s->height; unsigned int uf = s->usageflags; Q_strncpyz(name, s->name, sizeof(name)); s->genargs = NULL; s->defaulttextures = NULL; Shader_Free(s); memset(s, 0, sizeof(*s)); s->portalfboscale = r_refractreflect_scale.value; //unless otherwise specified, this cvar specifies the value. s->remapto = s; s->id = id; s->width = w; s->height = h; s->defaulttextures = dt; s->numdefaulttextures = dtcount; s->defaulttextures_fps = dtrate; s->generator = defaultgen; s->genargs = genargs; s->usageflags = uf; s->uses = uses; Q_strncpyz(s->name, name, sizeof(s->name)); Hash_Add(&shader_active_hash, s->name, s, &s->bucket); } void Shader_Shutdown (void) { int i; shader_t *shader; if (!r_shaders) return; /*nothing needs freeing yet*/ for (i = 0; i < r_numshaders; i++) { shader = r_shaders[i]; if (!shader) continue; Shader_Free(shader); Z_Free(r_shaders[i]); r_shaders[i] = NULL; } Shader_FlushCache(); Shader_FlushGenerics(); R_SkyShutdown(); r_maxshaders = 0; r_numshaders = 0; free(r_shaders); r_shaders = NULL; free(shader_hash); shader_hash = NULL; free(shader_active_hash_mem); shader_active_hash_mem = NULL; shader_reload_needed = false; } void Shader_SetBlendmode (shaderpass_t *pass, shaderpass_t *lastpass) { qboolean lightmapoverbright; if (pass->texgen == T_GEN_DELUXMAP) { pass->blendmode = PBM_DOTPRODUCT; return; } if (pass->texgen < T_GEN_DIFFUSE && !TEXVALID(pass->anim_frames[0]) && !(pass->flags & SHADER_PASS_LIGHTMAP)) { pass->blendmode = PBM_MODULATE; return; } if (!(pass->shaderbits & SBITS_BLEND_BITS)) { if (pass->texgen == T_GEN_LIGHTMAP && lastpass) pass->blendmode = PBM_OVERBRIGHT; else if ((pass->rgbgen == RGB_GEN_IDENTITY) && (pass->alphagen == ALPHA_GEN_IDENTITY)) { pass->blendmode = PBM_REPLACE; return; } else if ((pass->rgbgen == RGB_GEN_IDENTITY_LIGHTING) && (pass->alphagen == ALPHA_GEN_IDENTITY)) { pass->shaderbits &= ~SBITS_BLEND_BITS; pass->shaderbits |= SBITS_SRCBLEND_ONE; pass->shaderbits |= SBITS_DSTBLEND_ZERO; pass->blendmode = PBM_REPLACELIGHT; } else { pass->shaderbits &= ~SBITS_BLEND_BITS; pass->shaderbits |= SBITS_SRCBLEND_ONE; pass->shaderbits |= SBITS_DSTBLEND_ZERO; pass->blendmode = PBM_MODULATE; } return; } lightmapoverbright = pass->texgen == T_GEN_LIGHTMAP || (lastpass && lastpass->texgen == T_GEN_LIGHTMAP && lastpass->blendmode != PBM_OVERBRIGHT); if (((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ZERO|SBITS_DSTBLEND_SRC_COLOR)) || ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_DST_COLOR|SBITS_DSTBLEND_ZERO)) || ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_DST_COLOR|SBITS_DSTBLEND_ONE_MINUS_DST_ALPHA))) pass->blendmode = lightmapoverbright?PBM_OVERBRIGHT:PBM_MODULATE; else if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ONE)) pass->blendmode = PBM_ADD; else if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_SRC_ALPHA|SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA)) pass->blendmode = PBM_DECAL; else pass->blendmode = lightmapoverbright?PBM_OVERBRIGHT:PBM_MODULATE; } void Shader_FixupProgPasses(shader_t *shader, shaderpass_t *pass) { int i; int maxpasses = SHADER_PASS_MAX - (pass-shader->passes); struct { int gen; unsigned int flags; } defaulttgen[] = { //light {T_GEN_SHADOWMAP, 0}, //0 {T_GEN_LIGHTCUBEMAP, 0}, //1 //material {T_GEN_DIFFUSE, SHADER_HASDIFFUSE}, //2 {T_GEN_NORMALMAP, SHADER_HASNORMALMAP}, //3 {T_GEN_SPECULAR, SHADER_HASGLOSS}, //4 {T_GEN_UPPEROVERLAY, SHADER_HASTOPBOTTOM}, //5 {T_GEN_LOWEROVERLAY, SHADER_HASTOPBOTTOM}, //6 {T_GEN_FULLBRIGHT, SHADER_HASFULLBRIGHT}, //7 {T_GEN_PALETTED, SHADER_HASPALETTED}, //8 {T_GEN_REFLECTCUBE, 0}, //9 {T_GEN_REFLECTMASK, 0}, //10 {T_GEN_DISPLACEMENT, SHADER_HASDISPLACEMENT},//11 // {T_GEN_REFLECTION, SHADER_HASREFLECT}, // // {T_GEN_REFRACTION, SHADER_HASREFRACT}, // // {T_GEN_REFRACTIONDEPTH, SHADER_HASREFRACTDEPTH},// // {T_GEN_RIPPLEMAP, SHADER_HASRIPPLEMAP}, // //batch {T_GEN_LIGHTMAP, SHADER_HASLIGHTMAP}, //12 {T_GEN_DELUXMAP, 0}, //13 //more lightmaps //14,15,16 //mode deluxemaps //17,18,19 }; #ifdef HAVE_MEDIA_DECODER cin_t *cin = R_ShaderGetCinematic(shader); #endif //if the glsl doesn't specify all samplers, just trim them. pass->numMergedPasses = pass->prog->numsamplers; #ifdef HAVE_MEDIA_DECODER if (cin && R_ShaderGetCinematic(shader) == cin) cin = NULL; #endif //if the glsl has specific textures listed, be sure to provide a pass for them. for (i = 0; i < sizeof(defaulttgen)/sizeof(defaulttgen[0]); i++) { if (pass->prog->defaulttextures & (1u<numMergedPasses >= maxpasses) { //panic... parsestate.droppass = true; break; } pass[pass->numMergedPasses].flags |= SHADER_PASS_NOCOLORARRAY; pass[pass->numMergedPasses].flags &= ~SHADER_PASS_DEPTHCMP; if (defaulttgen[i].gen == T_GEN_SHADOWMAP) pass[pass->numMergedPasses].flags |= SHADER_PASS_DEPTHCMP; #ifdef HAVE_MEDIA_DECODER if (!i && cin) { pass[pass->numMergedPasses].texgen = T_GEN_VIDEOMAP; pass[pass->numMergedPasses].cin = cin; cin = NULL; } else #endif { pass[pass->numMergedPasses].texgen = defaulttgen[i].gen; #ifdef HAVE_MEDIA_DECODER pass[pass->numMergedPasses].cin = NULL; #endif } pass->numMergedPasses++; shader->flags |= defaulttgen[i].flags; } } //must have at least one texture. if (!pass->numMergedPasses) { #ifdef HAVE_MEDIA_DECODER pass[0].texgen = cin?T_GEN_VIDEOMAP:T_GEN_DIFFUSE; pass[0].cin = cin; #else pass[0].texgen = T_GEN_DIFFUSE; #endif pass->numMergedPasses = 1; } #ifdef HAVE_MEDIA_DECODER else if (cin) Media_ShutdownCin(cin); #endif shader->numpasses = (pass-shader->passes)+pass->numMergedPasses; } struct scondinfo_s { int depth; int level[8]; #define COND_IGNORE 1 #define COND_IGNOREPARENT 2 #define COND_ALLOWELSE 4 #define COND_TAKEN 8 }; static qboolean Shader_Conditional_Read(shader_t *shader, struct scondinfo_s *cond, char *token, char **ptr) { if (!Q_stricmp(token, "if")) { if (cond->depth+1 == countof(cond->level)) { Con_Printf("if statements nest too deeply in shader %s\n", shader->name); *ptr += strlen(*ptr); return true; } cond->depth++; cond->level[cond->depth] = (Shader_EvaluateCondition(shader, ptr)?0:COND_IGNORE); cond->level[cond->depth] |= COND_ALLOWELSE; if (cond->level[cond->depth-1] & (COND_IGNORE|COND_IGNOREPARENT)) cond->level[cond->depth] |= COND_IGNOREPARENT; //if ignoring the parent, ignore this one too, even if valid if (!(cond->level[cond->depth] & (COND_IGNORE|COND_IGNOREPARENT))) cond->level[cond->depth] |= COND_TAKEN; //if we're not ignoring the contained commands then flag it so we don't take any elifs/elses } else if (!Q_stricmp(token, "elif")) { if (cond->level[cond->depth] & COND_ALLOWELSE) { if (cond->level[cond->depth] & COND_TAKEN) { //if we took the if/elif then don't take this elif either Shader_EvaluateCondition(shader, ptr); cond->level[cond->depth] = COND_ALLOWELSE|COND_TAKEN|COND_IGNORE; } else { cond->level[cond->depth] = (Shader_EvaluateCondition(shader, ptr)?0:COND_IGNORE); cond->level[cond->depth] |= COND_ALLOWELSE; } if (cond->level[cond->depth-1] & (COND_IGNORE|COND_IGNOREPARENT)) cond->level[cond->depth] |= COND_IGNOREPARENT; if (!(cond->level[cond->depth] & (COND_IGNORE|COND_IGNOREPARENT))) cond->level[cond->depth] |= COND_TAKEN; } else { Con_Printf("unexpected elif statement in shader %s\n", shader->name); *ptr += strlen(*ptr); } } else if (!Q_stricmp(token, "endif")) { if (!cond->depth) { Con_Printf("endif without if in shader %s\n", shader->name); *ptr += strlen(*ptr); return true; } cond->depth--; } else if (!Q_stricmp(token, "else")) { if (cond->level[cond->depth] & COND_ALLOWELSE) { if (cond->level[cond->depth] & COND_TAKEN) cond->level[cond->depth] |= COND_IGNORE; else cond->level[cond->depth] ^= COND_IGNORE; cond->level[cond->depth] &= ~COND_ALLOWELSE; } else { Con_Printf("unexpected else statement in shader %s\n", shader->name); *ptr += strlen(*ptr); } } else if (cond->level[cond->depth] & (COND_IGNORE|COND_IGNOREPARENT)) { //eat it while (ptr) { token = COM_ParseExt(ptr, false, true); if ( !token[0] ) break; } } else return false; return true; } void Shader_Readpass (parsestate_t *ps) { shader_t *shader = ps->s; char *token; shaderpass_t *pass; static shader_t dummy; struct scondinfo_s cond = {0}; unsigned int oldflags = shader->flags; if ( shader->numpasses >= SHADER_PASS_MAX ) { parsestate.droppass = true; shader = &dummy; shader->numpasses = 1; pass = shader->passes; } else { parsestate.droppass = false; pass = &shader->passes[shader->numpasses++]; } // Set defaults pass->flags = 0; pass->anim_frames[0] = r_nulltex; pass->anim_numframes = 0; pass->rgbgen = RGB_GEN_UNKNOWN; pass->alphagen = ALPHA_GEN_IDENTITY; pass->tcgen = TC_GEN_UNSPECIFIED; pass->numtcmods = 0; pass->stagetype = ST_AMBIENT; pass->numMergedPasses = 0; if (shader->flags & SHADER_NOMIPMAPS) pass->flags |= SHADER_PASS_NOMIPMAP; ps->pass = pass; while ( ps->ptr ) { token = COM_ParseExt (&ps->ptr, true, true); if ( !token[0] ) { continue; } else if (!Shader_Conditional_Read(shader, &cond, token, &ps->ptr)) { if ( token[0] == '}' ) break; else if (token[0] == '{') Con_Printf("unexpected indentation in %s\n", shader->name); else if ( Shader_Parsetok (ps, shaderpasskeys, token) ) break; } } //if there was no texgen, then its too late now. if (!pass->numMergedPasses) pass->numMergedPasses = 1; if (cond.depth) { Con_Printf("if statements without endif in shader %s\n", shader->name); } if (pass->tcgen == TC_GEN_UNSPECIFIED) pass->tcgen = TC_GEN_BASE; if (!parsestate.droppass) { switch(pass->stagetype) { case ST_DIFFUSEMAP: if (pass->texgen == T_GEN_SINGLEMAP) shader->defaulttextures->base = pass->anim_frames[0]; break; case ST_AMBIENT: break; case ST_BUMPMAP: if (pass->texgen == T_GEN_SINGLEMAP) shader->defaulttextures->bump = pass->anim_frames[0]; parsestate.droppass = true; //fixme: scrolling etc may be important. but we're not doom3. break; case ST_SPECULARMAP: if (pass->texgen == T_GEN_SINGLEMAP) shader->defaulttextures->specular = pass->anim_frames[0]; parsestate.droppass = true; //fixme: scrolling etc may be important. but we're not doom3. break; } } // check some things if (!parsestate.droppass) if ((pass->shaderbits&SBITS_BLEND_BITS) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO)) { pass->shaderbits |= SBITS_MISC_DEPTHWRITE; shader->flags |= SHADER_DEPTHWRITE; } switch (pass->rgbgen) { case RGB_GEN_IDENTITY_LIGHTING: case RGB_GEN_IDENTITY: case RGB_GEN_CONST: case RGB_GEN_WAVE: case RGB_GEN_ENTITY: case RGB_GEN_ONE_MINUS_ENTITY: case RGB_GEN_UNKNOWN: // assume RGB_GEN_IDENTITY or RGB_GEN_IDENTITY_LIGHTING switch (pass->alphagen) { case ALPHA_GEN_IDENTITY: case ALPHA_GEN_CONST: case ALPHA_GEN_WAVE: case ALPHA_GEN_ENTITY: pass->flags |= SHADER_PASS_NOCOLORARRAY; break; default: break; } break; default: break; } /*if ((shader->flags & SHADER_SKY) && (shader->flags & SHADER_DEPTHWRITE)) { #ifdef warningmsg #pragma warningmsg("is this valid?") #endif pass->shaderbits &= ~SBITS_MISC_DEPTHWRITE; } */ //if this pass specified a program, make sure it has all the textures that the program requires if (!parsestate.droppass && pass->prog) Shader_FixupProgPasses(shader, pass); if (parsestate.droppass) { while (pass->numMergedPasses > 0) { Shader_FreePass (pass+--pass->numMergedPasses); shader->numpasses--; } shader->flags = oldflags; } ps->pass = NULL; } //we've read the first token, now make sense of it and any args static qboolean Shader_Parsetok(parsestate_t *ps, shaderkey_t *keys, char *token) { shaderkey_t *key; char *prefix; qboolean toolchainprefix = false; if (*token == '_') { //forward compat: make sure there's a way to shut stuff up if you're using future extensions in an outdated engine. token++; toolchainprefix = true; } //handle known prefixes. if (!Q_strncasecmp(token, "fte", 3)) {prefix = token; token += 3; } else if (!Q_strncasecmp(token, "dp", 2)) {prefix = token; token += 2; } else if (!Q_strncasecmp(token, "doom3", 5)) {prefix = token; token += 5; } else if (!Q_strncasecmp(token, "rscript", 7)) {prefix = token; token += 7; } else if (!Q_strncasecmp(token, "qer_", 4)) {prefix = token; token += 3; toolchainprefix = true; } else if (!Q_strncasecmp(token, "q3map_", 6)) {prefix = token; token += 5; toolchainprefix = true; } else prefix = NULL; if (prefix && *token == '_') token++; for (key = keys; key->keyword != NULL; key++) { if (!Q_stricmp (token, key->keyword)) { if (!prefix || (prefix && key->prefix && !Q_strncasecmp(prefix, key->prefix, strlen(key->prefix)))) { if (key->func) key->func ( ps->s, ps->pass, &ps->ptr ); return (ps->ptr && *ps->ptr == '}' ); } } } if (!toolchainprefix) //we don't really give a damn about prefixes owned by various toolchains - they shouldn't affect us. { if (prefix) Con_DPrintf("Unknown shader directive parsing %s: \"%s\"\n", ps->s->name, prefix); else Con_DPrintf("Unknown shader directive parsing %s: \"%s\"\n", ps->s->name, token); } // Next Line while (ps->ptr) { token = COM_ParseExt(&ps->ptr, false, true); if ( !token[0] ) { break; } } return false; } void Shader_SetPassFlush (shaderpass_t *pass, shaderpass_t *pass2) { if (((pass->flags & SHADER_PASS_DETAIL) && !r_detailtextures.value) || ((pass2->flags & SHADER_PASS_DETAIL) && !r_detailtextures.value) || (pass->flags & SHADER_PASS_VIDEOMAP) || (pass2->flags & SHADER_PASS_VIDEOMAP)) { return; } //don't merge passes if they're got their own programs. if (pass->prog || pass2->prog) return; /*identity alpha is required for merging*/ if (pass->alphagen != ALPHA_GEN_IDENTITY || pass2->alphagen != ALPHA_GEN_IDENTITY) return; /*don't merge passes if the hardware cannot support it*/ if (pass->numMergedPasses >= be_maxpasses) return; /*rgbgen must be identity too except if the later pass is identity_ligting, in which case all is well and we can switch the first pass to identity_lighting instead*/ if (pass2->rgbgen == RGB_GEN_IDENTITY_LIGHTING && (pass2->blendmode == PBM_OVERBRIGHT || pass2->blendmode == PBM_MODULATE) && pass->rgbgen == RGB_GEN_IDENTITY) { if (pass->blendmode == PBM_REPLACE) pass->blendmode = PBM_REPLACELIGHT; pass->rgbgen = RGB_GEN_IDENTITY_LIGHTING; pass2->rgbgen = RGB_GEN_IDENTITY; } /*rgbgen must be identity (or the first is identity_lighting)*/ else if (pass2->rgbgen != RGB_GEN_IDENTITY || (pass->rgbgen != RGB_GEN_IDENTITY && pass->rgbgen != RGB_GEN_IDENTITY_LIGHTING)) return; /*if its alphatest, don't merge with anything other than lightmap (although equal stuff can be merged)*/ if ((pass->shaderbits & SBITS_ATEST_BITS) && (((pass2->shaderbits & SBITS_DEPTHFUNC_BITS) != SBITS_DEPTHFUNC_EQUAL) || pass2->texgen != T_GEN_LIGHTMAP)) return; if ((pass->shaderbits & SBITS_MASK_BITS) != (pass2->shaderbits & SBITS_MASK_BITS)) return; // check if we can use multiple passes if (pass2->blendmode == PBM_DOTPRODUCT) { pass->numMergedPasses++; } else if (pass->numMergedPasses < be_maxpasses) { if (pass->blendmode == PBM_REPLACE || pass->blendmode == PBM_REPLACELIGHT) { if ((pass2->blendmode == PBM_DECAL && sh_config.tex_env_combine) || (pass2->blendmode == PBM_ADD && sh_config.env_add) || (pass2->blendmode && pass2->blendmode != PBM_ADD) || sh_config.nv_tex_env_combine4) { pass->numMergedPasses++; } } else if (pass->blendmode == PBM_ADD && pass2->blendmode == PBM_ADD && sh_config.env_add) { pass->numMergedPasses++; } else if ((pass->blendmode == PBM_MODULATE || pass->blendmode == PBM_OVERBRIGHT) && (pass2->blendmode == PBM_MODULATE || pass2->blendmode == PBM_OVERBRIGHT)) { pass->numMergedPasses++; } else if (pass->blendmode == PBM_DECAL && pass2->blendmode == PBM_OVERBRIGHT) pass->numMergedPasses++; //HACK: allow modulating overbright passes with decal passes. overbright passes need to blend with something for their lighting to be correct, so this is a tradeoff. else return; } else return; /*if (pass->texgen == T_GEN_LIGHTMAP && (pass->blendmode == PBM_REPLACE || pass->blendmode == PBM_REPLACELIGHT) && pass2->blendmode == PBM_MODULATE && sh_config.tex_env_combine) { // if (pass->rgbgen == RGB_GEN_IDENTITY) // pass->rgbgen = RGB_GEN_IDENTITY_OVERBRIGHT; //get the light levels right // pass2->blendmode = PBM_OVERBRIGHT; } if (pass2->texgen == T_GEN_LIGHTMAP && pass2->blendmode == PBM_OVERBRIGHT && sh_config.tex_env_combine) { // if (pass->rgbgen == RGB_GEN_IDENTITY) // pass->rgbgen = RGB_GEN_IDENTITY_OVERBRIGHT; //get the light levels right // pass->blendmode = PBM_REPLACELIGHT; // pass2->blendmode = PBM_OVERBRIGHT; pass->blendmode = PBM_OVERBRIGHT; } */ } const char *Shader_AlphaMaskProgArgs(shader_t *s) { if (s->numpasses) { //alpha mask values ALWAYS come from the first pass. shaderpass_t *pass = &s->passes[0]; switch(pass->shaderbits & SBITS_ATEST_BITS) { default: break; //cases inverted. the atest is to retain the pixel, glsl is to discard (alpha OP MASK). case SBITS_ATEST_GT0: return "#MASK=0.0#MASKLT=1"; case SBITS_ATEST_LT128: return "#MASK=0.5"; case SBITS_ATEST_GE128: return "#MASK=0.5#MASKLT=1"; //ignore the eq part. } } return ""; } void Shader_Programify (shader_t *s) { unsigned int reflectrefract = 0; const char *prog = NULL; const char *mask; char args[1024]; qboolean eightbit = false; /* enum { T_UNKNOWN, T_WALL, T_MODEL } type = 0;*/ int i; shaderpass_t *pass, *lightmap = NULL, *modellighting = NULL, *vertexlighting = NULL; for (i = 0; i < s->numpasses; i++) { pass = &s->passes[i]; if (pass->rgbgen == RGB_GEN_LIGHTING_DIFFUSE || pass->rgbgen == RGB_GEN_ENTITY_LIGHTING_DIFFUSE) modellighting = pass; else if (pass->rgbgen == RGB_GEN_ENTITY) modellighting = pass; else if (pass->rgbgen == RGB_GEN_VERTEX_LIGHTING || pass->rgbgen == RGB_GEN_VERTEX_EXACT) { if (s->usageflags & (SUF_LIGHTMAP|SUF_2D)) vertexlighting = pass; else modellighting = pass; //fucking DP morons who don't know what lightmaps are. } else if (pass->texgen == T_GEN_LIGHTMAP && pass->tcgen == TC_GEN_LIGHTMAP) { if (s->usageflags & SUF_LIGHTMAP) lightmap = pass; else modellighting = pass; //fucking DP morons who don't know what lightmaps are. } /*if (pass->numtcmods || (pass->shaderbits & SBITS_ATEST_BITS)) return; if (pass->texgen == T_GEN_LIGHTMAP && pass->tcgen == TC_GEN_LIGHTMAP) ; else if (pass->texgen != T_GEN_LIGHTMAP && pass->tcgen == TC_GEN_BASE) ; else return;*/ } if (parsestate.forcedshader) prog = parsestate.forcedshader; else if (parsestate.dpwatertype) { prog = va("altwater%s#USEMODS#FRESNEL_EXP=2.0" //variable parts "#STRENGTH_REFR=%g#STRENGTH_REFL=%g" "#TINT_REFR=%g,%g,%g" "#TINT_REFL=%g,%g,%g" "#FRESNEL_MIN=%g#FRESNEL_RANGE=%g" "#ALPHA=%g", //those args (parsestate.dpwatertype&1)?"#REFLECT":"", parsestate.refractfactor*0.01, parsestate.reflectfactor*0.01, parsestate.refractcolour[0],parsestate.refractcolour[1],parsestate.refractcolour[2], parsestate.reflectcolour[0],parsestate.reflectcolour[1],parsestate.reflectcolour[2], parsestate.reflectmin, parsestate.reflectmax-parsestate.reflectmin, parsestate.wateralpha ); //clear out blending and force regular depth. s->passes[0].shaderbits &= ~(SBITS_BLEND_BITS|SBITS_MISC_NODEPTHTEST|SBITS_DEPTHFUNC_BITS); s->passes[0].shaderbits |= SBITS_MISC_DEPTHWRITE; if (parsestate.dpwatertype & 1) reflectrefract |= SHADER_HASREFLECT; if (parsestate.dpwatertype & 2) reflectrefract |= SHADER_HASREFRACT; if (parsestate.dpwatertype & 4) { reflectrefract |= SHADER_HASREFRACT|SHADER_HASPORTAL; //doubles up as a 'camera' if (s->sort == SHADER_SORT_PORTAL) s->sort = SHADER_SORT_OPAQUE; //don't do it twice. } } else if (modellighting) { eightbit = r_softwarebanding && (qrenderer == QR_OPENGL) && sh_config.progs_supported; if (eightbit) prog = "defaultskin#EIGHTBIT"; else prog = "defaultskin"; } else if (lightmap) { eightbit = r_softwarebanding && (qrenderer == QR_OPENGL || qrenderer == QR_VULKAN) && sh_config.progs_supported; if (eightbit) prog = "defaultwall#EIGHTBIT"; else prog = "defaultwall"; } else if (vertexlighting) { if (r_forceprogramify.ival < 0) prog = "defaultfill"; else { pass = vertexlighting; prog = "default2d"; } } else { if (r_forceprogramify.ival < 0) prog = "defaultfill"; else return; } args[0] = 0; if (parsestate.specularvalscale != 1) Q_strncatz(args, va("#specmul=%g", parsestate.specularvalscale), sizeof(args)); if (parsestate.specularexpscale != 1) Q_strncatz(args, va("#specexp=%g", parsestate.specularexpscale), sizeof(args)); /* switch(parsestate.offsetmappingmode) { case 0: //force off. Q_strncatz(args, va("#NOOFFSETMAPPING", parsestate.specularexpscale), sizeof(args)); break; case 1: //force linear Q_strncatz(args, va("#NORELIEFMAPPING", parsestate.specularexpscale), sizeof(args)); break; case 2: //force relief Q_strncatz(args, va("#RELIEFMAPPING", parsestate.specularexpscale), sizeof(args)); break; } if (parsestate.offsetmappingscale != 1) Q_strncatz(args, va("#OFFSETMAPPING_SCALE=%g", parsestate.offsetmappingscale), sizeof(args)); if (parsestate.offsetmappingbias != 0) Q_strncatz(args, va("#OFFSETMAPPING_BIAS=%g", parsestate.offsetmappingbias), sizeof(args)); */ mask = strchr(s->name, '#'); if (mask) Q_strncatz(args, mask, sizeof(args)); mask = Shader_AlphaMaskProgArgs(s); s->prog = Shader_FindGeneric(va("%s%s%s", prog, mask, args), qrenderer); if (s->prog) { s->numpasses = 0; if (reflectrefract) { if (s->passes[0].numtcmods > 0 && s->passes[0].tcmods[0].type == SHADER_TCMOD_SCALE) { //crappy workaround for DP bug. s->passes[0].tcmods[0].args[0] *= 4; s->passes[0].tcmods[0].args[1] *= 4; } s->passes[s->numpasses++].texgen = T_GEN_REFRACTION; s->passes[s->numpasses++].texgen = T_GEN_REFLECTION; // s->passes[s->numpasses++].texgen = T_GEN_RIPPLEMAP; // s->passes[s->numpasses++].texgen = T_GEN_REFRACTIONDEPTH; s->flags |= reflectrefract; } else { if (eightbit) { s->passes[s->numpasses].anim_frames[0] = R_LoadColourmapImage(); s->passes[s->numpasses++].texgen = T_GEN_SINGLEMAP; } else s->passes[s->numpasses++].texgen = T_GEN_DIFFUSE; s->flags |= SHADER_HASDIFFUSE; } } } void Shader_Finish (shader_t *s) { int i; shaderpass_t *pass; //FIXME: reorder doom3 stages. //put diffuse first. give it a lightmap pass also, if we found a diffuse one with no lightmap. //then the ambient stages. //and forget about the bump/specular stages as we don't support them and already stripped them. #if 0 if (s->flags & SHADER_SKY) { /*skies go all black if fastsky is set*/ if (r_fastsky.ival) s->flags = 0; /*or if its purely a skybox and has missing textures*/ // if (!s->numpasses) // for (i = 0; i < 6; i++) // if (missing_texture.ref == s->skydome->farbox_textures[i].ref) // s->flags = 0; if (!(s->flags & SHADER_SKY)) { Shader_Reset(s); Shader_DefaultScript(s->name, s, "{\n" "sort sky\n" "{\n" "map $whiteimage\n" "rgbgen srgb $r_fastskycolour\n" "}\n" "surfaceparm nodlight\n" "}\n" ); return; } } #endif if (s->prog && !s->numpasses) { pass = &s->passes[s->numpasses++]; pass->tcgen = TC_GEN_BASE; pass->texgen = T_GEN_DIFFUSE; pass->shaderbits |= SBITS_MISC_DEPTHWRITE; pass->rgbgen = RGB_GEN_IDENTITY; pass->alphagen = ALPHA_GEN_IDENTITY; pass->numMergedPasses = 1; Shader_SetBlendmode(pass, NULL); } if (!s->numpasses && s->sort != SHADER_SORT_PORTAL && !(s->flags & (SHADER_NODRAW|SHADER_SKY)) && !s->fog_dist) { pass = &s->passes[s->numpasses++]; pass = &s->passes[0]; pass->tcgen = TC_GEN_BASE; if (TEXVALID(s->defaulttextures->base)) pass->texgen = T_GEN_DIFFUSE; else { pass->texgen = T_GEN_SINGLEMAP; TEXASSIGN(pass->anim_frames[0], R_LoadHiResTexture(s->name, NULL, IF_NOALPHA)); if (!TEXVALID(pass->anim_frames[0])) { Con_Printf("Shader %s failed to load default texture\n", s->name); pass->anim_frames[0] = missing_texture; } Con_Printf("Shader %s with no passes and no surfaceparm nodraw, inserting pass\n", s->name); } pass->shaderbits |= SBITS_MISC_DEPTHWRITE; pass->rgbgen = RGB_GEN_VERTEX_LIGHTING; pass->alphagen = ALPHA_GEN_IDENTITY; pass->numMergedPasses = 1; Shader_SetBlendmode(pass, NULL); } if (!Q_stricmp (s->name, "flareShader")) { s->flags |= SHADER_FLARE; s->flags |= SHADER_NODRAW; } if (!s->numpasses && !s->sort) { s->sort = SHADER_SORT_ADDITIVE; return; } if (!s->sort && s->passes->texgen == T_GEN_CURRENTRENDER) s->sort = SHADER_SORT_NEAREST; if ((s->polyoffset.unit < 0) && !s->sort) { s->sort = SHADER_SORT_DECAL; } if ((r_vertexlight.value || !(s->usageflags & SUF_LIGHTMAP)) && !s->prog) { // do we have a lightmap pass? pass = s->passes; for (i = 0; i < s->numpasses; i++, pass++) { if (pass->flags & SHADER_PASS_LIGHTMAP) break; } if (i == s->numpasses) { goto done; } if (!r_vertexlight.value && pass->rgbgen == RGB_GEN_IDENTITY) { //we found the lightmap pass. if we need a vertex-lit shader then just switch over the rgbgen+texture and hope other things work out pass->rgbgen = RGB_GEN_VERTEX_LIGHTING; pass->flags &= ~SHADER_PASS_LIGHTMAP; pass->tcgen = T_GEN_SINGLEMAP; goto done; } // try to find pass with rgbgen set to RGB_GEN_VERTEX pass = s->passes; for (i = 0; i < s->numpasses; i++, pass++) { if (pass->rgbgen == RGB_GEN_VERTEX_LIGHTING) break; } if (i < s->numpasses) { // we found it pass->flags |= SHADER_CULL_FRONT; pass->flags &= ~SHADER_PASS_ANIMMAP; pass->shaderbits &= ~SBITS_BLEND_BITS; pass->blendmode = 0; pass->shaderbits |= SBITS_MISC_DEPTHWRITE; pass->alphagen = ALPHA_GEN_IDENTITY; pass->numMergedPasses = 1; s->flags |= SHADER_DEPTHWRITE; s->sort = SHADER_SORT_OPAQUE; s->numpasses = 1; memcpy(&s->passes[0], pass, sizeof(shaderpass_t)); } else { // we didn't find it - simply remove all lightmap passes pass = s->passes; for(i = 0; i < s->numpasses; i++, pass++) { if (pass->flags & SHADER_PASS_LIGHTMAP) break; } if ( i == s->numpasses -1 ) { s->numpasses--; } else if ( i < s->numpasses - 1 ) { for ( ; i < s->numpasses - 1; i++, pass++ ) { memcpy ( pass, &s->passes[i+1], sizeof(shaderpass_t) ); } s->numpasses--; } if ( s->passes[0].numtcmods ) { pass = s->passes; for ( i = 0; i < s->numpasses; i++, pass++ ) { if ( !pass->numtcmods ) break; } memcpy ( &s->passes[0], pass, sizeof(shaderpass_t) ); } s->passes[0].rgbgen = RGB_GEN_VERTEX_LIGHTING; s->passes[0].alphagen = ALPHA_GEN_IDENTITY; s->passes[0].blendmode = 0; s->passes[0].flags &= ~(SHADER_PASS_ANIMMAP|SHADER_PASS_NOCOLORARRAY); s->passes[0].shaderbits &= ~SBITS_BLEND_BITS; s->passes[0].shaderbits |= SBITS_MISC_DEPTHWRITE; s->passes[0].numMergedPasses = 1; s->numpasses = 1; s->flags |= SHADER_DEPTHWRITE; } } done:; //if we've no specular map, try and find whatever the q3 syntax said. hopefully it'll be compatible... if (!TEXVALID(s->defaulttextures->specular) && !(s->flags & SHADER_HASGLOSS)) { for (pass = s->passes, i = 0; i < s->numpasses; i++, pass++) { if (pass->alphagen == ALPHA_GEN_SPECULAR) if (pass->texgen == T_GEN_ANIMMAP || pass->texgen == T_GEN_SINGLEMAP) s->defaulttextures->specular = pass->anim_frames[0]; } } if (!TEXVALID(s->defaulttextures->base) && !(s->flags & SHADER_HASDIFFUSE) && !s->prog) { //if one of the other passes specifies $diffuse, don't try and guess one, because that means that other pass's texture gets used for BOTH passes, which isn't good. //also, don't guess one if a program was specified. shaderpass_t *best = NULL; int bestweight = 9999999; int weight; for (pass = s->passes, i = 0; i < s->numpasses; i++, pass++) { weight = 0; if (pass->flags & SHADER_PASS_DETAIL) weight += 500; //prefer not to use a detail pass. these are generally useless. if (pass->numtcmods || pass->tcgen != TC_GEN_BASE) weight += 200; if (pass->rgbgen != RGB_GEN_IDENTITY && pass->rgbgen != RGB_GEN_IDENTITY_OVERBRIGHT && pass->rgbgen != RGB_GEN_IDENTITY_LIGHTING) weight += 100; if (pass->texgen != T_GEN_ANIMMAP && pass->texgen != T_GEN_SINGLEMAP && pass->texgen != T_GEN_VIDEOMAP) weight += 1000; if ((pass->texgen == T_GEN_ANIMMAP || pass->texgen == T_GEN_SINGLEMAP) && pass->anim_frames[0] && *pass->anim_frames[0]->ident == '$') weight += 1500; if (weight < bestweight) { bestweight = weight; best = pass; } } if (best) { if (best->texgen == T_GEN_ANIMMAP || best->texgen == T_GEN_SINGLEMAP) { if (best->anim_frames[0] && *best->anim_frames[0]->ident != '$') s->defaulttextures->base = best->anim_frames[0]; } #ifdef HAVE_MEDIA_DECODER else if (pass->texgen == T_GEN_VIDEOMAP && pass->cin) s->defaulttextures->base = Media_UpdateForShader(best->cin); #endif } } for (i = 0; i < s->numpasses; i += (pass->prog?pass->numMergedPasses:1)) { pass = s->passes+i; if (!(pass->shaderbits & (SBITS_BLEND_BITS|SBITS_MASK_BITS))) { break; } } // all passes have blendfuncs if (i == s->numpasses) { int maskpass; qboolean isopaque = false; maskpass = -1; pass = s->passes; for (i = 0; i < s->numpasses; i++, pass++ ) { if (pass->shaderbits & SBITS_ATEST_BITS) { maskpass = i; } else if ((pass->shaderbits & SBITS_MASK_BITS) == 0) { //a few shaders use blendfunc one zero so that they're ignored when using r_vertexlight (while later alpha-masked surfs are not). if (/*(pass->shaderbits & (SBITS_SRCBLEND_BITS|SBITS_DSTBLEND_BITS)) == 0 ||*/ (pass->shaderbits & (SBITS_SRCBLEND_BITS|SBITS_DSTBLEND_BITS)) == (SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ZERO)) isopaque = true; } if (pass->rgbgen == RGB_GEN_UNKNOWN) { if ( (pass->shaderbits & SBITS_SRCBLEND_BITS) == 0 || (pass->shaderbits & SBITS_SRCBLEND_BITS) == SBITS_SRCBLEND_ONE || (pass->shaderbits & SBITS_SRCBLEND_BITS) == SBITS_SRCBLEND_SRC_ALPHA) pass->rgbgen = RGB_GEN_IDENTITY_LIGHTING; else pass->rgbgen = RGB_GEN_IDENTITY; } Shader_SetBlendmode (pass, i?pass-1:NULL); if (pass->blendmode == PBM_ADD) s->defaulttextures->fullbright = pass->anim_frames[0]; } if (!(s->flags & SHADER_SKY ) && !s->sort) { if (isopaque) s->sort = SHADER_SORT_OPAQUE; else if (maskpass == -1) { if (s->numpasses && s->passes[0].blendmode == PBM_ADD) s->sort = SHADER_SORT_ADDITIVE; else s->sort = SHADER_SORT_BLEND; } else s->sort = SHADER_SORT_SEETHROUGH; } } else { int j; shaderpass_t *sp; sp = s->passes; for (j = 0; j < s->numpasses; j++, sp++) { if (sp->rgbgen == RGB_GEN_UNKNOWN) { if (sp->flags & SHADER_PASS_LIGHTMAP) sp->rgbgen = RGB_GEN_IDENTITY_LIGHTING; else sp->rgbgen = RGB_GEN_IDENTITY; } Shader_SetBlendmode (sp, j?sp-1:NULL); } if (!s->sort) { if (i < s->numpasses && (s->passes[i].shaderbits & SBITS_ATEST_BITS)) s->sort = SHADER_SORT_SEETHROUGH; } if (!( s->flags & SHADER_DEPTHWRITE) && !(s->flags & SHADER_SKY)) { s->passes->shaderbits |= SBITS_MISC_DEPTHWRITE; s->flags |= SHADER_DEPTHWRITE; } } if (s->numpasses >= 2) { int j; pass = s->passes; for (i = 0; i < s->numpasses;) { if (i == s->numpasses - 1) break; pass = s->passes + i; for (j = 1; j < s->numpasses-i && j == pass->numMergedPasses && j+1 < be_maxpasses; j++) Shader_SetPassFlush (pass, pass + j); i += pass->numMergedPasses; } } if (!s->sort) { s->sort = SHADER_SORT_OPAQUE; } if ((s->flags & SHADER_SKY) && (s->flags & SHADER_DEPTHWRITE)) { s->flags &= ~SHADER_DEPTHWRITE; } if (!s->bemoverrides[bemoverride_depthonly]) { const char *mask = Shader_AlphaMaskProgArgs(s); if (*mask || (s->prog&&s->prog->tess)) s->bemoverrides[bemoverride_depthonly] = R_RegisterShader(va("depthonly%s%s", mask, (s->prog&&s->prog->tess)?"#TESS":""), SUF_NONE, "{\n" "program depthonly\n" "{\n" "map $diffuse\n" "depthwrite\n" "maskcolor\n" "}\n" "}\n"); } if (!s->bemoverrides[LSHADER_STANDARD] && (s->prog&&s->prog->tess)) { int mode; for (mode = 0; mode < LSHADER_MODES; mode++) { if ((mode & LSHADER_CUBE) && (mode & LSHADER_SPOT)) continue; if (s->bemoverrides[mode]) continue; s->bemoverrides[mode] = R_RegisterShader(va("rtlight%s%s%s%s#TESS", (mode & LSHADER_SMAP)?"#PCF":"", (mode & LSHADER_SPOT)?"#SPOT":"", (mode & LSHADER_CUBE)?"#CUBE":"", #ifdef GLQUAKE (qrenderer == QR_OPENGL && gl_config.arb_shadow && (mode & (LSHADER_SMAP|LSHADER_SPOT)))?"#USE_ARB_SHADOW":"" #else "" #endif ) , s->usageflags, "{\n" "program rtlight\n" "{\n" "map $diffuse\n" "blendfunc add\n" "}\n" "}\n"); } } if (!s->prog && sh_config.progs_supported && (r_forceprogramify.ival || (parsestate.parseflags & SPF_PROGRAMIFY))) { if (r_forceprogramify.ival >= 2) { if (s->passes[0].numtcmods == 1 && s->passes[0].tcmods[0].type == SHADER_TCMOD_SCALE) s->passes[0].numtcmods = 0; //DP sucks and doesn't use normalized texture coords *if* there's a shader specified. so lets ignore any extra scaling that this imposes. if (s->passes[0].shaderbits & SBITS_ATEST_BITS) //mimic DP's limited alphafunc support s->passes[0].shaderbits = (s->passes[0].shaderbits & ~SBITS_ATEST_BITS) | SBITS_ATEST_GE128; s->passes[0].shaderbits &= ~SBITS_DEPTHFUNC_BITS; //DP ignores this too. } Shader_Programify(s); } if (s->prog) { struct { int gen; unsigned int flags; } defaulttgen[] = { //light {T_GEN_SHADOWMAP, 0}, //0 {T_GEN_LIGHTCUBEMAP, 0}, //1 //material {T_GEN_DIFFUSE, SHADER_HASDIFFUSE}, //2 {T_GEN_NORMALMAP, SHADER_HASNORMALMAP}, //3 {T_GEN_SPECULAR, SHADER_HASGLOSS}, //4 {T_GEN_UPPEROVERLAY, SHADER_HASTOPBOTTOM}, //5 {T_GEN_LOWEROVERLAY, SHADER_HASTOPBOTTOM}, //6 {T_GEN_FULLBRIGHT, SHADER_HASFULLBRIGHT}, //7 {T_GEN_PALETTED, SHADER_HASPALETTED}, //8 {T_GEN_REFLECTCUBE, 0}, //9 {T_GEN_REFLECTMASK, 0}, //10 {T_GEN_DISPLACEMENT, SHADER_HASDISPLACEMENT},//11 // {T_GEN_REFLECTION, SHADER_HASREFLECT}, // // {T_GEN_REFRACTION, SHADER_HASREFRACT}, // // {T_GEN_REFRACTIONDEPTH, SHADER_HASREFRACTDEPTH},// // {T_GEN_RIPPLEMAP, SHADER_HASRIPPLEMAP}, // //batch {T_GEN_LIGHTMAP, SHADER_HASLIGHTMAP}, //12 {T_GEN_DELUXMAP, 0}, //13 //more lightmaps //14,15,16 //mode deluxemaps //17,18,19 }; #ifdef HAVE_MEDIA_DECODER cin_t *cin = R_ShaderGetCinematic(s); #endif //if the glsl doesn't specify all samplers, just trim them. s->numpasses = s->prog->numsamplers; #ifdef HAVE_MEDIA_DECODER if (cin && R_ShaderGetCinematic(s) == cin) cin = NULL; #endif //if the glsl has specific textures listed, be sure to provide a pass for them. for (i = 0; i < sizeof(defaulttgen)/sizeof(defaulttgen[0]); i++) { if (s->prog->defaulttextures & (1u<numpasses >= SHADER_PASS_MAX) break; //panic... s->passes[s->numpasses].flags &= ~SHADER_PASS_DEPTHCMP; if (defaulttgen[i].gen == T_GEN_SHADOWMAP) s->passes[s->numpasses].flags |= SHADER_PASS_DEPTHCMP; #ifdef HAVE_MEDIA_DECODER if (!i && cin) { s->passes[s->numpasses].texgen = T_GEN_VIDEOMAP; s->passes[s->numpasses].cin = cin; cin = NULL; } else #endif { s->passes[s->numpasses].texgen = defaulttgen[i].gen; #ifdef HAVE_MEDIA_DECODER s->passes[s->numpasses].cin = NULL; #endif } s->numpasses++; s->flags |= defaulttgen[i].flags; } } //must have at least one texture. if (!s->numpasses) { #ifdef HAVE_MEDIA_DECODER s->passes[0].texgen = cin?T_GEN_VIDEOMAP:T_GEN_DIFFUSE; s->passes[0].cin = cin; #else s->passes[0].texgen = T_GEN_DIFFUSE; #endif s->numpasses = 1; } #ifdef HAVE_MEDIA_DECODER else if (cin) Media_ShutdownCin(cin); #endif s->passes->numMergedPasses = s->numpasses; } else if (s->numdeforms) s->flags |= SHADER_NEEDSARRAYS; else { for (i = 0; i < s->numpasses; i++) { pass = &s->passes[i]; if (pass->prog) continue; if (pass->numtcmods || (s->passes[i].tcgen != TC_GEN_BASE && s->passes[i].tcgen != TC_GEN_LIGHTMAP) || !(s->passes[i].flags & SHADER_PASS_NOCOLORARRAY)) { s->flags |= SHADER_NEEDSARRAYS; break; } if (!(pass->flags & SHADER_PASS_NOCOLORARRAY)) { if (!(((pass->rgbgen == RGB_GEN_VERTEX_LIGHTING) || (pass->rgbgen == RGB_GEN_VERTEX_EXACT) || (pass->rgbgen == RGB_GEN_ONE_MINUS_VERTEX)) && (pass->alphagen == ALPHA_GEN_VERTEX))) { s->flags |= SHADER_NEEDSARRAYS; break; } } } } } /* void Shader_UpdateRegistration (void) { int i, j, l; shader_t *shader; shaderpass_t *pass; shader = r_shaders; for (i = 0; i < MAX_SHADERS; i++, shader++) { if (!shader->registration_sequence) continue; if (shader->registration_sequence != registration_sequence) { Shader_Free ( shader ); shader->registration_sequence = 0; continue; } pass = shader->passes; for (j = 0; j < shader->numpasses; j++, pass++) { if (pass->flags & SHADER_PASS_ANIMMAP) { for (l = 0; l < pass->anim_numframes; l++) { if (pass->anim_frames[l]) pass->anim_frames[l]->registration_sequence = registration_sequence; } } else if ( pass->flags & SHADER_PASS_VIDEOMAP ) { // Shader_RunCinematic will do the job // pass->cin->frame = -1; } else if ( !(pass->flags & SHADER_PASS_LIGHTMAP) ) { if ( pass->anim_frames[0] ) pass->anim_frames[0]->registration_sequence = registration_sequence; } } } } */ /* if (*shader_diffusemapname) { if (!s->defaulttextures.base) s->defaulttextures.base = Shader_FindImage (va("%s.tga", shader_diffusemapname), 0); if (!s->defaulttextures.bump) s->defaulttextures.bump = Shader_FindImage (va("%s_norm.tga", shader_diffusemapname), 0); if (!s->defaulttextures.fullbright) s->defaulttextures.fullbright = Shader_FindImage (va("%s_glow.tga", shader_diffusemapname), 0); if (!s->defaulttextures.specular) s->defaulttextures.specular = Shader_FindImage (va("%s_gloss.tga", shader_diffusemapname), 0); if (!s->defaulttextures.upperoverlay) s->defaulttextures.upperoverlay = Shader_FindImage (va("%s_shirt.tga", shader_diffusemapname), 0); if (!s->defaulttextures.loweroverlay) s->defaulttextures.loweroverlay = Shader_FindImage (va("%s_pants.tga", shader_diffusemapname), 0); //stupid yanks... } */ void Shader_DefaultSkin(const char *shortname, shader_t *s, const void *args); void QDECL R_BuildDefaultTexnums(texnums_t *src, shader_t *shader, unsigned int imageflags) { char *h; char imagename[MAX_QPATH]; char mapname[MAX_QPATH]; char *subpath = NULL; texnums_t *tex; unsigned int a, aframes; strcpy(imagename, shader->name); h = strchr(imagename, '#'); if (h) *h = 0; if (*imagename == '/' || strchr(imagename, ':')) { //this is not security. this is anti-spam for the verbose security in the filesystem code. Con_Printf("Warning: shader has absolute path: %s\n", shader->name); *imagename = 0; } //skins can use an alternative path in certain cases, to work around dodgy models. if (shader->generator == Shader_DefaultSkin) subpath = shader->genargs; tex = shader->defaulttextures; aframes = max(1, shader->numdefaulttextures); //if any were specified explicitly, replicate that into all. //this means animmap can be used, with any explicit textures overriding all. if (!shader->numdefaulttextures && src) { //only do this if there wasn't an animmap thing to break everything. if (!TEXVALID(tex->base)) tex->base = src->base; if (!TEXVALID(tex->bump)) tex->bump = src->bump; if (!TEXVALID(tex->fullbright)) tex->fullbright = src->fullbright; if (!TEXVALID(tex->specular)) tex->specular = src->specular; if (!TEXVALID(tex->loweroverlay)) tex->loweroverlay = src->loweroverlay; if (!TEXVALID(tex->upperoverlay)) tex->upperoverlay = src->upperoverlay; if (!TEXVALID(tex->reflectmask)) tex->reflectmask = src->reflectmask; if (!TEXVALID(tex->reflectcube)) tex->reflectcube = src->reflectcube; if (!TEXVALID(tex->displacement)) tex->displacement = src->displacement; } for (a = 1; a < aframes; a++) { if (!TEXVALID(tex[a].base)) tex[a].base = tex[0].base; if (!TEXVALID(tex[a].bump)) tex[a].bump = tex[0].bump; if (!TEXVALID(tex[a].fullbright)) tex[a].fullbright = tex[0].fullbright; if (!TEXVALID(tex[a].specular)) tex[a].specular = tex[0].specular; if (!TEXVALID(tex[a].loweroverlay)) tex[a].loweroverlay = tex[0].loweroverlay; if (!TEXVALID(tex[a].upperoverlay)) tex[a].upperoverlay = tex[0].upperoverlay; if (!TEXVALID(tex[a].reflectmask)) tex[a].reflectmask = tex[0].reflectmask; if (!TEXVALID(tex[a].reflectcube)) tex[a].reflectcube = tex[0].reflectcube; if (!TEXVALID(tex[a].displacement)) tex[a].displacement = tex[0].displacement; } for (a = 0; a < aframes; a++, tex++) { COM_StripExtension(tex->mapname, mapname, sizeof(mapname)); if (!TEXVALID(tex->base)) { /*dlights/realtime lighting needs some stuff*/ if (!TEXVALID(tex->base) && *tex->mapname)// && (shader->flags & SHADER_HASDIFFUSE)) tex->base = R_LoadHiResTexture(tex->mapname, NULL, imageflags); if (!TEXVALID(tex->base)) tex->base = R_LoadHiResTexture(imagename, subpath, (*imagename=='{')?0:IF_NOALPHA|imageflags); } if ((shader->flags & SHADER_HASPALETTED) && !TEXVALID(tex->paletted)) { if (!TEXVALID(tex->paletted) && *tex->mapname) tex->paletted = R_LoadHiResTexture(va("%s", tex->mapname), NULL, 0|IF_NEAREST|IF_PALETTIZE|imageflags); if (!TEXVALID(tex->paletted)) tex->paletted = R_LoadHiResTexture(va("%s", imagename), subpath, ((*imagename=='{')?0:IF_NOALPHA)|IF_NEAREST|IF_PALETTIZE|imageflags); } imageflags |= IF_LOWPRIORITY; COM_StripExtension(imagename, imagename, sizeof(imagename)); if (!TEXVALID(tex->bump)) { if (r_loadbumpmapping || (shader->flags & SHADER_HASNORMALMAP)) { if (!TEXVALID(tex->bump) && *mapname && (shader->flags & SHADER_HASNORMALMAP)) tex->bump = R_LoadHiResTexture(va("%s_norm", mapname), NULL, imageflags|IF_TRYBUMP|IF_NOSRGB|imageflags); if (!TEXVALID(tex->bump)) tex->bump = R_LoadHiResTexture(va("%s_norm", imagename), subpath, imageflags|IF_TRYBUMP|IF_NOSRGB|imageflags); } } if (!TEXVALID(tex->loweroverlay)) { if (shader->flags & SHADER_HASTOPBOTTOM) { if (!TEXVALID(tex->loweroverlay) && *mapname) tex->loweroverlay = R_LoadHiResTexture(va("%s_pants", mapname), NULL, imageflags); if (!TEXVALID(tex->loweroverlay)) tex->loweroverlay = R_LoadHiResTexture(va("%s_pants", imagename), subpath, imageflags); /*how rude*/ } } if (!TEXVALID(tex->upperoverlay)) { if (shader->flags & SHADER_HASTOPBOTTOM) { if (!TEXVALID(tex->upperoverlay) && *mapname) tex->upperoverlay = R_LoadHiResTexture(va("%s_shirt", mapname), NULL, imageflags); if (!TEXVALID(tex->upperoverlay)) tex->upperoverlay = R_LoadHiResTexture(va("%s_shirt", imagename), subpath, imageflags); } } if (!TEXVALID(tex->specular)) { extern cvar_t gl_specular; if ((shader->flags & SHADER_HASGLOSS) && gl_specular.value && gl_load24bit.value) { if (!TEXVALID(tex->specular) && *mapname) tex->specular = R_LoadHiResTexture(va("%s_gloss", mapname), NULL, imageflags); if (!TEXVALID(tex->specular)) tex->specular = R_LoadHiResTexture(va("%s_gloss", imagename), subpath, imageflags); } } if (!TEXVALID(tex->fullbright)) { extern cvar_t r_fb_bmodels; if ((shader->flags & SHADER_HASFULLBRIGHT) && r_fb_bmodels.value && gl_load24bit.value) { if (!TEXVALID(tex->fullbright) && *mapname) tex->fullbright = R_LoadHiResTexture(va("%s_luma:%s_glow", mapname, mapname), NULL, imageflags); if (!TEXVALID(tex->fullbright)) tex->fullbright = R_LoadHiResTexture(va("%s_luma:%s_glow", imagename, imagename), subpath, imageflags); } } //if there's a reflectcube texture specified by the shader, make sure we have a reflectmask to go with it. if (tex->reflectcube) { if (!TEXVALID(tex->reflectmask) && *mapname) tex->reflectmask = R_LoadHiResTexture(va("%s_reflect", mapname), NULL, imageflags); if (!TEXVALID(tex->reflectmask)) tex->reflectmask = R_LoadHiResTexture(va("%s_reflect", imagename), subpath, imageflags); } } } #if 0//def Q2BSPS static qbyte *ReadRGBA8ImageFile(const char *fname, const char *subpath, int *width, int *height) { qboolean hasalpha; qofs_t filesize; qbyte *img, *filedata = NULL; char *patterns[] = { "*overrides/%s.tga", "*overrides/%s.jpg", "textures/%s.tga", "textures/%s.jpg", }; char nname[MAX_QPATH]; size_t p; for (p = 0; p < countof(patterns) && !filedata; p++) { if (*patterns[p] == '*') Q_snprintfz(nname, sizeof(nname), patterns[p]+1, COM_SkipPath(fname)); else Q_snprintfz(nname, sizeof(nname), patterns[p], fname); filedata = FS_MallocFile(nname, FS_GAME, &filesize); } img = filedata?Read32BitImageFile(filedata, filesize, width, height, &hasalpha, fname):NULL; BZ_Free(filedata); return img; } #endif //call this with some fallback textures to directly load some textures void QDECL R_BuildLegacyTexnums(shader_t *shader, const char *fallbackname, const char *subpath, unsigned int loadflags, unsigned int imageflags, uploadfmt_t basefmt, size_t width, size_t height, qbyte *mipdata[4], qbyte *palette) { char *h; char imagename[MAX_QPATH]; char mapname[MAX_QPATH]; //as specified by the shader. //extern cvar_t gl_miptexLevel; texnums_t *tex = shader->defaulttextures; int a, aframes; qbyte *dontcrashme[4] = {NULL}; if (!mipdata) mipdata = dontcrashme; /*else if (gl_miptexLevel.ival) { unsigned int miplevel = 0, i; for (i = 0; i < 3 && i < gl_miptexLevel.ival && mipdata[i]; i++) miplevel = i; for (i = 0; i < 3; i++) dontcrashme[i] = (miplevel+i)>3?NULL:mipdata[miplevel+i]; width >>= miplevel; height >>= miplevel; mipdata = dontcrashme; } */ strcpy(imagename, shader->name); h = strchr(imagename, '#'); if (h) *h = 0; if (*imagename == '/' || strchr(imagename, ':')) { //this is not security. this is anti-spam for the verbose security in the filesystem code. Con_Printf("Warning: shader has absolute path: %s\n", shader->name); *imagename = 0; } //for water texture replacements while((h = strchr(imagename, '*'))) *h = '#'; loadflags &= shader->flags; //skins can use an alternative path in certain cases, to work around dodgy models. if (shader->generator == Shader_DefaultSkin) subpath = shader->genargs; //optimise away any palette info if we can... if (!palette || palette == host_basepal) { if (basefmt == TF_MIP4_8PAL24) basefmt = TF_MIP4_SOLID8; // if (basefmt == TF_MIP4_8PAL24_T255) // basefmt = TF_MIP4_TRANS8; } //make sure the noalpha thing is set properly. switch(basefmt) { case TF_MIP4_8PAL24: case TF_MIP4_SOLID8: case TF_SOLID8: imageflags |= IF_NOALPHA; if (!mipdata || !mipdata[0] || !mipdata[1] || !mipdata[2] || !mipdata[3]) basefmt = TF_SOLID8; break; default: if (!mipdata || !mipdata[0] || !mipdata[1] || !mipdata[2] || !mipdata[3]) basefmt = TF_SOLID8; break; } imageflags |= IF_MIPCAP; COM_StripExtension(imagename, imagename, sizeof(imagename)); aframes = max(1, shader->numdefaulttextures); //if any were specified explicitly, replicate that into all. //this means animmap can be used, with any explicit textures overriding all. for (a = 1; a < aframes; a++) { if (!TEXVALID(tex[a].base)) tex[a].base = tex[0].base; if (!TEXVALID(tex[a].bump)) tex[a].bump = tex[0].bump; if (!TEXVALID(tex[a].fullbright)) tex[a].fullbright = tex[0].fullbright; if (!TEXVALID(tex[a].specular)) tex[a].specular = tex[0].specular; if (!TEXVALID(tex[a].loweroverlay)) tex[a].loweroverlay = tex[0].loweroverlay; if (!TEXVALID(tex[a].upperoverlay)) tex[a].upperoverlay = tex[0].upperoverlay; if (!TEXVALID(tex[a].reflectmask)) tex[a].reflectmask = tex[0].reflectmask; if (!TEXVALID(tex[a].reflectcube)) tex[a].reflectcube = tex[0].reflectcube; if (!TEXVALID(tex[a].displacement)) tex[a].displacement = tex[0].displacement; } for (a = 0; a < aframes; a++, tex++) { COM_StripExtension(tex->mapname, mapname, sizeof(mapname)); #if 0//def Q2BSPS if (gl_load24bit.ival == 2) { qbyte *base; int basewidth, baseheight; qbyte *norm; int normwidth, normheight; qbyte tmp; int x, y; base = ReadRGBA8ImageFile(imagename, subpath, &basewidth, &baseheight); //base contains diffuse RGB, and height if (base) { tex->base = Image_GetTexture(va("%s_diff", imagename), subpath, imageflags|IF_NOREPLACE, base, NULL, basewidth, baseheight, TF_RGBX32); //queues the texture creation. norm = ReadRGBA8ImageFile(va("%s_bump", imagename), subpath, &normwidth, &normheight); if (norm) { if (normwidth != basewidth || normheight != baseheight) { //sucks... tex->bump = Image_GetTexture(va("%s_norm", imagename), subpath, imageflags|IF_NOREPLACE, norm, NULL, normwidth, normheight, TF_RGBX32); //queues the texture creation. } else { //so we have a base texture, and normalmap //we already uploaded the diffuse, so now we can just pretend that the base is the specularmap. //we just need to swap the two alpha channels. for (y = 0; y < baseheight; y++) { for (x = 0; x < basewidth; x++) { tmp = base[(x+y*basewidth)*4+3]; base[(x+y*basewidth)*4+3] = norm[(x+y*basewidth)*4+3]; norm[(x+y*basewidth)*4+3] = ((x+y)&1)*255;//tmp; } } tex->bump = Image_GetTexture(va("%s_norm", imagename), subpath, imageflags|IF_NOREPLACE, norm, NULL, normwidth, normheight, TF_RGBA32); //queues the texture creation. tex->specular = Image_GetTexture(va("%s_spec", imagename), subpath, imageflags|IF_NOREPLACE, base, NULL, basewidth, baseheight, TF_RGBA32); //queues the texture creation. } BZ_Free(norm); } else { //generate a height8 image from the alpha channel. we can do it in place for (y = 0; y < baseheight; y++) { for (x = 0; x < basewidth; x++) base[(x+y*basewidth)] = base[(x+y*basewidth)*4+3]; } tex->bump = Image_GetTexture(va("%s_norm", imagename), subpath, imageflags|IF_NOREPLACE, base, NULL, basewidth, baseheight, TF_HEIGHT8); } BZ_Free(base); } } #endif /*dlights/realtime lighting needs some stuff*/ if (loadflags & SHADER_HASDIFFUSE) { if (!TEXVALID(tex->base) && *mapname) tex->base = R_LoadHiResTexture(mapname, NULL, imageflags); if (!TEXVALID(tex->base) && fallbackname) { if (gl_load24bit.ival) { tex->base = Image_GetTexture(imagename, subpath, imageflags|IF_NOWORKER, NULL, NULL, width, height, basefmt); if (!TEXLOADED(tex->base)) { tex->base = Image_GetTexture(fallbackname, subpath, imageflags|IF_NOWORKER, NULL, NULL, width, height, basefmt); if (TEXLOADED(tex->base)) Q_strncpyz(imagename, fallbackname, sizeof(imagename)); } } if (!TEXLOADED(tex->base)) tex->base = Image_GetTexture(imagename, subpath, imageflags, mipdata[0], palette, width, height, basefmt); } else if (!TEXVALID(tex->base)) tex->base = Image_GetTexture(imagename, subpath, imageflags, mipdata[0], palette, width, height, basefmt); } if (loadflags & SHADER_HASPALETTED) { if (!TEXVALID(tex->paletted) && *mapname) tex->paletted = R_LoadHiResTexture(va("%s_pal", mapname), NULL, imageflags|IF_NEAREST); if (!TEXVALID(tex->paletted)) tex->paletted = Image_GetTexture(va("%s_pal", imagename), subpath, imageflags|IF_NEAREST|IF_NOSRGB, mipdata[0], palette, width, height, (basefmt==TF_MIP4_SOLID8)?TF_MIP4_P8:PTI_P8); } imageflags |= IF_LOWPRIORITY; //all the rest need/want an alpha channel in some form. imageflags &= ~IF_NOALPHA; imageflags |= IF_NOGAMMA; if (loadflags & SHADER_HASNORMALMAP||*imagename=='#') { extern cvar_t r_shadow_bumpscale_basetexture; if (!TEXVALID(tex->bump) && *mapname) tex->bump = R_LoadHiResTexture(va("%s_norm", mapname), NULL, imageflags|IF_TRYBUMP|IF_NOSRGB); if (!TEXVALID(tex->bump) && (r_shadow_bumpscale_basetexture.ival||*imagename=='#'||gl_load24bit.ival)) tex->bump = Image_GetTexture(va("%s_norm", imagename), subpath, imageflags|IF_TRYBUMP|IF_NOSRGB|(*imagename=='#'?IF_LINEAR:0), (r_shadow_bumpscale_basetexture.ival||*imagename=='#')?mipdata[0]:NULL, palette, width, height, TF_HEIGHT8PAL); } if (loadflags & SHADER_HASTOPBOTTOM) { if (!TEXVALID(tex->loweroverlay) && *mapname) tex->loweroverlay = R_LoadHiResTexture(va("%s_pants", mapname), NULL, imageflags); if (!TEXVALID(tex->loweroverlay)) tex->loweroverlay = Image_GetTexture(va("%s_pants", imagename), subpath, imageflags, NULL, palette, width, height, 0); } if (loadflags & SHADER_HASTOPBOTTOM) { if (!TEXVALID(tex->upperoverlay) && *mapname) tex->upperoverlay = R_LoadHiResTexture(va("%s_shirt", mapname), NULL, imageflags); if (!TEXVALID(tex->upperoverlay)) tex->upperoverlay = Image_GetTexture(va("%s_shirt", imagename), subpath, imageflags, NULL, palette, width, height, 0); } if (loadflags & SHADER_HASGLOSS) { if (!TEXVALID(tex->specular) && *mapname) tex->specular = R_LoadHiResTexture(va("%s_gloss", mapname), NULL, imageflags); if (!TEXVALID(tex->specular)) tex->specular = Image_GetTexture(va("%s_gloss", imagename), subpath, imageflags, NULL, palette, width, height, 0); } if (tex->reflectcube) { if (!TEXVALID(tex->reflectmask) && *mapname) tex->reflectmask = R_LoadHiResTexture(va("%s_reflect", mapname), NULL, imageflags); if (!TEXVALID(tex->reflectmask)) tex->reflectmask = Image_GetTexture(va("%s_reflect", imagename), subpath, imageflags, NULL, NULL, width, height, TF_INVALID); } if (loadflags & SHADER_HASFULLBRIGHT) { if (!TEXVALID(tex->fullbright) && *mapname) tex->fullbright = R_LoadHiResTexture(va("%s_luma", mapname), NULL, imageflags); if (!TEXVALID(tex->fullbright)) { int s=-1; if (mipdata[0] && (!palette || palette == host_basepal)) for(s = width*height-1; s>=0; s--) { if (mipdata[0][s] >= 256-vid.fullbright) break; } tex->fullbright = Image_GetTexture(va("%s_luma:%s_glow", imagename,imagename), subpath, imageflags, (s>=0)?mipdata[0]:NULL, palette, width, height, TF_TRANS8_FULLBRIGHT); } } } } void Shader_DefaultScript(const char *shortname, shader_t *s, const void *args) { const char *f = args; if (!args) return; while (*f == ' ' || *f == '\t' || *f == '\n' || *f == '\r') f++; if (*f == '{') { f++; Shader_ReadShader(s, (void*)f, NULL); } } void Shader_DefaultBSPLM(const char *shortname, shader_t *s, const void *args) { char *builtin = NULL; if (Shader_ParseShader("defaultwall", s)) return; if (!builtin && r_softwarebanding && (qrenderer == QR_OPENGL || qrenderer == QR_VULKAN) && sh_config.progs_supported) builtin = ( "{\n" "{\n" "program defaultwall#EIGHTBIT\n" "map $colourmap\n" "}\n" "}\n" ); if (!builtin && r_lightmap.ival) builtin = ( "{\n" "fte_program drawflat_wall#LM\n" "{\n" "map $lightmap\n" "tcgen lightmap\n" "}\n" "}\n" ); if (!builtin && r_drawflat.ival) builtin = ( "{\n" "fte_program drawflat_wall\n" "{\n" "map $lightmap\n" "tcgen lightmap\n" "rgbgen srgb $r_floorcolour\n" "}\n" "}\n" ); if (!builtin && r_lightprepass) { builtin = ( "{\n" "{\n" "fte_program lpp_wall\n" "map $gbuffer2\n" //diffuse lighting info "map $gbuffer3\n" //specular lighting info "}\n" //this is drawn during the initial gbuffer pass to prepare it "fte_bemode gbuffer\n" "{\n" "{\n" "fte_program lpp_depthnorm\n" // "map $normalmap\n" "tcgen base\n" "}\n" "}\n" "}\n" ); } //d3d has no position-invariant. this results in all sorts of glitches, so try not to use it. if (!builtin && ((sh_config.progs_supported && (qrenderer == QR_OPENGL/*||qrenderer == QR_DIRECT3D9*/)) || sh_config.progs_required)) { builtin = ( "{\n" "fte_program defaultwall\n" "{\n" "map $diffuse\n" "}\n" "}\n" ); } if (!builtin) builtin = ( "{\n" /* "if $deluxmap\n" "{\n" "map $normalmap\n" "tcgen base\n" "depthwrite\n" "}\n" "{\n" "map $deluxmap\n" "tcgen lightmap\n" "}\n" "endif\n" */// "if !r_fullbright\n" "{\n" "map $lightmap\n" // "if $deluxmap\n" // "blendfunc gl_dst_color gl_zero\n" // "endif\n" "}\n" // "endif\n" "{\n" "map $diffuse\n" "tcgen base\n" // "if $deluxmap || !r_fullbright\n" // "blendfunc gl_dst_color gl_zero\n" "blendfunc filter\n" // "endif\n" "}\n" "if gl_fb_bmodels\n" "{\n" "map $fullbright\n" "blendfunc add\n" "depthfunc equal\n" "}\n" "endif\n" "}\n" ); Shader_DefaultScript(shortname, s, builtin); if (r_lightprepass) s->flags |= SHADER_HASNORMALMAP; } void Shader_DefaultCinematic(const char *shortname, shader_t *s, const void *args) { Shader_DefaultScript(shortname, s, va( "{\n" "program default2d\n" "{\n" "videomap \"%s\"\n" "blendfunc gl_one gl_one_minus_src_alpha\n" "}\n" "}\n" , (const char*)args) ); } /*shortname should begin with 'skybox_'*/ void Shader_DefaultSkybox(const char *shortname, shader_t *s, const void *args) { int i; Shader_DefaultScript(shortname, s, va( "{\n" "sort sky\n" "surfaceparm nodlight\n" "skyparms %s - -\n" "}\n" , shortname+7) ); for (i = 0; i < 6; i++) { if (s->skydome->farbox_textures[i] == missing_texture) { if (s->skydome) Z_Free(s->skydome); s->skydome = NULL; return; } } } char *Shader_DefaultBSPWater(shader_t *s, const char *shortname, char *buffer, size_t buffersize) { int wstyle; int type; float alpha; qboolean explicitalpha = false; cvar_t *alphavars[] = { &r_wateralpha, &r_lavaalpha, &r_slimealpha, &r_telealpha}; cvar_t *stylevars[] = { &r_waterstyle, &r_lavastyle, &r_slimestyle, &r_telestyle}; if (!strncmp(shortname, "*portal", 7)) { return "{\n" "portal\n" "}\n"; } else if (!strncmp(shortname, "*lava", 5)) type = 1; else if (!strncmp(shortname, "*slime", 6)) type = 2; else if (!strncmp(shortname, "*tele", 5)) type = 3; else type = 0; alpha = Shader_FloatArgument(s, "ALPHA"); if (alpha) explicitalpha = true; else { if (ruleset_allow_watervis.ival) alpha = *alphavars[type]->string?alphavars[type]->value:alphavars[0]->value; else alpha = 1; } if (alpha <= 0) wstyle = -1; else if (r_fastturb.ival) wstyle = 0; else if (*stylevars[type]->string) wstyle = stylevars[type]->ival; else if (stylevars[0]->ival > 0) wstyle = stylevars[0]->ival; else wstyle = 1; if (wstyle > 1 && !ruleset_allow_watervis.ival) wstyle = 1; if (wstyle > 1 && !sh_config.progs_supported) wstyle = 1; //extra silly limitations switch(qrenderer) { #ifdef GLQUAKE case QR_OPENGL: if (wstyle > 2 && !gl_config.ext_framebuffer_objects) wstyle = 2; break; #endif #ifdef VKQUAKE case QR_VULKAN: if (wstyle > 3) wstyle = 3; break; #endif default: //altwater not supported with other renderers if (wstyle > 1) wstyle = 1; } switch(wstyle) { case -1: //invisible return ( "{\n" "surfaceparm nodraw\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "surfaceparm hasdiffuse\n" "}\n" ); case -2: //regular with r_wateralpha forced off. return ( "{\n" "{\n" "program defaultwarp\n" "map $diffuse\n" "tcmod turb 0.02 0.1 0.5 0.1\n" "}\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "surfaceparm hasdiffuse\n" "}\n" ); case 0: //fastturb return ( "{\n" "{\n" // "program defaultfill\n" "map $whiteimage\n" "rgbgen srgb $r_fastturbcolour\n" "}\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "surfaceparm hasdiffuse\n" "}\n" ); default: case 1: //vanilla style Q_snprintfz(buffer, buffersize, "{\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "{\n" "program defaultwarp%s\n" "map $diffuse\n" "tcmod turb 0.02 0.1 0.5 0.1\n" "if %g < 1\n" "alphagen const %g\n" "blendfunc gl_src_alpha gl_one_minus_src_alpha\n" "endif\n" "}\n" "surfaceparm hasdiffuse\n" "}\n" , explicitalpha?"":va("#ALPHA=%g",alpha), alpha, alpha); return buffer; case 2: //refraction of the underwater surface, with a fresnel return ( "{\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "{\n" "program altwater#FRESNEL=4\n" "map $refraction\n" "map $null\n"//$reflection "map $null\n"//$ripplemap "map $null\n"//$refractiondepth "}\n" "surfaceparm hasdiffuse\n" "}\n" ); case 3: //reflections return ( "{\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "{\n" "program altwater#REFLECT#FRESNEL=4\n" "map $refraction\n" "map $reflection\n" "map $null\n"//$ripplemap "map $null\n"//$refractiondepth "}\n" "surfaceparm hasdiffuse\n" "}\n" ); case 4: //ripples return ( "{\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "{\n" "program altwater#RIPPLEMAP#FRESNEL=4\n" "map $refraction\n" "map $null\n"//$reflection "map $ripplemap\n" "map $null\n"//$refractiondepth "}\n" "surfaceparm hasdiffuse\n" "}\n" ); case 5: //ripples+reflections return ( "{\n" "surfaceparm nodlight\n" "surfaceparm nomarks\n" "{\n" "program altwater#REFLECT#RIPPLEMAP#FRESNEL=4\n" "map $refraction\n" "map $reflection\n" "map $ripplemap\n" "map $null\n"//$refractiondepth "}\n" "surfaceparm hasdiffuse\n" "}\n" ); } } void Shader_DefaultWaterShader(const char *shortname, shader_t *s, const void *args) { char tmpbuffer[2048]; Shader_DefaultScript(shortname, s, Shader_DefaultBSPWater(s, shortname, tmpbuffer, sizeof(tmpbuffer))); } void Shader_DefaultBSPQ2(const char *shortname, shader_t *s, const void *args) { if (!strncmp(shortname, "sky/", 4)) { Shader_DefaultScript(shortname, s, "{\n" "surfaceparm nodlight\n" "skyparms - - -\n" "}\n" ); } else if (Shader_FloatArgument(s, "WARP"))//!strncmp(shortname, "warp/", 5) || !strncmp(shortname, "warp33/", 7) || !strncmp(shortname, "warp66/", 7)) { char tmpbuffer[2048]; Shader_DefaultScript(shortname, s, Shader_DefaultBSPWater(s, shortname, tmpbuffer, sizeof(tmpbuffer))); } else if (Shader_FloatArgument(s, "ALPHA"))// !strncmp(shortname, "trans/", 6)) { Shader_DefaultScript(shortname, s, "{\n" "{\n" "map $diffuse\n" "alphagen const $#ALPHA\n" "blendfunc blend\n" "}\n" "}\n" ); } else Shader_DefaultBSPLM(shortname, s, args); } void Shader_DefaultBSPQ1(const char *shortname, shader_t *s, const void *args) { char *builtin = NULL; char tmpbuffer[2048]; if (!strcmp(shortname, "mirror_portal")) { builtin = "{\n" "portal\n" "}\n"; } else if (r_mirroralpha.value < 1 && (!strcmp(shortname, "window02_1") || !strncmp(shortname, "mirror", 6))) { if (r_mirroralpha.value < 0) { builtin = "{\n" "portal\n" "{\n" "map $diffuse\n" "blendfunc blend\n" "alphagen portal 512\n" "depthwrite\n" "}\n" "}\n"; } else { builtin = "{\n" "portal\n" "{\n" "map $diffuse\n" "blendfunc blend\n" "alphagen const $r_mirroralpha\n" "depthwrite\n" "}\n" "surfaceparm nodlight\n" "}\n"; } } if (!builtin && (*shortname == '*' || *shortname == '!')) { builtin = Shader_DefaultBSPWater(s, shortname, tmpbuffer, sizeof(tmpbuffer)); } if (!builtin && !strncmp(shortname, "sky", 3)) { //q1 sky /*if (r_fastsky.ival) { builtin = ( "{\n" "sort sky\n" "{\n" "map $whiteimage\n" "rgbgen srgb $r_fastskycolour\n" "}\n" "surfaceparm nodlight\n" "}\n" ); }*/ /*else if (*r_skyboxname.string || *cl.skyname) { qboolean okay; Z_Free(s->skydome); s->skydome = (skydome_t *)Z_Malloc(sizeof(skydome_t)); okay = Shader_ParseSkySides(shortname, "", s->skydome->farbox_textures); s->flags |= SHADER_SKY|SHADER_NODLIGHT; s->sort = SHADER_SORT_SKY; if (okay) return; builtin = NULL; //if the r_skybox failed to load or whatever, reset and fall through and just use the regular sky Shader_Reset(s); }*/ if (!builtin) builtin = ( "{\n" "sort sky\n" "program defaultsky\n" "skyparms - 512 -\n" /*WARNING: these values are not authentic quake, only close aproximations*/ "{\n" "map $diffuse\n" "tcmod scale 10 10\n" "tcmod scroll 0.04 0.04\n" "depthwrite\n" "}\n" "{\n" "map $fullbright\n" "blendfunc blend\n" "tcmod scale 10 10\n" "tcmod scroll 0.02 0.02\n" "}\n" "}\n" ); } if (!builtin && *shortname == '{') { /*alpha test*/ if (sh_config.progs_supported) builtin = ( "{\n" "fte_program defaultwall#MASK=0.666#MASKLT\n" "}\n" ); else builtin = ( "{\n" /* "if $deluxmap\n" "{\n" "map $normalmap\n" "tcgen base\n" "}\n" "{\n" "map $deluxmap\n" "tcgen lightmap\n" "}\n" "endif\n"*/ "{\n" "map $diffuse\n" "tcgen base\n" "alphafunc ge128\n" "}\n" // "if $lightmap\n" "{\n" "map $lightmap\n" "if gl_overbright > 1\n" "blendfunc gl_dst_color gl_src_color\n" //scale it up twice. will probably still get clamped, but what can you do "else\n" "blendfunc gl_dst_color gl_zero\n" "endif\n" "depthfunc equal\n" "}\n" // "endif\n" "{\n" "map $fullbright\n" "blendfunc add\n" "depthfunc equal\n" "}\n" "}\n" ); } /*Hack: note that halflife would normally expect you to use rendermode/renderampt*/ if (!builtin && (!strncmp(shortname, "glass", 5)/* || !strncmp(shortname, "window", 6)*/)) { /*alpha bended*/ builtin = ( "{\n" "{\n" "map $diffuse\n" "tcgen base\n" "blendfunc blend\n" "}\n" "}\n" ); } if (builtin) Shader_DefaultScript(shortname, s, builtin); else Shader_DefaultBSPLM(shortname, s, args); } void Shader_DefaultBSPVertex(const char *shortname, shader_t *s, const void *args) { char *builtin = NULL; if (Shader_ParseShader("defaultvertexlit", s)) return; if (!builtin) { builtin = ( "{\n" "program defaultwall#VERTEXLIT\n" "{\n" "map $diffuse\n" "rgbgen vertex\n" "alphagen vertex\n" "}\n" "}\n" ); } Shader_DefaultScript(shortname, s, builtin); } void Shader_DefaultBSPFlare(const char *shortname, shader_t *s, const void *args) { shaderpass_t *pass; if (Shader_ParseShader("defaultflare", s)) return; pass = &s->passes[0]; pass->flags = SHADER_PASS_NOCOLORARRAY; pass->shaderbits |= SBITS_SRCBLEND_ONE|SBITS_DSTBLEND_ONE; pass->anim_frames[0] = R_LoadHiResTexture(shortname, NULL, 0); pass->rgbgen = RGB_GEN_VERTEX_LIGHTING; pass->alphagen = ALPHA_GEN_IDENTITY; pass->numtcmods = 0; pass->tcgen = TC_GEN_BASE; pass->numMergedPasses = 1; Shader_SetBlendmode(pass, NULL); if (!TEXVALID(pass->anim_frames[0])) { Con_DPrintf (CON_WARNING "Shader %s has a stage with no image: %s.\n", s->name, shortname ); pass->anim_frames[0] = missing_texture; } s->numpasses = 1; s->numdeforms = 0; s->flags = SHADER_FLARE; s->sort = SHADER_SORT_ADDITIVE; s->flags |= SHADER_NODRAW; } void Shader_DefaultSkin(const char *shortname, shader_t *s, const void *args) { if (Shader_ParseShader("defaultskin", s)) return; if (r_softwarebanding && qrenderer == QR_OPENGL && sh_config.progs_supported) { Shader_DefaultScript(shortname, s, "{\n" "program defaultskin#EIGHTBIT\n" "affine\n" "{\n" "map $colourmap\n" "}\n" "}\n" ); return; } if (r_lightprepass) { Shader_DefaultScript(shortname, s, "{\n" "{\n" "fte_program lpp_wall\n" "map $gbuffer2\n" //diffuse lighting info "map $gbuffer3\n" //specular lighting info "}\n" //this is drawn during the initial gbuffer pass to prepare it "fte_bemode gbuffer\n" "{\n" "{\n" "fte_program lpp_depthnorm\n" "tcgen base\n" "}\n" "}\n" "}\n" ); return; } if (r_tessellation.ival && sh_config.progs_supported) { Shader_DefaultScript(shortname, s, "{\n" "program defaultskin#TESS\n" "}\n" ); return; } Shader_DefaultScript(shortname, s, "{\n" "if $lpp\n" "program lpp_skin\n" "else\n" "program defaultskin\n" "endif\n" "if gl_affinemodels\n" "affine\n" "endif\n" "{\n" "map $diffuse\n" "rgbgen lightingDiffuse\n" "}\n" "{\n" "map $loweroverlay\n" "rgbgen bottomcolor\n" "blendfunc gl_src_alpha gl_one\n" "}\n" "{\n" "map $upperoverlay\n" "rgbgen topcolor\n" "blendfunc gl_src_alpha gl_one\n" "}\n" "{\n" "map $fullbright\n" "blendfunc add\n" "}\n" "}\n" ); } void Shader_DefaultSkinShell(const char *shortname, shader_t *s, const void *args) { if (Shader_ParseShader("defaultskinshell", s)) return; Shader_DefaultScript(shortname, s, "{\n" "sort seethrough\n" //before blend, but after other stuff. should fix most issues with shotgun etc effects obscuring it. // "deformvertexes normal 1 1\n" //draw it with depth but no colours at all "{\n" "map $whiteimage\n" "maskcolor\n" "depthwrite\n" "}\n" //now draw it again, depthfunc = equal should fill only the near-side, avoiding any excess-brightness issues with overlapping triangles "{\n" "map $whiteimage\n" "rgbgen entity\n" "alphagen entity\n" "blendfunc blend\n" "}\n" "}\n" ); } void Shader_Default2D(const char *shortname, shader_t *s, const void *genargs) { if (Shader_ParseShader("default2d", s)) return; if (sh_config.progs_supported && qrenderer != QR_DIRECT3D9 && !dpcompat_nopremulpics.ival) { //hexen2 needs premultiplied alpha to avoid looking ugly //but that results in problems where things are drawn with alpha not 0, so scale vertex colour by alpha in the fragment program Shader_DefaultScript(shortname, s, "{\n" "affine\n" "nomipmaps\n" "program default2d#PREMUL\n" "{\n" "clampmap $diffuse\n" "blendfunc gl_one gl_one_minus_src_alpha\n" "}\n" "sort additive\n" "}\n" ); TEXASSIGN(s->defaulttextures->base, R_LoadHiResTexture(s->name, genargs, IF_PREMULTIPLYALPHA|IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_CLAMP)); } else { Shader_DefaultScript(shortname, s, "{\n" "affine\n" "nomipmaps\n" "{\n" "clampmap $diffuse\n" "rgbgen vertex\n" "alphagen vertex\n" "blendfunc gl_src_alpha gl_one_minus_src_alpha\n" "}\n" "sort additive\n" "}\n" ); TEXASSIGN(s->defaulttextures->base, R_LoadHiResTexture(s->name, genargs, IF_UIPIC|IF_NOPICMIP|IF_NOMIPMAP|IF_CLAMP)); } } static qboolean Shader_ReadShaderTerms(parsestate_t *ps, struct scondinfo_s *cond) { char *token; if (!ps->ptr) return false; token = COM_ParseExt (&ps->ptr, true, true); if ( !token[0] ) return true; else if (!Shader_Conditional_Read(ps->s, cond, token, &ps->ptr)) { int i; for (i = 0; shadermacros[i].name; i++) { if (!Q_stricmp (token, shadermacros[i].name)) { #define SHADER_MACRO_ARGS 6 int argn = 0; char *oldptr; char arg[SHADER_MACRO_ARGS][256]; //parse args until the end of the line while (ps->ptr) { token = COM_ParseExt(&ps->ptr, false, true); if ( !token[0] ) { break; } if (argn <= SHADER_MACRO_ARGS) { Q_strncpyz(arg[argn], token, sizeof(arg[argn])); argn++; } } oldptr = ps->ptr; ps->ptr = shadermacros[i].body; Shader_ReadShaderTerms(ps, cond); ps->ptr = oldptr; return true; } } if (token[0] == '}') return false; else if (token[0] == '{') Shader_Readpass(ps); else if (Shader_Parsetok(ps, shaderkeys, token)) return false; } return true; } //loads a shader string into an existing shader object, and finalises it and stuff static void Shader_ReadShader(shader_t *s, char *shadersource, shadercachefile_t *sourcefile) { struct scondinfo_s cond = {0}; memset(&parsestate, 0, sizeof(parsestate)); if (sourcefile) { parsestate.forcedshader = *sourcefile->forcedshadername?sourcefile->forcedshadername:NULL; parsestate.parseflags = sourcefile->parseflags; } else parsestate.parseflags = 0; parsestate.specularexpscale = 1; parsestate.specularvalscale = 1; parsestate.ptr = shadersource; parsestate.s = s; if (!s->defaulttextures) { s->defaulttextures = Z_Malloc(sizeof(*s->defaulttextures)); s->numdefaulttextures = 0; } // set defaults s->flags = SHADER_CULL_FRONT; while (Shader_ReadShaderTerms(&parsestate, &cond)) { } if (cond.depth) { Con_Printf("if statements without endif in shader %s\n", s->name); } Shader_Finish ( s ); //querying the shader body often requires generating the shader, which then gets parsed. if (saveshaderbody) { size_t l = parsestate.ptr?parsestate.ptr - shadersource:0; Z_Free(*saveshaderbody); *saveshaderbody = BZ_Malloc(l+1); (*saveshaderbody)[l] = 0; memcpy(*saveshaderbody, shadersource, l); saveshaderbody = NULL; } } static qboolean Shader_ParseShader(char *parsename, shader_t *s) { size_t offset = 0, length; char *buf = NULL; shadercachefile_t *sourcefile = NULL; char *file; const char *token; if (!strchr(parsename, ':')) { //if the named shader is a .shader file then just directly load it. token = COM_GetFileExtension(parsename, NULL); if (!strcmp(token, ".mat") || !*token) { char shaderfile[MAX_QPATH]; if (!*token) { Q_snprintfz(shaderfile, sizeof(shaderfile), "%s.mat", parsename); file = COM_LoadTempMoreFile(shaderfile, &length); } else file = COM_LoadTempMoreFile(parsename, &length); if (file) { Shader_Reset(s); token = COM_ParseExt (&file, true, false); //we need to skip over the leading {. if (*token != '{') token = COM_ParseExt (&file, true, false); //try again, in case we found some legacy name. if (*token == '{') { Shader_ReadShader(s, file, NULL); return true; } else Con_Printf("file %s.shader does not appear to contain a shader\n", shaderfile); } } } if (Shader_LocateSource(parsename, &buf, &length, &offset, &sourcefile)) { // the shader is in the shader scripts if (buf && offset < length ) { file = buf + offset; token = COM_ParseExt (&file, true, true); if ( !file || token[0] != '{' ) { FS_FreeFile(buf); return false; } Shader_Reset(s); Shader_ReadShader(s, file, sourcefile); return true; } } return false; } void R_UnloadShader(shader_t *shader) { if (!shader) return; if (shader->uses <= 0) { Con_Printf("Shader double free (%s %i)\n", shader->name, shader->usageflags); return; } if (--shader->uses == 0) Shader_Free(shader); } static shader_t *R_LoadShader (const char *name, unsigned int usageflags, shader_gen_t *defaultgen, const char *genargs) { int i, f = -1; char cleanname[MAX_QPATH]; char shortname[MAX_QPATH]; char *argsstart; shader_t *s; if (!*name) name = "gfx/unspecified"; COM_AssertMainThread("R_LoadShader"); Q_strncpyz(cleanname, name, sizeof(cleanname)); COM_CleanUpPath(cleanname); // check the hash first s = Hash_Get(&shader_active_hash, cleanname); while (s) { //make sure the same texture can be used as either a lightmap or vertexlit shader //if it has an explicit shader overriding it then that still takes precidence. we might just have multiple copies of it. //q3 has a separate (internal) shader for every lightmap. if (!((s->usageflags ^ usageflags) & SUF_LIGHTMAP)) { if (!s->uses) break; s->uses++; return s; } s = Hash_GetNext(&shader_active_hash, cleanname, s); } // not loaded, find a free slot for (i = 0; i < r_numshaders; i++) { if (!r_shaders[i] || !r_shaders[i]->uses) { if ( f == -1 ) // free shader { f = i; break; } } } if (f == -1) { shader_t **n; int nm; f = r_numshaders; if (f == r_maxshaders) { if (!r_maxshaders) Sys_Error( "R_LoadShader: shader system not inited."); nm = r_maxshaders * 2; n = realloc(r_shaders, nm*sizeof(*n)); if (!n) { Sys_Error( "R_LoadShader: Shader limit exceeded."); return NULL; } memset(n+r_maxshaders, 0, (nm - r_maxshaders)*sizeof(*n)); r_shaders = n; r_maxshaders = nm; } } if (strlen(cleanname) >= sizeof(s->name)) { Sys_Error( "R_LoadShader: Shader name too long."); return NULL; } s = r_shaders[f]; if (!s) { s = r_shaders[f] = Z_Malloc(sizeof(*s)); } s->id = f; if (r_numshaders < f+1) r_numshaders = f+1; if (!s->defaulttextures) s->defaulttextures = Z_Malloc(sizeof(*s->defaulttextures)); else memset(s->defaulttextures, 0, sizeof(*s->defaulttextures)); s->numdefaulttextures = 0; Q_strncpyz(s->name, cleanname, sizeof(s->name)); s->usageflags = usageflags; s->generator = defaultgen; s->width = 0; s->height = 0; s->uses = 1; if (genargs) s->genargs = strdup(genargs); else s->genargs = NULL; //now determine the 'short name'. ie: the shader that is loaded off disk (no args, no extension) argsstart = strchr(cleanname, '#'); if (argsstart) *argsstart = 0; COM_StripExtension (cleanname, shortname, sizeof(shortname)); if (ruleset_allow_shaders.ival && !(usageflags & SUR_FORCEFALLBACK)) { if (sh_config.shadernamefmt) { char drivername[MAX_QPATH]; Q_snprintfz(drivername, sizeof(drivername), sh_config.shadernamefmt, cleanname); if (Shader_ParseShader(drivername, s)) return s; } if (Shader_ParseShader(cleanname, s)) return s; if (strcmp(cleanname, shortname)) if (Shader_ParseShader(shortname, s)) return s; } // make a default shader if (s->generator) { Shader_Reset(s); if (!strcmp(shortname, "textures/common/clip")) Shader_DefaultScript(cleanname, s, "{\n" "surfaceparm nodraw\n" "surfaceparm nodlight\n" "}\n"); else s->generator(cleanname, s, s->genargs); return s; } else { Shader_Free(s); } return NULL; } #ifdef _DEBUG static char *Shader_DecomposePass(char *o, shaderpass_t *p, qboolean simple) { if (!simple) { switch(p->rgbgen) { default: sprintf(o, "RGB_GEN_? "); break; case RGB_GEN_ENTITY: sprintf(o, "RGB_GEN_ENTITY "); break; case RGB_GEN_ONE_MINUS_ENTITY: sprintf(o, "RGB_GEN_ONE_MINUS_ENTITY "); break; case RGB_GEN_VERTEX_LIGHTING: sprintf(o, "RGB_GEN_VERTEX_LIGHTING "); break; case RGB_GEN_VERTEX_EXACT: sprintf(o, "RGB_GEN_VERTEX_EXACT "); break; case RGB_GEN_ONE_MINUS_VERTEX: sprintf(o, "RGB_GEN_ONE_MINUS_VERTEX "); break; case RGB_GEN_IDENTITY_LIGHTING: sprintf(o, "RGB_GEN_IDENTITY_LIGHTING "); break; case RGB_GEN_IDENTITY_OVERBRIGHT: sprintf(o, "RGB_GEN_IDENTITY_OVERBRIGHT "); break; case RGB_GEN_IDENTITY: sprintf(o, "RGB_GEN_IDENTITY "); break; case RGB_GEN_CONST: sprintf(o, "RGB_GEN_CONST "); break; case RGB_GEN_ENTITY_LIGHTING_DIFFUSE: sprintf(o, "RGB_GEN_ENTITY_LIGHTING_DIFFUSE "); break; case RGB_GEN_LIGHTING_DIFFUSE: sprintf(o, "RGB_GEN_LIGHTING_DIFFUSE "); break; case RGB_GEN_WAVE: sprintf(o, "RGB_GEN_WAVE "); break; case RGB_GEN_TOPCOLOR: sprintf(o, "RGB_GEN_TOPCOLOR "); break; case RGB_GEN_BOTTOMCOLOR: sprintf(o, "RGB_GEN_BOTTOMCOLOR "); break; case RGB_GEN_UNKNOWN: sprintf(o, "RGB_GEN_UNKNOWN "); break; } o+=strlen(o); sprintf(o, "\n"); o+=strlen(o); switch(p->alphagen) { default: sprintf(o, "ALPHA_GEN_? "); break; case ALPHA_GEN_ENTITY: sprintf(o, "ALPHA_GEN_ENTITY "); break; case ALPHA_GEN_WAVE: sprintf(o, "ALPHA_GEN_WAVE "); break; case ALPHA_GEN_PORTAL: sprintf(o, "ALPHA_GEN_PORTAL "); break; case ALPHA_GEN_SPECULAR: sprintf(o, "ALPHA_GEN_SPECULAR "); break; case ALPHA_GEN_IDENTITY: sprintf(o, "ALPHA_GEN_IDENTITY "); break; case ALPHA_GEN_VERTEX: sprintf(o, "ALPHA_GEN_VERTEX "); break; case ALPHA_GEN_CONST: sprintf(o, "ALPHA_GEN_CONST "); break; } o+=strlen(o); sprintf(o, "\n"); o+=strlen(o); } if (p->prog) { sprintf(o, "program %s\n", p->prog->name); o+=strlen(o); } if (p->shaderbits & SBITS_MISC_DEPTHWRITE) { sprintf(o, "SBITS_MISC_DEPTHWRITE\n"); o+=strlen(o); } if (p->shaderbits & SBITS_MISC_NODEPTHTEST) { sprintf(o, "SBITS_MISC_NODEPTHTEST\n"); o+=strlen(o); } else switch (p->shaderbits & SBITS_DEPTHFUNC_BITS) { case SBITS_DEPTHFUNC_EQUAL: sprintf(o, "depthfunc equal\n"); break; case SBITS_DEPTHFUNC_CLOSER: sprintf(o, "depthfunc less\n"); break; case SBITS_DEPTHFUNC_CLOSEREQUAL: sprintf(o, "depthfunc lequal\n"); break; case SBITS_DEPTHFUNC_FURTHER: sprintf(o, "depthfunc greater\n"); break; } if (p->shaderbits & SBITS_TESSELLATION) { sprintf(o, "SBITS_TESSELLATION\n"); o+=strlen(o); } if (p->shaderbits & SBITS_AFFINE) { sprintf(o, "SBITS_AFFINE\n"); o+=strlen(o); } if (p->shaderbits & SBITS_MASK_BITS) { sprintf(o, "SBITS_MASK_BITS\n"); o+=strlen(o); } if (p->shaderbits & SBITS_BLEND_BITS) { sprintf(o, "blendfunc"); o+=strlen(o); switch(p->shaderbits & SBITS_SRCBLEND_BITS) { case SBITS_SRCBLEND_NONE: sprintf(o, " SBITS_SRCBLEND_NONE"); break; case SBITS_SRCBLEND_ZERO: sprintf(o, " SBITS_SRCBLEND_ZERO"); break; case SBITS_SRCBLEND_ONE: sprintf(o, " SBITS_SRCBLEND_ONE"); break; case SBITS_SRCBLEND_DST_COLOR: sprintf(o, " SBITS_SRCBLEND_DST_COLOR"); break; case SBITS_SRCBLEND_ONE_MINUS_DST_COLOR: sprintf(o, " SBITS_SRCBLEND_ONE_MINUS_DST_COLOR"); break; case SBITS_SRCBLEND_SRC_ALPHA: sprintf(o, " SBITS_SRCBLEND_SRC_ALPHA"); break; case SBITS_SRCBLEND_ONE_MINUS_SRC_ALPHA: sprintf(o, " SBITS_SRCBLEND_ONE_MINUS_SRC_ALPHA"); break; case SBITS_SRCBLEND_DST_ALPHA: sprintf(o, " SBITS_SRCBLEND_DST_ALPHA"); break; case SBITS_SRCBLEND_ONE_MINUS_DST_ALPHA: sprintf(o, " SBITS_SRCBLEND_ONE_MINUS_DST_ALPHA"); break; case SBITS_SRCBLEND_SRC_COLOR_INVALID: sprintf(o, " SBITS_SRCBLEND_SRC_COLOR_INVALID"); break; case SBITS_SRCBLEND_ONE_MINUS_SRC_COLOR_INVALID: sprintf(o, " SBITS_SRCBLEND_ONE_MINUS_SRC_COLOR_INVALID"); break; case SBITS_SRCBLEND_ALPHA_SATURATE: sprintf(o, " SBITS_SRCBLEND_ALPHA_SATURATE"); break; default: sprintf(o, " SBITS_SRCBLEND_INVALID"); break; } o+=strlen(o); switch(p->shaderbits & SBITS_DSTBLEND_BITS) { case SBITS_DSTBLEND_NONE: sprintf(o, " SBITS_DSTBLEND_NONE"); break; case SBITS_DSTBLEND_ZERO: sprintf(o, " SBITS_DSTBLEND_ZERO"); break; case SBITS_DSTBLEND_ONE: sprintf(o, " SBITS_DSTBLEND_ONE"); break; case SBITS_DSTBLEND_DST_COLOR_INVALID: sprintf(o, " SBITS_DSTBLEND_DST_COLOR_INVALID"); break; case SBITS_DSTBLEND_ONE_MINUS_DST_COLOR_INVALID: sprintf(o, " SBITS_DSTBLEND_ONE_MINUS_DST_COLOR_INVALID"); break; case SBITS_DSTBLEND_SRC_ALPHA: sprintf(o, " SBITS_DSTBLEND_SRC_ALPHA"); break; case SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA: sprintf(o, " SBITS_DSTBLEND_ONE_MINUS_SRC_ALPHA"); break; case SBITS_DSTBLEND_DST_ALPHA: sprintf(o, " SBITS_DSTBLEND_DST_ALPHA"); break; case SBITS_DSTBLEND_ONE_MINUS_DST_ALPHA: sprintf(o, " SBITS_DSTBLEND_ONE_MINUS_DST_ALPHA"); break; case SBITS_DSTBLEND_SRC_COLOR: sprintf(o, " SBITS_DSTBLEND_SRC_COLOR"); break; case SBITS_DSTBLEND_ONE_MINUS_SRC_COLOR: sprintf(o, " SBITS_DSTBLEND_ONE_MINUS_SRC_COLOR"); break; case SBITS_DSTBLEND_ALPHA_SATURATE_INVALID: sprintf(o, " SBITS_DSTBLEND_ALPHA_SATURATE_INVALID"); break; default: sprintf(o, " SBITS_DSTBLEND_INVALID"); break; } o+=strlen(o); sprintf(o, "\n"); o+=strlen(o); } switch(p->shaderbits & SBITS_ATEST_BITS) { case SBITS_ATEST_NONE: break; case SBITS_ATEST_GE128: sprintf(o, "SBITS_ATEST_GE128\n"); break; case SBITS_ATEST_LT128: sprintf(o, "SBITS_ATEST_LT128\n"); break; case SBITS_ATEST_GT0: sprintf(o, "SBITS_ATEST_GT0\n"); break; } o+=strlen(o); return o; } static char *Shader_DecomposeSubPass(char *o, shaderpass_t *p, qboolean simple) { int i; if (!simple) { switch(p->tcgen) { default: sprintf(o, "TC_GEN_? "); break; case TC_GEN_BASE: sprintf(o, "TC_GEN_BASE "); break; case TC_GEN_LIGHTMAP: sprintf(o, "TC_GEN_LIGHTMAP "); break; case TC_GEN_ENVIRONMENT: sprintf(o, "TC_GEN_ENVIRONMENT "); break; case TC_GEN_DOTPRODUCT: sprintf(o, "TC_GEN_DOTPRODUCT "); break; case TC_GEN_VECTOR: sprintf(o, "TC_GEN_VECTOR "); break; case TC_GEN_NORMAL: sprintf(o, "TC_GEN_NORMAL "); break; case TC_GEN_SVECTOR: sprintf(o, "TC_GEN_SVECTOR "); break; case TC_GEN_TVECTOR: sprintf(o, "TC_GEN_TVECTOR "); break; case TC_GEN_SKYBOX: sprintf(o, "TC_GEN_SKYBOX "); break; case TC_GEN_WOBBLESKY: sprintf(o, "TC_GEN_WOBBLESKY "); break; case TC_GEN_REFLECT: sprintf(o, "TC_GEN_REFLECT "); break; case TC_GEN_UNSPECIFIED: sprintf(o, "TC_GEN_UNSPECIFIED "); break; } o+=strlen(o); sprintf(o, "\n"); o+=strlen(o); for (i = 0; i < p->numtcmods; i++) { switch(p->tcmods[i].type) { default: sprintf(o, "TCMOD_GEN_? "); break; case SHADER_TCMOD_NONE: sprintf(o, "SHADER_TCMOD_NONE "); break; case SHADER_TCMOD_SCALE: sprintf(o, "SHADER_TCMOD_SCALE "); break; case SHADER_TCMOD_SCROLL: sprintf(o, "SHADER_TCMOD_SCROLL "); break; case SHADER_TCMOD_STRETCH: sprintf(o, "SHADER_TCMOD_STRETCH "); break; case SHADER_TCMOD_ROTATE: sprintf(o, "SHADER_TCMOD_ROTATE "); break; case SHADER_TCMOD_TRANSFORM: sprintf(o, "SHADER_TCMOD_TRANSFORM "); break; case SHADER_TCMOD_TURB: sprintf(o, "SHADER_TCMOD_TURB "); break; case SHADER_TCMOD_PAGE: sprintf(o, "SHADER_TCMOD_PAGE "); break; } o+=strlen(o); sprintf(o, "\n"); o+=strlen(o); } switch(p->blendmode) { default: sprintf(o, "PBM_? "); break; case PBM_MODULATE: sprintf(o, "PBM_MODULATE "); break; case PBM_OVERBRIGHT: sprintf(o, "PBM_OVERBRIGHT "); break; case PBM_DECAL: sprintf(o, "PBM_DECAL "); break; case PBM_ADD: sprintf(o, "PBM_ADD "); break; case PBM_DOTPRODUCT: sprintf(o, "PBM_DOTPRODUCT "); break; case PBM_REPLACE: sprintf(o, "PBM_REPLACE "); break; case PBM_REPLACELIGHT: sprintf(o, "PBM_REPLACELIGHT "); break; case PBM_MODULATE_PREV_COLOUR: sprintf(o, "PBM_MODULATE_PREV_COLOUR "); break; } o+=strlen(o); } switch(p->texgen) { default: sprintf(o, "T_GEN_? "); break; case T_GEN_SINGLEMAP: if (p->anim_frames[0]) { unsigned int flags = p->anim_frames[0]->flags; sprintf(o, "singlemap \"%s\" %ix%i%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s", p->anim_frames[0]->ident, p->anim_frames[0]->width, p->anim_frames[0]->height, (p->anim_frames[0]->status == TEX_FAILED)?" FAILED":"", (flags&IF_CLAMP)?" clamp":"", (flags&IF_NOMIPMAP)?" nomipmap":"", (flags&IF_NEAREST)?" nearest":"", (flags&IF_LINEAR)?" linear":"", (flags&IF_UIPIC)?" uipic":"", (flags&IF_SRGB)?" srgb":"", (flags&IF_NOPICMIP)?" nopicmip":"", (flags&IF_NOALPHA)?" noalpha":"", (flags&IF_NOGAMMA)?" noalpha":"", (flags&IF_TEXTYPE)?" non-2d":"", (flags&IF_MIPCAP)?"":" nomipcap", (flags&IF_PREMULTIPLYALPHA)?" premultiply":"", (flags&IF_NOSRGB)?" nosrgb":"", (flags&IF_PALETTIZE)?" palettize":"", (flags&IF_NOPURGE)?" nopurge":"", (flags&IF_HIGHPRIORITY)?" highpri":"", (flags&IF_LOWPRIORITY)?" lowpri":"", (flags&IF_LOADNOW)?" loadnow":"", (flags&IF_TRYBUMP)?" trybump":"", (flags&IF_RENDERTARGET)?" rendertarget":"", (flags&IF_EXACTEXTENSION)?" exactext":"", (flags&IF_NOREPLACE)?" noreplace":"", (flags&IF_NOWORKER)?" noworker":"" ); } else sprintf(o, "singlemap "); break; case T_GEN_ANIMMAP: sprintf(o, "animmap "); break; case T_GEN_LIGHTMAP: sprintf(o, "lightmap "); break; case T_GEN_DELUXMAP: sprintf(o, "deluxmap "); break; case T_GEN_SHADOWMAP: sprintf(o, "shadowmap "); break; case T_GEN_LIGHTCUBEMAP: sprintf(o, "lightcubemap "); break; case T_GEN_DIFFUSE: sprintf(o, "diffuse "); break; case T_GEN_NORMALMAP: sprintf(o, "normalmap "); break; case T_GEN_SPECULAR: sprintf(o, "specular "); break; case T_GEN_UPPEROVERLAY: sprintf(o, "upperoverlay "); break; case T_GEN_LOWEROVERLAY: sprintf(o, "loweroverlay "); break; case T_GEN_FULLBRIGHT: sprintf(o, "fullbright "); break; case T_GEN_PALETTED: sprintf(o, "paletted "); break; case T_GEN_REFLECTCUBE: sprintf(o, "reflectcube "); break; case T_GEN_REFLECTMASK: sprintf(o, "reflectmask "); break; case T_GEN_DISPLACEMENT: sprintf(o, "displacementmap "); break; case T_GEN_CURRENTRENDER: sprintf(o, "currentrender "); break; case T_GEN_SOURCECOLOUR: sprintf(o, "sourcecolour "); break; case T_GEN_SOURCEDEPTH: sprintf(o, "sourcedepth "); break; case T_GEN_REFLECTION: sprintf(o, "reflection "); break; case T_GEN_REFRACTION: sprintf(o, "refraction "); break; case T_GEN_REFRACTIONDEPTH: sprintf(o, "refractiondepth "); break; case T_GEN_RIPPLEMAP: sprintf(o, "ripplemap "); break; case T_GEN_SOURCECUBE: sprintf(o, "sourcecube "); break; case T_GEN_VIDEOMAP: sprintf(o, "videomap "); break; case T_GEN_CUBEMAP: sprintf(o, "cubemap "); break; case T_GEN_3DMAP: sprintf(o, "3dmap "); break; case T_GEN_GBUFFERCASE: sprintf(o, "gbuffer%i ",p->texgen-T_GEN_GBUFFER0); break; } o+=strlen(o); sprintf(o, "\n"); o+=strlen(o); return o; } char *Shader_Decompose(shader_t *s) { static char decomposebuf[32768]; char *o = decomposebuf; shaderpass_t *p; unsigned int i, j; sprintf(o, "\n<---\n"); o+=strlen(o); switch (s->sort) { default: sprintf(o, "sort %i\n", s->sort); break; case SHADER_SORT_NONE: sprintf(o, "sort %i (SHADER_SORT_NONE)\n", s->sort); break; case SHADER_SORT_RIPPLE: sprintf(o, "sort %i (SHADER_SORT_RIPPLE)\n", s->sort); break; case SHADER_SORT_DEFERREDLIGHT: sprintf(o, "sort %i (SHADER_SORT_DEFERREDLIGHT)\n", s->sort); break; case SHADER_SORT_PORTAL: sprintf(o, "sort %i (SHADER_SORT_PORTAL)\n", s->sort); break; case SHADER_SORT_SKY: sprintf(o, "sort %i (SHADER_SORT_SKY)\n", s->sort); break; case SHADER_SORT_OPAQUE: sprintf(o, "sort %i (SHADER_SORT_OPAQUE)\n", s->sort); break; case SHADER_SORT_DECAL: sprintf(o, "sort %i (SHADER_SORT_DECAL)\n", s->sort); break; case SHADER_SORT_SEETHROUGH: sprintf(o, "sort %i (SHADER_SORT_SEETHROUGH)\n", s->sort); break; case SHADER_SORT_BANNER: sprintf(o, "sort %i (SHADER_SORT_BANNER)\n", s->sort); break; case SHADER_SORT_UNDERWATER: sprintf(o, "sort %i (SHADER_SORT_UNDERWATER)\n", s->sort); break; case SHADER_SORT_BLEND: sprintf(o, "sort %i (SHADER_SORT_BLEND)\n", s->sort); break; case SHADER_SORT_ADDITIVE: sprintf(o, "sort %i (SHADER_SORT_ADDITIVE)\n", s->sort); break; case SHADER_SORT_NEAREST: sprintf(o, "sort %i (SHADER_SORT_NEAREST)\n", s->sort); break; } o+=strlen(o); if (s->prog) { sprintf(o, "program %s\n", s->prog->name); o+=strlen(o); p = s->passes; o = Shader_DecomposePass(o, p, true); for (j = 0; j < s->numpasses; j++) o = Shader_DecomposeSubPass(o, p+j, true); } else { for (i = 0; i < s->numpasses; i+= p->numMergedPasses) { p = &s->passes[i]; sprintf(o, "{\n"); o+=strlen(o); o = Shader_DecomposePass(o, p, false); for (j = 0; j < p->numMergedPasses; j++) o = Shader_DecomposeSubPass(o, p+j, !!p->prog); sprintf(o, "}\n"); o+=strlen(o); } } sprintf(o, "--->\n"); o+=strlen(o); return decomposebuf; } #endif char *Shader_GetShaderBody(shader_t *s, char *fname, size_t fnamesize) { char *adr, *parsename=NULL, *argsstart; char cleanname[MAX_QPATH]; char shortname[MAX_QPATH]; char drivername[MAX_QPATH]; int oldsort; qboolean resort = false; if (!s || !s->uses) return NULL; adr = Z_StrDup("UNKNOWN BODY"); saveshaderbody = &adr; strcpy(cleanname, s->name); argsstart = strchr(cleanname, '#'); if (argsstart) *argsstart = 0; COM_StripExtension (cleanname, shortname, sizeof(shortname)); if (ruleset_allow_shaders.ival && !(s->usageflags & SUR_FORCEFALLBACK)) { if (sh_config.shadernamefmt) { Q_snprintfz(drivername, sizeof(drivername), sh_config.shadernamefmt, cleanname); if (!parsename && Shader_ParseShader(drivername, s)) parsename = drivername; } if (!parsename && Shader_ParseShader(cleanname, s)) parsename = cleanname; if (!parsename && Shader_ParseShader(shortname, s)) parsename = shortname; } if (!parsename && s->generator) { oldsort = s->sort; Shader_Reset(s); s->generator(shortname, s, s->genargs); if (s->sort != oldsort) resort = true; } if (resort) { Mod_ResortShaders(); } if (fnamesize) { *fname = 0; if (parsename) { unsigned int key; shadercache_t *cache; key = Hash_Key (parsename, HASH_SIZE); cache = shader_hash[key]; for ( ; cache; cache = cache->hash_next) { if (!Q_stricmp (cache->name, parsename)) { char *c, *stop; int line = 1; //okay, this is the shader we're looking for, we know where it came from too, so there's handy. //figure out the line index now, by just counting the \ns up to the offset for (c = cache->source->data, stop = c+cache->offset; c < stop; c++) { if (*c == '\n') line++; } Q_snprintfz(fname, fnamesize, "%s:%i", cache->source->name, line); break; } } if (!strchr(parsename, ':')) { //if the named shader is a .shader file then just directly load it. const char *token = COM_GetFileExtension(parsename, NULL); if (!strcmp(token, ".shader") || !*token) { char shaderfile[MAX_QPATH]; if (!*token) { Q_snprintfz(shaderfile, sizeof(shaderfile), "%s.shader", parsename); if (COM_FCheckExists(shaderfile)) Q_snprintfz(fname, fnamesize, "%s:%i", shaderfile, 1); } else if (COM_FCheckExists(parsename)) Q_snprintfz(fname, fnamesize, "%s:%i", parsename, 1); } } } } #ifdef _DEBUG { char *add, *ret; add = Shader_Decompose(s); if (*add) { ret = Z_Malloc(strlen(add) + strlen(adr) + 1); strcpy(ret, adr); strcpy(ret + strlen(ret), add); Z_Free(adr); adr = ret; } } #endif saveshaderbody = NULL; return adr; } void Shader_ShowShader_f(void) { char *sourcename = Cmd_Argv(1); shader_t *o = R_LoadShader(sourcename, SUF_NONE, NULL, NULL); if (!o) o = R_LoadShader(sourcename, SUF_LIGHTMAP, NULL, NULL); if (!o) o = R_LoadShader(sourcename, SUF_2D, NULL, NULL); if (o) { char *body = Shader_GetShaderBody(o, NULL, 0); if (body) { Con_Printf("%s\n{%s\n", o->name, body); Z_Free(body); } else { Con_Printf("Shader \"%s\" is not in use\n", o->name); } } else Con_Printf("Shader \"%s\" is not loaded\n", sourcename); } void Shader_TouchTextures(void) { int i, j, k; shader_t *s; shaderpass_t *p; texnums_t *t; for (i = 0; i < r_numshaders; i++) { s = r_shaders[i]; if (!s || !s->uses) continue; for (j = 0; j < s->numpasses; j++) { p = &s->passes[j]; for (k = 0; k < countof(p->anim_frames); k++) if (p->anim_frames[k]) p->anim_frames[k]->regsequence = r_regsequence; } for (j = 0; j < max(1,s->numdefaulttextures); j++) { t = &s->defaulttextures[j]; if (t->base) t->base->regsequence = r_regsequence; if (t->bump) t->bump->regsequence = r_regsequence; if (t->fullbright) t->fullbright->regsequence = r_regsequence; if (t->specular) t->specular->regsequence = r_regsequence; if (t->upperoverlay) t->upperoverlay->regsequence = r_regsequence; if (t->loweroverlay) t->loweroverlay->regsequence = r_regsequence; } } } void Shader_DoReload(void) { shader_t *s; unsigned int i; char shortname[MAX_QPATH]; char cleanname[MAX_QPATH], *argsstart; int oldsort; qboolean resort = false; //don't spam shader reloads while we're connecting, as that's just wasteful. if (cls.state && cls.state < ca_active) return; if (!r_shaders) return; //err, not ready yet if (shader_rescan_needed) { Shader_FlushCache(); if (ruleset_allow_shaders.ival) { COM_EnumerateFiles("materials/*.mtr", Shader_InitCallback_Doom3, NULL); COM_EnumerateFiles("shaders/*.shader", Shader_InitCallback, NULL); COM_EnumerateFiles("scripts/*.shader", Shader_InitCallback, NULL); COM_EnumerateFiles("scripts/*.rscript", Shader_InitCallback, NULL); } shader_reload_needed = true; shader_rescan_needed = false; Con_DPrintf("Rescanning shaders\n"); } else { if (!shader_reload_needed) return; Con_DPrintf("Reloading shaders\n"); } shader_reload_needed = false; R2D_ImageColours(1,1,1,1); Shader_ReloadGenerics(); for (i = 0; i < r_numshaders; i++) { s = r_shaders[i]; if (!s || !s->uses) continue; strcpy(cleanname, s->name); argsstart = strchr(cleanname, '#'); if (argsstart) *argsstart = 0; COM_StripExtension (cleanname, shortname, sizeof(shortname)); if (ruleset_allow_shaders.ival && !(s->usageflags & SUR_FORCEFALLBACK)) { if (sh_config.shadernamefmt) { char drivername[MAX_QPATH]; Q_snprintfz(drivername, sizeof(drivername), sh_config.shadernamefmt, cleanname); if (Shader_ParseShader(drivername, s)) continue; } if (Shader_ParseShader(cleanname, s)) continue; if (strcmp(cleanname, shortname)) if (Shader_ParseShader(shortname, s)) continue; } if (s->generator) { oldsort = s->sort; Shader_Reset(s); s->generator(shortname, s, s->genargs); if (s->sort != oldsort) resort = true; } } if (resort) { Mod_ResortShaders(); } } void Shader_NeedReload(qboolean rescanfs) { if (rescanfs) shader_rescan_needed = true; shader_reload_needed = true; } cin_t *R_ShaderGetCinematic(shader_t *s) { #ifdef HAVE_MEDIA_DECODER int j; if (!s) return NULL; for (j = 0; j < s->numpasses; j++) if (s->passes[j].cin) return s->passes[j].cin; #endif /*no cinematic in this shader!*/ return NULL; } shader_t *R_ShaderFind(const char *name) { int i; char shortname[MAX_QPATH]; shader_t *s; if (!r_shaders) return NULL; COM_StripExtension ( name, shortname, sizeof(shortname)); COM_CleanUpPath(shortname); //try and find it for (i = 0; i < r_numshaders; i++) { s = r_shaders[i]; if (!s || !s->uses) continue; if (!Q_stricmp (shortname, s->name) ) return s; } return NULL; } cin_t *R_ShaderFindCinematic(const char *name) { #ifdef HAVE_MEDIA_DECODER return R_ShaderGetCinematic(R_ShaderFind(name)); #else return NULL; #endif } void Shader_ResetRemaps(void) { shader_t *s; int i; for (i = 0; i < r_numshaders; i++) { s = r_shaders[i]; if (!s) continue; s->remapto = s; s->remaptime = 0; } } void R_RemapShader(const char *sourcename, const char *destname, float timeoffset) { shader_t *o; shader_t *n; //make sure all types of the shader are remapped properly. //if there's a .shader file with it then it should 'just work'. o = R_LoadShader (sourcename, SUF_NONE, NULL, NULL); n = R_LoadShader (destname, SUF_NONE, NULL, NULL); if (o) { if (!n) n = o; o->remapto = n; o->remaptime = timeoffset; //this just feels wrong. } o = R_LoadShader (sourcename, SUF_2D, NULL, NULL); n = R_LoadShader (destname, SUF_2D, NULL, NULL); if (o) { if (!n) n = o; o->remapto = n; o->remaptime = timeoffset; } o = R_LoadShader (sourcename, SUF_LIGHTMAP, NULL, NULL); n = R_LoadShader (destname, SUF_LIGHTMAP, NULL, NULL); if (o) { if (!n) { n = R_LoadShader (destname, SUF_2D, NULL, NULL); if (!n) n = o; } o->remapto = n; o->remaptime = timeoffset; } } void Shader_RemapShader_f(void) { char *sourcename = Cmd_Argv(1); char *destname = Cmd_Argv(2); float timeoffset = atof(Cmd_Argv(3)); if (!Cmd_FromGamecode() && strcmp(InfoBuf_ValueForKey(&cl.serverinfo, "*cheats"), "ON")) { Con_Printf("%s may only be used from gamecode, or when cheats are enabled\n", Cmd_Argv(0)); return; } if (!*sourcename) { Con_Printf("%s originalshader remappedshader starttime\n", Cmd_Argv(0)); return; } R_RemapShader(sourcename, destname, timeoffset); } //blocks int R_GetShaderSizes(shader_t *shader, int *width, int *height, qboolean blocktillloaded) { if (!shader) return false; if (!shader->width && !shader->height) { int i; if (width) *width = 0; if (height) *height = 0; if ((shader->flags & SHADER_HASDIFFUSE) && shader->defaulttextures->base) { if (shader->defaulttextures->base->status == TEX_LOADING) { if (!blocktillloaded) return -1; COM_WorkerPartialSync(shader->defaulttextures->base, &shader->defaulttextures->base->status, TEX_LOADING); } if (shader->defaulttextures->base->status == TEX_LOADED) { shader->width = shader->defaulttextures->base->width; shader->height = shader->defaulttextures->base->height; } } else if ((shader->flags & SHADER_HASPALETTED) && shader->defaulttextures->paletted) { if (shader->defaulttextures->paletted->status == TEX_LOADING) { if (!blocktillloaded) return -1; COM_WorkerPartialSync(shader->defaulttextures->paletted, &shader->defaulttextures->paletted->status, TEX_LOADING); } if (shader->defaulttextures->paletted->status == TEX_LOADED) { shader->width = shader->defaulttextures->paletted->width; shader->height = shader->defaulttextures->paletted->height; } } else { for (i = 0; i < shader->numpasses; i++) { if (shader->passes[i].texgen == T_GEN_SINGLEMAP && shader->passes[i].anim_frames[0] && shader->passes[i].anim_frames[0]->status == TEX_LOADING) { if (!blocktillloaded) return -1; COM_WorkerPartialSync(shader->passes[i].anim_frames[0], &shader->passes[i].anim_frames[0]->status, TEX_LOADING); } if (shader->passes[i].texgen == T_GEN_DIFFUSE && (shader->defaulttextures->base && shader->defaulttextures->base->status == TEX_LOADING)) { if (!blocktillloaded) return -1; COM_WorkerPartialSync(shader->defaulttextures->base, &shader->defaulttextures->base->status, TEX_LOADING); } if (shader->passes[i].texgen == T_GEN_PALETTED && (shader->defaulttextures->paletted && shader->defaulttextures->paletted->status == TEX_LOADING)) { if (!blocktillloaded) return -1; COM_WorkerPartialSync(shader->defaulttextures->paletted, &shader->defaulttextures->paletted->status, TEX_LOADING); } } for (i = 0; i < shader->numpasses; i++) { if (shader->passes[i].texgen == T_GEN_SINGLEMAP) { if (shader->passes[i].anim_frames[0] && shader->passes[i].anim_frames[0]->status == TEX_LOADED) { shader->width = shader->passes[i].anim_frames[0]->width; shader->height = shader->passes[i].anim_frames[0]->height; } break; } if (shader->passes[i].texgen == T_GEN_DIFFUSE) { if (shader->defaulttextures->base && shader->defaulttextures->base->status == TEX_LOADED) { shader->width = shader->defaulttextures->base->width; shader->height = shader->defaulttextures->base->height; } break; } if (shader->passes[i].texgen == T_GEN_PALETTED) { if (shader->defaulttextures->paletted && shader->defaulttextures->paletted->status == TEX_LOADED) { shader->width = shader->defaulttextures->paletted->width; shader->height = shader->defaulttextures->paletted->height; } break; } } if (i == shader->numpasses) { //this shader has no textures from which to source a width and height if (!shader->width) shader->width = 64; if (!shader->height) shader->height = 64; } } } if (shader->width && shader->height) { if (width) *width = shader->width; if (height) *height = shader->height; return true; //final size } else { //fill with dummy values if (width) *width = 64; if (height) *height = 64; return false; } } shader_t *R_RegisterPic (const char *name, const char *subdirs) { shader_t *shader; shader = R_LoadShader (name, SUF_2D, Shader_Default2D, subdirs); return shader; } shader_t *QDECL R_RegisterShader (const char *name, unsigned int usageflags, const char *shaderscript) { return R_LoadShader (name, usageflags, Shader_DefaultScript, shaderscript); } shader_t *R_RegisterShader_Lightmap (const char *name) { return R_LoadShader (name, SUF_LIGHTMAP, Shader_DefaultBSPLM, NULL); } shader_t *R_RegisterShader_Vertex (const char *name) { return R_LoadShader (name, 0, Shader_DefaultBSPVertex, NULL); } shader_t *R_RegisterShader_Flare (const char *name) { return R_LoadShader (name, 0, Shader_DefaultBSPFlare, NULL); } shader_t *QDECL R_RegisterSkin (const char *shadername, const char *modname) { char newsname[MAX_QPATH]; shader_t *shader; #ifdef _DEBUG if (shadername == com_token) Con_Printf("R_RegisterSkin was passed com_token. that will bug out.\n"); #endif newsname[0] = 0; if (modname && !strchr(shadername, '/') && *shadername) { char *b = COM_SkipPath(modname); if (b != modname && b-modname + strlen(shadername)+1 < sizeof(newsname)) { b--; //no trailing / memcpy(newsname, modname, b - modname); newsname[b-modname] = 0; } } if (*newsname) { int l = strlen(newsname); Q_strncpyz(newsname+l, ":models", sizeof(newsname)-l); } else Q_strncpyz(newsname, "models", sizeof(newsname)); shader = R_LoadShader (shadername, 0, Shader_DefaultSkin, newsname); return shader; } shader_t *R_RegisterCustom (const char *name, unsigned int usageflags, shader_gen_t *defaultgen, const void *args) { return R_LoadShader (name, usageflags, defaultgen, args); } #endif //SERVERONLY