mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-23 20:42:49 +00:00
823 lines
23 KiB
C++
823 lines
23 KiB
C++
#include "tr_local.h"
|
|
|
|
|
|
struct GLSL_Program {
|
|
GLuint p; // linked program
|
|
GLuint vs; // vertex shader
|
|
GLuint fs; // fragment shader
|
|
};
|
|
|
|
|
|
static GLuint progCurrent;
|
|
|
|
static void GL_Program( const GLSL_Program& prog )
|
|
{
|
|
assert( prog.p );
|
|
|
|
if ( prog.p != progCurrent ) {
|
|
qglUseProgram( prog.p );
|
|
progCurrent = prog.p;
|
|
}
|
|
}
|
|
|
|
void GL_Program()
|
|
{
|
|
if ( progCurrent != 0 ) {
|
|
qglUseProgram(0);
|
|
progCurrent = 0;
|
|
}
|
|
}
|
|
|
|
|
|
static GLSL_Program dynLightProg;
|
|
|
|
struct GLSL_DynLightProgramAttribs {
|
|
// vertex shader:
|
|
GLint osEyePos; // 4f, object-space
|
|
GLint osLightPos; // 4f, object-space
|
|
|
|
// pixel shader:
|
|
GLint texture; // 2D texture
|
|
GLint lightColorRadius; // 4f, w = 1 / (r^2)
|
|
};
|
|
|
|
static GLSL_DynLightProgramAttribs dynLightProgAttribs;
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
void GL2_SetupDynLight()
|
|
{
|
|
GL_Program( dynLightProg );
|
|
|
|
const dlight_t* dl = tess.light;
|
|
vec3_t lightColor;
|
|
VectorCopy( dl->color, lightColor );
|
|
|
|
qglUniform4f( dynLightProgAttribs.osLightPos, dl->transformed[0], dl->transformed[1], dl->transformed[2], 0.0f );
|
|
qglUniform4f( dynLightProgAttribs.osEyePos, backEnd.orient.viewOrigin[0], backEnd.orient.viewOrigin[1], backEnd.orient.viewOrigin[2], 0.0f );
|
|
qglUniform4f( dynLightProgAttribs.lightColorRadius, lightColor[0], lightColor[1], lightColor[2], 1.0f / Square(dl->radius) );
|
|
qglUniform1i( dynLightProgAttribs.texture, 0 ); // we use texture unit 0
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
static void GL2_StageIterator_Lighting()
|
|
{
|
|
backEnd.pc[RB_LIT_VERTICES_LATECULLTEST] += tess.numVertexes;
|
|
|
|
int i;
|
|
byte clipBits[SHADER_MAX_VERTEXES];
|
|
const dlight_t* dl = tess.light;
|
|
|
|
for ( i = 0; i < tess.numVertexes; ++i ) {
|
|
vec3_t dist;
|
|
VectorSubtract( dl->transformed, tess.xyz[i], dist );
|
|
|
|
if ( DotProduct( dist, tess.normal[i] ) <= 0.0 ) {
|
|
clipBits[i] = (byte)-1;
|
|
continue;
|
|
}
|
|
|
|
int 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
|
|
int numIndexes = 0;
|
|
unsigned hitIndexes[SHADER_MAX_INDEXES];
|
|
for ( i = 0; i < tess.numIndexes; i += 3 ) {
|
|
int a = tess.indexes[i];
|
|
int b = tess.indexes[i+1];
|
|
int c = tess.indexes[i+2];
|
|
if ( !(clipBits[a] & clipBits[b] & clipBits[c]) ) {
|
|
hitIndexes[numIndexes] = a;
|
|
hitIndexes[numIndexes+1] = b;
|
|
hitIndexes[numIndexes+2] = c;
|
|
numIndexes += 3;
|
|
}
|
|
}
|
|
|
|
backEnd.pc[RB_LIT_INDICES_LATECULL_IN] += numIndexes;
|
|
backEnd.pc[RB_LIT_INDICES_LATECULL_OUT] += tess.numIndexes - numIndexes;
|
|
|
|
if ( !numIndexes )
|
|
return;
|
|
|
|
GL_State( GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL );
|
|
|
|
const shaderStage_t* const pStage = tess.xstages[tess.shader->lightingStages[ST_DIFFUSE]];
|
|
GL_SelectTexture( 0 );
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
|
|
qglDrawElements( GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, hitIndexes );
|
|
}
|
|
|
|
|
|
static void GL2_StageIterator_LightingPass()
|
|
{
|
|
if (tess.shader->lightingStages[ST_DIFFUSE] == -1)
|
|
return;
|
|
|
|
RB_DeformTessGeometry();
|
|
|
|
GL_Cull( tess.shader->cullType );
|
|
|
|
const shaderStage_t* pStage = tess.xstages[ tess.shader->lightingStages[ST_DIFFUSE] ];
|
|
R_ComputeTexCoords( pStage, tess.svars );
|
|
|
|
// since this is guaranteed to be a single pass, fill and lock all the arrays
|
|
|
|
qglDisableClientState( GL_COLOR_ARRAY );
|
|
|
|
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords );
|
|
|
|
qglEnableClientState( GL_NORMAL_ARRAY );
|
|
qglNormalPointer( GL_FLOAT, 16, tess.normal );
|
|
|
|
qglVertexPointer( 3, GL_FLOAT, 16, tess.xyz );
|
|
qglLockArraysEXT( 0, tess.numVertexes );
|
|
|
|
GL2_StageIterator_Lighting();
|
|
|
|
qglUnlockArraysEXT();
|
|
|
|
qglDisableClientState( GL_NORMAL_ARRAY );
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
// returns qtrue if needs to break early
|
|
static qbool GL2_StageIterator_MultitextureStage( int stage )
|
|
{
|
|
static stageVars_t svarsMT; // this is a huge struct
|
|
|
|
const shaderStage_t* pPrevStage = tess.xstages[stage++];
|
|
const shaderStage_t* pStage = tess.xstages[stage];
|
|
const qbool lightmapOnly = r_lightmap->integer && (pStage->type == ST_LIGHTMAP || pPrevStage->type == ST_LIGHTMAP);
|
|
|
|
if ( lightmapOnly ) {
|
|
if ( pStage->type == ST_LIGHTMAP ) {
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
R_ComputeTexCoords( pStage, svarsMT );
|
|
qglTexCoordPointer( 2, GL_FLOAT, 0, svarsMT.texcoords );
|
|
}
|
|
qglDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
return qtrue;
|
|
}
|
|
|
|
if ( r_fullbright->integer ) {
|
|
if ( pStage->type == ST_LIGHTMAP ) {
|
|
Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 );
|
|
qglDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
return qfalse;
|
|
} else if ( pPrevStage->type == ST_LIGHTMAP ) {
|
|
Com_Memset( tess.svars.colors, tr.identityLightByte, tess.numVertexes * 4 );
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
R_ComputeTexCoords( pStage, svarsMT );
|
|
qglTexCoordPointer( 2, GL_FLOAT, 0, svarsMT.texcoords );
|
|
qglDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
return qfalse;
|
|
}
|
|
}
|
|
|
|
GL_SelectTexture( 1 );
|
|
qglEnable( GL_TEXTURE_2D );
|
|
GL_TexEnv( pStage->mtEnv );
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
|
|
R_ComputeTexCoords( pStage, svarsMT );
|
|
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
qglTexCoordPointer( 2, GL_FLOAT, 0, svarsMT.texcoords );
|
|
|
|
qglDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
|
|
qglDisable( GL_TEXTURE_2D );
|
|
GL_SelectTexture( 0 );
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
void GL2_StageIterator()
|
|
{
|
|
if (tess.pass == shaderCommands_t::TP_LIGHT) {
|
|
GL2_StageIterator_LightingPass();
|
|
return;
|
|
}
|
|
|
|
GL_Program();
|
|
|
|
RB_DeformTessGeometry();
|
|
|
|
GL_Cull( tess.shader->cullType );
|
|
|
|
if ( tess.shader->polygonOffset )
|
|
qglEnable( GL_POLYGON_OFFSET_FILL );
|
|
|
|
// geometry is per-shader and can be compiled
|
|
// color and tc are per-stage, and can't
|
|
|
|
qglDisableClientState( GL_COLOR_ARRAY );
|
|
qglDisableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
|
|
qglVertexPointer( 3, GL_FLOAT, 16, tess.xyz ); // padded for SIMD
|
|
qglLockArraysEXT( 0, tess.numVertexes );
|
|
|
|
qglEnableClientState( GL_COLOR_ARRAY );
|
|
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
|
|
qglEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords );
|
|
|
|
for ( int stage = 0; stage < tess.shader->numStages; ++stage ) {
|
|
const shaderStage_t* pStage = tess.xstages[stage];
|
|
R_ComputeColors( pStage, tess.svars );
|
|
R_ComputeTexCoords( pStage, tess.svars );
|
|
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
GL_State( pStage->stateBits );
|
|
|
|
// !!! FUCKING ati drivers incorrectly need this
|
|
// they're locking+pulling the color array even tho it was explicitly NOT locked
|
|
// so color changes are ignored unless we "update" the color pointer again
|
|
qglColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars.colors );
|
|
qglTexCoordPointer( 2, GL_FLOAT, 0, tess.svars.texcoords );
|
|
|
|
if ( pStage->mtStages ) {
|
|
// we can't really cope with massive collapses, so
|
|
assert( pStage->mtStages == 1 );
|
|
if ( GL2_StageIterator_MultitextureStage( stage ) )
|
|
break;
|
|
stage += pStage->mtStages;
|
|
continue;
|
|
}
|
|
|
|
qglDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
}
|
|
|
|
if ( tess.fogNum && tess.shader->fogPass )
|
|
RB_FogPass();
|
|
|
|
qglUnlockArraysEXT();
|
|
|
|
if ( tess.shader->polygonOffset )
|
|
qglDisable( GL_POLYGON_OFFSET_FILL );
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
|
|
|
|
static qbool GL2_CreateShader( GLuint* shaderPtr, GLenum shaderType, const char* shaderSource )
|
|
{
|
|
GLuint shader = qglCreateShader( shaderType );
|
|
qglShaderSource( shader, 1, &shaderSource, NULL );
|
|
qglCompileShader( shader );
|
|
|
|
GLint result = GL_FALSE;
|
|
qglGetShaderiv( shader, GL_COMPILE_STATUS, &result );
|
|
if ( result == GL_TRUE ) {
|
|
*shaderPtr = shader;
|
|
return qtrue;
|
|
}
|
|
|
|
GLint logLength = 0;
|
|
qglGetShaderiv( shader, GL_INFO_LOG_LENGTH, &logLength );
|
|
|
|
static char log[4096]; // I've seen logs over 3 KB in size.
|
|
qglGetShaderInfoLog( shader, sizeof(log), NULL, log );
|
|
ri.Printf( PRINT_ERROR, "%s shader: %s\n", shaderType == GL_VERTEX_SHADER ? "Vertex" : "Fragment", log );
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
static qbool GL2_CreateProgram( GLSL_Program& prog, const char* vs, const char* fs )
|
|
{
|
|
if ( !GL2_CreateShader( &prog.vs, GL_VERTEX_SHADER, vs ) )
|
|
return qfalse;
|
|
|
|
if ( !GL2_CreateShader( &prog.fs, GL_FRAGMENT_SHADER, fs ) )
|
|
return qfalse;
|
|
|
|
prog.p = qglCreateProgram();
|
|
qglAttachShader( prog.p, prog.vs );
|
|
qglAttachShader( prog.p, prog.fs );
|
|
qglLinkProgram( prog.p );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
// We don't use "gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;"
|
|
// because most everything is rendered using the fixed function pipeline and
|
|
// ftransform makes sure we get matching results (and thus avoid Z-fighting).
|
|
|
|
static const char* dynLightVS =
|
|
"uniform vec4 osLightPos;\n"
|
|
"uniform vec4 osEyePos;\n"
|
|
"varying vec4 L;\n" // object-space light vector
|
|
"varying vec4 V;\n" // object-space view vector
|
|
"varying vec3 nN;\n" // normalized object-space normal vector
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0] = gl_MultiTexCoord0;\n"
|
|
" L = osLightPos - gl_Vertex;\n"
|
|
" V = osEyePos - gl_Vertex;\n"
|
|
" nN = gl_Normal;\n"
|
|
"}\n"
|
|
"";
|
|
|
|
static const char* dynLightFS =
|
|
"uniform sampler2D texture;\n"
|
|
"uniform vec4 lightColorRadius;" // w = 1 / (r^2)
|
|
"varying vec4 L;\n" // object-space light vector
|
|
"varying vec4 V;\n" // object-space view vector
|
|
"varying vec3 nN;\n" // normalized object-space normal vector
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec4 base = texture2D(texture, gl_TexCoord[0].xy);\n"
|
|
" vec3 nL = normalize(L.xyz);\n" // normalized light vector
|
|
" vec3 nV = normalize(V.xyz);\n" // normalized view vector
|
|
// light intensity
|
|
" float intensFactor = dot(L.xyz, L.xyz) * lightColorRadius.w;"
|
|
" vec3 intens = lightColorRadius.rgb * (1.0 - intensFactor);\n"
|
|
// specular reflection term (N.H)
|
|
" float specFactor = clamp(dot(nN, normalize(nL + nV)), 0.0, 1.0);\n"
|
|
" float spec = pow(specFactor, 16.0) * 0.25;\n"
|
|
// Lambertian diffuse reflection term (N.L)
|
|
" float diffuse = clamp(dot(nN, nL), 0.0, 1.0);\n"
|
|
" gl_FragColor = (base * vec4(diffuse) + vec4(spec)) * vec4(intens, 1.0);\n"
|
|
"}\n"
|
|
"";
|
|
|
|
|
|
struct FrameBuffer {
|
|
GLuint fbo;
|
|
GLuint color; // texture if MS, buffer if SS
|
|
GLuint depthStencil; // texture if MS, buffer if SS
|
|
qbool multiSampled;
|
|
qbool hasDepthStencil;
|
|
};
|
|
|
|
static FrameBuffer frameBufferMain;
|
|
static FrameBuffer frameBuffersPostProcess[2];
|
|
static unsigned int frameBufferReadIndex = 0; // read this for the latest color/depth data
|
|
static qbool frameBufferMultiSampling = qfalse;
|
|
|
|
|
|
#define CASE( x ) case x: return #x
|
|
#define GL( call ) call; GL2_CheckError( #call, __FUNCTION__, __FILE__, __LINE__ )
|
|
|
|
|
|
static const char* GL2_GetErrorString( GLenum ec )
|
|
{
|
|
switch ( ec )
|
|
{
|
|
CASE( GL_NO_ERROR );
|
|
CASE( GL_INVALID_ENUM );
|
|
CASE( GL_INVALID_VALUE );
|
|
CASE( GL_INVALID_OPERATION );
|
|
CASE( GL_INVALID_FRAMEBUFFER_OPERATION );
|
|
CASE( GL_OUT_OF_MEMORY );
|
|
CASE( GL_STACK_UNDERFLOW );
|
|
CASE( GL_STACK_OVERFLOW );
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
|
|
static void GL2_CheckError( const char* call, const char* function, const char* file, int line )
|
|
{
|
|
const GLenum ec = qglGetError();
|
|
if ( ec == GL_NO_ERROR )
|
|
return;
|
|
|
|
const char* fileName = file;
|
|
while ( *file )
|
|
{
|
|
if ( *file == '/' || *file == '\\' )
|
|
fileName = file + 1;
|
|
|
|
++file;
|
|
}
|
|
|
|
ri.Printf( PRINT_ERROR, "%s failed\n", call );
|
|
ri.Printf( PRINT_ERROR, "%s:%d in %s\n", fileName, line, function );
|
|
ri.Printf( PRINT_ERROR, "GL error code: 0x%X (%d)\n", (unsigned int)ec, (int)ec );
|
|
ri.Printf( PRINT_ERROR, "GL error message: %s\n", GL2_GetErrorString(ec) );
|
|
}
|
|
|
|
|
|
static const char* GL2_GetFBOStatusString( GLenum status )
|
|
{
|
|
switch ( status )
|
|
{
|
|
CASE( GL_FRAMEBUFFER_COMPLETE );
|
|
CASE( GL_FRAMEBUFFER_UNDEFINED );
|
|
CASE( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT );
|
|
CASE( GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT );
|
|
CASE( GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER );
|
|
CASE( GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER );
|
|
CASE( GL_FRAMEBUFFER_UNSUPPORTED );
|
|
CASE( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE );
|
|
CASE( GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS );
|
|
default: return "?";
|
|
}
|
|
}
|
|
|
|
|
|
#undef CASE
|
|
|
|
|
|
static qbool GL2_FBO_CreateSS( FrameBuffer& fb, qbool depthStencil )
|
|
{
|
|
while ( qglGetError() != GL_NO_ERROR ) {} // clear the error queue
|
|
|
|
if ( depthStencil )
|
|
{
|
|
GL(qglGenTextures( 1, &fb.depthStencil ));
|
|
GL(qglBindTexture( GL_TEXTURE_2D, fb.depthStencil ));
|
|
GL(qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ));
|
|
GL(qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ));
|
|
GL(qglTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, glConfig.vidWidth, glConfig.vidHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL ));
|
|
}
|
|
|
|
GL(qglGenTextures( 1, &fb.color ));
|
|
GL(qglBindTexture( GL_TEXTURE_2D, fb.color ));
|
|
GL(qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ));
|
|
GL(qglTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ));
|
|
GL(qglTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, glConfig.vidWidth, glConfig.vidHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL ));
|
|
|
|
GL(qglGenFramebuffers( 1, &fb.fbo ));
|
|
GL(qglBindFramebuffer( GL_FRAMEBUFFER, fb.fbo ));
|
|
GL(qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color, 0 ));
|
|
if ( depthStencil )
|
|
GL(qglFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fb.depthStencil, 0 ));
|
|
|
|
const GLenum fboStatus = qglCheckFramebufferStatus( GL_FRAMEBUFFER );
|
|
if ( fboStatus != GL_FRAMEBUFFER_COMPLETE )
|
|
{
|
|
ri.Printf( PRINT_ERROR, "Failed to create FBO (status 0x%X, error 0x%X)\n", (unsigned int)fboStatus, (unsigned int)qglGetError() );
|
|
ri.Printf( PRINT_ERROR, "FBO status string: %s\n", GL2_GetFBOStatusString(fboStatus) );
|
|
return qfalse;
|
|
}
|
|
|
|
qglBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
|
fb.multiSampled = qfalse;
|
|
fb.hasDepthStencil = depthStencil;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static qbool GL2_FBO_CreateMS( FrameBuffer& fb )
|
|
{
|
|
while ( qglGetError() != GL_NO_ERROR ) {} // clear the error queue
|
|
|
|
GL(qglGenFramebuffers( 1, &fb.fbo ));
|
|
GL(qglBindFramebuffer( GL_FRAMEBUFFER, fb.fbo ));
|
|
|
|
GL(qglGenRenderbuffers( 1, &fb.color ));
|
|
GL(qglBindRenderbuffer( GL_RENDERBUFFER, fb.color ));
|
|
GL(qglRenderbufferStorageMultisample( GL_RENDERBUFFER, r_msaa->integer, GL_RGBA8, glConfig.vidWidth, glConfig.vidHeight ));
|
|
GL(qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb.color ));
|
|
|
|
GL(qglGenRenderbuffers( 1, &fb.depthStencil ));
|
|
GL(qglBindRenderbuffer( GL_RENDERBUFFER, fb.depthStencil ));
|
|
GL(qglRenderbufferStorageMultisample( GL_RENDERBUFFER, r_msaa->integer, GL_DEPTH24_STENCIL8, glConfig.vidWidth, glConfig.vidHeight ));
|
|
|
|
GL(qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb.color ));
|
|
GL(qglFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.depthStencil ));
|
|
|
|
const GLenum fboStatus = qglCheckFramebufferStatus( GL_FRAMEBUFFER );
|
|
if ( fboStatus != GL_FRAMEBUFFER_COMPLETE )
|
|
{
|
|
ri.Printf( PRINT_ERROR, "Failed to create FBO (status 0x%X, error 0x%X)\n", (unsigned int)fboStatus, (unsigned int)qglGetError() );
|
|
ri.Printf( PRINT_ERROR, "FBO status string: %s\n", GL2_GetFBOStatusString(fboStatus) );
|
|
return qfalse;
|
|
}
|
|
|
|
qglBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
|
fb.multiSampled = qtrue;
|
|
fb.hasDepthStencil = qtrue;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static qbool GL2_FBO_Init()
|
|
{
|
|
const int msaa = r_msaa->integer;
|
|
const qbool validOption = msaa >= 2 && msaa <= 16;
|
|
const qbool enable = validOption && qglRenderbufferStorageMultisample != NULL;
|
|
frameBufferMultiSampling = enable;
|
|
if ( validOption && !enable )
|
|
ri.Printf( PRINT_WARNING, "MSAA requested but disabled because glRenderbufferStorageMultisample wasn't found\n" );
|
|
|
|
if ( !enable )
|
|
return GL2_FBO_CreateSS( frameBuffersPostProcess[0], qtrue ) &&
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[1], qtrue );
|
|
|
|
return GL2_FBO_CreateMS( frameBufferMain ) &&
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[0], qfalse ) &&
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[1], qfalse );
|
|
}
|
|
|
|
|
|
static void GL2_FBO_Bind( const FrameBuffer& fb )
|
|
{
|
|
qglBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
|
|
qglReadBuffer( GL_COLOR_ATTACHMENT0 );
|
|
qglDrawBuffer( GL_COLOR_ATTACHMENT0 );
|
|
}
|
|
|
|
|
|
static void GL2_FBO_Bind()
|
|
{
|
|
GL2_FBO_Bind( frameBuffersPostProcess[frameBufferReadIndex] );
|
|
}
|
|
|
|
|
|
static void GL2_FBO_Swap()
|
|
{
|
|
frameBufferReadIndex ^= 1;
|
|
}
|
|
|
|
|
|
static void GL2_FBO_BlitSSToBackBuffer()
|
|
{
|
|
// fixing up the blit mode here to avoid unnecessary qglClear calls
|
|
int blitMode = r_blitMode->integer;
|
|
if ( r_mode->integer != VIDEOMODE_UPSCALE )
|
|
blitMode = BLITMODE_STRETCHED;
|
|
|
|
if ( blitMode != BLITMODE_STRETCHED ) {
|
|
qglBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
|
qglClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
|
|
qglClear( GL_COLOR_BUFFER_BIT );
|
|
}
|
|
|
|
const FrameBuffer& fbo = frameBuffersPostProcess[frameBufferReadIndex];
|
|
qglBindFramebuffer( GL_READ_FRAMEBUFFER, fbo.fbo );
|
|
qglBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
|
|
qglReadBuffer( GL_COLOR_ATTACHMENT0 );
|
|
qglDrawBuffer( GL_BACK );
|
|
|
|
const int sw = glConfig.vidWidth;
|
|
const int sh = glConfig.vidHeight;
|
|
const int dw = glInfo.winWidth;
|
|
const int dh = glInfo.winHeight;
|
|
if ( blitMode == BLITMODE_STRETCHED ) {
|
|
qglBlitFramebuffer( 0, 0, sw, sh, 0, 0, dw, dh, GL_COLOR_BUFFER_BIT, GL_LINEAR );
|
|
} else if ( blitMode == BLITMODE_CENTERED ) {
|
|
const int dx = ( dw - sw ) / 2;
|
|
const int dy = ( dh - sh ) / 2;
|
|
qglBlitFramebuffer( 0, 0, sw, sh, dx, dy, dx + sw, dy + sh, GL_COLOR_BUFFER_BIT, GL_LINEAR );
|
|
} else { // blitMode == BLITMODE_ASPECT
|
|
const float rx = (float)dw / (float)sw;
|
|
const float ry = (float)dh / (float)sh;
|
|
const float ar = min( rx, ry );
|
|
const int w = (int)( sw * ar );
|
|
const int h = (int)( sh * ar );
|
|
const int x = ( dw - w ) / 2;
|
|
const int y = ( dh - h ) / 2;
|
|
qglBlitFramebuffer( 0, 0, sw, sh, x, y, x + w, y + h, GL_COLOR_BUFFER_BIT, GL_LINEAR );
|
|
}
|
|
}
|
|
|
|
|
|
static void GL2_FBO_BlitMSToSS()
|
|
{
|
|
const FrameBuffer& r = frameBufferMain;
|
|
const FrameBuffer& d = frameBuffersPostProcess[frameBufferReadIndex];
|
|
qglBindFramebuffer( GL_READ_FRAMEBUFFER, r.fbo );
|
|
qglBindFramebuffer( GL_DRAW_FRAMEBUFFER, d.fbo );
|
|
qglReadBuffer( GL_COLOR_ATTACHMENT0 );
|
|
qglDrawBuffer( GL_COLOR_ATTACHMENT0 );
|
|
|
|
const int w = glConfig.vidWidth;
|
|
const int h = glConfig.vidHeight;
|
|
qglBlitFramebuffer( 0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR );
|
|
}
|
|
|
|
|
|
static void GL2_FullScreenQuad()
|
|
{
|
|
const float w = glConfig.vidWidth;
|
|
const float h = glConfig.vidHeight;
|
|
qglBegin( GL_QUADS );
|
|
qglTexCoord2f( 0.0f, 0.0f );
|
|
qglVertex2f( 0.0f, h );
|
|
qglTexCoord2f( 0.0f, 1.0f );
|
|
qglVertex2f( 0.0f, 0.0f );
|
|
qglTexCoord2f( 1.0f, 1.0f );
|
|
qglVertex2f( w, 0.0f );
|
|
qglTexCoord2f( 1.0f, 0.0f );
|
|
qglVertex2f( w, h );
|
|
qglEnd();
|
|
}
|
|
|
|
|
|
static GLSL_Program gammaProg;
|
|
|
|
struct GLSL_GammaProgramAttribs
|
|
{
|
|
int texture;
|
|
int gammaOverbright;
|
|
};
|
|
|
|
static GLSL_GammaProgramAttribs gammaProgAttribs;
|
|
|
|
|
|
static void GL2_PostProcessGamma()
|
|
{
|
|
const float brightness = r_brightness->value;
|
|
const float gamma = 1.0f / r_gamma->value;
|
|
|
|
if ( gamma == 1.0f && brightness == 1.0f )
|
|
return;
|
|
|
|
GL2_FBO_Swap();
|
|
GL2_FBO_Bind();
|
|
|
|
GL_Program( gammaProg );
|
|
qglUniform1i( gammaProgAttribs.texture, 0 ); // we use texture unit 0
|
|
qglUniform4f( gammaProgAttribs.gammaOverbright, gamma, gamma, gamma, brightness );
|
|
GL_SelectTexture( 0 );
|
|
qglBindTexture( GL_TEXTURE_2D, frameBuffersPostProcess[frameBufferReadIndex ^ 1].color );
|
|
GL2_FullScreenQuad();
|
|
}
|
|
|
|
|
|
static const char* gammaVS =
|
|
"void main()\n"
|
|
"{\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0] = gl_MultiTexCoord0;\n"
|
|
"}\n"
|
|
"";
|
|
|
|
static const char* gammaFS =
|
|
"uniform sampler2D texture;\n"
|
|
"uniform vec4 gammaOverbright;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec3 base = texture2D(texture, gl_TexCoord[0].xy).rgb;\n"
|
|
" gl_FragColor = vec4(pow(base, gammaOverbright.xyz) * gammaOverbright.w, 1.0);\n"
|
|
"}\n"
|
|
"";
|
|
|
|
|
|
static GLSL_Program greyscaleProg;
|
|
|
|
struct GLSL_GreyscaleProgramAttribs
|
|
{
|
|
int texture;
|
|
int greyscale;
|
|
};
|
|
|
|
static GLSL_GreyscaleProgramAttribs greyscaleProgAttribs;
|
|
static qbool greyscaleProgramValid = qfalse;
|
|
|
|
|
|
static void GL2_PostProcessGreyscale()
|
|
{
|
|
if ( !greyscaleProgramValid )
|
|
return;
|
|
|
|
const float greyscale = Com_Clamp( 0.0f, 1.0f, r_greyscale->value );
|
|
if ( greyscale == 0.0f )
|
|
return;
|
|
|
|
GL2_FBO_Swap();
|
|
GL2_FBO_Bind();
|
|
|
|
GL_Program( greyscaleProg );
|
|
qglUniform1i( greyscaleProgAttribs.texture, 0 ); // we use texture unit 0
|
|
qglUniform1f( greyscaleProgAttribs.greyscale, greyscale );
|
|
GL_SelectTexture( 0 );
|
|
qglBindTexture( GL_TEXTURE_2D, frameBuffersPostProcess[frameBufferReadIndex ^ 1].color );
|
|
GL2_FullScreenQuad();
|
|
}
|
|
|
|
|
|
static const char* greyscaleVS =
|
|
"void main()\n"
|
|
"{\n"
|
|
" gl_Position = ftransform();\n"
|
|
" gl_TexCoord[0] = gl_MultiTexCoord0;\n"
|
|
"}\n"
|
|
"";
|
|
|
|
static const char* greyscaleFS =
|
|
"uniform sampler2D texture;\n"
|
|
"uniform float greyscale;\n"
|
|
"\n"
|
|
"void main()\n"
|
|
"{\n"
|
|
" vec3 base = texture2D(texture, gl_TexCoord[0].xy).rgb;\n"
|
|
" vec3 grey = vec3(0.299 * base.r + 0.587 * base.g + 0.114 * base.b);\n"
|
|
" gl_FragColor = vec4(mix(base, grey, greyscale), 1.0);\n"
|
|
"}\n"
|
|
"";
|
|
|
|
|
|
qbool GL2_Init()
|
|
{
|
|
if ( !GL2_FBO_Init() ) {
|
|
ri.Printf( PRINT_ERROR, "Failed to create FBOs\n" );
|
|
return qfalse;
|
|
}
|
|
|
|
if ( !GL2_CreateProgram( dynLightProg, dynLightVS, dynLightFS ) ) {
|
|
ri.Printf( PRINT_ERROR, "Failed to compile dynamic light shaders\n" );
|
|
return qfalse;
|
|
}
|
|
dynLightProgAttribs.osEyePos = qglGetUniformLocation( dynLightProg.p, "osEyePos" );
|
|
dynLightProgAttribs.osLightPos = qglGetUniformLocation( dynLightProg.p, "osLightPos" );
|
|
dynLightProgAttribs.texture = qglGetUniformLocation( dynLightProg.p, "texture" );
|
|
dynLightProgAttribs.lightColorRadius = qglGetUniformLocation( dynLightProg.p, "lightColorRadius" );
|
|
|
|
if ( !GL2_CreateProgram( gammaProg, gammaVS, gammaFS ) ) {
|
|
ri.Printf( PRINT_ERROR, "Failed to compile gamma correction shaders\n" );
|
|
return qfalse;
|
|
}
|
|
gammaProgAttribs.texture = qglGetUniformLocation( gammaProg.p, "texture" );
|
|
gammaProgAttribs.gammaOverbright = qglGetUniformLocation( gammaProg.p, "gammaOverbright" );
|
|
|
|
greyscaleProgramValid = GL2_CreateProgram( greyscaleProg, greyscaleVS, greyscaleFS );
|
|
if ( greyscaleProgramValid ) {
|
|
greyscaleProgAttribs.texture = qglGetUniformLocation( greyscaleProg.p, "texture" );
|
|
greyscaleProgAttribs.greyscale = qglGetUniformLocation( greyscaleProg.p, "greyscale" );
|
|
} else {
|
|
ri.Printf( PRINT_ERROR, "Failed to compile greyscale shaders\n" );
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
void GL2_BeginFrame()
|
|
{
|
|
if ( frameBufferMultiSampling )
|
|
GL2_FBO_Bind( frameBufferMain );
|
|
else
|
|
GL2_FBO_Bind();
|
|
|
|
GL_Program();
|
|
}
|
|
|
|
|
|
void GL2_EndFrame()
|
|
{
|
|
if ( frameBufferMultiSampling )
|
|
GL2_FBO_BlitMSToSS();
|
|
|
|
// this call is needed because there is no insurance for
|
|
// what the state might be right now
|
|
// we disable depth test, depth write and blending
|
|
GL_State( GLS_DEPTHTEST_DISABLE );
|
|
|
|
qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
|
|
GL2_PostProcessGamma();
|
|
GL2_PostProcessGreyscale();
|
|
|
|
// needed for later calls to GL_Bind because
|
|
// the above functions use qglBindTexture directly
|
|
glState.texID[glState.currenttmu] = 0;
|
|
|
|
qglViewport (0, 0, glInfo.winWidth, glInfo.winHeight );
|
|
qglScissor( 0, 0, glInfo.winWidth, glInfo.winHeight );
|
|
|
|
GL2_FBO_BlitSSToBackBuffer();
|
|
}
|
|
|