dhewm3/neo/renderer/draw_arb2.cpp
2012-07-20 00:12:55 +02:00

520 lines
17 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() );
// 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_R200_INTERACTION, "R200_interaction.vp" },
{ GL_VERTEX_PROGRAM_ARB, VPROG_NV20_BUMP_AND_LIGHT, "nv20_bumpAndLight.vp" },
{ GL_VERTEX_PROGRAM_ARB, VPROG_NV20_DIFFUSE_COLOR, "nv20_diffuseColor.vp" },
{ GL_VERTEX_PROGRAM_ARB, VPROG_NV20_SPECULAR_COLOR, "nv20_specularColor.vp" },
{ GL_VERTEX_PROGRAM_ARB, VPROG_NV20_DIFFUSE_AND_SPECULAR_COLOR, "nv20_diffuseAndSpecularColor.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
=================
*/
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;
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 {
common->Printf( "error at %i:\n%s", ofs, start + ofs );
}
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;
}