2023-05-08 19:53:53 +02:00
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
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
|
2024-12-01 15:26:01 +01:00
|
|
|
along with Foobar; if not, write to the Free Software
|
2023-05-08 19:53:53 +02:00
|
|
|
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
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
typedef struct shadertext_s {
|
|
|
|
char name[64];
|
|
|
|
char* text;
|
|
|
|
shader_t* shader;
|
|
|
|
struct shadertext_s* next;
|
|
|
|
} shadertext_t;
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
static char *s_shaderText;
|
|
|
|
|
|
|
|
// the shader is parsed into these global variables, then copied into
|
|
|
|
// dynamically allocated memory if it is valid.
|
2024-12-01 15:26:01 +01:00
|
|
|
static shaderStage_t unfoggedStages[MAX_SHADER_STAGES];
|
2023-05-08 19:53:53 +02:00
|
|
|
static shader_t shader;
|
|
|
|
static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
#define FILE_HASH_SIZE 8192
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
static qboolean shader_noPicMip;
|
|
|
|
static qboolean shader_noMipMaps;
|
|
|
|
static qboolean shader_force32bit;
|
|
|
|
static shadertext_t* currentShader = NULL;
|
|
|
|
static shadertext_t* hashTable[FILE_HASH_SIZE];
|
|
|
|
|
|
|
|
#define GL_CLAMP_TO_EDGE 0x812F
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
return a hash value for the filename
|
|
|
|
================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
static long generateHashValue( const char *fname ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
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));
|
2024-12-01 15:26:01 +01:00
|
|
|
hash &= (FILE_HASH_SIZE-1);
|
2023-05-08 19:53:53 +02:00
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
static shadertext_t* AddShaderTextToHash(const char* name, int hash)
|
|
|
|
{
|
|
|
|
shadertext_t* shader;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
shader = (shadertext_t*)ri.Malloc(sizeof(shadertext_t));
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
Com_Memset(shader, 0, sizeof(shadertext_t));
|
|
|
|
strncpy(shader->name, name, sizeof(shader->name));
|
|
|
|
shader->next = hashTable[hash];
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
hashTable[hash] = shader;
|
|
|
|
return shader;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
static shadertext_t* AllocShaderText(const char* name)
|
|
|
|
{
|
|
|
|
int hash;
|
|
|
|
|
|
|
|
hash = generateHashValue(name);
|
|
|
|
return AddShaderTextToHash(name, hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
FindShaderText
|
|
|
|
|
|
|
|
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 shadertext_t* FindShaderText(const char* name)
|
|
|
|
{
|
|
|
|
shadertext_t* shader;
|
|
|
|
int hash;
|
|
|
|
|
|
|
|
hash = generateHashValue(name);
|
|
|
|
shader = hashTable[hash];
|
|
|
|
if (!shader) {
|
|
|
|
return AddShaderTextToHash(name, hash);
|
|
|
|
}
|
|
|
|
|
|
|
|
while (Q_stricmp(shader->name, name))
|
|
|
|
{
|
|
|
|
shader = shader->next;
|
|
|
|
if (!shader) {
|
|
|
|
return AddShaderTextToHash(name, hash);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return shader;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
ParseVector
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
static qboolean ParseVector( char **text, int count, float *v ) {
|
|
|
|
char *token;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
void InitStaticShaders()
|
|
|
|
{
|
|
|
|
char* text;
|
|
|
|
const char* token;
|
|
|
|
char* buf;
|
|
|
|
char shadername[64];
|
|
|
|
|
|
|
|
if (ri.FS_ReadFile("scripts/static_shaders.txt", (void**)&buf) == -1)
|
|
|
|
{
|
|
|
|
ri.Printf(3, "Couldn't find static shaders file: scripts/staticshaders.txt\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
text = buf;
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(&text, 1);
|
|
|
|
if (!*token) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
strncpy(shadername, token, 0x40u);
|
|
|
|
if (!R_FindShader(shadername, -1, 1, 1, 1, 1)) {
|
|
|
|
ri.Printf(3, "InitStaticShaders: Couldn't find shader: %s\n", shadername);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ri.FS_FreeFile(buf);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( funcname, "LT_FOLIAGE1" ) )
|
|
|
|
{
|
|
|
|
return GLS_ATEST_LT_FOLIAGE1;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( funcname, "GE_FOLIAGE1" ) )
|
|
|
|
{
|
|
|
|
return GLS_ATEST_GE_FOLIAGE1;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( funcname, "LT_FOLIAGE2" ) )
|
|
|
|
{
|
|
|
|
return GLS_ATEST_LT_FOLIAGE2;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( funcname, "GE_FOLIAGE2" ) )
|
|
|
|
{
|
|
|
|
return GLS_ATEST_GE_FOLIAGE2;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
===================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
static void ParseTexMod( char *_text, shaderStage_t *stage, int cntBundle )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
const char *token;
|
|
|
|
char **text = &_text;
|
|
|
|
texModInfo_t *tmi;
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( stage->bundle[cntBundle].numTexMods == TR_MAX_TEXMODS ) {
|
|
|
|
ri.Error( ERR_DROP, "ERROR: too many tcMod unfoggedStages in shader '%s'\n", shader.name );
|
2023-05-08 19:53:53 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
tmi = &stage->bundle[cntBundle].texMods[stage->bundle[cntBundle].numTexMods];
|
|
|
|
stage->bundle[cntBundle].numTexMods++;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
shader.flags |= 2;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
//
|
|
|
|
// 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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(token, "fromEntity")) {
|
|
|
|
tmi->scroll[0] = 1234567;
|
|
|
|
} else {
|
|
|
|
tmi->scroll[0] = atof(token);
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(token, "fromEntity")) {
|
|
|
|
tmi->scroll[1] = 1234567;
|
|
|
|
} else {
|
|
|
|
tmi->scroll[1] = atof(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
if (atof(token) < 0) {
|
|
|
|
tmi->scroll[0] -= (rand() % (int)(atof(token) * 1000.0)) / 1000.0;
|
|
|
|
} else if (atof(token) > 0) {
|
|
|
|
tmi->scroll[0] += (rand() % (int)(atof(token) * 1000.0)) / 1000.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
if (atof(token) < 0) {
|
|
|
|
tmi->scroll[1] -= (rand() % (int)(atof(token) * 1000.0)) / 1000.0;
|
|
|
|
} else if (atof(token) > 0) {
|
|
|
|
tmi->scroll[1] += (rand() % (int)(atof(token) * 1000.0)) / 1000.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
tmi->type = TMOD_SCROLL;
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
shader.flags |= 2;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
//
|
|
|
|
// stretch
|
|
|
|
//
|
|
|
|
else if ( !Q_stricmp( token, "stretch" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ParseWaveForm(text, &tmi->wave);
|
|
|
|
tmi->type = TMOD_STRETCH;
|
|
|
|
shader.flags |= 2;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "wavetrans" ) )
|
|
|
|
{
|
|
|
|
ParseWaveForm(text, &tmi->wave);
|
|
|
|
tmi->type = TMOD_WAVETRANS;
|
|
|
|
shader.flags |= 2;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "wavetrant" ) )
|
|
|
|
{
|
|
|
|
ParseWaveForm(text, &tmi->wave);
|
|
|
|
tmi->type = TMOD_WAVETRANT;
|
|
|
|
shader.flags |= 2;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "bulge" ) )
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing tcMod bulge parms in shader '%s'\n", shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
tmi->wave.base = atof(token);
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing tcMod bulge parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
tmi->wave.amplitude = atof(token);
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing tcMod bulge parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->wave.frequency = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing tcMod bulge parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->wave.phase = atof(token);
|
|
|
|
|
|
|
|
tmi->type = TMOD_BULGETRANS;
|
|
|
|
shader.flags |= 2;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
//
|
|
|
|
// 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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
if (!Q_stricmp(token, "fromEntity")) {
|
|
|
|
tmi->rotateSpeed = 1234567;
|
|
|
|
} else {
|
|
|
|
tmi->rotateSpeed = atof(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
if (!Q_stricmp(token, "fromEntity")) {
|
|
|
|
tmi->rotateStart = 1234567;
|
|
|
|
} else {
|
|
|
|
tmi->rotateStart = atof(token);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
tmi->rotateStart = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
tmi->rotateCoef = atof(token);
|
|
|
|
} else {
|
|
|
|
tmi->rotateCoef = 1;
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
tmi->type = TMOD_ROTATE;
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.flags |= 2;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "offset")) {
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing offset parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Q_stricmp(token, "fromEntity")) {
|
|
|
|
tmi->scroll[0] = 1234567;
|
|
|
|
} else {
|
|
|
|
tmi->scroll[0] = atof(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing offset parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Q_stricmp(token, "fromEntity")) {
|
|
|
|
tmi->scroll[1] = 1234567;
|
|
|
|
} else {
|
|
|
|
tmi->scroll[1] = atof(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
if (atof(token) < 0) {
|
|
|
|
tmi->scroll[0] -= (rand() % (int)(atof(token) * 1000.0 + 1.0)) / 1000.0;
|
|
|
|
} else if (atof(token) > 0) {
|
|
|
|
tmi->scroll[0] += (rand() % (int)(atof(token) * 1000.0 + 1.0)) / 1000.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
if (atof(token) < 0) {
|
|
|
|
tmi->scroll[1] -= (rand() % (int)(atof(token) * 1000.0 + 1.0)) / 1000.0;
|
|
|
|
} else if (atof(token) > 0) {
|
|
|
|
tmi->scroll[1] += (rand() % (int)(atof(token) * 1000.0 + 1.0)) / 1000.0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->type = TMOD_OFFSET;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
//
|
|
|
|
// entityTranslate
|
|
|
|
//
|
|
|
|
else if ( !Q_stricmp( token, "entityTranslate" ) )
|
|
|
|
{
|
|
|
|
tmi->type = TMOD_ENTITY_TRANSLATE;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "parallax" ) )
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing rate parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->rate[0] = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing rate parms in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->rate[1] = atof(token);
|
|
|
|
tmi->type = TMOD_PARALLAX;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "macro" ) )
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing scale parms in shader '%s' for macro definition\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->scale[0] = 1.0 / atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing scale parms in shader '%s' for macro definition\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmi->scale[1] = 1.0 / atof(token);
|
|
|
|
|
|
|
|
tmi->type = TMOD_MACRO;
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
else
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: unknown tcMod '%s' in shader '%s'\n", token, shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===================
|
|
|
|
ParseStage
|
|
|
|
===================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
static qboolean ParseStage(shaderStage_t* stage, char** text, qboolean picmip)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
char* token;
|
|
|
|
int depthMaskBits = GLS_DEPTHMASK_TRUE, blendSrcBits = 0, blendDstBits = 0, atestBits = 0, depthFuncBits = 0, colorBits = 0;
|
2023-05-08 19:53:53 +02:00
|
|
|
qboolean depthMaskExplicit = qfalse;
|
2024-12-01 15:26:01 +01:00
|
|
|
int cntBundle = 0;
|
|
|
|
int depthTestBits = 0;
|
|
|
|
int fogBits = 0;
|
|
|
|
qboolean shouldProcess = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
stage->active = qtrue;
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->noMipMaps = shader_noMipMaps;
|
|
|
|
stage->noPicMip = shader_noPicMip;
|
|
|
|
stage->force32bit = shader_force32bit;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
while (1)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
token = COM_ParseExt(text, qtrue);
|
|
|
|
if (!token[0])
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: no matching '}' found\n");
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (token[0] == '}')
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
// no picmip adjustment
|
|
|
|
else if (!Q_stricmp(token, "nomipmaps"))
|
|
|
|
{
|
|
|
|
stage->noMipMaps = qtrue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// no picmip adjustment
|
|
|
|
else if (!Q_stricmp(token, "nopicmip"))
|
|
|
|
{
|
|
|
|
stage->noPicMip = qtrue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// no picmip adjustment
|
|
|
|
else if (!Q_stricmp(token, "nofog"))
|
|
|
|
{
|
|
|
|
fogBits = GLS_FOG;
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
//
|
|
|
|
// map <name>
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "map"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0])
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'map' keyword in shader '%s'\n", shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(token, "$whiteimage"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].image[0] = tr.whiteImage;
|
2023-05-08 19:53:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "$lightmap"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].isLightmap = qtrue;
|
|
|
|
if (shader.lightmapIndex < 0) {
|
|
|
|
stage->bundle[cntBundle].image[0] = tr.whiteImage;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
stage->bundle[cntBundle].image[0] = tr.lightmaps[shader.lightmapIndex];
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "$bumpmap"))
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0])
|
|
|
|
{
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'bumpmap' keyword in shader '%s'\n", shader.name);
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
// This is not implemented in mohaa
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
else
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].image[0] = R_FindImageFile(token, !stage->noMipMaps, (!stage->noPicMip ? picmip : 0), stage->force32bit, GL_REPEAT, GL_REPEAT);
|
|
|
|
if (!stage->bundle[cntBundle].image[0])
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// clampmap <name>
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmpn(token, "clampmap", 8))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
int clampx, clampy;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!token[8]) {
|
|
|
|
if (!haveClampToEdge) {
|
|
|
|
clampx = GL_CLAMP;
|
|
|
|
clampy = GL_CLAMP;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clampx = GL_CLAMP_TO_EDGE;
|
|
|
|
clampy = GL_CLAMP_TO_EDGE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (token[8] == 'x')
|
|
|
|
{
|
|
|
|
if (!haveClampToEdge) {
|
|
|
|
clampx = GL_CLAMP;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clampx = GL_CLAMP_TO_EDGE;
|
|
|
|
}
|
|
|
|
clampy = GL_CLAMP;
|
|
|
|
}
|
|
|
|
else if (token[8] == 'y')
|
|
|
|
{
|
|
|
|
clampx = GL_CLAMP;
|
|
|
|
if (!haveClampToEdge) {
|
|
|
|
clampy = GL_CLAMP;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
clampy = GL_CLAMP_TO_EDGE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: Converting unknown clampmap type to clampmap\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0])
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'clampmap' keyword in shader '%s'\n", shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].image[0] = R_FindImageFile(token, !stage->noMipMaps, (!stage->noPicMip ? picmip : 0), stage->force32bit, clampx, clampy);
|
|
|
|
if (!stage->bundle[cntBundle].image[0])
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// animMap <frequency> <image1> .... <imageN>
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "animMap" ) || !Q_stricmp(token, "animMapOnce") || !Q_stricmp(token, "animMapPhase"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
qboolean phased;
|
|
|
|
|
|
|
|
shader.flags |= 3;
|
|
|
|
|
|
|
|
if (!Q_stricmp(token, "animMapOnce")) {
|
|
|
|
stage->bundle[cntBundle].flags |= BUNDLE_ANIMATE_ONCE;
|
|
|
|
}
|
|
|
|
|
|
|
|
phased = !Q_stricmp(token, "animMapPhase");
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
token = COM_ParseExt( text, qfalse );
|
|
|
|
if ( !token[0] )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf( PRINT_WARNING, "WARNING: missing parameter for 'animMmap' keyword in shader '%s'\n", shader.name );
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].imageAnimationSpeed = atof( token );
|
|
|
|
stage->bundle[cntBundle].imageAnimationPhase = 0;
|
|
|
|
|
|
|
|
if (phased)
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0])
|
|
|
|
{
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing phase for 'animMapPhase' keyword in shader '%s'\n", shader.name);
|
|
|
|
return qfalse;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].imageAnimationPhase = atof(token);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
// parse up to MAX_IMAGE_ANIMATIONS animations
|
|
|
|
while ( 1 ) {
|
|
|
|
int num;
|
|
|
|
|
|
|
|
token = COM_ParseExt( text, qfalse );
|
|
|
|
if ( !token[0] ) {
|
|
|
|
break;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
num = stage->bundle[cntBundle].numImageAnimations;
|
2023-05-08 19:53:53 +02:00
|
|
|
if ( num < MAX_IMAGE_ANIMATIONS ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].image[num] = R_FindImageFile(token, !stage->noMipMaps, (!stage->noPicMip ? picmip : 0), stage->force32bit, GL_REPEAT, GL_REPEAT );
|
|
|
|
if ( !stage->bundle[cntBundle].image[num] )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
ri.Printf( PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name );
|
|
|
|
return qfalse;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].numImageAnimations++;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "normalmap"))
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// added in 2.30
|
|
|
|
//
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0])
|
|
|
|
{
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing parameter for 'normalmap' keyword in shader '%s'\n", shader.name);
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->normalMap = R_FindImageFile(token, !stage->noMipMaps, (!stage->noPicMip ? picmip : 0), stage->force32bit, GL_REPEAT, GL_REPEAT);
|
|
|
|
if (!stage->normalMap)
|
|
|
|
{
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: R_FindImageFile could not find '%s' in shader '%s'\n", token, shader.name);
|
|
|
|
return qfalse;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
stage->hasNormalMap = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
//
|
|
|
|
// 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" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
depthTestBits = 0;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "equal" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
depthTestBits = GLS_DEPTHFUNC_EQUAL;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ri.Printf( PRINT_WARNING, "WARNING: unknown depthfunc '%s' in shader '%s'\n", token, shader.name );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// 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;
|
2024-12-01 15:26:01 +01:00
|
|
|
} else if ( !Q_stricmp( token, "alphaadd" ) ) {
|
|
|
|
blendSrcBits = GLS_SRCBLEND_SRC_ALPHA;
|
|
|
|
blendDstBits = GLS_DSTBLEND_ONE;
|
2023-05-08 19:53:53 +02:00
|
|
|
} 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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "colorwave"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
vec3_t v;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
ParseVector(text, 3, v);
|
|
|
|
stage->colorConst[0] = v[0] * 255.0;
|
|
|
|
stage->colorConst[1] = v[1] * 255.0;
|
|
|
|
stage->colorConst[2] = v[2] * 255.0;
|
|
|
|
stage->rgbGen = CGEN_MULTIPLY_BY_WAVEFORM;
|
|
|
|
shader.flags |= 2;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "identity"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_IDENTITY;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "identityLighting"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_IDENTITY_LIGHTING;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "global"))
|
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_GLOBAL_COLOR;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "entity") || !Q_stricmp(token, "fromentity"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_ENTITY;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "oneMinusEntity"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_ONE_MINUS_ENTITY;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "vertex") || !Q_stricmp(token, "fromclient"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_VERTEX;
|
2024-12-01 15:26:01 +01:00
|
|
|
if (stage->alphaGen == 0) {
|
2023-05-08 19:53:53 +02:00
|
|
|
stage->alphaGen = AGEN_VERTEX;
|
|
|
|
}
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "exactVertex"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->rgbGen = CGEN_EXACT_VERTEX;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "lightingGrid"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->rgbGen = CGEN_LIGHTING_GRID;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "lightingSpherical"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->rgbGen = CGEN_LIGHTING_SPHERICAL;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "oneMinusVertex"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->rgbGen = CGEN_ONE_MINUS_VERTEX;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "const" ) || !Q_stricmp(token, "constant"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
vec3_t color;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
ParseVector( text, 3, color );
|
|
|
|
stage->colorConst[0] = 255 * color[0];
|
|
|
|
stage->colorConst[1] = 255 * color[1];
|
|
|
|
stage->colorConst[2] = 255 * color[2];
|
|
|
|
|
|
|
|
stage->rgbGen = CGEN_CONSTANT;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "static"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->rgbGen = CGEN_STATIC;
|
|
|
|
if (stage->alphaGen == AGEN_IDENTITY) {
|
|
|
|
stage->alphaGen = AGEN_VERTEX;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "sCoord") || !Q_stricmp(token, "tCoord"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(token, "sCoord")) {
|
|
|
|
stage->rgbGen = CGEN_SCOORD;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
stage->rgbGen = CGEN_TCOORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
stage->alphaConstMin = 0;
|
|
|
|
stage->alphaConst = -1;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->alphaConstMin = atof(token) * 255.0;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing rgbGen sCoord or tCoord parm 'max' in shader '%s'\n", shader.name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->alphaConst = atof(token) * 255.0;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "dot"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
|
|
|
|
stage->rgbGen = CGEN_DOT;
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "oneminusdot"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
|
|
|
|
stage->rgbGen = CGEN_ONE_MINUS_DOT;
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
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;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "global"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->alphaGen = AGEN_GLOBAL_ALPHA;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "entity" ) || !Q_stricmp(token, "fromentity"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->alphaGen = AGEN_ENTITY;
|
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "oneMinusEntity" ) )
|
|
|
|
{
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_ENTITY;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "vertex" ) || !Q_stricmp(token, "fromclient"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
stage->alphaGen = AGEN_VERTEX;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "oneMinusVertex" ) || !Q_stricmp(token, "oneMinusFromClient"))
|
|
|
|
{
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_VERTEX;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "lightingSpecular"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.needsNormal = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
stage->alphaGen = AGEN_LIGHTING_SPECULAR;
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->alphaMax = 255;
|
|
|
|
stage->specOrigin[0] = -960;
|
|
|
|
stage->specOrigin[1] = 1980;
|
|
|
|
stage->specOrigin[2] = 96;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0]) {
|
|
|
|
stage->alphaMax = atof(token) * 255.0;
|
|
|
|
ParseVector(text, 3, stage->specOrigin);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "distFade") || !Q_stricmp(token, "oneMinusDistFade")
|
|
|
|
|| !Q_stricmp(token, "tikiDistFade") || !Q_stricmp(token, "oneMinusTikiDistFade"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(token, "distFade")) {
|
|
|
|
stage->alphaGen = AGEN_DIST_FADE;
|
|
|
|
} else if (!Q_stricmp(token, "oneMinusDistFade")) {
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_DIST_FADE;
|
|
|
|
} else if (!Q_stricmp(token, "tikiDistFade")) {
|
|
|
|
stage->alphaGen = AGEN_TIKI_DIST_FADE;
|
|
|
|
} else {
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_TIKI_DIST_FADE;
|
|
|
|
}
|
|
|
|
|
|
|
|
shader.fDistNear = 256.0;
|
|
|
|
shader.fDistRange = 256.0;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
shader.fDistNear = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
shader.fDistRange = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "dot"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
stage->alphaGen = AGEN_DOT;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "heightFade"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->alphaMin = 256.0f;
|
|
|
|
stage->alphaMax = 512.0f;
|
|
|
|
stage->alphaGen = AGEN_HEIGHT_FADE;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "oneMinusDot"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_DOT;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "dotView"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
stage->alphaGen = AGEN_DOT_VIEW;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "oneMinusDotView"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_DOT_VIEW;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "const") || !Q_stricmp(token, "constant"))
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
stage->colorConst[3] = 255 * atof(token);
|
|
|
|
stage->alphaGen = AGEN_CONSTANT;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "skyAlpha"))
|
|
|
|
{
|
|
|
|
stage->alphaGen = AGEN_SKYALPHA;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "oneMinusSkyAlpha"))
|
|
|
|
{
|
|
|
|
stage->alphaGen = AGEN_ONE_MINUS_SKYALPHA;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "sCoord") || !Q_stricmp(token, "tCoord"))
|
|
|
|
{
|
|
|
|
if (!Q_stricmp(token, "sCoord")) {
|
|
|
|
stage->alphaGen = AGEN_SCOORD;
|
|
|
|
} else {
|
|
|
|
stage->alphaGen = AGEN_TCOORD;
|
|
|
|
}
|
|
|
|
|
|
|
|
stage->alphaMin = 0.0;
|
|
|
|
stage->alphaMax = 1.0;
|
|
|
|
stage->alphaConstMin = 0;
|
|
|
|
stage->alphaConst = -1;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMin = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaMax = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaConstMin = atof(token) * 255.0;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing alphaGen s or tCoord parm 'max' in shader '%s'\n", shader.name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
stage->alphaConst = atof(token) * 255.0;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
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" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->bundle[cntBundle].tcGen = TCGEN_ENVIRONMENT_MAPPED;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "environmentmodel"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->bundle[cntBundle].tcGen = TCGEN_ENVIRONMENT_MAPPED2;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "sunreflection"))
|
|
|
|
{
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
stage->bundle[cntBundle].tcGen = TCGEN_SUN_REFLECTION;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
else if ( !Q_stricmp( token, "lightmap" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].tcGen = TCGEN_LIGHTMAP;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "texture" ) || !Q_stricmp( token, "base" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].tcGen = TCGEN_TEXTURE;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
else if ( !Q_stricmp( token, "vector" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
ParseVector( text, 3, stage->bundle[cntBundle].tcGenVectors[0] );
|
|
|
|
ParseVector( text, 3, stage->bundle[cntBundle].tcGenVectors[1] );
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
stage->bundle[cntBundle].tcGen = TCGEN_VECTOR;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
strcat( buffer, token );
|
|
|
|
strcat( buffer, " " );
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
ParseTexMod( buffer, stage, cntBundle );
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// depthmask
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "depthwrite" ) || !Q_stricmp(token, "depthmask"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
depthMaskBits = GLS_DEPTHMASK_TRUE;
|
|
|
|
depthMaskExplicit = qtrue;
|
|
|
|
|
|
|
|
continue;
|
2024-12-01 15:26:01 +01:00
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "nodepthwrite") || !Q_stricmp(token, "nodepthmask"))
|
|
|
|
{
|
|
|
|
depthMaskBits = 0;
|
|
|
|
depthMaskExplicit = qtrue;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "nocolorwrite") || !Q_stricmp(token, "nocolormask"))
|
|
|
|
{
|
|
|
|
colorBits = GLS_COLOR_NOMASK;
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "noDepthTest"))
|
|
|
|
{
|
|
|
|
depthFuncBits = GLS_DEPTHTEST_DISABLE;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "nextBundle"))
|
|
|
|
{
|
|
|
|
if (!qglActiveTextureARB) {
|
|
|
|
ri.Printf(PRINT_ALL, "WARNING: " PRODUCT_NAME " requires a video card with multitexturing capability\n");
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] && !Q_stricmp(token, "add")) {
|
|
|
|
stage->multitextureEnv = GL_ADD;
|
|
|
|
} else {
|
|
|
|
stage->multitextureEnv = GL_MODULATE;
|
|
|
|
}
|
|
|
|
|
|
|
|
cntBundle++;
|
|
|
|
if (cntBundle > NUM_TEXTURE_BUNDLES) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: too many nextBundle commands in shader '%s'\n", shader.name);
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// Added in 2.0
|
|
|
|
//
|
|
|
|
else if (!Q_stricmp(token, "ifCvar") || !Q_stricmp(token, "ifCvarnot"))
|
|
|
|
{
|
|
|
|
cvar_t* var;
|
|
|
|
qboolean isNot = token[6] != 0;
|
|
|
|
qboolean evaluatedValue;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0]) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing cvar name in %s", isNot ? "ifCvarnot" : "ifCvar");
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
var = ri.Cvar_Get(token, "0", 0);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!token[0]) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing cvar value in %s", isNot ? "ifCvarnot" : "ifCvar");
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (atof(token) != var->value) {
|
|
|
|
evaluatedValue = qfalse;
|
|
|
|
} else if (var->value) {
|
|
|
|
evaluatedValue = qtrue;
|
|
|
|
} else if (token[0] == '0' || (token[0] == '.' && token[1] == '0')) {
|
|
|
|
evaluatedValue = qtrue;
|
|
|
|
} else if (!Q_stricmp(var->string, token)) {
|
|
|
|
evaluatedValue = qtrue;
|
|
|
|
} else {
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
shouldProcess &= evaluatedValue ^ isNot;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ri.Printf( PRINT_WARNING, "WARNING: unknown parameter '%s' in shader '%s'\n", token, shader.name );
|
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// Added in 2.0
|
|
|
|
// Ignore vars that didn't met with 'ifCvar' / 'ifCvarnot'
|
|
|
|
if (!shouldProcess) {
|
|
|
|
stage->active = qfalse;
|
|
|
|
stage->rgbGen = CGEN_BAD;
|
|
|
|
return qtrue;
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
//
|
|
|
|
// if cgen isn't explicitly specified, use either identity or identitylighting
|
|
|
|
//
|
|
|
|
if ( stage->rgbGen == CGEN_BAD ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
if (cntBundle || stage->bundle[0].isLightmap) {
|
|
|
|
stage->rgbGen = CGEN_IDENTITY;
|
|
|
|
} else if (blendSrcBits == 0 ||
|
|
|
|
blendSrcBits == GLS_SRCBLEND_ONE ||
|
|
|
|
blendSrcBits == GLS_SRCBLEND_SRC_ALPHA) {
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (depthFuncBits == GLS_DEPTHTEST_DISABLE) {
|
|
|
|
depthMaskBits = 0;
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
// decide which agens we can skip
|
|
|
|
if ( stage->alphaGen == AGEN_IDENTITY ) {
|
|
|
|
if ( stage->rgbGen == CGEN_IDENTITY
|
2024-12-01 15:26:01 +01:00
|
|
|
|| stage->rgbGen == CGEN_LIGHTING_GRID
|
|
|
|
|| stage->rgbGen == CGEN_LIGHTING_SPHERICAL ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
stage->alphaGen = AGEN_SKIP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// compute state bits
|
|
|
|
//
|
|
|
|
stage->stateBits = depthMaskBits |
|
|
|
|
blendSrcBits | blendDstBits |
|
|
|
|
atestBits |
|
2024-12-01 15:26:01 +01:00
|
|
|
depthFuncBits |
|
|
|
|
depthTestBits |
|
|
|
|
colorBits |
|
|
|
|
fogBits;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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, "autosprite" ) ) {
|
|
|
|
ds->deformation = DEFORM_AUTOSPRITE;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !Q_stricmp( token, "autosprite2" ) ) {
|
|
|
|
ds->deformation = DEFORM_AUTOSPRITE2;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(token, "lightglow")) {
|
|
|
|
ds->deformation = DEFORM_LIGHTGLOW;
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.needsNormal = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Q_stricmp(token, "flap")) {
|
|
|
|
texDirection_t coordDirection;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 's') {
|
|
|
|
coordDirection = USE_S_COORDS;
|
|
|
|
}
|
|
|
|
else if (token[0] == 't') {
|
|
|
|
coordDirection = USE_T_COORDS;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: deformVertexes flap requires 's' or 't' in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes flap parm in shader '%s'\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!atof(token)) {
|
|
|
|
ds->deformationSpread = 100.0;
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: illegal div value of 0 in deformVertexes command for shader '%s'\n", shader.name);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ds->deformationSpread = 1.0 / atof(token);
|
|
|
|
}
|
|
|
|
|
|
|
|
ParseWaveForm(text, &ds->deformationWave);
|
|
|
|
|
|
|
|
if (coordDirection == USE_T_COORDS) {
|
|
|
|
ds->deformation = DEFORM_FLAP_T;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ds->deformation = DEFORM_FLAP_S;
|
|
|
|
}
|
|
|
|
|
|
|
|
shader.needsNormal = qtrue;
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0)
|
|
|
|
{
|
|
|
|
ds->bulgeWidth = 0.0;
|
|
|
|
ds->bulgeHeight = 1.0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ds->bulgeWidth = atof(token);
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0)
|
|
|
|
{
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing deformVertexes parm 'max' in shader '%s'\n\n", shader.name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ds->bulgeHeight = atof(token);
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.needsNormal = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
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] );
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!haveClampToEdge) {
|
|
|
|
shader.sky.outerbox[i] = R_FindImageFile((char*)pathname, qtrue, qtrue, shader_force32bit, GL_CLAMP, GL_CLAMP);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
shader.sky.outerbox[i] = R_FindImageFile((char*)pathname, qtrue, qtrue, shader_force32bit, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
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] );
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.sky.outerbox[i] = R_FindImageFile( ( char * ) pathname, qtrue, qtrue, shader_force32bit, GL_REPEAT, GL_REPEAT );
|
2023-05-08 19:53:53 +02:00
|
|
|
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 },
|
2024-12-01 15:26:01 +01:00
|
|
|
{"fence", 1, 0, CONTENTS_FENCE },
|
|
|
|
{"weaponclip", 1, 0, CONTENTS_WEAPONCLIP },
|
|
|
|
{"vehicleclip", 1, 0, CONTENTS_VEHICLECLIP },
|
2023-05-08 19:53:53 +02:00
|
|
|
{"nodrop", 1, 0, CONTENTS_NODROP }, // don't drop items or leave bodies (death fog, lava, etc)
|
2024-12-01 15:26:01 +01:00
|
|
|
{"nonsolid", 1, SURF_NONSOLID, 0 }, // clears the solid flag
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// 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
|
|
|
|
{"fog", 1, 0, CONTENTS_FOG}, // carves surfaces entering
|
2024-12-01 15:26:01 +01:00
|
|
|
{"sky", 0, SURF_SKY, 0 }, // emit light from an environment map
|
|
|
|
{"alphashadow", 1, SURF_ALPHASHADOW, 0 }, // carves surfaces entering
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// 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 },
|
|
|
|
{"nosteps", 0, SURF_NOSTEPS, 0 },
|
2024-12-01 15:26:01 +01:00
|
|
|
{"paper", 0, SURF_PAPER, 0 },
|
|
|
|
{"wood", 0, SURF_WOOD, 0 },
|
|
|
|
{"metal", 0, SURF_METAL, 0 },
|
|
|
|
{"rock", 0, SURF_ROCK, 0 },
|
|
|
|
{"dirt", 0, SURF_DIRT, 0 },
|
|
|
|
{"grill", 0, SURF_GRILL, 0 },
|
|
|
|
{"grass", 0, SURF_GRASS, 0 },
|
|
|
|
{"mud", 0, SURF_MUD, 0 },
|
|
|
|
{"puddle", 0, SURF_PUDDLE, 0 },
|
|
|
|
{"glass", 0, SURF_GLASS, 0 },
|
|
|
|
{"gravel", 0, SURF_GRAVEL, 0 },
|
|
|
|
{"sand", 0, SURF_SAND, 0 },
|
|
|
|
{"foliage", 0, SURF_FOLIAGE, 0 },
|
|
|
|
{"snow", 0, SURF_SNOW, 0 },
|
|
|
|
{"carpet", 0, SURF_CARPET, 0 },
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// drawsurf attributes
|
2024-12-01 15:26:01 +01:00
|
|
|
{"nodraw", 0, SURF_NODRAW, 0 }, // don't generate a drawsurface (or a lightmap)
|
|
|
|
{"castshadow", 0, SURF_CASTSHADOW, 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
|
|
|
|
{"hint", 0, SURF_HINT, 0} // leave a dust trail when walking on this surface
|
2023-05-08 19:53:53 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
ParseSurfaceParm
|
|
|
|
|
|
|
|
surfaceparm <name>
|
|
|
|
===============
|
|
|
|
*/
|
|
|
|
static void ParseSurfaceParm( char **text ) {
|
|
|
|
char *token;
|
2024-12-01 15:26:01 +01:00
|
|
|
int numInfoParms = sizeof(infoParms) / sizeof(infoParms[0]);
|
2023-05-08 19:53:53 +02:00
|
|
|
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.
|
|
|
|
=================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
static qboolean ParseShader( char **text, qboolean picmip )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
char *token;
|
|
|
|
int s;
|
2024-12-01 15:26:01 +01:00
|
|
|
int matchingendifs;
|
|
|
|
qboolean bInElseBlock;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
s = 0;
|
2024-12-01 15:26:01 +01:00
|
|
|
matchingendifs = 0;
|
|
|
|
bInElseBlock = qfalse;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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] == '{' )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( !ParseStage( &unfoggedStages[s], text, picmip ) )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
return qfalse;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
if (unfoggedStages[s].rgbGen != CGEN_BAD) {
|
|
|
|
unfoggedStages[s].active = qtrue;
|
|
|
|
if (s > 0) {
|
|
|
|
if (!(unfoggedStages[s].stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS))) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: shader '%s' has opaque maps defined after stage 0!!!\n", shader.name);
|
|
|
|
}
|
|
|
|
if (unfoggedStages[s].stateBits & GLS_DEPTHMASK_TRUE) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: shader '%s' has depthmask enabled after stage 0!!!\n", shader.name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
s++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// skip stuff that only the QuakeEdRadient needs
|
|
|
|
else if ( !Q_stricmpn( token, "qer", 3 ) ) {
|
|
|
|
SkipRestOfLine( text );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// sun parms
|
2024-12-01 15:26:01 +01:00
|
|
|
else if ( !Q_stricmp( token, "q3map_sun" ) ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
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" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
shader_noMipMaps = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// no picmip adjustment
|
|
|
|
else if ( !Q_stricmp( token, "nopicmip" ) )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
shader_noPicMip = qtrue;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// force 32 bit images
|
|
|
|
else if (!Q_stricmp(token, "force32bit"))
|
|
|
|
{
|
|
|
|
shader_force32bit = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "noMerge"))
|
|
|
|
{
|
|
|
|
shader.flags |= 1;
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
// portal
|
|
|
|
else if (!Q_stricmp(token, "portalsky"))
|
|
|
|
{
|
|
|
|
shader.sort = SS_PORTALSKY;
|
|
|
|
shader.isPortalSky = qtrue;
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
// light <value> determines flaring in q3map, not needed here
|
|
|
|
else if ( !Q_stricmp(token, "light") )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
token = COM_ParseExt( text, qfalse );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "spritegen"))
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing spritegen parm in shader '%s'\n", shader.name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Q_stricmp(token, "parallel")) {
|
|
|
|
shader.sprite.type = SPRITE_PARALLEL;
|
|
|
|
} else if (!Q_stricmp(token, "parallel_oriented")) {
|
|
|
|
shader.sprite.type = SPRITE_PARALLEL_ORIENTED;
|
|
|
|
} else if (!Q_stricmp(token, "parallel_upright")) {
|
|
|
|
shader.sprite.type = SPRITE_PARALLEL_UPRIGHT;
|
|
|
|
} else if (!Q_stricmp(token, "oriented")) {
|
|
|
|
shader.sprite.type = SPRITE_ORIENTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
shader.sprite.scale = 1.0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "spritescale"))
|
|
|
|
{
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: missing spritescale parm in shader '%s'\n", shader.name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
shader.sprite.scale = atof(token);
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (!Q_stricmp(token, "#if") || !Q_stricmp(token, "#if_not") || !Q_stricmp(token, "#else"))
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
qboolean conditionpassed;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
conditionpassed = qfalse;
|
|
|
|
if (!Q_stricmp(token, "#else"))
|
|
|
|
{
|
|
|
|
if (bInElseBlock) {
|
|
|
|
ri.Printf(
|
|
|
|
PRINT_WARNING,
|
|
|
|
"WARNING: illegal #else after a previous #else in shader '%s'\n",
|
|
|
|
shader.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
matchingendifs--;
|
|
|
|
if (matchingendifs < 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: unmatched #else in shader '%s'\n", shader.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
bInElseBlock = qtrue;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
qboolean bInvertResult;
|
|
|
|
|
|
|
|
bInvertResult = !Q_stricmp(token, "#if_not");
|
|
|
|
|
|
|
|
token = COM_ParseExt(text, qfalse);
|
|
|
|
if (!Q_stricmp(token, "separate_env")) {
|
|
|
|
conditionpassed = r_textureDetails->integer != 0;
|
|
|
|
} else if (!Q_stricmp(token, "0") || !Q_stricmp(token, "false")) {
|
|
|
|
conditionpassed = qfalse;
|
|
|
|
} else if (!Q_stricmp(token, "1") || !Q_stricmp(token, "true")) {
|
|
|
|
conditionpassed = qtrue;
|
|
|
|
} else {
|
|
|
|
ri.Printf(
|
|
|
|
PRINT_WARNING,
|
|
|
|
"WARNING: invalid #if argument '%s' in shader '%s', not passing.\n",
|
|
|
|
token,
|
|
|
|
shader.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bInvertResult) {
|
|
|
|
conditionpassed = !conditionpassed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!conditionpassed) {
|
|
|
|
int nestedifs = 0;
|
|
|
|
|
|
|
|
SkipRestOfLine(text);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
token = COM_ParseExt(text, qtrue);
|
|
|
|
if (!token[0]) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: no matching #endif in shader '%s'\n", shader.name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Q_stricmp(token, "#if")) {
|
|
|
|
nestedifs++;
|
|
|
|
SkipRestOfLine(text);
|
|
|
|
} else if (!Q_stricmp(token, "#endif")) {
|
|
|
|
nestedifs--;
|
|
|
|
} else if (!Q_stricmp(token, "#else")) {
|
|
|
|
if (bInElseBlock) {
|
|
|
|
ri.Printf(
|
|
|
|
PRINT_WARNING,
|
|
|
|
"WARNING: illegal #else after a previous #else in shader '%s'\n",
|
|
|
|
shader.name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!nestedifs) {
|
|
|
|
matchingendifs++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
nestedifs++;
|
|
|
|
} else {
|
|
|
|
SkipRestOfLine(text);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nestedifs == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
matchingendifs++;
|
|
|
|
}
|
|
|
|
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
else if (!Q_stricmp(token, "#endif"))
|
|
|
|
{
|
|
|
|
matchingendifs--;
|
|
|
|
if (matchingendifs < 0) {
|
|
|
|
ri.Printf(PRINT_WARNING, "WARNING: unmatched #endif in shader '%s'\n", shader.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
bInElseBlock = qfalse;
|
|
|
|
}
|
|
|
|
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 unfoggedStages, unless it is a sky or fog
|
|
|
|
//
|
|
|
|
if ( s == 0 && !shader.isSky && !(shader.contentFlags & CONTENTS_FOG) && !(shader.surfaceFlags & SURF_NODRAW)) {
|
2023-05-08 19:53:53 +02:00
|
|
|
return qfalse;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!(shader.flags & 2)) {
|
|
|
|
shader.flags &= ~1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((shader.contentFlags & CONTENTS_FENCE) != 0 && shader.cullType == CT_TWO_SIDED) {
|
|
|
|
shader.cullType = CT_FRONT_SIDED;
|
|
|
|
}
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
goto done;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( r_ignoreFastPath->integer )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// see if this can go into the vertex lit fast path
|
|
|
|
//
|
|
|
|
if ( shader.numUnfoggedPasses == 1 )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( unfoggedStages[0].rgbGen == CGEN_LIGHTING_GRID || unfoggedStages[0].rgbGen == CGEN_LIGHTING_SPHERICAL )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( unfoggedStages[0].alphaGen == AGEN_IDENTITY || unfoggedStages[0].alphaGen == AGEN_SKIP)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( unfoggedStages[0].bundle[0].tcGen == TCGEN_TEXTURE && !unfoggedStages[0].bundle[0].numTexMods)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
if ( !shader.polygonOffset )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( !unfoggedStages[0].multitextureEnv )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
if ( !shader.numDeforms )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.optimalStageIteratorFunc = RB_StageIteratorVertexLitTextureUnfogged;
|
|
|
|
goto done;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// see if this can go into an optimized LM, multitextured path
|
|
|
|
//
|
|
|
|
if ( shader.numUnfoggedPasses == 1 )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( ( unfoggedStages[0].rgbGen == CGEN_IDENTITY ) && ( unfoggedStages[0].alphaGen == AGEN_IDENTITY || unfoggedStages[0].alphaGen == AGEN_SKIP ) )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( unfoggedStages[0].bundle[0].tcGen == TCGEN_TEXTURE &&
|
|
|
|
unfoggedStages[0].bundle[1].tcGen == TCGEN_LIGHTMAP )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
|
|
|
if ( !shader.polygonOffset )
|
|
|
|
{
|
|
|
|
if ( !shader.numDeforms )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if (unfoggedStages[0].multitextureEnv )
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.optimalStageIteratorFunc = RB_StageIteratorLightmappedMultitextureUnfogged;
|
|
|
|
goto done;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
done:
|
|
|
|
return;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
Attempt to combine two unfoggedStages into a single multitexture stage
|
2023-05-08 19:53:53 +02:00
|
|
|
FIXME: I think modulated add + modulated add collapses incorrectly
|
|
|
|
=================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
static void CollapseMultitexture(int *stagecounter) {
|
|
|
|
int iUseCollapse;
|
|
|
|
int stagenum;
|
2023-05-08 19:53:53 +02:00
|
|
|
int abits, bbits;
|
2024-12-01 15:26:01 +01:00
|
|
|
shaderStage_t* stage;
|
2023-05-08 19:53:53 +02:00
|
|
|
textureBundle_t tmpBundle;
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
for (stagenum = 0; stagenum < *stagecounter - 1; stagenum++) {
|
|
|
|
stage = &unfoggedStages[stagenum];
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// make sure both unfoggedStages are active
|
|
|
|
if (!stage[0].active || !stage[1].active) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (stage->multitextureEnv) {
|
|
|
|
continue;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// on voodoo2, don't combine different tmus
|
|
|
|
if (glConfig.driverType == GLDRV_VOODOO) {
|
|
|
|
if (stage[1].bundle[0].image[0]->TMU ==
|
|
|
|
unfoggedStages[stagenum].bundle[0].image[0]->TMU) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
abits = stage[0].stateBits;
|
|
|
|
bbits = stage[1].stateBits;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// make sure that both unfoggedStages 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))
|
|
|
|
&& (bbits & ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE)
|
|
|
|
& abits & ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHMASK_TRUE)
|
|
|
|
& ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS | GLS_DEPTHFUNC_EQUAL | GLS_DEPTHMASK_TRUE)
|
|
|
|
|| !(abits & GLS_DEPTHMASK_TRUE)
|
|
|
|
|| !(bbits & GLS_DEPTHFUNC_EQUAL))
|
|
|
|
) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
abits &= (GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);
|
|
|
|
bbits &= (GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);
|
|
|
|
|
|
|
|
// search for a valid multitexture blend function
|
|
|
|
for (iUseCollapse = 0; collapse[iUseCollapse].blendA != -1; iUseCollapse++) {
|
|
|
|
if (abits == collapse[iUseCollapse].blendA
|
|
|
|
&& bbits == collapse[iUseCollapse].blendB) {
|
|
|
|
break;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// nothing found
|
|
|
|
if (collapse[iUseCollapse].blendA == -1) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// GL_ADD is a separate extension
|
|
|
|
if (collapse[iUseCollapse].multitextureEnv == GL_ADD && !glConfig.textureEnvAddAvailable) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// make sure waveforms have identical parameters
|
|
|
|
if ((stage->rgbGen != unfoggedStages[stagenum + 1].rgbGen) ||
|
|
|
|
(stage->alphaGen != unfoggedStages[stagenum + 1].alphaGen)) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// an add collapse can only have identity colors
|
|
|
|
if (collapse[iUseCollapse].multitextureEnv == GL_ADD && stage->rgbGen != CGEN_IDENTITY) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (stage->rgbGen == CGEN_WAVEFORM)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if (memcmp(&stage->rgbWave,
|
|
|
|
&unfoggedStages[stagenum + 1].rgbWave,
|
|
|
|
sizeof(stage->rgbWave)))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
if (stage->alphaGen == CGEN_WAVEFORM)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
if (memcmp(&stage->alphaWave,
|
|
|
|
&unfoggedStages[stagenum + 1].alphaWave,
|
|
|
|
sizeof(stage->alphaWave)))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// make sure that lightmaps are in bundle 1 for 3dfx
|
|
|
|
if (stage->bundle[0].isLightmap)
|
|
|
|
{
|
|
|
|
tmpBundle = stage->bundle[0];
|
|
|
|
stage->bundle[0] = stage[1].bundle[0];
|
|
|
|
stage->bundle[1] = tmpBundle;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
stage->bundle[1] = stage[1].bundle[0];
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// set the new blend state bits
|
|
|
|
stage->multitextureEnv = collapse[iUseCollapse].multitextureEnv;
|
|
|
|
stage->stateBits &= ~(GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS);
|
|
|
|
stage->stateBits |= collapse[iUseCollapse].multitextureBlend;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
//
|
|
|
|
// move down subsequent shaders
|
|
|
|
//
|
|
|
|
if (stagenum + 2 < MAX_SHADER_STAGES) {
|
|
|
|
memmove(&unfoggedStages[stagenum + 1], &unfoggedStages[stagenum + 2], sizeof(unfoggedStages[0]) * (MAX_SHADER_STAGES - 2 - stagenum));
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
Com_Memset(&unfoggedStages[MAX_SHADER_STAGES - 1], 0, sizeof(unfoggedStages[0]));
|
|
|
|
(*stagecounter)--;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=============
|
|
|
|
|
|
|
|
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 ) {
|
2024-12-01 16:26:26 +01:00
|
|
|
renderCommandList_t *cmdList = &backEndData->commands;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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[]
|
2024-12-01 15:26:01 +01:00
|
|
|
array so that the shader->sort key is sorted reletive to the other
|
2023-05-08 19:53:53 +02:00
|
|
|
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++;
|
|
|
|
}
|
|
|
|
|
|
|
|
newShader->sortedIndex = i+1;
|
|
|
|
tr.sortedShaders[i+1] = newShader;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
GeneratePermanentShader
|
|
|
|
====================
|
|
|
|
*/
|
|
|
|
static shader_t *GeneratePermanentShader( void ) {
|
|
|
|
shader_t *newShader;
|
|
|
|
int i, b;
|
2024-12-01 15:26:01 +01:00
|
|
|
int size;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
if ( tr.numShaders == MAX_SHADERS ) {
|
|
|
|
ri.Printf( PRINT_WARNING, "WARNING: GeneratePermanentShader - MAX_SHADERS hit\n");
|
|
|
|
return tr.defaultShader;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
newShader = ri.Hunk_Alloc( sizeof( shader_t ), h_dontcare);
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
*newShader = shader;
|
2024-12-01 15:26:01 +01:00
|
|
|
newShader->next = currentShader->shader;
|
|
|
|
currentShader->shader = newShader;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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++ ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( !unfoggedStages[i].active ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
break;
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
newShader->unfoggedStages[i] = ri.Hunk_Alloc( sizeof( unfoggedStages[i] ), h_dontcare );
|
|
|
|
*newShader->unfoggedStages[i] = unfoggedStages[i];
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
for ( b = 0 ; b < NUM_TEXTURE_BUNDLES ; b++ ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
size = newShader->unfoggedStages[i]->bundle[b].numTexMods * sizeof( texModInfo_t );
|
|
|
|
if (size) {
|
|
|
|
newShader->unfoggedStages[i]->bundle[b].texMods = ri.Hunk_Alloc(size, h_dontcare);
|
|
|
|
Com_Memcpy(newShader->unfoggedStages[i]->bundle[b].texMods, unfoggedStages[i].bundle[b].texMods, size);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
SortNewShader();
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
currentShader = NULL;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
return newShader;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
VertexLightingCollapse
|
|
|
|
|
|
|
|
If vertex lighting is enabled, only render a single
|
2024-12-01 15:26:01 +01:00
|
|
|
pass, trying to guess which is the correct one to best aproximate
|
2023-05-08 19:53:53 +02:00
|
|
|
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
|
2024-12-01 15:26:01 +01:00
|
|
|
bestStage = &unfoggedStages[0];
|
2023-05-08 19:53:53 +02:00
|
|
|
bestImageRank = -999999;
|
|
|
|
|
|
|
|
for ( stage = 0; stage < MAX_SHADER_STAGES; stage++ ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
shaderStage_t *pStage = &unfoggedStages[stage];
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].bundle[0] = bestStage->bundle[0];
|
|
|
|
unfoggedStages[0].stateBits &= ~( GLS_DSTBLEND_BITS | GLS_SRCBLEND_BITS );
|
|
|
|
unfoggedStages[0].stateBits |= GLS_DEPTHMASK_TRUE;
|
2023-05-08 19:53:53 +02:00
|
|
|
if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].rgbGen = CGEN_LIGHTING_GRID;
|
2023-05-08 19:53:53 +02:00
|
|
|
} else {
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].rgbGen = CGEN_EXACT_VERTEX;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].alphaGen = AGEN_SKIP;
|
2023-05-08 19:53:53 +02:00
|
|
|
} else {
|
|
|
|
// don't use a lightmap (tesla coils)
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( unfoggedStages[0].bundle[0].isLightmap ) {
|
|
|
|
unfoggedStages[0] = unfoggedStages[1];
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// if we were in a cross-fade cgen, hack it to normal
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( unfoggedStages[0].rgbGen == CGEN_ONE_MINUS_ENTITY || unfoggedStages[1].rgbGen == CGEN_ONE_MINUS_ENTITY ) {
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( ( unfoggedStages[0].rgbGen == CGEN_WAVEFORM && unfoggedStages[0].rgbWave.func == GF_SAWTOOTH )
|
|
|
|
&& ( unfoggedStages[1].rgbGen == CGEN_WAVEFORM && unfoggedStages[1].rgbWave.func == GF_INVERSE_SAWTOOTH ) ) {
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( ( unfoggedStages[0].rgbGen == CGEN_WAVEFORM && unfoggedStages[0].rgbWave.func == GF_INVERSE_SAWTOOTH )
|
|
|
|
&& ( unfoggedStages[1].rgbGen == CGEN_WAVEFORM && unfoggedStages[1].rgbWave.func == GF_SAWTOOTH ) ) {
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( stage = 1; stage < MAX_SHADER_STAGES; stage++ ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
shaderStage_t *pStage = &unfoggedStages[stage];
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
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;
|
2024-12-01 15:26:01 +01:00
|
|
|
int i;
|
|
|
|
int bundle;
|
|
|
|
qboolean hasLightmapStage;
|
|
|
|
|
|
|
|
if (!currentShader) {
|
|
|
|
currentShader = FindShaderText(shader.name);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shader.defaultShader)
|
|
|
|
{
|
|
|
|
currentShader->shader = tr.defaultShader;
|
|
|
|
currentShader = NULL;
|
|
|
|
return tr.defaultShader;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
hasLightmapStage = qfalse;
|
|
|
|
|
|
|
|
//
|
|
|
|
// set sky stuff appropriate
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( shader.isPortalSky ) {
|
|
|
|
shader.sort = SS_PORTALSKY;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
if ( shader.isSky ) {
|
|
|
|
shader.sort = SS_ENVIRONMENT;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// set polygon offset
|
|
|
|
//
|
|
|
|
if ( shader.polygonOffset && !shader.sort ) {
|
|
|
|
shader.sort = SS_DECAL;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.needsLGrid = qfalse;
|
|
|
|
shader.needsLSpherical = qfalse;
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
//
|
|
|
|
// set appropriate stage information
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
stage = 0;
|
|
|
|
for (shaderStage_t* pStage = &unfoggedStages[0]; pStage->active; pStage++, stage++) {
|
|
|
|
// check for a missing texture
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (pStage->rgbGen == CGEN_LIGHTING_GRID) {
|
|
|
|
shader.needsLGrid = qtrue;
|
|
|
|
} else if (pStage->rgbGen == CGEN_LIGHTING_SPHERICAL || pStage->rgbGen == CGEN_STATIC) {
|
|
|
|
shader.needsLSpherical = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// default texture coordinate generation
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
for (bundle = 0; bundle < 2; bundle++) {
|
|
|
|
if (pStage->bundle[bundle].isLightmap) {
|
|
|
|
if (pStage->bundle[bundle].tcGen == TCGEN_BAD) {
|
|
|
|
pStage->bundle[bundle].tcGen = TCGEN_LIGHTMAP;
|
|
|
|
}
|
|
|
|
hasLightmapStage = qtrue;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else {
|
|
|
|
if (pStage->bundle[bundle].tcGen == TCGEN_BAD) {
|
|
|
|
pStage->bundle[bundle].tcGen = TCGEN_TEXTURE;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (pStage->multitextureEnv && pStage->bundle[0].isLightmap) {
|
|
|
|
//
|
|
|
|
// exchange bundle
|
|
|
|
//
|
|
|
|
textureBundle_t tmpBundle = pStage->bundle[0];
|
|
|
|
pStage->bundle[0] = pStage->bundle[1];
|
|
|
|
pStage->bundle[1] = tmpBundle;
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
//
|
|
|
|
// if we are in r_vertexLight mode, never use a lightmap texture
|
|
|
|
//
|
|
|
|
if (stage > 1)
|
|
|
|
{
|
|
|
|
if (r_vertexLight->integer) {
|
|
|
|
VertexLightingCollapse();
|
|
|
|
stage = 1;
|
|
|
|
hasLightmapStage = qfalse;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
// look for multitexture potential
|
2023-05-08 19:53:53 +02:00
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
if (qglActiveTextureARB) {
|
|
|
|
CollapseMultitexture(&stage);
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
for (i = 0; i < stage; i++) {
|
|
|
|
int blendSrcBits, blendDstBits;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
shaderStage_t* pStage = &unfoggedStages[i];
|
|
|
|
if (pStage->stateBits & (GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS))
|
|
|
|
{
|
|
|
|
blendSrcBits = pStage->stateBits & GLS_SRCBLEND_BITS;
|
|
|
|
blendDstBits = pStage->stateBits & GLS_DSTBLEND_BITS;
|
|
|
|
if (blendSrcBits == GLS_SRCBLEND_ONE && blendDstBits == GLS_DSTBLEND_ONE
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_ZERO && blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_SRC_ALPHA && blendDstBits == GLS_DSTBLEND_ONE
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_DST_COLOR && blendDstBits == GLS_DSTBLEND_ONE
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_ONE_MINUS_DST_COLOR && blendDstBits == GLS_DSTBLEND_ONE)
|
|
|
|
{
|
|
|
|
pStage->stateBits |= GLS_FOG_ENABLED | GLS_FOG_BLACK;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (blendSrcBits == GLS_SRCBLEND_DST_COLOR && blendDstBits == GLS_DSTBLEND_ZERO
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_ZERO && blendDstBits == GLS_DSTBLEND_SRC_COLOR)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
pStage->stateBits |= GLS_FOG_ENABLED | GLS_FOG_WHITE;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else if (blendSrcBits == GLS_SRCBLEND_SRC_ALPHA && blendDstBits == GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA && blendDstBits == GLS_DSTBLEND_SRC_ALPHA
|
|
|
|
|| blendSrcBits == GLS_SRCBLEND_SRC_ALPHA && blendDstBits == GLS_DSTBLEND_ONE)
|
2023-05-08 19:53:53 +02:00
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
pStage->stateBits |= GLS_FOG_ENABLED;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
ri.Printf(PRINT_WARNING, "Shader '%s' stage# %i is unfoggable\n", shader.name, i + 1);
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (shader.sort == SS_BAD)
|
|
|
|
{
|
|
|
|
if (pStage->stateBits & GLS_DEPTHMASK_TRUE) {
|
2023-05-08 19:53:53 +02:00
|
|
|
shader.sort = SS_SEE_THROUGH;
|
2024-12-01 15:26:01 +01:00
|
|
|
}
|
|
|
|
else {
|
2023-05-08 19:53:53 +02:00
|
|
|
shader.sort = SS_BLEND0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else {
|
|
|
|
pStage->stateBits |= GLS_FOG_ENABLED;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (shader.sort == SS_BAD) {
|
|
|
|
shader.sort = SS_OPAQUE;
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (pStage->stateBits & GLS_MULTITEXTURE_ENV) {
|
|
|
|
pStage->stateBits &= ~GLS_FOG_ENABLED;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// fogonly shaders don't have any normal passes
|
|
|
|
if (shader.sort == SS_BAD) {
|
|
|
|
shader.sort = SS_OPAQUE;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (shader.lightmapIndex >= 0 && !hasLightmapStage) {
|
|
|
|
ri.Printf(PRINT_DEVELOPER, "WARNING: shader '%s' has lightmap but no lightmap stage!\n", shader.name);
|
|
|
|
shader.lightmapIndex = LIGHTMAP_NONE;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// compute number of passes
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.numUnfoggedPasses = 0;
|
|
|
|
for (stage = 0; stage < MAX_SHADER_STAGES; stage++) {
|
|
|
|
shaderStage_t* pStage = &unfoggedStages[stage];
|
|
|
|
if (!pStage->active) {
|
|
|
|
break;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.numUnfoggedPasses++;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// determine which stage iterator function is appropriate
|
|
|
|
ComputeStageIteratorFunc();
|
|
|
|
|
|
|
|
return GeneratePermanentShader();
|
|
|
|
}
|
|
|
|
|
|
|
|
//========================================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============
|
|
|
|
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
|
2024-12-01 15:26:01 +01:00
|
|
|
dynamic diffuse lighting applied to it, as apropriate for most
|
2023-05-08 19:53:53 +02:00
|
|
|
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
|
2024-12-01 15:26:01 +01:00
|
|
|
the vertex rgba modulate values, as apropriate for misc_model
|
2023-05-08 19:53:53 +02:00
|
|
|
pre-lit surfaces.
|
|
|
|
|
|
|
|
Other lightmapIndex values will have a lightmap stage created
|
2024-12-01 15:26:01 +01:00
|
|
|
and src*dest blending applied with the texture, as apropriate for
|
2023-05-08 19:53:53 +02:00
|
|
|
most world construction surfaces.
|
|
|
|
|
|
|
|
===============
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
shader_t* R_FindShader(const char* name, int lightmapIndex, qboolean mipRawImage, qboolean picmip, qboolean wrapx, qboolean wrapy) {
|
2023-05-08 19:53:53 +02:00
|
|
|
char strippedName[MAX_QPATH];
|
2024-12-01 15:26:01 +01:00
|
|
|
char fileName[MAX_QPATH];
|
|
|
|
int i, j, hash;
|
2023-05-08 19:53:53 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
COM_StripExtension( name, strippedName, sizeof(strippedName));
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
hash = generateHashValue(strippedName);
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
//
|
|
|
|
// see if the shader is already loaded
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
for (currentShader = hashTable[hash]; currentShader; currentShader = currentShader->next) {
|
2023-05-08 19:53:53 +02:00
|
|
|
// 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.
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!Q_stricmp(currentShader->name, strippedName))
|
|
|
|
{
|
|
|
|
// shader text found, but it has no shared assigned
|
|
|
|
break;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (currentShader)
|
|
|
|
{
|
|
|
|
for (sh = currentShader->shader; sh; sh = sh->next)
|
|
|
|
{
|
|
|
|
if (sh->lightmapIndex == lightmapIndex || sh == tr.defaultShader) {
|
|
|
|
return sh;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// create a new shader text
|
|
|
|
currentShader = AddShaderTextToHash(strippedName, hash);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// clear the global shader
|
|
|
|
Com_Memset( &shader, 0, sizeof( shader ) );
|
|
|
|
Com_Memset(&unfoggedStages, 0, sizeof(unfoggedStages));
|
|
|
|
Com_Memset(&texMods, 0, sizeof(texMods));
|
|
|
|
|
|
|
|
shader.sprite.scale = 1.0;
|
|
|
|
shader_noPicMip = qfalse;
|
|
|
|
shader_noMipMaps = qfalse;
|
|
|
|
shader_force32bit = qfalse;
|
|
|
|
Q_strncpyz(shader.name, strippedName, sizeof(shader.name));
|
|
|
|
shader.lightmapIndex = lightmapIndex;
|
|
|
|
for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
|
|
|
|
for (j = 0; j < NUM_TEXTURE_BUNDLES; j++) {
|
|
|
|
unfoggedStages[i].bundle[j].texMods = texMods[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: set these "need" values apropriately
|
|
|
|
shader.needsNormal = qfalse;
|
2023-05-08 19:53:53 +02:00
|
|
|
shader.needsST1 = qtrue;
|
|
|
|
shader.needsST2 = qtrue;
|
|
|
|
shader.needsColor = qtrue;
|
|
|
|
|
|
|
|
//
|
|
|
|
// attempt to define shader from an explicit parameter file
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( currentShader->text ) {
|
|
|
|
shaderText = currentShader->text;
|
2023-05-08 19:53:53 +02:00
|
|
|
// 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 );
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( !ParseShader( &shaderText, picmip ) ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
// had errors, so use default shader
|
|
|
|
shader.defaultShader = qtrue;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (shader.lightmapIndex == LIGHTMAP_BY_VERTEX && !(shader.surfaceFlags & SURF_HINT)) {
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_EXACT_VERTEX;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
sh = FinishShader();
|
|
|
|
return sh;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
// if not defined in the in-memory shader descriptions,
|
|
|
|
// look for a single TGA, BMP, or PCX
|
2023-05-08 19:53:53 +02:00
|
|
|
//
|
2024-12-01 15:26:01 +01:00
|
|
|
Q_strncpyz( fileName, name, sizeof( fileName ) );
|
|
|
|
COM_DefaultExtension( fileName, sizeof( fileName ), ".tga" );
|
|
|
|
if (!haveClampToEdge) {
|
|
|
|
image = R_FindImageFile(fileName, mipRawImage, picmip, qfalse, wrapx ? GL_REPEAT : GL_CLAMP, wrapy ? GL_REPEAT : GL_CLAMP);
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
else {
|
|
|
|
image = R_FindImageFile(fileName, mipRawImage, picmip, qfalse, wrapx ? GL_REPEAT : GL_CLAMP_TO_EDGE, wrapy ? GL_REPEAT : GL_CLAMP_TO_EDGE);
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if ( !image ) {
|
|
|
|
ri.Printf( PRINT_WARNING, "Couldn't find image for shader %s\n", name );
|
|
|
|
shader.defaultShader = qtrue;
|
|
|
|
return FinishShader();
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// create the default shading commands
|
|
|
|
//
|
|
|
|
if ( shader.lightmapIndex == LIGHTMAP_NONE ) {
|
|
|
|
// dynamic colors at vertexes
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].bundle[0].image[0] = image;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_LIGHTING_GRID;
|
|
|
|
unfoggedStages[0].stateBits = GLS_DEFAULT;
|
2023-05-08 19:53:53 +02:00
|
|
|
} else if ( shader.lightmapIndex == LIGHTMAP_BY_VERTEX ) {
|
|
|
|
// explicit colors at vertexes
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].bundle[0].image[0] = image;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_EXACT_VERTEX;
|
|
|
|
unfoggedStages[0].alphaGen = AGEN_SKIP;
|
|
|
|
unfoggedStages[0].stateBits = GLS_DEFAULT;
|
2023-05-08 19:53:53 +02:00
|
|
|
} else if ( shader.lightmapIndex == LIGHTMAP_2D ) {
|
|
|
|
// GUI elements
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].bundle[0].image[0] = image;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].force32bit = qtrue;
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_GLOBAL_COLOR;
|
|
|
|
unfoggedStages[0].alphaGen = AGEN_GLOBAL_ALPHA;
|
|
|
|
unfoggedStages[0].stateBits = GLS_DEPTHTEST_DISABLE |
|
2023-05-08 19:53:53 +02:00
|
|
|
GLS_SRCBLEND_SRC_ALPHA |
|
|
|
|
GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
|
|
|
|
} else if ( shader.lightmapIndex == LIGHTMAP_WHITEIMAGE ) {
|
|
|
|
// fullbright level
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].bundle[0].image[0] = tr.whiteImage;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
|
|
|
|
unfoggedStages[0].stateBits = GLS_DEFAULT;
|
|
|
|
|
|
|
|
unfoggedStages[1].bundle[0].image[0] = image;
|
|
|
|
unfoggedStages[1].active = qtrue;
|
|
|
|
unfoggedStages[1].rgbGen = CGEN_IDENTITY;
|
|
|
|
unfoggedStages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
|
2023-05-08 19:53:53 +02:00
|
|
|
} else {
|
|
|
|
// two pass lightmap
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex];
|
|
|
|
unfoggedStages[0].bundle[0].isLightmap = qtrue;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation
|
2023-05-08 19:53:53 +02:00
|
|
|
// for identitylight
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[0].stateBits = GLS_DEFAULT;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
unfoggedStages[1].bundle[0].image[0] = image;
|
|
|
|
unfoggedStages[1].active = qtrue;
|
|
|
|
unfoggedStages[1].rgbGen = CGEN_IDENTITY;
|
|
|
|
unfoggedStages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
return FinishShader();
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
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)
|
|
|
|
====================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
qhandle_t RE_RegisterShader( const char *name ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
shader_t *sh;
|
|
|
|
|
|
|
|
if ( strlen( name ) >= MAX_QPATH ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
Com_Printf( "Shader name exceeds MAX_QPATH\n" );
|
2023-05-08 19:53:53 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
sh = R_FindShader( name, LIGHTMAP_2D, qtrue, qtrue, qtrue, qtrue);
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
/*
|
2023-05-08 19:53:53 +02:00
|
|
|
====================
|
2024-12-01 15:26:01 +01:00
|
|
|
RE_RegisterShaderNoMip
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
For menu graphics that should never be picmiped
|
2023-05-08 19:53:53 +02:00
|
|
|
====================
|
|
|
|
*/
|
2024-12-01 15:26:01 +01:00
|
|
|
qhandle_t RE_RegisterShaderNoMip( const char *name ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
shader_t *sh;
|
|
|
|
|
|
|
|
if ( strlen( name ) >= MAX_QPATH ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
Com_Printf( "Shader name exceeds MAX_QPATH\n" );
|
2023-05-08 19:53:53 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
sh = R_FindShader( name, LIGHTMAP_2D, qfalse, qfalse, qfalse, qfalse );
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
qhandle_t RE_RefreshShaderNoMip(const char* name) {
|
|
|
|
shader_t* sh;
|
|
|
|
char strippedName[64];
|
|
|
|
int hash;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!name || !*name) {
|
2023-05-08 19:53:53 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
COM_StripExtension(name, strippedName, sizeof(strippedName));
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
hash = generateHashValue(strippedName);
|
|
|
|
|
|
|
|
for (currentShader = hashTable[hash]; currentShader; currentShader = currentShader->next) {
|
|
|
|
if (!Q_stricmp(currentShader->name, strippedName)) {
|
|
|
|
if (currentShader) {
|
|
|
|
image_t* image;
|
|
|
|
|
|
|
|
sh = currentShader->shader;
|
|
|
|
image = sh->unfoggedStages[0]->bundle[0].image[0];
|
|
|
|
currentShader = NULL;
|
|
|
|
|
|
|
|
if (image) {
|
|
|
|
sh->unfoggedStages[0]->bundle[0].image[0] = R_RefreshImageFile(
|
|
|
|
image->imgName,
|
|
|
|
image->numMipmaps,
|
|
|
|
image->allowPicmip,
|
|
|
|
image->force32bit,
|
|
|
|
image->wrapClampModeX,
|
|
|
|
image->wrapClampModeY
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return sh->index;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sh = R_FindShader(name, -4, 0, 0, 0, 0);
|
|
|
|
if (sh->defaultShader) {
|
2023-05-08 19:53:53 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
return sh->index;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
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 ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
ri.Printf( PRINT_WARNING, "R_GetShaderByHandle: out of range hShader '%d'\n", hShader ); // bk: FIXME name
|
2023-05-08 19:53:53 +02:00
|
|
|
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++ ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
int stage;
|
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
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, " ");
|
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
|
|
|
|
for (stage = 0; shader->unfoggedStages[stage] && shader->unfoggedStages[stage]->active; stage++) {
|
|
|
|
if (shader->unfoggedStages[stage]->multitextureEnv == GL_ADD ) {
|
|
|
|
ri.Printf( PRINT_ALL, "MT(a) " );
|
|
|
|
} else if (shader->unfoggedStages[stage]->multitextureEnv == GL_MODULATE ) {
|
|
|
|
ri.Printf( PRINT_ALL, "MT(m) " );
|
|
|
|
} else if (shader->unfoggedStages[stage]->multitextureEnv == GL_DECAL ) {
|
|
|
|
ri.Printf( PRINT_ALL, "MT(d) " );
|
|
|
|
} else {
|
|
|
|
ri.Printf( PRINT_ALL, " " );
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
2024-12-01 15:26:01 +01:00
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
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 " );
|
2024-12-01 15:26:01 +01:00
|
|
|
} else if ( shader->optimalStageIteratorFunc == RB_StageIteratorLightmappedMultitextureUnfogged ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
ri.Printf( PRINT_ALL, "lmmt" );
|
2024-12-01 15:26:01 +01:00
|
|
|
} else if ( shader->optimalStageIteratorFunc == RB_StageIteratorVertexLitTextureUnfogged ) {
|
2023-05-08 19:53:53 +02:00
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
|
2023-05-08 19:53:53 +02:00
|
|
|
/*
|
|
|
|
====================
|
|
|
|
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 )
|
|
|
|
{
|
2024-12-01 15:26:01 +01:00
|
|
|
char** shaderFiles;
|
|
|
|
char* buffers[MAX_SHADER_FILES];
|
|
|
|
char* p;
|
|
|
|
int numShaders;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (numShaders > MAX_SHADER_FILES) {
|
|
|
|
numShaders = MAX_SHADER_FILES;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
// build single large buffer
|
|
|
|
s_shaderText = ri.Malloc(sum + numShaders * 2);
|
|
|
|
s_shaderText[0] = 0;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
COM_Compress(s_shaderText);
|
|
|
|
// free up memory
|
|
|
|
ri.FS_FreeFileList(shaderFiles);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
static void FindShadersInShaderText()
|
|
|
|
{
|
|
|
|
char* p;
|
|
|
|
char* oldp;
|
|
|
|
char* token;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (!s_shaderText) {
|
|
|
|
return;
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
p = s_shaderText;
|
2024-12-01 15:26:01 +01:00
|
|
|
// look for label
|
|
|
|
while (1) {
|
|
|
|
oldp = p;
|
|
|
|
token = COM_ParseExt(&p, qtrue);
|
|
|
|
if (token[0] == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*token == '{')
|
|
|
|
{
|
|
|
|
p = oldp;
|
2024-12-01 17:53:42 +01:00
|
|
|
SkipBracedSection(&p, 0);
|
2024-12-01 15:26:01 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currentShader = AllocShaderText(token);
|
|
|
|
currentShader->text = p;
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
====================
|
|
|
|
CreateInternalShaders
|
|
|
|
====================
|
|
|
|
*/
|
|
|
|
static void CreateInternalShaders( void ) {
|
|
|
|
tr.numShaders = 0;
|
|
|
|
|
|
|
|
// init the default shader
|
2024-12-01 15:26:01 +01:00
|
|
|
Com_Memset( &shader, 0, sizeof( shader ) );
|
|
|
|
Com_Memset( &unfoggedStages, 0, sizeof( unfoggedStages ) );
|
|
|
|
|
|
|
|
Q_strncpyz( shader.name, "<default>", sizeof( shader.name ) );
|
|
|
|
shader.lightmapIndex = LIGHTMAP_NONE;
|
|
|
|
unfoggedStages[0].bundle[0].image[0] = tr.defaultImage;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].stateBits = GLS_DEFAULT;
|
|
|
|
currentShader = FindShaderText(shader.name);
|
|
|
|
tr.defaultShader = FinishShader();
|
|
|
|
|
|
|
|
// white shader
|
|
|
|
Q_strncpyz(shader.name, "<white>", sizeof(shader.name));
|
|
|
|
shader.lightmapIndex = LIGHTMAP_NONE;
|
|
|
|
unfoggedStages[0].bundle[0].image[0] = tr.whiteImage;
|
|
|
|
unfoggedStages[0].active = qtrue;
|
|
|
|
unfoggedStages[0].stateBits = GLS_DEFAULT;
|
|
|
|
currentShader = FindShaderText(shader.name);
|
|
|
|
FinishShader();
|
2023-05-08 19:53:53 +02:00
|
|
|
|
|
|
|
// shadow shader is just a marker
|
|
|
|
Q_strncpyz( shader.name, "<stencil shadow>", sizeof( shader.name ) );
|
2024-12-01 15:26:01 +01:00
|
|
|
shader.sort = SS_STENCIL_SHADOW;
|
|
|
|
shader.lightmapIndex = LIGHTMAP_NONE;
|
|
|
|
currentShader = FindShaderText(shader.name);
|
2023-05-08 19:53:53 +02:00
|
|
|
tr.shadowShader = FinishShader();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void CreateExternalShaders( void ) {
|
2024-12-01 15:26:01 +01:00
|
|
|
tr.flareShader = R_FindShader( "flareShader", LIGHTMAP_NONE, qtrue, qtrue, qtrue, qtrue);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
void R_StartupShaders()
|
|
|
|
{
|
|
|
|
ri.Printf(PRINT_ALL, "Initializing Shaders\n");
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
currentShader = NULL;
|
|
|
|
s_shaderText = NULL;
|
|
|
|
|
|
|
|
Com_Memset(hashTable, 0, sizeof(hashTable));
|
|
|
|
|
|
|
|
ScanAndLoadShaderFiles();
|
|
|
|
|
|
|
|
FindShadersInShaderText();
|
|
|
|
|
|
|
|
R_SetupShaders();
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
void R_ShutdownShaders()
|
|
|
|
{
|
|
|
|
shadertext_t* shader, * next;
|
|
|
|
int hash;
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
if (s_shaderText) {
|
|
|
|
ri.Free(s_shaderText);
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
for (hash = 0; hash < FILE_HASH_SIZE; hash++) {
|
|
|
|
for (shader = hashTable[hash]; shader; shader = next) {
|
|
|
|
next = shader->next;
|
|
|
|
ri.Free(shader);
|
|
|
|
}
|
|
|
|
hashTable[hash] = NULL;
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
void R_SetupShaders()
|
|
|
|
{
|
|
|
|
shadertext_t* shader;
|
|
|
|
int hash;
|
|
|
|
|
|
|
|
ri.Printf(0, "Setting up Shaders\n");
|
|
|
|
currentShader = NULL;
|
|
|
|
for (hash = 0; hash < FILE_HASH_SIZE; ++hash)
|
|
|
|
{
|
|
|
|
for (shader = hashTable[hash]; shader; shader = shader->next) {
|
|
|
|
shader->shader = NULL;
|
|
|
|
}
|
|
|
|
}
|
2023-05-08 19:53:53 +02:00
|
|
|
|
2024-12-01 15:26:01 +01:00
|
|
|
CreateInternalShaders();
|
|
|
|
InitStaticShaders();
|
|
|
|
CreateExternalShaders();
|
2023-05-08 19:53:53 +02:00
|
|
|
}
|