mirror of
https://github.com/Q3Rally-Team/rallyunlimited-engine.git
synced 2024-11-25 21:51:20 +00:00
2206 lines
58 KiB
C
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();
|
|
}
|
|
}
|