diff options
Diffstat (limited to 'code/renderer/tr_shader.c')
-rwxr-xr-x | code/renderer/tr_shader.c | 6026 |
1 files changed, 3013 insertions, 3013 deletions
diff --git a/code/renderer/tr_shader.c b/code/renderer/tr_shader.c index ecf3798..b554f34 100755 --- a/code/renderer/tr_shader.c +++ b/code/renderer/tr_shader.c @@ -1,3013 +1,3013 @@ -/*
-===========================================================================
-Copyright (C) 1999-2005 Id Software, Inc.
-
-This file is part of Quake III Arena source code.
-
-Quake III Arena 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.
-
-Quake III Arena 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 Foobar; 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];
-static qboolean deferLoad;
-
-#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
-================
-*/
-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 );
- 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'\n", 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 ) {
- 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 );
- 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 == CGEN_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 );
- 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 = sizeof(infoParms) / sizeof(infoParms[0]);
- 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 ( !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 == CGEN_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[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_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 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->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; stage++ ) {
- 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;
- continue;
- }
-
- //
- // ditch this stage if it's detail and detail textures are disabled
- //
- if ( pStage->isDetail && !r_detailTextures->integer ) {
- if ( stage < ( MAX_SHADER_STAGES - 1 ) ) {
- memmove( pStage, pStage + 1, sizeof( *pStage ) * ( MAX_SHADER_STAGES - stage - 1 ) );
- Com_Memset( pStage + 1, 0, sizeof( *pStage ) );
- }
- 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;
- }
- }
- }
- }
-
- // 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.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);
-
- 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) ) { // bk001205
- return tr.defaultShader;
- }
-
- COM_StripExtension( name, 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];
- char fileName[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;
- }
-
- COM_StripExtension( name, 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;
- }
- }
-
- // 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.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 TGA, BMP, or PCX
- //
- Q_strncpyz( fileName, name, sizeof( fileName ) );
- COM_DefaultExtension( fileName, sizeof( fileName ), ".tga" );
- image = R_FindImageFile( fileName, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP );
- if ( !image ) {
- ri.Printf( PRINT_DEVELOPER, "Couldn't find image 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);
-
- //
- // 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;
- }
- }
-
- // 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.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 ) {
- Com_Printf( "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 ) {
- Com_Printf( "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 ) {
- Com_Printf( "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 ); // 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;
-
- 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 numShaders;
- int i;
- char *oldp, *token, *hashMem;
- int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;
-
- long sum = 0;
- // scan for shader files
- shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaders );
-
- if ( !shaderFiles || !numShaders )
- {
- ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" );
- return;
- }
-
- if ( numShaders > MAX_SHADER_FILES ) {
- numShaders = MAX_SHADER_FILES;
- }
-
- // load and parse shader files
- for ( i = 0; i < numShaders; i++ )
- {
- char filename[MAX_QPATH];
-
- Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] );
- ri.Printf( PRINT_ALL, "...loading '%s'\n", filename );
- sum += ri.FS_ReadFile( filename, (void **)&buffers[i] );
- if ( !buffers[i] ) {
- ri.Error( ERR_DROP, "Couldn't load %s", filename );
- }
- }
-
- // build single large buffer
- s_shaderText = ri.Hunk_Alloc( sum + numShaders*2, h_low );
-
- // free in reverse order, so the temp files are all dumped
- for ( i = numShaders - 1; i >= 0 ; i-- ) {
- 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);
- }
-
- // free up memory
- ri.FS_FreeFileList( shaderFiles );
-
- Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
- size = 0;
- //
- for ( i = 0; i < numShaders; i++ ) {
- // pointer to the first shader file
- p = buffers[i];
- // look for label
- while ( 1 ) {
- token = COM_ParseExt( &p, qtrue );
- if ( token[0] == 0 ) {
- break;
- }
-
- hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
- shaderTextHashTableSizes[hash]++;
- size++;
- 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++ ) {
- // 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;
- }
-
- hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
- shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp;
-
- SkipBracedSection(&p);
- // if we passed the pointer to the next shader file
- if ( i < numShaders - 1 ) {
- if ( p > buffers[i+1] ) {
- break;
- }
- }
- }
- }
-
- 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 );
- 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));
-
- deferLoad = qfalse;
-
- CreateInternalShaders();
-
- ScanAndLoadShaderFiles();
-
- CreateExternalShaders();
-}
+/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena 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. + +Quake III Arena 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 Foobar; 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]; +static qboolean deferLoad; + +#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 +================ +*/ +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 ); + 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'\n", 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 ) { + 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 ); + 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 == CGEN_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 ); + 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 = sizeof(infoParms) / sizeof(infoParms[0]); + 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 ( !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 == CGEN_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[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_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 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->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; stage++ ) { + 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; + continue; + } + + // + // ditch this stage if it's detail and detail textures are disabled + // + if ( pStage->isDetail && !r_detailTextures->integer ) { + if ( stage < ( MAX_SHADER_STAGES - 1 ) ) { + memmove( pStage, pStage + 1, sizeof( *pStage ) * ( MAX_SHADER_STAGES - stage - 1 ) ); + Com_Memset( pStage + 1, 0, sizeof( *pStage ) ); + } + 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; + } + } + } + } + + // 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.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); + + 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) ) { // bk001205 + return tr.defaultShader; + } + + COM_StripExtension( name, 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]; + char fileName[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; + } + + COM_StripExtension( name, 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; + } + } + + // 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.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 TGA, BMP, or PCX + // + Q_strncpyz( fileName, name, sizeof( fileName ) ); + COM_DefaultExtension( fileName, sizeof( fileName ), ".tga" ); + image = R_FindImageFile( fileName, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP ); + if ( !image ) { + ri.Printf( PRINT_DEVELOPER, "Couldn't find image 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); + + // + // 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; + } + } + + // 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.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 ) { + Com_Printf( "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 ) { + Com_Printf( "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 ) { + Com_Printf( "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 ); // 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; + + 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 numShaders; + int i; + char *oldp, *token, *hashMem; + int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size; + + long sum = 0; + // scan for shader files + shaderFiles = ri.FS_ListFiles( "scripts", ".shader", &numShaders ); + + if ( !shaderFiles || !numShaders ) + { + ri.Printf( PRINT_WARNING, "WARNING: no shader files found\n" ); + return; + } + + if ( numShaders > MAX_SHADER_FILES ) { + numShaders = MAX_SHADER_FILES; + } + + // load and parse shader files + for ( i = 0; i < numShaders; i++ ) + { + char filename[MAX_QPATH]; + + Com_sprintf( filename, sizeof( filename ), "scripts/%s", shaderFiles[i] ); + ri.Printf( PRINT_ALL, "...loading '%s'\n", filename ); + sum += ri.FS_ReadFile( filename, (void **)&buffers[i] ); + if ( !buffers[i] ) { + ri.Error( ERR_DROP, "Couldn't load %s", filename ); + } + } + + // build single large buffer + s_shaderText = ri.Hunk_Alloc( sum + numShaders*2, h_low ); + + // free in reverse order, so the temp files are all dumped + for ( i = numShaders - 1; i >= 0 ; i-- ) { + 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); + } + + // free up memory + ri.FS_FreeFileList( shaderFiles ); + + Com_Memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes)); + size = 0; + // + for ( i = 0; i < numShaders; i++ ) { + // pointer to the first shader file + p = buffers[i]; + // look for label + while ( 1 ) { + token = COM_ParseExt( &p, qtrue ); + if ( token[0] == 0 ) { + break; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTableSizes[hash]++; + size++; + 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++ ) { + // 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; + } + + hash = generateHashValue(token, MAX_SHADERTEXT_HASH); + shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp; + + SkipBracedSection(&p); + // if we passed the pointer to the next shader file + if ( i < numShaders - 1 ) { + if ( p > buffers[i+1] ) { + break; + } + } + } + } + + 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 ); + 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)); + + deferLoad = qfalse; + + CreateInternalShaders(); + + ScanAndLoadShaderFiles(); + + CreateExternalShaders(); +} |