/* Copyright (C) 1997-2001 Id Software, Inc. 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 2 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ // tr_bloom.c: General post-processing shader stuff including bloom, leifx, and everything else // that's postprocessed. Maintained by leilei and Hitchiker #include "tr_local.h" static cvar_t *r_bloom; static cvar_t *r_bloom_sample_size; static cvar_t *r_bloom_fast_sample; static cvar_t *r_bloom_alpha; static cvar_t *r_bloom_darken; static cvar_t *r_bloom_intensity; static cvar_t *r_bloom_diamond_size; static cvar_t *r_bloom_cascade; static cvar_t *r_bloom_cascade_blur; static cvar_t *r_bloom_cascade_intensity; static cvar_t *r_bloom_cascade_alpha; static cvar_t *r_bloom_cascade_dry; static cvar_t *r_bloom_dry; extern cvar_t *r_overBrightBits; extern cvar_t *r_gamma; static cvar_t *r_bloom_reflection; // LEILEI static cvar_t *r_bloom_sky_only; // LEILEI extern float ScreenFrameCount; static struct { #ifdef GLSL_POSTPROCESSING struct { image_t *texture; int width, height; float readW, readH; } effect; struct { image_t *texture; int width, height; float readW, readH; } screen; struct { image_t *texture; int width, height; float readW, readH; } depth; struct { int width, height; } work; // leilei - motion blur struct { image_t *texture; int width, height; float readW, readH; } motion1; struct { image_t *texture; int width, height; float readW, readH; } motion2; struct { image_t *texture; int width, height; float readW, readH; } motion3; struct { image_t *texture; int width, height; float readW, readH; } motion4; struct { image_t *texture; int width, height; float readW, readH; } motion5; struct { image_t *texture; int width, height; float readW, readH; } mpass1; struct { image_t *texture; int width, height; float readW, readH; } mpass2; struct { image_t *texture; int width, height; float readW, readH; } mpass3; struct { image_t *texture; int width, height; float readW, readH; } mpass4; struct { image_t *texture; int width, height; float readW, readH; } tv; struct { int width, height; } tvwork; struct { image_t *texture; int width, height; float readW, readH; } tveffect; qboolean started; #endif } postproc; cvar_t *r_film; extern int force32upload; int leifxmode; int leifxpass; int fakeit = 0; int tvinterlace = 1; int tvinter= 1; extern int tvWidth; extern int tvHeight; extern float tvAspectW; // aspect correction extern int vresWidth; extern int vresHeight; /* ============================================================================== LIGHT BLOOMS ============================================================================== */ static float Diamond8x[8][8] = { { 0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f, }, { 0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f, }, { 0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f, }, { 0.1f, 0.3f, 0.6f, 0.9f, 0.9f, 0.6f, 0.3f, 0.1f, }, { 0.0f, 0.2f, 0.4f, 0.6f, 0.6f, 0.4f, 0.2f, 0.0f, }, { 0.0f, 0.0f, 0.2f, 0.3f, 0.3f, 0.2f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, 0.0f } }; static float Star8x[8][8] = { { 0.4f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.4f, }, { 0.1f, 0.6f, 0.2f, 0.0f, 0.0f, 0.2f, 0.6f, 0.1f, }, { 0.0f, 0.2f, 0.7f, 0.6f, 0.6f, 0.7f, 0.2f, 0.0f, }, { 0.0f, 0.0f, 0.6f, 0.9f, 0.9f, 0.6f, 0.0f, 0.0f, }, { 0.0f, 0.0f, 0.6f, 0.9f, 0.9f, 0.6f, 0.0f, 0.0f, }, { 0.0f, 0.2f, 0.7f, 0.6f, 0.6f, 0.7f, 0.2f, 0.0f, }, { 0.1f, 0.6f, 0.2f, 0.0f, 0.0f, 0.2f, 0.6f, 0.1f, }, { 0.4f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, 0.1f, 0.4f, } }; static float Diamond6x[6][6] = { { 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f, }, { 0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f, }, { 0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f, }, { 0.1f, 0.5f, 0.9f, 0.9f, 0.5f, 0.1f, }, { 0.0f, 0.3f, 0.5f, 0.5f, 0.3f, 0.0f, }, { 0.0f, 0.0f, 0.1f, 0.1f, 0.0f, 0.0f } }; static float Diamond4x[4][4] = { { 0.3f, 0.4f, 0.4f, 0.3f, }, { 0.4f, 0.9f, 0.9f, 0.4f, }, { 0.4f, 0.9f, 0.9f, 0.4f, }, { 0.3f, 0.4f, 0.4f, 0.3f } }; static struct { struct { image_t *texture; int width, height; float readW, readH; } effect; struct { image_t *texture; int width, height; float readW, readH; } effect2; struct { image_t *texture; int width, height; float readW, readH; } screen; struct { int width, height; } work; qboolean started; } bloom; static void ID_INLINE R_Bloom_Quad( int width, int height, float texX, float texY, float texWidth, float texHeight ) { int x = 0; int y = 0; x = 0; y += glConfig.vidHeight - height; width += x; height += y; texWidth += texX; texHeight += texY; qglBegin( GL_QUADS ); qglTexCoord2f( texX, texHeight ); qglVertex2f( x, y ); qglTexCoord2f( texX, texY ); qglVertex2f( x, height ); qglTexCoord2f( texWidth, texY ); qglVertex2f( width, height ); qglTexCoord2f( texWidth, texHeight ); qglVertex2f( width, y ); qglEnd (); } // LEILEI - Bloom Reflection static void ID_INLINE R_Bloom_Quad_Lens(float offsert, int width, int height, float texX, float texY, float texWidth, float texHeight) { int x = 0; int y = 0; x = 0; y += glConfig.vidHeight - height; width += x; height += y; offsert = offsert * 9; // bah texWidth -= texX; texHeight -= texY; qglBegin( GL_QUADS ); qglTexCoord2f( texX, texHeight ); qglVertex2f( width + offsert, height + offsert ); qglTexCoord2f( texX, texY ); qglVertex2f( width + offsert, y - offsert); qglTexCoord2f( texWidth, texY ); qglVertex2f( x - offsert, y - offsert); qglTexCoord2f( texWidth, texHeight ); qglVertex2f( x - offsert, height + offsert); qglEnd (); } // LEILEI - end /* ================= R_Bloom_InitTextures ================= */ static void R_Bloom_InitTextures( void ) { byte *data; // find closer power of 2 to screen size for (bloom.screen.width = 1; bloom.screen.width< glConfig.vidWidth; bloom.screen.width *= 2); for (bloom.screen.height = 1; bloom.screen.height < glConfig.vidHeight; bloom.screen.height *= 2); bloom.screen.readW = glConfig.vidWidth / (float)bloom.screen.width; bloom.screen.readH = glConfig.vidHeight / (float)bloom.screen.height; // find closer power of 2 to effect size bloom.work.width = r_bloom_sample_size->integer; bloom.work.height = bloom.work.width * ( glConfig.vidWidth / glConfig.vidHeight ); for (bloom.effect.width = 1; bloom.effect.width < bloom.work.width; bloom.effect.width *= 2); for (bloom.effect.height = 1; bloom.effect.height < bloom.work.height; bloom.effect.height *= 2); bloom.effect.readW = bloom.work.width / (float)bloom.effect.width; bloom.effect.readH = bloom.work.height / (float)bloom.effect.height; bloom.effect2.readW=bloom.effect.readW; bloom.effect2.readH=bloom.effect.readH; bloom.effect2.width=bloom.effect.width; bloom.effect2.height=bloom.effect.height; // disable blooms if we can't handle a texture of that size if( bloom.screen.width > glConfig.maxTextureSize || bloom.screen.height > glConfig.maxTextureSize || bloom.effect.width > glConfig.maxTextureSize || bloom.effect.height > glConfig.maxTextureSize || bloom.work.width > glConfig.vidWidth || bloom.work.height > glConfig.vidHeight ) { ri.Cvar_Set( "r_bloom", "0" ); Com_Printf( S_COLOR_YELLOW"WARNING: 'R_InitBloomTextures' too high resolution for light bloom, effect disabled\n" ); return; } // leilei - let's not do that bloom disabling anymore force32upload = 1; data = ri.Hunk_AllocateTempMemory( bloom.screen.width * bloom.screen.height * 4 ); Com_Memset( data, 0, bloom.screen.width * bloom.screen.height * 4 ); bloom.screen.texture = R_CreateImage( "***bloom screen texture***", data, bloom.screen.width, bloom.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); ri.Hunk_FreeTempMemory( data ); data = ri.Hunk_AllocateTempMemory( bloom.effect.width * bloom.effect.height * 4 ); Com_Memset( data, 0, bloom.effect.width * bloom.effect.height * 4 ); bloom.effect.texture = R_CreateImage( "***bloom effect texture***", data, bloom.effect.width, bloom.effect.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); bloom.effect2.texture = R_CreateImage( "***bloom effect texture 2***", data, bloom.effect.width, bloom.effect.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); ri.Hunk_FreeTempMemory( data ); bloom.started = qtrue; force32upload = 0; } /* ================= R_InitBloomTextures ================= */ void R_InitBloomTextures( void ) { if( !r_bloom->integer ) return; memset( &bloom, 0, sizeof( bloom )); R_Bloom_InitTextures (); } /* ================= R_Bloom_DrawEffect ================= */ static void R_Bloom_DrawEffect( void ) { float alpha=r_bloom_alpha->value; GL_Bind( bloom.effect.texture ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); if(r_bloom_cascade->integer) { alpha=r_bloom_cascade_alpha->value; } qglColor4f( alpha,alpha,alpha, 1.0f ); R_Bloom_Quad( glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); } /* ================= R_Bloom_LensEffect LEILEI's Silly hack ================= */ static void R_Bloom_LensEffect( void ) { GL_Bind( bloom.effect.texture ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE ); qglColor4f( 0.78f, 0.23f, 0.34f, 0.07f ); R_Bloom_Quad_Lens(16, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.78f, 0.39f, 0.21f, 0.07f ); R_Bloom_Quad_Lens(32, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.78f, 0.59f, 0.21f, 0.07f ); R_Bloom_Quad_Lens(48, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.71f, 0.75f, 0.21f, 0.07f ); R_Bloom_Quad_Lens(64, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.52f, 0.78f, 0.21f, 0.07f ); R_Bloom_Quad_Lens(80, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.32f, 0.78f, 0.21f, 0.07f ); R_Bloom_Quad_Lens(96, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.21f, 0.78f, 0.28f, 0.07f ); R_Bloom_Quad_Lens(112, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.21f, 0.78f, 0.47f, 0.07f ); R_Bloom_Quad_Lens(128, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.21f, 0.77f, 0.66f, 0.07f ); R_Bloom_Quad_Lens(144, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.21f, 0.67f, 0.78f, 0.07f ); R_Bloom_Quad_Lens(160, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.21f, 0.47f, 0.78f, 0.07f ); R_Bloom_Quad_Lens(176, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.21f, 0.28f, 0.78f, 0.07f ); R_Bloom_Quad_Lens(192, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.35f, 0.21f, 0.78f, 0.07f ); R_Bloom_Quad_Lens(208, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.53f, 0.21f, 0.78f, 0.07f ); R_Bloom_Quad_Lens(224, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.72f, 0.21f, 0.75f, 0.07f ); R_Bloom_Quad_Lens(240, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); qglColor4f( 0.78f, 0.21f, 0.59f, 0.07f ); R_Bloom_Quad_Lens(256, glConfig.vidWidth, glConfig.vidHeight, 0, 0, bloom.effect.readW, bloom.effect.readW ); } /* ================= R_Bloom_Cascaded ================= Tcpp: sorry for my poor English skill. */ static void R_Bloom_Cascaded( void ) { int scale; int oldWorkW, oldWorkH; int newWorkW, newWorkH; float bloomShiftX=r_bloom_cascade_blur->value/(float)bloom.effect.width; float bloomShiftY=r_bloom_cascade_blur->value/(float)bloom.effect.height; float intensity=r_bloom_cascade_intensity->value; float intensity2; qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); //Take the backup texture and downscale it GL_Bind( bloom.screen.texture ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); R_Bloom_Quad( bloom.work.width, bloom.work.height, 0, 0, bloom.screen.readW, bloom.screen.readH ); //Copy downscaled framebuffer into a texture GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); /* Copy the result to the effect texture */ GL_Bind( bloom.effect2.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); // do blurs.. scale=32; while(bloom.work.width>=1; while(bloom.work.height>=1; // prepare the first level. newWorkW=bloom.work.width/scale; newWorkH=bloom.work.height/scale; GL_Bind( bloom.effect2.texture ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); intensity2=intensity/(float)scale; qglColor4f( intensity2, intensity2, intensity2, 1.0 ); R_Bloom_Quad( newWorkW, newWorkH, 0, 0, bloom.effect2.readW, bloom.effect2.readH ); // go through levels. while(scale>1) { float oldScaleInv=1.f/(float)scale; scale>>=1; oldWorkH=newWorkH; oldWorkW=newWorkW; newWorkW=bloom.work.width/scale; newWorkH=bloom.work.height/scale; // get effect texture. GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, oldWorkW, oldWorkH); // maginfy the previous level. if(r_bloom_cascade_blur->value<.01f) { // don't blur. qglColor4f( 1.f, 1.f, 1.f, 1.0 ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); R_Bloom_Quad( newWorkW, newWorkH, 0, 0, bloom.effect.readW*oldScaleInv, bloom.effect.readH*oldScaleInv ); } else { // blur. qglColor4f( .25f, .25f, .25f, 1.0 ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); R_Bloom_Quad( newWorkW, newWorkH, -bloomShiftX, -bloomShiftY, bloom.effect.readW*oldScaleInv, bloom.effect.readH*oldScaleInv ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); R_Bloom_Quad( newWorkW, newWorkH, bloomShiftX, -bloomShiftY, bloom.effect.readW*oldScaleInv, bloom.effect.readH*oldScaleInv ); R_Bloom_Quad( newWorkW, newWorkH, -bloomShiftX, bloomShiftY, bloom.effect.readW*oldScaleInv, bloom.effect.readH*oldScaleInv ); R_Bloom_Quad( newWorkW, newWorkH, bloomShiftX, bloomShiftY, bloom.effect.readW*oldScaleInv, bloom.effect.readH*oldScaleInv ); } // add the input. intensity2=intensity/(float)scale; qglColor4f( intensity2, intensity2, intensity2, 1.0 ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); GL_Bind( bloom.effect2.texture ); R_Bloom_Quad( newWorkW, newWorkH, 0, 0, bloom.effect2.readW, bloom.effect2.readH ); } GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); } /* ================= R_Bloom_GeneratexDiamonds ================= */ static void R_Bloom_WarsowEffect( void ) { int i, j, k; float intensity, scale, *diamond; qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); //Take the backup texture and downscale it GL_Bind( bloom.screen.texture ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); R_Bloom_Quad( bloom.work.width, bloom.work.height, 0, 0, bloom.screen.readW, bloom.screen.readH ); //Copy downscaled framebuffer into a texture GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); // darkening passes with repeated filter if( r_bloom_darken->integer ) { int i; GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); for( i = 0; i < r_bloom_darken->integer; i++ ) { R_Bloom_Quad( bloom.work.width, bloom.work.height, 0, 0, bloom.effect.readW, bloom.effect.readH ); } qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); } /* Copy the result to the effect texture */ GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); // bluring passes, warsow uses a repeated semi blend on a selectable diamond grid qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE_MINUS_SRC_COLOR ); if( r_bloom_diamond_size->integer > 7 || r_bloom_diamond_size->integer <= 3 ) { if( r_bloom_diamond_size->integer != 8 ) ri.Cvar_Set( "r_bloom_diamond_size", "8" ); } else if( r_bloom_diamond_size->integer > 5 ) { if( r_bloom_diamond_size->integer != 6 ) ri.Cvar_Set( "r_bloom_diamond_size", "6" ); } else if( r_bloom_diamond_size->integer > 3 ) { if( r_bloom_diamond_size->integer != 4 ) ri.Cvar_Set( "r_bloom_diamond_size", "4" ); } switch( r_bloom_diamond_size->integer ) { case 4: k = 2; diamond = &Diamond4x[0][0]; scale = r_bloom_intensity->value * 0.8f; break; case 6: k = 3; diamond = &Diamond6x[0][0]; scale = r_bloom_intensity->value * 0.5f; break; case 9: k = 4; diamond = &Star8x[0][0]; scale = r_bloom_intensity->value * 0.3f; break; default: case 8: k = 4; diamond = &Diamond8x[0][0]; scale = r_bloom_intensity->value * 0.3f; break; } for( i = 0; i < r_bloom_diamond_size->integer; i++ ) { for( j = 0; j < r_bloom_diamond_size->integer; j++, diamond++ ) { float x, y; intensity = *diamond * scale; if( intensity < 0.01f ) continue; qglColor4f( intensity, intensity, intensity, 1.0 ); x = (i - k) * ( 2 / 640.0f ) * bloom.effect.readW; y = (j - k) * ( 2 / 480.0f ) * bloom.effect.readH; R_Bloom_Quad( bloom.work.width, bloom.work.height, x, y, bloom.effect.readW, bloom.effect.readH ); } } qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); } /* ================= R_Bloom_BackupScreen Backup the full original screen to a texture for downscaling and later restoration ================= */ static void R_Bloom_BackupScreen( void ) { GL_Bind( bloom.screen.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } /* ================= R_Bloom_RestoreScreen Restore the temporary framebuffer section we used with the backup texture ================= */ static void R_Bloom_RestoreScreen( void ) { float dry=r_bloom_dry->value; if(r_bloom_cascade->integer) dry=r_bloom_cascade_dry->value; GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( bloom.screen.texture ); qglColor4f( dry,dry,dry, 1 ); if(dry<.99f) { R_Bloom_Quad( bloom.screen.width, bloom.screen.height, 0, 0, 1.f, 1.f ); } else { R_Bloom_Quad( bloom.work.width, bloom.work.height, 0, 0, bloom.work.width / (float)bloom.screen.width, bloom.work.height / (float)bloom.screen.height ); } } /* ================= R_Bloom_RestoreScreen_Postprocessed Restore the temporary framebuffer section we used with the backup texture ================= */ extern int mpasses; static void ID_INLINE R_Bloom_QuadTV( int width, int height, float texX, float texY, float texWidth, float texHeight, int aa ) { float aspcenter = 0; float raa = r_retroAA->value; if (raa < 1) raa = 1; float xpix = 1.0f / width / (4 / raa); float ypix = 1.0f / height / (4 / raa); float xaa; float yaa; int x = 0; int y = 0; if (aa == 0) { xaa = 0; yaa = 0; } if (aa == 1) { xaa = -xpix; yaa = ypix; } if (aa == 2) { xaa = -xpix; yaa = -ypix; } if (aa == 3) { xaa = xpix; yaa = -ypix; } if (aa == 4) { xaa = xpix; yaa = ypix; } //y += tvHeight - height; width += x; height += y; texWidth += texX; texHeight += texY; if (tvAspectW != 1.0) { aspcenter = tvWidth * ((1.0f - tvAspectW) / 2); // leilei - also do a quad that is 100% black, hiding our actual rendered viewport // qglViewport (0, 0, tvWidth, tvHeight ); // qglScissor (0, 0, tvWidth, tvHeight ); qglBegin( GL_QUADS ); if (r_tvFilter->integer) { // bilinear filter qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); } else { qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); } qglColor4f( 0.0, 0.0, 0.0, 1 ); qglVertex2f(0,0 ); qglVertex2f(0,height); qglVertex2f(width,height); qglVertex2f(width,0); qglEnd (); qglColor4f( 1.0, 1.0, 1.0, 1 ); } if (!aa) { qglViewport(aspcenter, 0, (tvWidth * tvAspectW), tvHeight ); qglScissor(aspcenter, 0, (tvWidth * tvAspectW), tvHeight ); } qglBegin( GL_QUADS ); //GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); if (aa) qglColor4f( 0.25, 0.25, 0.25, 1 ); qglTexCoord2f( texX + xaa, texHeight + yaa); qglVertex2f( x, y ); qglTexCoord2f( texX + xaa, texY + yaa ); qglVertex2f( x, height ); qglTexCoord2f( texWidth + xaa, texY + yaa ); qglVertex2f( width, height ); qglTexCoord2f( texWidth + xaa, texHeight + yaa ); qglVertex2f( width, y ); qglEnd (); } static void R_Bloom_RestoreScreen_Postprocessed( void ) { #ifdef GLSL_POSTPROCESSING glslProgram_t *program = NULL; if (leifxmode) { if (leifxmode == 1) { if (vertexShaders) R_GLSL_UseProgram(tr.leiFXDitherProgram); program=tr.programs[tr.leiFXDitherProgram]; } if (leifxmode == 2) { if (vertexShaders) R_GLSL_UseProgram(tr.leiFXGammaProgram); program=tr.programs[tr.leiFXGammaProgram]; } if (leifxmode == 3) { if (vertexShaders) R_GLSL_UseProgram(tr.leiFXFilterProgram); program=tr.programs[tr.leiFXFilterProgram]; } if (leifxmode == 888) { if (vertexShaders) R_GLSL_UseProgram(tr.animeProgram); program=tr.programs[tr.animeProgram]; } if (leifxmode == 999) { if (vertexShaders) R_GLSL_UseProgram(tr.animeFilmProgram); program=tr.programs[tr.animeFilmProgram]; } if (leifxmode == 777) { if (vertexShaders) R_GLSL_UseProgram(tr.motionBlurProgram); program=tr.programs[tr.motionBlurProgram]; } if (leifxmode == 778) { if (vertexShaders) R_GLSL_UseProgram(tr.motionBlurProgram); program=tr.programs[tr.motionBlurProgram]; } if (leifxmode == 779) { if (vertexShaders) R_GLSL_UseProgram(tr.motionBlurPostProgram); program=tr.programs[tr.motionBlurPostProgram]; } if (leifxmode == 632) { if (vertexShaders) R_GLSL_UseProgram(tr.NTSCEncodeProgram); program=tr.programs[tr.NTSCEncodeProgram]; } if (leifxmode == 633) { if (vertexShaders) R_GLSL_UseProgram(tr.NTSCDecodeProgram); program=tr.programs[tr.NTSCDecodeProgram]; } if (leifxmode == 634) { if (vertexShaders) R_GLSL_UseProgram(tr.NTSCBleedProgram); program=tr.programs[tr.NTSCBleedProgram]; } if (leifxmode == 666) { if (vertexShaders) R_GLSL_UseProgram(tr.BrightnessProgram); program=tr.programs[tr.BrightnessProgram]; } if (leifxmode == 1236) { if (vertexShaders) R_GLSL_UseProgram(tr.CRTProgram); program=tr.programs[tr.CRTProgram]; } if (leifxmode == 1997) { if (vertexShaders) R_GLSL_UseProgram(tr.paletteProgram); program=tr.programs[tr.paletteProgram]; } } else { if (vertexShaders) R_GLSL_UseProgram(tr.postprocessingProgram); // Feed GLSL postprocess program program=tr.programs[tr.postprocessingProgram]; } if (!program) { //If leifsmode has been set to something invalid we have to bail out or we will dereference a null pointer Com_Printf( S_COLOR_YELLOW"WARNING: 'leifxmode' has an invalid value\n" ); return; } if (program->u_ScreenSizeX > -1) R_GLSL_SetUniform_u_ScreenSizeX(program, glConfig.vidWidth); if (program->u_ScreenSizeY > -1) R_GLSL_SetUniform_u_ScreenSizeY(program, glConfig.vidHeight); if (program->u_ScreenToNextPixelX > -1) R_GLSL_SetUniform_u_ScreenToNextPixelX(program, (float)1.0/(float)glConfig.vidWidth); if (program->u_ScreenToNextPixelY > -1) R_GLSL_SetUniform_u_ScreenToNextPixelY(program, (float)1.0/(float)glConfig.vidHeight); // leilei - for TV shaders if (program->u_ActualScreenSizeX > -1) R_GLSL_SetUniform_u_ActualScreenSizeX(program, tvWidth); if (program->u_ActualScreenSizeY > -1) R_GLSL_SetUniform_u_ActualScreenSizeY(program, tvHeight); //if (program->u_Time > -1) R_GLSL_SetUniform_Time(program, backEnd.refdef.time); if (program->u_Time > -1) R_GLSL_SetUniform_Time(program, ScreenFrameCount); // Brightness stuff if (program->u_CC_Brightness > -1) R_GLSL_SetUniform_u_CC_Brightness(program, 1.0); if (program->u_CC_Gamma > -1) R_GLSL_SetUniform_u_CC_Gamma(program, r_gamma->value); if (program->u_CC_Overbright > -1) R_GLSL_SetUniform_u_CC_Overbright(program, r_overBrightBits->value); if (program->u_CC_Saturation > -1) R_GLSL_SetUniform_u_CC_Saturation(program, 1.0); if (program->u_CC_Contrast > -1) R_GLSL_SetUniform_u_CC_Contrast(program, 1.0); // if (leifxmode == 3) { R_GLSL_SetUniform_u_CC_Brightness(program, leifxpass); } if (program->u_zFar > -1) R_GLSL_SetUniform_u_zFar(program, tr.viewParms.zFar); GL_SelectTexture(0); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.screen.texture ); GL_SelectTexture(7); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.depth.texture ); // motion blur crap if( r_motionblur->integer > 2) { if (program->u_mpasses > -1) R_GLSL_SetUniform_u_mpasses(program, mpasses); GL_SelectTexture(2); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.motion1.texture ); GL_SelectTexture(3); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.motion2.texture ); GL_SelectTexture(4); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.motion3.texture ); GL_SelectTexture(5); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.motion4.texture ); GL_SelectTexture(6); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.motion5.texture ); GL_SelectTexture(11); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.mpass1.texture ); GL_SelectTexture(12); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.mpass1.texture ); GL_SelectTexture(13); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.mpass1.texture ); GL_SelectTexture(14); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); GL_Bind( postproc.mpass1.texture ); } qglColor4f( 1, 1, 1, 1 ); // if (leifxmode == 778) // return; if (leifxmode == 1234) { { R_Bloom_QuadTV( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH, 0 ); } } else if (leifxmode == 1236) { { R_Bloom_QuadTV( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH, 0 ); } } else if (leifxmode == 1233) { R_Bloom_QuadTV( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH, 1 ); GL_SelectTexture(0); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); GL_Bind( postproc.screen.texture ); R_Bloom_QuadTV( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH, 2 ); R_Bloom_QuadTV( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH, 3 ); R_Bloom_QuadTV( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH, 4 ); } else { R_Bloom_Quad( glConfig.vidWidth, glConfig.vidHeight, 0, 0, postproc.screen.readW,postproc.screen.readH ); } if (vertexShaders) R_GLSL_UseProgram(0); GL_SelectTexture(0); #endif } /* ================= R_Bloom_DownsampleView Scale the copied screen back to the sample size used for subsequent passes ================= */ /*static void R_Bloom_DownsampleView( void ) { // TODO, Provide option to control the color strength here / // qglColor4f( r_bloom_darken->value, r_bloom_darken->value, r_bloom_darken->value, 1.0f ); qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); GL_Bind( bloom.screen.texture ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); //Downscale it R_Bloom_Quad( bloom.work.width, bloom.work.height, 0, 0, bloom.screen.readW, bloom.screen.readH ); #if 1 GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); // darkening passes if( r_bloom_darken->integer ) { int i; GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO ); for( i = 0; i < r_bloom_darken->integer; i++ ) { R_Bloom_Quad( bloom.work.width, bloom.work.height, 0, 0, bloom.effect.readW, bloom.effect.readH ); } qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); } #endif // Copy the result to the effect texture / GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); } static void R_Bloom_CreateEffect( void ) { int dir, x; int range; //First step will zero dst, rest will one add GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ZERO ); // GL_Bind( bloom.screen.texture ); GL_Bind( bloom.effect.texture ); range = 4; for (dir = 0;dir < 2;dir++) { // blend on at multiple vertical offsets to achieve a vertical blur // TODO: do offset blends using GLSL for (x = -range;x <= range;x++) { float xoffset, yoffset, r; if (!dir){ xoffset = 0; yoffset = x*1.5; } else { xoffset = x*1.5; yoffset = 0; } xoffset /= bloom.work.width; yoffset /= bloom.work.height; // this r value looks like a 'dot' particle, fading sharply to // black at the edges // (probably not realistic but looks good enough) //r = ((range*range+1)/((float)(x*x+1)))/(range*2+1); //r = (dir ? 1.0f : brighten)/(range*2+1); r = 2.0f /(range*2+1)*(1 - x*x/(float)(range*range)); // r *= r_bloom_darken->value; qglColor4f(r, r, r, 1); R_Bloom_Quad( bloom.work.width, bloom.work.height, xoffset, yoffset, bloom.effect.readW, bloom.effect.readH ); // bloom.screen.readW, bloom.screen.readH ); GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE | GLS_DSTBLEND_ONE ); } } GL_Bind( bloom.effect.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, bloom.work.width, bloom.work.height ); }*/ /* ================= R_BloomScreen ================= */ void R_BloomScreen( void ) { if( !r_bloom->integer ) return; if(r_bloom_sky_only->integer) return; if ( backEnd.doneBloom ) return; if ( !backEnd.doneSurfaces ) return; backEnd.doneBloom = qtrue; if( !bloom.started ) { R_Bloom_InitTextures(); if( !bloom.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); #if 0 // set up full screen workspace GL_TexEnv( GL_MODULATE ); qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); qglMatrixMode( GL_PROJECTION ); qglLoadIdentity (); qglOrtho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 ); qglMatrixMode( GL_MODELVIEW ); qglLoadIdentity (); GL_Cull( CT_TWO_SIDED ); #endif qglColor4f( 1, 1, 1, 1 ); //Backup the old screen in a texture R_Bloom_BackupScreen(); // create the bloom texture using one of a few methods if(r_bloom_cascade->integer) R_Bloom_Cascaded(); else R_Bloom_WarsowEffect (); // R_Bloom_CreateEffect(); // restore the screen-backup to the screen R_Bloom_RestoreScreen(); // Do the final pass using the bloom texture for the final effect R_Bloom_DrawEffect (); // LEILEI - Lens Bloom Hack // The concept of this is to inverse the coordinates on both X and Y, // then scale outward using the offset value applied, as well as the modulated color // applied to give a rainbow streak effect. Most effective with high bloom darkness // values. if(r_bloom_reflection->integer) R_Bloom_LensEffect (); } void R_BloomInit( void ) { memset( &bloom, 0, sizeof( bloom )); r_bloom = ri.Cvar_Get( "r_bloom", "0", CVAR_ARCHIVE ); r_bloom_alpha = ri.Cvar_Get( "r_bloom_alpha", "0.3", CVAR_ARCHIVE ); r_bloom_diamond_size = ri.Cvar_Get( "r_bloom_diamond_size", "8", CVAR_ARCHIVE ); r_bloom_intensity = ri.Cvar_Get( "r_bloom_intensity", "1.3", CVAR_ARCHIVE ); r_bloom_darken = ri.Cvar_Get( "r_bloom_darken", "4", CVAR_ARCHIVE ); r_bloom_sample_size = ri.Cvar_Get( "r_bloom_sample_size", "256", CVAR_ARCHIVE|CVAR_LATCH ); // Tcpp: I prefer 256 to 128 r_bloom_fast_sample = ri.Cvar_Get( "r_bloom_fast_sample", "0", CVAR_ARCHIVE|CVAR_LATCH ); r_bloom_cascade = ri.Cvar_Get( "r_bloom_cascade", "0", CVAR_ARCHIVE ); r_bloom_cascade_blur = ri.Cvar_Get( "r_bloom_cascade_blur", ".4", CVAR_ARCHIVE ); r_bloom_cascade_intensity= ri.Cvar_Get( "r_bloom_cascade_intensity", "20", CVAR_ARCHIVE ); r_bloom_cascade_alpha = ri.Cvar_Get( "r_bloom_cascade_alpha", "0.15", CVAR_ARCHIVE ); r_bloom_cascade_dry = ri.Cvar_Get( "r_bloom_cascade_dry", "0.8", CVAR_ARCHIVE ); r_bloom_dry = ri.Cvar_Get( "r_bloom_dry", "1", CVAR_ARCHIVE ); r_bloom_reflection = ri.Cvar_Get( "r_bloom_reflection", "0", CVAR_ARCHIVE ); r_bloom_sky_only = ri.Cvar_Get( "r_bloom_sky_only", "0", CVAR_ARCHIVE ); } // ================================================================= // GLSL POST PROCESSING // ================================================================= /* ================= R_Postprocess_InitTextures ================= */ static void R_Postprocess_InitTextures( void ) { #ifdef GLSL_POSTPROCESSING byte *data; int intdiv = 1; force32upload = 1; // find closer power of 2 to screen size for (postproc.screen.width = 1; postproc.screen.width< glConfig.vidWidth; postproc.screen.width *= 2) {} // if (r_tvMode->integer > 1) // interlaced // for (postproc.screen.height = 1;postproc.screen.height < vidinted;postproc.screen.height *= 2); //else for (postproc.screen.height = 1; postproc.screen.height < glConfig.vidHeight; postproc.screen.height *= 2) {} // if (r_tvMode->integer > 1) // intdiv = 2; postproc.screen.readW = glConfig.vidWidth / (float)postproc.screen.width; postproc.screen.readH = glConfig.vidHeight / (float)postproc.screen.height; // find closer power of 2 to effect size postproc.work.width = r_bloom_sample_size->integer; postproc.work.height = postproc.work.width * ( glConfig.vidWidth / glConfig.vidHeight ); for (postproc.effect.width = 1; postproc.effect.width < postproc.work.width; postproc.effect.width *= 2) {} for (postproc.effect.height = 1; postproc.effect.height < postproc.work.height; postproc.effect.height *= 2) {} postproc.effect.readW = postproc.work.width / (float)postproc.effect.width; postproc.effect.readH = postproc.work.height / (float)postproc.effect.height; // postproc.screen.readH /= intdiv; // interlacey // disable blooms if we can't handle a texture of that size if( postproc.screen.width > glConfig.maxTextureSize || postproc.screen.height > glConfig.maxTextureSize ) { ri.Cvar_Set( "r_postprocess", "none" ); Com_Printf( S_COLOR_YELLOW"WARNING: 'R_InitPostprocessTextures' too high resolution for postprocessing, effect disabled\n" ); postprocess=0; return; } force32upload = 1; data = ri.Hunk_AllocateTempMemory( postproc.screen.width * postproc.screen.height * 4 ); Com_Memset( data, 0, postproc.screen.width * postproc.screen.height * 4 ); postproc.screen.texture = R_CreateImage( "***postproc screen texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); ri.Hunk_FreeTempMemory( data ); // leilei - tv output texture if (r_tvMode->integer > -1) { // find closer power of 2 to screen size for (postproc.tv.width = 1; postproc.tv.width< tvWidth; postproc.tv.width *= 2); for (postproc.tv.height = 1; postproc.tv.height < tvHeight; postproc.tv.height *= 2); //postproc.tv.height /= intdiv; // interlacey postproc.tv.readW = tvWidth / (float)postproc.tv.width; postproc.tv.readH = tvHeight / (float)postproc.tv.height; // find closer power of 2 to effect size postproc.tvwork.width = r_bloom_sample_size->integer; postproc.tvwork.height = postproc.tvwork.width * ( tvWidth / tvHeight ); // postproc.tvwork.height /= intdiv; // interlacey for (postproc.tveffect.width = 1; postproc.tveffect.width < postproc.tvwork.width; postproc.tveffect.width *= 2) {} if (intdiv > 1) { for (postproc.tveffect.height = 1; (postproc.tveffect.height/2) < postproc.tvwork.height; postproc.tveffect.height *= 2) {} } else { for (postproc.tveffect.height = 1; postproc.tveffect.height < postproc.tvwork.height; postproc.tveffect.height *= 2) {} } postproc.tveffect.readW = postproc.tvwork.width / (float)postproc.tveffect.width; postproc.tveffect.readH = postproc.tvwork.height / (float)postproc.tveffect.height; data = ri.Hunk_AllocateTempMemory( tvWidth * tvHeight * 4 ); Com_Memset( data, 0, tvWidth * tvHeight * 4 ); postproc.tv.texture = R_CreateImage( "***tv output screen texture***", data, tvWidth, tvHeight, qfalse, qfalse, GL_CLAMP_TO_EDGE ); ri.Hunk_FreeTempMemory( data ); } // leilei - motion blur textures! if (r_motionblur->integer > 2) { data = ri.Hunk_AllocateTempMemory( postproc.screen.width * postproc.screen.height * 4 ); Com_Memset( data, 0, postproc.screen.width * postproc.screen.height * 4 ); postproc.motion1.texture = R_CreateImage( "***motionblur1 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.motion2.texture = R_CreateImage( "***motionblur2 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.motion3.texture = R_CreateImage( "***motionblur3 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.motion4.texture = R_CreateImage( "***motionblur4 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.motion5.texture = R_CreateImage( "***motionblur5 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.mpass1.texture = R_CreateImage( "***motionaccum1 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.mpass2.texture = R_CreateImage( "***motionaccum1 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.mpass3.texture = R_CreateImage( "***motionaccum1 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); postproc.mpass4.texture = R_CreateImage( "***motionaccum1 texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); ri.Hunk_FreeTempMemory( data ); } // GLSL Depth Buffer data = ri.Hunk_AllocateTempMemory( postproc.screen.width * postproc.screen.height *4); Com_Memset( data, 0, postproc.screen.width * postproc.screen.height * 4 ); depthimage=1; postproc.depth.texture = R_CreateImage( "***depthbuffer texture***", data, postproc.screen.width, postproc.screen.height, qfalse, qfalse, GL_CLAMP_TO_EDGE ); depthimage=0; ri.Hunk_FreeTempMemory( data ); postproc.started = qtrue; force32upload = 0; #endif } /* ================= R_InitPostprocessTextures ================= */ void R_InitPostprocessTextures( void ) { #ifdef GLSL_POSTPROCESSING if( !postprocess ) return; memset( &postproc, 0, sizeof( postproc )); R_Postprocess_InitTextures (); #endif } /* ================= R_Postprocess_BackupScreen Backup the full original screen to a texture for downscaling and later restoration ================= */ static void R_Postprocess_BackupScreen( void ) { #ifdef GLSL_POSTPROCESSING GL_Bind( postproc.screen.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); GL_Bind( postproc.depth.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); #endif } /* ================= R_PostprocessScreen ================= */ void R_PostprocessScreen( void ) { #ifdef GLSL_POSTPROCESSING if( !postprocess ) return; if ( backEnd.donepostproc ) return; if ( !backEnd.doneSurfaces ) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this backEnd.donepostproc = qtrue; if( !postproc.started ) { R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); qglColor4f( 1, 1, 1, 1 ); //Backup the old screen in a texture R_Postprocess_BackupScreen(); //Redraw texture using GLSL program R_Bloom_RestoreScreen_Postprocessed(); #endif } void R_PostprocessingInit(void) { #ifdef GLSL_POSTPROCESSING memset( &postproc, 0, sizeof( postproc )); #endif } // ================================================================= // LEIFX FILTER EFFECTS // // Some require external GLSL scripts (placed in baseoa/glsl) // Some don't but are unstable on non-GL2 hardware anyway. // // ================================================================= static void ID_INLINE R_LeiFX_Pointless_Quad( int width, int height, float texX, float texY, float texWidth, float texHeight ) { int x = 0; int y = 0; x = 0; y += glConfig.vidHeight - height; width += x; height += y; texWidth += texX; texHeight += texY; qglBegin( GL_QUADS ); qglTexCoord2f( texX, texHeight ); qglVertex2f( x, y ); qglTexCoord2f( texX, texY ); qglVertex2f( x, height ); qglTexCoord2f( texWidth, texY ); qglVertex2f( width, height ); qglTexCoord2f( texWidth, texHeight ); qglVertex2f( width, y ); qglEnd (); } void R_LeiFX_Stupid_Hack (void) { GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_SRC_COLOR); GL_Bind( tr.whiteImage ); GL_Cull( CT_TWO_SIDED ); qglColor4f( 1, 1, 1, 1 ); R_LeiFX_Pointless_Quad( 0, 0, 0, 0, 1, 1 ); } void R_LeiFXPostprocessDitherScreen( void ) { #ifdef GLSL_POSTPROCESSING if( !r_leifx->integer) return; if ( backEnd.doneleifx) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this // if ( !backEnd.doneSurfaces ) // return; // backEnd.doneleifx = qtrue; if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); // postprocess = 1; leifxmode = 1; // The stupidest hack in america R_LeiFX_Stupid_Hack(); if (r_leifx->integer > 1) { leifxmode = 1; // reduct and dither - 1 pass R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); } force32upload = 0; #endif } void R_LeiFXPostprocessFilterScreen( void ) { #ifdef GLSL_POSTPROCESSING if( !r_leifx->integer) return; if ( backEnd.doneleifx) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this // if ( !backEnd.doneSurfaces ) // return; if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) { RB_SetGL2D(); } force32upload = 1; // postprocess = 1; /* if (r_leifx->integer == 3){ leifxmode = 2; // gamma - 1 pass // The stupidest hack in america R_LeiFX_Stupid_Hack(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); } */ // Gamma disabled because r_alternateBrightness 2 makes it redundant now. if (r_leifx->integer > 3) { leifxmode = 3; // filter - 4 pass // The stupidest hack in america R_LeiFX_Stupid_Hack(); leifxpass = 0; R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); leifxpass = 1; R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); leifxpass = 2; R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); leifxpass = 3; R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); } backEnd.doneleifx = qtrue; force32upload = 0; #endif } // ================================================================= // NTSC EFFECTS // // Thanks to the libretro team for their gpl ntsc shader code // // ================================================================= void R_NTSCScreen( void ) { #ifdef GLSL_POSTPROCESSING if( !r_ntsc->integer) return; if ( backEnd.donentsc) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this // if ( !backEnd.doneSurfaces ) // return; if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) { RB_SetGL2D(); } force32upload = 1; int ntsc_bleed = 0; int ntsc_encode = 0; int ntsc_decode = 0; // TODO: Switch it up if (r_ntsc->integer == 1) { ntsc_bleed = 1; } else if (r_ntsc->integer == 2) { ntsc_bleed = 1; ntsc_encode = 1; ntsc_decode = 1; } else if (r_ntsc->integer == 3) { ntsc_bleed = 0; ntsc_encode = 1; ntsc_decode = 1; } else if (r_ntsc->integer == 4) { ntsc_bleed = 1; ntsc_encode = 1; ntsc_decode = 1; } else if (r_ntsc->integer > 6) { ntsc_bleed = 666; ntsc_encode = 0; ntsc_decode = 0; } else { ntsc_bleed = 0; ntsc_encode = 1; ntsc_decode = 0; } // qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); // qglTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); R_LeiFX_Stupid_Hack(); if (ntsc_encode) { leifxmode = 632; // Encode to composite R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); } if (ntsc_decode) { leifxmode = 633; // Decode to RGB R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); } if (ntsc_bleed) { leifxmode = 634; // Encode to YUV and decode to RGB R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); } if (ntsc_bleed == 666) { leifxmode = 634; // Encode to YUV and decode to RGB EXCESSIVELY int passasses = r_ntsc->integer; int j; for (j=0; jinteger) return; if ( backEnd.doneAltBrightness) return; if (r_alternateBrightness->integer == 1) mode = 1; // force use blend #ifdef GLSL_POSTPROCESSING if (r_alternateBrightness->integer == 2) { // Automatically determine from capabilities if ( vertexShaders ) { mode = 2; } else { mode = 1; } } // the modern pixel shader way if (mode == 2) { if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); force32upload = 1; leifxmode = 666; // anime effect - outlines, desat, bloom and other crap to go with it R_LeiFX_Stupid_Hack(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); backEnd.doneAltBrightness = qtrue; force32upload = 0; } // the fixed function quad blending way else if (mode == 1) #endif { int eh, ah; if ((r_overBrightBits->integer)) { ah = r_overBrightBits->integer; if (ah < 1) { ah = 1; } if (ah > 2) { ah = 2; // clamp so it never looks stupid } // Blend method // do a loop for every overbright bit enabled for (eh=0; ehinteger ) return; if ( backEnd.doneFilm ) return; if ( !backEnd.projection2D ) RB_SetGL2D(); backEnd.doneFilm = qtrue; // set up our colors, this is our default tone[0] = 0.8f; tone[1] = 0.9f; tone[2] = 1.0f; //VectorCopy( backEnd.currentEntity->ambientLight, tone ); if (backEnd.currentEntity) { if (backEnd.currentEntity->ambientLight[0] > 0.001f && backEnd.currentEntity->ambientLight[1] > 0.001f && backEnd.currentEntity->ambientLight[2] > 0.001f) { tone[0] = backEnd.currentEntity->ambientLight[0]; tone[1] = backEnd.currentEntity->ambientLight[1]; tone[2] = backEnd.currentEntity->ambientLight[2]; } } // VectorNormalize(tone); tone[0] *= 0.3 + 0.7; tone[1] *= 0.3 + 0.7; tone[2] *= 0.3 + 0.7; // tone[0] = 1.6f; // tone[1] = 1.2f; // tone[2] = 0.7f; // TODO: Get overexposure to flares raising this faking "HDR" tonecont[0] = 0.0f; tonecont[1] = 0.0f; tonecont[2] = 0.0f; // inverted toneinv[0] = tone[0] * -1 + 1 + tonecont[0]; toneinv[1] = tone[1] * -1 + 1 + tonecont[1]; toneinv[2] = tone[2] * -1 + 1 + tonecont[2]; // darken vignette. GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ZERO); GL_Bind( tr.dlightImage ); GL_Cull( CT_TWO_SIDED ); qglColor4f( 0.941177, 0.952941, 0.968628, 1.0f ); R_Brighter_Quad( glConfig.vidWidth, glConfig.vidHeight, 0.35f, 0.35f, 0.2f, 0.2f ); // brighten. GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_ONE); GL_Bind( tr.dlightImage ); GL_Cull( CT_TWO_SIDED ); //qglColor4f( 0.941177, 0.952941, 0.968628, 1.0f ); qglColor4f( (0.9f + (tone[0] * 0.5)), (0.9f + (tone[1] * 0.5)), (0.9f + (tone[2] * 0.5)), 1.0f ); R_Brighter_Quad( glConfig.vidWidth, glConfig.vidHeight, 0.25f, 0.25f, 0.48f, 0.48f ); // invert. GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE_MINUS_DST_COLOR | GLS_DSTBLEND_ONE_MINUS_SRC_COLOR); GL_Bind( tr.whiteImage ); GL_Cull( CT_TWO_SIDED ); // qglColor4f(0.85098, 0.85098, 0.815686, 1.0f ); qglColor4f( (0.8f + (toneinv[0] * 0.5)), (0.8f + (toneinv[1] * 0.5)), (0.8f + (toneinv[2] * 0.5)), 1.0f ); R_Brighter_Quad( glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1, 1 ); // brighten. GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_SRC_COLOR); GL_Bind( tr.whiteImage ); GL_Cull( CT_TWO_SIDED ); qglColor4f( 0.615686, 0.615686, 0.615686, 1.0f ); R_Brighter_Quad( glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1, 1 ); // invoort. GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_ONE_MINUS_DST_COLOR | GLS_DSTBLEND_ONE_MINUS_SRC_COLOR); GL_Bind( tr.whiteImage ); GL_Cull( CT_TWO_SIDED ); qglColor4f(1.0f, 1.0f, 1.0f , 1.0f ); R_Brighter_Quad( glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1, 1 ); // brighten. GL_State( GLS_DEPTHTEST_DISABLE | GLS_SRCBLEND_DST_COLOR | GLS_DSTBLEND_SRC_COLOR); GL_Bind( tr.whiteImage ); GL_Cull( CT_TWO_SIDED ); // qglColor4f( 0.866667, 0.847059, 0.776471, 1.0f ); qglColor4f( (0.73f + (toneinv[0] * 0.4)), (0.73f + (toneinv[1] * 0.4)), (0.73f + (toneinv[2] * 0.4)), 1.0f ); R_Brighter_Quad( glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1, 1 ); } // ================================================================= // OLD-STYLE ANTIALIASING // ================================================================= // leilei - old 2000 console-style antialiasing/antiflickering void R_RetroAAScreen( void ) { // this should not need glsl either, but #ifdef GLSL_POSTPROCESSING if( !r_retroAA->integer) return; if ( backEnd.doneraa) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); force32upload = 1; leifxmode = 1233; // just show it through to tvWidth/tvHeight //R_Postprocess_BackupScreen(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); backEnd.doneraa = qtrue; force32upload = 0; #endif } // ================================================================= // !GLSL! // ANIME SHADERS // ================================================================= void R_AnimeScreen( void ) { #ifdef GLSL_POSTPROCESSING if( !r_anime->integer) return; if ( backEnd.doneanime) return; if ( !backEnd.doneSurfaces ) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); force32upload = 1; leifxmode = 888; // anime effect - outlines, desat, bloom and other crap to go with it R_LeiFX_Stupid_Hack(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); leifxmode = 999; // film effect - to blur things slightly, and add some grain and chroma stuff R_LeiFX_Stupid_Hack(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); backEnd.doneanime = qtrue; force32upload = 0; #endif } // ================================================================= // !GLSL! // MOTION BLUR EFFECT // ================================================================= // leilei - motion blur hack void R_MotionBlur_BackupScreen(int which) { #ifdef GLSL_POSTPROCESSING if( r_motionblur->integer < 3) return; if (which == 1) { GL_Bind( postproc.motion1.texture ); // gather thee samples qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 2) { GL_Bind( postproc.motion2.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 3) { GL_Bind( postproc.motion3.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 4) { GL_Bind( postproc.motion4.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 5) { GL_Bind( postproc.motion5.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 11) { GL_Bind( postproc.mpass1.texture ); // to accum qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 12) { GL_Bind( postproc.mpass1.texture ); // to accum qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 13) { GL_Bind( postproc.mpass1.texture ); // to accum qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 14) { GL_Bind( postproc.mpass1.texture ); // to accum qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } if (which == 18) { GL_Bind( postproc.screen.texture ); // to accum qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight ); } #endif } void R_MblurScreen( void ) { #ifdef GLSL_POSTPROCESSING if( r_motionblur->integer < 3) return; if ( backEnd.donemblur) return; if ( !backEnd.doneSurfaces ) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); force32upload = 1; leifxmode = 777; // accumulate frames for the motion blur buffer R_LeiFX_Stupid_Hack(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); force32upload = 0; #else // NO! Use the accumulation buffer instead. #endif } void R_MblurScreenPost( void ) { #ifdef GLSL_POSTPROCESSING if( r_motionblur->integer < 3) return; if ( !backEnd.doneSurfaces ) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } backEnd.donemblur = qtrue; if ( !backEnd.projection2D ) RB_SetGL2D(); force32upload = 1; leifxmode = 770; // accumulate frames for the motion blur buffer R_LeiFX_Stupid_Hack(); // R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); force32upload = 0; #else // NO! #endif } // ================================================================= // TV MODE // ================================================================= static void R_Postprocess_BackupScreenTV( void ) { #ifdef GLSL_POSTPROCESSING GL_TexEnv( GL_MODULATE ); qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); qglMatrixMode( GL_PROJECTION ); qglLoadIdentity (); qglOrtho( 0, glConfig.vidWidth, glConfig.vidHeight, 0, 0, 1 ); qglMatrixMode( GL_MODELVIEW ); qglLoadIdentity (); GL_Bind( postproc.screen.texture ); qglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glConfig.vidWidth, glConfig.vidHeight); #endif } static void R_Postprocess_ScaleTV( void ) { #ifdef GLSL_POSTPROCESSING GL_TexEnv( GL_MODULATE ); qglScissor( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); qglViewport( 0, 0, glConfig.vidWidth, glConfig.vidHeight ); qglMatrixMode( GL_PROJECTION ); qglLoadIdentity (); #endif } // tvmode doesn't do any actual postprocessing yet (ntsc, shadow mask etc) float tvtime; void R_TVScreen( void ) { // leilei - this might not actually need glsl but we're doing this anyway. #ifdef GLSL_POSTPROCESSING if( r_tvMode->integer < 0) return; if ( backEnd.donetv) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } // if ( !backEnd.projection2D ) // RB_SetGL2D(); force32upload = 1; // postprocess = 1; if (backEnd.refdef.time > tvtime) { tvinterlace *= -1; tvtime = backEnd.refdef.time + (1000.0f / 60); // 60hz } tvinter = tvinterlace; if (tvinter < 0) tvinter = 0; if (r_tvMode->integer < 100) tvinter = 0; tvinter = 0; leifxmode = 1234; // just show it through to tvWidth/tvHeight //if (r_tvMode->integer > 2) // leifxmode = 1236; // run it through a shader //R_Postprocess_BackupScreen(); if (r_tvMode->integer > 100) { R_Postprocess_ScaleTV(); } else { R_Postprocess_BackupScreenTV(); R_Bloom_RestoreScreen_Postprocessed(); } backEnd.donetv = qtrue; force32upload = 0; #else // NO! #endif } // ================================================================= // PALLETIZING // // Processes the screen into having a 8-bit indexed color // palette to suit a throwback mode better // // ================================================================= void R_PaletteScreen( void ) { #ifdef GLSL_POSTPROCESSING if( !r_palletize->integer) return; if ( backEnd.donepalette) return; if ( !backEnd.doneSurfaces ) return; if ( !vertexShaders ) return; // leilei - cards without support for this should not ever activate this if( !postproc.started ) { force32upload = 1; R_Postprocess_InitTextures(); if( !postproc.started ) return; } if ( !backEnd.projection2D ) RB_SetGL2D(); force32upload = 1; leifxmode = 1997; // anime effect - outlines, desat, bloom and other crap to go with it R_LeiFX_Stupid_Hack(); R_Postprocess_BackupScreen(); R_Bloom_RestoreScreen_Postprocessed(); backEnd.donepalette = qtrue; force32upload = 0; #else // NO! #endif } // ================================================================= // WATER BUFFER TEST // ================================================================= static struct { // NO! } water; // leilei - experimental water effect static void R_Water_InitTextures( void ) { // NO! } void R_InitWaterTextures( void ) { // NO! } static void R_Water_BackupScreen( void ) { // NO! } static void R_WaterWorks( void ) { // NO! } static void R_Water_RestoreScreen( void ) { // NO! } void R_WaterInit( void ) { // NO! } void R_WaterScreen( void ) { // NO! }