rallyunlimited-engine/code/renderer/tr_arb.c
2024-02-02 19:46:17 +03:00

2206 lines
58 KiB
C

#include "tr_local.h"
#include "tr_common.h"
#define COMMON_DEPTH_STENCIL
//#define DEPTH_RENDER_BUFFER
//#define USE_FBO_BLIT
// screenMap texture dimensions
#define SCR_WIDTH 128
#define SCR_HEIGHT 64
#define BLOOM_BASE 5
#define FBO_COUNT (BLOOM_BASE+(MAX_BLUR_PASSES*2))
#if BLOOM_BASE < 2
#error no space for main/postprocess buffers
#endif
static GLuint programs[ PROGRAM_COUNT ];
static GLuint current_vp;
static GLuint current_fp;
static int programCompiled = 0;
static int programEnabled = 0;
qboolean fboEnabled = qfalse;
qboolean fboBloomInited = qfalse;
int fboReadIndex = 0;
GLint fboInternalFormat;
GLint fboTextureFormat;
GLint fboTextureType;
int fboBloomPasses;
int fboBloomBlendBase;
int fboBloomFilterSize;
qboolean windowAdjusted;
int blitX0, blitX1;
int blitY0, blitY1;
int blitClear;
GLenum blitFilter;
qboolean superSampled;
typedef struct frameBuffer_s {
GLuint fbo;
GLuint color; // renderbuffer if multisampled
GLuint depthStencil; // renderbuffer if multisampled
GLint width;
GLint height;
qboolean multiSampled;
} frameBuffer_t;
static GLuint commonDepthStencil;
static frameBuffer_t frameBufferMS;
static frameBuffer_t frameBuffers[ FBO_COUNT ];
static qboolean frameBufferMultiSampling = qfalse;
qboolean blitMSfbo = qfalse;
#ifndef GL_TEXTURE_IMAGE_FORMAT
#define GL_TEXTURE_IMAGE_FORMAT 0x828F
#endif
#ifndef GL_TEXTURE_IMAGE_TYPE
#define GL_TEXTURE_IMAGE_TYPE 0x8290
#endif
extern void RB_SetGL2D( void );
qboolean GL_ProgramAvailable( void )
{
return (programCompiled != 0);
}
static void ARB_ProgramDisable( void )
{
if ( current_vp )
qglDisable( GL_VERTEX_PROGRAM_ARB );
if ( current_fp )
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
current_vp = 0;
current_fp = 0;
programEnabled = 0;
}
void GL_ProgramDisable( void )
{
if ( programEnabled )
{
ARB_ProgramDisable();
}
}
void ARB_ProgramEnableExt( GLuint vertexProgram, GLuint fragmentProgram )
{
if ( programCompiled )
{
if ( current_vp != vertexProgram ) {
current_vp = vertexProgram;
if ( current_vp ) {
qglEnable( GL_VERTEX_PROGRAM_ARB );
qglBindProgramARB( GL_VERTEX_PROGRAM_ARB, current_vp );
} else {
qglDisable( GL_VERTEX_PROGRAM_ARB );
}
}
if ( current_fp != fragmentProgram ) {
current_fp = fragmentProgram;
if ( current_fp ) {
qglEnable( GL_FRAGMENT_PROGRAM_ARB );
qglBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, current_fp );
} else {
qglDisable( GL_FRAGMENT_PROGRAM_ARB );
}
}
programEnabled = 1;
}
}
static void ARB_ProgramEnable( programNum vp, programNum fp )
{
ARB_ProgramEnableExt( programs[ vp ], programs[ fp ] );
}
void GL_ProgramEnable( void )
{
ARB_ProgramEnable( DUMMY_VERTEX, SPRITE_FRAGMENT );
}
#ifdef USE_PMLIGHT
static void ARB_Lighting( const shaderStage_t* pStage )
{
const dlight_t* dl;
byte clipBits[ SHADER_MAX_VERTEXES ];
unsigned hitIndexes[ SHADER_MAX_INDEXES ];
int numIndexes;
int clip;
int i;
backEnd.pc.c_lit_vertices_lateculltest += tess.numVertexes;
dl = tess.light;
for ( i = 0; i < tess.numVertexes; ++i ) {
vec3_t dist;
VectorSubtract( dl->transformed, tess.xyz[i], dist );
if ( tess.surfType != SF_GRID && DotProduct( dist, tess.normal[i] ) <= 0.0f ) {
clipBits[ i ] = 63;
continue;
}
clip = 0;
if ( dist[0] > dl->radius ) {
clip |= 1;
} else if ( dist[0] < -dl->radius ) {
clip |= 2;
}
if ( dist[1] > dl->radius ) {
clip |= 4;
} else if ( dist[1] < -dl->radius ) {
clip |= 8;
}
if ( dist[2] > dl->radius ) {
clip |= 16;
} else if ( dist[2] < -dl->radius ) {
clip |= 32;
}
clipBits[i] = clip;
}
// build a list of triangles that need light
numIndexes = 0;
for ( i = 0 ; i < tess.numIndexes ; i += 3 ) {
int a, b, c;
a = tess.indexes[i];
b = tess.indexes[i+1];
c = tess.indexes[i+2];
if ( clipBits[a] & clipBits[b] & clipBits[c] ) {
continue; // not lighted
}
hitIndexes[numIndexes] = a;
hitIndexes[numIndexes+1] = b;
hitIndexes[numIndexes+2] = c;
numIndexes += 3;
}
backEnd.pc.c_lit_indices_latecull_in += numIndexes;
backEnd.pc.c_lit_indices_latecull_out += tess.numIndexes - numIndexes;
if ( !numIndexes )
return;
if ( tess.shader->sort < SS_OPAQUE ) {
GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
} else {
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
}
GL_SelectTexture( 0 );
R_BindAnimatedImage( &pStage->bundle[ tess.shader->lightingBundle ] );
R_DrawElements( numIndexes, hitIndexes );
}
static void ARB_Lighting_Fast( const shaderStage_t* pStage )
{
if ( !tess.numIndexes )
return;
if ( tess.shader->sort < SS_OPAQUE ) {
GL_State( GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
} else {
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
}
GL_SelectTexture( 0 );
R_BindAnimatedImage( &pStage->bundle[ tess.shader->lightingBundle ] );
R_DrawElements( tess.numIndexes, tess.indexes );
}
void ARB_SetupLightParams( void )
{
programNum vertexProgram;
programNum fragmentProgram;
const fogProgramParms_t *fp;
qboolean fogPass;
const dlight_t *dl;
vec3_t lightRGB;
float radius;
tess.dlightUpdateParams = qfalse;
tess.cullType = tess.shader->cullType;
if ( !programCompiled )
return;
dl = tess.light;
if ( !glConfig.deviceSupportsGamma && !fboEnabled )
VectorScale( dl->color, 2 * powf( r_intensity->value, r_gamma->value ), lightRGB );
else
VectorCopy( dl->color, lightRGB );
radius = dl->radius;
fogPass = ( tess.fogNum && tess.shader->fogPass );
fp = NULL;
vertexProgram = DLIGHT_VERTEX;
if ( dl->linear ) {
fragmentProgram = (tess.shader->cullType == CT_TWO_SIDED) ? DLIGHT_LINEAR_ABS_FRAGMENT : DLIGHT_LINEAR_FRAGMENT;
} else {
fragmentProgram = (tess.shader->cullType == CT_TWO_SIDED) ? DLIGHT_ABS_FRAGMENT : DLIGHT_FRAGMENT;
}
if ( fogPass ) {
fp = RB_CalcFogProgramParms();
// switch to fog programs
if ( fp->eyeOutside ) {
vertexProgram += 2;
} else {
vertexProgram += 1;
}
++fragmentProgram;
}
ARB_ProgramEnable( vertexProgram, fragmentProgram );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, lightRGB[0], lightRGB[1], lightRGB[2], 1.0f / Square( radius ) );
if ( dl->linear )
{
vec3_t ab;
VectorSubtract( dl->transformed2, dl->transformed, ab );
//qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 2, dl->transformed[0], dl->transformed[1], dl->transformed[2], 0 );
//qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 3, dl->transformed2[0], dl->transformed2[1], dl->transformed2[2], 0 );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 4, ab[0], ab[1], ab[2], 1.0f / DotProduct( ab, ab ) );
}
qglProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 0, backEnd.or.viewOrigin[0], backEnd.or.viewOrigin[1], backEnd.or.viewOrigin[2], 0 );
qglProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 1, dl->transformed[0], dl->transformed[1], dl->transformed[2], 0 );
if ( fogPass )
{
GL_BindTexture( 1, tr.fogImage->texnum );
//qglProgramLocalParameter4fvARB( GL_FRAGMENT_PROGRAM_ARB, 5, fp->fogColor );
qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 2, fp->fogDistanceVector );
qglProgramLocalParameter4fvARB( GL_VERTEX_PROGRAM_ARB, 3, fp->fogDepthVector );
qglProgramLocalParameter4fARB( GL_VERTEX_PROGRAM_ARB, 4, fp->eyeT, 0.0f, 0.0f, 0.0f );
GL_SelectTexture( 0 );
}
}
void ARB_LightingPass( void )
{
const shaderStage_t* pStage;
if ( tess.shader->lightingStage < 0 )
return;
// we may need to update programs for fog transitions
if ( tess.dlightUpdateParams )
ARB_SetupLightParams();
RB_DeformTessGeometry();
GL_Cull( tess.shader->cullType );
// set polygon offset if necessary
if ( tess.shader->polygonOffset )
{
qglEnable( GL_POLYGON_OFFSET_FILL );
qglPolygonOffset( r_offsetFactor->value, r_offsetUnits->value );
}
pStage = tess.xstages[ tess.shader->lightingStage ];
R_ComputeTexCoords( 0, &pStage->bundle[ tess.shader->lightingBundle ] );
GL_ClientState( 1, CLS_NONE );
GL_ClientState( 0, CLS_TEXCOORD_ARRAY | CLS_NORMAL_ARRAY );
// since this is guaranteed to be a single pass, fill and lock all the arrays
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoordPtr[0] );
qglNormalPointer( GL_FLOAT, sizeof( tess.normal[0] ), tess.normal );
qglVertexPointer( 3, GL_FLOAT, sizeof( tess.xyz[0] ), tess.xyz );
if ( qglLockArraysEXT )
qglLockArraysEXT( 0, tess.numVertexes );
// CPU may limit performance in following cases
if ( tess.light->linear || gl_version >= 40 )
ARB_Lighting_Fast( pStage );
else
ARB_Lighting( pStage );
if ( qglUnlockArraysEXT )
qglUnlockArraysEXT();
// reset polygon offset
if ( tess.shader->polygonOffset )
{
qglDisable( GL_POLYGON_OFFSET_FILL );
}
}
#endif // USE_PMLIGHT
const char *fogOutVPCode = {
"PARAM fogDistanceVector = program.local[2]; \n"
"PARAM fogDepthVector = program.local[3]; \n"
"PARAM eyeT = program.local[4]; \n"
"PARAM _01_32 = 0.03125; \n"
"PARAM _30_32 = 0.93750; \n"
"TEMP st; \n"
// s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3];
"DP3 st.x, fogDistanceVector, vertex.position; \n"
"ADD st.x, st.x, fogDistanceVector.w; \n"
// t = DotProduct( v, fogDepthVector ) + fogDepthVector[3];
"DP3 st.y, fogDepthVector, vertex.position; \n"
"ADD st.y, st.y, fogDepthVector.w; \n"
// if ( t < 1.0 ) { t = 1.0/32; } else { t = 1.0/32 + 30.0/32 * t / ( t - eyeT ); }
"SGE st.w, st.y, 1.0; \n"
"SUB st.z, st.y, eyeT.x; \n"
"RCP st.z, st.z; \n"
"MUL st.z, st.z, st.y; \n"
"MUL st.z, st.z, _30_32; \n"
"MAD st.y, st.z, st.w, _01_32; \n"
//"MOV st.z, {1.0}; \n"
"MOV st.w, {1.0}; \n"
"MOV result.texcoord[4], st; \n"
};
const char *fogInVPCode = {
"PARAM fogDistanceVector = program.local[2]; \n"
"PARAM fogDepthVector = program.local[3]; \n"
"PARAM eyeT = program.local[4]; \n"
"PARAM _01_32 = 0.03125; \n"
"PARAM _30_32 = 0.93750; \n"
"TEMP st; \n"
// s = DotProduct( v, fogDistanceVector ) + fogDistanceVector[3];
"DP3 st.x, fogDistanceVector, vertex.position; \n"
"ADD st.x, st.x, fogDistanceVector.w; \n"
// t = DotProduct( v, fogDepthVector ) + fogDepthVector[3];
"DP3 st.y, fogDepthVector, vertex.position; \n"
"ADD st.y, st.y, fogDepthVector.w; \n"
//if ( t < 0 ) { t = 1.0/32; } else { t = 31.0/32; }
"SGE st.w, st.y, 0.0; \n"
"MAD st.y, st.w, _30_32, _01_32; \n"
//"MOV st.z, {1.0}; \n"
"MOV st.w, {1.0}; \n"
"MOV result.texcoord[4], st; \n"
};
#ifdef USE_PMLIGHT
static const char *dlightVP = {
"!!ARBvp1.0 \n"
"OPTION ARB_position_invariant; \n"
"PARAM posEye = program.local[0]; \n"
"PARAM posLight = program.local[1]; \n"
"OUTPUT lv = result.texcoord[1]; \n" // 1
"OUTPUT ev = result.texcoord[2]; \n" // 2
"OUTPUT n = result.texcoord[3]; \n" // 3
"MOV result.texcoord[0], vertex.texcoord; \n" // 0
"SUB lv, posLight, vertex.position; \n"
"SUB ev, posEye, vertex.position; \n"
"MOV n, vertex.normal; \n"
"%s" // fog shader if needed
"END \n"
};
static const char *ARB_BuildDlightFP( char *program, int programIndex )
{
qboolean fog = qfalse;
qboolean linear = qfalse;
qboolean abslight = qfalse;
program[0] = '\0';
switch ( programIndex ) {
case DLIGHT_FRAGMENT_FOG:
case DLIGHT_ABS_FRAGMENT_FOG:
case DLIGHT_LINEAR_FRAGMENT_FOG:
case DLIGHT_LINEAR_ABS_FRAGMENT_FOG:
fog = qtrue;
break;
}
switch ( programIndex ) {
case DLIGHT_LINEAR_FRAGMENT:
case DLIGHT_LINEAR_FRAGMENT_FOG:
case DLIGHT_LINEAR_ABS_FRAGMENT:
case DLIGHT_LINEAR_ABS_FRAGMENT_FOG:
linear = qtrue;
break;
}
switch ( programIndex ) {
case DLIGHT_ABS_FRAGMENT:
case DLIGHT_ABS_FRAGMENT_FOG:
case DLIGHT_LINEAR_ABS_FRAGMENT:
case DLIGHT_LINEAR_ABS_FRAGMENT_FOG:
abslight = qtrue;
break;
}
strcat( program,
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"PARAM lightRGB = program.local[0]; \n"
//"PARAM lightRange2recip = program.local[1]; \n"
//"PARAM fogColor = program.local[5]; \n" // fogColor
"TEMP base, tmp; \n"
"TEX base, fragment.texcoord[0], texture[0], 2D; \n" );
if ( linear ) {
strcat( program,
"PARAM lightVector = program.local[4]; \n"
"ATTRIB LV = fragment.texcoord[1]; \n"
"TEMP dnLV; \n"
// project fragment on light vector
"DP3 tmp.w, -LV, lightVector; \n"
"MUL_SAT tmp.x, tmp.w, lightVector.w; \n"
// calculate light vector from projection point
"MAD dnLV, lightVector, tmp.x, LV; \n"
);
} else {
strcat( program, "ATTRIB dnLV = fragment.texcoord[1]; \n" );
}
strcat( program,
"ATTRIB dnEV = fragment.texcoord[2]; \n" // 2
"ATTRIB n = fragment.texcoord[3]; \n" // 3
// normalize light vector
"TEMP lv; \n"
"DP3 tmp.w, dnLV, dnLV; \n"
"RSQ lv.w, tmp.w; \n"
"MUL lv.xyz, dnLV, lv.w; \n"
// calculate light intensity
"TEMP light; \n"
"MUL tmp.x, tmp.w, lightRGB.w; \n"
"SUB tmp.x, {1.0}, tmp.x; \n"
// discard blank fragments
"KIL tmp.x; \n"
"MUL light, lightRGB, tmp.x; \n" ); // light.rgb
if ( r_dlightSpecColor->value > 0 )
strcat( program, va( "PARAM specRGB = %1.2f; \n", r_dlightSpecColor->value ) );
strcat( program, va( "PARAM specEXP = %1.2f; \n", r_dlightSpecPower->value ) );
strcat( program,
// normalize eye vector
"TEMP ev; \n"
"DP3 ev.w, dnEV, dnEV; \n"
"RSQ ev.w, ev.w; \n"
"MUL ev.xyz, dnEV, ev.w; \n"
// normalize (eye + light) vector
"ADD tmp, lv, ev; \n"
"DP3 tmp.w, tmp, tmp; \n"
"RSQ tmp.w, tmp.w; \n"
"MUL tmp.xyz, tmp, tmp.w; \n" );
// modulate specular strength
if ( abslight ) {
strcat( program,
"DP3 tmp.w, n, tmp; \n"
"ABS tmp.w, tmp.w; \n" );
} else {
strcat( program,
"DP3_SAT tmp.w, n, tmp; \n" );
}
strcat( program,
"POW tmp.w, tmp.w, specEXP.w; \n"
"TEMP spec; \n" );
if ( r_dlightSpecColor->value > 0 ) {
// by constant
strcat( program, "MUL spec, specRGB, tmp.w; \n" );
} else {
// by texture
strcat( program, va( "MUL tmp.w, tmp.w, %1.2f; \n", -r_dlightSpecColor->value ) );
strcat( program, "MUL spec, base, tmp.w; \n" );
}
// diffuse
if ( abslight ) {
strcat( program,
"TEMP bump; \n"
"DP3 bump.w, n, lv; \n"
// make sure that light and eye vectors are on the same plane side
"DP3 tmp.w, n, ev; \n"
"MUL tmp.w, tmp.w, bump.w; \n"
"KIL tmp.w; \n"
"ABS bump.w, bump.w; \n" );
} else {
strcat( program,
"TEMP bump; \n"
"DP3_SAT bump.w, n, lv; \n" );
}
strcat( program, "MAD base, base, bump.w, spec; \n" );
if ( fog ) {
strcat( program,
"TEMP fog; \n"
"TEX fog, fragment.texcoord[4], texture[1], 2D; \n" // fog texture
//"MUL fog, fog, fogColor; \n"
// blend with fog
//"LRP_SAT base, fog.a, fog, base; \n"
// modulate by inverted fog alpha
"SUB fog.a, {1.0}, fog.a; \n"
"MUL base, base, fog.a; \n" );
}
strcat( program,
"MUL_SAT result.color, base, light; \n"
"END \n" );
r_dlightSpecColor->modified = qfalse;
r_dlightSpecPower->modified = qfalse;
return program;
}
#endif // USE_PMLIGHT
static const char *dummyVP = {
"!!ARBvp1.0 \n"
"OPTION ARB_position_invariant; \n"
"MOV result.texcoord[0], vertex.texcoord; \n"
"END \n"
};
static const char *spriteFP = {
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"TEMP base; \n"
"TEX base, fragment.texcoord[0], texture[0], 2D; \n"
"TEMP test; \n"
"SUB test.a, base.a, 0.85; \n"
"KIL test.a; \n"
"MOV base, 0.0; \n"
"MOV result.color, base; \n"
"MOV result.depth, fragment.position.z; \n"
"END \n"
};
static char *ARB_BuildGreyscaleProgram( char *buf ) {
char *s;
if ( r_greyscale->value == 0 ) {
*buf = '\0';
return buf;
}
s = Q_stradd( buf, "PARAM sRGB = { 0.2126, 0.7152, 0.0722, 1.0 }; \n" );
if ( r_greyscale->value == 1.0 ) {
Q_stradd( s, "DP3 base.xyz, base, sRGB; \n" );
} else {
s = Q_stradd( s, "TEMP luma; \n" );
s = Q_stradd( s, "DP3 luma, base, sRGB; \n" );
/*s +=*/ sprintf( s, "LRP base.xyz, %1.2f, luma, base; \n", r_greyscale->value );
}
return buf;
}
static const char *gammaFP = {
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"PARAM gamma = program.local[0]; \n"
"TEMP base; \n"
"TEX base, fragment.texcoord[0], texture[0], 2D; \n"
"POW base.x, base.x, gamma.x; \n"
"POW base.y, base.y, gamma.y; \n"
"POW base.z, base.z, gamma.z; \n"
"MUL base.xyz, base, gamma.w; \n"
"%s" // for greyscale shader if needed
"MOV base.w, 1.0; \n"
"MOV_SAT result.color, base; \n"
"END \n"
};
static char *ARB_BuildBloomProgram( char *buf ) {
qboolean intensityCalculated;
char *s = buf;
intensityCalculated = qfalse;
s = Q_stradd( s,
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"PARAM thres = program.local[0]; \n"
"TEMP base; \n"
"TEX base, fragment.texcoord[0], texture[0], 2D; \n" );
if ( r_bloom_threshold_mode->integer == 0 ) {
// (r|g|b) >= threshold
s = Q_stradd( s,
"TEMP minv; \n"
"SGE minv, base, thres; \n"
"DP3_SAT minv.w, minv, minv; \n"
"MUL base.rgb, base, minv.w; \n" );
} else if ( r_bloom_threshold_mode->integer == 1 ) {
// (r+g+b)/3 >= threshold
s = Q_stradd( s,
"PARAM scale = { 0.3333, 0.3334, 0.3333, 1.0 }; \n"
"TEMP avg; \n"
"DP3_SAT avg, base, scale; \n"
"SGE avg.w, avg.x, thres.x; \n"
"MUL base.rgb, base, avg.w; \n" );
} else {
// luma(r,g,b) >= threshold
s = Q_stradd( s,
"PARAM luma = { 0.2126, 0.7152, 0.0722, 1.0 }; \n"
"TEMP intensity; \n"
"DP3_SAT intensity, base, luma; \n"
"SGE intensity.w, intensity.x, thres.x; \n"
"MUL base.rgb, base, intensity.w; \n" );
intensityCalculated = qtrue;
}
// modulation
if ( r_bloom_modulate->integer ) {
if ( r_bloom_modulate->integer == 1 ) {
// by itself
s = Q_stradd( s, "MUL base, base, base; \n" );
} else {
// by intensity
if ( !intensityCalculated ) {
s = Q_stradd( s,
"PARAM luma = { 0.2126, 0.7152, 0.0722, 1.0 }; \n"
"TEMP intensity; \n"
"DP3_SAT intensity, base, luma; \n" );
}
s = Q_stradd( s, "MUL base, base, intensity; \n" );
}
}
/*s = */ Q_stradd( s,
"MOV base.w, 1.0; \n"
"MOV result.color, base; \n"
"END \n" );
return buf;
}
// Gaussian blur shader
static char *ARB_BuildBlurProgram( char *buf, int taps ) {
int i;
char *s = buf;
*s = '\0';
s = Q_stradd( s,
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"ATTRIB tc = fragment.texcoord[0]; \n" );
for ( i = 0; i < taps; i++ ) {
s = Q_stradd( s, va( "PARAM p%i = program.local[%i]; \n", i, i ) ); // tex_offset_x, tex_offset_y, 0.0, weight
}
s = Q_stradd( s, "TEMP cc; \n"
"MOV cc, {0.0, 0.0, 0.0, 1.0};\n" ); // initialize final color
for ( i = 0; i < taps; i++ ) {
s = Q_stradd( s, va( "TEMP c%i, tc%i; \n", i, i ) );
}
for ( i = 0; i < taps; i++ ) {
s = Q_stradd( s, va( "ADD tc%i.xy, tc, p%i; \n", i, i ) );
}
for ( i = 0; i < taps; i++ ) {
s = Q_stradd( s, va( "TEX c%i, tc%i, texture[0], 2D; \n", i, i ) );
s = Q_stradd( s, va( "MAD cc, c%i, p%i.w, cc; \n", i, i ) ); // cc = cc + cN + pN.w
}
/*s = */ Q_stradd( s,
"MOV cc.a, 1.0; \n"
"MOV_SAT result.color, cc; \n"
"END \n" );
return buf;
}
static char *ARB_BuildBlendProgram( char *buf, int count ) {
int i;
char *s = buf;
*s = '\0';
s = Q_stradd( s,
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"ATTRIB tc = fragment.texcoord[0]; \n"
"TEMP cx, cc;\n"
"MOV cc, {0.0, 0.0, 0.0, 1.0}; \n" );
for ( i = 0; i < count; i++ ) {
s = Q_stradd( s, va( "TEX cx, fragment.texcoord[0], texture[%i], 2D; \n"
"ADD cc, cx, cc; \n", i ) );
}
/*s = */ Q_stradd( s,
"MOV cc.a, 1.0; \n"
"MOV_SAT result.color, cc; \n"
"END \n" );
return buf;
}
// blend 2 texture together
static const char *blend2FP = {
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"PARAM factor = program.local[1]; \n"
"TEMP base; \n"
"TEMP post; \n"
"TEX base, fragment.texcoord[0], texture[0], 2D; \n"
"TEX post, fragment.texcoord[0], texture[1], 2D; \n"
"MAD base, post, factor.x, base; \n"
//"ADD base, base, post; \n"
"MOV base.w, 1.0; \n"
"MOV_SAT result.color, base; \n"
"END \n"
};
// combined blend + gamma correction pass
static const char *blend2gammaFP = {
"!!ARBfp1.0 \n"
"OPTION ARB_precision_hint_fastest; \n"
"PARAM gamma = program.local[0]; \n"
"PARAM factor = program.local[1]; \n"
"TEMP base; \n"
"TEMP post; \n"
"TEX base, fragment.texcoord[0], texture[0], 2D; \n"
"TEX post, fragment.texcoord[0], texture[1], 2D; \n"
//"ADD base, base, post; \n"
"MAD base, post, factor.x, base; \n"
"POW base.x, base.x, gamma.x; \n"
"POW base.y, base.y, gamma.y; \n"
"POW base.z, base.z, gamma.z; \n"
"MUL base.xyz, base, gamma.w; \n"
"%s" // for greyscale shader if needed
"MOV base.w, 1.0; \n"
"MOV_SAT result.color, base; \n"
"END \n"
};
static void RenderQuad( int w, int h )
{
static const vec2_t t[4] = { {0.0, 1.0}, {1.0, 1.0}, {0.0, 0.0}, {1.0, 0.0} };
static vec3_t v[4] = { { 0 } };
v[1][0] = w;
v[2][1] = h;
v[3][0] = w;
v[3][1] = h;
GL_ClientState( 0, CLS_TEXCOORD_ARRAY );
qglVertexPointer( 3, GL_FLOAT, 0, v );
qglTexCoordPointer( 2, GL_FLOAT, 0, t );
qglDrawArrays( GL_TRIANGLE_STRIP, 0, 4 );
}
static void ARB_BlurParams( int width, int height, int ksize, qboolean horizontal )
{
static float weight[ MAX_FILTER_SIZE ];
static int old_ksize = -1;
static const float x_k[ MAX_FILTER_SIZE+1 ][ MAX_FILTER_SIZE + 1 ] = {
// [1/weight], coeff.1, coeff.2, [...]
{ 0 },
{ 1.0/1, 1 },
{ 1.0/2, 1, 1 },
// { 1/4, 1, 2, 1 },
{ 1.0/16, 5, 6, 5 },
{ 1.0/8, 1, 3, 3, 1 },
{ 1.0/16, 1, 4, 6, 4, 1 },
{ 1.0/32, 1, 5, 10, 10, 5, 1 },
{ 1.0/64, 1, 6, 15, 20, 15, 6, 1 },
{ 1.0/128, 1, 7, 21, 35, 35, 21, 7, 1 },
{ 1.0/256, 1, 8, 28, 56, 70, 56, 28, 8, 1 },
{ 1.0/512, 1, 9, 36, 84, 126, 126, 84, 36, 9, 1 },
{ 1.0/1024, 1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1 },
{ 1.0/2048, 1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1 },
{ 1.0/4096, 1, 12, 66, 220, 495, 792, 924, 792, 495, 220, 66, 12, 1 },
{ 1.0/8192, 1, 13, 78, 286, 715, 1287, 1716, 1716, 1287, 715, 286, 78, 13, 1 },
{ 1.0/16384, 1, 14, 91, 364, 1001, 2002, 3003, 3432, 3003, 2002, 1001, 364, 91, 14, 1 },
{ 1.0/32768, 1, 15, 105, 455, 1365, 3003, 5005, 6435, 6435, 5005, 3003, 1365, 455, 105, 15, 1 },
{ 1.0/65536, 1, 16, 120, 560, 1820, 4368, 8008, 11440, 12870, 11440, 8008, 4368, 1820, 560, 120, 16, 1 },
{ 1.0/131072, 1, 17, 136, 680, 2380, 6188, 12376, 19448, 24310, 24310, 19448, 12376, 6188, 2380, 680, 136, 17, 1 },
{ 1.0/262144, 1, 18, 153, 816, 3060, 8568, 18564, 31824, 43758, 48620, 43758, 31824, 18564, 8568, 3060, 816, 153, 18, 1 },
{ 1.0/524288, 1, 19, 171, 969, 3876, 11628, 27132, 50388, 75582, 92378, 92378, 75582, 50388, 27132, 11628, 3876, 969, 171, 19, 1 },
};
static const float x_o[ MAX_FILTER_SIZE+1 ][ MAX_FILTER_SIZE ] = {
{ 0 },
{ 0.0 },
{ -0.5, 0.5 },
// { -1.0, 0.0, 1.0 },
{ -1.2f, 0.0, 1.2f },
{ -1.5, -0.5, 0.5, 1.5 },
{ -2.0, -1.0, 0.0, 1.0, 2.0 },
{ -2.5, -1.5, -0.5, 0.5, 1.5, 2.5 },
{ -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0 },
{ -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5 },
{ -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0 },
{ -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5 },
{ -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0 },
{ -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5 },
{ -6.0, -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0 },
{ -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5 },
{ -7.0, -6.0, -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 },
{ -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5 },
{ -8.0, -7.0, -6.0, -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 },
{ -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5 },
{ -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 },
{ -9.5, -8.5, -7.5, -6.5, -5.5, -4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5 },
};
const float *coeffs = x_k[ ksize ] + 1;
const float *off = x_o[ ksize ];
int i;
float rsum;
float texel_size_x;
float texel_size_y;
float offset[ MAX_FILTER_SIZE ][ 2 ]; // xy
// texel size
texel_size_x = 1.0 / (float) width;
texel_size_y = 1.0 / (float) height;
rsum = x_k[ ksize ][ 0 ];
if ( old_ksize != ksize ) {
old_ksize = ksize;
for ( i = 0; i < ksize; i++ ) {
weight[i] = coeffs[i] * rsum;
}
}
// calculate texture offsets for lookup
for ( i = 0; i < ksize; i++ ) {
offset[i][0] = texel_size_x * off[i];
offset[i][1] = texel_size_y * off[i];
}
if ( horizontal ) {
// horizontal pass
for ( i = 0; i < ksize; i++ )
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, i, offset[i][0], 0.0, 0.0, weight[i] );
} else {
// vertical pass
for ( i = 0; i < ksize; i++ )
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, i, 0.0, offset[i][1], 0.0, weight[i] );
}
}
static void ARB_DeletePrograms( void )
{
qglDeleteProgramsARB( ARRAY_LEN( programs ) - PROGRAM_BASE, programs + PROGRAM_BASE );
Com_Memset( programs, 0, sizeof( programs ) );
programCompiled = 0;
}
qboolean ARB_CompileProgram( programType ptype, const char *text, GLuint program )
{
GLint errorPos;
unsigned int errCode;
int kind;
if ( ptype == Fragment )
kind = GL_FRAGMENT_PROGRAM_ARB;
else
kind = GL_VERTEX_PROGRAM_ARB;
qglBindProgramARB( kind, program );
qglProgramStringARB( kind, GL_PROGRAM_FORMAT_ASCII_ARB, strlen( text ), text );
qglGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &errorPos );
if ( (errCode = qglGetError()) != GL_NO_ERROR || errorPos != -1 )
{
// we may receive error with active FBO but compiled programs will continue to work properly
if ( (errCode == GL_INVALID_OPERATION && !fboEnabled) || errorPos != -1 )
{
ri.Printf( PRINT_ALL, S_COLOR_YELLOW "%s Compile Error(%i,%i): %s\n" S_COLOR_CYAN "%s\n", (ptype == Fragment) ? "FP" : "VP",
errCode, errorPos, qglGetString( GL_PROGRAM_ERROR_STRING_ARB ), text );
qglBindProgramARB( kind, 0 );
ARB_DeletePrograms();
return qfalse;
}
}
return qtrue;
}
qboolean ARB_UpdatePrograms( void )
{
#ifdef USE_PMLIGHT
const char *program;
int i;
#endif
char buf[4096];
if ( !qglGenProgramsARB )
return qfalse;
if ( programCompiled ) // delete old programs
{
ARB_ProgramDisable();
ARB_DeletePrograms();
}
qglGenProgramsARB( ARRAY_LEN( programs ) - PROGRAM_BASE, programs + PROGRAM_BASE );
#ifdef USE_PMLIGHT
if ( !ARB_CompileProgram( Vertex, va( dlightVP, "" ), programs[ DLIGHT_VERTEX ] ) )
return qfalse;
if ( !ARB_CompileProgram( Vertex, va( dlightVP, fogInVPCode ), programs[ DLIGHT_VERTEX_FOG_IN ] ) )
return qfalse;
if ( !ARB_CompileProgram( Vertex, va( dlightVP, fogOutVPCode ), programs[ DLIGHT_VERTEX_FOG_OUT ] ) )
return qfalse;
for ( i = DLIGHT_FRAGMENT; i <= DLIGHT_LINEAR_ABS_FRAGMENT_FOG; i++ ) {
program = ARB_BuildDlightFP( buf, i );
if ( !ARB_CompileProgram( Fragment, program, programs[ i ] ) ) {
return qfalse;
}
}
#endif // USE_PMLIGHT
if ( !ARB_CompileProgram( Vertex, dummyVP, programs[ DUMMY_VERTEX ] ) )
return qfalse;
if ( !ARB_CompileProgram( Fragment, spriteFP, programs[ SPRITE_FRAGMENT ] ) )
return qfalse;
if ( !ARB_CompileProgram( Fragment, va( gammaFP, ARB_BuildGreyscaleProgram( buf ) ), programs[ GAMMA_FRAGMENT ] ) )
return qfalse;
if ( !ARB_CompileProgram( Fragment, ARB_BuildBloomProgram( buf ), programs[ BLOOM_EXTRACT_FRAGMENT ] ) )
return qfalse;
// only 1, 2, 3, 6, 8, 10, 12, 14, 16, 18 and 20 produces real visual difference
fboBloomFilterSize = r_bloom_filter_size->integer;
if ( !ARB_CompileProgram( Fragment, ARB_BuildBlurProgram( buf, fboBloomFilterSize ), programs[ BLUR_FRAGMENT ] ) )
return qfalse;
if ( !ARB_CompileProgram( Fragment, ARB_BuildBlurProgram( buf, 6 ), programs[ BLUR2_FRAGMENT ] ) )
return qfalse;
fboBloomBlendBase = r_bloom_blend_base->integer;
if ( !ARB_CompileProgram( Fragment, ARB_BuildBlendProgram( buf, r_bloom_passes->integer - fboBloomBlendBase ), programs[ BLENDX_FRAGMENT ] ) )
return qfalse;
if ( !ARB_CompileProgram( Fragment, blend2FP, programs[ BLEND2_FRAGMENT ] ) )
return qfalse;
if ( !ARB_CompileProgram( Fragment, va( blend2gammaFP, ARB_BuildGreyscaleProgram( buf ) ), programs[ BLEND2_GAMMA_FRAGMENT ] ) )
return qfalse;
programCompiled = 1;
return qtrue;
}
static void FBO_Bind( GLuint target, GLuint buffer );
void FBO_Clean( frameBuffer_t *fb )
{
if ( fb->fbo )
{
FBO_Bind( GL_FRAMEBUFFER, fb->fbo );
if ( fb->multiSampled )
{
qglBindRenderbuffer( GL_RENDERBUFFER, 0 );
if ( fb->color )
{
qglDeleteRenderbuffers( 1, &fb->color );
fb->color = 0;
}
if ( fb->depthStencil )
{
qglDeleteRenderbuffers( 1, &fb->depthStencil );
fb->depthStencil = 0;
}
}
else
{
GL_BindTexture( 0, 0 );
if ( fb->color )
{
qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0 );
qglDeleteTextures( 1, &fb->color );
fb->color = 0;
}
if ( fb->depthStencil )
{
#ifdef DEPTH_RENDER_BUFFER
if ( fb->depthStencil && fb->depthStencil != commonDepthStencil )
{
qglDeleteRenderbuffers( 1, &fb->depthStencil );
fb->depthStencil = 0;
}
#else
if ( r_stencilbits->integer == 0 )
qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0 );
else
qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0 );
if ( fb->depthStencil && fb->depthStencil != commonDepthStencil )
{
qglDeleteTextures( 1, &fb->depthStencil );
fb->depthStencil = 0;
}
#endif
}
}
FBO_Bind( GL_FRAMEBUFFER, 0 );
qglDeleteFramebuffers( 1, &fb->fbo );
fb->fbo = 0;
}
Com_Memset( fb, 0, sizeof( *fb ) );
}
static void FBO_CleanBloom( void )
{
int i;
for ( i = 0; i < MAX_BLUR_PASSES; i++ )
{
FBO_Clean( &frameBuffers[ i * 2 + BLOOM_BASE + 0 ] );
FBO_Clean( &frameBuffers[ i * 2 + BLOOM_BASE + 1 ] );
}
}
static void FBO_CleanDepth( void )
{
#ifdef COMMON_DEPTH_STENCIL
if ( commonDepthStencil )
{
#ifdef DEPTH_RENDER_BUFFER
qglDeleteRenderbuffers( 1, &commonDepthStencil );
#else
GL_BindTexture( 0, 0 );
qglDeleteTextures( 1, &commonDepthStencil );
#endif
commonDepthStencil = 0;
}
#endif
}
static GLuint FBO_CreateDepthTextureOrBuffer( GLsizei width, GLsizei height )
{
#ifdef DEPTH_RENDER_BUFFER
GLuint buffer;
qglGenRenderbuffers( 1, &buffer );
qglBindRenderbuffer( GL_RENDERBUFFER, buffer );
if ( r_stencilbits->integer == 0 )
qglRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, width, height );
else
qglRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height );
return buffer;
#else
GLuint tex;
qglGenTextures( 1, &tex );
GL_BindTexture( 0, tex );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
if ( r_stencilbits->integer == 0 )
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL );
else
qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, width, height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL );
return tex;
#endif
}
static const char *glDefToStr( GLint define )
{
#define CASE_STR(x) case (x): return #x
static int index;
static char buf[8][32];
char *s;
switch ( define )
{
// texture formats
CASE_STR(GL_BGR);
CASE_STR(GL_BGRA);
CASE_STR(GL_RGB);
CASE_STR(GL_RGBA);
CASE_STR(GL_RGBA4);
CASE_STR(GL_RGBA8);
CASE_STR(GL_RGBA12);
CASE_STR(GL_RGBA16);
CASE_STR(GL_RGB10_A2);
CASE_STR(GL_R11F_G11F_B10F);
// data types
CASE_STR(GL_BYTE);
CASE_STR(GL_UNSIGNED_BYTE);
CASE_STR(GL_SHORT);
CASE_STR(GL_UNSIGNED_SHORT);
CASE_STR(GL_INT);
CASE_STR(GL_UNSIGNED_INT);
CASE_STR(GL_FLOAT);
CASE_STR(GL_DOUBLE);
CASE_STR(GL_UNSIGNED_SHORT_4_4_4_4);
CASE_STR(GL_UNSIGNED_INT_8_8_8_8);
CASE_STR(GL_UNSIGNED_INT_10_10_10_2);
CASE_STR(GL_UNSIGNED_SHORT_4_4_4_4_REV);
CASE_STR(GL_UNSIGNED_INT_8_8_8_8_REV);
CASE_STR(GL_UNSIGNED_INT_2_10_10_10_REV);
CASE_STR(GL_UNSIGNED_NORMALIZED);
// error codes
CASE_STR(GL_NO_ERROR);
CASE_STR(GL_INVALID_ENUM);
CASE_STR(GL_INVALID_VALUE);
CASE_STR(GL_INVALID_OPERATION);
CASE_STR(GL_STACK_OVERFLOW);
CASE_STR(GL_STACK_UNDERFLOW);
CASE_STR(GL_OUT_OF_MEMORY);
// fbo error codes
CASE_STR(GL_FRAMEBUFFER_COMPLETE);
CASE_STR(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
CASE_STR(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT);
CASE_STR(GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER);
CASE_STR(GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER);
CASE_STR(GL_FRAMEBUFFER_UNSUPPORTED);
}
s = buf[ index ]; // to handle multiple invocations as function parameters
sprintf( s, "0x%04x", define );
index = ( index + 1 ) & 7;
return s;
}
static void getPreferredFormatAndType( GLint format, GLint *pFormat, GLint *pType )
{
GLint preferredFormat;
GLint preferredType;
if ( qglGetInternalformativ && gl_version >= 43 ) {
qglGetInternalformativ( GL_TEXTURE_2D, /*GL_RGBA8*/ format, GL_TEXTURE_IMAGE_FORMAT, 1, &preferredFormat );
if ( qglGetError() != GL_NO_ERROR ) {
goto __fallback;
}
qglGetInternalformativ( GL_TEXTURE_2D, /*GL_RGBA8*/ format, GL_TEXTURE_IMAGE_TYPE, 1, &preferredType );
if ( qglGetError() != GL_NO_ERROR ) {
goto __fallback;
}
if ( preferredFormat == 0 ) // nVidia ION drivers can do that
preferredFormat = GL_RGBA;
if ( preferredType == GL_UNSIGNED_NORMALIZED ) { // Intel HD 530 drivers can do that as well
if ( format == GL_RGBA12 || format == GL_RGBA16 )
preferredType = GL_UNSIGNED_SHORT;
else
preferredType = GL_UNSIGNED_BYTE;
}
} else {
__fallback:
if ( format == GL_RGBA12 || format == GL_RGBA16 ) {
preferredFormat = GL_RGBA;
preferredType = GL_UNSIGNED_SHORT;
} else {
preferredFormat = GL_RGBA;
preferredType = GL_UNSIGNED_BYTE;
}
}
*pFormat = preferredFormat;
*pType = preferredType;
}
static qboolean FBO_Create( frameBuffer_t *fb, GLsizei width, GLsizei height, qboolean depthStencil, GLint *outFormat, GLint *outType )
{
int fboStatus;
GLint internalFormat;
GLint textureFormat;
GLint textureType;
fb->multiSampled = qfalse;
fb->depthStencil = 0;
// color texture
qglGenTextures( 1, &fb->color );
GL_BindTexture( 0, fb->color );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, gl_clamp_mode );
qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, gl_clamp_mode );
// always use GL_RGB10_A2 for bloom textures which is fast as usual GL_RGBA8
// (GL_R11F_G11F_B10F is a bit slower at least on AMD GPUs)
// but can provide better precision for blurring, also we barely need more than 10 bits for that,
// texture formats that doesn't fit into 32bits are just performance-killers for bloom
if ( fb - frameBuffers >= BLOOM_BASE )
internalFormat = GL_RGB10_A2;
else
internalFormat = fboInternalFormat;
getPreferredFormatAndType( internalFormat, &textureFormat, &textureType );
qglTexImage2D( GL_TEXTURE_2D, 0, internalFormat, width, height, 0, textureFormat, textureType, NULL );
// TODO: handle GL_INVALID_OPERATION in case of unsupported internalFormat/textureFormat
if ( outFormat )
*outFormat = textureFormat;
if ( outType )
*outType = textureType;
qglGenFramebuffers( 1, &fb->fbo );
FBO_Bind( GL_FRAMEBUFFER, fb->fbo );
qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb->color, 0 );
if ( depthStencil )
{
#ifdef COMMON_DEPTH_STENCIL
if ( !commonDepthStencil )
commonDepthStencil = FBO_CreateDepthTextureOrBuffer( width, height );
fb->depthStencil = commonDepthStencil;
#else
fb->depthStencil = FBO_CreateDepthTextureOrBuffer( width, height );
#endif
#ifdef DEPTH_RENDER_BUFFER
if ( r_stencilbits->integer == 0 )
qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb->depthStencil );
else
qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->depthStencil );
#else
if ( r_stencilbits->integer == 0 )
qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fb->depthStencil, 0 );
else
qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fb->depthStencil, 0 );
#endif
}
GL_BindTexture( 0, 0 );
fboStatus = qglCheckFramebufferStatus( GL_FRAMEBUFFER );
if ( fboStatus != GL_FRAMEBUFFER_COMPLETE )
{
ri.Printf( PRINT_ALL, "Failed to create %s (%s:%s) FBO (status %s, error %s)\n",
glDefToStr( internalFormat ), glDefToStr( textureFormat ), glDefToStr( textureType ),
glDefToStr( fboStatus ), glDefToStr( (int)qglGetError() ) );
FBO_Clean( fb );
return qfalse;
}
fb->width = width;
fb->height = height;
qglClearColor( 0.0, 0.0, 0.0, 1.0 );
if ( depthStencil )
qglClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT );
else
qglClear( GL_COLOR_BUFFER_BIT );
FBO_Bind( GL_FRAMEBUFFER, 0 );
return qtrue;
}
static qboolean FBO_CreateMS( frameBuffer_t *fb, int width, int height )
{
GLsizei nSamples = r_ext_multisample->integer;
int fboStatus;
fb->multiSampled = qtrue;
if ( nSamples <= 0 || !qglRenderbufferStorageMultisample )
{
return qfalse;
}
nSamples = PAD( nSamples, 2 );
qglGenFramebuffers( 1, &fb->fbo );
FBO_Bind( GL_FRAMEBUFFER, fb->fbo );
qglGenRenderbuffers( 1, &fb->color );
qglBindRenderbuffer( GL_RENDERBUFFER, fb->color );
while ( nSamples > 0 ) {
qglRenderbufferStorageMultisample( GL_RENDERBUFFER, nSamples, fboInternalFormat, width, height );
if ( (int)qglGetError() == GL_INVALID_VALUE/* != GL_NO_ERROR */ ) {
ri.Printf( PRINT_ALL, "...%ix MSAA is not available\n", nSamples );
nSamples -= 2;
} else {
ri.Printf( PRINT_ALL, "...using %ix MSAA\n", nSamples );
break;
}
}
if ( nSamples <= 0 )
{
FBO_Clean( fb );
return qfalse;
}
qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb->color );
qglGenRenderbuffers( 1, &fb->depthStencil );
qglBindRenderbuffer( GL_RENDERBUFFER, fb->depthStencil );
if ( r_stencilbits->integer == 0 )
qglRenderbufferStorageMultisample( GL_RENDERBUFFER, nSamples, GL_DEPTH_COMPONENT32, width, height );
else
qglRenderbufferStorageMultisample( GL_RENDERBUFFER, nSamples, GL_DEPTH24_STENCIL8, width, height );
if ( (int)qglGetError() != GL_NO_ERROR )
{
FBO_Clean( fb );
return qfalse;
}
if ( r_stencilbits->integer == 0 )
qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb->depthStencil );
else
qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb->depthStencil );
fboStatus = qglCheckFramebufferStatus( GL_FRAMEBUFFER );
if ( fboStatus != GL_FRAMEBUFFER_COMPLETE )
{
ri.Printf( PRINT_WARNING, "Failed to create MS FBO (status %s, error %s)\n", glDefToStr( fboStatus ), glDefToStr( (int)qglGetError() ) );
FBO_Clean( fb );
return qfalse;
}
fb->width = width;
fb->height = height;
qglClearColor( 0.0, 0.0, 0.0, 1.0 );
qglClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT );
FBO_Bind( GL_FRAMEBUFFER, 0 );
return qtrue;
}
static qboolean FBO_CreateBloom( void )
{
int width = glConfig.vidWidth;
int height = glConfig.vidHeight;
int i;
fboBloomPasses = 0;
if ( glConfig.numTextureUnits < r_bloom_passes->integer )
{
ri.Printf( PRINT_WARNING, "...not enough texture units (%i) for %i-pass bloom\n",
glConfig.numTextureUnits, r_bloom_passes->integer );
return qfalse;
}
for ( i = 0; i < r_bloom_passes->integer; i++ )
{
// we may need depth/stencil buffers for first bloom buffer in \r_bloom 2 mode
if ( !FBO_Create( &frameBuffers[ i*2 + BLOOM_BASE + 0 ], width, height, i == 0 ? qtrue : qfalse, NULL, NULL ) ||
!FBO_Create( &frameBuffers[ i*2 + BLOOM_BASE + 1 ], width, height, qfalse, NULL, NULL ) ) {
return qfalse;
}
width = width / 2;
height = height / 2;
fboBloomPasses++;
if ( width < 2 || height < 2 )
break;
}
ri.Printf( PRINT_ALL, "...%i bloom passes\n", fboBloomPasses );
return qtrue;
}
GLuint FBO_ScreenTexture( void )
{
return frameBuffers[ 2 ].color;
}
static void FBO_Bind( GLuint target, GLuint buffer )
{
#if 1
static GLuint draw_buffer = (GLuint)-1;
static GLuint read_buffer = (GLuint)-1;
if ( target == GL_FRAMEBUFFER ) {
if ( draw_buffer != buffer || read_buffer != buffer )
qglBindFramebuffer( GL_FRAMEBUFFER, buffer );
draw_buffer = buffer;
read_buffer = buffer;
} else {
if ( target == GL_READ_FRAMEBUFFER ) {
if ( read_buffer != buffer )
qglBindFramebuffer( GL_READ_FRAMEBUFFER, buffer );
read_buffer = buffer;
} else {
if ( draw_buffer != buffer )
qglBindFramebuffer( GL_DRAW_FRAMEBUFFER, buffer );
draw_buffer = buffer;
}
}
#else
qglBindFramebuffer( target, buffer );
#endif
}
void FBO_BindMain( void )
{
if ( fboEnabled )
{
const frameBuffer_t *fb;
if ( frameBufferMultiSampling )
{
blitMSfbo = qtrue;
fb = &frameBufferMS;
}
else
{
blitMSfbo = qfalse;
fb = &frameBuffers[ 0 ];
}
FBO_Bind( GL_FRAMEBUFFER, fb->fbo );
fboReadIndex = 0;
}
}
static void FBO_BlitToBackBuffer( int index )
{
const frameBuffer_t *src = &frameBuffers[ index ];
FBO_Bind( GL_READ_FRAMEBUFFER, src->fbo );
FBO_Bind( GL_DRAW_FRAMEBUFFER, 0 );
//qglReadBuffer( GL_COLOR_ATTACHMENT0 );
qglDrawBuffer( GL_BACK );
if ( windowAdjusted )
{
if ( blitClear > 0 )
{
blitClear--;
qglClearColor( 0.0, 0.0, 0.0, 1.0 );
qglClear( GL_COLOR_BUFFER_BIT );
}
qglViewport( blitX0, blitY0, blitX1 - blitX0, blitY1 - blitY0 );
qglScissor( blitX0, blitY0, blitX1 - blitX0, blitY1 - blitY0 );
}
qglBlitFramebuffer( 0, 0, src->width, src->height, blitX0, blitY0, blitX1, blitY1, GL_COLOR_BUFFER_BIT, blitFilter );
fboReadIndex = index;
}
void FBO_BlitSS( void )
{
const frameBuffer_t *src = &frameBuffers[ fboReadIndex ];
const frameBuffer_t *dst = &frameBuffers[ 4 ];
FBO_Bind( GL_DRAW_FRAMEBUFFER, dst->fbo );
qglBlitFramebuffer( 0, 0, src->width, src->height, 0, 0, dst->width, dst->height, GL_COLOR_BUFFER_BIT, GL_LINEAR );
FBO_Bind( GL_READ_FRAMEBUFFER, dst->fbo );
}
void FBO_BlitMS( qboolean depthOnly )
{
//if ( blitMSfbo )
//{
const int w = glConfig.vidWidth;
const int h = glConfig.vidHeight;
const frameBuffer_t *r = &frameBufferMS;
const frameBuffer_t *d = &frameBuffers[ 0 ];
fboReadIndex = 0;
FBO_Bind( GL_READ_FRAMEBUFFER, r->fbo );
FBO_Bind( GL_DRAW_FRAMEBUFFER, d->fbo );
if ( depthOnly )
{
qglBlitFramebuffer( 0, 0, w, h, 0, 0, w, h, GL_DEPTH_BUFFER_BIT, GL_NEAREST );
FBO_Bind( GL_READ_FRAMEBUFFER, d->fbo );
return;
}
qglBlitFramebuffer( 0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_NEAREST );
// bind all further reads to main buffer
FBO_Bind( GL_READ_FRAMEBUFFER, d->fbo );
}
static void FBO_Blur( const frameBuffer_t *fb1, const frameBuffer_t *fb2, const frameBuffer_t *fb3 )
{
const int w = glConfig.vidWidth;
const int h = glConfig.vidHeight;
qglViewport( 0, 0, fb1->width, fb1->height );
// apply horizontal blur - render from FBO1 to FBO2
FBO_Bind( GL_DRAW_FRAMEBUFFER, fb2->fbo );
GL_BindTexture( 0, fb1->color );
ARB_ProgramEnable( DUMMY_VERTEX, BLUR_FRAGMENT );
ARB_BlurParams( fb1->width, fb1->height, fboBloomFilterSize, qtrue );
RenderQuad( w, h );
// apply vectical blur - render from FBO2 to FBO3
FBO_Bind( GL_DRAW_FRAMEBUFFER, fb3->fbo );
GL_BindTexture( 0, fb2->color );
ARB_BlurParams( fb1->width, fb1->height, fboBloomFilterSize, qfalse );
RenderQuad( w, h );
}
static void FBO_Blur2( const frameBuffer_t *fb1, const frameBuffer_t *fb2, const frameBuffer_t *fb3 )
{
const int w = glConfig.vidWidth;
const int h = glConfig.vidHeight;
qglViewport( 0, 0, fb1->width, fb1->height );
// apply horizontal blur - render from FBO1 to FBO2
FBO_Bind( GL_DRAW_FRAMEBUFFER, fb2->fbo );
GL_BindTexture( 0, fb1->color );
ARB_ProgramEnable( DUMMY_VERTEX, BLUR2_FRAGMENT );
ARB_BlurParams( fb1->width, fb1->height, 6, qtrue );
RenderQuad( w, h );
// apply vectical blur - render from FBO2 to FBO3
FBO_Bind( GL_DRAW_FRAMEBUFFER, fb3->fbo );
GL_BindTexture( 0, fb2->color );
ARB_BlurParams( fb1->width, fb1->height, 6, qfalse );
RenderQuad( w, h );
}
void FBO_CopyScreen( void )
{
const frameBuffer_t *dst;
const frameBuffer_t *src;
int yCrop;
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
// resolve multisample buffer first
if ( blitMSfbo )
{
src = &frameBufferMS;
dst = &frameBuffers[ 0 ];
FBO_Bind( GL_READ_FRAMEBUFFER, src->fbo );
FBO_Bind( GL_DRAW_FRAMEBUFFER, dst->fbo );
qglBlitFramebuffer( 0, 0, src->width, src->height, 0, 0, dst->width, dst->height, GL_COLOR_BUFFER_BIT, GL_NEAREST );
}
src = &frameBuffers[ 0 ];
dst = &frameBuffers[ 2 ];
FBO_Bind( GL_READ_FRAMEBUFFER, src->fbo );
FBO_Bind( GL_DRAW_FRAMEBUFFER, dst->fbo );
yCrop = backEnd.viewParms.viewportHeight / 4;
qglBlitFramebuffer( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY + yCrop,
backEnd.viewParms.viewportWidth + backEnd.viewParms.viewportX,
backEnd.viewParms.viewportHeight + backEnd.viewParms.viewportY,
0, 0, dst->width, dst->height, GL_COLOR_BUFFER_BIT, GL_LINEAR );
//if ( !backEnd.projection2D )
{
qglMatrixMode( GL_PROJECTION );
qglLoadMatrixf( GL_Ortho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 ) );
qglMatrixMode( GL_MODELVIEW );
qglLoadIdentity();
GL_Cull( CT_TWO_SIDED );
qglDisable( GL_CLIP_PLANE0 );
}
qglColor4f( 1, 1, 1, 1 );
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
FBO_Blur2( dst, dst+1, dst );
ARB_ProgramDisable();
//restore viewport and scissor
qglViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
qglScissor( backEnd.viewParms.scissorX, backEnd.viewParms.scissorY,
backEnd.viewParms.scissorWidth, backEnd.viewParms.scissorHeight );
FBO_BindMain();
}
static void R_Setup_Quad_Lens( float offset, vec4_t color, vec3_t *verts, vec2_t *coords, vec4_t *colors )
{
static const vec2_t t[6] = { {1.0, 0.0}, {0.0, 0.0}, {0.0, 1.0}, {0.0, 1.0}, {1.0, 1.0}, {1.0, 0.0} };
const float width = (float)glConfig.vidWidth;
const float height = (float)glConfig.vidHeight;
int i;
for ( i = 0; i < 6; i++ ) {
coords[i][0] = t[i][0];
coords[i][1] = t[i][1];
Vector4Copy( color, colors[i] );
verts[i][2] = 0.0;
}
verts[0][0] = -offset;
verts[0][1] = -offset;
verts[1][0] = width + offset;
verts[1][1] = -offset;
verts[2][0] = width + offset;
verts[2][1] = height + offset;
verts[3][0] = width + offset;
verts[3][1] = height + offset;
verts[4][0] = -offset;
verts[4][1] = height + offset;
verts[5][0] = -offset;
verts[5][1] = -offset;
}
static void R_Bloom_LensEffect( float alpha )
{
// lens rainbow colors
static const GLfloat lc[][3] = {
{ 0.78f, 0.23f, 0.34f },
{ 0.78f, 0.39f, 0.21f },
{ 0.78f, 0.59f, 0.21f },
{ 0.71f, 0.75f, 0.21f },
{ 0.52f, 0.78f, 0.21f },
{ 0.32f, 0.78f, 0.21f },
{ 0.21f, 0.78f, 0.28f },
{ 0.21f, 0.78f, 0.47f },
{ 0.21f, 0.77f, 0.66f },
{ 0.21f, 0.67f, 0.78f },
{ 0.21f, 0.47f, 0.78f },
{ 0.21f, 0.28f, 0.78f },
{ 0.35f, 0.21f, 0.78f },
{ 0.53f, 0.21f, 0.78f },
{ 0.72f, 0.21f, 0.75f },
{ 0.78f, 0.21f, 0.59f },
};
int i;
vec3_t verts[ ARRAY_LEN(lc) * 6 ];
vec2_t coords[ ARRAY_LEN(lc) * 6 ];
vec4_t colors[ ARRAY_LEN(lc) * 6 ];
vec4_t color;
alpha /= (float)ARRAY_LEN( lc );
for ( i = 0; i < ARRAY_LEN( lc ); i++ ) {
VectorCopy( lc[i], color ); color[3] = alpha;
R_Setup_Quad_Lens( (i+1)*144, color, &verts[i*6], &coords[i*6], &colors[i*6] );
}
GL_ClientState( 0, CLS_TEXCOORD_ARRAY | CLS_COLOR_ARRAY );
qglVertexPointer( 3, GL_FLOAT, 0, verts );
qglTexCoordPointer( 2, GL_FLOAT, 0, coords );
qglColorPointer( 4, GL_FLOAT, 0, colors );
qglDrawArrays( GL_TRIANGLES, 0, ARRAY_LEN( verts ) );
}
qboolean FBO_Bloom( const float gamma, const float obScale, qboolean finalStage )
{
const int w = glConfig.vidWidth;
const int h = glConfig.vidHeight;
frameBuffer_t *src, *dst;
int finalBloomFBO;
int i;
if ( backEnd.doneBloom || !backEnd.doneSurfaces )
{
return qfalse;
}
backEnd.doneBloom = qtrue;
if ( !fboBloomInited )
{
if ( (fboBloomInited = FBO_CreateBloom() ) == qfalse )
{
ri.Printf( PRINT_WARNING, "...error creating framebuffers for bloom\n" );
ri.Cvar_Set( "r_bloom", "0" );
FBO_CleanBloom();
return qfalse;
}
else
{
ri.Printf( PRINT_ALL, "...bloom framebuffers created\n" );
}
}
if ( blitMSfbo )
{
FBO_BlitMS( qfalse );
blitMSfbo = qfalse;
}
// extract intensity from main FBO to BLOOM_BASE
src = &frameBuffers[ 0 ];
dst = &frameBuffers[ BLOOM_BASE ];
FBO_Bind( GL_FRAMEBUFFER, dst->fbo );
GL_BindTexture( 0, src->color );
qglViewport( 0, 0, dst->width, dst->height );
ARB_ProgramEnable( DUMMY_VERTEX, BLOOM_EXTRACT_FRAGMENT );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, r_bloom_threshold->value, r_bloom_threshold->value,
r_bloom_threshold->value, 1.0 );
RenderQuad( w, h );
// downscale and blur
src = frameBuffers + BLOOM_BASE;
for ( i = 1; i < fboBloomPasses; i++, src+=2 ) {
dst = src + 2;
// copy image to next level
#ifdef USE_FBO_BLIT
FBO_Bind( GL_READ_FRAMEBUFFER, src->fbo );
FBO_Bind( GL_DRAW_FRAMEBUFFER, dst->fbo );
qglBlitFramebuffer( 0, 0, src->width, src->height, 0, 0, dst->width, dst->height, GL_COLOR_BUFFER_BIT, GL_LINEAR );
#else
ARB_ProgramDisable();
FBO_Bind( GL_FRAMEBUFFER, dst->fbo );
GL_BindTexture( 0, src->color );
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
qglViewport( 0, 0, dst->width, dst->height );
RenderQuad( w, h );
#endif
FBO_Blur( dst, dst+1, dst );
}
// restore viewport
qglViewport( 0, 0, w, h );
// blend all bloom buffers to BLOOM_BASE+1 texture
finalBloomFBO = BLOOM_BASE+1;
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
FBO_Bind( GL_FRAMEBUFFER, frameBuffers[ finalBloomFBO ].fbo );
ARB_ProgramEnable( DUMMY_VERTEX, BLENDX_FRAGMENT );
// setup all texture units
for ( i = 0; i < fboBloomPasses - fboBloomBlendBase; i++ ) {
GL_BindTexture( i, frameBuffers[ (i+fboBloomBlendBase)*2 + BLOOM_BASE ].color );
}
RenderQuad( w, h );
if ( r_bloom_reflection->value )
{
ARB_ProgramDisable();
// copy final bloom image to some downscaled buffer
src = &frameBuffers[ finalBloomFBO ];
dst = &frameBuffers[ BLOOM_BASE + 2 + 2 ]; // 4x downscale
FBO_Bind( GL_DRAW_FRAMEBUFFER, dst->fbo );
FBO_Bind( GL_READ_FRAMEBUFFER, src->fbo );
qglBlitFramebuffer( 0, 0, src->width, src->height, 0, 0, dst->width, dst->height, GL_COLOR_BUFFER_BIT, GL_LINEAR );
// set render target to paired destination buffer and draw reflections
FBO_Bind( GL_DRAW_FRAMEBUFFER, (dst+1)->fbo );
GL_BindTexture( 0, dst->color );
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE );
qglViewport( 0, 0, dst->width, dst->height );
R_Bloom_LensEffect( fabs( r_bloom_reflection->value ) );
// restore color and blend mode
qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
// blur lens effect in paired buffer
FBO_Blur( dst+1, dst, dst+1 );
ARB_ProgramDisable();
// add lens effect to final bloom buffer
FBO_Bind( GL_FRAMEBUFFER, src->fbo );
if ( r_bloom_reflection->value > 0 ) {
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE );
} else {
// negative reflection values will replace bloom texture with just lens effect
}
qglViewport( 0, 0, w, h );
GL_BindTexture( 0, (dst+1)->color );
RenderQuad( w, h );
// restore blend mode
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
}
if ( windowAdjusted || backEnd.screenshotMask ) {
finalStage = qfalse; // can't blit directly into back buffer in this case
}
// if we don't need to read pixels later - blend directly to back buffer
if ( finalStage ) {
if ( backEnd.screenshotMask ) {
FBO_Bind( GL_FRAMEBUFFER, frameBuffers[ BLOOM_BASE ].fbo );
} else {
FBO_Bind( GL_FRAMEBUFFER, 0 );
}
} else {
FBO_Bind( GL_FRAMEBUFFER, frameBuffers[ BLOOM_BASE ].fbo );
}
GL_BindTexture( 1, frameBuffers[ finalBloomFBO ].color ); // final bloom texture
GL_BindTexture( 0, frameBuffers[ 0 ].color ); // original image
if ( finalStage ) {
// blend & apply gamma in one pass
ARB_ProgramEnable( DUMMY_VERTEX, BLEND2_GAMMA_FRAGMENT );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, gamma, gamma, gamma, obScale );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 1, r_bloom_intensity->value, 0, 0, 0 );
} else {
// just blend
ARB_ProgramEnable( DUMMY_VERTEX, BLEND2_FRAGMENT );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 1, r_bloom_intensity->value, 0, 0, 0 );
}
RenderQuad( w, h );
ARB_ProgramDisable();
if ( finalStage ) {
if ( backEnd.screenshotMask ) {
FBO_BlitToBackBuffer( BLOOM_BASE ); // so any further qglReadPixels() will read from BLOOM_BASE
// fboReadIndex = 0;
} else {
// already in back buffer
fboReadIndex = 0;
}
} else {
// we need depth/stencil buffers there
fboReadIndex = BLOOM_BASE;
}
return finalStage;
}
void R_BloomScreen( void )
{
if ( r_bloom->integer == 1 && fboEnabled && qglActiveTextureARB )
{
if ( !backEnd.doneBloom && backEnd.doneSurfaces )
{
if ( !backEnd.projection2D )
RB_SetGL2D();
qglColor4f( 1, 1, 1, 1 );
FBO_Bloom( 0, 0, qfalse );
}
}
}
void FBO_PostProcess( void )
{
const float obScale = 1 << tr.overbrightBits;
const float gamma = 1.0f / r_gamma->value;
const float w = glConfig.vidWidth;
const float h = glConfig.vidHeight;
qboolean minimized;
ARB_ProgramDisable();
if ( !backEnd.projection2D )
{
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
qglMatrixMode( GL_PROJECTION );
qglLoadMatrixf( GL_Ortho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 ) );
qglMatrixMode( GL_MODELVIEW );
qglLoadIdentity();
backEnd.projection2D = qtrue;
}
if ( blitMSfbo )
{
FBO_BlitMS( qfalse );
blitMSfbo = qfalse;
}
qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO );
GL_Cull( CT_TWO_SIDED );
if ( r_anaglyphMode->integer )
qglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
minimized = ri.CL_IsMinimized();
if ( r_bloom->integer && programCompiled && qglActiveTextureARB ) {
if ( FBO_Bloom( gamma, obScale, !minimized ) ) {
return;
}
}
// check if we can perform final draw directly into back buffer
if ( backEnd.screenshotMask == 0 && !windowAdjusted && !minimized ) {
FBO_Bind( GL_FRAMEBUFFER, 0 );
GL_BindTexture( 0, frameBuffers[ fboReadIndex ].color );
ARB_ProgramEnable( DUMMY_VERTEX, GAMMA_FRAGMENT );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, gamma, gamma, gamma, obScale );
RenderQuad( w, h );
ARB_ProgramDisable();
return;
}
// apply gamma shader
FBO_Bind( GL_FRAMEBUFFER, frameBuffers[ 1 ].fbo ); // destination - secondary buffer
GL_BindTexture( 0, frameBuffers[ fboReadIndex ].color ); // source - main color buffer
ARB_ProgramEnable( DUMMY_VERTEX, GAMMA_FRAGMENT );
qglProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 0, gamma, gamma, gamma, obScale );
RenderQuad( w, h );
ARB_ProgramDisable();
if ( !minimized ) {
FBO_BlitToBackBuffer( 1 );
}
}
void QGL_SetRenderScale( qboolean verbose )
{
windowAdjusted = qfalse;
blitX0 = blitY0 = 0;
blitX1 = gls.windowWidth;
blitY1 = gls.windowHeight;
blitFilter = GL_NEAREST;
superSampled = qfalse;
if ( !qglGenProgramsARB || !qglGenFramebuffers )
return;
if ( !r_fbo->integer )
{
if ( verbose && r_renderScale->integer )
{
ri.Printf( PRINT_ALL, "...ignoring \r_renderScale due to disabled FBO\n" );
}
return;
}
if ( r_ext_supersample->integer )
{
superSampled = qtrue;
blitFilter = GL_LINEAR; // default value for (r_renderScale==0) case
}
if ( gls.windowWidth != glConfig.vidWidth || gls.windowHeight != glConfig.vidHeight )
{
if ( r_renderScale->integer > 0 )
{
int scaleMode = r_renderScale->integer - 1;
if ( scaleMode & 1 )
{
// preserve aspect ratio (black bars on sides)
float windowAspect = (float) gls.windowWidth / (float) gls.windowHeight;
float renderAspect = (float) glConfig.vidWidth / (float) glConfig.vidHeight;
if ( windowAspect >= renderAspect )
{
float scale = (float) gls.windowHeight / ( float ) glConfig.vidHeight;
int bias = ( gls.windowWidth - scale * (float) glConfig.vidWidth ) / 2;
blitX0 += bias;
blitX1 -= bias;
}
else
{
float scale = (float) gls.windowWidth / ( float ) glConfig.vidWidth;
int bias = ( gls.windowHeight - scale * (float) glConfig.vidHeight ) / 2;
blitY0 += bias;
blitY1 -= bias;
}
}
// linear filtering
if ( scaleMode & 2 )
blitFilter = GL_LINEAR;
else
blitFilter = GL_NEAREST;
}
windowAdjusted = qtrue;
}
}
void QGL_DoneFBO( void )
{
if ( qglGenFramebuffers )
{
FBO_Bind(GL_FRAMEBUFFER, 0);
FBO_Clean(&frameBufferMS);
FBO_Clean(&frameBuffers[0]);
FBO_Clean(&frameBuffers[1]);
FBO_Clean(&frameBuffers[2]);
FBO_Clean(&frameBuffers[3]);
FBO_Clean(&frameBuffers[4]);
FBO_CleanBloom();
FBO_CleanDepth();
fboEnabled = qfalse;
fboBloomInited = qfalse;
}
}
void QGL_InitFBO( void )
{
int w, h;
qboolean depthStencil;
qboolean result = qfalse;
QGL_DoneFBO();
w = glConfig.vidWidth;
h = glConfig.vidHeight;
fboEnabled = qfalse;
frameBufferMultiSampling = qfalse;
if ( r_fbo->integer && ( !qglGenProgramsARB || !qglGenFramebuffers ) )
ri.Printf( PRINT_WARNING, "...FBO is not available\n" );
if ( !r_fbo->integer || !qglGenProgramsARB || !qglGenFramebuffers )
return;
qglGetError(); // reset error code
if ( windowAdjusted )
blitClear = 2; // front & back buffers
else
blitClear = 0;
switch ( r_hdr->integer )
{
case -1: fboInternalFormat = GL_RGBA4; break;
case 0: fboInternalFormat = GL_RGBA8; break;
default: fboInternalFormat = GL_RGBA16; break;
}
if ( FBO_CreateMS( &frameBufferMS, w, h ) )
{
frameBufferMultiSampling = qtrue;
if ( r_flares->integer )
depthStencil = qtrue;
else
depthStencil = qfalse;
result = FBO_Create( &frameBuffers[ 0 ], w, h, depthStencil, &fboTextureFormat, &fboTextureType )
&& FBO_Create( &frameBuffers[ 1 ], w, h, depthStencil, NULL, NULL )
&& FBO_Create( &frameBuffers[ 2 ], SCR_WIDTH, SCR_HEIGHT, qfalse, NULL, NULL )
&& FBO_Create( &frameBuffers[ 3 ], SCR_WIDTH, SCR_HEIGHT, qfalse, NULL, NULL );
frameBufferMultiSampling = result;
}
else
{
result = FBO_Create( &frameBuffers[ 0 ], w, h, qtrue, &fboTextureFormat, &fboTextureType )
&& FBO_Create( &frameBuffers[ 1 ], w, h, qtrue, NULL, NULL )
&& FBO_Create( &frameBuffers[ 2 ], SCR_WIDTH, SCR_HEIGHT, qfalse, NULL, NULL )
&& FBO_Create( &frameBuffers[ 3 ], SCR_WIDTH, SCR_HEIGHT, qfalse, NULL, NULL );
}
if ( result && superSampled )
{
result &= FBO_Create( &frameBuffers[ 4 ], gls.captureWidth, gls.captureHeight, qfalse, NULL, NULL );
}
if ( result )
{
fboEnabled = qtrue;
FBO_BindMain();
ri.Printf( PRINT_ALL, "...using %s (%s:%s) FBO\n", glDefToStr( fboInternalFormat ),
glDefToStr( fboTextureFormat ), glDefToStr( fboTextureType ) );
}
else
{
QGL_DoneFBO();
}
}
void QGL_InitARB( void )
{
ARB_UpdatePrograms();
QGL_SetRenderScale( qtrue );
QGL_InitFBO();
ri.Cvar_ResetGroup( CVG_RENDERER, qtrue );
}
void QGL_DoneARB( void )
{
QGL_DoneFBO();
if ( programCompiled )
{
ARB_ProgramDisable();
ARB_DeletePrograms();
}
}