Paranoia2/cl_dll/render/gl_backend.cpp

1057 lines
26 KiB
C++

/*
gl_backend.cpp - renderer backend routines
Copyright (C) 2013 Uncle Mike
This program 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 3 of the License, or
(at your option) any later version.
This program 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.
*/
#include <stdarg.h>
#include "hud.h"
#include "cl_util.h"
#include "const.h"
#include "com_model.h"
#include <stringlib.h>
#include "r_studioint.h"
#include <pm_movevars.h>
#include <pm_defs.h>
#include "ref_params.h"
#include "pmtrace.h"
#include "event_api.h"
#include "gl_local.h"
#include "gl_studio.h"
#include "gl_sprite.h"
#include "gl_world.h"
#include "gl_grass.h"
#include "gl_shader.h"
#include "screenfade.h"
#include "shake.h"
#define SKY_FOG_FACTOR 16.0f // experimentally determined value (chislo s potolka)
/*
==============
R_Speeds_Printf
helper to print into r_speeds message
==============
*/
void R_Speeds_Printf( const char *msg, ... )
{
va_list argptr;
char text[2048];
va_start( argptr, msg );
Q_vsprintf( text, msg, argptr );
va_end( argptr );
Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg ));
}
/*
==============
GL_PrintStats
display renderer stats
==============
*/
void GL_PrintStats( int params )
{
GLint cur_avail_mem_kb = 0;
GLint total_mem_kb = 0;
if( r_speeds->value <= 0.0f || !FBitSet( params, RP_DRAW_WORLD ))
return;
msurface_t *surf = r_stats.debug_surface;
switch( (int)r_speeds->value )
{
case 1:
R_Speeds_Printf( "%3i solid faces\n%3i solid meshes\n", RI->frame.solid_faces.Count(), RI->frame.solid_meshes.Count() );
R_Speeds_Printf( "%3i trans entries\n%3i grass faces\n", RI->frame.trans_list.Count(), RI->frame.grass_list.Count() );
R_Speeds_Printf( "%3i mirror faces\n%3i tess verts\n", RI->frame.num_subview_faces, RI->frame.primverts.Count() );
break;
case 2:
R_Speeds_Printf( "DIP count %3i\nShader bind %3i\n", r_stats.num_flushes, r_stats.num_shader_binds );
R_Speeds_Printf( "Frame total tris %3i\n", r_stats.c_total_tris );
R_Speeds_Printf( "Total GLSL shaders %3i", num_glsl_programs - 1 );
break;
case 3:
Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i mirrors\n%3i shadow passes\n%3i screencopy\n%3i occluded",
r_stats.c_subview_passes, r_stats.c_shadow_passes, r_stats.c_screen_copy, r_stats.c_occlusion_culled );
break;
case 4:
if( glConfig.hardware_type == GLHW_NVIDIA )
{
pglGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &total_mem_kb );
pglGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &cur_avail_mem_kb );
}
R_Speeds_Printf( "TEX used memory %s\n", Q_memprint( RENDER_GET_PARM( PARM_TEX_MEMORY, 0 )));
R_Speeds_Printf( "VBO used memory %s\n", Q_memprint( tr.total_vbo_memory ));
R_Speeds_Printf( "GPU used memory %s\n", Q_memprint(( total_mem_kb - cur_avail_mem_kb ) * 1024 ));
break;
case 5:
// draw hierarchy map of recursion calls
R_Speeds_Printf( "total %3i subview passes\n", r_stats.c_subview_passes );
R_Speeds_Printf( "%s", r_depth_msg );
break;
case 6:
if( r_stats.debug_surface != NULL )
{
glsl_program_t *cur = surf->info->forwardScene[0].GetShader();
R_Speeds_Printf( "Surf: ^1%s^7\n", surf->texinfo->texture->name );
R_Speeds_Printf( "Shader: ^3#%i %s^7\n", surf->info->forwardScene[0].GetHandle(), cur->name );
R_Speeds_Printf( "List Options:\n" );
R_Speeds_Printf( "%s\n", GL_PretifyListOptions( cur->options, true ));
}
break;
case 7:
R_Speeds_Printf( "%3i world lights (from %d lights)\n", r_stats.c_worldlights, world->numworldlights );
break;
}
}
/*
==============
GL_ComputeScreenRays
==============
*/
void GL_ComputeScreenRays( void )
{
vec3_t axis[3];
float tx, ty;
int i;
// compute the world-space rays to the far plane corners
tx = tan( DEG2RAD( RI->view.fov_x * 0.5f ));
ty = tan( DEG2RAD( RI->view.fov_y * 0.5f ));
for( i = 0; i < 3; i++ )
{
axis[0][i] = RI->view.matrix[0][i];
axis[1][i] = RI->view.matrix[1][i] * tx;
axis[2][i] = RI->view.matrix[2][i] * ty;
// counter-clockwise order
tr.screen_normals[0][i] = axis[0][i] + axis[1][i] + axis[2][i]; // top left
tr.screen_normals[1][i] = axis[0][i] + axis[1][i] - axis[2][i]; // bottom left
tr.screen_normals[2][i] = axis[0][i] - axis[1][i] - axis[2][i]; // bottom right
tr.screen_normals[3][i] = axis[0][i] - axis[1][i] + axis[2][i]; // top right
}
}
/*
==============
GL_ComputeSunParams
==============
*/
void GL_ComputeSunParams( const Vector &skyVector )
{
float sunPos = -skyVector.z;
float RefractionFactor = ( 1.0f - sqrt( Q_max( sunPos, 0.0f )));
Vector SunColor = Vector( 1.0f, 1.0f, 1.0f ) - Vector( 0.0f, 0.5f, 1.0f ) * RefractionFactor;
// calculate ambient and diffuse light color
Vector DiffuseColor = Vector( 1.0f, 1.0f, 1.0f ) - Vector( 0.0f, 0.25f, 0.5f ) * RefractionFactor;
float AmbientIntensity = 0.0025f + 0.1875f * Q_min( 1.0f, Q_max( 0.0f, ( 0.375f + sunPos ) / 0.25f ));
float DiffuseIntensity = 0.75f * Q_min( 1.0f, Q_max( 0.0f, ( 0.03125f + sunPos ) / 0.0625f ));
float ambientFactor = RemapVal( tr.ambientFactor, AMBIENT_EPSILON, r_lighting_modulate->value, 0.0f, 1.0f );
tr.sun_ambient = AmbientIntensity * ambientFactor * 2.0f;
tr.sun_ambient = bound( 0.0f, tr.sun_ambient, tr.ambientFactor );
tr.sun_diffuse = DiffuseColor * DiffuseIntensity;
tr.sun_refract = RefractionFactor;
tr.sky_normal = skyVector;
}
/*
==============
GL_BackendStartFrame
==============
*/
bool GL_BackendStartFrame( ref_viewpass_t *rvp, int params )
{
bool allow_dynamic_sun = false;
static float cached_lighting = 0.0f;
static float shadowmap_size = 0.0f;
static int waterlevel_old;
r_speeds_msg[0] = r_depth_msg[0] = '\0';
tr.fCustomRendering = false;
tr.fGamePaused = RENDER_GET_PARM( PARAM_GAMEPAUSED, 0 );
memset( &r_stats, 0, sizeof( r_stats ));
tr.sunlight = NULL;
if( r_buildstats.total_buildtime > 0.0 && RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 ))
{
// display time to building some stuff: lighting, VBO, shaders etc
if( r_buildstats.compile_shader > 0.0 )
ALERT( at_aiconsole, "shader compiled in %g secs\n", r_buildstats.compile_shader );
if( r_buildstats.create_buffer_object > 0.0 )
ALERT( at_aiconsole, "creating VBO in %g secs\n", r_buildstats.create_buffer_object - r_buildstats.compile_shader );
if( r_buildstats.create_light_cache > 0.0 )
ALERT( at_aiconsole, "creating VL cache in %g secs\n", r_buildstats.create_light_cache );
if( r_buildstats.create_buffer_object > 0.0 || r_buildstats.create_light_cache > 0.0 )
ALERT( at_aiconsole, "total building time %g\n", r_buildstats.total_buildtime );
memset( &r_buildstats, 0, sizeof( r_buildstats ));
}
if( !g_fRenderInitialized )
return 0;
if( !FBitSet( params, RP_DRAW_WORLD ))
GL_Setup3D();
if( CVAR_TO_BOOL( r_finish ) && FBitSet( params, RP_DRAW_WORLD ))
pglFinish();
// setup light factors
tr.ambientFactor = bound( AMBIENT_EPSILON, r_lighting_ambient->value, r_lighting_modulate->value );
float cachedFactor = bound( tr.ambientFactor, r_lighting_modulate->value, 1.0f );
if( cachedFactor != tr.diffuseFactor )
{
tr.diffuseFactor = cachedFactor;
R_StudioClearLightCache();
}
if( cached_lighting != r_lighting_extended->value )
{
cached_lighting = r_lighting_extended->value;
R_StudioClearLightCache();
}
if( shadowmap_size != r_shadowmap_size->value )
{
shadowmap_size = r_shadowmap_size->value;
R_InitShadowTextures();
}
// FIXME: allow player overview for custom renderer
if( !FBitSet( params, RP_DRAW_WORLD ))
return 0;
// use engine renderer
if( cv_renderer->value == 0 )
{
glState.depthmin = -1.0f;
glState.depthmax = -1.0f;
glState.depthmask = -1;
return 0;
}
// keep world in actual state
GET_ENTITY( 0 )->curstate.messagenum = r_currentMessageNum;
if( gHUD.m_flLevelTime != -1.0f && CVAR_TO_BOOL( r_sun_allowed ))
{
Vector skyVector;
matrix4x4 a;
float time = gHUD.m_flLevelTime;
float timeAng = ((( time + 12.0f ) / 24.0f ) * M_PI * 2.0f );
float longitude = RAD2DEG( 0.5f * M_PI ) - 90.0f;
a.CreateRotate( RAD2DEG( timeAng ) - 180.0f, 1.0, 0.5f, 0.0f );
a.ConcatRotate( longitude, -0.5f, 1.0f, 0.0f );
skyVector = a.VectorRotate( Vector( 0.0f, 0.0f, 1.0f ));
gEngfuncs.Con_NPrintf( 7, "time %i:%02.f\n", (int)time, ( time - (int)time ) * 59.0f );
skyVector = skyVector.Normalize();
GL_ComputeSunParams( skyVector );
allow_dynamic_sun = true;
}
else
{
Vector skyVector;
skyVector.x = tr.movevars->skyvec_x;
skyVector.y = tr.movevars->skyvec_y;
skyVector.z = tr.movevars->skyvec_z;
skyVector = skyVector.Normalize();
GL_ComputeSunParams( skyVector );
// hidden test mode
if( r_sun_allowed->value == 2.0 )
allow_dynamic_sun = true;
}
tr.sky_ambient.x = tr.movevars->skycolor_r;
tr.sky_ambient.y = tr.movevars->skycolor_g;
tr.sky_ambient.z = tr.movevars->skycolor_b;
// NOTE: sunlight must be added first in list
if( FBitSet( world->features, WORLD_HAS_SKYBOX ) && allow_dynamic_sun )
{
tr.sunlight = CL_AllocDlight( SUNLIGHT_KEY ); // alloc sun source
R_SetupLightParams( tr.sunlight, g_vecZero, g_vecZero, 32768.0f, 90.0f, LIGHT_DIRECTIONAL );
R_SetupLightTexture( tr.sunlight, tr.whiteTexture );
tr.sunlight->die = GET_CLIENT_TIME();
tr.sunlight->color = tr.sun_diffuse;
tr.sun_light_enabled = true;
}
tr.gravity = tr.movevars->gravity;
tr.farclip = tr.movevars->zmax;
if( tr.farclip == 0.0f || worldmodel == NULL )
tr.farclip = 4096.0f;
tr.cached_vieworigin = rvp->vieworigin;
tr.cached_viewangles = rvp->viewangles;
tr.waterlevel = g_pViewParams->waterlevel;
if(( tr.waterlevel >= 3 ) != ( waterlevel_old >= 3 ))
{
waterlevel_old = tr.waterlevel;
tr.glsl_valid_sequence++; // now all uber-shaders are invalidate and possible need for recompile
tr.params_changed = true;
}
if( tr.waterlevel >= 3 )
{
int waterent = WATER_ENTITY( g_pViewParams->vieworg );
// FIXME: how to allow fog on a world water?
if( waterent > 0 && waterent < g_pViewParams->max_entities )
tr.waterentity = GET_ENTITY( waterent );
else tr.waterentity = NULL;
}
else tr.waterentity = NULL;
R_GrassSetupFrame();
// check for fog
if( tr.waterentity )
{
entity_state_t *state = &tr.waterentity->curstate;
if( state->rendercolor.r || state->rendercolor.g || state->rendercolor.b )
{
// enable global exponential color fog
tr.fogColor[0] = (state->rendercolor.r) / 255.0f;
tr.fogColor[1] = (state->rendercolor.g) / 255.0f;
tr.fogColor[2] = (state->rendercolor.b) / 255.0f;
tr.fogDensity = (state->renderamt) * 0.000025f;
tr.fogSkyDensity = tr.fogDensity * SKY_FOG_FACTOR;
tr.fogEnabled = true;
}
}
else if( tr.movevars->fog_settings != 0 )
{
// enable global exponential color fog
tr.fogColor[0] = (float)TEXTURE_TO_TEXGAMMA((tr.movevars->fog_settings & 0xFF000000) >> 24) / 255.0f;
tr.fogColor[1] = (float)TEXTURE_TO_TEXGAMMA((tr.movevars->fog_settings & 0xFF0000) >> 16) / 255.0f;
tr.fogColor[2] = (float)TEXTURE_TO_TEXGAMMA((tr.movevars->fog_settings & 0xFF00) >> 8) / 255.0f;
tr.fogDensity = (tr.movevars->fog_settings & 0xFF) * 0.000025f;
tr.fogSkyDensity = tr.fogDensity * SKY_FOG_FACTOR;
tr.fogEnabled = true;
}
else
{
tr.fogColor[0] = 0.0f;
tr.fogColor[1] = 0.0f;
tr.fogColor[2] = 0.0f;
tr.fogDensity = 0.0f;
tr.fogSkyDensity = 0.0f;
tr.fogEnabled = false;
}
// apply the underwater warp
if( tr.waterlevel >= 3 )
{
float f = sin( tr.time * 0.4f * ( M_PI * 1.7f ));
rvp->fov_x += f;
rvp->fov_y -= f;
}
// apply the blur effect
if( gHUD.m_flBlurAmount > 0.0f )
{
float f = sin( tr.time * 0.4 * ( M_PI * 1.7f )) * gHUD.m_flBlurAmount * 20.0f;
rvp->fov_x += f;
rvp->fov_y += f;
screenfade_t fade;
f = ( sin( tr.time * 0.5f * ( M_PI * 1.7f )) * 127 ) + 128;
fade.fadeFlags = FFADE_STAYOUT;
fade.fader = fade.fadeg = fade.fadeb = 0;
fade.fadeReset = tr.time + 0.1f;
fade.fadeEnd = tr.time + 0.1f;
fade.fadealpha = bound( 0, (byte)f, gHUD.m_flBlurAmount * 255 );
gEngfuncs.pfnSetScreenFade( &fade );
}
Mod_ResortFaces();
GL_LoadAndRebuildCubemaps( params );
tr.fCustomRendering = true;
r_stats.debug_surface = NULL;
if( r_speeds->value == 6.0f )
{
Vector vecSrc = RI->view.origin;
Vector vecEnd = vecSrc + GetVForward() * RI->view.farClip;
pmtrace_t trace;
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
gEngfuncs.pEventAPI->EV_PlayerTrace( (float *)&vecSrc, (float *)&vecEnd, PM_NORMAL, -1, &trace );
r_stats.debug_surface = gEngfuncs.pEventAPI->EV_TraceSurface( trace.ent, (float *)&vecSrc, (float *)&vecEnd );
}
// setup light animation tables
R_AnimateLight();
return 1;
}
/*
==============
GL_BackendEndFrame
==============
*/
void GL_BackendEndFrame( ref_viewpass_t *rvp, int params )
{
mstudiolight_t light;
tr.frametime = tr.saved_frametime;
// go into 2D mode (in case we draw PlayerSetup between two 2d calls)
if( !FBitSet( params, RP_DRAW_WORLD ))
GL_Setup2D();
if( tr.show_uniforms_peak )
{
ALERT( at_aiconsole, "peak used uniforms: %i\n", glConfig.peak_used_uniforms );
tr.show_uniforms_peak = false;
}
DrawLightProbes(); // 3D
DrawCubeMaps(); // 3D
DrawViewLeaf(); // 3D
DrawWireFrame(); // 3D
DrawTangentSpaces(); // 3D
DrawWirePoly( r_stats.debug_surface ); // 3D
DBG_DrawLightFrustum(); // 3D
R_PushRefState();
RI->params = params;
RI->view.fov_x = rvp->fov_x;
RI->view.fov_y = rvp->fov_y;
if( !CVAR_TO_BOOL( cv_deferred ))
R_DrawViewModel(); // 3D
RenderSunShafts(); // 2D
RenderDOF(); // 2D
RenderNerveGasBlur(); // 2D
RenderUnderwaterBlur(); // 2D
if( !CVAR_TO_BOOL( cv_deferred ))
R_DrawHeadShield(); // 3D
R_RenderDebugStudioList( true ); // 3D
RenderMonochrome(); // 2D
R_ShowLightMaps(); // 2D
if( g_iGunMode == 3 )
{
// used for lighting scope
R_LightVec( rvp->vieworigin, &light, true );
tr.ambientLight = light.diffuse;
}
GL_CleanupDrawState();
R_PopRefState();
// fill the r_speeds message
GL_PrintStats( params );
R_UnloadFarGrass();
tr.params_changed = false;
tr.realframecount++;
}
/*
===============
R_ScreenToWorld
transform point from 3D position to ortho
===============
*/
bool R_WorldToScreen( const Vector &point, Vector &screen )
{
matrix4x4 worldToScreen;
bool behind;
float w;
worldToScreen = RI->view.worldProjectionMatrix;
screen[0] = worldToScreen[0][0] * point[0] + worldToScreen[1][0] * point[1] + worldToScreen[2][0] * point[2] + worldToScreen[3][0];
screen[1] = worldToScreen[0][1] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[2][1] * point[2] + worldToScreen[3][1];
w = worldToScreen[0][3] * point[0] + worldToScreen[1][3] * point[1] + worldToScreen[2][3] * point[2] + worldToScreen[3][3];
screen[2] = 0.0f; // just so we have something valid here
if( w < 0.001f )
{
behind = true;
screen[0] *= 100000;
screen[1] *= 100000;
}
else
{
float invw = 1.0f / w;
behind = false;
screen[0] *= invw;
screen[1] *= invw;
}
return behind;
}
/*
===============
R_ScreenToWorld
transform point from ortho to 3D position
===============
*/
void R_ScreenToWorld( const Vector &screen, Vector &point )
{
matrix4x4 screenToWorld;
Vector temp;
float w;
screenToWorld = RI->view.worldProjectionMatrix.InvertFull();
temp[0] = 2.0f * (screen[0] - RI->view.port[0]) / RI->view.port[2] - 1;
temp[1] = -2.0f * (screen[1] - RI->view.port[1]) / RI->view.port[3] + 1;
temp[2] = 0.0f; // just so we have something valid here
point[0] = temp[0] * screenToWorld[0][0] + temp[1] * screenToWorld[0][1] + temp[2] * screenToWorld[0][2] + screenToWorld[0][3];
point[1] = temp[0] * screenToWorld[1][0] + temp[1] * screenToWorld[1][1] + temp[2] * screenToWorld[1][2] + screenToWorld[1][3];
point[2] = temp[0] * screenToWorld[2][0] + temp[1] * screenToWorld[2][1] + temp[2] * screenToWorld[2][2] + screenToWorld[2][3];
w = temp[0] * screenToWorld[3][0] + temp[1] * screenToWorld[3][1] + temp[2] * screenToWorld[3][2] + screenToWorld[3][3];
if( w ) point *= ( 1.0f / w );
}
/*
=============
R_GetSpriteTexture
=============
*/
int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame )
{
if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data )
return 0;
return R_GetSpriteFrame( m_pSpriteModel, frame )->gl_texturenum;
}
void R_RenderQuadPrimitive( CSolidEntry *entry )
{
if( entry->m_bDrawType != DRAWTYPE_QUAD )
return;
GL_CleanupDrawState();
// select properly rendermode
switch( entry->m_iRenderMode )
{
case kRenderTransAlpha:
GL_AlphaTest( GL_TRUE );
pglAlphaFunc( GL_GREATER, 0.0f );
case kRenderTransColor:
case kRenderTransTexture:
GL_Blend( GL_TRUE );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
GL_DepthMask( GL_FALSE );
break;
case kRenderGlow:
pglDisable( GL_DEPTH_TEST );
case kRenderTransAdd:
GL_Blend( GL_TRUE );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
GL_DepthMask( GL_FALSE );
break;
case kRenderNormal:
default:
GL_DepthMask( GL_TRUE );
GL_Blend( GL_FALSE );
break;
}
int r, g, b, a;
// draw the particle
GL_BindTexture( GL_TEXTURE0, entry->m_hTexture );
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
UnpackRGBA( r, g, b, a, entry->m_iColor );
pglColor4ub( r, g, b, a );
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, 1.0f );
pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+0] );
pglTexCoord2f( 0.0f, 0.0f );
pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+1] );
pglTexCoord2f( 1.0f, 0.0f );
pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+2] );
pglTexCoord2f( 1.0f, 1.0f );
pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+3] );
pglEnd();
if( entry->m_iRenderMode == kRenderGlow )
pglEnable( GL_DEPTH_TEST );
if( entry->m_iRenderMode == kRenderTransAlpha )
GL_AlphaTest( GL_FALSE );
}
int R_AllocFrameBuffer( int viewport[4] )
{
int i = tr.num_framebuffers;
if( i >= MAX_FRAMEBUFFERS )
{
ALERT( at_error, "R_AllocateFrameBuffer: FBO limit exceeded!\n" );
return -1; // disable
}
gl_fbo_t *fbo = &tr.frame_buffers[i];
tr.num_framebuffers++;
if( fbo->init )
{
ALERT( at_warning, "R_AllocFrameBuffer: buffer %i already initialized\n", i );
return i;
}
// create a depth-buffer
pglGenRenderbuffers( 1, &fbo->renderbuffer );
pglBindRenderbuffer( GL_RENDERBUFFER_EXT, fbo->renderbuffer );
pglRenderbufferStorage( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, viewport[2], viewport[3] );
pglBindRenderbuffer( GL_RENDERBUFFER_EXT, 0 );
// create frame-buffer
pglGenFramebuffers( 1, &fbo->framebuffer );
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, fbo->framebuffer );
pglFramebufferRenderbuffer( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->renderbuffer );
pglDrawBuffer( GL_COLOR_ATTACHMENT0_EXT );
pglReadBuffer( GL_COLOR_ATTACHMENT0_EXT );
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 );
fbo->init = true;
return i;
}
void R_FreeFrameBuffer( int buffer )
{
if( buffer < 0 || buffer >= MAX_FRAMEBUFFERS )
{
ALERT( at_error, "R_FreeFrameBuffer: invalid buffer enum %i\n", buffer );
return;
}
gl_fbo_t *fbo = &tr.frame_buffers[buffer];
pglDeleteRenderbuffers( 1, &fbo->renderbuffer );
pglDeleteFramebuffers( 1, &fbo->framebuffer );
memset( fbo, 0, sizeof( *fbo ));
}
void GL_BindFrameBuffer( int buffer, int texture )
{
gl_fbo_t *fbo = NULL;
if( buffer >= 0 && buffer < MAX_FRAMEBUFFERS )
fbo = &tr.frame_buffers[buffer];
if( !fbo )
{
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 );
glState.frameBuffer = buffer;
return;
}
// at this point FBO index is always positive
if((unsigned int)glState.frameBuffer != fbo->framebuffer )
{
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, fbo->framebuffer );
glState.frameBuffer = fbo->framebuffer;
}
if( fbo->texture != texture )
{
// change texture attachment
GLuint texnum = RENDER_GET_PARM( PARM_TEX_TEXNUM, texture );
pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texnum, 0 );
fbo->texture = texture;
}
}
/*
==============
GL_BindFBO
==============
*/
void GL_BindFBO( GLuint buffer )
{
if( !GL_Support( R_FRAMEBUFFER_OBJECT ))
return;
if( glState.frameBuffer == buffer )
return;
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, buffer );
glState.frameBuffer = buffer;
}
/*
==================
GL_BindDrawbuffer
==================
*/
void GL_BindDrawbuffer( gl_drawbuffer_t *drawbuffer )
{
if( !GL_Support( R_FRAMEBUFFER_OBJECT ))
return;
if( !drawbuffer )
{
if( !glState.frameBuffer )
return;
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 );
glState.frameBuffer = 0;
return;
}
if( glState.frameBuffer == drawbuffer->id )
return;
pglBindFramebuffer( GL_FRAMEBUFFER_EXT, drawbuffer->id );
glState.frameBuffer = drawbuffer->id;
}
/*
==============
GL_DepthRange
==============
*/
void GL_DepthRange( GLfloat depthmin, GLfloat depthmax )
{
if( glState.depthmin == depthmin && glState.depthmax == depthmax )
return;
pglDepthRange( depthmin, depthmax );
glState.depthmin = depthmin;
glState.depthmax = depthmax;
}
/*
==============
GL_DepthMask
==============
*/
void GL_DepthMask( GLint enable )
{
// it's won't work anyway
// if( glState.depthmask == enable )
// return;
glState.depthmask = enable;
pglDepthMask( enable );
}
/*
==============
GL_AlphaTest
==============
*/
void GL_AlphaTest( GLint enable )
{
if( pglIsEnabled( GL_ALPHA_TEST ) == enable )
return;
if( enable ) pglEnable( GL_ALPHA_TEST );
else pglDisable( GL_ALPHA_TEST );
}
/*
==============
GL_DepthTest
==============
*/
void GL_DepthTest( GLint enable )
{
if( pglIsEnabled( GL_DEPTH_TEST ) == enable )
return;
if( enable ) pglEnable( GL_DEPTH_TEST );
else pglDisable( GL_DEPTH_TEST );
}
/*
==============
GL_Blend
==============
*/
void GL_Blend( GLint enable )
{
if( pglIsEnabled( GL_BLEND ) == enable )
return;
if( enable ) pglEnable( GL_BLEND );
else pglDisable( GL_BLEND );
}
/*
==============
GL_CleanupAllTextureUnits
==============
*/
void GL_CleanupAllTextureUnits( void )
{
// force to cleanup all the units
GL_SelectTexture( glConfig.max_texture_units - 1 );
GL_CleanUpTextureUnits( 0 );
}
/*
==============
GL_CleanupDrawState
==============
*/
void GL_CleanupDrawState( void )
{
GL_CleanupAllTextureUnits();
pglBindVertexArray( GL_FALSE );
pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
GL_BindShader( NULL );
}
/*
==============
GL_CheckVertexArrayBinding
==============
*/
void GL_CheckVertexArrayBinding( void )
{
int current_vao = 0;
pglGetIntegerv( GL_VERTEX_ARRAY_BINDING, (int *)&current_vao );
if( !current_vao ) return;
ALERT( at_error, "detected active VAO %d while uploading vertex buffer!\n", current_vao );
pglBindVertexArray( GL_FALSE );
}
/*
==============
GL_DisableAllTexGens
==============
*/
void GL_DisableAllTexGens( void )
{
GL_TexGen( GL_S, 0 );
GL_TexGen( GL_T, 0 );
GL_TexGen( GL_R, 0 );
GL_TexGen( GL_Q, 0 );
}
/*
=================
GL_ClipPlane
=================
*/
void GL_ClipPlane( bool enable )
{
// if cliplane was not set - do nothing
if( !FBitSet( RI->params, RP_CLIPPLANE ))
return;
if( glConfig.hardware_type == GLHW_RADEON )
return; // ATI doesn't need clip planes
if( enable )
{
GLdouble clip[4];
mplane_t *p = &RI->clipPlane;
clip[0] = p->normal[0];
clip[1] = p->normal[1];
clip[2] = p->normal[2];
clip[3] = -p->dist;
pglClipPlane( GL_CLIP_PLANE0, clip );
pglEnable( GL_CLIP_PLANE0 );
}
else
{
pglDisable( GL_CLIP_PLANE0 );
}
}
/*
=================
GL_Cull
=================
*/
void GL_Cull( GLenum cull )
{
if( !cull )
{
pglDisable( GL_CULL_FACE );
glState.faceCull = 0;
return;
}
pglEnable( GL_CULL_FACE );
pglCullFace( cull );
glState.faceCull = cull;
}
/*
=================
GL_FrontFace
=================
*/
void GL_FrontFace( GLenum front )
{
pglFrontFace( front ? GL_CW : GL_CCW );
glState.frontFace = front;
}
/*
=================
GL_LoadTexMatrix
=================
*/
void GL_LoadTexMatrix( const matrix4x4 &source )
{
GLfloat dest[16];
source.CopyToArray( dest );
GL_LoadTextureMatrix( dest );
}
/*
=================
GL_LoadMatrix
=================
*/
void GL_LoadMatrix( const matrix4x4 &source )
{
GLfloat dest[16];
source.CopyToArray( dest );
pglLoadMatrixf( dest );
}
/*
=================
GL_Setup2D
=================
*/
void GL_Setup2D( void )
{
// set up full screen workspace
pglMatrixMode( GL_PROJECTION );
pglLoadIdentity();
pglOrtho( 0, glState.width, glState.height, 0, -99999, 99999 );
pglMatrixMode( GL_MODELVIEW );
pglLoadIdentity();
pglDisable( GL_DEPTH_TEST );
GL_AlphaTest( GL_FALSE );
GL_DepthMask( GL_FALSE );
GL_Blend( GL_FALSE );
GL_Cull( GL_FRONT );
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
pglViewport( 0, 0, glState.width, glState.height );
}
/*
=================
GL_Setup3D
=================
*/
void GL_Setup3D( void )
{
pglViewport( RI->glstate.viewport[0], RI->glstate.viewport[1], RI->glstate.viewport[2], RI->glstate.viewport[3] );
pglMatrixMode( GL_PROJECTION );
GL_LoadMatrix( RI->glstate.projectionMatrix );
pglMatrixMode( GL_MODELVIEW );
GL_LoadMatrix( RI->glstate.modelviewMatrix );
pglEnable( GL_DEPTH_TEST );
GL_DepthMask( GL_TRUE );
GL_Cull( GL_FRONT );
}
void CompressNormalizedVector( char outVec[3], const Vector &inVec )
{
const byte *bestFitTexture = GET_TEXTURE_DATA( tr.normalsFitting );
Vector vNorm = inVec.Normalize(); // should be normalized
if( bestFitTexture != NULL )
{
Vector uNorm = vNorm.Abs(); // get unsigned normal
float maxNAbs = VectorMax( uNorm ); // get the main axis for cubemap lookup
// get texture dimensions (probably always equal 512)
uint width = RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, tr.normalsFitting );
uint height = RENDER_GET_PARM( PARM_TEX_SRC_HEIGHT, tr.normalsFitting );
// get texture coordinates in a collapsed cubemap
float u = (uNorm.z < maxNAbs ? (uNorm.y < maxNAbs ? uNorm.y : uNorm.x) : uNorm.x);
float v = (uNorm.z < maxNAbs ? (uNorm.y < maxNAbs ? uNorm.z : uNorm.z) : uNorm.y);
vNorm /= maxNAbs; // fit normal into the edge of unit cube
if( u < v ) swap( v, u );
if( u != 0.0f ) v /= u;
uint x = uint( u * (float)width );
uint y = uint( v * (float)height );
// look-up fitting length and scale the normal to get the best fit
float fFittingScale = (float)bestFitTexture[(y * width + x)] * (1.0f / 255.0f);
// scale the normal to get the best fit
vNorm *= fFittingScale;
}
outVec[0] = FloatToChar( vNorm.x );
outVec[1] = FloatToChar( vNorm.y );
outVec[2] = FloatToChar( vNorm.z );
}