summaryrefslogtreecommitdiff
path: root/src/renderergl1/tr_shader.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/renderergl1/tr_shader.c')
-rw-r--r--src/renderergl1/tr_shader.c3068
1 files changed, 3068 insertions, 0 deletions
diff --git a/src/renderergl1/tr_shader.c b/src/renderergl1/tr_shader.c
new file mode 100644
index 00000000..8440dd63
--- /dev/null
+++ b/src/renderergl1/tr_shader.c
@@ -0,0 +1,3068 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2009 Darklegion Development
+
+This file is part of Tremulous.
+
+Tremulous 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.
+
+Tremulous 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 Tremulous; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+===========================================================================
+*/
+#include "tr_local.h"
+
+// tr_shader.c -- this file deals with the parsing and definition of shaders
+
+static char *s_shaderText;
+
+// the shader is parsed into these global variables, then copied into
+// dynamically allocated memory if it is valid.
+static shaderStage_t stages[MAX_SHADER_STAGES];
+static shader_t shader;
+static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
+
+#define FILE_HASH_SIZE 1024
+static shader_t* hashTable[FILE_HASH_SIZE];
+
+#define MAX_SHADERTEXT_HASH 2048
+static char **shaderTextHashTable[MAX_SHADERTEXT_HASH];
+
+/*
+================
+return a hash value for the filename
+================
+*/
+#ifdef __GNUCC__
+ #warning TODO: check if long is ok here
+#endif
+static long generateHashValue( const char *fname, const int size ) {
+ int i;
+ long hash;
+ char letter;
+
+ hash = 0;
+ i = 0;
+ while (fname[i] != '\0') {
+ 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_RegisterShaderLightMap(shaderName, 0);
+ 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_RegisterShaderLightMap(newShaderName, 0);
+ 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 = hashTable[hash]; sh; sh = sh->next) {
+ if (Q_stricmp(sh->name, strippedName) == 0) {
+ if (sh != sh2) {
+ sh->remappedShader = sh2;
+ } else {
+ sh->remappedShader = NULL;
+ }
+ }
+ }
+ if (timeOffset) {
+ sh2->timeOffset = atof(timeOffset);
+ }
+}
+
+/*
+===============
+ParseVector
+===============
+*/
+static qboolean ParseVector( char **text, int count, float *v ) {
+ char *token;
+ int i;
+
+ // FIXME: spaces are currently required after parens, should change parseext...
+ 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;
+}
+
+
+/*
+===============
+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_80;
+ }
+ else if ( !Q_stricmp( funcname, "GE128" ) )
+ {
+ return GLS_ATEST_GE_80;
+ }
+
+ 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 void ParseTexMod( char *_text, shaderStage_t *stage )
+{
+ const char *token;
+ char **text = &_text;
+ texModInfo_t *tmi;
+
+ if ( stage->bundle[0].numTexMods == TR_MAX_TEXMODS ) {
+ ri.Error( ERR_DROP, "ERROR: too many tcMod stages in shader '%s'", shader.name );
+ return;
+ }
+
+ tmi = &stage->bundle[0].texMods[stage->bundle[0].numTexMods];
+ stage->bundle[0].numTexMods++;
+
+ token = COM_ParseExt( text, qfalse );
+
+ //
+ // 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ tmi->wave.frequency = atof( token );
+
+ tmi->type = TMOD_STRETCH;
+ }
+ //
+ // transform
+ //
+ else if ( !Q_stricmp( token, "transform" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing transform parms in shader '%s'\n", shader.name );
+ return;
+ }
+ tmi->matrix[0][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;
+ }
+ tmi->matrix[0][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;
+ }
+ tmi->matrix[1][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;
+ }
+ tmi->matrix[1][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;
+ }
+ tmi->translate[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;
+ }
+ tmi->translate[1] = 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;
+ }
+ 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 );
+ }
+}
+
+
+/*
+===================
+ParseStage
+===================
+*/
+static qboolean ParseStage( shaderStage_t *stage, char **text )
+{
+ char *token;
+ int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0;
+ qboolean depthMaskExplicit = qfalse;
+
+ stage->active = qtrue;
+
+ 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;
+ }
+ //
+ // map <name>
+ //
+ else if ( !Q_stricmp( token, "map" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+
+ if ( !Q_stricmp( token, "$whiteimage" ) )
+ {
+ stage->bundle[0].image[0] = tr.whiteImage;
+ continue;
+ }
+ else if ( !Q_stricmp( token, "$lightmap" ) )
+ {
+ stage->bundle[0].isLightmap = qtrue;
+ if ( shader.lightmapIndex < 0 || !tr.lightmaps ) {
+ stage->bundle[0].image[0] = tr.whiteImage;
+ } else {
+ stage->bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+ }
+ continue;
+ }
+ else
+ {
+ stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT );
+ 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;
+ }
+ }
+ }
+ //
+ // clampmap <name>
+ //
+ 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;
+ }
+
+ stage->bundle[0].image[0] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_CLAMP_TO_EDGE );
+ 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 <frequency> <image1> .... <imageN>
+ //
+ 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 );
+
+ // parse up to MAX_IMAGE_ANIMATIONS animations
+ while ( 1 ) {
+ int num;
+
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] ) {
+ break;
+ }
+ num = stage->bundle[0].numImageAnimations;
+ if ( num < MAX_IMAGE_ANIMATIONS ) {
+ stage->bundle[0].image[num] = R_FindImageFile( token, !shader.noMipMaps, !shader.noPicMip, GL_REPEAT );
+ 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].numImageAnimations++;
+ }
+ }
+ }
+ else if ( !Q_stricmp( token, "videoMap" ) )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( !token[0] )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'videoMmap' keyword in shader '%s'\n", shader.name );
+ return qfalse;
+ }
+ stage->bundle[0].videoMapHandle = ri.CIN_PlayCinematic( token, 0, 0, 256, 256, (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];
+ }
+ }
+ //
+ // alphafunc <func>
+ //
+ 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 );
+ }
+ //
+ // depthFunc <func>
+ //
+ 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;
+ }
+ }
+ //
+ // detail
+ //
+ else if ( !Q_stricmp( token, "detail" ) )
+ {
+ stage->isDetail = qtrue;
+ }
+ //
+ // blendfunc <srcFactor> <dstFactor>
+ // or blendfunc <add|filter|blend>
+ //
+ 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 {
+ // 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;
+ }
+ }
+ //
+ // 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_EXACT_VERTEX;
+ }
+ else if ( !Q_stricmp( token, "lightingDiffuse" ) )
+ {
+ stage->rgbGen = CGEN_LIGHTING_DIFFUSE;
+ }
+ else if ( !Q_stricmp( token, "oneMinusVertex" ) )
+ {
+ stage->rgbGen = CGEN_ONE_MINUS_VERTEX;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown rgbGen parameter '%s' in shader '%s'\n", token, shader.name );
+ continue;
+ }
+ }
+ //
+ // 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" ) )
+ {
+ 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" ) )
+ {
+ stage->alphaGen = AGEN_LIGHTING_SPECULAR;
+ }
+ else if ( !Q_stricmp( token, "oneMinusVertex" ) )
+ {
+ stage->alphaGen = AGEN_ONE_MINUS_VERTEX;
+ }
+ else if ( !Q_stricmp( token, "portal" ) )
+ {
+ stage->alphaGen = AGEN_PORTAL;
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ {
+ shader.portalRange = 256;
+ ri.Printf( PRINT_WARNING, "WARNING: missing range parameter for alphaGen portal in shader '%s', defaulting to 256\n", shader.name );
+ }
+ else
+ {
+ shader.portalRange = atof( token );
+ }
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown alphaGen parameter '%s' in shader '%s'\n", token, shader.name );
+ continue;
+ }
+ }
+ //
+ // tcGen <function>
+ //
+ 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" ) )
+ {
+ stage->bundle[0].tcGen = TCGEN_ENVIRONMENT_MAPPED;
+ }
+ else if ( !Q_stricmp( token, "lightmap" ) )
+ {
+ stage->bundle[0].tcGen = TCGEN_LIGHTMAP;
+ }
+ else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) )
+ {
+ stage->bundle[0].tcGen = TCGEN_TEXTURE;
+ }
+ else if ( !Q_stricmp( token, "vector" ) )
+ {
+ ParseVector( text, 3, stage->bundle[0].tcGenVectors[0] );
+ ParseVector( text, 3, stage->bundle[0].tcGenVectors[1] );
+
+ stage->bundle[0].tcGen = TCGEN_VECTOR;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown texgen parm in shader '%s'\n", shader.name );
+ }
+ }
+ //
+ // tcMod <type> <...>
+ //
+ else if ( !Q_stricmp( token, "tcMod" ) )
+ {
+ char buffer[1024] = "";
+
+ while ( 1 )
+ {
+ token = COM_ParseExt( text, qfalse );
+ if ( token[0] == 0 )
+ break;
+ strcat( buffer, token );
+ strcat( buffer, " " );
+ }
+
+ ParseTexMod( buffer, stage );
+
+ continue;
+ }
+ //
+ // depthmask
+ //
+ else if ( !Q_stricmp( token, "depthwrite" ) )
+ {
+ depthMaskBits = GLS_DEPTHMASK_TRUE;
+ depthMaskExplicit = qtrue;
+
+ continue;
+ }
+ else
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+ }
+
+ //
+ // 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;
+ }
+
+ // decide which agens we can skip
+ if ( stage->alphaGen == AGEN_IDENTITY ) {
+ if ( stage->rgbGen == CGEN_IDENTITY
+ || stage->rgbGen == CGEN_LIGHTING_DIFFUSE ) {
+ stage->alphaGen = AGEN_SKIP;
+ }
+ }
+
+ //
+ // compute state bits
+ //
+ stage->stateBits = depthMaskBits |
+ blendSrcBits | blendDstBits |
+ atestBits |
+ depthFuncBits;
+
+ return qtrue;
+}
+
+/*
+===============
+ParseDeform
+
+deformVertexes wave <spread> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes normal <frequency> <amplitude>
+deformVertexes move <vector> <waveform> <base> <amplitude> <phase> <frequency>
+deformVertexes bulge <bulgeWidth> <bulgeHeight> <bulgeSpeed>
+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;
+ }
+
+ ri.Printf( PRINT_WARNING, "WARNING: unknown deformVertexes subtype '%s' found in shader '%s'\n", token, shader.name );
+}
+
+
+/*
+===============
+ParseSkyParms
+
+skyParms <outerbox> <cloudheight> <innerbox>
+===============
+*/
+static void ParseSkyParms( char **text ) {
+ char *token;
+ static char *suf[6] = {"rt", "bk", "lf", "ft", "up", "dn"};
+ char pathname[MAX_QPATH];
+ int i;
+
+ // 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, "-" ) ) {
+ for (i=0 ; i<6 ; i++) {
+ Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga"
+ , token, suf[i] );
+ shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_CLAMP_TO_EDGE );
+
+ if ( !shader.sky.outerbox[i] ) {
+ shader.sky.outerbox[i] = tr.defaultImage;
+ }
+ }
+ }
+
+ // 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;
+ }
+ R_InitSkyTexCoords( shader.sky.cloudHeight );
+
+
+ // 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, "-" ) ) {
+ for (i=0 ; i<6 ; i++) {
+ Com_sprintf( pathname, sizeof(pathname), "%s_%s.tga"
+ , token, suf[i] );
+ shader.sky.innerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, GL_REPEAT );
+ if ( !shader.sky.innerbox[i] ) {
+ shader.sky.innerbox[i] = tr.defaultImage;
+ }
+ }
+ }
+
+ shader.isSky = qtrue;
+}
+
+
+/*
+=================
+ParseSort
+=================
+*/
+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" ) ) {
+ shader.sort = SS_PORTAL;
+ } else if ( !Q_stricmp( token, "sky" ) ) {
+ shader.sort = SS_ENVIRONMENT;
+ } 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, "additive" ) ) {
+ shader.sort = SS_BLEND1;
+ } else if ( !Q_stricmp( token, "nearest" ) ) {
+ shader.sort = SS_NEAREST;
+ } else if ( !Q_stricmp( token, "underwater" ) ) {
+ shader.sort = SS_UNDERWATER;
+ } else {
+ shader.sort = atof( token );
+ }
+}
+
+
+
+// this table is also present in q3map
+
+typedef struct {
+ char *name;
+ int clearSolid, surfaceFlags, contents;
+} infoParm_t;
+
+infoParm_t infoParms[] = {
+ // server relevant contents
+ {"water", 1, 0, CONTENTS_WATER },
+ {"slime", 1, 0, CONTENTS_SLIME }, // mildly damaging
+ {"lava", 1, 0, CONTENTS_LAVA }, // very damaging
+ {"playerclip", 1, 0, CONTENTS_PLAYERCLIP },
+ {"monsterclip", 1, 0, CONTENTS_MONSTERCLIP },
+ {"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
+
+ // utility relevant attributes
+ {"origin", 1, 0, CONTENTS_ORIGIN }, // center of rotating brushes
+ {"trans", 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
+
+ {"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 },
+ {"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
+ {"ladder", 0, SURF_LADDER, 0 },
+ {"nodamage", 0, SURF_NODAMAGE, 0 },
+ {"metalsteps", 0, SURF_METALSTEPS,0 },
+ {"flesh", 0, SURF_FLESH, 0 },
+ {"nosteps", 0, SURF_NOSTEPS, 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, SURF_NODLIGHT, 0 }, // don't ever add dynamic lights
+ {"dust", 0, SURF_DUST, 0} // leave a dust trail when walking on this surface
+};
+
+
+/*
+===============
+ParseSurfaceParm
+
+surfaceparm <name>
+===============
+*/
+static void ParseSurfaceParm( char **text ) {
+ char *token;
+ int numInfoParms = ARRAY_LEN( infoParms );
+ int i;
+
+ token = COM_ParseExt( text, qfalse );
+ 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
+ break;
+ }
+ }
+}
+
+/*
+=================
+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 *token;
+ int s;
+
+ s = 0;
+
+ token = COM_ParseExt( text, qtrue );
+ if ( token[0] != '{' )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: expecting '{', found '%s' instead in shader '%s'\n", token, shader.name );
+ return qfalse;
+ }
+
+ 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 ) {
+ 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 ) ) {
+ SkipRestOfLine( text );
+ continue;
+ }
+ // sun parms
+ else if ( !Q_stricmp( token, "q3map_sun" ) ) {
+ float a, b;
+
+ token = COM_ParseExt( text, qfalse );
+ tr.sunLight[0] = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ tr.sunLight[1] = atof( token );
+ token = COM_ParseExt( text, qfalse );
+ tr.sunLight[2] = atof( token );
+
+ VectorNormalize( tr.sunLight );
+
+ token = COM_ParseExt( text, qfalse );
+ a = atof( token );
+ VectorScale( tr.sunLight, a, tr.sunLight);
+
+ token = COM_ParseExt( text, qfalse );
+ a = atof( token );
+ a = a / 180 * M_PI;
+
+ token = COM_ParseExt( text, qfalse );
+ 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 );
+ }
+ else if ( !Q_stricmp( token, "deformVertexes" ) ) {
+ ParseDeform( text );
+ continue;
+ }
+ else if ( !Q_stricmp( token, "tesssize" ) ) {
+ SkipRestOfLine( text );
+ continue;
+ }
+ else if ( !Q_stricmp( token, "clampTime" ) ) {
+ token = COM_ParseExt( text, qfalse );
+ if (token[0]) {
+ shader.clampTime = atof(token);
+ }
+ }
+ // skip stuff that only the q3map needs
+ else if ( !Q_stricmpn( token, "q3map", 5 ) ) {
+ SkipRestOfLine( text );
+ continue;
+ }
+ // skip stuff that only q3map or the server needs
+ else if ( !Q_stricmp( token, "surfaceParm" ) ) {
+ ParseSurfaceParm( text );
+ continue;
+ }
+ // no mip maps
+ else if ( !Q_stricmp( token, "nomipmaps" ) )
+ {
+ shader.noMipMaps = qtrue;
+ shader.noPicMip = qtrue;
+ continue;
+ }
+ // no picmip adjustment
+ else if ( !Q_stricmp( token, "nopicmip" ) )
+ {
+ shader.noPicMip = qtrue;
+ continue;
+ }
+ // polygonOffset
+ else if ( !Q_stricmp( token, "polygonOffset" ) )
+ {
+ shader.polygonOffset = 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" ) )
+ {
+ 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 );
+
+ // skip any old gradient directions
+ SkipRestOfLine( text );
+ continue;
+ }
+ // portal
+ else if ( !Q_stricmp(token, "portal") )
+ {
+ shader.sort = SS_PORTAL;
+ continue;
+ }
+ // skyparms <cloudheight> <outerbox> <innerbox>
+ else if ( !Q_stricmp( token, "skyparms" ) )
+ {
+ ParseSkyParms( text );
+ continue;
+ }
+ // light <value> determines flaring in q3map, not needed here
+ else if ( !Q_stricmp(token, "light") )
+ {
+ token = COM_ParseExt( text, qfalse );
+ continue;
+ }
+ // cull <face>
+ 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;
+ }
+ // sort
+ else if ( !Q_stricmp( token, "sort" ) )
+ {
+ ParseSort( text );
+ 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.isSky && !(shader.contentFlags & CONTENTS_FOG ) ) {
+ return qfalse;
+ }
+
+ shader.explicitlyDefined = qtrue;
+
+ return qtrue;
+}
+
+/*
+========================================================================================
+
+SHADER OPTIMIZATION AND FOGGING
+
+========================================================================================
+*/
+
+/*
+===================
+ComputeStageIteratorFunc
+
+See if we can use on of the simple fastpath stage functions,
+otherwise set to the generic stage function
+===================
+*/
+static void ComputeStageIteratorFunc( void )
+{
+ shader.optimalStageIteratorFunc = RB_StageIteratorGeneric;
+
+ //
+ // see if this should go into the sky path
+ //
+ if ( shader.isSky )
+ {
+ shader.optimalStageIteratorFunc = RB_StageIteratorSky;
+ goto done;
+ }
+
+ if ( r_ignoreFastPath->integer )
+ {
+ return;
+ }
+
+ //
+ // see if this can go into the vertex lit fast path
+ //
+ if ( shader.numUnfoggedPasses == 1 )
+ {
+ if ( stages[0].rgbGen == CGEN_LIGHTING_DIFFUSE )
+ {
+ if ( stages[0].alphaGen == AGEN_IDENTITY )
+ {
+ if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE )
+ {
+ if ( !shader.polygonOffset )
+ {
+ if ( !shader.multitextureEnv )
+ {
+ if ( !shader.numDeforms )
+ {
+ shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTexture;
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //
+ // see if this can go into an optimized LM, multitextured path
+ //
+ if ( shader.numUnfoggedPasses == 1 )
+ {
+ if ( ( stages[0].rgbGen == CGEN_IDENTITY ) && ( stages[0].alphaGen == AGEN_IDENTITY ) )
+ {
+ if ( stages[0].bundle[0].tcGen == TCGEN_TEXTURE &&
+ stages[0].bundle[1].tcGen == TCGEN_LIGHTMAP )
+ {
+ if ( !shader.polygonOffset )
+ {
+ if ( !shader.numDeforms )
+ {
+ if ( shader.multitextureEnv )
+ {
+ shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitexture;
+ goto done;
+ }
+ }
+ }
+ }
+ }
+ }
+
+done:
+ return;
+}
+
+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
+
+Attempt to combine two stages into a single multitexture stage
+FIXME: I think modulated add + modulated add collapses incorrectly
+=================
+*/
+static qboolean CollapseMultitexture( void ) {
+ int abits, bbits;
+ int i;
+ textureBundle_t tmpBundle;
+
+ if ( !qglActiveTextureARB ) {
+ return qfalse;
+ }
+
+ // make sure both stages are active
+ if ( !stages[0].active || !stages[1].active ) {
+ return qfalse;
+ }
+
+ // on voodoo2, don't combine different tmus
+ if ( glConfig.driverType == GLDRV_VOODOO ) {
+ if ( stages[0].bundle[0].image[0]->TMU ==
+ stages[1].bundle[0].image[0]->TMU ) {
+ return qfalse;
+ }
+ }
+
+ abits = stages[0].stateBits;
+ bbits = stages[1].stateBits;
+
+ // make sure that both stages have identical state other than blend modes
+ if ( ( abits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) !=
+ ( bbits & ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE ) ) ) {
+ return qfalse;
+ }
+
+ abits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+ bbits &= ( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+
+ // search for a valid multitexture blend function
+ for ( i = 0; collapse[i].blendA != -1 ; i++ ) {
+ if ( abits == collapse[i].blendA
+ && bbits == collapse[i].blendB ) {
+ break;
+ }
+ }
+
+ // nothing found
+ if ( collapse[i].blendA == -1 ) {
+ return qfalse;
+ }
+
+ // GL_ADD is a separate extension
+ if ( collapse[i].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable ) {
+ return qfalse;
+ }
+
+ // make sure waveforms have identical parameters
+ if ( ( stages[0].rgbGen != stages[1].rgbGen ) ||
+ ( stages[0].alphaGen != stages[1].alphaGen ) ) {
+ return qfalse;
+ }
+
+ // an add collapse can only have identity colors
+ if ( collapse[i].multitextureEnv == GL_ADD && stages[0].rgbGen != CGEN_IDENTITY ) {
+ return qfalse;
+ }
+
+ if ( stages[0].rgbGen == CGEN_WAVEFORM )
+ {
+ if ( memcmp( &stages[0].rgbWave,
+ &stages[1].rgbWave,
+ sizeof( stages[0].rgbWave ) ) )
+ {
+ return qfalse;
+ }
+ }
+ if ( stages[0].alphaGen == AGEN_WAVEFORM )
+ {
+ if ( memcmp( &stages[0].alphaWave,
+ &stages[1].alphaWave,
+ sizeof( stages[0].alphaWave ) ) )
+ {
+ return qfalse;
+ }
+ }
+
+
+ // make sure that lightmaps are in bundle 1 for 3dfx
+ if ( stages[0].bundle[0].isLightmap )
+ {
+ tmpBundle = stages[0].bundle[0];
+ stages[0].bundle[0] = stages[1].bundle[0];
+ stages[0].bundle[1] = tmpBundle;
+ }
+ else
+ {
+ stages[0].bundle[1] = stages[1].bundle[0];
+ }
+
+ // set the new blend state bits
+ shader.multitextureEnv = collapse[i].multitextureEnv;
+ stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+ stages[0].stateBits |= collapse[i].multitextureBlend;
+
+ //
+ // move down subsequent shaders
+ //
+ memmove( &stages[1], &stages[2], sizeof( stages[0] ) * ( MAX_SHADER_STAGES - 2 ) );
+ Com_Memset( &stages[MAX_SHADER_STAGES-1], 0, sizeof( stages[0] ) );
+
+ return qtrue;
+}
+
+/*
+=============
+
+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->commands;
+
+ if( cmdList ) {
+ const void *curCmd = cmdList->cmds;
+
+ while ( 1 ) {
+ curCmd = PADP(curCmd, sizeof(void *));
+
+ 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_SURFS:
+ {
+ int i;
+ drawSurf_t *drawSurf;
+ shader_t *shader;
+ int fogNum;
+ int entityNum;
+ int dlightMap;
+ int sortedIndex;
+ const drawSurfsCommand_t *ds_cmd = (const drawSurfsCommand_t *)curCmd;
+
+ for( i = 0, drawSurf = ds_cmd->drawSurfs; i < ds_cmd->numDrawSurfs; i++, drawSurf++ ) {
+ R_DecomposeSort( drawSurf->sort, &entityNum, &shader, &fogNum, &dlightMap );
+ sortedIndex = (( drawSurf->sort >> QSORT_SHADERNUM_SHIFT ) & (MAX_SHADERS-1));
+ if( sortedIndex >= newShader ) {
+ sortedIndex++;
+ drawSurf->sort = (sortedIndex << QSORT_SHADERNUM_SHIFT) | entityNum | ( fogNum << QSORT_FOGNUM_SHIFT ) | (int)dlightMap;
+ }
+ }
+ curCmd = (const void *)(ds_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 relative 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->numUnfoggedPasses ; 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 < NUM_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 = hashTable[hash];
+ hashTable[hash] = newShader;
+
+ return newShader;
+}
+
+/*
+=================
+VertexLightingCollapse
+
+If vertex lighting is enabled, only render a single
+pass, trying to guess which is the correct one to best aproximate
+what it is supposed to look like.
+=================
+*/
+static void VertexLightingCollapse( void ) {
+ int stage;
+ shaderStage_t *bestStage;
+ int bestImageRank;
+ int rank;
+
+ // if we aren't opaque, just use the first pass
+ if ( shader.sort == SS_OPAQUE ) {
+
+ // pick the best texture for the single pass
+ bestStage = &stages[0];
+ bestImageRank = -999999;
+
+ for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) {
+ shaderStage_t *pStage = &stages[stage];
+
+ if ( !pStage->active ) {
+ break;
+ }
+ rank = 0;
+
+ if ( pStage->bundle[0].isLightmap ) {
+ rank -= 100;
+ }
+ if ( pStage->bundle[0].tcGen != TCGEN_TEXTURE ) {
+ rank -= 5;
+ }
+ if ( pStage->bundle[0].numTexMods ) {
+ rank -= 5;
+ }
+ if ( pStage->rgbGen != CGEN_IDENTITY && pStage->rgbGen != CGEN_IDENTITY_LIGHTING ) {
+ rank -= 3;
+ }
+
+ if ( rank > bestImageRank ) {
+ bestImageRank = rank;
+ bestStage = pStage;
+ }
+ }
+
+ stages[0].bundle[0] = bestStage->bundle[0];
+ stages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
+ stages[0].stateBits |= GLS_DEPTHMASK_TRUE;
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+ } else {
+ stages[0].rgbGen = CGEN_EXACT_VERTEX;
+ }
+ stages[0].alphaGen = AGEN_SKIP;
+ } else {
+ // don't use a lightmap (tesla coils)
+ if ( stages[0].bundle[0].isLightmap ) {
+ stages[0] = stages[1];
+ }
+
+ // if we were in a cross-fade cgen, hack it to normal
+ if ( stages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || stages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) {
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_SAWTOOTH )
+ && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) {
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ if ( ( stages[0].rgbGen == CGEN_WAVEFORM && stages[0].rgbWave.func == GF_INVERSE_SAWTOOTH )
+ && ( stages[1].rgbGen == CGEN_WAVEFORM && stages[1].rgbWave.func == GF_SAWTOOTH ) ) {
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ }
+ }
+
+ for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) {
+ shaderStage_t *pStage = &stages[stage];
+
+ if ( !pStage->active ) {
+ break;
+ }
+
+ Com_Memset( pStage, 0, sizeof( *pStage ) );
+ }
+}
+
+/*
+=========================
+FinishShader
+
+Returns a freshly allocated shader with all the needed info
+from the current global working shader
+=========================
+*/
+static shader_t *FinishShader( void ) {
+ int stage;
+ qboolean hasLightmapStage;
+ qboolean vertexLightmap;
+
+ hasLightmapStage = qfalse;
+ vertexLightmap = qfalse;
+
+ //
+ // set sky stuff appropriate
+ //
+ if ( shader.isSky ) {
+ shader.sort = SS_ENVIRONMENT;
+ }
+
+ //
+ // set polygon offset
+ //
+ if ( shader.polygonOffset && !shader.sort ) {
+ shader.sort = SS_DECAL;
+ }
+
+ //
+ // set appropriate stage information
+ //
+ for ( stage = 0; stage < MAX_SHADER_STAGES; ) {
+ shaderStage_t *pStage = &stages[stage];
+
+ if ( !pStage->active ) {
+ break;
+ }
+
+ // check for a missing texture
+ if ( !pStage->bundle[0].image[0] ) {
+ ri.Printf( PRINT_WARNING, "Shader %s has a stage with no image\n", shader.name );
+ pStage->active = qfalse;
+ stage++;
+ continue;
+ }
+
+ //
+ // ditch this stage if it's detail and detail textures are disabled
+ //
+ if ( pStage->isDetail && !r_detailTextures->integer )
+ {
+ int index;
+
+ for(index = stage + 1; index < MAX_SHADER_STAGES; index++)
+ {
+ if(!stages[index].active)
+ break;
+ }
+
+ if(index < MAX_SHADER_STAGES)
+ memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage));
+ else
+ {
+ if(stage + 1 < MAX_SHADER_STAGES)
+ memmove(pStage, pStage + 1, sizeof(*pStage) * (index - stage - 1));
+
+ Com_Memset(&stages[index - 1], 0, sizeof(*stages));
+ }
+
+ continue;
+ }
+
+ //
+ // default texture coordinate generation
+ //
+ if ( pStage->bundle[0].isLightmap ) {
+ if ( pStage->bundle[0].tcGen == TCGEN_BAD ) {
+ pStage->bundle[0].tcGen = TCGEN_LIGHTMAP;
+ }
+ hasLightmapStage = qtrue;
+ } else {
+ if ( pStage->bundle[0].tcGen == TCGEN_BAD ) {
+ pStage->bundle[0].tcGen = TCGEN_TEXTURE;
+ }
+ }
+
+
+ // not a true lightmap but we want to leave existing
+ // behaviour in place and not print out a warning
+ //if (pStage->rgbGen == CGEN_VERTEX) {
+ // vertexLightmap = qtrue;
+ //}
+
+
+
+ //
+ // 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;
+ }
+ }
+ }
+
+ 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 ) {
+ shader.sort = SS_OPAQUE;
+ }
+
+ //
+ // if we are in r_vertexLight mode, never use a lightmap texture
+ //
+ if ( stage > 1 && ( (r_vertexLight->integer && !r_uiFullScreen->integer) || glConfig.hardwareType == GLHW_PERMEDIA2 ) ) {
+ VertexLightingCollapse();
+ stage = 1;
+ hasLightmapStage = qfalse;
+ }
+
+ //
+ // look for multitexture potential
+ //
+ if ( stage > 1 && CollapseMultitexture() ) {
+ stage--;
+ }
+
+ if ( shader.lightmapIndex >= 0 && !hasLightmapStage ) {
+ if (vertexLightmap) {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has VERTEX forced lightmap!\n", shader.name );
+ } else {
+ ri.Printf( PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name );
+ shader.lightmapIndex = LIGHTMAP_NONE;
+ }
+ }
+
+
+ //
+ // compute number of passes
+ //
+ shader.numUnfoggedPasses = stage;
+
+ // fogonly shaders don't have any normal passes
+ if (stage == 0 && !shader.isSky)
+ shader.sort = SS_FOG;
+
+ // determine which stage iterator function is appropriate
+ ComputeStageIteratorFunc();
+
+ return GeneratePermanentShader();
+}
+
+//========================================================================================
+
+/*
+====================
+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);
+
+ if(shaderTextHashTable[hash])
+ {
+ for (i = 0; shaderTextHashTable[hash][i]; i++)
+ {
+ p = shaderTextHashTable[hash][i];
+ token = COM_ParseExt(&p, qtrue);
+
+ if(!Q_stricmp(token, 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 ) ) {
+ return p;
+ }
+ else {
+ // skip the definition
+ 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) ) {
+ return tr.defaultShader;
+ }
+
+ COM_StripExtension(name, strippedName, sizeof(strippedName));
+
+ hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+
+ //
+ // see if the shader is already loaded
+ //
+ for (sh=hashTable[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 lightmapIndex == LIGHTMAP_NONE, 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 lightmapIndex == LIGHTMAP_NONE, then the image will have
+dynamic diffuse lighting applied to it, as apropriate for most
+entity skin surfaces.
+
+If lightmapIndex == LIGHTMAP_2D, then the image will be used
+for 2D rendering unless an explicit shader is found
+
+If lightmapIndex == LIGHTMAP_BY_VERTEX, then the image will use
+the vertex rgba modulate values, as apropriate for misc_model
+pre-lit surfaces.
+
+Other lightmapIndex values will have a lightmap stage created
+and src*dest blending applied with the texture, as apropriate for
+most world construction surfaces.
+
+===============
+*/
+shader_t *R_FindShader( const char *name, int lightmapIndex, qboolean mipRawImage ) {
+ char strippedName[MAX_QPATH];
+ int i, hash;
+ char *shaderText;
+ image_t *image;
+ shader_t *sh;
+
+ if ( name[0] == 0 ) {
+ return tr.defaultShader;
+ }
+
+ // use (fullbright) vertex lighting if the bsp file doesn't have
+ // lightmaps
+ if ( lightmapIndex >= 0 && lightmapIndex >= tr.numLightmaps ) {
+ lightmapIndex = LIGHTMAP_BY_VERTEX;
+ } else if ( lightmapIndex < LIGHTMAP_2D ) {
+ // negative lightmap indexes cause stray pointers (think tr.lightmaps[lightmapIndex])
+ ri.Printf( PRINT_WARNING, "WARNING: shader '%s' has invalid lightmap index of %d\n", name, lightmapIndex );
+ lightmapIndex = LIGHTMAP_BY_VERTEX;
+ }
+
+ COM_StripExtension(name, strippedName, sizeof(strippedName));
+
+ hash = generateHashValue(strippedName, FILE_HASH_SIZE);
+
+ //
+ // see if the shader is already loaded
+ //
+ for (sh = hashTable[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 lightmapIndex == LIGHTMAP_NONE, 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->lightmapIndex == lightmapIndex || sh->defaultShader) &&
+ !Q_stricmp(sh->name, strippedName)) {
+ // match found
+ return sh;
+ }
+ }
+
+ // 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.lightmapIndex = lightmapIndex;
+ for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
+ stages[i].bundle[0].texMods = texMods[i];
+ }
+
+ // FIXME: set these "need" values apropriately
+ shader.needsNormal = qtrue;
+ shader.needsST1 = qtrue;
+ shader.needsST2 = qtrue;
+ shader.needsColor = qtrue;
+
+ //
+ // 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, "*SHADER* %s\n", name );
+ }
+
+ if ( !ParseShader( &shaderText ) ) {
+ // had errors, so use default shader
+ shader.defaultShader = qtrue;
+ }
+ sh = FinishShader();
+ return sh;
+ }
+
+
+ //
+ // if not defined in the in-memory shader descriptions,
+ // look for a single supported image file
+ //
+ image = R_FindImageFile( name, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP_TO_EDGE );
+ if ( !image ) {
+ ri.Printf( PRINT_DEVELOPER, "Couldn't find image file for shader %s\n", name );
+ shader.defaultShader = qtrue;
+ return FinishShader();
+ }
+
+ //
+ // create the default shading commands
+ //
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ // dynamic colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
+ // explicit colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_EXACT_VERTEX;
+ stages[0].alphaGen = AGEN_SKIP;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_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;
+ } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
+ // fullbright level
+ stages[0].bundle[0].image[0] = tr.whiteImage;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ } else {
+ // two pass lightmap
+ stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+ stages[0].bundle[0].isLightmap = qtrue;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation
+ // for identitylight
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ }
+
+ return FinishShader();
+}
+
+
+qhandle_t RE_RegisterShaderFromImage(const char *name, int lightmapIndex, image_t *image, qboolean mipRawImage) {
+ int i, hash;
+ shader_t *sh;
+
+ hash = generateHashValue(name, FILE_HASH_SIZE);
+
+ // probably not necessary since this function
+ // only gets called from tr_font.c with lightmapIndex == LIGHTMAP_2D
+ // but better safe than sorry.
+ if ( lightmapIndex >= tr.numLightmaps ) {
+ lightmapIndex = LIGHTMAP_WHITEIMAGE;
+ }
+
+ //
+ // see if the shader is already loaded
+ //
+ for (sh=hashTable[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 lightmapIndex == LIGHTMAP_NONE, 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->lightmapIndex == lightmapIndex || sh->defaultShader) &&
+ // index by name
+ !Q_stricmp(sh->name, name)) {
+ // match found
+ return sh->index;
+ }
+ }
+
+ // 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.lightmapIndex = lightmapIndex;
+ for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
+ stages[i].bundle[0].texMods = texMods[i];
+ }
+
+ // FIXME: set these "need" values apropriately
+ shader.needsNormal = qtrue;
+ shader.needsST1 = qtrue;
+ shader.needsST2 = qtrue;
+ shader.needsColor = qtrue;
+
+ //
+ // create the default shading commands
+ //
+ if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
+ // dynamic colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
+ // explicit colors at vertexes
+ stages[0].bundle[0].image[0] = image;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_EXACT_VERTEX;
+ stages[0].alphaGen = AGEN_SKIP;
+ stages[0].stateBits = GLS_DEFAULT;
+ } else if ( shader.lightmapIndex == LIGHTMAP_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;
+ } else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
+ // fullbright level
+ stages[0].bundle[0].image[0] = tr.whiteImage;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ } else {
+ // two pass lightmap
+ stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
+ stages[0].bundle[0].isLightmap = qtrue;
+ stages[0].active = qtrue;
+ stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation
+ // for identitylight
+ stages[0].stateBits = GLS_DEFAULT;
+
+ stages[1].bundle[0].image[0] = image;
+ stages[1].active = qtrue;
+ stages[1].rgbGen = CGEN_IDENTITY;
+ stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
+ }
+
+ 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_RegisterShaderLightMap( const char *name, int lightmapIndex ) {
+ shader_t *sh;
+
+ if ( strlen( name ) >= MAX_QPATH ) {
+ ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sh = R_FindShader( name, lightmapIndex, 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_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 ) {
+ ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sh = R_FindShader( name, LIGHTMAP_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 ) {
+ ri.Printf( PRINT_ALL, "Shader name exceeds MAX_QPATH\n" );
+ return 0;
+ }
+
+ sh = R_FindShader( name, LIGHTMAP_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;
+}
+
+/*
+====================
+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 );
+ 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;
+
+ ri.Printf (PRINT_ALL, "-----------------------\n");
+
+ count = 0;
+ for ( i = 0 ; i < tr.numShaders ; i++ ) {
+ if ( ri.Cmd_Argc() > 1 ) {
+ shader = tr.sortedShaders[i];
+ } else {
+ shader = tr.shaders[i];
+ }
+
+ ri.Printf( PRINT_ALL, "%i ", shader->numUnfoggedPasses );
+
+ if (shader->lightmapIndex >= 0 ) {
+ ri.Printf (PRINT_ALL, "L ");
+ } else {
+ ri.Printf (PRINT_ALL, " ");
+ }
+ if ( shader->multitextureEnv == GL_ADD ) {
+ ri.Printf( PRINT_ALL, "MT(a) " );
+ } else if ( shader->multitextureEnv == GL_MODULATE ) {
+ ri.Printf( PRINT_ALL, "MT(m) " );
+ } else if ( shader->multitextureEnv == GL_DECAL ) {
+ ri.Printf( PRINT_ALL, "MT(d) " );
+ } else {
+ ri.Printf( PRINT_ALL, " " );
+ }
+ if ( shader->explicitlyDefined ) {
+ ri.Printf( PRINT_ALL, "E " );
+ } else {
+ ri.Printf( PRINT_ALL, " " );
+ }
+
+ if ( shader->optimalStageIteratorFunc == RB_StageIteratorGeneric ) {
+ ri.Printf( PRINT_ALL, "gen " );
+ } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorSky ) {
+ ri.Printf( PRINT_ALL, "sky " );
+ } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitexture ) {
+ ri.Printf( PRINT_ALL, "lmmt" );
+ } else if ( shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTexture ) {
+ ri.Printf( PRINT_ALL, "vlt " );
+ } 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");
+}
+
+/*
+====================
+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 numShaderFiles;
+ int i;
+ char *oldp, *token, *hashMem, *textEnd;
+ int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;
+
+ long sum = 0, summand;
+ // scan for shader files
+ shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaderFiles );
+
+ if ( !shaderFiles || !numShaderFiles )
+ {
+ ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" );
+ return;
+ }
+
+ if ( numShaderFiles > MAX_SHADER_FILES ) {
+ numShaderFiles = MAX_SHADER_FILES;
+ }
+
+ // load and parse shader files
+ for ( i = 0; i < numShaderFiles; i++ )
+ {
+ char filename[MAX_QPATH];
+
+ Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] );
+ ri.Printf( PRINT_DEVELOPER, "...loading '%s'\n", filename );
+ summand = ri.FS_ReadFile( filename, (void **)&buffers[i] );
+
+ if ( !buffers[i] )
+ ri.Error( ERR_DROP, "Couldn't load %s", filename );
+
+ // Do a simple check on the shader structure in that file to make sure one bad shader file cannot fuck up all other shaders.
+ p = buffers[i];
+ while(1)
+ {
+ token = COM_ParseExt(&p, qtrue);
+
+ if(!*token)
+ break;
+
+ oldp = p;
+
+ token = COM_ParseExt(&p, qtrue);
+ if(token[0] != '{' && token[1] != '\0')
+ {
+ ri.Printf(PRINT_WARNING, "WARNING: Bad shader file %s has incorrect syntax.\n", filename);
+ ri.FS_FreeFile(buffers[i]);
+ buffers[i] = NULL;
+ break;
+ }
+
+ SkipBracedSection(&oldp);
+ p = oldp;
+ }
+
+
+ if (buffers[i])
+ sum += summand;
+ }
+
+ // build single large buffer
+ s_shaderText = ri.Hunk_Alloc( sum + numShaderFiles*2, h_low );
+ s_shaderText[ 0 ] = '\0';
+ textEnd = s_shaderText;
+
+ // free in reverse order, so the temp files are all dumped
+ for ( i = numShaderFiles - 1; i >= 0 ; i-- )
+ {
+ if ( !buffers[i] )
+ continue;
+
+ strcat( textEnd, buffers[i] );
+ strcat( textEnd, "\n" );
+ textEnd += strlen( textEnd );
+ ri.FS_FreeFile( buffers[i] );
+ }
+
+ COM_Compress( s_shaderText );
+
+ // free up memory
+ ri.FS_FreeFileList( shaderFiles );
+
+ Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
+ size = 0;
+
+ p = s_shaderText;
+ // look for shader names
+ while ( 1 ) {
+ token = COM_ParseExt( &p, qtrue );
+ if ( token[0] == 0 ) {
+ break;
+ }
+
+ hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
+ shaderTextHashTableSizes[hash]++;
+ size++;
+ SkipBracedSection(&p);
+ }
+
+ 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));
+
+ p = s_shaderText;
+ // look for shader names
+ while ( 1 ) {
+ oldp = p;
+ token = COM_ParseExt( &p, qtrue );
+ if ( token[0] == 0 ) {
+ break;
+ }
+
+ hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
+ shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp;
+
+ SkipBracedSection(&p);
+ }
+
+ return;
+
+}
+
+
+/*
+====================
+CreateInternalShaders
+====================
+*/
+static void CreateInternalShaders( void ) {
+ tr.numShaders = 0;
+
+ // init the default shader
+ Com_Memset( &shader, 0, sizeof( shader ) );
+ Com_Memset( &stages, 0, sizeof( stages ) );
+
+ Q_strncpyz( shader.name, "<default>", sizeof( shader.name ) );
+
+ shader.lightmapIndex = LIGHTMAP_NONE;
+ stages[0].bundle[0].image[0] = tr.defaultImage;
+ stages[0].active = qtrue;
+ stages[0].stateBits = GLS_DEFAULT;
+ tr.defaultShader = FinishShader();
+
+ // shadow shader is just a marker
+ Q_strncpyz( shader.name, "<stencil shadow>", sizeof( shader.name ) );
+ shader.sort = SS_STENCIL_SHADOW;
+ tr.shadowShader = FinishShader();
+}
+
+static void CreateExternalShaders( void ) {
+ tr.projectionShadowShader = R_FindShader( "projectionShadow", LIGHTMAP_NONE, qtrue );
+ tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue );
+
+ // Hack to make fogging work correctly on flares. Fog colors are calculated
+ // in tr_flare.c already.
+ if(!tr.flareShader->defaultShader)
+ {
+ int index;
+
+ for(index = 0; index < tr.flareShader->numUnfoggedPasses; index++)
+ {
+ tr.flareShader->stages[index]->adjustColorsForFog = ACFF_NONE;
+ tr.flareShader->stages[index]->stateBits |= GLS_DEPTHTEST_DISABLE;
+ }
+ }
+
+ tr.sunShader = R_FindShader( "sun", LIGHTMAP_NONE, qtrue );
+}
+
+/*
+==================
+R_InitShaders
+==================
+*/
+void R_InitShaders( void ) {
+ ri.Printf( PRINT_ALL, "Initializing Shaders\n" );
+
+ Com_Memset(hashTable, 0, sizeof(hashTable));
+
+ CreateInternalShaders();
+
+ ScanAndLoadShaderFiles();
+
+ CreateExternalShaders();
+}