// 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 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= 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 ////////////////