/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2006-2011 Robert Beckebans This file is part of XreaL source code. XreaL source code 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. XreaL source code 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 XreaL source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // tr_shader.c -- this file deals with the parsing and definition of shaders #include "tr_local.h" #define MAX_GUIDETEXT_HASH 2048 static char **guideTextHashTable[MAX_GUIDETEXT_HASH]; #define MAX_SHADERTABLE_HASH 1024 static shaderTable_t *shaderTableHashTable[MAX_SHADERTABLE_HASH]; #define FILE_HASH_SIZE 1024 static shader_t *shaderHashTable[FILE_HASH_SIZE]; #define MAX_SHADERTEXT_HASH 2048 static char **shaderTextHashTable[MAX_SHADERTEXT_HASH]; static char *s_guideText; static char *s_shaderText; // the shader is parsed into these global variables, then copied into // dynamically allocated memory if it is valid. static shaderTable_t table; static shaderStage_t stages[MAX_SHADER_STAGES]; static shader_t shader; static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS]; static qboolean deferLoad; // ydnar: these are here because they are only referenced while parsing a shader static char implicitMap[MAX_QPATH]; static unsigned implicitStateBits; static cullType_t implicitCullType; /* ================ return a hash value for the filename ================ */ static long generateHashValue(const char *fname, const int size) { int i; // int len; long hash; char letter; hash = 0; i = 0; // len = strlen(fname); while(fname[i] != '\0') // for(i = 0; i < len; i++) { letter = tolower(fname[i]); if(letter == '.') break; // don't include extension if(letter == '\\') letter = '/'; // damn path names if(letter == PATH_SEP) letter = '/'; // damn path names hash += (long)(letter) * (i + 119); i++; } hash = (hash ^ (hash >> 10) ^ (hash >> 20)); hash &= (size - 1); return hash; } void R_RemapShader(const char *shaderName, const char *newShaderName, const char *timeOffset) { char strippedName[MAX_QPATH]; int hash; shader_t *sh, *sh2; qhandle_t h; sh = R_FindShaderByName(shaderName); if(sh == NULL || sh == tr.defaultShader) { h = RE_RegisterShader(shaderName); sh = R_GetShaderByHandle(h); } if(sh == NULL || sh == tr.defaultShader) { ri.Printf(PRINT_WARNING, "WARNING: R_RemapShader: shader %s not found\n", shaderName); return; } sh2 = R_FindShaderByName(newShaderName); if(sh2 == NULL || sh2 == tr.defaultShader) { h = RE_RegisterShader(newShaderName); sh2 = R_GetShaderByHandle(h); } if(sh2 == NULL || sh2 == tr.defaultShader) { ri.Printf(PRINT_WARNING, "WARNING: R_RemapShader: new shader %s not found\n", newShaderName); return; } // remap all the shaders with the given name // even tho they might have different lightmaps COM_StripExtension(shaderName, strippedName, sizeof(strippedName)); hash = generateHashValue(strippedName, FILE_HASH_SIZE); for(sh = shaderHashTable[hash]; sh; sh = sh->next) { if(Q_stricmp(sh->name, strippedName) == 0) { if(sh != sh2) { sh->remappedShader = sh2; } else { sh->remappedShader = NULL; } } } } /* =============== ParseVector =============== */ static qboolean ParseVector(char **text, int count, float *v) { char *token; int i; token = COM_ParseExt(text, qfalse); if(strcmp(token, "(")) { ri.Printf(PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name); return qfalse; } for(i = 0; i < count; i++) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing vector element in shader '%s'\n", shader.name); return qfalse; } v[i] = atof(token); } token = COM_ParseExt(text, qfalse); if(strcmp(token, ")")) { ri.Printf(PRINT_WARNING, "WARNING: missing parenthesis in shader '%s'\n", shader.name); return qfalse; } return qtrue; } opstring_t opStrings[] = { {"bad", OP_BAD} , {"&&", OP_LAND} , {"||", OP_LOR} , {">=", OP_GE} , {"<=", OP_LE} , {"==", OP_LEQ} , {"!=", OP_LNE} , {"+", OP_ADD} , {"-", OP_SUB} , {"/", OP_DIV} , {"%", OP_MOD} , {"*", OP_MUL} , {"neg", OP_NEG} , {"<", OP_LT} , {">", OP_GT} , {"(", OP_LPAREN} , {")", OP_RPAREN} , {"[", OP_LBRACKET} , {"]", OP_RBRACKET} , {"c", OP_NUM} , {"time", OP_TIME} , {"parm0", OP_PARM0} , {"parm1", OP_PARM1} , {"parm2", OP_PARM2} , {"parm3", OP_PARM3} , {"parm4", OP_PARM4} , {"parm5", OP_PARM5} , {"parm6", OP_PARM6} , {"parm7", OP_PARM7} , {"parm8", OP_PARM8} , {"parm9", OP_PARM9} , {"parm10", OP_PARM10} , {"parm11", OP_PARM11} , {"global0", OP_GLOBAL0} , {"global1", OP_GLOBAL1} , {"global2", OP_GLOBAL2} , {"global3", OP_GLOBAL3} , {"global4", OP_GLOBAL4} , {"global5", OP_GLOBAL5} , {"global6", OP_GLOBAL6} , {"global7", OP_GLOBAL7} , {"fragmentShaders", OP_FRAGMENTSHADERS} , {"frameBufferObjects", OP_FRAMEBUFFEROBJECTS} , {"sound", OP_SOUND} , {"distance", OP_DISTANCE} , {"table", OP_TABLE} , {NULL, OP_BAD} }; static void GetOpType(char *token, expOperation_t * op) { opstring_t *opString; char tableName[MAX_QPATH]; int hash; shaderTable_t *tb; if((token[0] >= '0' && token[0] <= '9') || //(token[0] == '-' && token[1] >= '0' && token[1] <= '9') || //(token[0] == '+' && token[1] >= '0' && token[1] <= '9') || (token[0] == '.' && token[1] >= '0' && token[1] <= '9')) { op->type = OP_NUM; return; } Q_strncpyz(tableName, token, sizeof(tableName)); hash = generateHashValue(tableName, MAX_SHADERTABLE_HASH); for(tb = shaderTableHashTable[hash]; tb; tb = tb->next) { if(Q_stricmp(tb->name, tableName) == 0) { // match found op->type = OP_TABLE; op->value = tb->index; return; } } for(opString = opStrings; opString->s; opString++) { if(!Q_stricmp(token, opString->s)) { op->type = opString->type; return; } } op->type = OP_BAD; } static qboolean IsOperand(opcode_t oc) { switch (oc) { case OP_NUM: case OP_TIME: case OP_PARM0: case OP_PARM1: case OP_PARM2: case OP_PARM3: case OP_PARM4: case OP_PARM5: case OP_PARM6: case OP_PARM7: case OP_PARM8: case OP_PARM9: case OP_PARM10: case OP_PARM11: case OP_GLOBAL0: case OP_GLOBAL1: case OP_GLOBAL2: case OP_GLOBAL3: case OP_GLOBAL4: case OP_GLOBAL5: case OP_GLOBAL6: case OP_GLOBAL7: case OP_FRAGMENTSHADERS: case OP_FRAMEBUFFEROBJECTS: case OP_SOUND: case OP_DISTANCE: return qtrue; default: return qfalse; } } static qboolean IsOperator(opcode_t oc) { switch (oc) { case OP_LAND: case OP_LOR: case OP_GE: case OP_LE: case OP_LEQ: case OP_LNE: case OP_ADD: case OP_SUB: case OP_DIV: case OP_MOD: case OP_MUL: case OP_NEG: case OP_LT: case OP_GT: case OP_TABLE: return qtrue; default: return qfalse; } } static int GetOpPrecedence(opcode_t oc) { switch (oc) { case OP_LOR: return 1; case OP_LAND: return 2; case OP_LEQ: case OP_LNE: return 3; case OP_GE: case OP_LE: case OP_LT: case OP_GT: return 4; case OP_ADD: case OP_SUB: return 5; case OP_DIV: case OP_MOD: case OP_MUL: return 6; case OP_NEG: return 7; case OP_TABLE: return 8; default: return 0; } } static char *ParseExpressionElement(char **data_p) { int c = 0, len; char *data; const char **punc; static char token[MAX_TOKEN_CHARS]; // multiple character punctuation tokens const char *punctuation[] = { "&&", "||", "<=", ">=", "==", "!=", NULL }; if(!data_p) { ri.Error(ERR_FATAL, "ParseExpressionElement: NULL data_p"); } data = *data_p; len = 0; token[0] = 0; // make sure incoming data is valid if(!data) { *data_p = NULL; return token; } // skip whitespace while(1) { // skip whitespace while((c = *data) <= ' ') { if(!c) { *data_p = NULL; return token; } else if(c == '\n') { data++; *data_p = data; return token; } else { data++; } } c = *data; // skip double slash comments if(c == '/' && data[1] == '/') { data += 2; while(*data && *data != '\n') { data++; } } // skip /* */ comments else if(c == '/' && data[1] == '*') { data += 2; while(*data && (*data != '*' || data[1] != '/')) { data++; } if(*data) { data += 2; } } else { // a real token to parse break; } } // handle quoted strings if(c == '\"') { data++; while(1) { c = *data++; if((c == '\\') && (*data == '\"')) { // allow quoted strings to use \" to indicate the " character data++; } else if(c == '\"' || !c) { token[len] = 0; *data_p = (char *)data; return token; } if(len < MAX_TOKEN_CHARS - 1) { token[len] = c; len++; } } } // check for a number if((c >= '0' && c <= '9') || //(c == '-' && data[1] >= '0' && data[1] <= '9') || //(c == '+' && data[1] >= '0' && data[1] <= '9') || (c == '.' && data[1] >= '0' && data[1] <= '9')) { do { if(len < MAX_TOKEN_CHARS - 1) { token[len] = c; len++; } data++; c = *data; } while((c >= '0' && c <= '9') || c == '.'); // parse the exponent if(c == 'e' || c == 'E') { if(len < MAX_TOKEN_CHARS - 1) { token[len] = c; len++; } data++; c = *data; if(c == '-' || c == '+') { if(len < MAX_TOKEN_CHARS - 1) { token[len] = c; len++; } data++; c = *data; } do { if(len < MAX_TOKEN_CHARS - 1) { token[len] = c; len++; } data++; c = *data; } while(c >= '0' && c <= '9'); } if(len == MAX_TOKEN_CHARS) { len = 0; } token[len] = 0; *data_p = (char *)data; return token; } // check for a regular word if((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_')) { do { if(len < MAX_TOKEN_CHARS - 1) { token[len] = c; len++; } data++; c = *data; } while((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c == '_') || (c >= '0' && c <= '9')); if(len == MAX_TOKEN_CHARS) { len = 0; } token[len] = 0; *data_p = (char *)data; return token; } // check for multi-character punctuation token for(punc = punctuation; *punc; punc++) { int l; int j; l = strlen(*punc); for(j = 0; j < l; j++) { if(data[j] != (*punc)[j]) { break; } } if(j == l) { // a valid multi-character punctuation memcpy(token, *punc, l); token[l] = 0; data += l; *data_p = (char *)data; return token; } } // single character punctuation token[0] = *data; token[1] = 0; data++; *data_p = (char *)data; return token; } /* =============== ParseExpression =============== */ static void ParseExpression(char **text, expression_t * exp) { int i; char *token; expOperation_t op, op2; expOperation_t inFixOps[MAX_EXPRESSION_OPS]; int numInFixOps; // convert stack expOperation_t tmpOps[MAX_EXPRESSION_OPS]; int numTmpOps; numInFixOps = 0; numTmpOps = 0; exp->numOps = 0; // push left parenthesis on the stack op.type = OP_LPAREN; op.value = 0; inFixOps[numInFixOps++] = op; while(1) { token = ParseExpressionElement(text); if(token[0] == 0 || token[0] == ',') break; if(numInFixOps == MAX_EXPRESSION_OPS) { ri.Printf(PRINT_ALL, "WARNING: too many arithmetic expression operations in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); return; } GetOpType(token, &op); switch (op.type) { case OP_BAD: ri.Printf(PRINT_ALL, "WARNING: unknown token '%s' for arithmetic expression in shader '%s'\n", token, shader.name); break; case OP_LBRACKET: inFixOps[numInFixOps++] = op; // add extra ( op2.type = OP_LPAREN; op2.value = 0; inFixOps[numInFixOps++] = op2; break; case OP_RBRACKET: // add extra ) op2.type = OP_RPAREN; op2.value = 0; inFixOps[numInFixOps++] = op2; inFixOps[numInFixOps++] = op; break; case OP_NUM: op.value = atof(token); inFixOps[numInFixOps++] = op; break; case OP_TABLE: // value already set by GetOpType inFixOps[numInFixOps++] = op; break; default: op.value = 0; inFixOps[numInFixOps++] = op; break; } } // push right parenthesis on the stack op.type = OP_RPAREN; op.value = 0; inFixOps[numInFixOps++] = op; for(i = 0; i < (numInFixOps - 1); i++) { op = inFixOps[i]; op2 = inFixOps[i + 1]; // convert OP_SUBs that should be unary into OP_NEG if(op2.type == OP_SUB && op.type != OP_RPAREN && op.type != OP_TABLE && !IsOperand(op.type)) inFixOps[i + 1].type = OP_NEG; } #if 0 ri.Printf(PRINT_ALL, "infix:\n"); for(i = 0; i < numInFixOps; i++) { op = inFixOps[i]; switch (op.type) { case OP_NUM: ri.Printf(PRINT_ALL, "%f ", op.value); break; case OP_TABLE: ri.Printf(PRINT_ALL, "%s ", tr.shaderTables[(int)op.value]->name); break; default: ri.Printf(PRINT_ALL, "%s ", opStrings[op.type].s); break; } } ri.Printf(PRINT_ALL, "\n"); #endif // http://cis.stvincent.edu/swd/stl/stacks/stacks.html // http://www.qiksearch.com/articles/cs/infix-postfix/ // http://www.experts-exchange.com/Programming/Programming_Languages/C/Q_20394130.html // // convert infix representation to postfix // for(i = 0; i < numInFixOps; i++) { op = inFixOps[i]; // if current operator in infix is digit if(IsOperand(op.type)) { exp->ops[exp->numOps++] = op; } // if current operator in infix is left parenthesis else if(op.type == OP_LPAREN) { tmpOps[numTmpOps++] = op; } // if current operator in infix is operator else if(IsOperator(op.type)) { while(qtrue) { if(!numTmpOps) { ri.Printf(PRINT_ALL, "WARNING: invalid infix expression in shader '%s'\n", shader.name); return; } else { // get top element op2 = tmpOps[numTmpOps - 1]; if(IsOperator(op2.type)) { if(GetOpPrecedence(op2.type) >= GetOpPrecedence(op.type)) { exp->ops[exp->numOps++] = op2; numTmpOps--; } else { break; } } else { break; } } } tmpOps[numTmpOps++] = op; } // if current operator in infix is right parenthesis else if(op.type == OP_RPAREN) { while(qtrue) { if(!numTmpOps) { ri.Printf(PRINT_ALL, "WARNING: invalid infix expression in shader '%s'\n", shader.name); return; } else { // get top element op2 = tmpOps[numTmpOps - 1]; if(op2.type != OP_LPAREN) { exp->ops[exp->numOps++] = op2; numTmpOps--; } else { numTmpOps--; break; } } } } } // everything went ok exp->active = qtrue; #if 0 ri.Printf(PRINT_ALL, "postfix:\n"); for(i = 0; i < exp->numOps; i++) { op = exp->ops[i]; switch (op.type) { case OP_NUM: ri.Printf(PRINT_ALL, "%f ", op.value); break; case OP_TABLE: ri.Printf(PRINT_ALL, "%s ", tr.shaderTables[(int)op.value]->name); break; default: ri.Printf(PRINT_ALL, "%s ", opStrings[op.type].s); break; } } ri.Printf(PRINT_ALL, "\n"); #endif } /* =============== NameToAFunc =============== */ static unsigned NameToAFunc(const char *funcname) { if(!Q_stricmp(funcname, "GT0")) { return GLS_ATEST_GT_0; } else if(!Q_stricmp(funcname, "LT128")) { return GLS_ATEST_LT_128; } else if(!Q_stricmp(funcname, "GE128")) { return GLS_ATEST_GE_128; } ri.Printf(PRINT_WARNING, "WARNING: invalid alphaFunc name '%s' in shader '%s'\n", funcname, shader.name); return 0; } /* =============== NameToSrcBlendMode =============== */ static int NameToSrcBlendMode(const char *name) { if(!Q_stricmp(name, "GL_ONE")) { return GLS_SRCBLEND_ONE; } else if(!Q_stricmp(name, "GL_ZERO")) { return GLS_SRCBLEND_ZERO; } else if(!Q_stricmp(name, "GL_DST_COLOR")) { return GLS_SRCBLEND_DST_COLOR; } else if(!Q_stricmp(name, "GL_ONE_MINUS_DST_COLOR")) { return GLS_SRCBLEND_ONE_MINUS_DST_COLOR; } else if(!Q_stricmp(name, "GL_SRC_ALPHA")) { return GLS_SRCBLEND_SRC_ALPHA; } else if(!Q_stricmp(name, "GL_ONE_MINUS_SRC_ALPHA")) { return GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA; } else if(!Q_stricmp(name, "GL_DST_ALPHA")) { return GLS_SRCBLEND_DST_ALPHA; } else if(!Q_stricmp(name, "GL_ONE_MINUS_DST_ALPHA")) { return GLS_SRCBLEND_ONE_MINUS_DST_ALPHA; } else if(!Q_stricmp(name, "GL_SRC_ALPHA_SATURATE")) { return GLS_SRCBLEND_ALPHA_SATURATE; } ri.Printf(PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name); return GLS_SRCBLEND_ONE; } /* =============== NameToDstBlendMode =============== */ static int NameToDstBlendMode(const char *name) { if(!Q_stricmp(name, "GL_ONE")) { return GLS_DSTBLEND_ONE; } else if(!Q_stricmp(name, "GL_ZERO")) { return GLS_DSTBLEND_ZERO; } else if(!Q_stricmp(name, "GL_SRC_ALPHA")) { return GLS_DSTBLEND_SRC_ALPHA; } else if(!Q_stricmp(name, "GL_ONE_MINUS_SRC_ALPHA")) { return GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; } else if(!Q_stricmp(name, "GL_DST_ALPHA")) { return GLS_DSTBLEND_DST_ALPHA; } else if(!Q_stricmp(name, "GL_ONE_MINUS_DST_ALPHA")) { return GLS_DSTBLEND_ONE_MINUS_DST_ALPHA; } else if(!Q_stricmp(name, "GL_SRC_COLOR")) { return GLS_DSTBLEND_SRC_COLOR; } else if(!Q_stricmp(name, "GL_ONE_MINUS_SRC_COLOR")) { return GLS_DSTBLEND_ONE_MINUS_SRC_COLOR; } ri.Printf(PRINT_WARNING, "WARNING: unknown blend mode '%s' in shader '%s', substituting GL_ONE\n", name, shader.name); return GLS_DSTBLEND_ONE; } /* =============== NameToGenFunc =============== */ static genFunc_t NameToGenFunc(const char *funcname) { if(!Q_stricmp(funcname, "sin")) { return GF_SIN; } else if(!Q_stricmp(funcname, "square")) { return GF_SQUARE; } else if(!Q_stricmp(funcname, "triangle")) { return GF_TRIANGLE; } else if(!Q_stricmp(funcname, "sawtooth")) { return GF_SAWTOOTH; } else if(!Q_stricmp(funcname, "inversesawtooth")) { return GF_INVERSE_SAWTOOTH; } else if(!Q_stricmp(funcname, "noise")) { return GF_NOISE; } ri.Printf(PRINT_WARNING, "WARNING: invalid genfunc name '%s' in shader '%s'\n", funcname, shader.name); return GF_SIN; } /* =================== ParseWaveForm =================== */ static void ParseWaveForm(char **text, waveForm_t * wave) { char *token; token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name); return; } wave->func = NameToGenFunc(token); // BASE, AMP, PHASE, FREQ token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name); return; } wave->base = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name); return; } wave->amplitude = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name); return; } wave->phase = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing waveform parm in shader '%s'\n", shader.name); return; } wave->frequency = atof(token); } /* =================== ParseTexMod =================== */ static qboolean ParseTexMod(char **text, shaderStage_t * stage) { const char *token; texModInfo_t *tmi; if(stage->bundle[0].numTexMods == TR_MAX_TEXMODS) { ri.Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name); return qfalse; } tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; stage->bundle[0].numTexMods++; token = COM_ParseExt(text, qfalse); // ri.Printf(PRINT_ALL, "using tcMod '%s' in shader '%s'\n", token, shader.name); // turb if(!Q_stricmp(token, "turb")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing tcMod turb parms in shader '%s'\n", shader.name); return qfalse; } tmi->wave.base = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name); return qfalse; } tmi->wave.amplitude = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name); return qfalse; } tmi->wave.phase = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing tcMod turb in shader '%s'\n", shader.name); return qfalse; } tmi->wave.frequency = atof(token); tmi->type = TMOD_TURBULENT; } // scale else if(!Q_stricmp(token, "scale")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name); return qfalse; } tmi->scale[0] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing scale parms in shader '%s'\n", shader.name); return qfalse; } tmi->scale[1] = atof(token); tmi->type = TMOD_SCALE; } // scroll else if(!Q_stricmp(token, "scroll")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name); return qfalse; } tmi->scroll[0] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing scale scroll parms in shader '%s'\n", shader.name); return qfalse; } tmi->scroll[1] = atof(token); tmi->type = TMOD_SCROLL; } // stretch else if(!Q_stricmp(token, "stretch")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name); return qfalse; } tmi->wave.func = NameToGenFunc(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name); return qfalse; } tmi->wave.base = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name); return qfalse; } tmi->wave.amplitude = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name); return qfalse; } tmi->wave.phase = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing stretch parms in shader '%s'\n", shader.name); return qfalse; } tmi->wave.frequency = atof(token); tmi->type = TMOD_STRETCH; } // transform else if(!Q_stricmp(token, "transform")) { MatrixIdentity(tmi->matrix); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name); return qfalse; } tmi->matrix[0] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name); return qfalse; } tmi->matrix[1] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name); return qfalse; } tmi->matrix[4] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name); return qfalse; } tmi->matrix[5] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name); return qfalse; } tmi->matrix[12] = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name); return qfalse; } tmi->matrix[13] = atof(token); tmi->type = TMOD_TRANSFORM; } // rotate else if(!Q_stricmp(token, "rotate")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing tcMod rotate parms in shader '%s'\n", shader.name); return qfalse; } tmi->rotateSpeed = atof(token); tmi->type = TMOD_ROTATE; } // entityTranslate else if(!Q_stricmp(token, "entityTranslate")) { tmi->type = TMOD_ENTITY_TRANSLATE; } else { ri.Printf(PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name); return qfalse; } // Tr3B NOTE: some shaders using tcMod are messed up by artists so we need this bugfix while(1) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) break; ri.Printf(PRINT_WARNING, "WARNING: obsolete tcMod parameter '%s' in shader '%s'\n", token, shader.name); } return qtrue; } static qboolean ParseMap(shaderStage_t * stage, char **text, char *buffer, int bufferSize) { int len; char *token; // examples // map textures/caves/tembrick1crum_local.tga // addnormals (textures/caves/tembrick1crum_local.tga, heightmap (textures/caves/tembrick1crum_bmp.tga, 3 )) // heightmap( textures/hell/hellbones_d07bbump.tga, 8) while(1) { token = COM_ParseExt(text, qfalse); if(!token[0]) { // end of line break; } Q_strcat(buffer, bufferSize, token); Q_strcat(buffer, bufferSize, " "); } if(!buffer[0]) { ri.Printf(PRINT_WARNING, "WARNING: 'map' missing parameter in shader '%s'\n", shader.name); return qfalse; } len = strlen(buffer); buffer[len - 1] = 0; // replace last ' ' with tailing zero return qtrue; } static qboolean LoadMap(shaderStage_t * stage, char *buffer) { char *token; int imageBits = 0; filterType_t filterType; wrapType_t wrapType; qboolean uncompressed; char *buffer_p = &buffer[0]; if(!buffer || !buffer[0]) { ri.Printf(PRINT_WARNING, "WARNING: NULL parameter for LoadMap in shader '%s'\n", shader.name); return qfalse; } // ri.Printf(PRINT_ALL, "LoadMap: buffer '%s'\n", buffer); token = COM_ParseExt(&buffer_p, qfalse); if(!Q_stricmp(token, "$whiteimage") || !Q_stricmp(token, "$white") || !Q_stricmp(token, "_white") || !Q_stricmp(token, "*white")) { stage->bundle[0].image[0] = tr.whiteImage; return qtrue; } else if(!Q_stricmp(token, "$blackimage") || !Q_stricmp(token, "$black") || !Q_stricmp(token, "_black") || !Q_stricmp(token, "*black")) { stage->bundle[0].image[0] = tr.blackImage; return qtrue; } else if(!Q_stricmp(token, "$flatimage") || !Q_stricmp(token, "$flat") || !Q_stricmp(token, "_flat") || !Q_stricmp(token, "*flat")) { stage->bundle[0].image[0] = tr.flatImage; return qtrue; } #if defined(COMPAT_ET) || defined(COMPAT_Q3A) else if(!Q_stricmp(token, "$lightmap")) { stage->type = ST_LIGHTMAP; return qtrue; } #endif // determine image options if(stage->overrideNoPicMip || shader.noPicMip || stage->highQuality || stage->forceHighQuality) { imageBits |= IF_NOPICMIP; } if(stage->type == ST_NORMALMAP || stage->type == ST_HEATHAZEMAP || stage->type == ST_LIQUIDMAP) { imageBits |= IF_NORMALMAP; } if(stage->type == ST_NORMALMAP && shader.parallax) { imageBits |= IF_DISPLACEMAP; } if(stage->uncompressed || stage->highQuality || stage->forceHighQuality || shader.uncompressed) { imageBits |= IF_NOCOMPRESSION; } if(stage->forceHighQuality) { imageBits |= IF_NOCOMPRESSION; } if(stage->stateBits & (GLS_ATEST_BITS)) { imageBits |= IF_ALPHATEST; } if(stage->overrideFilterType) { filterType = stage->filterType; } else { filterType = shader.filterType; } if(stage->overrideWrapType) { wrapType = stage->wrapType; } else { wrapType = shader.wrapType; } // try to load the image stage->bundle[0].image[0] = R_FindImageFile(buffer, imageBits, filterType, wrapType, shader.name); if(!stage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "WARNING: R_FindImageFile could not find image '%s' in shader '%s'\n", buffer, shader.name); return qfalse; } return qtrue; } /* =================== ParseStage =================== */ static qboolean ParseStage(shaderStage_t * stage, char **text) { char *token; int colorMaskBits = 0; int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0, polyModeBits = 0; qboolean depthMaskExplicit = qfalse; int imageBits = 0; filterType_t filterType; char buffer[1024] = ""; qboolean loadMap = qfalse; while(1) { token = COM_ParseExt(text, qtrue); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: no matching '}' found\n"); return qfalse; } if(token[0] == '}') { break; } // if() else if(!Q_stricmp(token, "if")) { ParseExpression(text, &stage->ifExp); } // map else if(!Q_stricmp(token, "map")) { if(!ParseMap(stage, text, buffer, sizeof(buffer))) { //ri.Printf(PRINT_WARNING, "WARNING: ParseMap could not create '%s' in shader '%s'\n", token, shader.name); return qfalse; } else { loadMap = qtrue; } } // lightmap else if(!Q_stricmp(token, "lightmap")) { #if defined(COMPAT_Q3A) || defined(COMPAT_ET) if(!ParseMap(stage, text, buffer, sizeof(buffer))) { //ri.Printf(PRINT_WARNING, "WARNING: ParseMap could not create '%s' in shader '%s'\n", token, shader.name); return qfalse; } else { loadMap = qtrue; } //stage->type = ST_LIGHTMAP; #else ri.Printf(PRINT_WARNING, "WARNING: lightmap keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); #endif } // remoteRenderMap else if(!Q_stricmp(token, "remoteRenderMap")) { ri.Printf(PRINT_WARNING, "WARNING: remoteRenderMap keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // mirrorRenderMap else if(!Q_stricmp(token, "mirrorRenderMap")) { ri.Printf(PRINT_WARNING, "WARNING: mirrorRenderMap keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // clampmap else if(!Q_stricmp(token, "clampmap")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name); return qfalse; } imageBits = 0; if(stage->overrideNoPicMip || shader.noPicMip) { imageBits |= IF_NOPICMIP; } if(stage->overrideFilterType) { filterType = stage->filterType; } else { filterType = shader.filterType; } stage->bundle[0].image[0] = R_FindImageFile(token, imageBits, filterType, WT_CLAMP, shader.name); if(!stage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name); return qfalse; } } // animMap .... else if(!Q_stricmp(token, "animMap")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name); return qfalse; } stage->bundle[0].imageAnimationSpeed = atof(token); imageBits = 0; if(stage->overrideNoPicMip || shader.noPicMip) { imageBits |= IF_NOPICMIP; } if(stage->overrideFilterType) { filterType = stage->filterType; } else { filterType = shader.filterType; } // parse up to MAX_IMAGE_ANIMATIONS animations while(1) { int num; token = COM_ParseExt(text, qfalse); if(!token[0]) { break; } num = stage->bundle[0].numImages; if(num < MAX_IMAGE_ANIMATIONS) { stage->bundle[0].image[num] = R_FindImageFile(token, imageBits, filterType, WT_REPEAT, shader.name); if(!stage->bundle[0].image[num]) { ri.Printf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name); return qfalse; } stage->bundle[0].numImages++; } } } else if(!Q_stricmp(token, "videoMap")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'videoMap' keyword in shader '%s'\n", shader.name); return qfalse; } stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic(token, 0, 0, 512, 512, (CIN_loop | CIN_silent | CIN_shader)); if(stage->bundle[0].videoMapHandle != -1) { stage->bundle[0].isVideoMap = qtrue; stage->bundle[0].image[0] = tr.scratchImage[stage->bundle[0].videoMapHandle]; } } // soundmap [waveform] else if(!Q_stricmp(token, "soundMap")) { ri.Printf(PRINT_WARNING, "WARNING: soundMap keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // cubeMap else if(!Q_stricmp(token, "cubeMap") || !Q_stricmp(token, "cameraCubeMap")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'cubeMap' keyword in shader '%s'\n", shader.name); return qfalse; } imageBits = 0; if(stage->overrideNoPicMip || shader.noPicMip) { imageBits |= IF_NOPICMIP; } if(stage->overrideFilterType) { filterType = stage->filterType; } else { filterType = shader.filterType; } stage->bundle[0].image[0] = R_FindCubeImage(token, imageBits, filterType, WT_EDGE_CLAMP, shader.name); if(!stage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "WARNING: R_FindCubeImage could not find '%s' in shader '%s'\n", token, shader.name); return qfalse; } } // alphafunc else if(!Q_stricmp(token, "alphaFunc")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'alphaFunc' keyword in shader '%s'\n", shader.name); return qfalse; } atestBits = NameToAFunc(token); } // alphaTest else if(!Q_stricmp(token, "alphaTest")) { atestBits = GLS_ATEST_GE_128; ParseExpression(text, &stage->alphaTestExp); } // depthFunc else if(!Q_stricmp(token, "depthfunc")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'depthfunc' keyword in shader '%s'\n", shader.name); return qfalse; } if(!Q_stricmp(token, "lequal")) { depthFuncBits = 0; } else if(!Q_stricmp(token, "equal")) { depthFuncBits = GLS_DEPTHFUNC_EQUAL; } else { ri.Printf(PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name); continue; } } // ignoreAlphaTest else if(!Q_stricmp(token, "ignoreAlphaTest")) { depthFuncBits = 0; } // nearest else if(!Q_stricmp(token, "nearest")) { stage->overrideFilterType = qtrue; stage->filterType = FT_NEAREST; } // linear else if(!Q_stricmp(token, "linear")) { stage->overrideFilterType = qtrue; stage->filterType = FT_LINEAR; stage->overrideNoPicMip = qtrue; } // noPicMip else if(!Q_stricmp(token, "noPicMip")) { stage->overrideNoPicMip = qtrue; } // clamp else if(!Q_stricmp(token, "clamp")) { stage->overrideWrapType = qtrue; stage->wrapType = WT_CLAMP; } // edgeClamp else if(!Q_stricmp(token, "edgeClamp")) { stage->overrideWrapType = qtrue; stage->wrapType = WT_EDGE_CLAMP; } // zeroClamp else if(!Q_stricmp(token, "zeroClamp")) { stage->overrideWrapType = qtrue; stage->wrapType = WT_ZERO_CLAMP; } // alphaZeroClamp else if(!Q_stricmp(token, "alphaZeroClamp")) { stage->overrideWrapType = qtrue; stage->wrapType = WT_ALPHA_ZERO_CLAMP; } // noClamp else if(!Q_stricmp(token, "noClamp")) { stage->overrideWrapType = qtrue; stage->wrapType = WT_REPEAT; } // uncompressed else if(!Q_stricmp(token, "uncompressed")) { stage->uncompressed = qtrue; } // highQuality else if(!Q_stricmp(token, "highQuality")) { stage->highQuality = qtrue; stage->overrideNoPicMip = qtrue; } // forceHighQuality else if(!Q_stricmp(token, "forceHighQuality")) { stage->forceHighQuality = qtrue; stage->overrideNoPicMip = qtrue; } // detail else if(!Q_stricmp(token, "detail")) { ri.Printf(PRINT_WARNING, "WARNING: detail keyword not supported in shader '%s'\n", shader.name); continue; } // ET fog else if(!Q_stricmp(token, "fog")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for fog in shader '%s'\n", shader.name); continue; } if(!Q_stricmp(token, "on")) { stage->noFog = qfalse; } else { stage->noFog = qtrue; } } // blendfunc // or blendfunc else if(!Q_stricmp(token, "blendfunc")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name); continue; } // check for "simple" blends first if(!Q_stricmp(token, "add")) { blendSrcBits = GLS_SRCBLEND_ONE; blendDstBits = GLS_DSTBLEND_ONE; } else if(!Q_stricmp(token, "filter")) { blendSrcBits = GLS_SRCBLEND_DST_COLOR; blendDstBits = GLS_DSTBLEND_ZERO; } else if(!Q_stricmp(token, "blend")) { blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; } else if ( !Q_stricmp( token, "alphaadd" ) ) { blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; blendDstBits = GLS_DSTBLEND_ONE; } else { // complex double blends blendSrcBits = NameToSrcBlendMode(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for blendFunc in shader '%s'\n", shader.name); continue; } blendDstBits = NameToDstBlendMode(token); } // clear depth mask for blended surfaces if(!depthMaskExplicit) { depthMaskBits = 0; } } // blend , // or blend // or blend else if(!Q_stricmp(token, "blend")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for blend in shader '%s'\n", shader.name); continue; } // check for "simple" blends first if(!Q_stricmp(token, "add")) { blendSrcBits = GLS_SRCBLEND_ONE; blendDstBits = GLS_DSTBLEND_ONE; } else if(!Q_stricmp(token, "filter")) { blendSrcBits = GLS_SRCBLEND_DST_COLOR; blendDstBits = GLS_DSTBLEND_ZERO; } else if(!Q_stricmp(token, "modulate")) { blendSrcBits = GLS_SRCBLEND_DST_COLOR; blendDstBits = GLS_DSTBLEND_ZERO; } else if(!Q_stricmp(token, "blend")) { blendSrcBits = GLS_SRCBLEND_SRC_ALPHA; blendDstBits = GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; } else if(!Q_stricmp(token, "none")) { blendSrcBits = GLS_SRCBLEND_ZERO; blendDstBits = GLS_DSTBLEND_ONE; } // check for other semantic meanings else if(!Q_stricmp(token, "diffuseMap")) { stage->type = ST_DIFFUSEMAP; } else if(!Q_stricmp(token, "bumpMap")) { stage->type = ST_NORMALMAP; } else if(!Q_stricmp(token, "specularMap")) { stage->type = ST_SPECULARMAP; } else { // complex double blends blendSrcBits = NameToSrcBlendMode(token); token = COM_ParseExt(text, qfalse); if(token[0] != ',') { ri.Printf(PRINT_WARNING, "WARNING: expecting ',', found '%s' instead for blend in shader '%s'\n", token, shader.name); continue; } token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for blend in shader '%s'\n", shader.name); continue; } blendDstBits = NameToDstBlendMode(token); } // clear depth mask for blended surfaces if(!depthMaskExplicit && stage->type == ST_COLORMAP) { depthMaskBits = 0; } } // stage else if(!Q_stricmp(token, "stage")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parameters for stage in shader '%s'\n", shader.name); continue; } if(!Q_stricmp(token, "colorMap")) { stage->type = ST_COLORMAP; } else if(!Q_stricmp(token, "diffuseMap")) { stage->type = ST_DIFFUSEMAP; } else if(!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap")) { stage->type = ST_NORMALMAP; } else if(!Q_stricmp(token, "specularMap")) { stage->type = ST_SPECULARMAP; } else if(!Q_stricmp(token, "reflectionMap")) { stage->type = ST_REFLECTIONMAP; } else if(!Q_stricmp(token, "refractionMap")) { stage->type = ST_REFRACTIONMAP; } else if(!Q_stricmp(token, "dispersionMap")) { stage->type = ST_DISPERSIONMAP; } else if(!Q_stricmp(token, "skyboxMap")) { stage->type = ST_SKYBOXMAP; } else if(!Q_stricmp(token, "screenMap")) { stage->type = ST_SCREENMAP; } else if(!Q_stricmp(token, "portalMap")) { stage->type = ST_PORTALMAP; } else if(!Q_stricmp(token, "heathazeMap")) { stage->type = ST_HEATHAZEMAP; } else if(!Q_stricmp(token, "liquidMap")) { stage->type = ST_LIQUIDMAP; } else if(!Q_stricmp(token, "attenuationMapXY")) { stage->type = ST_ATTENUATIONMAP_XY; } else if(!Q_stricmp(token, "attenuationMapZ")) { stage->type = ST_ATTENUATIONMAP_Z; } else { ri.Printf(PRINT_WARNING, "WARNING: unknown stage parameter '%s' in shader '%s'\n", token, shader.name); continue; } } // rgbGen else if(!Q_stricmp(token, "rgbGen")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parameters for rgbGen in shader '%s'\n", shader.name); continue; } if(!Q_stricmp(token, "wave")) { ParseWaveForm(text, &stage->rgbWave); stage->rgbGen = CGEN_WAVEFORM; } else if(!Q_stricmp(token, "const")) { vec3_t color; ParseVector(text, 3, color); stage->constantColor[0] = 255 * color[0]; stage->constantColor[1] = 255 * color[1]; stage->constantColor[2] = 255 * color[2]; stage->rgbGen = CGEN_CONST; } else if(!Q_stricmp(token, "identity")) { stage->rgbGen = CGEN_IDENTITY; } else if(!Q_stricmp(token, "identityLighting")) { stage->rgbGen = CGEN_IDENTITY_LIGHTING; } else if(!Q_stricmp(token, "entity")) { stage->rgbGen = CGEN_ENTITY; } else if(!Q_stricmp(token, "oneMinusEntity")) { stage->rgbGen = CGEN_ONE_MINUS_ENTITY; } else if(!Q_stricmp(token, "vertex")) { stage->rgbGen = CGEN_VERTEX; if(stage->alphaGen == 0) { stage->alphaGen = AGEN_VERTEX; } } else if(!Q_stricmp(token, "exactVertex")) { stage->rgbGen = CGEN_VERTEX; } else if(!Q_stricmp(token, "lightingDiffuse")) { //ri.Printf(PRINT_WARNING, "WARNING: obsolete rgbGen lightingDiffuse keyword not supported in shader '%s'\n", shader.name); stage->type = ST_DIFFUSEMAP; stage->rgbGen = CGEN_IDENTITY_LIGHTING; } else if(!Q_stricmp(token, "oneMinusVertex")) { stage->rgbGen = CGEN_ONE_MINUS_VERTEX; } // su44: MoHAA-specific shader keyword, not supported yet else if ( !Q_stricmp( token, "lightingSpherical" ) ) { //stage->rgbGen = CGEN_LIGHTING_SPHERICAL; } else if ( !Q_stricmp( token, "global" ) ) { // su44: it's used in ui shaders, eg main_a and main_b from scripts/mohmenu.shader stage->rgbGen = CGEN_IDENTITY; } else if ( !Q_stricmp( token, "static" ) ) { //stage->rgbGen = CGEN_STATIC; } else if ( !Q_stricmp( token, "lightingGrid" ) ) { //stage->rgbGen = CGEN_LIGHTING_GRID; } else { ri.Printf(PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name); continue; } } // rgb else if(!Q_stricmp(token, "rgb")) { stage->rgbGen = CGEN_CUSTOM_RGB; ParseExpression(text, &stage->rgbExp); } // red else if(!Q_stricmp(token, "red")) { stage->rgbGen = CGEN_CUSTOM_RGBs; ParseExpression(text, &stage->redExp); } // green else if(!Q_stricmp(token, "green")) { stage->rgbGen = CGEN_CUSTOM_RGBs; ParseExpression(text, &stage->greenExp); } // blue else if(!Q_stricmp(token, "blue")) { stage->rgbGen = CGEN_CUSTOM_RGBs; ParseExpression(text, &stage->blueExp); } // colored else if(!Q_stricmp(token, "colored")) { stage->rgbGen = CGEN_ENTITY; stage->alphaGen = AGEN_ENTITY; } // vertexColor else if(!Q_stricmp(token, "vertexColor")) { stage->rgbGen = CGEN_VERTEX; if(stage->alphaGen == 0) { stage->alphaGen = AGEN_VERTEX; } } // inverseVertexColor else if(!Q_stricmp(token, "inverseVertexColor")) { stage->rgbGen = CGEN_ONE_MINUS_VERTEX; if(stage->alphaGen == 0) { stage->alphaGen = AGEN_ONE_MINUS_VERTEX; } } // alphaGen else if(!Q_stricmp(token, "alphaGen")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing parameters for alphaGen in shader '%s'\n", shader.name); continue; } if(!Q_stricmp(token, "wave")) { ParseWaveForm(text, &stage->alphaWave); stage->alphaGen = AGEN_WAVEFORM; } else if(!Q_stricmp(token, "const") || !Q_stricmp( token, "constant" )) { token = COM_ParseExt(text, qfalse); stage->constantColor[3] = 255 * atof(token); stage->alphaGen = AGEN_CONST; } else if(!Q_stricmp(token, "identity")) { stage->alphaGen = AGEN_IDENTITY; } else if(!Q_stricmp(token, "entity")) { stage->alphaGen = AGEN_ENTITY; } else if(!Q_stricmp(token, "oneMinusEntity")) { stage->alphaGen = AGEN_ONE_MINUS_ENTITY; } else if(!Q_stricmp(token, "vertex")) { stage->alphaGen = AGEN_VERTEX; } else if(!Q_stricmp(token, "lightingSpecular")) { ri.Printf(PRINT_WARNING, "WARNING: alphaGen lightingSpecular keyword not supported in shader '%s'\n", shader.name); } else if(!Q_stricmp(token, "oneMinusVertex")) { stage->alphaGen = AGEN_ONE_MINUS_VERTEX; } else if(!Q_stricmp(token, "portal")) { ri.Printf(PRINT_WARNING, "WARNING: alphaGen portal keyword not supported in shader '%s'\n", shader.name); stage->type = ST_PORTALMAP; Com_SkipRestOfLine(text); } // IneQuation: more alpha generation methods from FAKK2 else if (!Q_stricmp(token, "dot")) { ri.Printf(PRINT_ALL, "FIXME: ParseStage: stub alphaGen dot!!!\n"); // TODO //stage->alphaGen = AGEN_DOT; token = COM_ParseExt(text, qfalse); if (token[0] == 0) { //shader.agenDotMin = 0.f; //shader.agenDotMax = 1.f; ri.Printf(PRINT_WARNING, "WARNING: missing range params for alphaGen dot in shader '%s', defaulting to 0..1\n", shader.name); } /*else shader.agenDotMin = atof(token);*/ token = COM_ParseExt(text, qfalse); if (token[0] == 0) { //shader.agenDotMin = 0.f; //shader.agenDotMax = 1.f; ri.Printf(PRINT_WARNING, "WARNING: missing range params for alphaGen dot in shader '%s', defaulting to 0..1\n", shader.name); } /*else shader.agenDotMax = atof(token);*/ } else if (!Q_stricmp(token, "oneMinusDot")) { ri.Printf(PRINT_ALL, "FIXME: ParseStage: stub alphaGen oneMinusDot!!!\n"); // TODO //stage->alphaGen = AGEN_ONE_MINUS_DOT; token = COM_ParseExt(text, qfalse); if (token[0] == 0) { //shader.agenDotMin = 0.f; //shader.agenDotMax = 1.f; ri.Printf(PRINT_WARNING, "WARNING: missing range params for alphaGen oneMinusDot in shader '%s', defaulting to 0..1\n", shader.name); } /*else shader.agenDotMin = atof(token);*/ token = COM_ParseExt(text, qfalse); if (token[0] == 0) { //shader.agenDotMin = 0.f; //shader.agenDotMax = 1.f; ri.Printf(PRINT_WARNING, "WARNING: missing range params for alphaGen oneMinusDot in shader '%s', defaulting to 0..1\n", shader.name); } /*else shader.agenDotMax = atof(token);*/ } else if (!Q_stricmp(token, "skyAlpha")) { ri.Printf(PRINT_ALL, "FIXME: ParseStage: stub alphaGen skyAlpha!!!\n"); // TODO //stage->alphaGen = AGEN_SKYALPHA; } else if (!Q_stricmp(token, "oneMinusSkyAlpha")) { ri.Printf(PRINT_ALL, "FIXME: ParseStage: stub alphaGen oneMinusSkyAlpha!!!\n"); // TODO //stage->alphaGen = AGEN_ONE_MINUS_SKYALPHA; } // this one seems to have been introduced in MoHAA else if (!Q_stricmp(token, "tCoord")) { ri.Printf(PRINT_ALL, "FIXME: ParseStage: stub alphaGen tCoord!!!\n"); // TODO //stage->alphaGen = AGEN_TCOORD; token = COM_ParseExt(text, qfalse); if (token[0] == 0) { //shader.tCoordS = 0.f; //shader.tCoordT = 1.f; ri.Printf(PRINT_WARNING, "WARNING: missing range params for alphaGen tCoord in shader '%s', defaulting to 0..1\n", shader.name); } /*else shader.tCoordS = atof(token);*/ token = COM_ParseExt(text, qfalse); if (token[0] == 0) { //shader.tCoordS = 0.f; //shader.tCoordT = 1.f; ri.Printf(PRINT_WARNING, "WARNING: missing range params for alphaGen tCoord in shader '%s', defaulting to 0..1\n", shader.name); } /*else shader.tCoordT = atof(token);*/ } // su44: see shader firstaid_dm else if ( !Q_stricmp( token, "distFade" ) ) {//usage: distFade 1024 512 ??? //stage->alphaGen = AGEN_DISTFADE; token = COM_ParseExt(text, qfalse); if (token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing distance1 parm for alphaGen distFade in shader '%s'\n", shader.name); } token = COM_ParseExt(text, qfalse); if (token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing distance2 parm for alphaGen distFade in shader '%s'\n", shader.name); } } // su44: see shader static_tree1sprite else if (!Q_stricmp(token, "oneMinusDistFade")) { //stage->alphaGen = AGEN_ONEMINUSDISTFADE; token = COM_ParseExt(text, qfalse); if (token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing distance1 parm for alphaGen oneMinusDistFade in shader '%s'\n", shader.name); } token = COM_ParseExt(text, qfalse); if (token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing distance2 parm for alphaGen oneMinusDistFade in shader '%s'\n", shader.name); } } else { ri.Printf(PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name); continue; } } // alpha else if(!Q_stricmp(token, "alpha")) { stage->alphaGen = AGEN_CUSTOM; ParseExpression(text, &stage->alphaExp); } // color , , , else if(!Q_stricmp(token, "color")) { stage->rgbGen = CGEN_CUSTOM_RGBs; stage->alphaGen = AGEN_CUSTOM; ParseExpression(text, &stage->redExp); ParseExpression(text, &stage->greenExp); ParseExpression(text, &stage->blueExp); ParseExpression(text, &stage->alphaExp); } // privatePolygonOffset else if(!Q_stricmp(token, "privatePolygonOffset")) { token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'privatePolygonOffset' keyword in shader '%s'\n", shader.name); return qfalse; } stage->privatePolygonOffset = qtrue; stage->privatePolygonOffsetValue = atof(token); } // tcGen else if(!Q_stricmp(token, "texGen") || !Q_stricmp(token, "tcGen")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing texGen parm in shader '%s'\n", shader.name); continue; } if(!Q_stricmp(token, "environment")) { //ri.Printf(PRINT_WARNING, "WARNING: texGen environment keyword not supported in shader '%s'\n", shader.name); stage->tcGen_Environment = qtrue; } else if(!Q_stricmp(token, "lightmap")) { #if !defined(COMPAT_Q3A) && !defined(COMPAT_ET) ri.Printf(PRINT_WARNING, "WARNING: texGen lightmap keyword not supported in shader '%s'\n", shader.name); #endif } else if(!Q_stricmp(token, "texture") || !Q_stricmp(token, "base")) { #if !defined(COMPAT_Q3A) && !defined(COMPAT_ET) ri.Printf(PRINT_WARNING, "WARNING: texGen texture keyword not supported in shader '%s'\n", shader.name); #endif } else if(!Q_stricmp(token, "vector")) { ri.Printf(PRINT_WARNING, "WARNING: texGen vector keyword not supported in shader '%s'\n", shader.name); } else if(!Q_stricmp(token, "reflect")) { //ri.Printf(PRINT_WARNING, "WARNING: use stage reflectionmap instead of texGen reflect keyword shader '%s'\n", // shader.name); stage->type = ST_REFLECTIONMAP; } else if(!Q_stricmp(token, "skybox")) { //ri.Printf(PRINT_WARNING, "WARNING: use stage skyboxmap instead of texGen skybox keyword shader '%s'\n", // shader.name); stage->type = ST_SKYBOXMAP; } else { ri.Printf(PRINT_WARNING, "WARNING: unknown tcGen keyword '%s' not supported in shader '%s'\n", token, shader.name); Com_SkipRestOfLine(text); } } // tcMod <...> else if(!Q_stricmp(token, "tcMod")) { if(!ParseTexMod(text, stage)) { return qfalse; } } // scroll else if(!Q_stricmp(token, "scroll") || !Q_stricmp(token, "translate")) { texModInfo_t *tmi; if(stage->bundle[0].numTexMods == TR_MAX_TEXMODS) { ri.Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name); return qfalse; } tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; stage->bundle[0].numTexMods++; ParseExpression(text, &tmi->sExp); ParseExpression(text, &tmi->tExp); tmi->type = TMOD_SCROLL2; } // scale else if(!Q_stricmp(token, "scale")) { texModInfo_t *tmi; if(stage->bundle[0].numTexMods == TR_MAX_TEXMODS) { ri.Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name); return qfalse; } tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; stage->bundle[0].numTexMods++; ParseExpression(text, &tmi->sExp); ParseExpression(text, &tmi->tExp); tmi->type = TMOD_SCALE2; } // centerScale else if(!Q_stricmp(token, "centerScale")) { texModInfo_t *tmi; if(stage->bundle[0].numTexMods == TR_MAX_TEXMODS) { ri.Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name); return qfalse; } tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; stage->bundle[0].numTexMods++; ParseExpression(text, &tmi->sExp); ParseExpression(text, &tmi->tExp); tmi->type = TMOD_CENTERSCALE; } // shear else if(!Q_stricmp(token, "shear")) { texModInfo_t *tmi; if(stage->bundle[0].numTexMods == TR_MAX_TEXMODS) { ri.Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name); return qfalse; } tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; stage->bundle[0].numTexMods++; ParseExpression(text, &tmi->sExp); ParseExpression(text, &tmi->tExp); tmi->type = TMOD_SHEAR; } // rotate else if(!Q_stricmp(token, "rotate")) { texModInfo_t *tmi; if(stage->bundle[0].numTexMods == TR_MAX_TEXMODS) { ri.Error(ERR_DROP, "ERROR: too many tcMod stages in shader '%s'\n", shader.name); return qfalse; } tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods]; stage->bundle[0].numTexMods++; ParseExpression(text, &tmi->rExp); tmi->type = TMOD_ROTATE2; } // depthwrite else if(!Q_stricmp(token, "depthwrite")) { depthMaskBits = GLS_DEPTHMASK_TRUE; depthMaskExplicit = qtrue; continue; } // maskRed else if(!Q_stricmp(token, "maskRed")) { colorMaskBits |= GLS_REDMASK_FALSE; } // maskGreen else if(!Q_stricmp(token, "maskGreen")) { colorMaskBits |= GLS_GREENMASK_FALSE; } // maskBlue else if(!Q_stricmp(token, "maskBlue")) { colorMaskBits |= GLS_BLUEMASK_FALSE; } // maskAlpha else if(!Q_stricmp(token, "maskAlpha")) { colorMaskBits |= GLS_ALPHAMASK_FALSE; } // maskColor else if(!Q_stricmp(token, "maskColor")) { colorMaskBits |= GLS_REDMASK_FALSE | GLS_GREENMASK_FALSE | GLS_BLUEMASK_FALSE; } // maskDepth else if(!Q_stricmp(token, "maskDepth")) { depthMaskBits &= ~GLS_DEPTHMASK_TRUE; depthMaskExplicit = qfalse; } // wireFrame else if(!Q_stricmp(token, "wireFrame")) { polyModeBits |= GLS_POLYMODE_LINE; } // refractionIndex else if(!Q_stricmp(token, "refractionIndex")) { ParseExpression(text, &stage->refractionIndexExp); } // fresnelPower else if(!Q_stricmp(token, "fresnelPower")) { ParseExpression(text, &stage->fresnelPowerExp); } // fresnelScale else if(!Q_stricmp(token, "fresnelScale")) { ParseExpression(text, &stage->fresnelScaleExp); } // fresnelBias else if(!Q_stricmp(token, "fresnelBias")) { ParseExpression(text, &stage->fresnelBiasExp); } // normalScale else if(!Q_stricmp(token, "normalScale")) { ParseExpression(text, &stage->normalScaleExp); } // fogDensity else if(!Q_stricmp(token, "fogDensity")) { ParseExpression(text, &stage->fogDensityExp); } // depthScale else if(!Q_stricmp(token, "depthScale")) { ParseExpression(text, &stage->depthScaleExp); } // deformMagnitude else if(!Q_stricmp(token, "deformMagnitude")) { ParseExpression(text, &stage->deformMagnitudeExp); } // blurMagnitude else if(!Q_stricmp(token, "blurMagnitude")) { ParseExpression(text, &stage->blurMagnitudeExp); } // wrapAroundLighting else if(!Q_stricmp(token, "wrapAroundLighting")) { ParseExpression(text, &stage->wrapAroundLightingExp); } // fragmentProgram else if(!Q_stricmp(token, "fragmentProgram")) { ri.Printf(PRINT_WARNING, "WARNING: fragmentProgram keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // vertexProgram else if(!Q_stricmp(token, "vertexProgram")) { ri.Printf(PRINT_WARNING, "WARNING: vertexProgram keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // program else if(!Q_stricmp(token, "program")) { ri.Printf(PRINT_WARNING, "WARNING: program keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // vertexParm [,exp1] [,exp2] [,exp3] else if(!Q_stricmp(token, "vertexParm")) { ri.Printf(PRINT_WARNING, "WARNING: vertexParm keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // fragmentMap [options] else if(!Q_stricmp(token, "fragmentMap")) { ri.Printf(PRINT_WARNING, "WARNING: fragmentMap keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // megaTexture else if(!Q_stricmp(token, "megaTexture")) { ri.Printf(PRINT_WARNING, "WARNING: megaTexture keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); } // glowStage else if(!Q_stricmp(token, "glowStage")) { ri.Printf(PRINT_WARNING, "WARNING: glowStage keyword not supported in shader '%s'\n", shader.name); } // su44: added for MoHAA else if(!Q_stricmp(token, "nextbundle")) { ri.Printf(PRINT_WARNING, "WARNING: MoHAA nextbundle keyword not supported in shader '%s'\n", shader.name); } else { ri.Printf(PRINT_WARNING, "WARNING: unknown shader stage parameter '%s' in shader '%s'\n", token, shader.name); Com_SkipRestOfLine(text); return qfalse; } } // parsing succeeded stage->active = qtrue; // if cgen isn't explicitly specified, use either identity or identitylighting if(stage->rgbGen == CGEN_BAD) { if(blendSrcBits == 0 || blendSrcBits == GLS_SRCBLEND_ONE || blendSrcBits == GLS_SRCBLEND_SRC_ALPHA) { stage->rgbGen = CGEN_IDENTITY_LIGHTING; } else { stage->rgbGen = CGEN_IDENTITY; } } // implicitly assume that a GL_ONE GL_ZERO blend mask disables blending if((blendSrcBits == GLS_SRCBLEND_ONE) && (blendDstBits == GLS_DSTBLEND_ZERO)) { blendDstBits = blendSrcBits = 0; depthMaskBits = GLS_DEPTHMASK_TRUE; } // tell shader if this stage has an alpha test if(atestBits & GLS_ATEST_BITS) { shader.alphaTest = qtrue; } // compute state bits stage->stateBits = colorMaskBits | depthMaskBits | blendSrcBits | blendDstBits | atestBits | depthFuncBits | polyModeBits; // load image if(loadMap && !LoadMap(stage, buffer)) { return qfalse; } return qtrue; } /* =============== ParseDeform deformVertexes wave deformVertexes normal deformVertexes move deformVertexes bulge deformVertexes projectionShadow deformVertexes autoSprite deformVertexes autoSprite2 deformVertexes text[0-7] =============== */ static void ParseDeform(char **text) { char *token; deformStage_t *ds; token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deform parm in shader '%s'\n", shader.name); return; } if(shader.numDeforms == MAX_SHADER_DEFORMS) { ri.Printf(PRINT_WARNING, "WARNING: MAX_SHADER_DEFORMS in '%s'\n", shader.name); return; } ds = &shader.deforms[shader.numDeforms]; shader.numDeforms++; if(!Q_stricmp(token, "projectionShadow")) { ds->deformation = DEFORM_PROJECTION_SHADOW; return; } if(!Q_stricmp(token, "autosprite")) { ds->deformation = DEFORM_AUTOSPRITE; return; } if(!Q_stricmp(token, "autosprite2")) { ds->deformation = DEFORM_AUTOSPRITE2; return; } if(!Q_stricmpn(token, "text", 4)) { int n; n = token[4] - '0'; if(n < 0 || n > 7) { n = 0; } ds->deformation = DEFORM_TEXT0 + n; return; } if(!Q_stricmp(token, "bulge")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name); return; } ds->bulgeWidth = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name); return; } ds->bulgeHeight = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes bulge parm in shader '%s'\n", shader.name); return; } ds->bulgeSpeed = atof(token); ds->deformation = DEFORM_BULGE; return; } if(!Q_stricmp(token, "wave")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name); return; } if(atof(token) != 0) { ds->deformationSpread = 1.0f / atof(token); } else { ds->deformationSpread = 100.0f; ri.Printf(PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name); } ParseWaveForm(text, &ds->deformationWave); ds->deformation = DEFORM_WAVE; return; } if(!Q_stricmp(token, "normal")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name); return; } ds->deformationWave.amplitude = atof(token); token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name); return; } ds->deformationWave.frequency = atof(token); ds->deformation = DEFORM_NORMALS; return; } if(!Q_stricmp(token, "move")) { int i; for(i = 0; i < 3; i++) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes parm in shader '%s'\n", shader.name); return; } ds->moveVector[i] = atof(token); } ParseWaveForm(text, &ds->deformationWave); ds->deformation = DEFORM_MOVE; return; } if(!Q_stricmp(token, "sprite")) { ds->deformation = DEFORM_SPRITE; return; } if(!Q_stricmp(token, "flare")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes flare parm in shader '%s'\n", shader.name); return; } ds->flareSize = atof(token); return; } ri.Printf(PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name); } /* =============== ParseSkyParms skyParms =============== */ static void ParseSkyParms(char **text) { char *token; char prefix[MAX_QPATH]; // outerbox token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name); return; } if(strcmp(token, "-")) { Q_strncpyz(prefix, token, sizeof(prefix)); shader.sky.outerbox = R_FindCubeImage(prefix, IF_NONE, FT_DEFAULT, WT_EDGE_CLAMP, shader.name); if(!shader.sky.outerbox) { ri.Printf(PRINT_WARNING, "WARNING: could not find cubemap '%s' for outer skybox in shader '%s'\n", prefix, shader.name); shader.sky.outerbox = tr.blackCubeImage; } } // cloudheight token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name); return; } shader.sky.cloudHeight = atof(token); if(!shader.sky.cloudHeight) { shader.sky.cloudHeight = 512; } #if defined(USE_D3D10) // TODO #else R_InitSkyTexCoords(shader.sky.cloudHeight); #endif // innerbox token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: 'skyParms' missing parameter in shader '%s'\n", shader.name); return; } if(strcmp(token, "-")) { Q_strncpyz(prefix, token, sizeof(prefix)); shader.sky.innerbox = R_FindCubeImage(prefix, IF_NONE, FT_DEFAULT, WT_EDGE_CLAMP, shader.name); if(!shader.sky.innerbox) { ri.Printf(PRINT_WARNING, "WARNING: could not find cubemap '%s' for inner skybox in shader '%s'\n", prefix, shader.name); shader.sky.innerbox = tr.blackCubeImage; } } shader.isSky = qtrue; } /* ================= ParseSort ================= */ static void ParseSort(char **text) { char *token; token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing sort parameter in shader '%s'\n", shader.name); return; } if(!Q_stricmp(token, "portal") || !Q_stricmp(token, "subview")) { shader.sort = SS_PORTAL; } else if(!Q_stricmp(token, "sky") || !Q_stricmp(token, "environment")) { shader.sort = SS_ENVIRONMENT_FOG; } else if(!Q_stricmp(token, "opaque")) { shader.sort = SS_OPAQUE; } else if(!Q_stricmp(token, "decal")) { shader.sort = SS_DECAL; } else if(!Q_stricmp(token, "seeThrough")) { shader.sort = SS_SEE_THROUGH; } else if(!Q_stricmp(token, "banner")) { shader.sort = SS_BANNER; } else if(!Q_stricmp(token, "underwater")) { shader.sort = SS_UNDERWATER; } else if(!Q_stricmp(token, "far")) { shader.sort = SS_FAR; } else if(!Q_stricmp(token, "medium")) { shader.sort = SS_MEDIUM; } else if(!Q_stricmp(token, "close")) { shader.sort = SS_CLOSE; } else if(!Q_stricmp(token, "additive")) { shader.sort = SS_BLEND1; } else if(!Q_stricmp(token, "almostNearest")) { shader.sort = SS_ALMOST_NEAREST; } else if(!Q_stricmp(token, "nearest")) { shader.sort = SS_NEAREST; } else if(!Q_stricmp(token, "postProcess")) { shader.sort = SS_POST_PROCESS; } else { shader.sort = atof(token); } } // this table is also present in xmap typedef struct { char *name; int clearSolid, surfaceFlags, contents; } infoParm_t; // *INDENT-OFF* infoParm_t infoParms[] = { // server relevant contents #if defined(COMPAT_ET) {"clipmissile", 1, 0, CONTENTS_MISSILECLIP}, // impact only specific weapons (rl, gl) #endif {"water", 1, 0, CONTENTS_WATER}, {"slag", 1, 0, CONTENTS_SLIME}, // uses the CONTENTS_SLIME flag, but the shader reference is changed to 'slag' // to idendify that this doesn't work the same as 'slime' did. #if !defined(COMPAT_ET) {"slime", 1, 0, CONTENTS_SLIME}, // mildly damaging #endif {"lava", 1, 0, CONTENTS_LAVA}, // very damaging {"playerclip", 1, 0, CONTENTS_PLAYERCLIP}, {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP}, #if !defined(COMPAT_ET) {"moveableclip", 1, 0, 0}, // FIXME {"ikclip", 1, 0, 0}, // FIXME #endif {"nodrop", 1, 0, CONTENTS_NODROP}, // don't drop items or leave bodies (death fog, lava, etc) {"nonsolid", 1, SURF_NONSOLID, 0}, // clears the solid flag {"blood", 1, 0, CONTENTS_WATER}, // utility relevant attributes {"origin", 1, 0, CONTENTS_ORIGIN}, // center of rotating brushes {"trans", 0, 0, CONTENTS_TRANSLUCENT}, // don't eat contained surfaces {"translucent", 0, 0, CONTENTS_TRANSLUCENT}, // don't eat contained surfaces {"detail", 0, 0, CONTENTS_DETAIL}, // don't include in structural bsp {"structural", 0, 0, CONTENTS_STRUCTURAL}, // force into structural bsp even if trnas {"areaportal", 1, 0, CONTENTS_AREAPORTAL}, // divides areas // {"clusterportal", 1, 0, CONTENTS_CLUSTERPORTAL}, // for bots {"donotenter", 1, 0, CONTENTS_DONOTENTER}, // for bots #if defined(COMPAT_ET) {"donotenterlarge", 1, 0, CONTENTS_DONOTENTER_LARGE}, // for larger bots #else {"botclip", 1, 0, CONTENTS_BOTCLIP}, // for bots #endif {"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering {"sky", 0, SURF_SKY, 0}, // emit light from an environment map // {"lightfilter", 0, SURF_LIGHTFILTER, 0}, // filter light going through it {"alphashadow", 0, SURF_ALPHASHADOW, 0}, // test light on a per-pixel basis {"hint", 0, SURF_HINT, 0}, // use as a primary splitter // server attributes {"slick", 0, SURF_SLICK, 0}, #if !defined(COMPAT_ET) // {"collision", 0, SURF_COLLISION, 0}, #endif {"noimpact", 0, SURF_NOIMPACT, 0}, // don't make impact explosions or marks {"nomarks", 0, SURF_NOMARKS, 0}, // don't make impact marks, but still explode {"nooverlays", 0, SURF_NOMARKS, 0}, // don't make impact marks, but still explode {"ladder", 0, SURF_LADDER, 0}, {"nodamage", 0, SURF_NODAMAGE, 0}, #if defined(COMPAT_ET) {"monsterslick", 0, SURF_MONSTERSLICK, 0}, // surf only slick for monsters #endif #if !defined(COMPAT_ET) // {"flesh", 0, SURF_FLESH, 0}, #endif {"glass", 0, SURF_GLASS, 0}, //----(SA) added // {"splash", 0, SURF_SPLASH, 0}, //----(SA) added // steps #if defined(COMPAT_ET) {"metal", 0, SURF_METAL, 0}, {"metalsteps", 0, SURF_METAL, 0}, #else // {"metal", 0, SURF_METALSTEPS, 0}, // {"metalsteps", 0, SURF_METALSTEPS, 0}, #endif #if !defined(COMPAT_ET) // {"wallwalk", 0, SURF_WALLWALK, 0}, #endif {"nosteps", 0, SURF_NOSTEPS, 0}, {"woodsteps", 0, SURF_WOOD, 0}, {"grasssteps", 0, SURF_GRASS, 0}, {"gravelsteps", 0, SURF_GRAVEL, 0}, {"carpetsteps", 0, SURF_CARPET, 0}, {"snowsteps", 0, SURF_SNOW, 0}, // {"roofsteps", 0, SURF_ROOF, 0}, // tile roof // {"rubble", 0, SURF_RUBBLE, 0}, // drawsurf attributes {"nodraw", 0, SURF_NODRAW, 0}, // don't generate a drawsurface (or a lightmap) // {"pointlight", 0, SURF_POINTLIGHT, 0}, // sample lighting at vertexes {"nolightmap", 0, SURF_NOLIGHTMAP, 0}, // don't generate a lightmap {"nodlight", 0, 0, 0}, // OBSELETE: don't ever add dynamic lights #if !defined(COMPAT_ET) // {"dust", 0, SURF_DUST, 0}, // leave a dust trail when walking on this surface #endif // monster ai #if defined(COMPAT_ET) {"monsterslicknorth", 0, SURF_MONSLICK_N, 0}, {"monsterslickeast", 0, SURF_MONSLICK_E, 0}, {"monsterslicksouth", 0, SURF_MONSLICK_S, 0}, {"monsterslickwest", 0, SURF_MONSLICK_W, 0}, #endif // unsupported Doom3 surface types for sound effects and blood splats #if defined(COMPAT_ET) {"metal", 0, SURF_METAL, 0}, #else // {"metal", 0, SURF_METALSTEPS, 0}, #endif {"stone", 0, 0, 0}, {"wood", 0, SURF_WOOD, 0}, {"cardboard", 0, 0, 0}, {"liquid", 0, 0, 0}, {"glass", 0, 0, 0}, {"plastic", 0, 0, 0}, {"ricochet", 0, 0, 0}, {"surftype10", 0, 0, 0}, {"surftype11", 0, 0, 0}, {"surftype12", 0, 0, 0}, {"surftype13", 0, 0, 0}, {"surftype14", 0, 0, 0}, {"surftype15", 0, 0, 0}, // other unsupported Doom3 surface types {"trigger", 0, 0, 0}, {"flashlight_trigger",0,0, 0}, {"aassolid", 0, 0, 0}, {"aasobstacle", 0, 0, 0}, {"nullNormal", 0, 0, 0}, {"discrete", 0, 0, 0}, }; // *INDENT-ON* /* =============== ParseSurfaceParm surfaceparm =============== */ static qboolean SurfaceParm(const char *token) { int numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]); int i; for(i = 0; i < numInfoParms; i++) { if(!Q_stricmp(token, infoParms[i].name)) { shader.surfaceFlags |= infoParms[i].surfaceFlags; shader.contentFlags |= infoParms[i].contents; #if 0 if(infoParms[i].clearSolid) { si->contents &= ~CONTENTS_SOLID; } #endif return qtrue; } } return qfalse; } static void ParseSurfaceParm(char **text) { char *token; token = COM_ParseExt(text, qfalse); SurfaceParm(token); } static void ParseDiffuseMap(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_DIFFUSEMAP; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_DEFAULT; if(!r_compressDiffuseMaps->integer) { stage->forceHighQuality = qtrue; } if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } static void ParseNormalMap(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_NORMALMAP; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_DEFAULT; if(!r_compressNormalMaps->integer) { stage->forceHighQuality = qtrue; } if(r_highQualityNormalMapping->integer) { stage->overrideFilterType = qtrue; stage->filterType = FT_LINEAR; stage->overrideNoPicMip = qtrue; } if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } static void ParseSpecularMap(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_SPECULARMAP; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_DEFAULT; if(!r_compressSpecularMaps->integer) { stage->forceHighQuality = qtrue; } if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } static void ParseGlowMap(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_COLORMAP; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE; // blend add if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } static void ParseReflectionMap(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_REFLECTIONMAP; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_DEFAULT; stage->overrideWrapType = qtrue; stage->wrapType = WT_EDGE_CLAMP; if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } static void ParseReflectionMapBlended(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_REFLECTIONMAP; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE; stage->overrideWrapType = qtrue; stage->wrapType = WT_EDGE_CLAMP; if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } static void ParseLightFalloffImage(shaderStage_t * stage, char **text) { char buffer[1024] = ""; stage->active = qtrue; stage->type = ST_ATTENUATIONMAP_Z; stage->rgbGen = CGEN_IDENTITY; stage->stateBits = GLS_DEFAULT; stage->overrideWrapType = qtrue; stage->wrapType = WT_EDGE_CLAMP; if(ParseMap(stage, text, buffer, sizeof(buffer))) { LoadMap(stage, buffer); } } /* ==================== FindShaderInShaderText Scans the combined text description of all the shader template files for the given guide name. return NULL if not found If found, it will return a valid template ===================== */ static char *FindGuideInGuideText(const char *guideName) { char *token, *p; int i, hash; if(!s_guideText) { // no guides loaded at all return NULL; } hash = generateHashValue(guideName, MAX_GUIDETEXT_HASH); for(i = 0; guideTextHashTable[hash][i]; i++) { p = guideTextHashTable[hash][i]; token = COM_ParseExt(&p, qtrue); if(!Q_stricmp(token, guideName)) { //ri.Printf(PRINT_ALL, "found guide '%s' by hashing\n", guideName); return p; } } p = s_guideText; if(!p) { return NULL; } // look for label while(1) { token = COM_ParseExt(&p, qtrue); if(token[0] == 0) { break; } if(Q_stricmp(token, "guide") && Q_stricmp(token, "inlineGuide")) { ri.Printf(PRINT_WARNING, "WARNING: expected guide or inlineGuide found '%s'\n", token); break; } // parse guide name token = COM_ParseExt(&p, qtrue); if(!Q_stricmp(token, guideName)) { ri.Printf(PRINT_ALL, "found guide '%s' by linear search\n", guideName); return p; } // skip parameters token = COM_ParseExt(&p, qtrue); if(Q_stricmp(token, "(")) { ri.Printf(PRINT_WARNING, "WARNING: expected ( found '%s'\n", token); break; } while(1) { token = COM_ParseExt(&p, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; } if(Q_stricmp(token, ")")) { ri.Printf(PRINT_WARNING, "WARNING: expected ) found '%s'\n", token); break; } // skip guide body Com_SkipBracedSection(&p); } return NULL; } /* ================= CreateShaderByGuide ================= */ #define MAX_GUIDE_PARAMETERS 16 static char *CreateShaderByGuide(const char *guideName, char *shaderText) { int i; char *guideText; char *token; char *p; static char buffer[4096]; char name[MAX_QPATH]; int numGuideParms; char guideParms[MAX_GUIDE_PARAMETERS][MAX_QPATH]; int numShaderParms; char shaderParms[MAX_GUIDE_PARAMETERS][MAX_QPATH]; Com_Memset(buffer, 0, sizeof(buffer)); Com_Memset(guideParms, 0, sizeof(guideParms)); Com_Memset(shaderParms, 0, sizeof(shaderParms)); // attempt to define shader from an explicit parameter file guideText = FindGuideInGuideText(guideName); if(guideText) { shader.createdByGuide = qtrue; // parse guide parameters numGuideParms = 0; token = COM_ParseExt(&guideText, qtrue); if(Q_stricmp(token, "(")) { ri.Printf(PRINT_WARNING, "WARNING: expected ( found '%s'\n", token); return NULL; } while(1) { token = COM_ParseExt(&guideText, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; if(numGuideParms >= MAX_GUIDE_PARAMETERS - 1) { ri.Printf(PRINT_ALL, "WARNING: more than %i guide parameters are not allowed\n", MAX_GUIDE_PARAMETERS); return NULL; } //ri.Printf(PRINT_ALL, "guide parameter %i = '%s'\n", numGuideParms, token); Q_strncpyz(guideParms[numGuideParms], token, MAX_QPATH); numGuideParms++; } if(Q_stricmp(token, ")")) { ri.Printf(PRINT_ALL, "WARNING: expected ) found '%s'\n", token); return NULL; } // parse shader parameters numShaderParms = 0; token = COM_ParseExt(&shaderText, qtrue); if(Q_stricmp(token, "(")) { ri.Printf(PRINT_ALL, "WARNING: expected ( found '%s'\n", token); return NULL; } while(1) { token = COM_ParseExt(&shaderText, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; if(numShaderParms >= MAX_GUIDE_PARAMETERS - 1) { ri.Printf(PRINT_ALL, "WARNING: more than %i guide parameters are not allowed\n", MAX_GUIDE_PARAMETERS); return NULL; } //ri.Printf(PRINT_ALL, "shader parameter %i = '%s'\n", numShaderParms, token); Q_strncpyz(shaderParms[numShaderParms], token, MAX_QPATH); numShaderParms++; } if(Q_stricmp(token, ")")) { ri.Printf(PRINT_ALL, "WARNING: expected ) found '%s'\n", token); return NULL; } if(numGuideParms != numShaderParms) { ri.Printf(PRINT_WARNING, "WARNING: %i numGuideParameters != %i numShaderParameters\n", numGuideParms, numShaderParms); return NULL; } #if 0 for(i = 0; i < numGuideParms; i++) { ri.Printf(PRINT_ALL, "guide parameter '%s' = '%s'\n", guideParms[i], shaderParms[i]); } #endif token = COM_ParseExt(&guideText, qtrue); if(Q_stricmp(token, "{")) { ri.Printf(PRINT_ALL, "WARNING: expected { found '%s'\n", token); return NULL; } // create buffer while(1) { // begin new line token = COM_ParseExt(&guideText, qtrue); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: no concluding '}' in guide %s\n", guideName); return NULL; } // end of guide definition if(token[0] == '}') { break; } Q_strncpyz(name, token, sizeof(name)); #if 0 // adjust name by guide parameters if necessary for(i = 0; i < numGuideParms; i++) { if((p = Q_stristr(name, (const char *)guideParms))) { //ri.Printf(PRINT_ALL, "guide parameter '%s' = '%s'\n", guideParms[i], shaderParms[i]); Q_strreplace(name, sizeof(name), guideParms[i], shaderParms[i]); } } #endif Q_strcat(buffer, sizeof(buffer), name); Q_strcat(buffer, sizeof(buffer), " "); // parse rest of line while(1) { token = COM_ParseExt(&guideText, qfalse); if(!token[0]) { // end of line break; } Q_strncpyz(name, token, sizeof(name)); // adjust name by guide parameters if necessary for(i = 0; i < numGuideParms; i++) { if((p = Q_stristr(name, (const char *)guideParms))) { //ri.Printf(PRINT_ALL, "guide parameter '%s' = '%s'\n", guideParms[i], shaderParms[i]); Q_strreplace(name, sizeof(name), guideParms[i], shaderParms[i]); } } Q_strcat(buffer, sizeof(buffer), name); Q_strcat(buffer, sizeof(buffer), " "); } Q_strcat(buffer, sizeof(buffer), "\n"); } if(Q_stricmp(token, "}")) { ri.Printf(PRINT_ALL, "WARNING: expected } found '%s'\n", token); return NULL; } Q_strcat(buffer, sizeof(buffer), "}"); ri.Printf(PRINT_ALL, "----- '%s' -----\n%s\n----------\n", shader.name, buffer); return buffer; } return NULL; } /* ================= ParseShader The current text pointer is at the explicit text definition of the shader. Parse it into the global shader variable. Later functions will optimize it. ================= */ static qboolean ParseShader(char *_text) { char **text; char *token; int s; s = 0; shader.explicitlyDefined = qtrue; text = &_text; token = COM_ParseExt(text, qtrue); if(token[0] != '{') { if(!(_text = CreateShaderByGuide(token, _text))) { ri.Printf(PRINT_WARNING, "WARNING: couldn't create shader '%s' by template '%s'\n", shader.name, token); //ri.Printf(PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name); return qfalse; } else { text = &_text; } } while(1) { token = COM_ParseExt(text, qtrue); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: no concluding '}' in shader %s\n", shader.name); return qfalse; } // end of shader definition if(token[0] == '}') { break; } // stage definition else if(token[0] == '{') { if(s >= (MAX_SHADER_STAGES - 1)) { ri.Printf(PRINT_WARNING, "WARNING: too many stages in shader %s\n", shader.name); return qfalse; } if(!ParseStage(&stages[s], text)) { return qfalse; } stages[s].active = qtrue; s++; continue; } // skip stuff that only the QuakeEdRadient needs else if(!Q_stricmpn(token, "qer", 3)) { Com_SkipRestOfLine(text); continue; } // skip description else if(!Q_stricmp(token, "description")) { Com_SkipRestOfLine(text); continue; } // skip renderbump else if(!Q_stricmp(token, "renderbump")) { Com_SkipRestOfLine(text); continue; } // skip unsmoothedTangents else if(!Q_stricmp(token, "unsmoothedTangents")) { ri.Printf(PRINT_WARNING, "WARNING: unsmoothedTangents keyword not supported in shader '%s'\n", shader.name); continue; } // skip guiSurf else if(!Q_stricmp(token, "guiSurf")) { Com_SkipRestOfLine(text); continue; } // skip decalInfo else if(!Q_stricmp(token, "decalInfo")) { ri.Printf(PRINT_WARNING, "WARNING: decalInfo keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); continue; } // skip Quake4's extra material types else if(!Q_stricmp(token, "materialType")) { ri.Printf(PRINT_WARNING, "WARNING: materialType keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); continue; } // skip Prey's extra material types else if(!Q_stricmpn(token, "matter", 6)) { //ri.Printf(PRINT_WARNING, "WARNING: materialType keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); continue; } // sun parms else if(!Q_stricmp(token, "xmap_sun")) { float a, b; token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } tr.sunLight[0] = atof(token); token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } tr.sunLight[1] = atof(token); token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } tr.sunLight[2] = atof(token); VectorNormalize(tr.sunLight); token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } a = atof(token); VectorScale(tr.sunLight, a, tr.sunLight); token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } a = atof(token); a = a / 180 * M_PI; token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'xmap_sun' keyword in shader '%s'\n", shader.name); continue; } b = atof(token); b = b / 180 * M_PI; tr.sunDirection[0] = cos(a) * cos(b); tr.sunDirection[1] = sin(a) * cos(b); tr.sunDirection[2] = sin(b); continue; } // noShadows else if(!Q_stricmp(token, "noShadows")) { shader.noShadows = qtrue; continue; } // noSelfShadow else if(!Q_stricmp(token, "noSelfShadow")) { ri.Printf(PRINT_WARNING, "WARNING: noSelfShadow keyword not supported in shader '%s'\n", shader.name); continue; } // forceShadows else if(!Q_stricmp(token, "forceShadows")) { ri.Printf(PRINT_WARNING, "WARNING: forceShadows keyword not supported in shader '%s'\n", shader.name); continue; } // forceOverlays else if(!Q_stricmp(token, "forceOverlays")) { ri.Printf(PRINT_WARNING, "WARNING: forceOverlays keyword not supported in shader '%s'\n", shader.name); continue; } // noPortalFog else if(!Q_stricmp(token, "noPortalFog")) { ri.Printf(PRINT_WARNING, "WARNING: noPortalFog keyword not supported in shader '%s'\n", shader.name); continue; } // fogLight else if(!Q_stricmp(token, "fogLight")) { ri.Printf(PRINT_WARNING, "WARNING: fogLight keyword not supported in shader '%s'\n", shader.name); shader.fogLight = qtrue; continue; } // blendLight else if(!Q_stricmp(token, "blendLight")) { ri.Printf(PRINT_WARNING, "WARNING: blendLight keyword not supported in shader '%s'\n", shader.name); shader.blendLight = qtrue; continue; } // ambientLight else if(!Q_stricmp(token, "ambientLight")) { ri.Printf(PRINT_WARNING, "WARNING: ambientLight keyword not supported in shader '%s'\n", shader.name); shader.ambientLight = qtrue; continue; } // volumetricLight else if(!Q_stricmp(token, "volumetricLight")) { shader.volumetricLight = qtrue; continue; } // translucent else if(!Q_stricmp(token, "translucent")) { shader.translucent = qtrue; continue; } // forceOpaque else if(!Q_stricmp(token, "forceOpaque")) { shader.forceOpaque = qtrue; continue; } // forceSolid else if(!Q_stricmp(token, "forceSolid") || !Q_stricmp(token, "solid")) { continue; } else if(!Q_stricmp(token, "deformVertexes") || !Q_stricmp(token, "deform")) { ParseDeform(text); continue; } else if(!Q_stricmp(token, "tesssize")) { Com_SkipRestOfLine(text); continue; } // skip noFragment if(!Q_stricmp(token, "noFragment")) { continue; } // skip stuff that only the xmap needs else if(!Q_stricmpn(token, "xmap", 4) || !Q_stricmpn(token, "q3map", 5)) { Com_SkipRestOfLine(text); continue; } // skip stuff that only xmap or the server needs else if(!Q_stricmp(token, "surfaceParm")) { ParseSurfaceParm(text); continue; } // no mip maps else if(!Q_stricmp(token, "nomipmap") || !Q_stricmp(token, "nomipmaps")) { shader.filterType = FT_LINEAR; shader.noPicMip = qtrue; continue; } // no picmip adjustment else if(!Q_stricmp(token, "nopicmip")) { shader.noPicMip = qtrue; continue; } // RF, allow each shader to permit compression if available else if(!Q_stricmp(token, "allowcompress")) { shader.uncompressed = qfalse; continue; } else if(!Q_stricmp(token, "nocompress")) { shader.uncompressed = qtrue; continue; } // polygonOffset else if(!Q_stricmp(token, "polygonOffset")) { shader.polygonOffset = qtrue; token = COM_ParseExt(text, qfalse); if(token[0]) { shader.polygonOffsetValue = atof(token); } continue; } // parallax mapping else if(!Q_stricmp(token, "parallax")) { shader.parallax = qtrue; continue; } // entityMergable, allowing sprite surfaces from multiple entities // to be merged into one batch. This is a savings for smoke // puffs and blood, but can't be used for anything where the // shader calcs (not the surface function) reference the entity color or scroll else if(!Q_stricmp(token, "entityMergable")) { shader.entityMergable = qtrue; continue; } // fogParms else if(!Q_stricmp(token, "fogParms")) { /* ri.Printf(PRINT_WARNING, "WARNING: fogParms keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); */ if(!ParseVector(text, 3, shader.fogParms.color)) { return qfalse; } token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'fogParms' keyword in shader '%s'\n", shader.name); continue; } shader.fogParms.depthForOpaque = atof(token); //shader.fogVolume = qtrue; shader.sort = SS_FOG; // skip any old gradient directions Com_SkipRestOfLine(text); continue; } // noFog else if(!Q_stricmp(token, "noFog")) { shader.noFog = qtrue; continue; } // portal else if(!Q_stricmp(token, "portal")) { shader.sort = SS_PORTAL; shader.isPortal = qtrue; token = COM_ParseExt(text, qfalse); if(token[0]) { shader.portalRange = atof(token); } else { shader.portalRange = 256; } continue; } // portal or mirror else if(!Q_stricmp(token, "mirror")) { shader.sort = SS_PORTAL; shader.isPortal = qtrue; continue; } // skyparms else if(!Q_stricmp(token, "skyparms")) { ParseSkyParms(text); continue; } // This is fixed fog for the skybox/clouds determined solely by the shader // it will not change in a level and will not be necessary // to force clients to use a sky fog the server says to. // skyfogvars <(r,g,b)> else if(!Q_stricmp(token, "skyfogvars")) { vec3_t fogColor; if(!ParseVector(text, 3, fogColor)) { return qfalse; } token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing density value for sky fog\n"); continue; } if(atof(token) > 1) { ri.Printf(PRINT_WARNING, "WARNING: last value for skyfogvars is 'density' which needs to be 0.0-1.0\n"); continue; } #if defined(COMPAT_ET) RE_SetFog(FOG_SKY, 0, 5, fogColor[0], fogColor[1], fogColor[2], atof(token)); #endif continue; } // ET waterfogvars else if(!Q_stricmp(token, "waterfogvars")) { vec3_t watercolor; float fogvar; if(!ParseVector(text, 3, watercolor)) { return qfalse; } token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing density/distance value for water fog\n"); continue; } fogvar = atof(token); //----(SA) right now allow one water color per map. I'm sure this will need // to change at some point, but I'm not sure how to track fog parameters // on a "per-water volume" basis yet. #if defined(COMPAT_ET) if(fogvar == 0) { // '0' specifies "use the map values for everything except the fog color // TODO } else if(fogvar > 1) { // distance "linear" fog RE_SetFog(FOG_WATER, 0, fogvar, watercolor[0], watercolor[1], watercolor[2], 1.1); } else { // density "exp" fog RE_SetFog(FOG_WATER, 0, 5, watercolor[0], watercolor[1], watercolor[2], fogvar); } #endif continue; } // ET fogvars else if(!Q_stricmp(token, "fogvars")) { vec3_t fogColor; float fogDensity; int fogFar; if(!ParseVector(text, 3, fogColor)) { return qfalse; } token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing density value for the fog\n"); continue; } //----(SA) NOTE: fogFar > 1 means the shader is setting the farclip, < 1 means setting // density (so old maps or maps that just need softening fog don't have to care about farclip) fogDensity = atof(token); if(fogDensity > 1) { // linear fogFar = fogDensity; } else { fogFar = 5; } #if defined(COMPAT_ET) RE_SetFog(FOG_MAP, 0, fogFar, fogColor[0], fogColor[1], fogColor[2], fogDensity); RE_SetFog(FOG_CMD_SWITCHFOG, FOG_MAP, 50, 0, 0, 0, 0); #endif continue; } // ET sunshader else if(!Q_stricmp(token, "sunshader")) { int tokenLen; token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing shader name for 'sunshader'\n"); continue; } /* RB: don't call tr.sunShader = R_FindShader(token, SHADER_3D_STATIC, qtrue); because it breaks the computation of the current shader */ tokenLen = strlen(token) + 1; tr.sunShaderName = ri.Hunk_Alloc(sizeof(char) * tokenLen, h_low); Q_strncpyz(tr.sunShaderName, token, tokenLen); } //----(SA) added else if(!Q_stricmp(token, "lightgridmulamb")) { // ambient multiplier for lightgrid token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing value for 'lightgrid ambient multiplier'\n"); continue; } if(atof(token) > 0) { tr.lightGridMulAmbient = atof(token); } } else if(!Q_stricmp(token, "lightgridmuldir")) { // directional multiplier for lightgrid token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing value for 'lightgrid directional multiplier'\n"); continue; } if(atof(token) > 0) { tr.lightGridMulDirected = atof(token); } } //----(SA) end // light determines flaring in xmap, not needed here else if(!Q_stricmp(token, "light")) { token = COM_ParseExt(text, qfalse); continue; } // cull else if(!Q_stricmp(token, "cull")) { token = COM_ParseExt(text, qfalse); if(token[0] == 0) { ri.Printf(PRINT_WARNING, "WARNING: missing cull parms in shader '%s'\n", shader.name); continue; } if(!Q_stricmp(token, "none") || !Q_stricmp(token, "twoSided") || !Q_stricmp(token, "disable")) { shader.cullType = CT_TWO_SIDED; } else if(!Q_stricmp(token, "back") || !Q_stricmp(token, "backside") || !Q_stricmp(token, "backsided")) { shader.cullType = CT_BACK_SIDED; } else { ri.Printf(PRINT_WARNING, "WARNING: invalid cull parm '%s' in shader '%s'\n", token, shader.name); } continue; } // twoSided else if(!Q_stricmp(token, "twoSided")) { shader.cullType = CT_TWO_SIDED; continue; } // backSided else if(!Q_stricmp(token, "backSided")) { shader.cullType = CT_BACK_SIDED; continue; } // clamp else if(!Q_stricmp(token, "clamp")) { shader.wrapType = WT_CLAMP; continue; } // edgeClamp else if(!Q_stricmp(token, "edgeClamp")) { shader.wrapType = WT_EDGE_CLAMP; continue; } // zeroClamp else if(!Q_stricmp(token, "zeroclamp")) { shader.wrapType = WT_ZERO_CLAMP; continue; } // alphaZeroClamp else if(!Q_stricmp(token, "alphaZeroClamp")) { shader.wrapType = WT_ALPHA_ZERO_CLAMP; continue; } // sort else if(!Q_stricmp(token, "sort")) { ParseSort(text); continue; } // ydnar: implicit default mapping to eliminate redundant/incorrect explicit shader stages else if(!Q_stricmpn(token, "implicit", 8)) { //ri.Printf(PRINT_WARNING, "WARNING: keyword '%s' not supported in shader '%s'\n", token, shader.name); //Com_SkipRestOfLine(text); // set implicit mapping state if(!Q_stricmp(token, "implicitBlend")) { implicitStateBits = GLS_DEPTHMASK_TRUE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; implicitCullType = CT_TWO_SIDED; } else if(!Q_stricmp(token, "implicitMask")) { implicitStateBits = GLS_DEPTHMASK_TRUE | GLS_ATEST_GE_128; implicitCullType = CT_TWO_SIDED; } else // "implicitMap" { implicitStateBits = GLS_DEPTHMASK_TRUE; implicitCullType = CT_FRONT_SIDED; } // get image token = COM_ParseExt(text, qfalse); if(token[0] != '\0') { Q_strncpyz(implicitMap, token, sizeof(implicitMap)); } else { implicitMap[0] = '-'; implicitMap[1] = '\0'; } continue; } // spectrum else if(!Q_stricmp(token, "spectrum")) { ri.Printf(PRINT_WARNING, "WARNING: spectrum keyword not supported in shader '%s'\n", shader.name); token = COM_ParseExt(text, qfalse); if(!token[0]) { ri.Printf(PRINT_WARNING, "WARNING: missing parm for 'spectrum' keyword in shader '%s'\n", shader.name); continue; } shader.spectrum = qtrue; shader.spectrumValue = atoi(token); continue; } // diffuseMap else if(!Q_stricmp(token, "diffuseMap")) { ParseDiffuseMap(&stages[s], text); s++; continue; } // normalMap else if(!Q_stricmp(token, "normalMap") || !Q_stricmp(token, "bumpMap")) { ParseNormalMap(&stages[s], text); s++; continue; } // specularMap else if(!Q_stricmp(token, "specularMap")) { ParseSpecularMap(&stages[s], text); s++; continue; } // glowMap else if(!Q_stricmp(token, "glowMap")) { ParseGlowMap(&stages[s], text); s++; continue; } // reflectionMap else if(!Q_stricmp(token, "reflectionMap")) { ParseReflectionMap(&stages[s], text); s++; continue; } // reflectionMapBlended else if(!Q_stricmp(token, "reflectionMapBlended")) { ParseReflectionMapBlended(&stages[s], text); s++; continue; } // lightMap else if(!Q_stricmp(token, "lightMap")) { ri.Printf(PRINT_WARNING, "WARNING: obsolete lightMap keyword not supported in shader '%s'\n", shader.name); Com_SkipRestOfLine(text); continue; } // lightFalloffImage else if(!Q_stricmp(token, "lightFalloffImage")) { ParseLightFalloffImage(&stages[s], text); s++; continue; } // Doom 3 DECAL_MACRO else if(!Q_stricmp(token, "DECAL_MACRO")) { shader.polygonOffset = qtrue; shader.polygonOffsetValue = 1; shader.sort = SS_DECAL; SurfaceParm("discrete"); SurfaceParm("noShadows"); continue; } // Prey DECAL_ALPHATEST_MACRO else if(!Q_stricmp(token, "DECAL_ALPHATEST_MACRO")) { // what's different? shader.polygonOffset = qtrue; shader.polygonOffsetValue = 1; shader.sort = SS_DECAL; SurfaceParm("discrete"); SurfaceParm("noShadows"); continue; } else if(SurfaceParm(token)) { continue; } // su44:: all keywords below were added for compatibility with MoHAA else if(!Q_stricmp(token, "force32bit")) { continue; } else { ri.Printf(PRINT_WARNING, "WARNING: unknown general shader parameter '%s' in '%s'\n", token, shader.name); return qfalse; } } // ignore shaders that don't have any stages, unless it is a sky or fog if(s == 0 && !shader.forceOpaque && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG) && implicitMap[0] == '\0') { return qfalse; } return qtrue; } /* ======================================================================================== SHADER OPTIMIZATION AND FOGGING ======================================================================================== */ /* typedef struct { int blendA; int blendB; int multitextureEnv; int multitextureBlend; } collapse_t; static collapse_t collapse[] = { {0, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GL_MODULATE, 0}, {0, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GL_MODULATE, 0}, {GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR}, {GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR}, {GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR}, {GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GLS_DSTBLEND_SRC_COLOR | GLS_SRCBLEND_ZERO, GL_MODULATE, GLS_DSTBLEND_ZERO | GLS_SRCBLEND_DST_COLOR}, {0, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GL_ADD, 0}, {GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE, GL_ADD, GLS_DSTBLEND_ONE | GLS_SRCBLEND_ONE}, #if 0 {0, GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA | GLS_SRCBLEND_SRC_ALPHA, GL_DECAL, 0}, #endif {-1} }; */ /* ================ CollapseMultitexture ================= */ // *INDENT-OFF* static void CollapseStages() { // int abits, bbits; int i, j; qboolean hasDiffuseStage; qboolean hasNormalStage; qboolean hasSpecularStage; qboolean hasReflectionStage; shaderStage_t tmpDiffuseStage; shaderStage_t tmpNormalStage; shaderStage_t tmpSpecularStage; shaderStage_t tmpReflectionStage; #if defined(COMPAT_Q3A) || defined(COMPAT_ET) int idxColorStage; shaderStage_t tmpColorStage; int idxLightmapStage; shaderStage_t tmpLightmapStage; #endif shader_t tmpShader; int numStages = 0; shaderStage_t tmpStages[MAX_SHADER_STAGES]; if(!r_collapseStages->integer) { return; } //ri.Printf(PRINT_ALL, "...collapsing '%s'\n", shader.name); Com_Memcpy(&tmpShader, &shader, sizeof(shader)); Com_Memset(&tmpStages[0], 0, sizeof(stages)); //Com_Memcpy(&tmpStages[0], &stages[0], sizeof(stages)); for(j = 0; j < MAX_SHADER_STAGES; j++) { hasDiffuseStage = qfalse; hasNormalStage = qfalse; hasSpecularStage = qfalse; hasReflectionStage = qfalse; Com_Memset(&tmpDiffuseStage, 0, sizeof(shaderStage_t)); Com_Memset(&tmpNormalStage, 0, sizeof(shaderStage_t)); Com_Memset(&tmpSpecularStage, 0, sizeof(shaderStage_t)); #if defined(COMPAT_Q3A) || defined(COMPAT_ET) idxColorStage = -1; Com_Memset(&tmpColorStage, 0, sizeof(shaderStage_t)); idxLightmapStage = -1; Com_Memset(&tmpLightmapStage, 0, sizeof(shaderStage_t)); #endif if(!stages[j].active) continue; if( #if !defined(COMPAT_Q3A) && !defined(COMPAT_ET) stages[j].type == ST_COLORMAP || #endif stages[j].type == ST_REFRACTIONMAP || stages[j].type == ST_DISPERSIONMAP || stages[j].type == ST_SKYBOXMAP || stages[j].type == ST_SCREENMAP || stages[j].type == ST_PORTALMAP || stages[j].type == ST_HEATHAZEMAP || stages[j].type == ST_LIQUIDMAP || stages[j].type == ST_ATTENUATIONMAP_XY || stages[j].type == ST_ATTENUATIONMAP_Z) { // only merge lighting relevant stages tmpStages[numStages] = stages[j]; numStages++; continue; } #if 0 //defined(COMPAT_Q3A) || defined(COMPAT_ET) for(i = 0; i < 2; i++) { if((j + i) >= MAX_SHADER_STAGES) continue; if(!stages[j + i].active) continue; if(stages[j + i].type == ST_COLORMAP && idxColorStage == -1) { idxColorStage = j + i; tmpColorStage = stages[j + i]; } else if(stages[j + i].type == ST_LIGHTMAP && idxLightmapStage == -1) { idxLightmapStage = j + i; tmpLightmapStage = stages[j + i]; } } // try to merge color/lightmap to diffuse if( idxColorStage != -1 && idxLightmapStage != -1 && // TODO check color stage no alphaGen (tmpLightmapStage.stateBits & ( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO)) ) { ri.Printf(PRINT_ALL, "color/lightmap combo\n"); tmpShader.collapseType = COLLAPSE_color_lightmap; tmpStages[numStages] = tmpColorStage; tmpStages[numStages].type = ST_DIFFUSEMAP; tmpStages[numStages].stateBits &= ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS); //tmpStages[numStages].stateBits |= GLS_DEPTHMASK_TRUE; //tmpStages[numStages].bundle[TB_NORMALMAP] = tmpNormalStage.bundle[0]; numStages++; j += 1; continue; } /* else if(idxLightmapStage > idxColorStage) { tmpStages[numStages] = tmpColorStage; numStages++; tmpStages[numStages] = tmpLightmapStage; numStages++; continue; } else { tmpStages[numStages] = tmpLightmapStage; numStages++; tmpStages[numStages] = tmpColorStage; numStages++; continue; } */ #endif for(i = 0; i < 3; i++) { if((j + i) >= MAX_SHADER_STAGES) continue; if(!stages[j + i].active) continue; if(stages[j + i].type == ST_DIFFUSEMAP && !hasDiffuseStage) { hasDiffuseStage = qtrue; tmpDiffuseStage = stages[j+i]; } else if(stages[j + i].type == ST_NORMALMAP && !hasNormalStage) { hasNormalStage = qtrue; tmpNormalStage = stages[j+i]; } else if(stages[j + i].type == ST_SPECULARMAP && !hasSpecularStage) { hasSpecularStage = qtrue; tmpSpecularStage = stages[j+i]; } else if(stages[j + i].type == ST_REFLECTIONMAP && !hasReflectionStage) { hasReflectionStage = qtrue; tmpReflectionStage = stages[j + i]; } } // NOTE: Tr3B - merge as many stages as possible // try to merge diffuse/normal/specular if( hasDiffuseStage && hasNormalStage && hasSpecularStage ) { //ri.Printf(PRINT_ALL, "lighting_DBS\n"); tmpShader.collapseType = COLLAPSE_lighting_DBS; tmpStages[numStages] = tmpDiffuseStage; tmpStages[numStages].type = ST_COLLAPSE_lighting_DBS; tmpStages[numStages].bundle[TB_NORMALMAP] = tmpNormalStage.bundle[0]; tmpStages[numStages].bundle[TB_SPECULARMAP] = tmpSpecularStage.bundle[0]; numStages++; j += 2; continue; } // try to merge diffuse/normal else if(hasDiffuseStage && hasNormalStage ) { //ri.Printf(PRINT_ALL, "lighting_DB\n"); tmpShader.collapseType = COLLAPSE_lighting_DB; tmpStages[numStages] = tmpDiffuseStage; tmpStages[numStages].type = ST_COLLAPSE_lighting_DB; tmpStages[numStages].bundle[TB_NORMALMAP] = tmpNormalStage.bundle[0]; numStages++; j += 1; continue; } // try to merge env/normal else if(hasReflectionStage && hasNormalStage ) { //ri.Printf(PRINT_ALL, "reflection_CB\n"); tmpShader.collapseType = COLLAPSE_reflection_CB; tmpStages[numStages] = tmpReflectionStage; tmpStages[numStages].type = ST_COLLAPSE_reflection_CB; tmpStages[numStages].bundle[TB_NORMALMAP] = tmpNormalStage.bundle[0]; numStages++; j += 1; continue; } // if there was no merge option just copy stage else { tmpStages[numStages] = stages[j]; numStages++; } } // clear unused stages Com_Memset(&tmpStages[numStages], 0, sizeof(stages[0]) * (MAX_SHADER_STAGES - numStages)); tmpShader.numStages = numStages; // copy result Com_Memcpy(&stages[0], &tmpStages[0], sizeof(stages)); Com_Memcpy(&shader, &tmpShader, sizeof(shader)); } // *INDENT-ON* /* ============= FixRenderCommandList https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 Arnout: this is a nasty issue. Shaders can be registered after drawsurfaces are generated but before the frame is rendered. This will, for the duration of one frame, cause drawsurfaces to be rendered with bad shaders. To fix this, need to go through all render commands and fix sortedIndex. ============== */ static void FixRenderCommandList(int newShader) { renderCommandList_t *cmdList = &backEndData[tr.smpFrame]->commands; if(cmdList) { const void *curCmd = cmdList->cmds; while(1) { switch (*(const int *)curCmd) { case RC_SET_COLOR: { const setColorCommand_t *sc_cmd = (const setColorCommand_t *)curCmd; curCmd = (const void *)(sc_cmd + 1); break; } case RC_STRETCH_PIC: { const stretchPicCommand_t *sp_cmd = (const stretchPicCommand_t *)curCmd; curCmd = (const void *)(sp_cmd + 1); break; } case RC_DRAW_VIEW: { int i; drawSurf_t *drawSurf; const drawViewCommand_t *dv_cmd = (const drawViewCommand_t *)curCmd; for(i = 0, drawSurf = dv_cmd->viewParms.drawSurfs; i < dv_cmd->viewParms.numDrawSurfs; i++, drawSurf++) { if(drawSurf->shaderNum >= newShader) { drawSurf->shaderNum++; } } curCmd = (const void *)(dv_cmd + 1); break; } case RC_DRAW_BUFFER: { const drawBufferCommand_t *db_cmd = (const drawBufferCommand_t *)curCmd; curCmd = (const void *)(db_cmd + 1); break; } case RC_SWAP_BUFFERS: { const swapBuffersCommand_t *sb_cmd = (const swapBuffersCommand_t *)curCmd; curCmd = (const void *)(sb_cmd + 1); break; } case RC_END_OF_LIST: default: return; } } } } /* ============== SortNewShader Positions the most recently created shader in the tr.sortedShaders[] array so that the shader->sort key is sorted reletive to the other shaders. Sets shader->sortedIndex ============== */ static void SortNewShader(void) { int i; float sort; shader_t *newShader; newShader = tr.shaders[tr.numShaders - 1]; sort = newShader->sort; for(i = tr.numShaders - 2; i >= 0; i--) { if(tr.sortedShaders[i]->sort <= sort) { break; } tr.sortedShaders[i + 1] = tr.sortedShaders[i]; tr.sortedShaders[i + 1]->sortedIndex++; } // Arnout: fix rendercommandlist // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=493 //FixRenderCommandList(i + 1); newShader->sortedIndex = i + 1; tr.sortedShaders[i + 1] = newShader; } /* ==================== GeneratePermanentShader ==================== */ static shader_t *GeneratePermanentShader(void) { shader_t *newShader; int i, b; int size, hash; if(tr.numShaders == MAX_SHADERS) { ri.Printf(PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n"); return tr.defaultShader; } newShader = ri.Hunk_Alloc(sizeof(shader_t), h_low); *newShader = shader; if(shader.sort <= SS_OPAQUE) { newShader->fogPass = FP_EQUAL; } else if(shader.contentFlags & CONTENTS_FOG) { newShader->fogPass = FP_LE; } tr.shaders[tr.numShaders] = newShader; newShader->index = tr.numShaders; tr.sortedShaders[tr.numShaders] = newShader; newShader->sortedIndex = tr.numShaders; tr.numShaders++; for(i = 0; i < newShader->numStages; i++) { if(!stages[i].active) { break; } newShader->stages[i] = ri.Hunk_Alloc(sizeof(stages[i]), h_low); *newShader->stages[i] = stages[i]; for(b = 0; b < MAX_TEXTURE_BUNDLES; b++) { size = newShader->stages[i]->bundle[b].numTexMods * sizeof(texModInfo_t); newShader->stages[i]->bundle[b].texMods = ri.Hunk_Alloc(size, h_low); Com_Memcpy(newShader->stages[i]->bundle[b].texMods, stages[i].bundle[b].texMods, size); } } SortNewShader(); hash = generateHashValue(newShader->name, FILE_HASH_SIZE); newShader->next = shaderHashTable[hash]; shaderHashTable[hash] = newShader; return newShader; } /* ==================== GeneratePermanentShaderTable ==================== */ static void GeneratePermanentShaderTable(float *values, int numValues) { shaderTable_t *newTable; int i; int hash; if(tr.numTables == MAX_SHADER_TABLES) { ri.Printf(PRINT_WARNING, "WARNING: GeneratePermanentShaderTables - MAX_SHADER_TABLES hit\n"); return; } newTable = ri.Hunk_Alloc(sizeof(shaderTable_t), h_low); *newTable = table; tr.shaderTables[tr.numTables] = newTable; newTable->index = tr.numTables; tr.numTables++; newTable->numValues = numValues; newTable->values = ri.Hunk_Alloc(sizeof(float) * numValues, h_low); // ri.Printf(PRINT_ALL, "values: \n"); for(i = 0; i < numValues; i++) { newTable->values[i] = values[i]; // ri.Printf(PRINT_ALL, "%f", newTable->values[i]); // if(i != numValues -1) // ri.Printf(PRINT_ALL, ", "); } // ri.Printf(PRINT_ALL, "\n"); hash = generateHashValue(newTable->name, MAX_SHADERTABLE_HASH); newTable->next = shaderTableHashTable[hash]; shaderTableHashTable[hash] = newTable; } /* ========================= FinishShader Returns a freshly allocated shader with all the needed info from the current global working shader ========================= */ static shader_t *FinishShader(void) { int stage; // set sky stuff appropriate if(shader.isSky) { if(shader.noFog) { shader.sort = SS_ENVIRONMENT_NOFOG; } else { shader.sort = SS_ENVIRONMENT_FOG; } } if(shader.forceOpaque) { shader.sort = SS_OPAQUE; } // set polygon offset if(shader.polygonOffset && !shader.sort) { shader.sort = SS_DECAL; } // all light materials need at least one z attenuation stage as first stage if(shader.type == SHADER_LIGHT) { if(stages[0].type != ST_ATTENUATIONMAP_Z) { // move up subsequent stages memmove(&stages[1], &stages[0], sizeof(stages[0]) * (MAX_SHADER_STAGES - 1)); stages[0].active = qtrue; stages[0].type = ST_ATTENUATIONMAP_Z; stages[0].rgbGen = CGEN_IDENTITY; stages[0].stateBits = GLS_DEFAULT; stages[0].overrideWrapType = qtrue; stages[0].wrapType = WT_EDGE_CLAMP; LoadMap(&stages[0], "lights/squarelight1a.tga"); } // force following shader stages to be xy attenuation stages for(stage = 1; stage < MAX_SHADER_STAGES; stage++) { shaderStage_t *pStage = &stages[stage]; if(!pStage->active) { break; } pStage->type = ST_ATTENUATIONMAP_XY; } } // set appropriate stage information for(stage = 0; stage < MAX_SHADER_STAGES; stage++) { shaderStage_t *pStage = &stages[stage]; if(!pStage->active) { break; } // check for a missing texture switch (pStage->type) { case ST_LIQUIDMAP: #if defined(COMPAT_Q3A) || defined(COMPAT_ET) case ST_LIGHTMAP: #endif // skip break; case ST_COLORMAP: default: { if(!pStage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "Shader %s has a colormap stage with no image\n", shader.name); pStage->active = qfalse; continue; } break; } case ST_DIFFUSEMAP: { if(!shader.isSky) { shader.interactLight = qtrue; } if(!pStage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "Shader %s has a diffusemap stage with no image\n", shader.name); pStage->bundle[0].image[0] = tr.defaultImage; } break; } case ST_NORMALMAP: { if(!pStage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "Shader %s has a normalmap stage with no image\n", shader.name); pStage->bundle[0].image[0] = tr.flatImage; } break; } case ST_SPECULARMAP: { if(!pStage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "Shader %s has a specularmap stage with no image\n", shader.name); pStage->bundle[0].image[0] = tr.blackImage; } break; } case ST_ATTENUATIONMAP_XY: { if(!pStage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "Shader %s has a xy attenuationmap stage with no image\n", shader.name); pStage->active = qfalse; continue; } break; } case ST_ATTENUATIONMAP_Z: { if(!pStage->bundle[0].image[0]) { ri.Printf(PRINT_WARNING, "Shader %s has a z attenuationmap stage with no image\n", shader.name); pStage->active = qfalse; continue; } break; } } if(shader.forceOpaque) { pStage->stateBits |= GLS_DEPTHMASK_TRUE; } if(shader.isSky && pStage->noFog) { shader.sort = SS_ENVIRONMENT_NOFOG; } // determine sort order and fog color adjustment if((pStage->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS)) && (stages[0].stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS))) { int blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS; int blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS; // fog color adjustment only works for blend modes that have a contribution // that aproaches 0 as the modulate values aproach 0 -- // GL_ONE, GL_ONE // GL_ZERO, GL_ONE_MINUS_SRC_COLOR // GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA // modulate, additive if(((blendSrcBits == GLS_SRCBLEND_ONE) && (blendDstBits == GLS_DSTBLEND_ONE)) || ((blendSrcBits == GLS_SRCBLEND_ZERO) && (blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR))) { pStage->adjustColorsForFog = ACFF_MODULATE_RGB; } // strict blend else if((blendSrcBits == GLS_SRCBLEND_SRC_ALPHA) && (blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA)) { pStage->adjustColorsForFog = ACFF_MODULATE_ALPHA; } // premultiplied alpha else if((blendSrcBits == GLS_SRCBLEND_ONE) && (blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA)) { pStage->adjustColorsForFog = ACFF_MODULATE_RGBA; } else { // we can't adjust this one correctly, so it won't be exactly correct in fog } // don't screw with sort order if this is a portal or environment if(!shader.sort) { // see through item, like a grill or grate if(pStage->stateBits & GLS_DEPTHMASK_TRUE) { shader.sort = SS_SEE_THROUGH; } else { shader.sort = SS_BLEND0; } } } } shader.numStages = stage; // there are times when you will need to manually apply a sort to // opaque alpha tested shaders that have later blend passes if(!shader.sort) { if(shader.translucent && !shader.forceOpaque) shader.sort = SS_DECAL; else shader.sort = SS_OPAQUE; } // HACK: allow alpha tested surfaces to create shadowmaps if(r_shadows->integer >= SHADOWING_ESM16) { if(shader.noShadows && shader.alphaTest) { shader.noShadows = qfalse; } } // look for multitexture potential CollapseStages(); // fogonly shaders don't have any normal passes if(shader.numStages == 0 && !shader.isSky) { shader.sort = SS_FOG; } return GeneratePermanentShader(); } //======================================================================================== //bani - dynamic shader list typedef struct dynamicshader dynamicshader_t; struct dynamicshader { char *shadertext; dynamicshader_t *next; }; static dynamicshader_t *dshader = NULL; /* ==================== RE_LoadDynamicShader bani - load a new dynamic shader if shadertext is NULL, looks for matching shadername and removes it returns qtrue if request was successful, qfalse if the gods were angered ==================== */ qboolean RE_LoadDynamicShader(const char *shadername, const char *shadertext) { #if 1 const char *func_err = "WARNING: RE_LoadDynamicShader"; dynamicshader_t *dptr, *lastdptr; char *q, *token; ri.Printf(PRINT_WARNING, "RE_LoadDynamicShader( name = '%s', text = '%s' )\n", shadername, shadertext); if(!shadername && shadertext) { ri.Printf(PRINT_WARNING, "%s called with NULL shadername and non-NULL shadertext:\n%s\n", func_err, shadertext); return qfalse; } if(shadername && strlen(shadername) >= MAX_QPATH) { ri.Printf(PRINT_WARNING, "%s shadername %s exceeds MAX_QPATH\n", func_err, shadername); return qfalse; } //empty the whole list if(!shadername && !shadertext) { dptr = dshader; while(dptr) { lastdptr = dptr->next; ri.Free(dptr->shadertext); ri.Free(dptr); dptr = lastdptr; } dshader = NULL; return qtrue; } //walk list for existing shader to delete, or end of the list dptr = dshader; lastdptr = NULL; while(dptr) { q = dptr->shadertext; token = COM_ParseExt(&q, qtrue); if((token[0] != 0) && !Q_stricmp(token, shadername)) { //request to nuke this dynamic shader if(!shadertext) { if(!lastdptr) { dshader = NULL; } else { lastdptr->next = dptr->next; } ri.Free(dptr->shadertext); ri.Free(dptr); return qtrue; } ri.Printf(PRINT_WARNING, "%s shader %s already exists!\n", func_err, shadername); return qfalse; } lastdptr = dptr; dptr = dptr->next; } //cant add a new one with empty shadertext if(!shadertext || !strlen(shadertext)) { ri.Printf(PRINT_WARNING, "%s new shader %s has NULL shadertext!\n", func_err, shadername); return qfalse; } //create a new shader dptr = (dynamicshader_t *) ri.Malloc(sizeof(*dptr)); if(!dptr) { Com_Error(ERR_FATAL, "Couldn't allocate struct for dynamic shader %s\n", shadername); } if(lastdptr) { lastdptr->next = dptr; } dptr->shadertext = ri.Malloc(strlen(shadertext) + 1); if(!dptr->shadertext) { Com_Error(ERR_FATAL, "Couldn't allocate buffer for dynamic shader %s\n", shadername); } Q_strncpyz(dptr->shadertext, shadertext, strlen(shadertext) + 1); dptr->next = NULL; if(!dshader) { dshader = dptr; } // ri.Printf( PRINT_ALL, "Loaded dynamic shader [%s] with shadertext [%s]\n", shadername, shadertext ); return qtrue; #else return qfalse; #endif } //======================================================================================== /* ==================== FindShaderInShaderText Scans the combined text description of all the shader files for the given shader name. return NULL if not found If found, it will return a valid shader ===================== */ static char *FindShaderInShaderText(const char *shaderName) { char *token, *p; int i, hash; hash = generateHashValue(shaderName, MAX_SHADERTEXT_HASH); for(i = 0; shaderTextHashTable[hash][i]; i++) { p = shaderTextHashTable[hash][i]; token = COM_ParseExt(&p, qtrue); if(!Q_stricmp(token, shaderName)) { //ri.Printf(PRINT_ALL, "found shader '%s' by hashing\n", shaderName); return p; } } p = s_shaderText; if(!p) { return NULL; } // look for label while(1) { token = COM_ParseExt(&p, qtrue); if(token[0] == 0) { break; } if(!Q_stricmp(token, shaderName)) { //ri.Printf(PRINT_ALL, "found shader '%s' by linear search\n", shaderName); return p; } // su44: in MoHAA, there is a single shader called "table" // which conflicts with "table" keyword used by Doom3 and Xreal. // See scripts/items.shader from pak0.pk3. #if 0 // skip shader tables else if(!Q_stricmp(token, "table")) { // skip table name token = COM_ParseExt(&p, qtrue); Com_SkipBracedSection(&p); } #endif // support shader templates else if(!Q_stricmp(token, "guide")) { // parse shader name token = COM_ParseExt(&p, qtrue); if(!Q_stricmp(token, shaderName)) { ri.Printf(PRINT_ALL, "found shader '%s' by linear search\n", shaderName); return p; } // skip guide name token = COM_ParseExt(&p, qtrue); // skip parameters token = COM_ParseExt(&p, qtrue); if(Q_stricmp(token, "(")) { break; } while(1) { token = COM_ParseExt(&p, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; } if(Q_stricmp(token, ")")) { break; } } else { // skip the shader body Com_SkipBracedSection(&p); } } return NULL; } /* ================== R_FindShaderByName Will always return a valid shader, but it might be the default shader if the real one can't be found. ================== */ shader_t *R_FindShaderByName(const char *name) { char strippedName[MAX_QPATH]; int hash; shader_t *sh; if((name == NULL) || (name[0] == 0)) { // bk001205 return tr.defaultShader; } COM_StripExtension(name, strippedName, sizeof(strippedName)); hash = generateHashValue(strippedName, FILE_HASH_SIZE); // see if the shader is already loaded for(sh = shaderHashTable[hash]; sh; sh = sh->next) { // NOTE: if there was no shader or image available with the name strippedName // then a default shader is created with type == SHADER_3D_DYNAMIC, so we // have to check all default shaders otherwise for every call to R_FindShader // with that same strippedName a new default shader is created. if(Q_stricmp(sh->name, strippedName) == 0) { // match found return sh; } } return tr.defaultShader; } /* =============== R_FindShader Will always return a valid shader, but it might be the default shader if the real one can't be found. In the interest of not requiring an explicit shader text entry to be defined for every single image used in the game, three default shader behaviors can be auto-created for any image: If type == SHADER_2D, then the image will be used for 2D rendering unless an explicit shader is found If type == SHADER_3D_DYNAMIC, then the image will have dynamic diffuse lighting applied to it, as apropriate for most entity skin surfaces. If type == SHADER_3D_STATIC, then the image will use the vertex rgba modulate values, as apropriate for misc_model pre-lit surfaces. =============== */ shader_t *R_FindShader(const char *name, shaderType_t type, qboolean mipRawImage) { char strippedName[MAX_QPATH]; char fileName[MAX_QPATH]; int i, hash; char *shaderText; image_t *image; shader_t *sh; if(name[0] == 0) { return tr.defaultShader; } COM_StripExtension(name, strippedName, sizeof(strippedName)); hash = generateHashValue(strippedName, FILE_HASH_SIZE); // see if the shader is already loaded for(sh = shaderHashTable[hash]; sh; sh = sh->next) { // NOTE: if there was no shader or image available with the name strippedName // then a default shader is created with type == SHADER_3D_DYNAMIC, so we // have to check all default shaders otherwise for every call to R_FindShader // with that same strippedName a new default shader is created. if((sh->type == type || sh->defaultShader) && !Q_stricmp(sh->name, strippedName)) { // match found return sh; } } // make sure the render thread is stopped, because we are probably // going to have to upload an image if(r_smp->integer) { R_SyncRenderThread(); } // clear the global shader Com_Memset(&shader, 0, sizeof(shader)); Com_Memset(&stages, 0, sizeof(stages)); Q_strncpyz(shader.name, strippedName, sizeof(shader.name)); shader.type = type; for(i = 0; i < MAX_SHADER_STAGES; i++) { stages[i].bundle[0].texMods = texMods[i]; } // ydnar: default to no implicit mappings implicitMap[0] = '\0'; implicitStateBits = GLS_DEFAULT; implicitCullType = CT_FRONT_SIDED; // attempt to define shader from an explicit parameter file shaderText = FindShaderInShaderText(strippedName); if(shaderText) { // enable this when building a pak file to get a global list // of all explicit shaders if(r_printShaders->integer) { ri.Printf(PRINT_ALL, "...loading explicit shader '%s'\n", strippedName); } if(!ParseShader(shaderText)) { // had errors, so use default shader shader.defaultShader = qtrue; sh = FinishShader(); return sh; } // ydnar: allow implicit mappings if(implicitMap[0] == '\0') { sh = FinishShader(); return sh; } } // ydnar: allow implicit mapping ('-' = use shader name) if(implicitMap[0] == '\0' || implicitMap[0] == '-') { Q_strncpyz(fileName, strippedName, sizeof(fileName)); } else { Q_strncpyz(fileName, implicitMap, sizeof(fileName)); } // ydnar: implicit shaders were breaking nopicmip/nomipmaps if(!mipRawImage) { //shader.noMipMaps = qtrue; shader.noPicMip = qtrue; } // if not defined in the in-memory shader descriptions, // look for a single supported image file image = R_FindImageFile(fileName, mipRawImage ? IF_NONE : IF_NOPICMIP, mipRawImage ? FT_DEFAULT : FT_LINEAR, mipRawImage ? WT_REPEAT : WT_CLAMP, shader.name); if(!image) { ri.Printf(PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name); shader.defaultShader = qtrue; return FinishShader(); } // set implicit cull type if(implicitCullType && !shader.cullType) { shader.cullType = implicitCullType; } // create the default shading commands switch (shader.type) { case SHADER_2D: { // GUI elements stages[0].bundle[0].image[0] = image; stages[0].active = qtrue; stages[0].rgbGen = CGEN_VERTEX; stages[0].alphaGen = AGEN_VERTEX; stages[0].stateBits = GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; break; } case SHADER_3D_DYNAMIC: { // dynamic colors at vertexes stages[0].type = ST_DIFFUSEMAP; stages[0].bundle[0].image[0] = image; stages[0].active = qtrue; stages[0].rgbGen = CGEN_IDENTITY_LIGHTING; stages[0].stateBits = implicitStateBits; break; } case SHADER_3D_STATIC: { // explicit colors at vertexes stages[0].type = ST_DIFFUSEMAP; stages[0].bundle[0].image[0] = image; stages[0].active = qtrue; stages[0].rgbGen = CGEN_VERTEX; stages[0].stateBits = implicitStateBits; break; } case SHADER_LIGHT: { stages[0].type = ST_ATTENUATIONMAP_Z; stages[0].bundle[0].image[0] = tr.noFalloffImage; // FIXME should be attenuationZImage stages[0].active = qtrue; stages[0].rgbGen = CGEN_IDENTITY; stages[0].stateBits = GLS_DEFAULT; stages[1].type = ST_ATTENUATIONMAP_XY; stages[1].bundle[0].image[0] = image; stages[1].active = qtrue; stages[1].rgbGen = CGEN_IDENTITY; stages[1].stateBits = GLS_DEFAULT; //stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO; break; } default: break; } return FinishShader(); } qhandle_t RE_RegisterShaderFromImage(const char *name, image_t * image, qboolean mipRawImage) { int i, hash; shader_t *sh; hash = generateHashValue(name, FILE_HASH_SIZE); // see if the shader is already loaded for(sh = shaderHashTable[hash]; sh; sh = sh->next) { // NOTE: if there was no shader or image available with the name strippedName // then a default shader is created with type == SHADER_3D_DYNAMIC, so we // have to check all default shaders otherwise for every call to R_FindShader // with that same strippedName a new default shader is created. if((sh->type == SHADER_2D || sh->defaultShader) && !Q_stricmp(sh->name, name)) { // match found return sh->index; } } // make sure the render thread is stopped, because we are probably // going to have to upload an image if(r_smp->integer) { R_SyncRenderThread(); } // clear the global shader Com_Memset(&shader, 0, sizeof(shader)); Com_Memset(&stages, 0, sizeof(stages)); Q_strncpyz(shader.name, name, sizeof(shader.name)); shader.type = SHADER_2D; for(i = 0; i < MAX_SHADER_STAGES; i++) { stages[i].bundle[0].texMods = texMods[i]; } // create the default shading commands // GUI elements stages[0].bundle[0].image[0] = image; stages[0].active = qtrue; stages[0].rgbGen = CGEN_VERTEX; stages[0].alphaGen = AGEN_VERTEX; stages[0].stateBits = GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA; sh = FinishShader(); return sh->index; } /* ==================== RE_RegisterShader This is the exported shader entry point for the rest of the system It will always return an index that will be valid. This should really only be used for explicit shaders, because there is no way to ask for different implicit lighting modes (vertex, lightmap, etc) ==================== */ qhandle_t RE_RegisterShader(const char *name) { shader_t *sh; if(strlen(name) >= MAX_QPATH) { Com_Printf("Shader name exceeds MAX_QPATH\n"); return 0; } sh = R_FindShader(name, SHADER_2D, qtrue); // we want to return 0 if the shader failed to // load for some reason, but R_FindShader should // still keep a name allocated for it, so if // something calls RE_RegisterShader again with // the same name, we don't try looking for it again if(sh->defaultShader) { return 0; } return sh->index; } /* ==================== RE_RegisterShaderNoMip For menu graphics that should never be picmiped ==================== */ qhandle_t RE_RegisterShaderNoMip(const char *name) { shader_t *sh; if(strlen(name) >= MAX_QPATH) { Com_Printf("Shader name exceeds MAX_QPATH\n"); return 0; } sh = R_FindShader(name, SHADER_2D, qfalse); // we want to return 0 if the shader failed to // load for some reason, but R_FindShader should // still keep a name allocated for it, so if // something calls RE_RegisterShader again with // the same name, we don't try looking for it again if(sh->defaultShader) { return 0; } return sh->index; } /* ==================== RE_RegisterShaderLightAttenuation For different Doom3 style light effects ==================== */ qhandle_t RE_RegisterShaderLightAttenuation(const char *name) { shader_t *sh; if(strlen(name) >= MAX_QPATH) { Com_Printf("Shader name exceeds MAX_QPATH\n"); return 0; } sh = R_FindShader(name, SHADER_LIGHT, qfalse); // we want to return 0 if the shader failed to // load for some reason, but R_FindShader should // still keep a name allocated for it, so if // something calls RE_RegisterShader again with // the same name, we don't try looking for it again if(sh->defaultShader) { return 0; } return sh->index; } /* ==================== R_GetShaderByHandle When a handle is passed in by another module, this range checks it and returns a valid (possibly default) shader_t to be used internally. ==================== */ shader_t *R_GetShaderByHandle(qhandle_t hShader) { if(hShader < 0) { ri.Printf(PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader); // bk: FIXME name return tr.defaultShader; } if(hShader >= tr.numShaders) { ri.Printf(PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader); return tr.defaultShader; } return tr.shaders[hShader]; } /* =============== R_ShaderList_f Dump information on all valid shaders to the console A second parameter will cause it to print in sorted order =============== */ void R_ShaderList_f(void) { int i; int count; shader_t *shader; char *s = NULL; ri.Printf(PRINT_ALL, "-----------------------\n"); if(ri.Cmd_Argc() > 1) { s = ri.Cmd_Argv(1); } count = 0; for(i = 0; i < tr.numShaders; i++) { if(ri.Cmd_Argc() > 2) { shader = tr.sortedShaders[i]; } else { shader = tr.shaders[i]; } if(s && Q_stricmpn(shader->name, s, strlen(s)) != 0) continue; ri.Printf(PRINT_ALL, "%i ", shader->numStages); switch (shader->type) { case SHADER_2D: ri.Printf(PRINT_ALL, "2D "); break; case SHADER_3D_DYNAMIC: ri.Printf(PRINT_ALL, "3D_D "); break; case SHADER_3D_STATIC: ri.Printf(PRINT_ALL, "3D_S "); break; case SHADER_LIGHT: ri.Printf(PRINT_ALL, "ATTN "); break; } /* if(shader->collapseType == COLLAPSE_genericMulti) { if(shader->collapseTextureEnv == GL_ADD) { ri.Printf(PRINT_ALL, "MT(a) "); } else if(shader->collapseTextureEnv == GL_MODULATE) { ri.Printf(PRINT_ALL, "MT(m) "); } else if(shader->collapseTextureEnv == GL_DECAL) { ri.Printf(PRINT_ALL, "MT(d) "); } } else */ if(shader->collapseType == COLLAPSE_lighting_DB) { ri.Printf(PRINT_ALL, "lighting_DB "); } else if(shader->collapseType == COLLAPSE_lighting_DBS) { ri.Printf(PRINT_ALL, "lighting_DBS "); } else if(shader->collapseType == COLLAPSE_reflection_CB) { ri.Printf(PRINT_ALL, "reflection_CB "); } else { ri.Printf(PRINT_ALL, " "); } if(shader->createdByGuide) { ri.Printf(PRINT_ALL, "G "); } else if(shader->explicitlyDefined) { ri.Printf(PRINT_ALL, "E "); } else { ri.Printf(PRINT_ALL, " "); } if(shader->sort == SS_BAD) { ri.Printf(PRINT_ALL, "SS_BAD "); } else if(shader->sort == SS_PORTAL) { ri.Printf(PRINT_ALL, "SS_PORTAL "); } else if(shader->sort == SS_ENVIRONMENT_FOG) { ri.Printf(PRINT_ALL, "SS_ENVIRONMENT_FOG "); } else if(shader->sort == SS_ENVIRONMENT_NOFOG) { ri.Printf(PRINT_ALL, "SS_ENVIRONMENT_NOFOG"); } else if(shader->sort == SS_OPAQUE) { ri.Printf(PRINT_ALL, "SS_OPAQUE "); } else if(shader->sort == SS_DECAL) { ri.Printf(PRINT_ALL, "SS_DECAL "); } else if(shader->sort == SS_SEE_THROUGH) { ri.Printf(PRINT_ALL, "SS_SEE_THROUGH "); } else if(shader->sort == SS_BANNER) { ri.Printf(PRINT_ALL, "SS_BANNER "); } else if(shader->sort == SS_FOG) { ri.Printf(PRINT_ALL, "SS_FOG "); } else if(shader->sort == SS_UNDERWATER) { ri.Printf(PRINT_ALL, "SS_UNDERWATER "); } else if(shader->sort == SS_WATER) { ri.Printf(PRINT_ALL, "SS_WATER "); } else if(shader->sort == SS_FAR) { ri.Printf(PRINT_ALL, "SS_FAR "); } else if(shader->sort == SS_MEDIUM) { ri.Printf(PRINT_ALL, "SS_MEDIUM "); } else if(shader->sort == SS_CLOSE) { ri.Printf(PRINT_ALL, "SS_CLOSE "); } else if(shader->sort == SS_BLEND0) { ri.Printf(PRINT_ALL, "SS_BLEND0 "); } else if(shader->sort == SS_BLEND1) { ri.Printf(PRINT_ALL, "SS_BLEND1 "); } else if(shader->sort == SS_BLEND2) { ri.Printf(PRINT_ALL, "SS_BLEND2 "); } else if(shader->sort == SS_BLEND3) { ri.Printf(PRINT_ALL, "SS_BLEND3 "); } else if(shader->sort == SS_BLEND6) { ri.Printf(PRINT_ALL, "SS_BLEND6 "); } else if(shader->sort == SS_ALMOST_NEAREST) { ri.Printf(PRINT_ALL, "SS_ALMOST_NEAREST "); } else if(shader->sort == SS_NEAREST) { ri.Printf(PRINT_ALL, "SS_NEAREST "); } else if(shader->sort == SS_POST_PROCESS) { ri.Printf(PRINT_ALL, "SS_POST_PROCESS "); } else { ri.Printf(PRINT_ALL, " "); } if(shader->interactLight) { ri.Printf(PRINT_ALL, "IA "); } else { ri.Printf(PRINT_ALL, " "); } if(shader->defaultShader) { ri.Printf(PRINT_ALL, ": %s (DEFAULTED)\n", shader->name); } else { ri.Printf(PRINT_ALL, ": %s\n", shader->name); } count++; } ri.Printf(PRINT_ALL, "%i total shaders\n", count); ri.Printf(PRINT_ALL, "------------------\n"); } void R_ShaderExp_f(void) { int i; int len; char buffer[1024] = ""; char *buffer_p = &buffer[0]; expression_t exp; strcpy(shader.name, "dummy"); ri.Printf(PRINT_ALL, "-----------------------\n"); for(i = 1; i < ri.Cmd_Argc(); i++) { strcat(buffer, ri.Cmd_Argv(i)); strcat(buffer, " "); } len = strlen(buffer); buffer[len - 1] = 0; // replace last " " with tailing zero ParseExpression(&buffer_p, &exp); ri.Printf(PRINT_ALL, "%i total ops\n", exp.numOps); ri.Printf(PRINT_ALL, "%f result\n", RB_EvalExpression(&exp, 0)); ri.Printf(PRINT_ALL, "------------------\n"); } /* ==================== ScanAndLoadShaderGuides Finds and loads all .guide files, combining them into a single large text block that can be scanned for shader template names ===================== */ #define MAX_GUIDE_FILES 1024 static void ScanAndLoadGuideFiles(void) { char **guideFiles; char *buffers[MAX_GUIDE_FILES]; char *p; int numGuides; int i; char *oldp, *token, *hashMem; int guideTextHashTableSizes[MAX_GUIDETEXT_HASH], hash, size; char filename[MAX_QPATH]; long sum = 0; int num; ri.Printf(PRINT_ALL, "----- ScanAndLoadGuideFiles -----\n"); s_guideText = NULL; Com_Memset(guideTextHashTableSizes, 0, sizeof(guideTextHashTableSizes)); Com_Memset(guideTextHashTable, 0, sizeof(guideTextHashTable)); // scan for guide files guideFiles = ri.FS_ListFiles( "guides", ".guide", qfalse, &numGuides ); if(!guideFiles || !numGuides) { ri.Printf(PRINT_WARNING, "WARNING: no shader guide files found\n"); return; } if(numGuides > MAX_GUIDE_FILES) { numGuides = MAX_GUIDE_FILES; } // build single large buffer for(i = 0; i < numGuides; i++) { Com_sprintf(filename, sizeof(filename), "guides/%s", guideFiles[i]); sum += ri.FS_ReadFile(filename, NULL); } s_guideText = ri.Hunk_Alloc(sum + numGuides * 2, h_low); // load in reverse order, so doubled templates are overriden properly for(i = numGuides - 1; i >= 0; i--) { Com_sprintf(filename, sizeof(filename), "guides/%s", guideFiles[i]); ri.Printf(PRINT_DEVELOPER, "...loading '%s'\n", filename); sum += ri.FS_ReadFile(filename, (void **)&buffers[i]); if(!buffers[i]) { ri.Error(ERR_DROP, "Couldn't load %s", filename); } strcat(s_guideText, "\n"); p = &s_guideText[strlen(s_guideText)]; strcat(s_guideText, buffers[i]); ri.FS_FreeFile(buffers[i]); buffers[i] = p; COM_Compress(p); } size = 0; // for(i = 0; i < numGuides; i++) { Com_sprintf(filename, sizeof(filename), "guides/%s", guideFiles[i]); COM_BeginParseSession(filename); // pointer to the first shader file p = buffers[i]; // look for label while(1) { token = COM_ParseExt(&p, qtrue); if(token[0] == 0) { break; } if(Q_stricmp(token, "guide") && Q_stricmp(token, "inlineGuide")) { COM_ParseWarning("expected guide or inlineGuide found '%s'\n", token); break; } // parse guide name token = COM_ParseExt(&p, qtrue); //ri.Printf(PRINT_ALL, "guide: '%s'\n", token); hash = generateHashValue(token, MAX_GUIDETEXT_HASH); guideTextHashTableSizes[hash]++; size++; // skip parameters token = COM_ParseExt(&p, qtrue); if(Q_stricmp(token, "(")) { COM_ParseWarning("expected ( found '%s'\n", token); break; } while(1) { token = COM_ParseExt(&p, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; } if(Q_stricmp(token, ")")) { COM_ParseWarning("expected ) found '%s'\n", token); break; } // skip guide body Com_SkipBracedSection(&p); // if we passed the pointer to the next shader file if(i < numGuides - 1) { if(p > buffers[i + 1]) { break; } } } } size += MAX_GUIDETEXT_HASH; hashMem = ri.Hunk_Alloc(size * sizeof(char *), h_low); for(i = 0; i < MAX_GUIDETEXT_HASH; i++) { guideTextHashTable[i] = (char **)hashMem; hashMem = ((char *)hashMem) + ((guideTextHashTableSizes[i] + 1) * sizeof(char *)); } Com_Memset(guideTextHashTableSizes, 0, sizeof(guideTextHashTableSizes)); // for(i = 0; i < numGuides; i++) { Com_sprintf(filename, sizeof(filename), "guides/%s", guideFiles[i]); COM_BeginParseSession(filename); // pointer to the first shader file p = buffers[i]; // look for label while(1) { token = COM_ParseExt(&p, qtrue); if(token[0] == 0) { break; } if(Q_stricmp(token, "guide") && Q_stricmp(token, "inlineGuide")) { COM_ParseWarning("expected guide or inlineGuide found '%s'\n", token); break; } // parse guide name oldp = p; token = COM_ParseExt(&p, qtrue); //ri.Printf(PRINT_ALL, "...hashing guide '%s'\n", token); hash = generateHashValue(token, MAX_GUIDETEXT_HASH); guideTextHashTable[hash][guideTextHashTableSizes[hash]++] = oldp; // skip parameters token = COM_ParseExt(&p, qtrue); if(Q_stricmp(token, "(")) { COM_ParseWarning("expected ( found '%s'\n", token); break; } while(1) { token = COM_ParseExt(&p, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; } if(Q_stricmp(token, ")")) { COM_ParseWarning("expected ) found '%s'\n", token); break; } // skip guide body Com_SkipBracedSection(&p); // if we passed the pointer to the next shader file if(i < numGuides - 1) { if(p > buffers[i + 1]) { break; } } } } // free up memory ri.FS_FreeFileList(guideFiles); } /* ==================== ScanAndLoadShaderFiles Finds and loads all .shader files, combining them into a single large text block that can be scanned for shader names ===================== */ #define MAX_SHADER_FILES 4096 static void ScanAndLoadShaderFiles(void) { char **shaderFiles; char *buffers[MAX_SHADER_FILES]; char *p; int numShaders; int i; char *oldp, *token, *hashMem; int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; char filename[MAX_QPATH]; long sum = 0; ri.Printf(PRINT_ALL, "----- ScanAndLoadShaderFiles -----\n"); // scan for shader files #if defined(COMPAT_Q3A) || defined(COMPAT_ET) shaderFiles = ri.FS_ListFiles("scripts", ".shader", qfalse, &numShaders); #else shaderFiles = ri.FS_ListFiles("materials", ".mtr", qfalse, &numShaders); #endif if(!shaderFiles || !numShaders) { ri.Printf(PRINT_WARNING, "WARNING: no shader files found\n"); return; } if(numShaders > MAX_SHADER_FILES) { numShaders = MAX_SHADER_FILES; } // build single large buffer for(i = 0; i < numShaders; i++) { #if defined(COMPAT_Q3A) || defined(COMPAT_ET) Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]); #else Com_sprintf(filename, sizeof(filename), "materials/%s", shaderFiles[i]); #endif sum += ri.FS_ReadFile(filename, NULL); } s_shaderText = ri.Hunk_Alloc(sum + numShaders * 2, h_low); // load in reverse order, so doubled shaders are overriden properly for(i = numShaders - 1; i >= 0; i--) { #if defined(COMPAT_Q3A) || defined(COMPAT_ET) Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]); #else Com_sprintf(filename, sizeof(filename), "materials/%s", shaderFiles[i]); #endif ri.Printf(PRINT_DEVELOPER, "...loading '%s'\n", filename); sum += ri.FS_ReadFile(filename, (void **)&buffers[i]); if(!buffers[i]) { ri.Error(ERR_DROP, "Couldn't load %s", filename); } strcat(s_shaderText, "\n"); p = &s_shaderText[strlen(s_shaderText)]; strcat(s_shaderText, buffers[i]); ri.FS_FreeFile(buffers[i]); buffers[i] = p; COM_Compress(p); } Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); size = 0; for(i = 0; i < numShaders; i++) { #if defined(COMPAT_Q3A) || defined(COMPAT_ET) Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]); #else Com_sprintf(filename, sizeof(filename), "materials/%s", shaderFiles[i]); #endif COM_BeginParseSession(filename); // pointer to the first shader file p = buffers[i]; // look for label while(1) { token = COM_ParseExt(&p, qtrue); if(token[0] == 0) { break; } // su44: in MoHAA, there is a single shader called "table" // which conflicts with "table" keyword used by Doom3 and Xreal. // See scripts/items.shader from pak0.pk3. #if 0 // skip shader tables if(!Q_stricmp(token, "table")) { // skip table name token = COM_ParseExt(&p, qtrue); Com_SkipBracedSection(&p); } // support shader templates else #endif if(!Q_stricmp(token, "guide")) { // parse shader name token = COM_ParseExt(&p, qtrue); //ri.Printf(PRINT_ALL, "...guided '%s'\n", token); hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableSizes[hash]++; size++; // skip guide name token = COM_ParseExt(&p, qtrue); // skip parameters token = COM_ParseExt(&p, qtrue); if(Q_stricmp(token, "(")) { COM_ParseWarning("expected ( found '%s'\n", token); break; } while(1) { token = COM_ParseExt(&p, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; } if(Q_stricmp(token, ")")) { COM_ParseWarning("expected ) found '%s'\n", token); break; } } else { hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTableSizes[hash]++; size++; Com_SkipBracedSection(&p); } // if we passed the pointer to the next shader file if(i < numShaders - 1) { if(p > buffers[i + 1]) { break; } } } } size += MAX_SHADERTEXT_HASH; hashMem = ri.Hunk_Alloc(size * sizeof(char *), h_low); for(i = 0; i < MAX_SHADERTEXT_HASH; i++) { shaderTextHashTable[i] = (char **)hashMem; hashMem = ((char *)hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *)); } Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); // for(i = 0; i < numShaders; i++) { #if defined(COMPAT_Q3A) || defined(COMPAT_ET) Com_sprintf(filename, sizeof(filename), "scripts/%s", shaderFiles[i]); #else Com_sprintf(filename, sizeof(filename), "materials/%s", shaderFiles[i]); #endif COM_BeginParseSession(filename); // pointer to the first shader file p = buffers[i]; // look for label while(1) { oldp = p; token = COM_ParseExt(&p, qtrue); if(token[0] == 0) { break; } // su44: in MoHAA, there is a single shader called "table" // which conflicts with "table" keyword used by Doom3 and Xreal. // See scripts/items.shader from pak0.pk3. #if 0 // parse shader tables if(!Q_stricmp(token, "table")) { int depth; float values[FUNCTABLE_SIZE]; int numValues; shaderTable_t *tb; qboolean alreadyCreated; Com_Memset(&table, 0, sizeof(table)); token = COM_ParseExt(&p, qtrue); Q_strncpyz(table.name, token, sizeof(table.name)); // check if already created alreadyCreated = qfalse; hash = generateHashValue(table.name, MAX_SHADERTABLE_HASH); for(tb = shaderTableHashTable[hash]; tb; tb = tb->next) { if(Q_stricmp(tb->name, table.name) == 0) { // match found alreadyCreated = qtrue; break; } } depth = 0; numValues = 0; do { token = COM_ParseExt(&p, qtrue); if(!Q_stricmp(token, "snap")) { table.snap = qtrue; } else if(!Q_stricmp(token, "clamp")) { table.clamp = qtrue; } else if(token[0] == '{') { depth++; } else if(token[0] == '}') { depth--; } else if(token[0] == ',') { continue; } else { if(numValues == FUNCTABLE_SIZE) { ri.Printf(PRINT_WARNING, "WARNING: FUNCTABLE_SIZE hit\n"); break; } values[numValues++] = atof(token); } } while(depth && p); if(!alreadyCreated) { ri.Printf(PRINT_DEVELOPER, "...generating '%s'\n", table.name); GeneratePermanentShaderTable(values, numValues); } } // support shader templates else #endif if(!Q_stricmp(token, "guide")) { // parse shader name oldp = p; token = COM_ParseExt(&p, qtrue); //ri.Printf(PRINT_ALL, "...guided '%s'\n", token); hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; // skip guide name token = COM_ParseExt(&p, qtrue); // skip parameters token = COM_ParseExt(&p, qtrue); if(Q_stricmp(token, "(")) { COM_ParseWarning("expected ( found '%s'\n", token); break; } while(1) { token = COM_ParseExt(&p, qtrue); if(!token[0]) break; if(!Q_stricmp(token, ")")) break; } if(Q_stricmp(token, ")")) { COM_ParseWarning("expected ) found '%s'\n", token); break; } } else { hash = generateHashValue(token, MAX_SHADERTEXT_HASH); shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; // skip shaderbody Com_SkipBracedSection(&p); } // if we passed the pointer to the next shader file if(i < numShaders - 1) { if(p > buffers[i + 1]) { break; } } } } // free up memory ri.FS_FreeFileList(shaderFiles); } /* ==================== CreateInternalShaders ==================== */ static void CreateInternalShaders(void) { ri.Printf(PRINT_ALL, "----- CreateInternalShaders -----\n"); tr.numShaders = 0; // init the default shader Com_Memset(&shader, 0, sizeof(shader)); Com_Memset(&stages, 0, sizeof(stages)); Q_strncpyz(shader.name, "", sizeof(shader.name)); shader.type = SHADER_3D_DYNAMIC; stages[0].type = ST_DIFFUSEMAP; stages[0].bundle[0].image[0] = tr.defaultImage; stages[0].active = qtrue; stages[0].stateBits = GLS_DEFAULT; tr.defaultShader = FinishShader(); // light shader /* Q_strncpyz(shader.name, "", sizeof(shader.name)); stages[0].type = ST_ATTENUATIONMAP_Z; stages[0].bundle[0].image[0] = tr.attenuationZImage; stages[0].active = qtrue; stages[0].stateBits = GLS_DEFAULT; stages[1].type = ST_ATTENUATIONMAP_XY; stages[1].bundle[0].image[0] = tr.attenuationXYImage; stages[1].active = qtrue; stages[1].stateBits = GLS_DEFAULT; tr.defaultLightShader = FinishShader(); */ } static void CreateExternalShaders(void) { ri.Printf(PRINT_ALL, "----- CreateExternalShaders -----\n"); tr.flareShader = R_FindShader("flareShader", SHADER_3D_DYNAMIC, qtrue); tr.sunShader = R_FindShader("sun", SHADER_3D_DYNAMIC, qtrue); tr.defaultPointLightShader = R_FindShader("lights/defaultPointLight", SHADER_LIGHT, qtrue); tr.defaultProjectedLightShader = R_FindShader("lights/defaultProjectedLight", SHADER_LIGHT, qtrue); tr.defaultDynamicLightShader = R_FindShader("lights/defaultDynamicLight", SHADER_LIGHT, qtrue); } /* ================== R_InitShaders ================== */ void R_InitShaders(void) { Com_Memset(shaderTableHashTable, 0, sizeof(shaderTableHashTable)); Com_Memset(shaderHashTable, 0, sizeof(shaderHashTable)); deferLoad = qfalse; CreateInternalShaders(); ScanAndLoadGuideFiles(); ScanAndLoadShaderFiles(); CreateExternalShaders(); }