Files
Jedi-Academy/tools/ModView/shader.cpp
2013-04-04 14:32:05 -07:00

776 lines
17 KiB
C++

// Filename:- shader.cpp
//
// guess...
#include "stdafx.h"
#include "includes.h"
#include "R_Common.h"
#include "R_Image.h"
#include "files.h"
//
#include "shader.h"
shaderCommands_t tess;
// stuff from lower down, may externalise.
//
char *COM_ParseExt( char **data_p, qboolean allowLineBreaks );
static char *s_shaderText = NULL;
/*
// the shader is parsed into these global variables, then copied into
// dynamically allocated memory if it is valid.
static shaderStage_t stages[MAX_SHADER_STAGES];
static shader_t shader;
static texModInfo_t texMods[MAX_SHADER_STAGES][TR_MAX_TEXMODS];
static qboolean deferLoad;
#define FILE_HASH_SIZE 1024
static shader_t* hashTable[FILE_HASH_SIZE];
//#define SHADERTEXTHASH
#define MAX_SHADERTEXT_HASH 2048
static char **shaderTextHashTable[MAX_SHADERTEXT_HASH];
*/
static char com_token[MAX_TOKEN_CHARS];
static char com_parsename[MAX_TOKEN_CHARS];
static int com_lines;
void COM_BeginParseSession( const char *name )
{
com_lines = 0;
Com_sprintf(com_parsename, sizeof(com_parsename), "%s", name);
}
int COM_GetCurrentParseLine( void )
{
return com_lines;
}
char *COM_Parse( char **data_p )
{
return COM_ParseExt( data_p, qtrue );
}
void COM_ParseError( char *format, ... )
{
va_list argptr;
static char string[4096];
va_start (argptr, format);
vsprintf (string, format, argptr);
va_end (argptr);
Com_Printf("ERROR: %s, line %d: %s\n", com_parsename, com_lines, string);
}
void COM_ParseWarning( char *format, ... )
{
va_list argptr;
static char string[4096];
va_start (argptr, format);
vsprintf (string, format, argptr);
va_end (argptr);
Com_Printf("WARNING: %s, line %d: %s\n", com_parsename, com_lines, string);
}
/*
==============
COM_Parse
Parse a token out of a string
Will never return NULL, just empty strings
If "allowLineBreaks" is qtrue then an empty
string will be returned if the next token is
a newline.
==============
*/
static char *SkipWhitespace( char *data, qboolean *hasNewLines ) {
int c;
while( (c = *data) <= ' ') {
if( !c ) {
return NULL;
}
if( c == '\n' ) {
com_lines++;
*hasNewLines = qtrue;
}
data++;
}
return data;
}
/*
=================
SkipBracedSection
The next token should be an open brace.
Skips until a matching close brace is found.
Internal brace depths are properly skipped.
=================
*/
void SkipBracedSection (char **program) {
char *token;
int depth;
depth = 0;
do {
token = COM_ParseExt( program, qtrue );
if( token[1] == 0 ) {
if( token[0] == '{' ) {
depth++;
}
else if( token[0] == '}' ) {
depth--;
}
}
} while( depth && *program );
}
/*
=================
SkipRestOfLine
=================
*/
void SkipRestOfLine ( char **data ) {
char *p;
int c;
p = *data;
while ( (c = *p++) != 0 ) {
if ( c == '\n' ) {
com_lines++;
break;
}
}
*data = p;
}
char *COM_ParseExt( char **data_p, qboolean allowLineBreaks )
{
int c = 0, len;
qboolean hasNewLines = qfalse;
char *data;
data = *data_p;
len = 0;
com_token[0] = 0;
// make sure incoming data is valid
if ( !data )
{
*data_p = NULL;
return com_token;
}
while ( 1 )
{
// skip whitespace
data = SkipWhitespace( data, &hasNewLines );
if ( !data )
{
*data_p = NULL;
return com_token;
}
if ( hasNewLines && !allowLineBreaks )
{
*data_p = data;
return com_token;
}
c = *data;
// skip double slash comments
if ( c == '/' && data[1] == '/' )
{
data += 2;
while (*data && *data != '\n') {
data++;
}
}
// skip /* */ comments
else if ( c=='/' && data[1] == '*' )
{
data += 2;
while ( *data && ( *data != '*' || data[1] != '/' ) )
{
data++;
}
if ( *data )
{
data += 2;
}
}
else
{
break;
}
}
// handle quoted strings
if (c == '\"')
{
data++;
while (1)
{
c = *data++;
if (c=='\"' || !c)
{
com_token[len] = 0;
*data_p = ( char * ) data;
return com_token;
}
if (len < MAX_TOKEN_CHARS)
{
com_token[len] = c;
len++;
}
}
}
// parse a regular word
do
{
if (len < MAX_TOKEN_CHARS)
{
com_token[len] = c;
len++;
}
data++;
c = *data;
if ( c == '\n' )
com_lines++;
} while (c>32);
if (len == MAX_TOKEN_CHARS)
{
// Com_Printf ("Token exceeded %i chars, discarded.\n", MAX_TOKEN_CHARS);
len = 0;
}
com_token[len] = 0;
*data_p = ( char * ) data;
return com_token;
}
/*
====================
FindShaderInShaderText
Scans the combined text description of all the shader files for
the given shader name.
return NULL if not found
If found, it will return a valid shader
=====================
*/
static char *FindShaderInShaderText( const char *shadername ) {
#ifdef SHADERTEXTHASH
char *token, *p;
int i, hash;
hash = generateHashValue(shadername, MAX_SHADERTEXT_HASH);
for (i = 0; shaderTextHashTable[hash][i]; i++) {
p = shaderTextHashTable[hash][i];
token = COM_ParseExt(&p, qtrue);
if ( !Q_stricmp( token, shadername ) ) {
return p;
}
}
return NULL;
#else
char *p = s_shaderText;
char *token;
if ( !p ) {
return NULL;
}
// look for label
while ( 1 ) {
token = COM_ParseExt( &p, qtrue );
if ( token[0] == 0 ) {
break;
}
if ( !Q_stricmp( token, shadername ) ) {
return p;
}
else {
// skip the definition
SkipBracedSection( &p );
}
}
return NULL;
#endif
}
/*
====================
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 1024
typedef map<string,string> ShadersFoundAndFilesPicked_t;
ShadersFoundAndFilesPicked_t ShadersFoundAndFilesPicked;
void KillAllShaderFiles(void)
{
SAFEFREE(s_shaderText);
ShadersFoundAndFilesPicked.clear();
}
void ScanAndLoadShaderFiles( void )
{
if (s_shaderText == NULL && strlen(gamedir))
{
char **shaderFiles;
char *buffers[MAX_SHADER_FILES];
#ifdef SHADERTEXTHASH
char *p, *oldp, *token, *hashMem;
int shaderTextHashTableSizes[MAX_SHADERTEXT_HASH], hash, size;
#endif
int numShaders;
int i;
long sum = 0;
#define sSHADER_DIR va("%sshaders",gamedir)
// scan for shader files
shaderFiles = //ri.FS_ListFiles( "shaders", ".shader", &numShaders );
Sys_ListFiles( sSHADER_DIR, // const char *directory,
".shader", // const char *extension,
NULL, // char *filter,
&numShaders,// int *numfiles,
qfalse // qboolean wantsubs
);
if ( !shaderFiles || !numShaders )
{
if (!bXMenPathHack)
{
ri.Printf( PRINT_WARNING, "WARNING: no shader files found in '%s'\n",sSHADER_DIR );
}
s_shaderText = CopyString("// blank shader file to avoid re-scanning shaders\n");
return;
}
if ( numShaders > MAX_SHADER_FILES ) {
numShaders = MAX_SHADER_FILES;
}
// load and parse shader files
for ( i = 0; i < numShaders; i++ )
{
char filename[MAX_QPATH];
Com_sprintf( filename, sizeof( filename ), "shaders/%s", shaderFiles[i] );
StatusMessage( va("Loading shader %d/%d: \"%s\"...",i+1,numShaders,filename));
//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 );
}
}
StatusMessage(NULL);
// build single large buffer
s_shaderText = (char *)ri.Hunk_Alloc( sum + numShaders*2 );
// free in reverse order, so the temp files are all dumped
for ( i = numShaders - 1; i >= 0 ; i-- ) {
strcat( s_shaderText, "\n" );
strcat( s_shaderText, buffers[i] );
ri.FS_FreeFile( buffers[i] );
}
// free up memory
//ri.FS_FreeFileList( shaderFiles );
Sys_FreeFileList( shaderFiles );
#ifdef SHADERTEXTHASH
memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
size = 0;
p = s_shaderText;
// look for label
while ( 1 ) {
token = COM_ParseExt( &p, qtrue );
if ( token[0] == 0 ) {
break;
}
hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
shaderTextHashTableSizes[hash]++;
size++;
SkipBracedSection(&p);
}
size += MAX_SHADERTEXT_HASH;
hashMem = (char *)ri.Hunk_Alloc( size * sizeof(char *) );
for (i = 0; i < MAX_SHADERTEXT_HASH; i++) {
shaderTextHashTable[i] = (char **) hashMem;
hashMem = ((char *) hashMem) + ((shaderTextHashTableSizes[i] + 1) * sizeof(char *));
}
memset(shaderTextHashTableSizes, 0, sizeof(shaderTextHashTableSizes));
p = s_shaderText;
// look for label
while ( 1 ) {
oldp = p;
token = COM_ParseExt( &p, qtrue );
if ( token[0] == 0 ) {
break;
}
hash = generateHashValue(token, MAX_SHADERTEXT_HASH);
shaderTextHashTable[hash][shaderTextHashTableSizes[hash]++] = oldp;
SkipBracedSection(&p);
}
#endif
}
}
/*
textures/sfx/dclogo
{
qer_editorimage textures/gothic_floor/largerblock3b.tga
nomipmaps
{
map textures/base_floor/clangdark.tga
rgbGen identity
tcmod scale 4 4
}
{
map $lightmap
rgbGen identity
blendfunc gl_dst_color gl_zero
}
{
clampmap textures/effects/dreamcast-logo2.tga
blendfunc gl_one gl_one
tcmod rotate -75
rgbGen wave sin .75 .25 0 .5
}
// END
}
*/
static bool CheckForFilenameArg(LPCSTR &psSearchPos, LPCSTR psKeyword)
{
LPCSTR psSearchResult = strstr(psSearchPos,psKeyword);
if (psSearchResult && isspace(psSearchResult[strlen(psKeyword)]))
{
psSearchResult += strlen(psKeyword);
while (*psSearchResult && isspace(*psSearchResult)) psSearchResult++;
if (strlen(psSearchResult) && *psSearchResult != '$' && ((psSearchResult>psSearchPos)?isspace(psSearchResult[-1]):1) ) // note error-check on -1 index
{
psSearchPos = psSearchResult;
return true;
}
}
if (psSearchResult)
{
// skip to line end...
//
while (*psSearchResult++ != '\n'){}
psSearchResult++;
psSearchPos = psSearchResult;
}
else
{
psSearchPos = NULL;
}
return false;
}
// have a look at the shader text pointed at by the supplied arg and try and work out
// a suitable image name.
//
// First, see if there's an editorimage,
// else check for a map with a valid texture (ie not '$lightmap' etc)
// else check for a clampmap
// else just return NULL, signifying that I'm stumped...
//
LPCSTR Shader_ExtractSuitableFilename(LPCSTR psShaderText)
{
char *psShaderTextEnd = (char*)psShaderText;
SkipBracedSection (&psShaderTextEnd);
int iShaderChars = psShaderTextEnd - psShaderText;
// make a small string with just this shader in, for easier searching...
//
char *psThisShader = (char *) malloc(iShaderChars+1); // +1 for trailing zero
char *psShaderSearch= psThisShader;
strncpy(psThisShader,psShaderText,iShaderChars);
psThisShader[iShaderChars]='\0';
LPCSTR psAnswer = NULL;
char *psShaderSearchTry = psShaderSearch;
if (CheckForFilenameArg(psShaderSearchTry, "qer_editorimage"))
{
psAnswer = psShaderSearchTry;
}
// these next ones I need to search for multiple times, else it hits things like "q3map_blah",
// doesn't find a space after "map", then gives up, so it should keep trying....
//
psShaderSearchTry = psShaderSearch;
while (psShaderSearchTry && !psAnswer)
{
if (CheckForFilenameArg(psShaderSearchTry, "map"))
{
psAnswer = psShaderSearchTry;
}
}
if (!psAnswer)
{
psShaderSearchTry = psShaderSearch;
while (psShaderSearchTry && !psAnswer)
{
if (CheckForFilenameArg(psShaderSearchTry, "clampmap"))
{
psAnswer = psShaderSearchTry;
}
}
}
static char sReturnName[MAX_QPATH];
if (psAnswer)
{
strncpy(sReturnName,psAnswer,sizeof(sReturnName));
sReturnName[sizeof(sReturnName)-1]='\0';
for (int i=0; i<sizeof(sReturnName)-1; i++)
{
if (sReturnName[i]=='\0')
break;
if (isspace(sReturnName[i]))
{
sReturnName[i] = '\0';
break;
}
}
}
free(psThisShader);
return psAnswer?sReturnName:NULL;
}
// massively hacked-down version of original code, this simply takes an input char * local filename, then
// modifies it to be either the name of the first MAP arg within the corresponding shader file, else leaves
// it alone if not found
const char *R_FindShader( const char *psLocalMaterialName)
{
static char sReturnString[MAX_QPATH];
char strippedName[MAX_QPATH];
/* char fileName[MAX_QPATH];
int i, hash;
*/ char *shaderText;
/* image_t *image;
shader_t *sh;
if ( name[0] == 0 ) {
return tr.defaultShader;
}
// use (fullbright) vertex lighting if the bsp file doesn't have
// lightmaps
if ( lightmapIndex[0] >= 0 && lightmapIndex[0] >= tr.numLightmaps ) {
lightmapIndex[0] = LIGHTMAP_BY_VERTEX;
}
*/
COM_StripExtension( psLocalMaterialName, strippedName );
strlwr(strippedName);
ShadersFoundAndFilesPicked_t::iterator it = ShadersFoundAndFilesPicked.find(strippedName);
if (it != ShadersFoundAndFilesPicked.end())
{
return (*it).second.c_str();
}
/*
hash = generateHashValue(strippedName, FILE_HASH_SIZE);
//
// see if the shader is already loaded
//
for (sh = hashTable[hash]; sh; sh = sh->next) {
// NOTE: if there was no shader or image available with the name strippedName
// then a default shader is created with lightmapIndex == LIGHTMAP_NONE, so we
// have to check all default shaders otherwise for every call to R_FindShader
// with that same strippedName a new default shader is created.
if (IsShader(sh, strippedName, lightmapIndex, styles))
{
return sh;
}
}
*/
/* // make sure the render thread is stopped, because we are probably
// going to have to upload an image
R_SyncRenderThread();
// clear the global shader
ClearGlobalShader();
memset( &stages, 0, sizeof( stages ) );
Q_strncpyz(shader.name, strippedName, sizeof(shader.name));
memcpy(shader.lightmapIndex, lightmapIndex, sizeof(shader.lightmapIndex));
memcpy(shader.styles, styles, sizeof(shader.styles));
for ( i = 0 ; i < MAX_SHADER_STAGES ; i++ ) {
stages[i].bundle[0].texMods = texMods[i];
}
// FIXME: set these "need" values apropriately
shader.needsNormal = qtrue;
shader.needsST1 = qtrue;
shader.needsST2 = qtrue;
shader.needsColor = qtrue;
*/
//
// attempt to define shader from an explicit parameter file
//
shaderText = FindShaderInShaderText( strippedName );
if ( shaderText )
{
/* // enable this when building a pak file to get a global list
// of all explicit shaders
if ( r_printShaders->integer ) {
ri.Printf( PRINT_ALL, "*SHADER* %s\n", name );
}
if ( !ParseShader( &shaderText ) ) {
// had errors, so use default shader
shader.defaultShader = qtrue;
}
sh = FinishShader();
return sh;
*/
LPCSTR psReturnName = Shader_ExtractSuitableFilename(shaderText);
if (psReturnName)
{
psLocalMaterialName = psReturnName;
}
}
ShadersFoundAndFilesPicked[strippedName] = psLocalMaterialName;
return psLocalMaterialName; // return original name
/*
//
// if not defined in the in-memory shader descriptions,
// look for a single TGA, BMP, or PCX
//
Q_strncpyz( fileName, name, sizeof( fileName ) );
image = R_FindImageFile( fileName, mipRawImage, mipRawImage, mipRawImage ? GL_REPEAT : GL_CLAMP );
if ( !image ) {
ri.Printf( PRINT_DEVELOPER, "Couldn't find image for shader %s\n", name );
shader.defaultShader = qtrue;
return FinishShader();
}
//
// create the default shading commands
//
if ( shader.lightmapIndex[0] == LIGHTMAP_NONE ) {
// dynamic colors at vertexes
stages[0].bundle[0].image[0] = image;
stages[0].active = qtrue;
stages[0].rgbGen = CGEN_LIGHTING_DIFFUSE;
stages[0].stateBits = GLS_DEFAULT;
} else if ( shader.lightmapIndex[0] == LIGHTMAP_BY_VERTEX ) {
// explicit colors at vertexes
stages[0].bundle[0].image[0] = image;
stages[0].active = qtrue;
stages[0].rgbGen = CGEN_EXACT_VERTEX;
stages[0].alphaGen = AGEN_SKIP;
stages[0].stateBits = GLS_DEFAULT;
} else if ( shader.lightmapIndex[0] == LIGHTMAP_2D ) {
// GUI elements
stages[0].bundle[0].image[0] = image;
stages[0].active = qtrue;
stages[0].rgbGen = CGEN_VERTEX;
stages[0].alphaGen = AGEN_VERTEX;
stages[0].stateBits = GLS_DEPTHTEST_DISABLE |
GLS_SRCBLEND_SRC_ALPHA |
GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA;
} else if ( shader.lightmapIndex[0] == LIGHTMAP_WHITEIMAGE ) {
// fullbright level
stages[0].bundle[0].image[0] = tr.whiteImage;
stages[0].active = qtrue;
stages[0].rgbGen = CGEN_IDENTITY_LIGHTING;
stages[0].stateBits = GLS_DEFAULT;
stages[1].bundle[0].image[0] = image;
stages[1].active = qtrue;
stages[1].rgbGen = CGEN_IDENTITY;
stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
} else {
// two pass lightmap
stages[0].bundle[0].image[0] = tr.lightmaps[shader.lightmapIndex[0]];
stages[0].bundle[0].isLightmap = qtrue;
stages[0].active = qtrue;
stages[0].rgbGen = CGEN_IDENTITY; // lightmaps are scaled on creation
// for identitylight
stages[0].stateBits = GLS_DEFAULT;
stages[1].bundle[0].image[0] = image;
stages[1].active = qtrue;
stages[1].rgbGen = CGEN_IDENTITY;
stages[1].stateBits |= GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO;
}
return FinishShader();
*/
}
////////////////// eof ////////////////