dhewm3/neo/renderer/draw_arb2.cpp
Daniel Gibson 78eef43d50 Fix rendering of mars globe in main menu with some drivers
The problem was that negative values (from dhewm3tmpres.xyz) were passed
to POW, and POW doesn't have to support negative bases, according to
ARB_fragment_program.txt, and Intels Linux drive apparently doesn't,
see also https://gitlab.freedesktop.org/mesa/mesa/-/issues/5131

Using MUL_SAT instead of MUL to clamp the value that gets passed to POW
afterwards to [0, 1] fixes the problem without any disadvantages.
2021-08-17 19:43:01 +02:00

673 lines
22 KiB
C++

/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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;
strncpy( progs[i].name, program, sizeof( progs[i].name ) - 1 );
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;
}