mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-30 15:52:16 +00:00
ca9757082f
this is because it would draw (parts of) geometric edges with different colors and that makes visual inspections annoying also, final MSAA sample counts are always reported by GL3 and GL2 now
1590 lines
41 KiB
C++
1590 lines
41 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 1999-2005 Id Software, Inc.
|
|
|
|
This file is part of Quake III Arena source code.
|
|
|
|
Quake III Arena source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Quake III Arena source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Quake III Arena source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
// OpenGL 2.0+ rendering back-end
|
|
|
|
#include "tr_local.h"
|
|
#include "GL/glew.h"
|
|
|
|
|
|
#define GL_INDEX_TYPE GL_UNSIGNED_INT
|
|
|
|
|
|
// the renderer front end should never modify glstate_t
|
|
typedef struct {
|
|
int currenttmu;
|
|
int texID[MAX_TMUS];
|
|
int texEnv[MAX_TMUS];
|
|
qbool finishCalled;
|
|
int faceCulling;
|
|
unsigned glStateBits;
|
|
} glstate_t;
|
|
|
|
static glstate_t glState;
|
|
|
|
|
|
static void GL_Bind( const image_t* image );
|
|
static void GL_SelectTexture( int unit );
|
|
static void GL_State( unsigned long stateVector );
|
|
static void GL_TexEnv( int env );
|
|
static void GL_Cull( int cullType );
|
|
static void R_BindAnimatedImage( const textureBundle_t* bundle );
|
|
static void RB_FogPass();
|
|
static void GAL_Begin2D();
|
|
static GLint GetTexEnv( texEnv_t texEnv );
|
|
|
|
void GL_GetRenderTargetFormat( GLenum* internalFormat, GLenum* format, GLenum* type, int cnq3Format );
|
|
void GL_CreateColorRenderBufferStorageMS( int* samples );
|
|
|
|
|
|
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 ) {
|
|
glUseProgram( prog.p );
|
|
progCurrent = prog.p;
|
|
}
|
|
}
|
|
|
|
static void GL_Program()
|
|
{
|
|
if ( progCurrent != 0 ) {
|
|
glUseProgram(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;
|
|
|
|
|
|
static void GAL_BeginDynamicLight()
|
|
{
|
|
GL_Program( dynLightProg );
|
|
|
|
const dlight_t* dl = tess.light;
|
|
vec3_t lightColor;
|
|
VectorCopy( dl->color, lightColor );
|
|
|
|
glUniform4f( dynLightProgAttribs.osLightPos, dl->transformed[0], dl->transformed[1], dl->transformed[2], 0.0f );
|
|
glUniform4f( dynLightProgAttribs.osEyePos, backEnd.orient.viewOrigin[0], backEnd.orient.viewOrigin[1], backEnd.orient.viewOrigin[2], 0.0f );
|
|
glUniform4f( dynLightProgAttribs.lightColorRadius, lightColor[0], lightColor[1], lightColor[2], 1.0f / Square(dl->radius) );
|
|
glUniform1i( dynLightProgAttribs.texture, 0 ); // we use texture unit 0
|
|
}
|
|
|
|
|
|
static void DrawDynamicLight()
|
|
{
|
|
GL_Cull( tess.shader->cullType );
|
|
|
|
if ( tess.shader->polygonOffset )
|
|
glEnable( GL_POLYGON_OFFSET_FILL );
|
|
|
|
const int stage = tess.shader->lightingStages[ST_DIFFUSE];
|
|
const shaderStage_t* pStage = tess.xstages[ stage ];
|
|
|
|
// since this is guaranteed to be a single pass, fill and lock all the arrays
|
|
|
|
glDisableClientState( GL_COLOR_ARRAY );
|
|
|
|
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
glTexCoordPointer( 2, GL_FLOAT, 0, tess.svars[stage].texcoordsptr );
|
|
|
|
glEnableClientState( GL_NORMAL_ARRAY );
|
|
glNormalPointer( GL_FLOAT, 16, tess.normal );
|
|
|
|
glVertexPointer( 3, GL_FLOAT, 16, tess.xyz );
|
|
glLockArraysEXT( 0, tess.numVertexes );
|
|
|
|
const int oldAlphaTestBits = pStage->stateBits & GLS_ATEST_BITS;
|
|
const int newBits = GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE | GLS_DEPTHFUNC_EQUAL;
|
|
GL_State( oldAlphaTestBits | newBits );
|
|
|
|
GL_SelectTexture( 0 );
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
|
|
glDrawElements( GL_TRIANGLES, tess.dlNumIndexes, GL_INDEX_TYPE, tess.dlIndexes );
|
|
|
|
glUnlockArraysEXT();
|
|
|
|
glDisableClientState( GL_NORMAL_ARRAY );
|
|
|
|
if ( tess.shader->polygonOffset )
|
|
glDisable( GL_POLYGON_OFFSET_FILL );
|
|
}
|
|
|
|
|
|
// returns qtrue if needs to break early
|
|
static qbool GL2_StageIterator_MultitextureStage( int stage )
|
|
{
|
|
const shaderStage_t* const pStage = tess.xstages[stage];
|
|
|
|
GL_SelectTexture( 1 );
|
|
glEnable( GL_TEXTURE_2D );
|
|
GL_TexEnv( GetTexEnv( pStage->mtEnv ) );
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
|
|
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
glTexCoordPointer( 2, GL_FLOAT, 0, tess.svars[stage].texcoordsptr );
|
|
|
|
glDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
|
|
glDisable( GL_TEXTURE_2D );
|
|
GL_SelectTexture( 0 );
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
|
|
static void DrawGeneric()
|
|
{
|
|
GL_Program();
|
|
|
|
GL_Cull( tess.shader->cullType );
|
|
|
|
if ( tess.shader->polygonOffset )
|
|
glEnable( GL_POLYGON_OFFSET_FILL );
|
|
|
|
// geometry is per-shader and can be compiled
|
|
// color and tc are per-stage, and can't
|
|
|
|
glDisableClientState( GL_COLOR_ARRAY );
|
|
glDisableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
|
|
glVertexPointer( 3, GL_FLOAT, 16, tess.xyz ); // padded for SIMD
|
|
glLockArraysEXT( 0, tess.numVertexes );
|
|
|
|
glEnableClientState( GL_COLOR_ARRAY );
|
|
glEnableClientState( GL_TEXTURE_COORD_ARRAY );
|
|
|
|
for ( int stage = 0; stage < tess.shader->numStages; ++stage ) {
|
|
const shaderStage_t* pStage = tess.xstages[stage];
|
|
glColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svars[stage].colors );
|
|
glTexCoordPointer( 2, GL_FLOAT, 0, tess.svars[stage].texcoordsptr );
|
|
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
GL_State( pStage->stateBits );
|
|
|
|
if ( pStage->mtStages ) {
|
|
// we can't really cope with massive collapses, so
|
|
assert( pStage->mtStages == 1 );
|
|
if ( GL2_StageIterator_MultitextureStage( stage + 1 ) )
|
|
break;
|
|
stage += pStage->mtStages;
|
|
continue;
|
|
}
|
|
|
|
glDrawElements( GL_TRIANGLES, tess.numIndexes, GL_INDEX_TYPE, tess.indexes );
|
|
}
|
|
|
|
if ( tess.drawFog )
|
|
RB_FogPass();
|
|
|
|
glUnlockArraysEXT();
|
|
|
|
if ( tess.shader->polygonOffset )
|
|
glDisable( GL_POLYGON_OFFSET_FILL );
|
|
}
|
|
|
|
|
|
static void GAL_Draw( drawType_t type )
|
|
{
|
|
if ( type == DT_DYNAMIC_LIGHT )
|
|
DrawDynamicLight();
|
|
else
|
|
DrawGeneric();
|
|
}
|
|
|
|
|
|
static qbool GL2_CreateShader( GLuint* shaderPtr, GLenum shaderType, const char* shaderSource )
|
|
{
|
|
GLuint shader = glCreateShader( shaderType );
|
|
glShaderSource( shader, 1, &shaderSource, NULL );
|
|
glCompileShader( shader );
|
|
|
|
GLint result = GL_FALSE;
|
|
glGetShaderiv( shader, GL_COMPILE_STATUS, &result );
|
|
if ( result == GL_TRUE ) {
|
|
*shaderPtr = shader;
|
|
return qtrue;
|
|
}
|
|
|
|
GLint logLength = 0;
|
|
glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &logLength );
|
|
|
|
static char log[4096]; // I've seen logs over 3 KB in size.
|
|
glGetShaderInfoLog( 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 = glCreateProgram();
|
|
glAttachShader( prog.p, prog.vs );
|
|
glAttachShader( prog.p, prog.fs );
|
|
glLinkProgram( 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 = vec4((base.rgb * vec3(diffuse) + vec3(spec)) * vec3(intens), base.a);\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 GL( call ) call; GL2_CheckError( #call, __FUNCTION__, __FILE__, __LINE__ )
|
|
|
|
|
|
static const char* GL2_GetErrorString( GLenum ec )
|
|
{
|
|
#define CASE( x ) case x: return #x
|
|
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 "?";
|
|
}
|
|
#undef CASE
|
|
}
|
|
|
|
|
|
static void GL2_CheckError( const char* call, const char* function, const char* file, int line )
|
|
{
|
|
const GLenum ec = glGetError();
|
|
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 qbool GL2_FBO_CreateSS( FrameBuffer& fb, qbool depthStencil )
|
|
{
|
|
while ( glGetError() != GL_NO_ERROR ) {} // clear the error queue
|
|
|
|
if ( depthStencil )
|
|
{
|
|
GL(glGenTextures( 1, &fb.depthStencil ));
|
|
GL(glBindTexture( GL_TEXTURE_2D, fb.depthStencil ));
|
|
GL(glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ));
|
|
GL(glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ));
|
|
GL(glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, glConfig.vidWidth, glConfig.vidHeight, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL ));
|
|
}
|
|
|
|
GLenum internalFormat, format, type;
|
|
GL_GetRenderTargetFormat( &internalFormat, &format, &type, r_rtColorFormat->integer );
|
|
GL(glGenTextures( 1, &fb.color ));
|
|
GL(glBindTexture( GL_TEXTURE_2D, fb.color ));
|
|
GL(glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ));
|
|
GL(glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ));
|
|
GL(glTexImage2D( GL_TEXTURE_2D, 0, internalFormat, glConfig.vidWidth, glConfig.vidHeight, 0, format, type, NULL ));
|
|
|
|
GL(glGenFramebuffers( 1, &fb.fbo ));
|
|
GL(glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo ));
|
|
GL(glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.color, 0 ));
|
|
if ( depthStencil )
|
|
GL(glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fb.depthStencil, 0 ));
|
|
|
|
const GLenum fboStatus = glCheckFramebufferStatus( GL_FRAMEBUFFER );
|
|
if ( fboStatus != GL_FRAMEBUFFER_COMPLETE )
|
|
{
|
|
ri.Error( ERR_FATAL, "Failed to create FBO (status 0x%X, error 0x%X)\n", (unsigned int)fboStatus, (unsigned int)glGetError() );
|
|
return qfalse;
|
|
}
|
|
|
|
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
|
fb.multiSampled = qfalse;
|
|
fb.hasDepthStencil = depthStencil;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static qbool GL2_FBO_CreateMS( int* sampleCount, FrameBuffer& fb )
|
|
{
|
|
while ( glGetError() != GL_NO_ERROR ) {} // clear the error queue
|
|
|
|
GL(glGenFramebuffers( 1, &fb.fbo ));
|
|
GL(glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo ));
|
|
|
|
GL(glGenRenderbuffers( 1, &fb.color ));
|
|
GL(glBindRenderbuffer( GL_RENDERBUFFER, fb.color ));
|
|
GL_CreateColorRenderBufferStorageMS( sampleCount );
|
|
GL(glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, fb.color ));
|
|
|
|
GL(glGenRenderbuffers( 1, &fb.depthStencil ));
|
|
GL(glBindRenderbuffer( GL_RENDERBUFFER, fb.depthStencil ));
|
|
GL(glRenderbufferStorageMultisample( GL_RENDERBUFFER, *sampleCount, GL_DEPTH24_STENCIL8, glConfig.vidWidth, glConfig.vidHeight ));
|
|
|
|
GL(glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, fb.color ));
|
|
GL(glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, fb.depthStencil ));
|
|
|
|
const GLenum fboStatus = glCheckFramebufferStatus( GL_FRAMEBUFFER );
|
|
if ( fboStatus != GL_FRAMEBUFFER_COMPLETE )
|
|
{
|
|
ri.Error( ERR_FATAL, "Failed to create FBO (status 0x%X, error 0x%X)\n", (unsigned int)fboStatus, (unsigned int)glGetError() );
|
|
return qfalse;
|
|
}
|
|
|
|
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
|
fb.multiSampled = qtrue;
|
|
fb.hasDepthStencil = qtrue;
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static qbool GL2_FBO_Init()
|
|
{
|
|
const qbool enable = r_msaa->integer >= 2;
|
|
frameBufferMultiSampling = enable;
|
|
int finalSampleCount = 1;
|
|
qbool result = qfalse;
|
|
|
|
if ( enable ) {
|
|
result =
|
|
GL2_FBO_CreateMS( &finalSampleCount, frameBufferMain ) &&
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[0], qfalse ) &&
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[1], qfalse );
|
|
} else {
|
|
result =
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[0], qtrue ) &&
|
|
GL2_FBO_CreateSS( frameBuffersPostProcess[1], qtrue );
|
|
}
|
|
|
|
if ( result )
|
|
ri.Printf( PRINT_ALL, "MSAA: %d samples requested, %d selected\n", r_msaa->integer, finalSampleCount );
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
static void GL2_FBO_Bind( const FrameBuffer& fb )
|
|
{
|
|
glBindFramebuffer( GL_FRAMEBUFFER, fb.fbo );
|
|
glReadBuffer( GL_COLOR_ATTACHMENT0 );
|
|
glDrawBuffer( 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 glClear calls
|
|
int blitMode = r_blitMode->integer;
|
|
if ( r_mode->integer != VIDEOMODE_UPSCALE )
|
|
blitMode = BLITMODE_STRETCHED;
|
|
|
|
if ( blitMode != BLITMODE_STRETCHED ) {
|
|
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
|
|
glClearColor( 0.0f, 0.0f, 0.0f, 0.0f );
|
|
glClear( GL_COLOR_BUFFER_BIT );
|
|
}
|
|
|
|
const FrameBuffer& fbo = frameBuffersPostProcess[frameBufferReadIndex];
|
|
glBindFramebuffer( GL_READ_FRAMEBUFFER, fbo.fbo );
|
|
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
|
|
glReadBuffer( GL_COLOR_ATTACHMENT0 );
|
|
glDrawBuffer( 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 ) {
|
|
glBlitFramebuffer( 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;
|
|
glBlitFramebuffer( 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;
|
|
glBlitFramebuffer( 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];
|
|
glBindFramebuffer( GL_READ_FRAMEBUFFER, r.fbo );
|
|
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, d.fbo );
|
|
glReadBuffer( GL_COLOR_ATTACHMENT0 );
|
|
glDrawBuffer( GL_COLOR_ATTACHMENT0 );
|
|
|
|
const int w = glConfig.vidWidth;
|
|
const int h = glConfig.vidHeight;
|
|
glBlitFramebuffer( 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;
|
|
glBegin( GL_QUADS );
|
|
glTexCoord2f( 0.0f, 0.0f );
|
|
glVertex2f( 0.0f, h );
|
|
glTexCoord2f( 0.0f, 1.0f );
|
|
glVertex2f( 0.0f, 0.0f );
|
|
glTexCoord2f( 1.0f, 1.0f );
|
|
glVertex2f( w, 0.0f );
|
|
glTexCoord2f( 1.0f, 0.0f );
|
|
glVertex2f( w, h );
|
|
glEnd();
|
|
}
|
|
|
|
|
|
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 );
|
|
glUniform1i( gammaProgAttribs.texture, 0 ); // we use texture unit 0
|
|
glUniform4f( gammaProgAttribs.gammaOverbright, gamma, gamma, gamma, brightness );
|
|
GL_SelectTexture( 0 );
|
|
glBindTexture( 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 );
|
|
glUniform1i( greyscaleProgAttribs.texture, 0 ); // we use texture unit 0
|
|
glUniform1f( greyscaleProgAttribs.greyscale, greyscale );
|
|
GL_SelectTexture( 0 );
|
|
glBindTexture( 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"
|
|
"";
|
|
|
|
|
|
static 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 = glGetUniformLocation( dynLightProg.p, "osEyePos" );
|
|
dynLightProgAttribs.osLightPos = glGetUniformLocation( dynLightProg.p, "osLightPos" );
|
|
dynLightProgAttribs.texture = glGetUniformLocation( dynLightProg.p, "texture" );
|
|
dynLightProgAttribs.lightColorRadius = glGetUniformLocation( dynLightProg.p, "lightColorRadius" );
|
|
|
|
if ( !GL2_CreateProgram( gammaProg, gammaVS, gammaFS ) ) {
|
|
ri.Printf( PRINT_ERROR, "Failed to compile gamma correction shaders\n" );
|
|
return qfalse;
|
|
}
|
|
gammaProgAttribs.texture = glGetUniformLocation( gammaProg.p, "texture" );
|
|
gammaProgAttribs.gammaOverbright = glGetUniformLocation( gammaProg.p, "gammaOverbright" );
|
|
|
|
greyscaleProgramValid = GL2_CreateProgram( greyscaleProg, greyscaleVS, greyscaleFS );
|
|
if ( greyscaleProgramValid ) {
|
|
greyscaleProgAttribs.texture = glGetUniformLocation( greyscaleProg.p, "texture" );
|
|
greyscaleProgAttribs.greyscale = glGetUniformLocation( greyscaleProg.p, "greyscale" );
|
|
} else {
|
|
ri.Printf( PRINT_ERROR, "Failed to compile greyscale shaders\n" );
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static void GL2_BeginFrame()
|
|
{
|
|
if ( frameBufferMultiSampling )
|
|
GL2_FBO_Bind( frameBufferMain );
|
|
else
|
|
GL2_FBO_Bind();
|
|
|
|
GL_Program();
|
|
}
|
|
|
|
|
|
static 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 );
|
|
|
|
glViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
glScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
|
|
GL2_PostProcessGamma();
|
|
GL2_PostProcessGreyscale();
|
|
|
|
// needed for later calls to GL_Bind because
|
|
// the above functions use glBindTexture directly
|
|
glState.texID[glState.currenttmu] = 0;
|
|
|
|
glViewport (0, 0, glInfo.winWidth, glInfo.winHeight );
|
|
glScissor( 0, 0, glInfo.winWidth, glInfo.winHeight );
|
|
|
|
GL2_FBO_BlitSSToBackBuffer();
|
|
}
|
|
|
|
|
|
static void ID_INLINE R_DrawElements( int numIndexes, const unsigned int* indexes )
|
|
{
|
|
glDrawElements( GL_TRIANGLES, numIndexes, GL_INDEX_TYPE, indexes );
|
|
}
|
|
|
|
|
|
static void UpdateAnimatedImage( image_t* image, int w, int h, const byte* data, qbool dirty )
|
|
{
|
|
GL_Bind( image );
|
|
if ( w != image->width || h != image->height ) {
|
|
// if the scratchImage isn't in the format we want, specify it as a new texture
|
|
image->width = w;
|
|
image->height = h;
|
|
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
|
|
} else if ( dirty ) {
|
|
// otherwise, just update it
|
|
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
}
|
|
}
|
|
|
|
|
|
static void R_BindAnimatedImage( const textureBundle_t* bundle )
|
|
{
|
|
GL_Bind( R_UpdateAndGetBundleImage( bundle, &UpdateAnimatedImage ) );
|
|
}
|
|
|
|
|
|
// blend a fog texture on top of everything else
|
|
static void RB_FogPass()
|
|
{
|
|
glEnableClientState( GL_COLOR_ARRAY );
|
|
glColorPointer( 4, GL_UNSIGNED_BYTE, 0, tess.svarsFog.colors );
|
|
|
|
glEnableClientState( GL_TEXTURE_COORD_ARRAY);
|
|
glTexCoordPointer( 2, GL_FLOAT, 0, tess.svarsFog.texcoordsptr );
|
|
|
|
GL_Bind( tr.fogImage );
|
|
|
|
GL_State( tess.fogStateBits );
|
|
|
|
R_DrawElements( tess.numIndexes, tess.indexes );
|
|
}
|
|
|
|
|
|
static void GL_Bind( const image_t* image )
|
|
{
|
|
GLuint texnum;
|
|
|
|
if ( !image ) {
|
|
ri.Printf( PRINT_WARNING, "GL_Bind: NULL image\n" );
|
|
texnum = (GLuint)tr.defaultImage->texnum;
|
|
} else {
|
|
texnum = (GLuint)image->texnum;
|
|
}
|
|
|
|
if ( r_nobind->integer && tr.defaultImage && !backEnd.projection2D ) {
|
|
texnum = (GLuint)tr.defaultImage->texnum;
|
|
}
|
|
|
|
if ( glState.texID[glState.currenttmu] != texnum ) {
|
|
glState.texID[glState.currenttmu] = texnum;
|
|
glBindTexture( GL_TEXTURE_2D, texnum );
|
|
}
|
|
}
|
|
|
|
|
|
static void GL_SelectTexture( int unit )
|
|
{
|
|
if ( glState.currenttmu == unit )
|
|
return;
|
|
|
|
if ( unit >= MAX_TMUS )
|
|
ri.Error( ERR_DROP, "GL_SelectTexture: unit = %i", unit );
|
|
|
|
glActiveTextureARB( GL_TEXTURE0_ARB + unit );
|
|
glClientActiveTextureARB( GL_TEXTURE0_ARB + unit );
|
|
|
|
glState.currenttmu = unit;
|
|
}
|
|
|
|
|
|
static void GL_Cull( int cullType ) {
|
|
if ( glState.faceCulling == cullType ) {
|
|
return;
|
|
}
|
|
|
|
glState.faceCulling = cullType;
|
|
|
|
if ( cullType == CT_TWO_SIDED )
|
|
{
|
|
glDisable( GL_CULL_FACE );
|
|
}
|
|
else
|
|
{
|
|
glEnable( GL_CULL_FACE );
|
|
|
|
if ( cullType == CT_BACK_SIDED )
|
|
{
|
|
if ( backEnd.viewParms.isMirror )
|
|
{
|
|
glCullFace( GL_FRONT );
|
|
}
|
|
else
|
|
{
|
|
glCullFace( GL_BACK );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( backEnd.viewParms.isMirror )
|
|
{
|
|
glCullFace( GL_BACK );
|
|
}
|
|
else
|
|
{
|
|
glCullFace( GL_FRONT );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static GLint GetTexEnv( texEnv_t texEnv )
|
|
{
|
|
switch ( texEnv )
|
|
{
|
|
case TE_MODULATE: return GL_MODULATE;
|
|
case TE_REPLACE: return GL_REPLACE;
|
|
case TE_DECAL: return GL_DECAL;
|
|
case TE_ADD: return GL_ADD;
|
|
default: return GL_MODULATE;
|
|
}
|
|
}
|
|
|
|
|
|
static void GL_TexEnv( GLint env )
|
|
{
|
|
if ( env == glState.texEnv[glState.currenttmu] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
glState.texEnv[glState.currenttmu] = env;
|
|
|
|
switch ( env )
|
|
{
|
|
case GL_MODULATE:
|
|
case GL_REPLACE:
|
|
case GL_DECAL:
|
|
case GL_ADD:
|
|
glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, env );
|
|
break;
|
|
default:
|
|
ri.Error( ERR_DROP, "GL_TexEnv: invalid env '%d' passed\n", env );
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void GL_State( unsigned long stateBits )
|
|
{
|
|
unsigned long diff = stateBits ^ glState.glStateBits;
|
|
|
|
if ( !diff )
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// check depthFunc bits
|
|
//
|
|
if ( diff & GLS_DEPTHFUNC_EQUAL )
|
|
{
|
|
if ( stateBits & GLS_DEPTHFUNC_EQUAL )
|
|
{
|
|
glDepthFunc( GL_EQUAL );
|
|
}
|
|
else
|
|
{
|
|
glDepthFunc( GL_LEQUAL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// check blend bits
|
|
//
|
|
if ( diff & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
|
|
{
|
|
GLenum srcFactor, dstFactor;
|
|
|
|
if ( stateBits & ( GLS_SRCBLEND_BITS | GLS_DSTBLEND_BITS ) )
|
|
{
|
|
switch ( stateBits & GLS_SRCBLEND_BITS )
|
|
{
|
|
case GLS_SRCBLEND_ZERO:
|
|
srcFactor = GL_ZERO;
|
|
break;
|
|
case GLS_SRCBLEND_ONE:
|
|
srcFactor = GL_ONE;
|
|
break;
|
|
case GLS_SRCBLEND_DST_COLOR:
|
|
srcFactor = GL_DST_COLOR;
|
|
break;
|
|
case GLS_SRCBLEND_ONE_MINUS_DST_COLOR:
|
|
srcFactor = GL_ONE_MINUS_DST_COLOR;
|
|
break;
|
|
case GLS_SRCBLEND_SRC_ALPHA:
|
|
srcFactor = GL_SRC_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA:
|
|
srcFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_DST_ALPHA:
|
|
srcFactor = GL_DST_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA:
|
|
srcFactor = GL_ONE_MINUS_DST_ALPHA;
|
|
break;
|
|
case GLS_SRCBLEND_ALPHA_SATURATE:
|
|
srcFactor = GL_SRC_ALPHA_SATURATE;
|
|
break;
|
|
default:
|
|
srcFactor = GL_ONE; // to get warning to shut up
|
|
ri.Error( ERR_DROP, "GL_State: invalid src blend state bits\n" );
|
|
break;
|
|
}
|
|
|
|
switch ( stateBits & GLS_DSTBLEND_BITS )
|
|
{
|
|
case GLS_DSTBLEND_ZERO:
|
|
dstFactor = GL_ZERO;
|
|
break;
|
|
case GLS_DSTBLEND_ONE:
|
|
dstFactor = GL_ONE;
|
|
break;
|
|
case GLS_DSTBLEND_SRC_COLOR:
|
|
dstFactor = GL_SRC_COLOR;
|
|
break;
|
|
case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR:
|
|
dstFactor = GL_ONE_MINUS_SRC_COLOR;
|
|
break;
|
|
case GLS_DSTBLEND_SRC_ALPHA:
|
|
dstFactor = GL_SRC_ALPHA;
|
|
break;
|
|
case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA:
|
|
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
|
break;
|
|
case GLS_DSTBLEND_DST_ALPHA:
|
|
dstFactor = GL_DST_ALPHA;
|
|
break;
|
|
case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA:
|
|
dstFactor = GL_ONE_MINUS_DST_ALPHA;
|
|
break;
|
|
default:
|
|
dstFactor = GL_ONE; // to get warning to shut up
|
|
ri.Error( ERR_DROP, "GL_State: invalid dst blend state bits\n" );
|
|
break;
|
|
}
|
|
|
|
glEnable( GL_BLEND );
|
|
glBlendFunc( srcFactor, dstFactor );
|
|
}
|
|
else
|
|
{
|
|
glDisable( GL_BLEND );
|
|
}
|
|
}
|
|
|
|
//
|
|
// check depthmask
|
|
//
|
|
if ( diff & GLS_DEPTHMASK_TRUE )
|
|
{
|
|
if ( stateBits & GLS_DEPTHMASK_TRUE )
|
|
{
|
|
glDepthMask( GL_TRUE );
|
|
}
|
|
else
|
|
{
|
|
glDepthMask( GL_FALSE );
|
|
}
|
|
}
|
|
|
|
//
|
|
// fill/line mode
|
|
//
|
|
if ( diff & GLS_POLYMODE_LINE )
|
|
{
|
|
if ( stateBits & GLS_POLYMODE_LINE )
|
|
{
|
|
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
|
|
}
|
|
else
|
|
{
|
|
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
|
|
}
|
|
}
|
|
|
|
//
|
|
// depthtest
|
|
//
|
|
if ( diff & GLS_DEPTHTEST_DISABLE )
|
|
{
|
|
if ( stateBits & GLS_DEPTHTEST_DISABLE )
|
|
{
|
|
glDisable( GL_DEPTH_TEST );
|
|
}
|
|
else
|
|
{
|
|
glEnable( GL_DEPTH_TEST );
|
|
}
|
|
}
|
|
|
|
//
|
|
// alpha test
|
|
//
|
|
if ( diff & GLS_ATEST_BITS )
|
|
{
|
|
switch ( stateBits & GLS_ATEST_BITS )
|
|
{
|
|
case 0:
|
|
glDisable( GL_ALPHA_TEST );
|
|
break;
|
|
case GLS_ATEST_GT_0:
|
|
glEnable( GL_ALPHA_TEST );
|
|
glAlphaFunc( GL_GREATER, 0.0f );
|
|
break;
|
|
case GLS_ATEST_LT_80:
|
|
glEnable( GL_ALPHA_TEST );
|
|
glAlphaFunc( GL_LESS, 0.5f );
|
|
break;
|
|
case GLS_ATEST_GE_80:
|
|
glEnable( GL_ALPHA_TEST );
|
|
glAlphaFunc( GL_GEQUAL, 0.5f );
|
|
break;
|
|
default:
|
|
assert( 0 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
glState.glStateBits = stateBits;
|
|
}
|
|
|
|
|
|
static void ApplyViewportAndScissor()
|
|
{
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadMatrixf( backEnd.viewParms.projectionMatrix );
|
|
glMatrixMode(GL_MODELVIEW);
|
|
|
|
// set the window clipping
|
|
glViewport( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
|
|
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
glScissor( backEnd.viewParms.viewportX, backEnd.viewParms.viewportY,
|
|
backEnd.viewParms.viewportWidth, backEnd.viewParms.viewportHeight );
|
|
}
|
|
|
|
|
|
// any mirrored or portaled views have already been drawn
|
|
// so prepare to actually render the visible surfaces for this view
|
|
|
|
static void GAL_Begin3D()
|
|
{
|
|
int clearBits = 0;
|
|
|
|
// sync with gl if needed
|
|
if ( r_finish->integer == 1 && !glState.finishCalled ) {
|
|
glFinish();
|
|
glState.finishCalled = qtrue;
|
|
}
|
|
if ( r_finish->integer == 0 ) {
|
|
glState.finishCalled = qtrue;
|
|
}
|
|
|
|
//
|
|
// set the modelview matrix for the viewer
|
|
//
|
|
ApplyViewportAndScissor();
|
|
|
|
// ensures that depth writes are enabled for the depth clear
|
|
GL_State( GLS_DEFAULT );
|
|
// clear relevant buffers
|
|
clearBits = GL_DEPTH_BUFFER_BIT;
|
|
|
|
if ( backEnd.refdef.rdflags & RDF_HYPERSPACE ) {
|
|
const float c = RB_HyperspaceColor();
|
|
clearBits |= GL_COLOR_BUFFER_BIT;
|
|
glClearColor( c, c, c, 1.0f );
|
|
} else if ( r_fastsky->integer && !( backEnd.refdef.rdflags & RDF_NOWORLDMODEL ) ) {
|
|
clearBits |= GL_COLOR_BUFFER_BIT;
|
|
// tr.sunLight could have colored fastsky properly for the last 9 years,
|
|
// ... if the code had actually been right >:( but, it's a bad idea to trust mappers anyway
|
|
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
|
|
}
|
|
glClear( clearBits );
|
|
|
|
glState.faceCulling = -1; // force face culling to set next time
|
|
|
|
// clip to the plane of the portal
|
|
if ( backEnd.viewParms.isPortal ) {
|
|
float plane[4];
|
|
double plane2[4];
|
|
|
|
plane[0] = backEnd.viewParms.portalPlane.normal[0];
|
|
plane[1] = backEnd.viewParms.portalPlane.normal[1];
|
|
plane[2] = backEnd.viewParms.portalPlane.normal[2];
|
|
plane[3] = backEnd.viewParms.portalPlane.dist;
|
|
|
|
plane2[0] = DotProduct (backEnd.viewParms.orient.axis[0], plane);
|
|
plane2[1] = DotProduct (backEnd.viewParms.orient.axis[1], plane);
|
|
plane2[2] = DotProduct (backEnd.viewParms.orient.axis[2], plane);
|
|
plane2[3] = DotProduct (plane, backEnd.viewParms.orient.origin) - plane[3];
|
|
|
|
glLoadMatrixf( s_flipMatrix );
|
|
glClipPlane (GL_CLIP_PLANE0, plane2);
|
|
glEnable (GL_CLIP_PLANE0);
|
|
} else {
|
|
glDisable (GL_CLIP_PLANE0);
|
|
}
|
|
}
|
|
|
|
|
|
static void GAL_BeginSkyAndClouds()
|
|
{
|
|
// r_showsky will let all the sky blocks be drawn in
|
|
// front of everything to allow developers to see how
|
|
// much sky is getting sucked in
|
|
if ( r_showsky->integer ) {
|
|
glDepthRange( 0.0, 0.0 );
|
|
} else {
|
|
glDepthRange( 1.0, 1.0 );
|
|
}
|
|
|
|
GL_State( 0 );
|
|
}
|
|
|
|
|
|
static void GAL_EndSkyAndClouds()
|
|
{
|
|
// back to normal depth range
|
|
glDepthRange( 0.0, 1.0 );
|
|
}
|
|
|
|
|
|
static GLint GL_TextureWrapMode( textureWrap_t w )
|
|
{
|
|
switch ( w ) {
|
|
case TW_REPEAT: return GL_REPEAT;
|
|
case TW_CLAMP_TO_EDGE: return GL_CLAMP_TO_EDGE;
|
|
default: return GL_REPEAT;
|
|
}
|
|
}
|
|
|
|
|
|
static GLint GL_TextureInternalFormat( textureFormat_t f )
|
|
{
|
|
switch ( f ) {
|
|
case TF_RGBA8:
|
|
default: return GL_RGBA8;
|
|
}
|
|
}
|
|
|
|
|
|
static GLenum GL_TextureFormat( textureFormat_t f )
|
|
{
|
|
switch ( f ) {
|
|
case TF_RGBA8:
|
|
default: return GL_RGBA;
|
|
}
|
|
}
|
|
|
|
|
|
static void GL_CheckErrors()
|
|
{
|
|
int err = glGetError();
|
|
if ((err == GL_NO_ERROR) || r_ignoreGLErrors->integer)
|
|
return;
|
|
|
|
char s[64];
|
|
switch( err ) {
|
|
case GL_INVALID_ENUM:
|
|
strcpy( s, "GL_INVALID_ENUM" );
|
|
break;
|
|
case GL_INVALID_VALUE:
|
|
strcpy( s, "GL_INVALID_VALUE" );
|
|
break;
|
|
case GL_INVALID_OPERATION:
|
|
strcpy( s, "GL_INVALID_OPERATION" );
|
|
break;
|
|
case GL_STACK_OVERFLOW:
|
|
strcpy( s, "GL_STACK_OVERFLOW" );
|
|
break;
|
|
case GL_STACK_UNDERFLOW:
|
|
strcpy( s, "GL_STACK_UNDERFLOW" );
|
|
break;
|
|
case GL_OUT_OF_MEMORY:
|
|
strcpy( s, "GL_OUT_OF_MEMORY" );
|
|
break;
|
|
default:
|
|
Com_sprintf( s, sizeof(s), "%i", err);
|
|
break;
|
|
}
|
|
|
|
ri.Error( ERR_FATAL, "GL_CheckErrors: %s", s );
|
|
}
|
|
|
|
|
|
static int GetMaxAnisotropy( image_t* image )
|
|
{
|
|
if ( (image->flags & IMG_NOAF) == 0 && glInfo.maxAnisotropy >= 2 && r_ext_max_anisotropy->integer >= 2 )
|
|
return min( r_ext_max_anisotropy->integer, glInfo.maxAnisotropy );
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
static void GAL_CreateTexture( image_t* image, int mipCount, int w, int h )
|
|
{
|
|
GLuint id;
|
|
glGenTextures( 1, &id );
|
|
image->texnum = (textureHandle_t)id;
|
|
|
|
GL_Bind( image );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, GetMaxAnisotropy(image) );
|
|
if ( image->flags & IMG_LMATLAS ) {
|
|
glTexImage2D( GL_TEXTURE_2D, 0, GL_TextureInternalFormat(image->format), w, h, 0, GL_TextureFormat(image->format), GL_UNSIGNED_BYTE, NULL );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
|
|
GL_CheckErrors();
|
|
return;
|
|
}
|
|
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_TextureWrapMode(image->wrapClampMode) );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_TextureWrapMode(image->wrapClampMode) );
|
|
|
|
if ( Q_stricmp( r_textureMode->string, "GL_NEAREST" ) == 0 &&
|
|
( image->flags & (IMG_LMATLAS | IMG_EXTLMATLAS | IMG_NOPICMIP )) == 0 ) {
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
|
|
} else if ( image->flags & IMG_NOMIPMAP ) {
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
} else {
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
}
|
|
|
|
GL_CheckErrors();
|
|
}
|
|
|
|
|
|
static void GAL_UpdateTexture( image_t* image, int mip, int x, int y, int w, int h, const void* data )
|
|
{
|
|
GL_Bind( image );
|
|
if ( image->flags & IMG_LMATLAS )
|
|
glTexSubImage2D( GL_TEXTURE_2D, (GLint)mip, x, y, w, h, GL_TextureFormat(image->format), GL_UNSIGNED_BYTE, data );
|
|
else
|
|
glTexImage2D( GL_TEXTURE_2D, (GLint)mip, GL_TextureInternalFormat(image->format), w, h, 0, GL_TextureFormat(image->format), GL_UNSIGNED_BYTE, data );
|
|
|
|
GL_CheckErrors();
|
|
}
|
|
|
|
|
|
static void GAL_UpdateScratch( image_t* image, int w, int h, const void* data, qbool dirty )
|
|
{
|
|
GL_Bind( image );
|
|
|
|
// if the scratchImage isn't in the format we want, specify it as a new texture
|
|
if ( w != image->width || h != image->height ) {
|
|
image->width = w;
|
|
image->height = h;
|
|
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP );
|
|
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP );
|
|
} else if ( dirty ) {
|
|
// otherwise, just subimage upload it so that drivers can tell we are going to be changing
|
|
// it and don't try and do a texture compression
|
|
glTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data );
|
|
}
|
|
}
|
|
|
|
|
|
static void GAL_CreateTextureEx( image_t* image, int mipCount, int mipOffset, int w, int h, const void* mip0 )
|
|
{
|
|
assert(0); // should never be called!
|
|
}
|
|
|
|
|
|
static void GL_SetDefaultState()
|
|
{
|
|
glClearDepth( 1.0f );
|
|
|
|
glCullFace( GL_FRONT );
|
|
|
|
glColor4f( 1,1,1,1 );
|
|
|
|
for ( int i = 0; i < MAX_TMUS; ++i ) {
|
|
GL_SelectTexture( i );
|
|
GL_TexEnv( GL_MODULATE );
|
|
glDisable( GL_TEXTURE_2D );
|
|
}
|
|
|
|
GL_SelectTexture( 0 );
|
|
glEnable( GL_TEXTURE_2D );
|
|
|
|
glShadeModel( GL_SMOOTH );
|
|
glDepthFunc( GL_LEQUAL );
|
|
|
|
glPolygonOffset( -1, -1 );
|
|
|
|
// the vertex array is always enabled, but the color and texture
|
|
// arrays are enabled and disabled around the compiled vertex array call
|
|
glEnableClientState( GL_VERTEX_ARRAY );
|
|
|
|
//
|
|
// make sure our GL state vector is set correctly
|
|
//
|
|
glState.glStateBits = GLS_DEPTHTEST_DISABLE | GLS_DEPTHMASK_TRUE;
|
|
|
|
glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
|
|
glDepthMask( GL_TRUE );
|
|
glDisable( GL_DEPTH_TEST );
|
|
glEnable( GL_SCISSOR_TEST );
|
|
glDisable( GL_CULL_FACE );
|
|
glDisable( GL_BLEND );
|
|
|
|
// Needed for some of our glReadPixels calls.
|
|
// The default alignment is 4.
|
|
// RGB with width 1366 -> not a multiple of 4!
|
|
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
|
|
}
|
|
|
|
|
|
static void InitGLConfig()
|
|
{
|
|
Q_strncpyz( glConfig.vendor_string, (const char*)glGetString( GL_VENDOR ), sizeof( glConfig.vendor_string ) );
|
|
Q_strncpyz( glConfig.renderer_string, (const char*)glGetString( GL_RENDERER ), sizeof( glConfig.renderer_string ) );
|
|
Q_strncpyz( glConfig.version_string, (const char*)glGetString( GL_VERSION ), sizeof( glConfig.version_string ) );
|
|
Q_strncpyz( glConfig.extensions_string, (const char*)glGetString( GL_EXTENSIONS ), sizeof( glConfig.extensions_string ) );
|
|
glGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.unused_maxTextureSize );
|
|
glConfig.unused_maxActiveTextures = 0;
|
|
glConfig.unused_driverType = 0; // ICD
|
|
glConfig.unused_hardwareType = 0; // generic
|
|
glConfig.unused_deviceSupportsGamma = qtrue;
|
|
glConfig.unused_textureCompression = 0; // no compression
|
|
glConfig.unused_textureEnvAddAvailable = qtrue;
|
|
glConfig.unused_displayFrequency = 0;
|
|
glConfig.unused_isFullscreen = !!r_fullscreen->integer;
|
|
glConfig.unused_stereoEnabled = qfalse;
|
|
glConfig.unused_smpActive = qfalse;
|
|
}
|
|
|
|
|
|
static void InitGLInfo()
|
|
{
|
|
glGetIntegerv( GL_MAX_TEXTURE_SIZE, &glInfo.maxTextureSize );
|
|
|
|
if ( strstr( glConfig.extensions_string, "GL_EXT_texture_filter_anisotropic" ) )
|
|
glGetIntegerv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &glInfo.maxAnisotropy );
|
|
else
|
|
glInfo.maxAnisotropy = 0;
|
|
|
|
glInfo.softSpriteSupport = qfalse;
|
|
glInfo.mipGenSupport = qfalse;
|
|
glInfo.alphaToCoverageSupport = qfalse;
|
|
}
|
|
|
|
|
|
static void InitExtensions()
|
|
{
|
|
GL2_Init();
|
|
}
|
|
|
|
|
|
static void GAL_ReadPixels( int x, int y, int w, int h, int alignment, colorSpace_t colorSpace, void* out )
|
|
{
|
|
const GLenum format = colorSpace == CS_BGR ? GL_BGR : GL_RGBA;
|
|
glPixelStorei( GL_PACK_ALIGNMENT, alignment );
|
|
glReadPixels( x, y, w, h, format, GL_UNSIGNED_BYTE, out );
|
|
glPixelStorei( GL_PACK_ALIGNMENT, 1 );
|
|
}
|
|
|
|
|
|
static void RB_DeleteTextures()
|
|
{
|
|
for ( int i = 0; i < tr.numImages; ++i )
|
|
glDeleteTextures( 1, (const GLuint*)&tr.images[i]->texnum );
|
|
|
|
tr.numImages = 0;
|
|
Com_Memset( tr.images, 0, sizeof( tr.images ) );
|
|
Com_Memset( glState.texID, 0, sizeof( glState.texID ) );
|
|
|
|
for ( int i = MAX_TMUS - 1; i >= 0; --i ) {
|
|
GL_SelectTexture( i );
|
|
glBindTexture( GL_TEXTURE_2D, 0 );
|
|
}
|
|
}
|
|
|
|
|
|
static void GAL_Begin2D()
|
|
{
|
|
// set 2D virtual screen size
|
|
glViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
glScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight );
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 );
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glLoadIdentity();
|
|
|
|
GL_State( GLS_DEFAULT_2D );
|
|
|
|
glDisable( GL_CULL_FACE );
|
|
glDisable( GL_CLIP_PLANE0 );
|
|
}
|
|
|
|
|
|
static void GAL_SetModelViewMatrix( const float* matrix )
|
|
{
|
|
glLoadMatrixf( matrix );
|
|
}
|
|
|
|
|
|
static void GAL_SetDepthRange( double zNear, double zFar )
|
|
{
|
|
glDepthRange( zNear, zFar );
|
|
}
|
|
|
|
|
|
static qbool GAL_Init()
|
|
{
|
|
if ( glConfig.vidWidth == 0 )
|
|
{
|
|
// the order of these calls can not be changed
|
|
Sys_V_Init( GAL_GL2 );
|
|
if ( !GLEW_VERSION_2_0 )
|
|
ri.Error( ERR_FATAL, "OpenGL 2.0 required!\n" );
|
|
if ( !GLEW_VERSION_3_0 && !GLEW_ARB_framebuffer_object )
|
|
ri.Error( ERR_FATAL, "Need at least OpenGL 3.0 or GL_ARB_framebuffer_object\n" );
|
|
InitGLConfig();
|
|
InitGLInfo();
|
|
InitExtensions();
|
|
|
|
// apply the current V-Sync option after the first rendered frame
|
|
r_swapInterval->modified = qtrue;
|
|
}
|
|
|
|
GL_SetDefaultState();
|
|
|
|
int err = glGetError();
|
|
if (err != GL_NO_ERROR)
|
|
ri.Printf( PRINT_ALL, "glGetError() = 0x%x\n", err );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
|
|
static void GAL_ShutDown( qbool fullShutDown )
|
|
{
|
|
RB_DeleteTextures();
|
|
memset( &glState, 0, sizeof( glState ) );
|
|
}
|
|
|
|
|
|
static void GAL_BeginFrame()
|
|
{
|
|
glState.finishCalled = qfalse;
|
|
|
|
if ( !r_ignoreGLErrors->integer ) {
|
|
int err;
|
|
if ( ( err = glGetError() ) != GL_NO_ERROR ) {
|
|
ri.Error( ERR_FATAL, "RE_BeginFrame() - glGetError() failed (0x%x)!\n", err );
|
|
}
|
|
}
|
|
|
|
GL2_BeginFrame();
|
|
|
|
if ( r_clear->integer )
|
|
glClearColor( 1.0f, 0.0f, 0.5f, 1.0f );
|
|
else
|
|
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
|
|
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
|
|
}
|
|
|
|
|
|
static void GAL_EndFrame()
|
|
{
|
|
if ( !backEnd.projection2D )
|
|
GAL_Begin2D();
|
|
|
|
GL2_EndFrame();
|
|
|
|
if ( !glState.finishCalled )
|
|
glFinish();
|
|
}
|
|
|
|
|
|
static void GAL_PrintInfo()
|
|
{
|
|
}
|
|
|
|
|
|
qbool GAL_GetGL2( graphicsAPILayer_t* rb )
|
|
{
|
|
rb->Init = &GAL_Init;
|
|
rb->ShutDown = &GAL_ShutDown;
|
|
rb->BeginSkyAndClouds = &GAL_BeginSkyAndClouds;
|
|
rb->EndSkyAndClouds = &GAL_EndSkyAndClouds;
|
|
rb->ReadPixels = &GAL_ReadPixels;
|
|
rb->BeginFrame = &GAL_BeginFrame;
|
|
rb->EndFrame = &GAL_EndFrame;
|
|
rb->CreateTexture = &GAL_CreateTexture;
|
|
rb->UpdateTexture = &GAL_UpdateTexture;
|
|
rb->UpdateScratch = &GAL_UpdateScratch;
|
|
rb->CreateTextureEx = &GAL_CreateTextureEx;
|
|
rb->Draw = &GAL_Draw;
|
|
rb->Begin2D = &GAL_Begin2D;
|
|
rb->Begin3D = &GAL_Begin3D;
|
|
rb->SetModelViewMatrix = &GAL_SetModelViewMatrix;
|
|
rb->SetDepthRange = &GAL_SetDepthRange;
|
|
rb->BeginDynamicLight = &GAL_BeginDynamicLight;
|
|
rb->PrintInfo = &GAL_PrintInfo;
|
|
|
|
return qtrue;
|
|
}
|