/* =========================================================================== Doom 3 GPL Source Code Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). Doom 3 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 3 of the License, or (at your option) any later version. Doom 3 Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 Source Code. If not, see . In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #include "sys/platform.h" #include "renderer/VertexCache.h" #include "renderer/tr_local.h" /* ========================================================================================= GENERAL INTERACTION RENDERING ========================================================================================= */ /* ==================== GL_SelectTextureNoClient ==================== */ static void GL_SelectTextureNoClient( int unit ) { backEnd.glState.currenttmu = unit; qglActiveTextureARB( GL_TEXTURE0_ARB + unit ); } /* ================== RB_ARB2_DrawInteraction ================== */ void RB_ARB2_DrawInteraction( const drawInteraction_t *din ) { // load all the vertex program parameters qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_ORIGIN, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_VIEW_ORIGIN, din->localViewOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_S, din->lightProjection[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_T, din->lightProjection[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_PROJECT_Q, din->lightProjection[2].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_LIGHT_FALLOFF_S, din->lightProjection[3].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_S, din->bumpMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_BUMP_MATRIX_T, din->bumpMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_S, din->diffuseMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_DIFFUSE_MATRIX_T, din->diffuseMatrix[1].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_S, din->specularMatrix[0].ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_SPECULAR_MATRIX_T, din->specularMatrix[1].ToFloatPtr() ); // testing fragment based normal mapping if ( r_testARBProgram.GetBool() ) { qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 2, din->localLightOrigin.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 3, din->localViewOrigin.ToFloatPtr() ); } static const float zero[4] = { 0, 0, 0, 0 }; static const float one[4] = { 1, 1, 1, 1 }; static const float negOne[4] = { -1, -1, -1, -1 }; switch ( din->vertexColor ) { case SVC_IGNORE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, zero ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; case SVC_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, one ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, zero ); break; case SVC_INVERSE_MODULATE: qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_MODULATE, negOne ); qglProgramEnvParameter4fvARB( GL_VERTEX_PROGRAM_ARB, PP_COLOR_ADD, one ); break; } // set the constant colors qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 0, din->diffuseColor.ToFloatPtr() ); qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 1, din->specularColor.ToFloatPtr() ); // DG: brightness and gamma in shader as program.env[4] if ( r_gammaInShader.GetBool() ) { // program.env[4].xyz are all r_brightness, program.env[4].w is 1.0/r_gamma float parm[4]; parm[0] = parm[1] = parm[2] = r_brightness.GetFloat(); parm[3] = 1.0/r_gamma.GetFloat(); // 1.0/gamma so the shader doesn't have to do this calculation qglProgramEnvParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 4, parm ); } // set the textures // texture 1 will be the per-surface bump map GL_SelectTextureNoClient( 1 ); din->bumpImage->Bind(); // texture 2 will be the light falloff texture GL_SelectTextureNoClient( 2 ); din->lightFalloffImage->Bind(); // texture 3 will be the light projection texture GL_SelectTextureNoClient( 3 ); din->lightImage->Bind(); // texture 4 is the per-surface diffuse map GL_SelectTextureNoClient( 4 ); din->diffuseImage->Bind(); // texture 5 is the per-surface specular map GL_SelectTextureNoClient( 5 ); din->specularImage->Bind(); // draw it RB_DrawElementsWithCounters( din->surf->geo ); } /* ============= RB_ARB2_CreateDrawInteractions ============= */ void RB_ARB2_CreateDrawInteractions( const drawSurf_t *surf ) { if ( !surf ) { return; } // perform setup here that will be constant for all interactions GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHMASK | backEnd.depthFunc ); // bind the vertex program if ( r_testARBProgram.GetBool() ) { qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_TEST ); qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_TEST ); } else { qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_INTERACTION ); qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, FPROG_INTERACTION ); } qglEnable(GL_VERTEX_PROGRAM_ARB); qglEnable(GL_FRAGMENT_PROGRAM_ARB); // enable the vertex arrays qglEnableVertexAttribArrayARB( 8 ); qglEnableVertexAttribArrayARB( 9 ); qglEnableVertexAttribArrayARB( 10 ); qglEnableVertexAttribArrayARB( 11 ); qglEnableClientState( GL_COLOR_ARRAY ); // texture 0 is the normalization cube map for the vector towards the light GL_SelectTextureNoClient( 0 ); if ( backEnd.vLight->lightShader->IsAmbientLight() ) { globalImages->ambientNormalMap->Bind(); } else { globalImages->normalCubeMapImage->Bind(); } // texture 6 is the specular lookup table GL_SelectTextureNoClient( 6 ); if ( r_testARBProgram.GetBool() ) { globalImages->specular2DTableImage->Bind(); // variable specularity in alpha channel } else { globalImages->specularTableImage->Bind(); } for ( ; surf ; surf=surf->nextOnLight ) { // perform setup here that will not change over multiple interaction passes // set the vertex pointers idDrawVert *ac = (idDrawVert *)vertexCache.Position( surf->geo->ambientCache ); qglColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( idDrawVert ), ac->color ); qglVertexAttribPointerARB( 11, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->normal.ToFloatPtr() ); qglVertexAttribPointerARB( 10, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[1].ToFloatPtr() ); qglVertexAttribPointerARB( 9, 3, GL_FLOAT, false, sizeof( idDrawVert ), ac->tangents[0].ToFloatPtr() ); qglVertexAttribPointerARB( 8, 2, GL_FLOAT, false, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); // this may cause RB_ARB2_DrawInteraction to be exacuted multiple // times with different colors and images if the surface or light have multiple layers RB_CreateSingleDrawInteractions( surf, RB_ARB2_DrawInteraction ); } qglDisableVertexAttribArrayARB( 8 ); qglDisableVertexAttribArrayARB( 9 ); qglDisableVertexAttribArrayARB( 10 ); qglDisableVertexAttribArrayARB( 11 ); qglDisableClientState( GL_COLOR_ARRAY ); // disable features GL_SelectTextureNoClient( 6 ); globalImages->BindNull(); GL_SelectTextureNoClient( 5 ); globalImages->BindNull(); GL_SelectTextureNoClient( 4 ); globalImages->BindNull(); GL_SelectTextureNoClient( 3 ); globalImages->BindNull(); GL_SelectTextureNoClient( 2 ); globalImages->BindNull(); GL_SelectTextureNoClient( 1 ); globalImages->BindNull(); backEnd.glState.currenttmu = -1; GL_SelectTexture( 0 ); qglDisable(GL_VERTEX_PROGRAM_ARB); qglDisable(GL_FRAGMENT_PROGRAM_ARB); } /* ================== RB_ARB2_DrawInteractions ================== */ void RB_ARB2_DrawInteractions( void ) { viewLight_t *vLight; GL_SelectTexture( 0 ); qglDisableClientState( GL_TEXTURE_COORD_ARRAY ); // // for each light, perform adding and shadowing // for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { backEnd.vLight = vLight; // do fogging later if ( vLight->lightShader->IsFogLight() ) { continue; } if ( vLight->lightShader->IsBlendLight() ) { continue; } if ( !vLight->localInteractions && !vLight->globalInteractions && !vLight->translucentInteractions ) { continue; } // clear the stencil buffer if needed if ( vLight->globalShadows || vLight->localShadows ) { backEnd.currentScissor = vLight->scissorRect; if ( r_useScissor.GetBool() ) { qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); } qglClear( GL_STENCIL_BUFFER_BIT ); } else { // no shadows, so no need to read or write the stencil buffer // we might in theory want to use GL_ALWAYS instead of disabling // completely, to satisfy the invarience rules qglStencilFunc( GL_ALWAYS, 128, 255 ); } if ( r_useShadowVertexProgram.GetBool() ) { qglEnable( GL_VERTEX_PROGRAM_ARB ); qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); RB_StencilShadowPass( vLight->globalShadows ); RB_ARB2_CreateDrawInteractions( vLight->localInteractions ); qglEnable( GL_VERTEX_PROGRAM_ARB ); qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW ); RB_StencilShadowPass( vLight->localShadows ); RB_ARB2_CreateDrawInteractions( vLight->globalInteractions ); qglDisable( GL_VERTEX_PROGRAM_ARB ); // if there weren't any globalInteractions, it would have stayed on } else { RB_StencilShadowPass( vLight->globalShadows ); RB_ARB2_CreateDrawInteractions( vLight->localInteractions ); RB_StencilShadowPass( vLight->localShadows ); RB_ARB2_CreateDrawInteractions( vLight->globalInteractions ); } // translucent surfaces never get stencil shadowed if ( r_skipTranslucent.GetBool() ) { continue; } qglStencilFunc( GL_ALWAYS, 128, 255 ); backEnd.depthFunc = GLS_DEPTHFUNC_LESS; RB_ARB2_CreateDrawInteractions( vLight->translucentInteractions ); backEnd.depthFunc = GLS_DEPTHFUNC_EQUAL; } // disable stencil shadow test qglStencilFunc( GL_ALWAYS, 128, 255 ); GL_SelectTexture( 0 ); qglEnableClientState( GL_TEXTURE_COORD_ARRAY ); } //=================================================================================== typedef struct { GLenum target; GLuint ident; char name[64]; } progDef_t; static const int MAX_GLPROGS = 200; // a single file can have both a vertex program and a fragment program static progDef_t progs[MAX_GLPROGS] = { { GL_VERTEX_PROGRAM_ARB, VPROG_TEST, "test.vfp" }, { GL_FRAGMENT_PROGRAM_ARB, FPROG_TEST, "test.vfp" }, { GL_VERTEX_PROGRAM_ARB, VPROG_INTERACTION, "interaction.vfp" }, { GL_FRAGMENT_PROGRAM_ARB, FPROG_INTERACTION, "interaction.vfp" }, { GL_VERTEX_PROGRAM_ARB, VPROG_BUMPY_ENVIRONMENT, "bumpyEnvironment.vfp" }, { GL_FRAGMENT_PROGRAM_ARB, FPROG_BUMPY_ENVIRONMENT, "bumpyEnvironment.vfp" }, { GL_VERTEX_PROGRAM_ARB, VPROG_AMBIENT, "ambientLight.vfp" }, { GL_FRAGMENT_PROGRAM_ARB, FPROG_AMBIENT, "ambientLight.vfp" }, { GL_VERTEX_PROGRAM_ARB, VPROG_STENCIL_SHADOW, "shadow.vp" }, { GL_VERTEX_PROGRAM_ARB, VPROG_ENVIRONMENT, "environment.vfp" }, { GL_FRAGMENT_PROGRAM_ARB, FPROG_ENVIRONMENT, "environment.vfp" }, { GL_VERTEX_PROGRAM_ARB, VPROG_GLASSWARP, "arbVP_glasswarp.txt" }, { GL_FRAGMENT_PROGRAM_ARB, FPROG_GLASSWARP, "arbFP_glasswarp.txt" }, // additional programs can be dynamically specified in materials }; /* ================= R_LoadARBProgram ================= */ static char* findLineThatStartsWith( char* text, const char* findMe ) { char* res = strstr( text, findMe ); while ( res != NULL ) { // skip whitespace before match, if any char* cur = res; if ( cur > text ) { --cur; } while ( cur > text && ( *cur == ' ' || *cur == '\t' ) ) { --cur; } // now we should be at a newline (or at the beginning) if ( cur == text ) { return cur; } if ( *cur == '\n' || *cur == '\r' ) { return cur+1; } // otherwise maybe we're in commented out text or whatever, search on res = strstr( res+1, findMe ); } return NULL; } static ID_INLINE bool isARBidentifierChar( int c ) { // according to chapter 3.11.2 in ARB_fragment_program.txt identifiers can only // contain these chars (first char mustn't be a number, but that doesn't matter here) // NOTE: isalnum() or isalpha() apparently doesn't work, as it also matches spaces (?!) return c == '$' || c == '_' || (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } void R_LoadARBProgram( int progIndex ) { int ofs; int err; idStr fullPath = "glprogs/"; fullPath += progs[progIndex].name; char *fileBuffer; char *buffer; char *start = NULL, *end; common->Printf( "%s", fullPath.c_str() ); // load the program even if we don't support it, so // fs_copyfiles can generate cross-platform data dumps fileSystem->ReadFile( fullPath.c_str(), (void **)&fileBuffer, NULL ); if ( !fileBuffer ) { common->Printf( ": File not found\n" ); return; } // copy to stack memory and free buffer = (char *)_alloca( strlen( fileBuffer ) + 1 ); strcpy( buffer, fileBuffer ); fileSystem->FreeFile( fileBuffer ); if ( !glConfig.isInitialized ) { return; } // // submit the program string at start to GL // if ( progs[progIndex].ident == 0 ) { // allocate a new identifier for this program progs[progIndex].ident = PROG_USER + progIndex; } // vertex and fragment programs can both be present in a single file, so // scan for the proper header to be the start point, and stamp a 0 in after the end if ( progs[progIndex].target == GL_VERTEX_PROGRAM_ARB ) { if ( !glConfig.ARBVertexProgramAvailable ) { common->Printf( ": GL_VERTEX_PROGRAM_ARB not available\n" ); return; } start = strstr( buffer, "!!ARBvp" ); } if ( progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) { if ( !glConfig.ARBFragmentProgramAvailable ) { common->Printf( ": GL_FRAGMENT_PROGRAM_ARB not available\n" ); return; } start = strstr( buffer, "!!ARBfp" ); } if ( !start ) { common->Printf( ": !!ARB not found\n" ); return; } end = strstr( start, "END" ); if ( !end ) { common->Printf( ": END not found\n" ); return; } end[3] = 0; // DG: hack gamma correction into shader if ( r_gammaInShader.GetBool() && progs[progIndex].target == GL_FRAGMENT_PROGRAM_ARB ) { // note that strlen("dhewm3tmpres") == strlen("result.color") const char* tmpres = "TEMP dhewm3tmpres; # injected by dhewm3 for gamma correction\n"; // Note: program.env[4].xyz = r_brightness; program.env[4].w = 1.0/r_gamma // outColor.rgb = pow(dhewm3tmpres.rgb*r_brightness, vec3(1.0/r_gamma)) // outColor.a = dhewm3tmpres.a; const char* extraLines = "# gamma correction in shader, injected by dhewm3 \n" // MUL_SAT clamps the result to [0, 1] - it must not be negative because // POW might not work with a negative base (it looks wrong with intel's Linux driver) // and clamping values >1 to 1 is ok because when writing to result.color // it's clamped anyway and pow(base, exp) is always >= 1 for base >= 1 "MUL_SAT dhewm3tmpres.xyz, program.env[4], dhewm3tmpres;\n" // first multiply with brightness "POW result.color.x, dhewm3tmpres.x, program.env[4].w;\n" // then do pow(dhewm3tmpres.xyz, vec3(1/gamma)) "POW result.color.y, dhewm3tmpres.y, program.env[4].w;\n" // (apparently POW only supports scalars, not whole vectors) "POW result.color.z, dhewm3tmpres.z, program.env[4].w;\n" "MOV result.color.w, dhewm3tmpres.w;\n" // alpha remains unmodified "\nEND\n\n"; // we add this block right at the end, replacing the original "END" string int fullLen = strlen( start ) + strlen( tmpres ) + strlen( extraLines ); char* outStr = (char*)_alloca( fullLen + 1 ); // add tmpres right after OPTION line (if any) char* insertPos = findLineThatStartsWith( start, "OPTION" ); if ( insertPos == NULL ) { // no OPTION? then just put it after the first line (usually sth like "!!ARBfp1.0\n") insertPos = start; } // but we want the position *after* that line while( *insertPos != '\0' && *insertPos != '\n' && *insertPos != '\r' ) { ++insertPos; } // skip the newline character(s) as well while( *insertPos == '\n' || *insertPos == '\r' ) { ++insertPos; } // copy text up to insertPos int curLen = insertPos-start; memcpy( outStr, start, curLen ); // copy tmpres ("TEMP dhewm3tmpres; # ..") memcpy( outStr+curLen, tmpres, strlen( tmpres ) ); curLen += strlen( tmpres ); // copy remaining original shader up to (excluding) "END" int remLen = end - insertPos; memcpy( outStr+curLen, insertPos, remLen ); curLen += remLen; outStr[curLen] = '\0'; // make sure it's NULL-terminated so normal string functions work // replace all existing occurrences of "result.color" with "dhewm3tmpres" for( char* resCol = strstr( outStr, "result.color" ); resCol != NULL; resCol = strstr( resCol+13, "result.color" ) ) { memcpy( resCol, "dhewm3tmpres", 12 ); // both strings have the same length. // if this was part of "OUTPUT bla = result.color;", replace // "OUTPUT bla" with "ALIAS bla" (so it becomes "ALIAS bla = dhewm3tmpres;") { char* s = resCol - 1; // first skip whitespace before "result.color" while( s > outStr && (*s == ' ' || *s == '\t') ) { --s; } // if there's no '=' before result.color, this line can't be affected if ( *s != '=' || s <= outStr + 8 ) { continue; // go on with next "result.color" in the for-loop } --s; // we were on '=', so go to the char before and it's time to skip whitespace again while( s > outStr && ( *s == ' ' || *s == '\t' ) ) { --s; } // now we should be at the end of "bla" (or however the variable/alias is called) if ( s <= outStr+7 || !isARBidentifierChar( *s ) ) { continue; } --s; // skip all the remaining chars that are legal in identifiers while( s > outStr && isARBidentifierChar( *s ) ) { --s; } // there should be at least one space/tab between "OUTPUT" and "bla" if ( s <= outStr + 6 || ( *s != ' ' && *s != '\t' ) ) { continue; } --s; // skip remaining whitespace (if any) while( s > outStr && ( *s == ' ' || *s == '\t' ) ) { --s; } // now we should be at "OUTPUT" (specifically at its last 'T'), // if this is indeed such a case if ( s <= outStr + 5 || *s != 'T' ) { continue; } s -= 5; // skip to start of "OUTPUT", if this is indeed "OUTPUT" if ( idStr::Cmpn( s, "OUTPUT", 6 ) == 0 ) { // it really is "OUTPUT" => replace "OUTPUT" with "ALIAS " memcpy(s, "ALIAS ", 6); } } } assert( curLen + strlen( extraLines ) <= fullLen ); // now add extraLines that calculate and set a gamma-corrected result.color // strcat() should be safe because fullLen was calculated taking all parts into account strcat( outStr, extraLines ); start = outStr; } qglBindProgramARB( progs[progIndex].target, progs[progIndex].ident ); qglGetError(); qglProgramStringARB( progs[progIndex].target, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( start ), start ); err = qglGetError(); qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, (GLint *)&ofs ); if ( err == GL_INVALID_OPERATION ) { const GLubyte *str = qglGetString( GL_PROGRAM_ERROR_STRING_ARB ); common->Printf( "\nGL_PROGRAM_ERROR_STRING_ARB: %s\n", str ); if ( ofs < 0 ) { common->Printf( "GL_PROGRAM_ERROR_POSITION_ARB < 0 with error\n" ); } else if ( ofs >= (int)strlen( start ) ) { common->Printf( "error at end of program\n" ); } else { int printOfs = Max( ofs - 20, 0 ); // DG: print some more context common->Printf( "error at %i:\n%s", ofs, start + printOfs ); } return; } if ( ofs != -1 ) { common->Printf( "\nGL_PROGRAM_ERROR_POSITION_ARB != -1 without error\n" ); return; } common->Printf( "\n" ); } /* ================== R_FindARBProgram Returns a GL identifier that can be bound to the given target, parsing a text file if it hasn't already been loaded. ================== */ int R_FindARBProgram( GLenum target, const char *program ) { int i; idStr stripped = program; stripped.StripFileExtension(); // see if it is already loaded for ( i = 0 ; progs[i].name[0] ; i++ ) { if ( progs[i].target != target ) { continue; } idStr compare = progs[i].name; compare.StripFileExtension(); if ( !idStr::Icmp( stripped.c_str(), compare.c_str() ) ) { return progs[i].ident; } } if ( i == MAX_GLPROGS ) { common->Error( "R_FindARBProgram: MAX_GLPROGS" ); } // add it to the list and load it progs[i].ident = (program_t)0; // will be gen'd by R_LoadARBProgram progs[i].target = target; idStr::Copynz( progs[i].name, program, sizeof( progs[i].name ) ); R_LoadARBProgram( i ); return progs[i].ident; } /* ================== R_ReloadARBPrograms_f ================== */ void R_ReloadARBPrograms_f( const idCmdArgs &args ) { int i; common->Printf( "----- R_ReloadARBPrograms -----\n" ); for ( i = 0 ; progs[i].name[0] ; i++ ) { R_LoadARBProgram( i ); } } /* ================== R_ARB2_Init ================== */ void R_ARB2_Init( void ) { glConfig.allowARB2Path = false; common->Printf( "ARB2 renderer: " ); if ( !glConfig.ARBVertexProgramAvailable || !glConfig.ARBFragmentProgramAvailable ) { common->Printf( "Not available.\n" ); return; } common->Printf( "Available.\n" ); glConfig.allowARB2Path = true; }