Paranoia2_original/cl_dll/render/gl_shader.cpp

1357 lines
42 KiB
C++

/*
gl_shader.cpp - glsl shaders
Copyright (C) 2014 Uncle Mike
This program 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.
This program 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.
*/
#include "hud.h"
#include "cl_util.h"
#include "const.h"
#include "ref_params.h"
#include "gl_local.h"
#include <mathlib.h>
#include <stringlib.h>
#include "gl_shader.h"
#include "virtualfs.h"
#include "gl_world.h"
#include "gl_decals.h"
#include "studio.h"
#include "gl_grass.h"
//#define _DEBUG_UNIFORMS
#define SHADERS_HASH_SIZE (MAX_GLSL_PROGRAMS >> 2)
#define MAX_FILE_STACK 64
static char filenames_stack[MAX_FILE_STACK][256];
glsl_program_t glsl_programs[MAX_GLSL_PROGRAMS];
glsl_program_t *glsl_programsHashTable[SHADERS_HASH_SIZE];
int num_glsl_programs;
static int file_stack_pos;
static bool cache_needs_update = false;
typedef struct
{
const char *name;
uniformType_t type;
int flags; // hints
} uniformTable_t;
glsl_program_t *shader_t :: GetShader( void )
{
return &glsl_programs[shadernum];
}
void shader_t :: SetShader( unsigned short hand )
{
sequence = tr.glsl_valid_sequence;
shadernum = hand;
}
bool shader_t :: IsValid( void )
{
return (shadernum > 0) && (sequence == tr.glsl_valid_sequence);
}
int uniform_t :: GetSizeInBytes( void )
{
switch( format )
{
case GL_SAMPLER_1D_ARB:
case GL_SAMPLER_2D_ARB:
case GL_SAMPLER_3D_ARB:
case GL_SAMPLER_CUBE_ARB:
case GL_SAMPLER_1D_SHADOW_ARB:
case GL_SAMPLER_2D_SHADOW_ARB:
case GL_SAMPLER_2D_RECT_ARB:
case GL_SAMPLER_2D_RECT_SHADOW_ARB:
case GL_SAMPLER_CUBE_SHADOW_EXT:
return 4;
case GL_FLOAT:
case GL_INT:
case GL_UNSIGNED_INT:
return 4;
case GL_FLOAT_VEC2_ARB:
case GL_INT_VEC2_ARB:
return 8;
case GL_FLOAT_VEC3_ARB:
case GL_INT_VEC3_ARB:
return 12;
case GL_FLOAT_VEC4_ARB:
case GL_INT_VEC4_ARB:
return 16;
case GL_FLOAT_MAT2_ARB:
return 16;
case GL_FLOAT_MAT3_ARB:
return 36;
case GL_FLOAT_MAT4_ARB:
return 64;
}
ALERT( at_error, "uniform %s has unspecified size\n", name );
return 0; // assume error
}
void uniform_t :: SetValue( const void *pdata, int count )
{
unicache_t *check = (unicache_t *)pdata;
// set texture unit
if( FBitSet( flags, UFL_TEXTURE_UNIT ) && unit >= 0 )
{
GL_BindTexture( unit, check->iValue[0] );
}
else if( size > 1 )
{
// handle arrays
if( count == -1 )
count = size;
switch( format )
{
case GL_FLOAT:
pglUniform1fvARB( location, count, (const float *)pdata );
break;
case GL_FLOAT_VEC2_ARB:
pglUniform2fvARB( location, count, (const float *)pdata );
break;
case GL_FLOAT_VEC3_ARB:
pglUniform3fvARB( location, count, (const float *)pdata );
break;
case GL_FLOAT_VEC4_ARB:
pglUniform4fvARB( location, count, (const float *)pdata );
break;
case GL_FLOAT_MAT2_ARB:
pglUniformMatrix2fvARB( location, count, GL_FALSE, (const float *)pdata );
break;
case GL_FLOAT_MAT3_ARB:
pglUniformMatrix3fvARB( location, count, GL_FALSE, (const float *)pdata );
break;
case GL_FLOAT_MAT4_ARB:
pglUniformMatrix4fvARB( location, count, GL_FALSE, (const float *)pdata );
break;
}
}
else
{
int testSize = GetSizeInBytes();
// some values could be cached
if( testSize <= 16 && !memcmp( &cache, check, testSize ))
return;
// single cached values
switch( format )
{
case GL_FLOAT:
pglUniform1fARB( location, check->fValue[0] );
cache.fValue[0] = check->fValue[0];
break;
case GL_FLOAT_VEC2_ARB:
pglUniform2fARB( location, check->fValue[0], check->fValue[1] );
cache.fValue[0] = check->fValue[0];
cache.fValue[1] = check->fValue[1];
break;
case GL_FLOAT_VEC3_ARB:
pglUniform3fARB( location, check->fValue[0], check->fValue[1], check->fValue[2] );
cache.fValue[0] = check->fValue[0];
cache.fValue[1] = check->fValue[1];
cache.fValue[2] = check->fValue[2];
break;
case GL_FLOAT_VEC4_ARB:
pglUniform4fARB( location, check->fValue[0], check->fValue[1], check->fValue[2], check->fValue[3] );
cache.fValue[0] = check->fValue[0];
cache.fValue[1] = check->fValue[1];
cache.fValue[2] = check->fValue[2];
cache.fValue[3] = check->fValue[3];
break;
case GL_INT:
pglUniform1iARB( location, check->iValue[0] );
cache.iValue[0] = check->iValue[0];
break;
case GL_INT_VEC2_ARB:
pglUniform2iARB( location, check->iValue[0], check->iValue[1] );
cache.iValue[0] = check->iValue[0];
cache.iValue[1] = check->iValue[1];
break;
case GL_INT_VEC3_ARB:
pglUniform3iARB( location, check->iValue[0], check->iValue[1], check->iValue[2] );
cache.iValue[0] = check->iValue[0];
cache.iValue[1] = check->iValue[1];
cache.iValue[2] = check->iValue[2];
break;
case GL_INT_VEC4_ARB:
pglUniform4iARB( location, check->iValue[0], check->iValue[1], check->iValue[2], check->iValue[3] );
cache.iValue[0] = check->iValue[0];
cache.iValue[1] = check->iValue[1];
cache.iValue[2] = check->iValue[2];
cache.iValue[3] = check->iValue[3];
break;
case GL_FLOAT_MAT2_ARB:
pglUniformMatrix2fvARB( location, 1, GL_FALSE, (const float *)pdata );
cache.fValue[0] = check->fValue[0];
cache.fValue[1] = check->fValue[1];
cache.fValue[2] = check->fValue[2];
cache.fValue[3] = check->fValue[3];
break;
case GL_FLOAT_MAT3_ARB:
pglUniformMatrix3fvARB( location, 1, GL_FALSE, (const float *)pdata );
break;
case GL_FLOAT_MAT4_ARB:
pglUniformMatrix4fvARB( location, 1, GL_FALSE, (const float *)pdata );
break;
}
}
}
// all known engine uniforms
static uniformTable_t glsl_uniformTable[] =
{
{ "u_ColorMap", UT_COLORMAP, UFL_TEXTURE_UNIT },
{ "u_DepthMap", UT_DEPTHMAP, UFL_TEXTURE_UNIT },
{ "u_NormalMap", UT_NORMALMAP, UFL_TEXTURE_UNIT },
{ "u_GlossMap", UT_GLOSSMAP, UFL_TEXTURE_UNIT },
{ "u_DetailMap", UT_DETAILMAP, UFL_TEXTURE_UNIT },
{ "u_ProjectMap", UT_PROJECTMAP, UFL_TEXTURE_UNIT },
{ "u_ShadowMap0", UT_SHADOWMAP0, UFL_TEXTURE_UNIT },
{ "u_ShadowMap1", UT_SHADOWMAP1, UFL_TEXTURE_UNIT },
{ "u_ShadowMap2", UT_SHADOWMAP2, UFL_TEXTURE_UNIT },
{ "u_ShadowMap3", UT_SHADOWMAP3, UFL_TEXTURE_UNIT },
{ "u_ShadowMap", UT_SHADOWMAP, UFL_TEXTURE_UNIT },
{ "u_LightMap", UT_LIGHTMAP, UFL_TEXTURE_UNIT },
{ "u_DeluxeMap", UT_DELUXEMAP, UFL_TEXTURE_UNIT },
{ "u_DecalMap", UT_DECALMAP, UFL_TEXTURE_UNIT },
{ "u_ScreenMap", UT_SCREENMAP, UFL_TEXTURE_UNIT },
{ "u_VisLightMap0", UT_VISLIGHTMAP0, UFL_TEXTURE_UNIT },
{ "u_VisLightMap1", UT_VISLIGHTMAP1, UFL_TEXTURE_UNIT },
{ "u_EnvMap0", UT_ENVMAP0, UFL_TEXTURE_UNIT },
{ "u_EnvMap1", UT_ENVMAP1, UFL_TEXTURE_UNIT },
{ "u_EnvMap", UT_ENVMAP, UFL_TEXTURE_UNIT },
{ "u_GlowMap", UT_GLOWMAP, UFL_TEXTURE_UNIT },
{ "u_HeightMap", UT_HEIGHTMAP, UFL_TEXTURE_UNIT },
{ "u_LayerMap", UT_LAYERMAP, UFL_TEXTURE_UNIT },
{ "u_FragData0", UT_FRAGDATA0, UFL_TEXTURE_UNIT },
{ "u_FragData1", UT_FRAGDATA1, UFL_TEXTURE_UNIT },
{ "u_FragData2", UT_FRAGDATA2, UFL_TEXTURE_UNIT },
{ "u_BspPlanesMap", UT_BSPPLANESMAP, UFL_TEXTURE_UNIT },
{ "u_BspModelsMap", UT_BSPMODELSMAP, UFL_TEXTURE_UNIT },
{ "u_BspNodesMap", UT_BSPNODESMAP, UFL_TEXTURE_UNIT },
{ "u_BspLightsMap", UT_BSPLIGHTSMAP, UFL_TEXTURE_UNIT },
{ "u_FitNormalMap", UT_FITNORMALMAP, UFL_TEXTURE_UNIT },
{ "u_ModelMatrix", UT_MODELMATRIX, 0 },
{ "u_ReflectMatrix", UT_REFLECTMATRIX, 0 },
{ "u_BonesArray", UT_BONESARRAY, 0 },
{ "u_BoneQuaternion", UT_BONEQUATERNION, 0 },
{ "u_BonePosition", UT_BONEPOSITION, 0 },
{ "u_ScreenSizeInv", UT_SCREENSIZEINV, UFL_GLOBAL_PARM },
{ "u_zFar", UT_ZFAR, UFL_GLOBAL_PARM },
{ "u_LightStyleValues", UT_LIGHTSTYLEVALUES, UFL_GLOBAL_PARM },
{ "u_LightStyles", UT_LIGHTSTYLES, 0 },
{ "u_RealTime", UT_REALTIME, UFL_GLOBAL_PARM },
{ "u_DetailScale", UT_DETAILSCALE, 0 },
{ "u_FogParams", UT_FOGPARAMS, UFL_GLOBAL_PARM },
{ "u_ShadowParams", UT_SHADOWPARMS, 0 },
{ "u_TexOffset", UT_TEXOFFSET, 0 },
{ "u_ViewOrigin", UT_VIEWORIGIN, 0 }, // not in a global because it's transformed into modelspace
{ "u_ViewRight", UT_VIEWRIGHT, 0 },
{ "u_RenderColor", UT_RENDERCOLOR, 0 },
{ "u_RenderAlpha", UT_RENDERALPHA, 0 },
{ "u_Smoothness", UT_SMOOTHNESS, 0 },
{ "u_ShadowMatrix", UT_SHADOWMATRIX, 0 },
{ "u_ShadowSplitDist", UT_SHADOWSPLITDIST, UFL_GLOBAL_PARM },
{ "u_TexelSize", UT_TEXELSIZE, UFL_GLOBAL_PARM },
{ "u_GammaTable", UT_GAMMATABLE, UFL_GLOBAL_PARM },
{ "u_LightDir", UT_LIGHTDIR, 0 },
{ "u_LightDiffuse", UT_LIGHTDIFFUSE, 0 },
{ "u_LightShade", UT_LIGHTSHADE, 0 },
{ "u_LightOrigin", UT_LIGHTORIGIN, 0 },
{ "u_LightViewProjMatrix", UT_LIGHTVIEWPROJMATRIX, 0 },
{ "u_DiffuseFactor", UT_DIFFUSEFACTOR, UFL_GLOBAL_PARM },
{ "u_AmbientFactor", UT_AMBIENTFACTOR, UFL_GLOBAL_PARM },
{ "u_AmbientCube", UT_AMBIENTCUBE, 0 },
{ "u_SunRefract", UT_SUNREFRACT, UFL_GLOBAL_PARM },
{ "u_LerpFactor", UT_LERPFACTOR, 0 },
{ "u_RefractScale", UT_REFRACTSCALE, 0 },
{ "u_ReflectScale", UT_REFLECTSCALE, 0 },
{ "u_AberrationScale", UT_ABERRATIONSCALE, 0 },
{ "u_BoxMins", UT_BOXMINS, 0 },
{ "u_BoxMaxs", UT_BOXMAXS, 0 },
{ "u_CubeOrigin", UT_CUBEORIGIN, 0 },
{ "u_CubeMipCount", UT_CUBEMIPCOUNT, 0 },
{ "u_LightNums0", UT_LIGHTNUMS0, 0 },
{ "u_LightNums1", UT_LIGHTNUMS1, 0 },
{ "u_GrassParams", UT_GRASSPARAMS, 0 },
{ "u_ReliefParams", UT_RELIEFPARAMS, 0 },
{ "u_BlurFactor", UT_BLURFACTOR, 0 },
{ "u_ScreenWidth", UT_SCREENWIDTH, 0 },
{ "u_ScreenHeight", UT_SCREENHEIGHT, 0 },
{ "u_FocalDepth", UT_FOCALDEPTH, 0 },
{ "u_FocalLength", UT_FOCALLENGTH, 0 },
{ "u_DofDebug", UT_DOFDEBUG, 0 },
{ "u_FStop", UT_FSTOP, 0 },
{ "u_GrayScale", UT_GRAYSCALE, 0 },
{ "u_LightGamma", UT_LIGHTGAMMA, UFL_GLOBAL_PARM },
{ "u_LightScale", UT_LIGHTSCALE, UFL_GLOBAL_PARM },
{ "u_LightThreshold", UT_LIGHTTHRESHOLD, UFL_GLOBAL_PARM },
{ "u_NumVisibleModels", UT_NUMVISIBLEMODELS, UFL_GLOBAL_PARM },
{ "u_Undefined", UT_UNDEFINED, 0 },
};
static char *GL_PrintInfoLog( GLhandleARB object )
{
static char msg[32768];
int maxLength = 0;
pglGetObjectParameterivARB( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength );
if( maxLength >= sizeof( msg ))
{
ALERT( at_warning, "GL_PrintInfoLog: message exceeds %i symbols\n", sizeof( msg ));
maxLength = sizeof( msg ) - 1;
}
pglGetInfoLogARB( object, maxLength, &maxLength, msg );
return msg;
}
static char *GL_PrintShaderSource( GLhandleARB object )
{
static char msg[8192];
int maxLength = 0;
pglGetObjectParameterivARB( object, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &maxLength );
if( maxLength >= sizeof( msg ))
{
ALERT( at_warning, "GL_PrintShaderSource: message exceeds %i symbols\n", sizeof( msg ));
maxLength = sizeof( msg ) - 1;
}
pglGetShaderSourceARB( object, maxLength, &maxLength, msg );
return msg;
}
static bool GL_PushFileStack( const char *filename )
{
if( file_stack_pos < MAX_FILE_STACK )
{
Q_strncpy( filenames_stack[file_stack_pos], filename, sizeof( filenames_stack[0] ));
file_stack_pos++;
return true;
}
ALERT( at_error, "GL_PushFileStack: stack overflow\n" );
return false;
}
static bool GL_PopFileStack( void )
{
file_stack_pos--;
if( file_stack_pos < 0 )
{
ALERT( at_error, "GL_PushFileStack: stack underflow\n" );
return false;
}
return true;
}
static bool GL_CheckFileStack( const char *filename )
{
for( int i = 0; i < file_stack_pos; i++ )
{
if( !Q_stricmp( filenames_stack[i], filename ))
return true;
}
return false;
}
static bool GL_TestSource( const char *szFilename, const char *szCacheName, CVirtualFS *file )
{
int iCompare;
if( GL_CheckFileStack( szFilename ))
return false;
// check vertex shader source or include-file
if( COMPARE_FILE_TIME( szFilename, szCacheName, &iCompare ))
{
// glsl file is newer.
if( iCompare > 0 )
{
Msg( "%s was changed, %s will be updated\n", szFilename, szCacheName );
cache_needs_update = true;
}
}
else
{
Msg( "%s will be created\n", szCacheName );
cache_needs_update = true;
}
int size;
char *source = (char *)gEngfuncs.COM_LoadFile((char *)szFilename, 5, &size );
if( !source ) return false;
GL_PushFileStack( szFilename );
file->Write( source, size );
file->Seek( 0, SEEK_SET ); // rewind
gEngfuncs.COM_FreeFile( source );
return true;
}
static void GL_TestFile( const char *szFilename, const char *szCacheName, CVirtualFS *file )
{
char *pfile, token[256];
char line[2048];
int ret;
do
{
ret = file->Gets( line, sizeof( line ));
pfile = line;
// NOTE: if first keyword it's not an '#include' just ignore it
pfile = COM_ParseFile( pfile, token );
if( !Q_strcmp( token, "#include" ))
{
CVirtualFS incfile;
char incname[256];
pfile = COM_ParseLine( pfile, token );
Q_snprintf( incname, sizeof( incname ), "glsl/%s", token );
if( !GL_TestSource( incname, szCacheName, &incfile ))
continue;
if( cache_needs_update )
break; // no reason to seek more
GL_TestFile( incname, szCacheName, &incfile );
}
} while( ret != EOF );
GL_PopFileStack();
}
static bool GL_TestShader( const char *szFilename, const char *szCacheName )
{
CVirtualFS file;
cache_needs_update = false;
file_stack_pos = 0;
if( !GL_TestSource( szFilename, szCacheName, &file ))
return false;
GL_TestFile( szFilename, szCacheName, &file );
return cache_needs_update;
}
static bool GL_LoadSource( const char *filename, CVirtualFS *file )
{
if( GL_CheckFileStack( filename ))
{
ALERT( at_error, "recursive include for %s\n", filename );
return false;
}
int size;
char *source = (char *)gEngfuncs.COM_LoadFile((char *)filename, 5, &size );
if( !source )
{
ALERT( at_error, "couldn't load %s\n", filename );
return false;
}
GL_PushFileStack( filename );
file->Write( source, size );
file->Seek( 0, SEEK_SET ); // rewind
gEngfuncs.COM_FreeFile( source );
return true;
}
static void GL_ParseFile( const char *filename, CVirtualFS *file, CVirtualFS *out )
{
char *pfile, token[256];
int ret, fileline = 1;
char line[2048];
// out->Printf( "#file %s\n", filename ); // OpenGL doesn't support #file :-(
out->Printf( "#line 0\n" );
do
{
ret = file->Gets( line, sizeof( line ));
pfile = line;
// NOTE: if first keyword it's not an '#include' just ignore it
pfile = COM_ParseFile( pfile, token );
if( !Q_strcmp( token, "#include" ))
{
CVirtualFS incfile;
char incname[256];
pfile = COM_ParseLine( pfile, token );
Q_snprintf( incname, sizeof( incname ), "glsl/%s", token );
if( !GL_LoadSource( incname, &incfile ))
{
fileline++;
continue;
}
GL_ParseFile( incname, &incfile, out );
// out->Printf( "#file %s\n", filename ); // OpenGL doesn't support #file :-(
out->Printf( "#line %i\n", fileline );
}
else out->Printf( "%s\n", line );
fileline++;
} while( ret != EOF );
GL_PopFileStack();
}
static bool GL_ProcessShader( const char *filename, CVirtualFS *out, const char *defines = NULL )
{
CVirtualFS file;
file_stack_pos = 0;
if( !GL_LoadSource( filename, &file ))
return false;
// add internal defines
out->Printf( "#version 120\n" ); // OpenGL 2.1 required (because 'flat' modifier support only starts from this version)
out->Printf( "#ifndef M_PI\n#define M_PI 3.14159265358979323846\n#endif\n" );
out->Printf( "#ifndef M_PI2\n#define M_PI2 6.28318530717958647692\n#endif\n" );
if( GL_Support( R_EXT_GPU_SHADER4 ))
{
out->Printf( "#extension GL_EXT_gpu_shader4 : require\n" ); // support bitwise ops
out->Printf( "#define GLSL_gpu_shader4\n" );
if( GL_Support( R_TEXTURE_ARRAY_EXT ))
out->Printf( "#define GLSL_ALLOW_TEXTURE_ARRAY\n" );
}
else if( GL_Support( R_TEXTURE_ARRAY_EXT ))
{
out->Printf( "#extension GL_EXT_texture_array : require\n" ); // support texture arrays
out->Printf( "#define GLSL_ALLOW_TEXTURE_ARRAY\n" );
}
if( GL_Support( R_TEXTURE_2D_RECT_EXT ))
{
out->Printf( "#extension GL_ARB_texture_rectangle : enable\n" ); // support texture rectangle
}
if( defines ) out->Print( defines );
// user may override this constants
out->Printf( "#ifndef MAXSTUDIOBONES\n#define MAXSTUDIOBONES %i\n#endif\n", glConfig.max_skinning_bones );
out->Printf( "#ifndef MAX_SHADOWMAPS\n#define MAX_SHADOWMAPS %i\n#endif\n", MAX_SHADOWMAPS );
out->Printf( "#ifndef NUM_SHADOW_SPLITS\n#define NUM_SHADOW_SPLITS %i\n#endif\n", NUM_SHADOW_SPLITS );
out->Printf( "#ifndef LIGHT_SAMPLES\n#define LIGHT_SAMPLES %i\n#endif\n", LIGHT_SAMPLES );
out->Printf( "#ifndef MAX_LIGHTSTYLES\n#define MAX_LIGHTSTYLES %i\n#endif\n", MAX_LIGHTSTYLES );
out->Printf( "#ifndef MAXLIGHTMAPS\n#define MAXLIGHTMAPS %i\n#endif\n", MAXLIGHTMAPS );
out->Printf( "#ifndef MAXDYNLIGHTS\n#define MAXDYNLIGHTS %i\n#endif\n", (int)cv_deferred_maxlights->value );
out->Printf( "#ifndef GRASS_ANIM_DIST\n#define GRASS_ANIM_DIST %f\n#endif\n", GRASS_ANIM_DIST );
GL_ParseFile( filename, &file, out );
out->Write( "", 1 ); // terminator
return true;
}
static void GL_LoadGPUShader( glsl_program_t *shader, const char *name, GLenum shaderType, const char *defines = NULL )
{
char filename[256];
GLhandleARB object;
CVirtualFS source;
GLint compiled;
ASSERT( shader != NULL );
switch( shaderType )
{
case GL_VERTEX_SHADER_ARB:
Q_snprintf( filename, sizeof( filename ), "glsl/%s_vp.glsl", name );
break;
case GL_FRAGMENT_SHADER_ARB:
Q_snprintf( filename, sizeof( filename ), "glsl/%s_fp.glsl", name );
break;
default:
ALERT( at_error, "GL_LoadGPUShader: unknown shader type %p\n", shaderType );
return;
}
// load includes, add some directives
if( !GL_ProcessShader( filename, &source, defines ))
return;
GLcharARB *buffer = (GLcharARB *)source.GetBuffer();
int bufferSize = source.GetSize();
ALERT( at_aiconsole, "loading '%s'\n", filename );
object = pglCreateShaderObjectARB( shaderType );
pglShaderSourceARB( object, GL_TRUE, (const GLcharARB **)&buffer, &bufferSize );
// compile shader
pglCompileShaderARB( object );
// check if shader compiled
pglGetObjectParameterivARB( object, GL_OBJECT_COMPILE_STATUS_ARB, &compiled );
if( !compiled )
{
if( developer_level ) Msg( "%s", GL_PrintInfoLog( object ));
if( developer_level ) Msg( "Shader options:%s\n", GL_PretifyListOptions( defines ));
ALERT( at_error, "Couldn't compile %s\n", filename );
return;
}
if( shaderType == GL_VERTEX_SHADER_ARB )
shader->status |= SHADER_VERTEX_COMPILED;
else shader->status |= SHADER_FRAGMENT_COMPILED;
// attach shader to program
pglAttachObjectARB( shader->handle, object );
// delete shader, no longer needed
pglDeleteObjectARB( object );
}
static bool GL_LoadGPUBinaryShader( glsl_program_t *shader, const char *vpname, const char *fpname, uint checksum )
{
char szFilename[MAX_PATH];
char szVpSource[MAX_PATH];
char szFpSource[MAX_PATH];
GLint linked = 0;
int length;
if( !GL_Support( R_BINARY_SHADER_EXT ))
return false;
Q_snprintf( szFilename, sizeof( szFilename ), "cache/glsl/%p.bin", checksum );
Q_snprintf( szVpSource, sizeof( szVpSource ), "glsl/%s_vp.glsl", vpname );
Q_snprintf( szFpSource, sizeof( szFpSource ), "glsl/%s_fp.glsl", fpname );
// check vertex shader source
if( GL_TestShader( szVpSource, szFilename ))
return false;
// check fragment shader source
if( GL_TestShader( szFpSource, szFilename ))
return false;
byte *aMemFile = LOAD_FILE( szFilename, &length );
if( !aMemFile ) return false;
pglProgramBinary( shader->handle, glConfig.binary_formats, aMemFile, length );
pglGetObjectParameterivARB( shader->handle, GL_OBJECT_LINK_STATUS_ARB, &linked );
SetBits( shader->status, SHADER_FRAGMENT_COMPILED|SHADER_VERTEX_COMPILED );
FREE_FILE( aMemFile );
if( linked )
{
ALERT( at_aiconsole, "loading %s\n", szFilename );
SetBits( shader->status, SHADER_PROGRAM_LINKED );
return true;
}
return false;
}
static bool GL_SaveGPUBinaryShader( glsl_program_t *shader, uint checksum )
{
char szFilename[MAX_PATH];
int length, result;
GLenum outFormat;
byte *binary;
if( !GL_Support( R_BINARY_SHADER_EXT ))
return false;
Q_snprintf( szFilename, sizeof( szFilename ), "cache/glsl/%p.bin", checksum );
pglGetObjectParameterivARB( shader->handle, GL_PROGRAM_BINARY_LENGTH, &length );
if( length <= 0 ) return false;
binary = (byte *)malloc( length );
pglGetProgramBinary( shader->handle, length, &length, &outFormat, binary );
result = SAVE_FILE( szFilename, binary, length );
ALERT( at_aiconsole, "write glsl cache: %s\n", szFilename );
free( binary );
return (result != 0 );
}
static void GL_LinkProgram( glsl_program_t *shader )
{
GLint linked = 0;
if( !shader ) return;
if( GL_Support( R_BINARY_SHADER_EXT ))
pglProgramParameteri( shader->handle, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE );
pglLinkProgramARB( shader->handle );
pglGetObjectParameterivARB( shader->handle, GL_OBJECT_LINK_STATUS_ARB, &linked );
if( !linked )
{
ALERT( at_error, "%s\n%s shader failed to link\n", GL_PrintInfoLog( shader->handle ), shader->name );
if( developer_level ) Msg( "Shader options:%s\n", GL_PretifyListOptions( shader->options ));
}
else shader->status |= SHADER_PROGRAM_LINKED;
}
static void GL_ValidateProgram( glsl_program_t *shader )
{
GLint validated = 0;
if( !shader ) return;
pglValidateProgramARB( shader->handle );
pglGetObjectParameterivARB( shader->handle, GL_OBJECT_VALIDATE_STATUS_ARB, &validated );
if( !validated ) ALERT( at_error, "%s\n%s shader failed to validate\n", GL_PrintInfoLog( shader->handle ), shader->name );
}
int GL_UniformTypeToDwordCount( GLuint type, bool align = false )
{
switch( type )
{
case GL_INT:
case GL_UNSIGNED_INT:
case GL_SAMPLER_1D_ARB:
case GL_SAMPLER_2D_ARB:
case GL_SAMPLER_3D_ARB:
case GL_SAMPLER_CUBE_ARB:
case GL_SAMPLER_1D_SHADOW_ARB:
case GL_SAMPLER_2D_SHADOW_ARB:
case GL_SAMPLER_2D_RECT_ARB:
case GL_SAMPLER_2D_RECT_SHADOW_ARB:
case GL_SAMPLER_CUBE_SHADOW_EXT:
return align ? 4 : 1; // int[1]
case GL_FLOAT:
return align ? 4 : 1; // float[1]
case GL_FLOAT_VEC2_ARB:
return align ? 4 : 2; // float[2]
case GL_FLOAT_VEC3_ARB:
return align ? 4 : 3; // float[3]
case GL_FLOAT_VEC4_ARB:
return 4; // float[4]
case GL_FLOAT_MAT2_ARB:
return 4; // float[2][2]
case GL_FLOAT_MAT3_ARB:
return align ? 12 : 9; // float[3][3]
case GL_FLOAT_MAT4_ARB:
return 16;// float[4][4]
default: return align ? 4 : 1; // assume error
}
}
const char *GL_UniformTypeToName( GLuint type )
{
switch( type )
{
case GL_INT:
return "int";
case GL_UNSIGNED_INT:
return "uint";
case GL_FLOAT:
return "float";
case GL_FLOAT_VEC2_ARB:
return "vec2";
case GL_FLOAT_VEC3_ARB:
return "vec3";
case GL_FLOAT_VEC4_ARB:
return "vec4";
case GL_FLOAT_MAT2_ARB:
return "mat2";
case GL_FLOAT_MAT3_ARB:
return "mat3";
case GL_FLOAT_MAT4_ARB:
return "mat4";
case GL_SAMPLER_1D_ARB:
return "sampler1D";
case GL_SAMPLER_2D_ARB:
return "sampler2D";
case GL_SAMPLER_3D_ARB:
return "sampler3D";
case GL_SAMPLER_CUBE_ARB:
return "samplerCube";
case GL_SAMPLER_1D_ARRAY_EXT:
return "sampler1DArray";
case GL_SAMPLER_2D_ARRAY_EXT:
return "sampler2DArray";
case GL_SAMPLER_1D_SHADOW_ARB:
return "sampler1DShadow";
case GL_SAMPLER_2D_SHADOW_ARB:
return "sampler2DShadow";
case GL_SAMPLER_2D_RECT_ARB:
return "sampler2DRect";
case GL_SAMPLER_2D_RECT_SHADOW_ARB:
return "sampler2DRectShadow";
case GL_SAMPLER_CUBE_SHADOW_EXT:
return "samplerCubeShadow";
default: return "???";
}
}
void GL_ShowProgramUniforms( glsl_program_t *shader )
{
int count, size;
char uniformName[256];
int total_uniforms_used = 0;
GLuint type;
if( !shader || developer_level < DEV_EXTENDED )
return;
// install the executables in the program object as part of current state.
pglUseProgramObjectARB( shader->handle );
// query the number of active uniforms
pglGetObjectParameterivARB( shader->handle, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &count );
// Loop over each of the active uniforms, and set their value
for( int i = 0; i < count; i++ )
{
pglGetActiveUniformARB( shader->handle, i, sizeof( uniformName ), NULL, &size, &type, uniformName );
if( developer_level >= DEV_EXTENDED )
{
if( size != 1 )
{
char *end = Q_strchr( uniformName, '[' );
if( end ) *end = '\0'; // cutoff [0]
}
if( size == 1 ) ALERT( at_aiconsole, "uniform %s %s;\n", GL_UniformTypeToName( type ), uniformName );
else ALERT( at_aiconsole, "uniform %s %s[%i];\n", GL_UniformTypeToName( type ), uniformName, size );
}
total_uniforms_used += GL_UniformTypeToDwordCount( type ) * size;
}
if( total_uniforms_used >= glConfig.max_vertex_uniforms )
ALERT( at_error, "used uniforms %i is overflowed max count %i\n", total_uniforms_used, glConfig.max_vertex_uniforms );
else ALERT( at_aiconsole, "used uniforms %i from %i\n", total_uniforms_used, glConfig.max_vertex_uniforms );
int max_shader_uniforms = total_uniforms_used;
if( max_shader_uniforms > ( glConfig.max_skinning_bones * 12 ))
max_shader_uniforms -= ( glConfig.max_skinning_bones * 12 );
ALERT( at_aiconsole, "%s used %i uniforms\n", shader->name, max_shader_uniforms );
if( max_shader_uniforms > glConfig.peak_used_uniforms )
{
glConfig.peak_used_uniforms = max_shader_uniforms;
tr.show_uniforms_peak = true;
}
pglUseProgramObjectARB( GL_NONE );
}
static void GL_ParseProgramUniforms( glsl_program_t *shader )
{
int count, size;
char uniformName[256];
int total_uniforms_used = 0;
char cleanName[256];
int enumUnits = 0;
GLuint format;
// query the number of active uniforms
pglGetObjectParameterivARB( shader->handle, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &count );
pglUseProgramObjectARB( shader->handle );
shader->uniforms = (uniform_t *)Mem_Alloc( count * sizeof( uniform_t ));
shader->numUniforms = 0;
#ifdef _DEBUG_UNIFORMS
Msg( "%c%s%s\n", 3, shader->name, GL_PretifyListOptions( shader->options ));
#endif
// Loop over each of the active uniforms, and set their value
for( int i = 0; i < count; i++ )
{
uniformTable_t *desc;
uniform_t *uniform;
int location;
pglGetActiveUniformARB( shader->handle, i, sizeof( uniformName ), NULL, &size, &format, uniformName );
if(( location = pglGetUniformLocationARB( shader->handle, uniformName )) == -1 )
continue; // ignore built-in uniforms
// remove array size from name
Q_strncpy( cleanName, uniformName, sizeof( cleanName ));
char *end = Q_strchr( cleanName, '[' );
if( end ) *end = '\0'; // cutoff [0]
// check for description
for( int j = 0; j < ARRAYSIZE( glsl_uniformTable ); j++ )
{
desc = &glsl_uniformTable[j];
if( !Q_strcmp( desc->name, cleanName ))
break;
}
if( desc->type == UT_UNDEFINED )
{
ALERT( at_error, "%cUnhandled uniform %s. Ignoring\n", 3, uniformName );
continue;
}
// fill next uniform
uniform = &shader->uniforms[shader->numUniforms++];
Q_strncpy( uniform->name, cleanName, sizeof( uniform->name ));
uniform->location = location;
uniform->flags = desc->flags;
uniform->type = desc->type;
uniform->format = format;
uniform->size = size;
if( FBitSet( uniform->flags, UFL_TEXTURE_UNIT ))
{
uniform->unit = enumUnits++;
pglUniform1iARB( uniform->location, uniform->unit );
}
else uniform->unit = -1; // not a texture
if( enumUnits >= glConfig.max_texture_units )
ALERT( at_warning, "%c%s [%d] exceeded GL_MAX_IMAGE_UNITS\n", 3, cleanName, enumUnits );
#ifdef _DEBUG_UNIFORMS
if( uniform->unit != -1 ) Msg( "%c%s %s : %d;\n", 3, GL_UniformTypeToName( format ), cleanName, uniform->unit );
else if( size == 1 ) Msg( "%cuniform %s %s;\n", 3, GL_UniformTypeToName( format ), cleanName );
else Msg( "%cuniform %s %s[%i];\n", 3, GL_UniformTypeToName( format ), cleanName, size );
#endif
total_uniforms_used += GL_UniformTypeToDwordCount( format ) * size;
}
if( total_uniforms_used >= glConfig.max_vertex_uniforms )
ALERT( at_error, "%cused uniforms %i is overflowed max count %i\n", 3, total_uniforms_used, glConfig.max_vertex_uniforms );
#ifdef _DEBUG_UNIFORMS
ALERT( at_aiconsole, "%c%s used %i uniforms\n", 3, shader->name, total_uniforms_used );
#endif
if( total_uniforms_used > glConfig.peak_used_uniforms )
{
glConfig.peak_used_uniforms = total_uniforms_used;
tr.show_uniforms_peak = true;
}
pglUseProgramObjectARB( GL_NONE );
}
static void GL_SetDefaultVertexAttribs( glsl_program_t *shader )
{
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_POSITION, "attr_Position" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TEXCOORD0, "attr_TexCoord0" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TEXCOORD1, "attr_TexCoord1" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TANGENT, "attr_Tangent" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_BINORMAL, "attr_Binormal" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_NORMAL, "attr_Normal" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_BONE_INDEXES, "attr_BoneIndexes" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_BONE_WEIGHTS, "attr_BoneWeights" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_STYLES, "attr_LightStyles" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_COLOR, "attr_LightColor" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_VECS, "attr_LightVecs" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_NUMS0, "attr_LightNums0" );
pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_NUMS1, "attr_LightNums1" );
}
static void GL_ParseProgramVertexAttribs( glsl_program_t *shader )
{
if( pglGetAttribLocationARB( shader->handle, "attr_Position" ) == ATTR_INDEX_POSITION )
SetBits( shader->attribs, FATTR_POSITION );
if( pglGetAttribLocationARB( shader->handle, "attr_TexCoord0" ) == ATTR_INDEX_TEXCOORD0 )
SetBits( shader->attribs, FATTR_TEXCOORD0 );
if( pglGetAttribLocationARB( shader->handle, "attr_TexCoord1" ) == ATTR_INDEX_TEXCOORD1 )
SetBits( shader->attribs, FATTR_TEXCOORD1 );
if( pglGetAttribLocationARB( shader->handle, "attr_TexCoord2" ) == ATTR_INDEX_TEXCOORD2 )
SetBits( shader->attribs, FATTR_TEXCOORD2 );
if( pglGetAttribLocationARB( shader->handle, "attr_Tangent" ) == ATTR_INDEX_TANGENT )
SetBits( shader->attribs, FATTR_TANGENT );
if( pglGetAttribLocationARB( shader->handle, "attr_Binormal" ) == ATTR_INDEX_BINORMAL )
SetBits( shader->attribs, FATTR_BINORMAL );
if( pglGetAttribLocationARB( shader->handle, "attr_Normal" ) == ATTR_INDEX_NORMAL )
SetBits( shader->attribs, FATTR_NORMAL );
if( pglGetAttribLocationARB( shader->handle, "attr_BoneIndexes" ) == ATTR_INDEX_BONE_INDEXES )
SetBits( shader->attribs, FATTR_BONE_INDEXES );
if( pglGetAttribLocationARB( shader->handle, "attr_BoneWeights" ) == ATTR_INDEX_BONE_WEIGHTS )
SetBits( shader->attribs, FATTR_BONE_WEIGHTS );
if( pglGetAttribLocationARB( shader->handle, "attr_LightStyles" ) == ATTR_INDEX_LIGHT_STYLES )
SetBits( shader->attribs, FATTR_LIGHT_STYLES );
if( pglGetAttribLocationARB( shader->handle, "attr_LightColor" ) == ATTR_INDEX_LIGHT_COLOR )
SetBits( shader->attribs, FATTR_LIGHT_COLOR );
if( pglGetAttribLocationARB( shader->handle, "attr_LightVecs" ) == ATTR_INDEX_LIGHT_VECS )
SetBits( shader->attribs, FATTR_LIGHT_VECS );
if( pglGetAttribLocationARB( shader->handle, "attr_LightNums0" ) == ATTR_INDEX_LIGHT_NUMS0 )
SetBits( shader->attribs, FATTR_LIGHT_NUMS0 );
if( pglGetAttribLocationARB( shader->handle, "attr_LightNums1" ) == ATTR_INDEX_LIGHT_NUMS1 )
SetBits( shader->attribs, FATTR_LIGHT_NUMS1 );
}
void GL_BindShader( glsl_program_t *shader )
{
if( !shader && RI->currentshader )
{
pglUseProgramObjectARB( GL_NONE );
RI->currentshader = NULL;
}
else if( shader != RI->currentshader && shader != NULL && shader->handle )
{
pglUseProgramObjectARB( shader->handle );
r_stats.num_shader_binds++;
RI->currentshader = shader;
}
}
static void GL_FreeGPUShader( glsl_program_t *shader )
{
if( shader && shader->handle )
{
uint hash;
glsl_program_t *cur;
glsl_program_t **prev;
const char *find;
find = va( "%s %s", shader->name, shader->options );
if( shader->uniforms != NULL )
{
Mem_Free( shader->uniforms );
shader->uniforms = NULL;
}
// remove from hash table
hash = COM_HashKey( find, SHADERS_HASH_SIZE );
prev = &glsl_programsHashTable[hash];
while( 1 )
{
cur = *prev;
if( !cur ) break;
if( cur == shader )
{
*prev = cur->nextHash;
break;
}
prev = &cur->nextHash;
}
pglDeleteObjectARB( shader->handle );
memset( shader, 0, sizeof( *shader ));
}
}
static glsl_program_t *GL_CreateUberShader( GLint slot, const char *glname, const char *vpname, const char *fpname, const char *options, uint checksum )
{
if( !GL_Support( R_SHADER_GLSL100_EXT ))
return NULL;
if( num_glsl_programs >= MAX_GLSL_PROGRAMS )
{
ALERT( at_error, "GL_CreateUberShader: GLSL shaders limit exceeded (%i max)\n", MAX_GLSL_PROGRAMS );
return NULL;
}
// alloc new shader
glsl_program_t *shader = &glsl_programs[slot];
shader->handle = pglCreateProgramObjectARB();
if( !shader->handle ) return NULL; // some bad happens
Q_strncpy( shader->name, glname, sizeof( shader->name ));
Q_strncpy( shader->options, options, sizeof( shader->options ));
if( !GL_LoadGPUBinaryShader( shader, vpname, fpname, checksum ))
{
if( vpname ) GL_LoadGPUShader( shader, vpname, GL_VERTEX_SHADER_ARB, options );
else SetBits( shader->status, SHADER_VERTEX_COMPILED );
if( fpname ) GL_LoadGPUShader( shader, fpname, GL_FRAGMENT_SHADER_ARB, options );
else SetBits( shader->status, SHADER_FRAGMENT_COMPILED );
if( vpname && FBitSet( shader->status, SHADER_VERTEX_COMPILED ))
GL_SetDefaultVertexAttribs( shader );
GL_LinkProgram( shader );
if( FBitSet( shader->status, SHADER_PROGRAM_LINKED ))
GL_SaveGPUBinaryShader( shader, checksum );
}
// dynamic ubershaders has the identically name of fragment and vertex program
if(( glname == vpname ) && ( glname == fpname ))
SetBits( shader->status, SHADER_UBERSHADER ); // it's UberShader!
if( FBitSet( shader->status, SHADER_PROGRAM_LINKED ))
{
// register shader uniforms
GL_ParseProgramVertexAttribs( shader );
GL_ParseProgramUniforms( shader );
GL_ValidateProgram( shader );
}
// rewind generated errors if shader compile was failed
// to avoid show them later e.g. on textures loading
while( pglGetError() != GL_NO_ERROR );
#ifdef _DEBUG_UNIFORMS
if( FBitSet( shader->status, SHADER_UBERSHADER ))
ALERT( at_aiconsole, "CompileUberShader #%i: %s\n%s\n", slot, glname, options );
else ALERT( at_aiconsole, "CompileShader #%i: %s\n%s\n", slot, glname, options );
#endif
if( slot == num_glsl_programs )
{
if( num_glsl_programs == MAX_GLSL_PROGRAMS )
{
ALERT( at_error, "GL_CreateUberShader: GLSL shaders limit exceeded (%i max)\n", MAX_GLSL_PROGRAMS );
GL_FreeGPUShader( shader );
return NULL;
}
num_glsl_programs++;
}
// all done, program is loaded
return shader;
}
word GL_FindUberShader( const char *glname, const char *options )
{
glsl_program_t *prog;
if( !GL_Support( R_SHADER_GLSL100_EXT ))
return 0;
ASSERT( glname != NULL );
const char *find = va( "%s %s", glname, options );
uint hash = COM_HashKey( find, SHADERS_HASH_SIZE );
// check for coexist
for( prog = glsl_programsHashTable[hash]; prog != NULL; prog = prog->nextHash )
{
if( !Q_strcmp( prog->name, glname ) && !Q_strcmp( prog->options, options ))
return (word)(prog - glsl_programs);
}
// find free spot
for( int i = 1; i < num_glsl_programs; i++ )
if( !glsl_programs[i].name[0] )
break;
double start = Sys_DoubleTime();
uint checksum = FILE_CRC32( find, Q_strlen( find ));
prog = GL_CreateUberShader( i, glname, glname, glname, options, checksum );
double end = Sys_DoubleTime();
r_buildstats.compile_shader += (end - start);
if( RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 ))
r_buildstats.total_buildtime += (end - start);
if( prog != NULL )
{
// add to hash table
prog->nextHash = glsl_programsHashTable[hash];
glsl_programsHashTable[hash] = prog;
}
return (word)(prog - glsl_programs);
}
word GL_FindShader( const char *glname, const char *vpname, const char *fpname, const char *options )
{
glsl_program_t *prog;
if( !GL_Support( R_SHADER_GLSL100_EXT ))
return 0;
ASSERT( glname != NULL );
const char *find = va( "%s %s", glname, options );
uint hash = COM_HashKey( find, SHADERS_HASH_SIZE );
// check for coexist
for( prog = glsl_programsHashTable[hash]; prog != NULL; prog = prog->nextHash )
{
if( !Q_strcmp( prog->name, glname ) && !Q_strcmp( prog->options, options ))
return (word)(prog - glsl_programs);
}
// find free spot
for( int i = 1; i < num_glsl_programs; i++ )
if( !glsl_programs[i].name[0] )
break;
double start = Sys_DoubleTime();
uint checksum = FILE_CRC32( find, Q_strlen( find ));
prog = GL_CreateUberShader( i, glname, vpname, fpname, options, checksum );
double end = Sys_DoubleTime();
r_buildstats.compile_shader += (end - start);
if( RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 ))
r_buildstats.total_buildtime += (end - start);
if( prog != NULL )
{
// add to hash table
prog->nextHash = glsl_programsHashTable[hash];
glsl_programsHashTable[hash] = prog;
}
return (word)(prog - glsl_programs);
}
void GL_AddShaderFeature( word shaderNum, int feature )
{
if( shaderNum <= 0 || shaderNum >= MAX_GLSL_PROGRAMS )
return;
glsl_program_t *shader = &glsl_programs[shaderNum];
SetBits( shader->status, feature );
}
void GL_SetShaderDirective( char *options, const char *directive )
{
options[0] = '\0';
Q_strncat( options, va( "#define %s\n", directive ), MAX_OPTIONS_LENGTH );
}
void GL_AddShaderDirective( char *options, const char *directive )
{
Q_strncat( options, va( "#define %s\n", directive ), MAX_OPTIONS_LENGTH );
}
const char *GL_PretifyListOptions( const char *options, bool newlines )
{
static char output[MAX_OPTIONS_LENGTH];
const char *pstart = options;
const char *pend = options + Q_strlen( options );
char *pout = output;
*pout = '\0';
while( pstart < pend )
{
const char *pfind = Q_strstr( pstart, "#define" );
if( !pfind ) break;
pstart = pfind + Q_strlen( "#define" );
for( ; *pstart != '\n'; pstart++, pout++ )
*pout = *pstart;
if( newlines )
*pout++ = *pstart++;
else pstart++; // skip '\n'
}
if( pout == output )
return ""; // nothing found
*pout++ = ' ';
*pout = '\0';
return output;
}
void GL_CheckTextureAlpha( char *options, int texturenum )
{
if( RENDER_GET_PARM( PARM_TEX_ENCODE, texturenum ) == DXT_ENCODE_ALPHA_SDF )
GL_AddShaderDirective( options, "SIGNED_DISTANCE_FIELD" );
}
void GL_EncodeNormal( char *options, int texturenum )
{
if( RENDER_GET_PARM( PARM_TEX_GLFORMAT, texturenum ) == GL_COMPRESSED_RED_GREEN_RGTC2_EXT )
{
GL_AddShaderDirective( options, "NORMAL_RG_PARABOLOID" );
}
else if( RENDER_GET_PARM( PARM_TEX_GLFORMAT, texturenum ) == GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI )
{
GL_AddShaderDirective( options, "NORMAL_3DC_PARABOLOID" );
}
else if( RENDER_GET_PARM( PARM_TEX_ENCODE, texturenum ) == DXT_ENCODE_NORMAL_AG_PARABOLOID )
{
GL_AddShaderDirective( options, "NORMAL_AG_PARABOLOID" );
}
else if( RENDER_GET_PARM( PARM_TEX_GLFORMAT, texturenum ) == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT )
{
// implicit DXT5NM format (Paranoia2 v 1.2 old stuff)
if( FBitSet( RENDER_GET_PARM( PARM_TEX_FLAGS, texturenum ), TF_HAS_ALPHA ))
GL_AddShaderDirective( options, "NORMAL_AG_PARABOLOID" );
}
}
void GL_ListGPUShaders( void )
{
int count = 0;
for( uint i = 1; i < num_glsl_programs; i++ )
{
glsl_program_t *cur = &glsl_programs[i];
if( !cur->name[0] ) continue;
const char *options = GL_PretifyListOptions( cur->options );
if( Q_stricmp( options, "" ))
Msg( "#%i %s [%s]\n", i, cur->name, options );
else Msg( "#%i %s\n", i, cur->name );
count++;
}
Msg( "total %i shaders\n", count );
}
void GL_InitGPUShaders( void )
{
char options[MAX_OPTIONS_LENGTH];
memset( &glsl_programs, 0, sizeof( glsl_programs ));
num_glsl_programs = 1; // entry #0 isn't used
if( !GL_Support( R_SHADER_GLSL100_EXT ))
return;
ADD_COMMAND( "shaderlist", GL_ListGPUShaders );
// init sky shaders
GL_SetShaderDirective( options, "SKYBOX_DAYTIME" );
tr.skyboxEnv[0] = GL_FindShader( "forward/skybox", "forward/generic", "forward/skybox" );
tr.skyboxEnv[1] = GL_FindShader( "forward/skybox", "forward/generic", "forward/skybox", options );
tr.defSceneSky = GL_FindShader( "deferred/skybox", "deferred/generic", "deferred/sky_scene" );
tr.defLightSky = GL_FindShader( "deferred/skybox", "deferred/generic", "deferred/sky_light" );
}
void GL_FreeUberShaders( void )
{
if( !GL_Support( R_SHADER_GLSL100_EXT ))
return;
for( uint i = 1; i < num_glsl_programs; i++ )
{
if( FBitSet( glsl_programs[i].status, SHADER_UBERSHADER ))
GL_FreeGPUShader( &glsl_programs[i] );
}
GL_BindShader( GL_NONE );
}
void GL_FreeGPUShaders( void )
{
if( !GL_Support( R_SHADER_GLSL100_EXT ))
return;
for( uint i = 1; i < num_glsl_programs; i++ )
GL_FreeGPUShader( &glsl_programs[i] );
GL_BindShader( GL_NONE );
}