mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-14 16:30:36 +00:00
1629 lines
42 KiB
C++
1629 lines
42 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 );
|
|
|
|
|
|
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;
|
|
backEnd.pc3D[ RB_SHADER_CHANGES ]++;
|
|
}
|
|
}
|
|
|
|
static void GL_Program()
|
|
{
|
|
if ( progCurrent != 0 ) {
|
|
glUseProgram(0);
|
|
progCurrent = 0;
|
|
backEnd.pc3D[ RB_SHADER_CHANGES ]++;
|
|
}
|
|
}
|
|
|
|
|
|
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)
|
|
GLint opaqueIntensity; // 2f
|
|
};
|
|
|
|
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 );
|
|
|
|
glUniform2f( dynLightProgAttribs.opaqueIntensity, backEnd.dlOpaque ? 1.0f : 0.0f, backEnd.dlIntensity );
|
|
|
|
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 );
|
|
|
|
GL_State( backEnd.dlStateBits );
|
|
|
|
GL_SelectTexture( 0 );
|
|
R_BindAnimatedImage( &pStage->bundle );
|
|
|
|
glDrawElements( GL_TRIANGLES, tess.dlNumIndexes, GL_INDEX_TYPE, tess.dlIndexes );
|
|
backEnd.pc3D[ RB_DRAW_CALLS ]++;
|
|
|
|
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 );
|
|
backEnd.pc3D[ RB_DRAW_CALLS ]++;
|
|
|
|
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 );
|
|
backEnd.pc3D[ RB_DRAW_CALLS ]++;
|
|
}
|
|
|
|
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;\n" // w = 1 / (r^2)
|
|
"uniform vec2 opaqueIntensity;\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"
|
|
"float BezierEase(float t)\n"
|
|
"{\n"
|
|
" return t * t * (3.0 - 2.0 * t);\n"
|
|
"}\n"
|
|
"\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 = min(dot(L.xyz, L.xyz) * lightColorRadius.w, 1.0);"
|
|
" vec3 intens = lightColorRadius.rgb * BezierEase(1.0 - sqrt(intensFactor));\n"
|
|
// specular reflection term (N.H)
|
|
" float specFactor = min(abs(dot(nN, normalize(nL + nV))), 1.0);\n"
|
|
" float spec = pow(specFactor, 16.0) * 0.25;\n"
|
|
// Lambertian diffuse reflection term (N.L)
|
|
" float diffuse = min(abs(dot(nN, nL)), 1.0);\n"
|
|
" vec3 color = (base.rgb * vec3(diffuse) + vec3(spec)) * intens * opaqueIntensity.y;\n"
|
|
" float alpha = mix(opaqueIntensity.x, 1.0, base.a);\n"
|
|
" gl_FragColor = vec4(color.rgb * alpha, alpha);\n"
|
|
"}\n"
|
|
"";
|
|
|
|
|
|
struct FrameBuffer {
|
|
GLuint fbo;
|
|
GLuint color; // texture if SS, renderbuffer if MS
|
|
GLuint depthStencil; // texture if SS, renderbuffer if MS
|
|
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 void GL2_CreateColorRenderBufferStorageMS( int* samples )
|
|
{
|
|
GLenum internalFormat, format, type;
|
|
GL_GetRenderTargetFormat( &internalFormat, &format, &type, r_rtColorFormat->integer );
|
|
|
|
int sampleCount = r_msaa->integer;
|
|
while ( glGetError() != GL_NO_ERROR ) {} // clear the error queue
|
|
|
|
if ( GLEW_VERSION_4_2 || GLEW_ARB_internalformat_query )
|
|
{
|
|
GLint maxSampleCount = 0;
|
|
glGetInternalformativ( GL_RENDERBUFFER, internalFormat, GL_SAMPLES, 1, &maxSampleCount );
|
|
if ( glGetError() == GL_NO_ERROR )
|
|
sampleCount = min(sampleCount, (int)maxSampleCount);
|
|
}
|
|
|
|
GLenum errorCode = GL_NO_ERROR;
|
|
for ( ;; )
|
|
{
|
|
// @NOTE: when the sample count is invalid, the error code is GL_INVALID_OPERATION
|
|
glRenderbufferStorageMultisample( GL_RENDERBUFFER, sampleCount, internalFormat, glConfig.vidWidth, glConfig.vidHeight );
|
|
errorCode = glGetError();
|
|
if ( errorCode == GL_NO_ERROR || sampleCount == 0 )
|
|
break;
|
|
|
|
--sampleCount;
|
|
}
|
|
|
|
if ( errorCode != GL_NO_ERROR )
|
|
ri.Error( ERR_FATAL, "Failed to create multi-sampled render buffer storage (error 0x%X)\n", (unsigned int)errorCode );
|
|
|
|
*samples = sampleCount;
|
|
}
|
|
|
|
|
|
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 ));
|
|
GL2_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" );
|
|
dynLightProgAttribs.opaqueIntensity = glGetUniformLocation( dynLightProg.p, "opaqueIntensity" );
|
|
|
|
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 );
|
|
backEnd.pc3D[ RB_DRAW_CALLS ]++;
|
|
}
|
|
|
|
|
|
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 ( 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( double depth )
|
|
{
|
|
glDepthRange( depth, depth );
|
|
}
|
|
|
|
|
|
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 );
|
|
|
|
glDisable( GL_DEPTH_CLAMP );
|
|
}
|
|
|
|
|
|
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.depthFadeSupport = 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;
|
|
}
|