sourcearena/mp/src/game/client/viewpostprocess.cpp
Joe Ludwig beaae8ac45 Updated the SDK with the latest code from the TF and HL2 branches
* Adds support for Visual Studio 2012 and 2013
* VR Mode:
. Switches from headtrack.dll to sourcevr.dll
. Improved readability of the UI in VR
. Removed the IPD calibration tool. TF2 will now obey the Oculus
configuration file. Use the Oculus calibration tool in your SDK or
install and run "OpenVR" under Tools in Steam to calibrate your IPD.
. Added dropdown to enable VR mode in the Video options. Removed the -vr
command line option.
. Added the ability to switch in and out of VR mode without quitting the
game
. By default VR mode will run full screen. To switch back to a
borderless window set the vr_force_windowed convar.
. Added support for VR mode on Linux
* Many assorted bug fixes and other changes from Team Fortress in
various shared files
2013-12-03 08:54:16 -08:00

2999 lines
114 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================
#include "cbase.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/itexture.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/imaterialsystemhardwareconfig.h"
#include "materialsystem/materialsystem_config.h"
#include "tier1/callqueue.h"
#include "colorcorrectionmgr.h"
#include "view_scene.h"
#include "c_world.h"
#include "bitmap/tgawriter.h"
#include "filesystem.h"
#include "tier0/vprof.h"
#include "proxyentity.h"
//-----------------------------------------------------------------------------
// Globals
//-----------------------------------------------------------------------------
// mapmaker controlled autoexposure
bool g_bUseCustomAutoExposureMin = false;
bool g_bUseCustomAutoExposureMax = false;
bool g_bUseCustomBloomScale = false;
float g_flCustomAutoExposureMin = 0;
float g_flCustomAutoExposureMax = 0;
float g_flCustomBloomScale = 0.0f;
float g_flCustomBloomScaleMinimum = 0.0f;
bool g_bFlashlightIsOn = false;
// hdr parameters
ConVar mat_bloomscale( "mat_bloomscale", "1" );
ConVar mat_hdr_level( "mat_hdr_level", "2", FCVAR_ARCHIVE );
ConVar mat_bloomamount_rate( "mat_bloomamount_rate", "0.05f", FCVAR_CHEAT );
static ConVar debug_postproc( "mat_debug_postprocessing_effects", "0", FCVAR_NONE, "0 = off, 1 = show post-processing passes in quadrants of the screen, 2 = only apply post-processing to the centre of the screen" );
static ConVar split_postproc( "mat_debug_process_halfscreen", "0", FCVAR_CHEAT );
static ConVar mat_postprocessing_combine( "mat_postprocessing_combine", "1", FCVAR_NONE, "Combine bloom, software anti-aliasing and color correction into one post-processing pass" );
static ConVar mat_dynamic_tonemapping( "mat_dynamic_tonemapping", "1", FCVAR_CHEAT );
static ConVar mat_show_ab_hdr( "mat_show_ab_hdr", "0" );
static ConVar mat_tonemapping_occlusion_use_stencil( "mat_tonemapping_occlusion_use_stencil", "0" );
ConVar mat_debug_autoexposure("mat_debug_autoexposure","0", FCVAR_CHEAT);
static ConVar mat_autoexposure_max( "mat_autoexposure_max", "2" );
static ConVar mat_autoexposure_min( "mat_autoexposure_min", "0.5" );
static ConVar mat_show_histogram( "mat_show_histogram", "0" );
ConVar mat_hdr_tonemapscale( "mat_hdr_tonemapscale", "1.0", FCVAR_CHEAT );
ConVar mat_hdr_uncapexposure( "mat_hdr_uncapexposure", "0", FCVAR_CHEAT );
ConVar mat_force_bloom("mat_force_bloom","0", FCVAR_CHEAT);
ConVar mat_disable_bloom("mat_disable_bloom","0");
ConVar mat_debug_bloom("mat_debug_bloom","0", FCVAR_CHEAT);
ConVar mat_colorcorrection( "mat_colorcorrection", "0" );
ConVar mat_accelerate_adjust_exposure_down( "mat_accelerate_adjust_exposure_down", "3.0", FCVAR_CHEAT );
ConVar mat_hdr_manual_tonemap_rate( "mat_hdr_manual_tonemap_rate", "1.0" );
// fudge factor to make non-hdr bloom more closely match hdr bloom. Because of auto-exposure, high
// bloomscales don't blow out as much in hdr. this factor was derived by comparing images in a
// reference scene.
ConVar mat_non_hdr_bloom_scalefactor("mat_non_hdr_bloom_scalefactor",".3");
// Apply addition scale to the final bloom scale
static ConVar mat_bloom_scalefactor_scalar( "mat_bloom_scalefactor_scalar", "1.0" );
//ConVar mat_exposure_center_region_x( "mat_exposure_center_region_x","0.75", FCVAR_CHEAT );
//ConVar mat_exposure_center_region_y( "mat_exposure_center_region_y","0.80", FCVAR_CHEAT );
//ConVar mat_exposure_center_region_x_flashlight( "mat_exposure_center_region_x_flashlight","0.33", FCVAR_CHEAT );
//ConVar mat_exposure_center_region_y_flashlight( "mat_exposure_center_region_y_flashlight","0.33", FCVAR_CHEAT );
ConVar mat_exposure_center_region_x( "mat_exposure_center_region_x","0.9", FCVAR_CHEAT );
ConVar mat_exposure_center_region_y( "mat_exposure_center_region_y","0.85", FCVAR_CHEAT );
ConVar mat_exposure_center_region_x_flashlight( "mat_exposure_center_region_x_flashlight","0.9", FCVAR_CHEAT );
ConVar mat_exposure_center_region_y_flashlight( "mat_exposure_center_region_y_flashlight","0.85", FCVAR_CHEAT );
ConVar mat_tonemap_algorithm( "mat_tonemap_algorithm", "1", FCVAR_CHEAT, "0 = Original Algorithm 1 = New Algorithm" );
ConVar mat_tonemap_percent_target( "mat_tonemap_percent_target", "60.0", FCVAR_CHEAT );
ConVar mat_tonemap_percent_bright_pixels( "mat_tonemap_percent_bright_pixels", "2.0", FCVAR_CHEAT );
ConVar mat_tonemap_min_avglum( "mat_tonemap_min_avglum", "3.0", FCVAR_CHEAT );
ConVar mat_fullbright( "mat_fullbright", "0", FCVAR_CHEAT );
extern ConVar localplayer_visionflags;
enum PostProcessingCondition {
PPP_ALWAYS,
PPP_IF_COND_VAR,
PPP_IF_NOT_COND_VAR
};
struct PostProcessingPass {
PostProcessingCondition ppp_test;
ConVar const *cvar_to_test;
char const *material_name; // terminate list with null
char const *dest_rendering_target;
char const *src_rendering_target; // can be null. needed for source scaling
int xdest_scale,ydest_scale; // allows scaling down
int xsrc_scale,ysrc_scale; // allows scaling down
CMaterialReference m_mat_ref; // so we don't have to keep searching
};
#define PPP_PROCESS_PARTIAL_SRC(srcmatname,dest_rt_name,src_tname,scale) \
{PPP_ALWAYS,0,srcmatname,dest_rt_name,src_tname,1,1,scale,scale}
#define PPP_PROCESS_PARTIAL_DEST(srcmatname,dest_rt_name,src_tname,scale) \
{PPP_ALWAYS,0,srcmatname,dest_rt_name,src_tname,scale,scale,1,1}
#define PPP_PROCESS_PARTIAL_SRC_PARTIAL_DEST(srcmatname,dest_rt_name,src_tname,srcscale,destscale) \
{PPP_ALWAYS,0,srcmatname,dest_rt_name,src_tname,destscale,destscale,srcscale,srcscale}
#define PPP_END {PPP_ALWAYS,0,NULL,NULL,0,0,0,0,0}
#define PPP_PROCESS(srcmatname,dest_rt_name) {PPP_ALWAYS,0,srcmatname,dest_rt_name,0,1,1,1,1}
#define PPP_PROCESS_IF_CVAR(cvarptr,srcmatname,dest_rt_name) \
{PPP_IF_COND_VAR,cvarptr,srcmatname,dest_rt_name,0,1,1,1,1}
#define PPP_PROCESS_IF_NOT_CVAR(cvarptr,srcmatname,dest_rt_name) \
{PPP_IF_NOT_COND_VAR,cvarptr,srcmatname,dest_rt_name,0,1,1,1,1}
#define PPP_PROCESS_IF_NOT_CVAR_SRCTEXTURE(cvarptr,srcmatname,src_tname,dest_rt_name) \
{PPP_IF_NOT_COND_VAR,cvarptr,srcmatname,dest_rt_name,src_tname,1,1,1,1}
#define PPP_PROCESS_IF_CVAR_SRCTEXTURE(cvarptr,srcmatname,src_txtrname,dest_rt_name) \
{PPP_IF_COND_VAR,cvarptr,srcmatname,dest_rt_name,src_txtrname,1,1,1,1}
#define PPP_PROCESS_SRCTEXTURE(srcmatname,src_tname,dest_rt_name) \
{PPP_ALWAYS,0,srcmatname,dest_rt_name,src_tname,1,1,1,1}
struct ClipBox
{
int m_minx, m_miny;
int m_maxx, m_maxy;
};
static void DrawClippedScreenSpaceRectangle(
IMaterial *pMaterial,
int destx, int desty,
int width, int height,
float src_texture_x0, float src_texture_y0, // which texel you want to appear at
// destx/y
float src_texture_x1, float src_texture_y1, // which texel you want to appear at
// destx+width-1, desty+height-1
int src_texture_width, int src_texture_height, // needed for fixup
ClipBox const *clipbox,
void *pClientRenderable = NULL )
{
if (clipbox)
{
if ( (destx > clipbox->m_maxx ) || ( desty > clipbox->m_maxy ))
return;
if ( (destx + width - 1 < clipbox->m_minx ) || ( desty + height - 1 < clipbox->m_miny ))
return;
// left clip
if ( destx < clipbox->m_minx )
{
src_texture_x0 = FLerp( src_texture_x0, src_texture_x1, destx, destx + width - 1, clipbox->m_minx );
width -= ( clipbox->m_minx - destx );
destx = clipbox->m_minx;
}
// top clip
if ( desty < clipbox->m_miny )
{
src_texture_y0 = FLerp( src_texture_y0, src_texture_y1, desty, desty + height - 1, clipbox->m_miny );
height -= ( clipbox->m_miny - desty );
desty = clipbox->m_miny;
}
// right clip
if ( destx + width - 1 > clipbox->m_maxx )
{
src_texture_x1 = FLerp( src_texture_x0, src_texture_x1, destx, destx + width - 1, clipbox->m_maxx );
width = clipbox->m_maxx - destx;
}
// bottom clip
if ( desty + height - 1 > clipbox->m_maxy )
{
src_texture_y1 = FLerp( src_texture_y0, src_texture_y1, desty, desty + height - 1, clipbox->m_maxy );
height = clipbox->m_maxy - desty;
}
}
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->DrawScreenSpaceRectangle( pMaterial, destx, desty, width, height, src_texture_x0,
src_texture_y0, src_texture_x1, src_texture_y1,
src_texture_width, src_texture_height, pClientRenderable );
}
void ApplyPostProcessingPasses(PostProcessingPass *pass_list, // table of effects to apply
ClipBox const *clipbox=0, // clipping box for these effects
ClipBox *dest_coords_out=0) // receives dest coords of last blit
{
CMatRenderContextPtr pRenderContext( materials );
ITexture *pSaveRenderTarget = pRenderContext->GetRenderTarget();
int pcount=0;
if ( debug_postproc.GetInt() == 1 )
{
pRenderContext->SetRenderTarget(NULL);
int dest_width,dest_height;
pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
pRenderContext->Viewport( 0, 0, dest_width, dest_height );
pRenderContext->ClearColor3ub(255,0,0);
// pRenderContext->ClearBuffers(true,true);
}
while(pass_list->material_name)
{
bool do_it=true;
switch(pass_list->ppp_test)
{
case PPP_IF_COND_VAR:
do_it=(pass_list->cvar_to_test)->GetBool();
break;
case PPP_IF_NOT_COND_VAR:
do_it=! ((pass_list->cvar_to_test)->GetBool());
break;
}
if ((pass_list->dest_rendering_target==0) && (debug_postproc.GetInt() == 1))
do_it=0;
if (do_it)
{
ClipBox const *cb=0;
if (pass_list->dest_rendering_target==0)
{
cb=clipbox;
}
IMaterial *src_mat=pass_list->m_mat_ref;
if (! src_mat)
{
src_mat=materials->FindMaterial(pass_list->material_name,
TEXTURE_GROUP_OTHER,true);
if (src_mat)
{
pass_list->m_mat_ref.Init(src_mat);
}
}
if (pass_list->dest_rendering_target)
{
ITexture *dest_rt=materials->FindTexture(pass_list->dest_rendering_target,
TEXTURE_GROUP_RENDER_TARGET );
pRenderContext->SetRenderTarget( dest_rt);
}
else
{
pRenderContext->SetRenderTarget( NULL );
}
int dest_width,dest_height;
pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
pRenderContext->Viewport( 0, 0, dest_width, dest_height );
dest_width/=pass_list->xdest_scale;
dest_height/=pass_list->ydest_scale;
if (pass_list->src_rendering_target)
{
ITexture *src_rt=materials->FindTexture(pass_list->src_rendering_target,
TEXTURE_GROUP_RENDER_TARGET );
int src_width=src_rt->GetActualWidth();
int src_height=src_rt->GetActualHeight();
int ssrc_width=(src_width-1)/pass_list->xsrc_scale;
int ssrc_height=(src_height-1)/pass_list->ysrc_scale;
DrawClippedScreenSpaceRectangle(
src_mat,0,0,dest_width,dest_height,
0,0,ssrc_width,ssrc_height,src_width,src_height,cb);
if ((pass_list->dest_rendering_target) && (debug_postproc.GetInt() == 1))
{
pRenderContext->SetRenderTarget(NULL);
int row=pcount/2;
int col=pcount %2;
int vdest_width,vdest_height;
pRenderContext->GetRenderTargetDimensions( vdest_width, vdest_height );
pRenderContext->Viewport( 0, 0, vdest_width, vdest_height );
pRenderContext->DrawScreenSpaceRectangle(
src_mat,col*400,200+row*300,dest_width,dest_height,
0,0,ssrc_width,ssrc_height,src_width,src_height);
}
}
else
{
// just draw the whole source
if ((pass_list->dest_rendering_target==0) && split_postproc.GetInt())
{
DrawClippedScreenSpaceRectangle(src_mat,0,0,dest_width/2,dest_height,
0,0,.5,1,1,1,cb);
}
else
{
DrawClippedScreenSpaceRectangle(src_mat,0,0,dest_width,dest_height,
0,0,1,1,1,1,cb);
}
if ((pass_list->dest_rendering_target) && (debug_postproc.GetInt() == 1))
{
pRenderContext->SetRenderTarget(NULL);
int row=pcount/4;
int col=pcount %4;
int dest_width,dest_height;
pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
pRenderContext->Viewport( 0, 0, dest_width, dest_height );
DrawClippedScreenSpaceRectangle(src_mat,10+col*220,10+row*220,
200,200,
0,0,1,1,1,1,cb);
}
}
if (dest_coords_out)
{
dest_coords_out->m_minx=0;
dest_coords_out->m_maxx=dest_width-1;
dest_coords_out->m_miny=0;
dest_coords_out->m_maxy=dest_height-1;
}
}
pass_list++;
pcount++;
}
pRenderContext->SetRenderTarget(pSaveRenderTarget);
}
PostProcessingPass HDRFinal_Float[] =
{
PPP_PROCESS_SRCTEXTURE( "dev/downsample", "_rt_FullFrameFB", "_rt_SmallFB0" ),
PPP_PROCESS_SRCTEXTURE( "dev/blurfilterx", "_rt_SmallFB0", "_rt_SmallFB1" ),
PPP_PROCESS_SRCTEXTURE( "dev/blurfiltery", "_rt_SmallFB1", "_rt_SmallFB0" ),
PPP_PROCESS_SRCTEXTURE("dev/floattoscreen_combine","_rt_FullFrameFB",NULL),
PPP_END
};
PostProcessingPass HDRFinal_Float_NoBloom[] =
{
PPP_PROCESS_SRCTEXTURE("dev/copyfullframefb", "_rt_FullFrameFB",NULL),
PPP_END
};
PostProcessingPass HDRSimulate_NonHDR[] =
{
PPP_PROCESS("dev/copyfullframefb_vanilla",NULL),
PPP_END
};
static void SetRenderTargetAndViewPort(ITexture *rt)
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->SetRenderTarget(rt);
pRenderContext->Viewport(0,0,rt->GetActualWidth(),rt->GetActualHeight());
}
#define FILTER_KERNEL_SLOP 20
// Note carefully about the downsampling: the first downsampling samples from the full rendertarget
// down to a temp. When doing this sampling, the texture source clamping will take care of the out
// of bounds sampling done because of the filter kernels's width. However, on any of the subsequent
// sampling operations, we will be sampling from a partially filled render target. So, texture
// coordinate clamping cannot help us here. So, we need to always render a few more pixels to the
// destination than we actually intend to, so as to replicate the border pixels so that garbage
// pixels do not get sucked into the sampling. To deal with this, we always add FILTER_KERNEL_SLOP
// to our widths/heights if there is room for them in the destination.
static void DrawScreenSpaceRectangleWithSlop(
ITexture *dest_rt,
IMaterial *pMaterial,
int destx, int desty,
int width, int height,
float src_texture_x0, float src_texture_y0, // which texel you want to appear at
// destx/y
float src_texture_x1, float src_texture_y1, // which texel you want to appear at
// destx+width-1, desty+height-1
int src_texture_width, int src_texture_height // needed for fixup
)
{
// add slop
int slopwidth = width + FILTER_KERNEL_SLOP; //min(dest_rt->GetActualWidth()-destx,width+FILTER_KERNEL_SLOP);
int slopheight = height + FILTER_KERNEL_SLOP; //min(dest_rt->GetActualHeight()-desty,height+FILTER_KERNEL_SLOP);
// adjust coordinates for slop
src_texture_x1 = FLerp( src_texture_x0, src_texture_x1, destx, destx + width - 1, destx + slopwidth - 1 );
src_texture_y1 = FLerp( src_texture_y0, src_texture_y1, desty, desty + height - 1, desty + slopheight - 1 );
width = slopwidth;
height = slopheight;
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->DrawScreenSpaceRectangle( pMaterial, destx, desty, width, height,
src_texture_x0, src_texture_y0,
src_texture_x1, src_texture_y1,
src_texture_width, src_texture_height );
}
enum Histogram_entry_state_t
{
HESTATE_INITIAL=0,
HESTATE_FIRST_QUERY_IN_FLIGHT,
HESTATE_QUERY_IN_FLIGHT,
HESTATE_QUERY_DONE,
};
#define N_LUMINANCE_RANGES 31
#define N_LUMINANCE_RANGES_NEW 17
#define MAX_QUERIES_PER_FRAME 1
class CHistogram_entry_t
{
public:
Histogram_entry_state_t m_state;
OcclusionQueryObjectHandle_t m_occ_handle; // the occlusion query handle
int m_frame_queued; // when this query was last queued
int m_npixels; // # of pixels this histogram represents
int m_npixels_in_range;
float m_min_lum, m_max_lum; // the luminance range this entry was queried with
float m_minx, m_miny, m_maxx, m_maxy; // range is 0..1 in fractions of the screen
bool ContainsValidData( void )
{
return ( m_state == HESTATE_QUERY_DONE ) || ( m_state == HESTATE_QUERY_IN_FLIGHT );
}
void IssueQuery( int frm_num );
};
void CHistogram_entry_t::IssueQuery( int frm_num )
{
CMatRenderContextPtr pRenderContext( materials );
if ( !m_occ_handle )
{
m_occ_handle = pRenderContext->CreateOcclusionQueryObject();
}
int xl, yl, dest_width, dest_height;
pRenderContext->GetViewport( xl, yl, dest_width, dest_height );
// Find min and max gamma-space text range
float flTestRangeMin = m_min_lum;
float flTestRangeMax = ( m_max_lum == 1.0f ) ? 10000.0f : m_max_lum; // Count all pixels >1.0 as 1.0
// First, set stencil bits where the colors match
IMaterial *test_mat=materials->FindMaterial( "dev/lumcompare", TEXTURE_GROUP_OTHER, true );
IMaterialVar *pMinVar = test_mat->FindVar( "$C0_X", NULL );
pMinVar->SetFloatValue( flTestRangeMin );
IMaterialVar *pMaxVar = test_mat->FindVar( "$C0_Y", NULL );
pMaxVar->SetFloatValue( flTestRangeMax );
int scrx_min = FLerp( xl, ( xl + dest_width - 1 ), 0, 1, m_minx );
int scrx_max = FLerp( xl, ( xl + dest_width - 1 ), 0, 1, m_maxx );
int scry_min = FLerp( yl, ( yl + dest_height - 1 ), 0, 1, m_miny );
int scry_max = FLerp( yl, ( yl + dest_height - 1 ), 0, 1, m_maxy );
float exposure_width_scale, exposure_height_scale;
// now, shrink region of interest if the flashlight is on
if ( g_bFlashlightIsOn )
{
exposure_width_scale = ( 0.5f * ( 1.0f - mat_exposure_center_region_x_flashlight.GetFloat() ) );
exposure_height_scale = ( 0.5f * ( 1.0f - mat_exposure_center_region_y_flashlight.GetFloat() ) );
}
else
{
exposure_width_scale = ( 0.5f * ( 1.0f - mat_exposure_center_region_x.GetFloat() ) );
exposure_height_scale = ( 0.5f * ( 1.0f - mat_exposure_center_region_y.GetFloat() ) );
}
int skip_edgex = ( 1 + scrx_max - scrx_min ) * exposure_width_scale;
int skip_edgey = ( 1 + scry_max - scry_min ) * exposure_height_scale;
// now, do luminance compare
float tscale = 1.0;
if ( g_pMaterialSystemHardwareConfig->GetHDRType() == HDR_TYPE_FLOAT )
{
tscale = pRenderContext->GetToneMappingScaleLinear().x;
}
IMaterialVar *use_t_scale = test_mat->FindVar( "$C0_Z", NULL );
use_t_scale->SetFloatValue( tscale );
m_npixels = ( 1 + scrx_max - scrx_min ) * ( 1 + scry_max - scry_min );
if ( mat_tonemapping_occlusion_use_stencil.GetInt() )
{
pRenderContext->SetStencilWriteMask( 1 );
// AV - We don't need to clear stencil here because it's already been cleared at the beginning of the frame
//pRenderContext->ClearStencilBufferRectangle( scrx_min, scry_min, scrx_max, scry_max, 0 );
pRenderContext->SetStencilEnable( true );
pRenderContext->SetStencilPassOperation( STENCILOPERATION_REPLACE );
pRenderContext->SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_ALWAYS );
pRenderContext->SetStencilFailOperation( STENCILOPERATION_KEEP );
pRenderContext->SetStencilZFailOperation( STENCILOPERATION_KEEP );
pRenderContext->SetStencilReferenceValue( 1 );
}
else
{
pRenderContext->BeginOcclusionQueryDrawing( m_occ_handle );
}
scrx_min += skip_edgex;
scry_min += skip_edgey;
scrx_max -= skip_edgex;
scry_max -= skip_edgey;
pRenderContext->DrawScreenSpaceRectangle( test_mat,
scrx_min, scry_min,
1 + scrx_max - scrx_min,
1 + scry_max - scry_min,
scrx_min, scry_min,
scrx_max, scry_max,
dest_width, dest_height);
if ( mat_tonemapping_occlusion_use_stencil.GetInt() )
{
// now, start counting how many pixels had their stencil bit set via an occlusion query
pRenderContext->BeginOcclusionQueryDrawing( m_occ_handle );
// now, issue an occlusion query using stencil as the mask
pRenderContext->SetStencilEnable( true );
pRenderContext->SetStencilTestMask( 1 );
pRenderContext->SetStencilPassOperation( STENCILOPERATION_KEEP );
pRenderContext->SetStencilCompareFunction( STENCILCOMPARISONFUNCTION_EQUAL );
pRenderContext->SetStencilFailOperation( STENCILOPERATION_KEEP );
pRenderContext->SetStencilZFailOperation( STENCILOPERATION_KEEP );
pRenderContext->SetStencilReferenceValue( 1 );
IMaterial *stest_mat=materials->FindMaterial( "dev/no_pixel_write", TEXTURE_GROUP_OTHER, true);
pRenderContext->DrawScreenSpaceRectangle( stest_mat,
scrx_min, scry_min,
1 + scrx_max - scrx_min,
1 + scry_max - scry_min,
scrx_min, scry_min,
scrx_max, scry_max,
dest_width, dest_height);
pRenderContext->SetStencilEnable( false );
}
pRenderContext->EndOcclusionQueryDrawing( m_occ_handle );
if ( m_state == HESTATE_INITIAL )
m_state = HESTATE_FIRST_QUERY_IN_FLIGHT;
else
m_state = HESTATE_QUERY_IN_FLIGHT;
m_frame_queued = frm_num;
}
#define HISTOGRAM_BAR_SIZE 200
class CLuminanceHistogramSystem
{
CHistogram_entry_t CurHistogram[N_LUMINANCE_RANGES];
int cur_query_frame;
public:
float FindLocationOfPercentBrightPixels( float flPercentBrightPixels, float flPercentTarget );
float GetTargetTonemapScalar( bool bGetIdealTargetForDebugMode );
void Update( void );
void DisplayHistogram( void );
void UpdateLuminanceRanges( void );
CLuminanceHistogramSystem(void)
{
UpdateLuminanceRanges();
}
};
void CLuminanceHistogramSystem::Update( void )
{
UpdateLuminanceRanges();
// find which histogram entries should have something done this frame
int n_queries_issued_this_frame=0;
cur_query_frame++;
int nNumRanges = N_LUMINANCE_RANGES;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumRanges = N_LUMINANCE_RANGES_NEW;
for ( int i=0; i<nNumRanges; i++ )
{
switch ( CurHistogram[i].m_state )
{
case HESTATE_INITIAL:
if ( n_queries_issued_this_frame<MAX_QUERIES_PER_FRAME )
{
CurHistogram[i].IssueQuery(cur_query_frame);
n_queries_issued_this_frame++;
}
break;
case HESTATE_FIRST_QUERY_IN_FLIGHT:
case HESTATE_QUERY_IN_FLIGHT:
if ( cur_query_frame > CurHistogram[i].m_frame_queued + 2 )
{
CMatRenderContextPtr pRenderContext( materials );
int np = pRenderContext->OcclusionQuery_GetNumPixelsRendered(
CurHistogram[i].m_occ_handle );
if ( np !=- 1 ) // -1=query not finished. wait until
// next time
{
CurHistogram[i].m_npixels_in_range = np;
// if (mat_debug_autoexposure.GetInt())
// Warning("min=%f max=%f np = %d\n",CurHistogram[i].m_min_lum,CurHistogram[i].m_max_lum,np);
CurHistogram[i].m_state = HESTATE_QUERY_DONE;
}
}
break;
}
}
// now, issue queries for the oldest finished queries we have
while( n_queries_issued_this_frame < MAX_QUERIES_PER_FRAME )
{
int nNumRanges = N_LUMINANCE_RANGES;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumRanges = N_LUMINANCE_RANGES_NEW;
int oldest_so_far =- 1;
for( int i = 0;i < nNumRanges;i ++ )
if ( ( CurHistogram[i].m_state == HESTATE_QUERY_DONE ) &&
( ( oldest_so_far ==- 1 ) ||
( CurHistogram[i].m_frame_queued <
CurHistogram[oldest_so_far].m_frame_queued ) ) )
oldest_so_far = i;
if ( oldest_so_far ==- 1 ) // nothing to do
break;
CurHistogram[oldest_so_far].IssueQuery( cur_query_frame );
n_queries_issued_this_frame ++;
}
}
float CLuminanceHistogramSystem::FindLocationOfPercentBrightPixels( float flPercentBrightPixels, float flPercentTargetToSnapToIfInSameBin = -1.0f )
{
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm
{
int nTotalValidPixels = 0;
for ( int i=0; i<N_LUMINANCE_RANGES_NEW-1; i++ )
{
if ( CurHistogram[i].ContainsValidData() )
{
nTotalValidPixels += CurHistogram[i].m_npixels_in_range;
}
}
if ( nTotalValidPixels == 0 )
{
return -1.0f;
}
// Find where percent range border is
float flTotalPercentRangeTested = 0.0f;
float flTotalPercentPixelsTested = 0.0f;
for ( int i=N_LUMINANCE_RANGES_NEW-2; i>=0; i-- ) // Start at the bright end
{
if ( !CurHistogram[i].ContainsValidData() )
return -1.0f;
float flPixelPercentNeeded = ( flPercentBrightPixels / 100.0f ) - flTotalPercentPixelsTested;
float flThisBinPercentOfTotalPixels = float( CurHistogram[i].m_npixels_in_range ) / float( nTotalValidPixels );
float flThisBinLuminanceRange = CurHistogram[i].m_max_lum - CurHistogram[i].m_min_lum;
if ( flThisBinPercentOfTotalPixels >= flPixelPercentNeeded ) // We found the bin needed
{
if ( flPercentTargetToSnapToIfInSameBin >= 0.0f )
{
if ( ( CurHistogram[i].m_min_lum <= ( flPercentTargetToSnapToIfInSameBin / 100.0f ) ) && ( CurHistogram[i].m_max_lum >= ( flPercentTargetToSnapToIfInSameBin / 100.0f ) ) )
{
// Sticky bin...We're in the same bin as the target so keep the tonemap scale where it is
return ( flPercentTargetToSnapToIfInSameBin / 100.0f );
}
}
float flPercentOfThesePixelsNeeded = flPixelPercentNeeded / flThisBinPercentOfTotalPixels;
float flPercentLocationOfBorder = 1.0f - ( flTotalPercentRangeTested + ( flThisBinLuminanceRange * flPercentOfThesePixelsNeeded ) );
flPercentLocationOfBorder = MAX( CurHistogram[i].m_min_lum, MIN( CurHistogram[i].m_max_lum, flPercentLocationOfBorder ) ); // Clamp to this bin just in case
return flPercentLocationOfBorder;
}
flTotalPercentPixelsTested += flThisBinPercentOfTotalPixels;
flTotalPercentRangeTested += flThisBinLuminanceRange;
}
return -1.0f;
}
else
{
// Don't know what to do for other algorithms yet
return -1.0f;
}
}
float CLuminanceHistogramSystem::GetTargetTonemapScalar( bool bGetIdealTargetForDebugMode = false )
{
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm
{
float flPercentLocationOfTarget;
if ( bGetIdealTargetForDebugMode == true)
flPercentLocationOfTarget = FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat() ); // Don't pass in the second arg so the scalar doesn't snap to a bin
else
flPercentLocationOfTarget = FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetFloat() );
if ( flPercentLocationOfTarget < 0.0f ) // This is the return error code
{
flPercentLocationOfTarget = mat_tonemap_percent_target.GetFloat() / 100.0f; // Pretend we're at the target
}
// Make sure this is > 0.0f
flPercentLocationOfTarget = MAX( 0.0001f, flPercentLocationOfTarget );
// Compute target scalar
float flTargetScalar = ( mat_tonemap_percent_target.GetFloat() / 100.0f ) / flPercentLocationOfTarget;
// Compute secondary target scalar
float flAverageLuminanceLocation = FindLocationOfPercentBrightPixels( 50.0f );
if ( flAverageLuminanceLocation > 0.0f )
{
float flTargetScalar2 = ( mat_tonemap_min_avglum.GetFloat() / 100.0f ) / flAverageLuminanceLocation;
// Only override it if it's trying to brighten the image more than the primary algorithm
if ( flTargetScalar2 > flTargetScalar )
{
flTargetScalar = flTargetScalar2;
}
}
// Apply this against last frames scalar
CMatRenderContextPtr pRenderContext( materials );
float flLastScale = pRenderContext->GetToneMappingScaleLinear().x;
flTargetScalar *= flLastScale;
flTargetScalar = MAX( 0.001f, flTargetScalar );
return flTargetScalar;
}
else // Original tonemapping
{
float average_luminance = 0.5f;
float total = 0;
int total_pixels = 0;
float scale_value = 1.0;
if ( CurHistogram[N_LUMINANCE_RANGES-1].ContainsValidData() )
{
scale_value = CurHistogram[N_LUMINANCE_RANGES-1].m_npixels * ( 1.0f / CurHistogram[N_LUMINANCE_RANGES-1].m_npixels_in_range );
if ( mat_debug_autoexposure.GetInt() )
{
engine->Con_NPrintf( 20, "Scale value = %f", scale_value );
//Warning( "scale value=%f\n", scale_value );
}
}
else
average_luminance = 0.5;
if ( !IsFinite( scale_value ) )
scale_value = 1.0f;
for ( int i=0; i<N_LUMINANCE_RANGES-1; i++ )
{
if ( CurHistogram[i].ContainsValidData() )
{
total += scale_value * CurHistogram[i].m_npixels_in_range * AVG( CurHistogram[i].m_min_lum, CurHistogram[i].m_max_lum );
total_pixels += CurHistogram[i].m_npixels;
}
else
average_luminance = 0.5; // always return 0.5 until we've queried a whole frame
}
if ( total_pixels > 0 )
average_luminance = total * ( 1.0 / total_pixels );
else
average_luminance = 0.5;
// Make sure this is > 0.0f
average_luminance = MAX( 0.0001f, average_luminance );
// Compute target scalar
float flTargetScalar = 0.005 / average_luminance;
return flTargetScalar;
}
}
static float GetCurrentBloomScale( void )
{
// Use the appropriate bloom scale settings. Mapmakers's overrides the convar settings.
float flCurrentBloomScale = 1.0f;
if ( g_bUseCustomBloomScale )
{
flCurrentBloomScale = g_flCustomBloomScale;
}
else
{
flCurrentBloomScale = mat_bloomscale.GetFloat();
}
return flCurrentBloomScale;
}
static void GetExposureRange( float *flAutoExposureMin, float *flAutoExposureMax )
{
// Get min
if ( ( g_bUseCustomAutoExposureMin ) && ( g_flCustomAutoExposureMin > 0.0f ) )
{
*flAutoExposureMin = g_flCustomAutoExposureMin;
}
else
{
*flAutoExposureMin = mat_autoexposure_min.GetFloat();
}
// Get max
if ( ( g_bUseCustomAutoExposureMax ) && ( g_flCustomAutoExposureMax > 0.0f ) )
{
*flAutoExposureMax = g_flCustomAutoExposureMax;
}
else
{
*flAutoExposureMax = mat_autoexposure_max.GetFloat();
}
// Override
if ( mat_hdr_uncapexposure.GetInt() )
{
*flAutoExposureMax = 20.0f;
*flAutoExposureMin = 0.0f;
}
// Make sure min <= max
if ( *flAutoExposureMin > *flAutoExposureMax )
{
*flAutoExposureMax = *flAutoExposureMin;
}
}
void CLuminanceHistogramSystem::UpdateLuminanceRanges( void )
{
// Only update if our mode changed
static int s_nCurrentBucketAlgorithm = -1;
if ( s_nCurrentBucketAlgorithm == mat_tonemap_algorithm.GetInt() )
return;
s_nCurrentBucketAlgorithm = mat_tonemap_algorithm.GetInt();
//==================================================================//
// Force fallback to original tone mapping algorithm for these mods //
//==================================================================//
static bool s_bFirstTime = true;
if ( engine == NULL )
{
// Force this code to get hit again so we can change algorithm based on the client
s_nCurrentBucketAlgorithm = -1;
}
else if ( s_bFirstTime == true )
{
s_bFirstTime = false;
// This seems like a bad idea but it's fine for now
const char *sModsForOriginalAlgorithm[] = { "dod", "cstrike", "lostcoast", "hl1" };
for ( int i=0; i<3; i++ )
{
if ( strlen( engine->GetGameDirectory() ) >= strlen( sModsForOriginalAlgorithm[i] ) )
{
if ( stricmp( &( engine->GetGameDirectory()[strlen( engine->GetGameDirectory() ) - strlen( sModsForOriginalAlgorithm[i] )] ), sModsForOriginalAlgorithm[i] ) == 0 )
{
mat_tonemap_algorithm.SetValue( 0 ); // Original algorithm
s_nCurrentBucketAlgorithm = mat_tonemap_algorithm.GetInt();
break;
}
}
}
}
int nNumRanges = N_LUMINANCE_RANGES;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumRanges = N_LUMINANCE_RANGES_NEW;
cur_query_frame=0;
for ( int bucket = 0; bucket < nNumRanges; bucket ++ )
{
int idx = bucket;
CHistogram_entry_t & e = CurHistogram[idx];
e.m_state = HESTATE_INITIAL;
e.m_minx = 0;
e.m_maxx = 1;
e.m_miny = 0;
e.m_maxy = 1;
if ( bucket != nNumRanges-1 ) // Last bucket is special
{
if ( mat_tonemap_algorithm.GetInt() == 0 ) // Original algorithm
{
// Use a logarithmic ramp for high range in the low range
e.m_min_lum = - 0.01 + exp( FLerp( log( .01 ), log( .01 + 1 ), 0, nNumRanges - 1, bucket ) );
e.m_max_lum = - 0.01 + exp( FLerp( log( .01 ), log( .01 + 1 ), 0, nNumRanges - 1, bucket + 1 ) );
}
else
{
// Use even distribution
e.m_min_lum = float( bucket ) / float( nNumRanges - 1 );
e.m_max_lum = float( bucket + 1 ) / float( nNumRanges - 1 );
// Use a distribution with slightly more bins in the low range
e.m_min_lum = e.m_min_lum > 0.0f ? powf( e.m_min_lum, 1.5f ) : e.m_min_lum;
e.m_max_lum = e.m_max_lum > 0.0f ? powf( e.m_max_lum, 1.5f ) : e.m_max_lum;
}
}
else
{
// The last bucket is used as a test to determine the return range for occlusion
// queries to use as a scale factor. some boards (nvidia) have their occlusion
// query return values larger when using AA.
e.m_min_lum = 0;
e.m_max_lum = 100000.0;
}
//Warning( "Bucket %d: min/max %f / %f ", bucket, e.m_min_lum, e.m_max_lum );
}
}
void CLuminanceHistogramSystem::DisplayHistogram( void )
{
bool bDrawTextThisFrame = true;
if ( IsX360() )
{
static float s_flLastTimeUpdate = 0.0f;
if ( int( gpGlobals->curtime ) - int( s_flLastTimeUpdate ) >= 2 )
{
s_flLastTimeUpdate = gpGlobals->curtime;
bDrawTextThisFrame = true;
}
else
{
bDrawTextThisFrame = false;
}
}
CMatRenderContextPtr pRenderContext( materials );
pRenderContext->PushRenderTargetAndViewport();
int nNumRanges = N_LUMINANCE_RANGES-1;
if ( mat_tonemap_algorithm.GetInt() == 1 )
nNumRanges = N_LUMINANCE_RANGES_NEW-1;
int nMaxValidPixels = 0;
int nTotalValidPixels = 0;
int nTotalGraphPixelsWide = 0;
for ( int l=0; l<nNumRanges; l++ )
{
CHistogram_entry_t &e = CurHistogram[l];
if ( e.ContainsValidData() )
{
nTotalValidPixels += e.m_npixels_in_range;
if ( e.m_npixels_in_range > nMaxValidPixels )
{
nMaxValidPixels = e.m_npixels_in_range;
}
}
int width = MAX( 1, 500 * ( e.m_max_lum - e.m_min_lum ) );
nTotalGraphPixelsWide += width + 2;
}
int xl, yl, dest_width, dest_height;
pRenderContext->GetViewport( xl, yl, dest_width, dest_height );
if ( bDrawTextThisFrame == true )
{
engine->Con_NPrintf( 17, "(All values in linear space)" );
engine->Con_NPrintf( 21, "AvgLum @ %4.2f%% mat_tonemap_min_avglum = %4.2f%% Using %d pixels of %d pixels on screen (%3d%%)",
MAX( 0.0f, FindLocationOfPercentBrightPixels( 50.0f ) ) * 100.0f, mat_tonemap_min_avglum.GetFloat(),
nTotalValidPixels, ( dest_width * dest_height ), int( float( nTotalValidPixels ) * 100.0f / float( dest_width * dest_height ) ) );
engine->Con_NPrintf( 23, "BloomScale = %4.2f mat_hdr_manual_tonemap_rate = %4.2f mat_accelerate_adjust_exposure_down = %4.2f",
GetCurrentBloomScale(), mat_hdr_manual_tonemap_rate.GetFloat(), mat_accelerate_adjust_exposure_down.GetFloat() );
}
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm only
{
float vTotalPixelsAndHigher[N_LUMINANCE_RANGES];
for ( int i=0; i<nNumRanges; i++ )
{
vTotalPixelsAndHigher[i] = CurHistogram[nNumRanges-1-i].m_npixels_in_range;
if ( i > 0 )
{
vTotalPixelsAndHigher[i] += vTotalPixelsAndHigher[i-1];
}
}
/* // This code works when N_LUMINANCE_RANGES_NEW = 11
if ( bDrawTextThisFrame == true )
{
engine->Con_NPrintf( 17, "%04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f ",
100.0f * float( vTotalPixelsAndHigher[9] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[8] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[7] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[6] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[5] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[4] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[3] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[2] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[1] ) / float( nTotalValidPixels ),
100.0f * float( vTotalPixelsAndHigher[0] ) / float( nTotalValidPixels ) );
engine->Con_NPrintf( 15, "%04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f %04.2f ",
100.0f * float( CurHistogram[nNumRanges-1-9].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-8].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-7].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-6].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-5].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-4].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-3].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-2].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-1].m_npixels_in_range ) / float( nTotalValidPixels ),
100.0f * float( CurHistogram[nNumRanges-1-0].m_npixels_in_range ) / float( nTotalValidPixels ) );
}
//*/
}
else
{
if ( bDrawTextThisFrame == true )
{
engine->Con_NPrintf( 17, "" );
engine->Con_NPrintf( 15, "" );
}
}
int xpStart = dest_width - nTotalGraphPixelsWide - 10;
if ( IsX360() )
{
xpStart -= 50;
}
int xp = xpStart;
for ( int l=0; l<nNumRanges; l++ )
{
int np = 0;
CHistogram_entry_t &e = CurHistogram[l];
if ( e.ContainsValidData() )
np += e.m_npixels_in_range;
int width = MAX( 1, 500 * ( e.m_max_lum - e.m_min_lum ) );
//Warning( "Bucket %d: min/max %f / %f. m_npixels_in_range=%d m_npixels=%d\n", l, e.m_min_lum, e.m_max_lum, e.m_npixels_in_range, e.m_npixels );
if ( np )
{
int height = MAX( 1, MIN( HISTOGRAM_BAR_SIZE, ( (float)np / (float)nMaxValidPixels ) * HISTOGRAM_BAR_SIZE ) );
pRenderContext->ClearColor3ub( 255, 0, 0 );
pRenderContext->Viewport( xp, 4 + HISTOGRAM_BAR_SIZE - height, width, height );
pRenderContext->ClearBuffers( true, true );
}
else
{
int height = 1;
pRenderContext->ClearColor3ub( 0, 0, 255 );
pRenderContext->Viewport( xp, 4 + HISTOGRAM_BAR_SIZE - height, width, height );
pRenderContext->ClearBuffers( true, true );
}
xp += width + 2;
}
if ( mat_tonemap_algorithm.GetInt() == 1 ) // New algorithm only
{
float flYellowTargetPixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * mat_tonemap_percent_target.GetFloat() / 100.0f ) );
float flYellowAveragePixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * mat_tonemap_min_avglum.GetFloat() / 100.0f ) );
float flTargetPixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetFloat() ) ) );
float flAveragePixelStart = ( xpStart + ( float( nTotalGraphPixelsWide ) * FindLocationOfPercentBrightPixels( 50.0f ) ) );
// Draw target yellow border bar
int height = HISTOGRAM_BAR_SIZE;
// Green is current percent target location
pRenderContext->Viewport( flYellowTargetPixelStart, 4 + HISTOGRAM_BAR_SIZE - height, 4, height );
pRenderContext->ClearColor3ub( 200, 200, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flTargetPixelStart, 4 + HISTOGRAM_BAR_SIZE - height, 4, height );
pRenderContext->ClearColor3ub( 0, 255, 0 );
pRenderContext->ClearBuffers( true, true );
// Blue is average luminance location
pRenderContext->Viewport( flYellowAveragePixelStart, 4 + HISTOGRAM_BAR_SIZE - height, 4, height );
pRenderContext->ClearColor3ub( 200, 200, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flAveragePixelStart, 4 + HISTOGRAM_BAR_SIZE - height, 4, height );
pRenderContext->ClearColor3ub( 0, 200, 200 );
pRenderContext->ClearBuffers( true, true );
}
// Show actual tonemap value
if ( 1 )
{
float flAutoExposureMin;
float flAutoExposureMax;
GetExposureRange( &flAutoExposureMin, &flAutoExposureMax );
float flBarWidth = 600.0f;
float flBarStart = dest_width - flBarWidth - 10.0f;
if ( IsX360() )
{
flBarStart -= 50;
}
pRenderContext->Viewport( flBarStart, 4 + HISTOGRAM_BAR_SIZE - 4 + 75, flBarWidth, 4 );
pRenderContext->ClearColor3ub( 200, 200, 200 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flBarStart, 4 + HISTOGRAM_BAR_SIZE - 4 + 75 + 1, flBarWidth, 2 );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->Viewport( flBarStart + ( flBarWidth * ( ( pRenderContext->GetToneMappingScaleLinear().x - flAutoExposureMin ) / ( flAutoExposureMax - flAutoExposureMin ) ) ),
4 + HISTOGRAM_BAR_SIZE - 4 + 75 - 6, 4, 16 );
pRenderContext->ClearColor3ub( 255, 0, 0 );
pRenderContext->ClearBuffers( true, true );
if ( bDrawTextThisFrame == true )
{
if ( IsX360() )
engine->Con_NPrintf( 26, "Min: %.2f Max: %.2f", flAutoExposureMin, flAutoExposureMax );
else
engine->Con_NPrintf( 26, "%.2f %.2f %.2f", flAutoExposureMin, ( flAutoExposureMax + flAutoExposureMin ) / 2.0f, flAutoExposureMax );
}
}
// Last bar doesn't clear properly so draw an extra pixel
pRenderContext->Viewport( 0, 0, 1, 1 );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
pRenderContext->PopRenderTargetAndViewport();
}
static CLuminanceHistogramSystem g_HDR_HistogramSystem;
static float s_MovingAverageToneMapScale[10] = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
static int s_nInAverage = 0;
void ResetToneMapping(float value)
{
CMatRenderContextPtr pRenderContext( materials );
s_nInAverage = 0;
pRenderContext->ResetToneMappingScale(value);
}
static ConVar mat_force_tonemap_scale( "mat_force_tonemap_scale", "0.0", FCVAR_CHEAT );
static void SetToneMapScale(IMatRenderContext *pRenderContext, float newvalue, float minvalue, float maxvalue)
{
Assert( IsFinite( newvalue ) );
if( !IsFinite( newvalue ) )
return;
float flForcedTonemapScale = mat_force_tonemap_scale.GetFloat();
if( mat_fullbright.GetInt() == 1 )
{
flForcedTonemapScale = 1.0f;
}
if( flForcedTonemapScale > 0.0f )
{
mat_hdr_tonemapscale.SetValue( flForcedTonemapScale );
pRenderContext->ResetToneMappingScale( flForcedTonemapScale );
return;
}
mat_hdr_tonemapscale.SetValue( newvalue );
pRenderContext->SetGoalToneMappingScale( newvalue );
if ( s_nInAverage < ARRAYSIZE( s_MovingAverageToneMapScale ))
{
s_MovingAverageToneMapScale[s_nInAverage ++]= newvalue;
}
else
{
// scroll, losing oldest
for( int i = 0;i < ARRAYSIZE( s_MovingAverageToneMapScale ) - 1;i ++ )
s_MovingAverageToneMapScale[i]= s_MovingAverageToneMapScale[i + 1];
s_MovingAverageToneMapScale[ARRAYSIZE( s_MovingAverageToneMapScale ) - 1]= newvalue;
}
// now, use the average of the last tonemap calculations as our goal scale
if ( s_nInAverage == ARRAYSIZE( s_MovingAverageToneMapScale )) // got full buffer yet?
{
float avg = 0.;
float sumweights = 0;
int sample_pt = ARRAYSIZE( s_MovingAverageToneMapScale ) / 2;
for( int i = 0;i < ARRAYSIZE( s_MovingAverageToneMapScale );i ++ )
{
float weight = abs( i - sample_pt ) * ( 1.0 / ( ARRAYSIZE( s_MovingAverageToneMapScale ) / 2 ));
sumweights += weight;
avg += weight * s_MovingAverageToneMapScale[i];
}
avg *= ( 1.0 / sumweights );
avg = MIN( maxvalue, MAX( minvalue, avg ));
pRenderContext->SetGoalToneMappingScale( avg );
mat_hdr_tonemapscale.SetValue( avg );
}
}
//=====================================================================================================================
// Engine_Post material proxy ============================================================================================
//=====================================================================================================================
static ConVar mat_software_aa_strength( "mat_software_aa_strength", "-1.0", FCVAR_ARCHIVE, "Software AA - perform a software anti-aliasing post-process (an alternative/supplement to MSAA). This value sets the strength of the effect: (0.0 - off), (1.0 - full)" );
static ConVar mat_software_aa_quality( "mat_software_aa_quality", "0", FCVAR_ARCHIVE, "Software AA quality mode: (0 - 5-tap filter), (1 - 9-tap filter)" );
static ConVar mat_software_aa_edge_threshold( "mat_software_aa_edge_threshold", "1.0", FCVAR_ARCHIVE, "Software AA - adjusts the sensitivity of the software AA shader's edge detection (default 1.0 - a lower value will soften more edges, a higher value will soften fewer)" );
static ConVar mat_software_aa_blur_one_pixel_lines( "mat_software_aa_blur_one_pixel_lines", "0.5", FCVAR_ARCHIVE, "How much software AA should blur one-pixel thick lines: (0.0 - none), (1.0 - lots)" );
static ConVar mat_software_aa_tap_offset( "mat_software_aa_tap_offset", "1.0", FCVAR_ARCHIVE, "Software AA - adjusts the displacement of the taps used by the software AA shader (default 1.0 - a lower value will make the image sharper, higher will make it blurrier)" );
static ConVar mat_software_aa_debug( "mat_software_aa_debug", "0", FCVAR_NONE, "Software AA debug mode: (0 - off), (1 - show number of 'unlike' samples: 0->black, 1->red, 2->green, 3->blue), (2 - show anti-alias blend strength), (3 - show averaged 'unlike' colour)" );
static ConVar mat_software_aa_strength_vgui( "mat_software_aa_strength_vgui", "-1.0", FCVAR_ARCHIVE, "Same as mat_software_aa_strength, but forced to this value when called by the post vgui AA pass." );
class CEnginePostMaterialProxy : public CEntityMaterialProxy
{
public:
CEnginePostMaterialProxy();
virtual ~CEnginePostMaterialProxy();
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( C_BaseEntity *pEntity );
virtual IMaterial *GetMaterial();
private:
IMaterialVar *m_pMaterialParam_AAValues;
IMaterialVar *m_pMaterialParam_AAValues2;
IMaterialVar *m_pMaterialParam_BloomEnable;
IMaterialVar *m_pMaterialParam_BloomUVTransform;
IMaterialVar *m_pMaterialParam_ColCorrectEnable;
IMaterialVar *m_pMaterialParam_ColCorrectNumLookups;
IMaterialVar *m_pMaterialParam_ColCorrectDefaultWeight;
IMaterialVar *m_pMaterialParam_ColCorrectLookupWeights;
public:
static IMaterial * SetupEnginePostMaterial( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, const Vector2D & destTexSize,
bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength );
static void SetupEnginePostMaterialAA( bool bPerformSoftwareAA, float flAAStrength );
static void SetupEnginePostMaterialTextureTransform( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, Vector2D destTexSize );
private:
static float s_vBloomAAValues[4];
static float s_vBloomAAValues2[4];
static float s_vBloomUVTransform[4];
static int s_PostBloomEnable;
};
float CEnginePostMaterialProxy::s_vBloomAAValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float CEnginePostMaterialProxy::s_vBloomAAValues2[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
float CEnginePostMaterialProxy::s_vBloomUVTransform[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
int CEnginePostMaterialProxy::s_PostBloomEnable = 1;
CEnginePostMaterialProxy::CEnginePostMaterialProxy()
{
m_pMaterialParam_AAValues = NULL;
m_pMaterialParam_AAValues2 = NULL;
m_pMaterialParam_BloomUVTransform = NULL;
m_pMaterialParam_BloomEnable = NULL;
m_pMaterialParam_ColCorrectEnable = NULL;
m_pMaterialParam_ColCorrectNumLookups = NULL;
m_pMaterialParam_ColCorrectDefaultWeight = NULL;
m_pMaterialParam_ColCorrectLookupWeights = NULL;
}
CEnginePostMaterialProxy::~CEnginePostMaterialProxy()
{
// Do nothing
}
bool CEnginePostMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool bFoundVar = false;
m_pMaterialParam_AAValues = pMaterial->FindVar( "$AAInternal1", &bFoundVar, false );
m_pMaterialParam_AAValues2 = pMaterial->FindVar( "$AAInternal3", &bFoundVar, false );
m_pMaterialParam_BloomUVTransform = pMaterial->FindVar( "$AAInternal2", &bFoundVar, false );
m_pMaterialParam_BloomEnable = pMaterial->FindVar( "$bloomEnable", &bFoundVar, false );
m_pMaterialParam_ColCorrectEnable = pMaterial->FindVar( "$colCorrectEnable", &bFoundVar, false );
m_pMaterialParam_ColCorrectNumLookups = pMaterial->FindVar( "$colCorrect_NumLookups", &bFoundVar, false );
m_pMaterialParam_ColCorrectDefaultWeight = pMaterial->FindVar( "$colCorrect_DefaultWeight", &bFoundVar, false );
m_pMaterialParam_ColCorrectLookupWeights = pMaterial->FindVar( "$colCorrect_LookupWeights", &bFoundVar, false );
return true;
}
void CEnginePostMaterialProxy::OnBind( C_BaseEntity *pEnt )
{
if ( m_pMaterialParam_AAValues )
m_pMaterialParam_AAValues->SetVecValue( s_vBloomAAValues, 4 );
if ( m_pMaterialParam_AAValues2 )
m_pMaterialParam_AAValues2->SetVecValue( s_vBloomAAValues2, 4 );
if ( m_pMaterialParam_BloomUVTransform )
m_pMaterialParam_BloomUVTransform->SetVecValue( s_vBloomUVTransform, 4 );
if ( m_pMaterialParam_BloomEnable )
m_pMaterialParam_BloomEnable->SetIntValue( s_PostBloomEnable );
}
IMaterial *CEnginePostMaterialProxy::GetMaterial()
{
if ( m_pMaterialParam_AAValues == NULL)
return NULL;
return m_pMaterialParam_AAValues->GetOwningMaterial();
}
void CEnginePostMaterialProxy::SetupEnginePostMaterialAA( bool bPerformSoftwareAA, float flAAStrength )
{
if ( bPerformSoftwareAA )
{
// Pass ConVars to the material by proxy
// - the strength of the AA effect (from 0 to 1)
// - how much to allow 1-pixel lines to be blurred (from 0 to 1)
// - pick one of the two quality modes (5-tap or 9-tap filter)
// - optionally enable one of several debug modes (via dynamic combos)
// NOTE: this order matches pixel shader constants in Engine_Post_ps2x.fxc
s_vBloomAAValues[0] = flAAStrength;
s_vBloomAAValues[1] = 1.0f - mat_software_aa_blur_one_pixel_lines.GetFloat();
s_vBloomAAValues[2] = mat_software_aa_quality.GetInt();
s_vBloomAAValues[3] = mat_software_aa_debug.GetInt();
s_vBloomAAValues2[0] = mat_software_aa_edge_threshold.GetFloat();
s_vBloomAAValues2[1] = mat_software_aa_tap_offset.GetFloat();
//s_vBloomAAValues2[2] = unused;
//s_vBloomAAValues2[3] = unused;
}
else
{
// Zero-strength AA is interpreted as "AA disabled"
s_vBloomAAValues[0] = 0.0f;
}
}
void CEnginePostMaterialProxy::SetupEnginePostMaterialTextureTransform( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, Vector2D fbSize )
{
// Engine_Post uses a UV transform (from (quarter-res) bloom texture coords ('1')
// to (full-res) framebuffer texture coords ('2')).
//
// We compute the UV transform as an offset and a scale, using the texture coordinates
// of the top-left corner of the screen to compute the offset and the coordinate
// change from the top-left to the bottom-right of the quad to compute the scale.
// Take texel coordinates (start = top-left, end = bottom-right):
Vector2D texelStart1 = Vector2D( fullViewportBloomUVs.x, fullViewportBloomUVs.y );
Vector2D texelStart2 = Vector2D( fullViewportFBUVs.x, fullViewportFBUVs.y );
Vector2D texelEnd1 = Vector2D( fullViewportBloomUVs.z, fullViewportBloomUVs.w );
Vector2D texelEnd2 = Vector2D( fullViewportFBUVs.z, fullViewportFBUVs.w );
// ...and transform to UV coordinates:
Vector2D texRes1 = fbSize / 4;
Vector2D texRes2 = fbSize;
Vector2D uvStart1 = ( texelStart1 + Vector2D(0.5,0.5) ) / texRes1;
Vector2D uvStart2 = ( texelStart2 + Vector2D(0.5,0.5) ) / texRes2;
Vector2D dUV1 = ( texelEnd1 - texelStart1 ) / texRes1;
Vector2D dUV2 = ( texelEnd2 - texelStart2 ) / texRes2;
// We scale about the rect's top-left pixel centre (not the origin) in UV-space:
// uv' = ((uv - uvStart1)*uvScale + uvStart1) + uvOffset
// = uvScale*uv + uvOffset + uvStart1*(1 - uvScale)
Vector2D uvOffset = uvStart2 - uvStart1;
Vector2D uvScale = dUV2 / dUV1;
uvOffset = uvOffset + uvStart1*(Vector2D(1,1) - uvScale);
s_vBloomUVTransform[0] = uvOffset.x;
s_vBloomUVTransform[1] = uvOffset.y;
s_vBloomUVTransform[2] = uvScale.x;
s_vBloomUVTransform[3] = uvScale.y;
}
IMaterial * CEnginePostMaterialProxy::SetupEnginePostMaterial( const Vector4D & fullViewportBloomUVs, const Vector4D & fullViewportFBUVs, const Vector2D & destTexSize,
bool bPerformSoftwareAA, bool bPerformBloom, bool bPerformColCorrect, float flAAStrength )
{
// Shouldn't get here if none of the effects are enabled
Assert( bPerformSoftwareAA || bPerformBloom || bPerformColCorrect );
s_PostBloomEnable = bPerformBloom ? 1 : 0;
SetupEnginePostMaterialAA( bPerformSoftwareAA, flAAStrength );
if ( bPerformSoftwareAA || bPerformColCorrect )
{
SetupEnginePostMaterialTextureTransform( fullViewportBloomUVs, fullViewportFBUVs, destTexSize );
return materials->FindMaterial( "dev/engine_post", TEXTURE_GROUP_OTHER, true);
}
else
{
// Just use the old bloomadd material (which uses additive blending, unlike engine_post)
// NOTE: this path is what gets used for DX8 (which cannot enable AA or col-correction)
return materials->FindMaterial( "dev/bloomadd", TEXTURE_GROUP_OTHER, true);
}
}
EXPOSE_INTERFACE( CEnginePostMaterialProxy, IMaterialProxy, "engine_post" IMATERIAL_PROXY_INTERFACE_VERSION );
static void DrawBloomDebugBoxes( IMatRenderContext *pRenderContext )
{
// draw inset rects which should have a centered bloom
pRenderContext->SetRenderTarget(NULL);
int dest_width, dest_height;
pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
// full screen clear
pRenderContext->Viewport( 0, 0, dest_width, dest_height );
pRenderContext->ClearColor3ub( 0, 0, 0 );
pRenderContext->ClearBuffers( true, true );
// inset for screensafe
int inset = 64;
int size = 32;
// centerish, translating
static int wx = 0;
wx = ( wx + 1 ) & 63;
pRenderContext->Viewport( dest_width / 2 + wx, dest_height / 2, size, size );
pRenderContext->ClearColor3ub( 255, 255, 255 );
pRenderContext->ClearBuffers( true, true );
// upper left
pRenderContext->Viewport( inset, inset, size, size );
pRenderContext->ClearBuffers( true, true );
// upper right
pRenderContext->Viewport( dest_width - inset - size, inset, size, size );
pRenderContext->ClearBuffers( true, true );
// lower right
pRenderContext->Viewport( dest_width - inset - size, dest_height - inset - size, size, size );
pRenderContext->ClearBuffers( true, true );
// lower left
pRenderContext->Viewport( inset, dest_height - inset - size, size, size );
pRenderContext->ClearBuffers( true, true );
// restore
pRenderContext->Viewport( 0, 0, dest_width, dest_height );
}
static float GetBloomAmount( void )
{
// return bloom amount ( 0.0 if disabled or otherwise turned off )
if ( engine->GetDXSupportLevel() < 80 )
return 0.0;
HDRType_t hdrType = g_pMaterialSystemHardwareConfig->GetHDRType();
bool bBloomEnabled = (mat_hdr_level.GetInt() >= 1);
if ( !engine->MapHasHDRLighting() )
bBloomEnabled = false;
if ( mat_force_bloom.GetInt() )
bBloomEnabled = true;
if ( mat_disable_bloom.GetInt() )
bBloomEnabled = false;
if ( building_cubemaps.GetBool() )
bBloomEnabled = false;
if ( mat_fullbright.GetInt() == 1 )
{
bBloomEnabled = false;
}
if( !g_pMaterialSystemHardwareConfig->CanDoSRGBReadFromRTs() && g_pMaterialSystemHardwareConfig->FakeSRGBWrite() )
{
bBloomEnabled = false;
}
float flBloomAmount=0.0;
if (bBloomEnabled)
{
static float currentBloomAmount = 1.0f;
float rate = mat_bloomamount_rate.GetFloat();
// Use the appropriate bloom scale settings. Mapmakers's overrides the convar settings.
currentBloomAmount = GetCurrentBloomScale() * rate + ( 1.0f - rate ) * currentBloomAmount;
flBloomAmount = currentBloomAmount;
}
if ( hdrType == HDR_TYPE_NONE )
{
flBloomAmount *= mat_non_hdr_bloom_scalefactor.GetFloat();
}
flBloomAmount *= mat_bloom_scalefactor_scalar.GetFloat();
return flBloomAmount;
}
// Control for dumping render targets to files for debugging
static ConVar mat_dump_rts( "mat_dump_rts", "0" );
static int s_nRTIndex = 0;
bool g_bDumpRenderTargets = false;
// Dump a rendertarget to a TGA. Useful for looking at intermediate render target results.
void DumpTGAofRenderTarget( const int width, const int height, const char *pFilename )
{
// Ensure that mat_queue_mode is zero
static ConVarRef mat_queue_mode( "mat_queue_mode" );
if ( mat_queue_mode.GetInt() != 0 )
{
DevMsg( "Error: mat_queue_mode must be 0 to dump debug rendertargets\n" );
mat_dump_rts.SetValue( 0 ); // Just report this error once and stop trying to dump images
return;
}
CMatRenderContextPtr pRenderContext( materials );
// Get the data from the render target and save to disk bitmap bits
unsigned char *pImage = ( unsigned char * )malloc( width * 4 * height );
// Get Bits from the material system
pRenderContext->ReadPixels( 0, 0, width, height, pImage, IMAGE_FORMAT_RGBA8888 );
// allocate a buffer to write the tga into
int iMaxTGASize = 1024 + (width * height * 4);
void *pTGA = malloc( iMaxTGASize );
CUtlBuffer buffer( pTGA, iMaxTGASize );
if( !TGAWriter::WriteToBuffer( pImage, buffer, width, height, IMAGE_FORMAT_RGBA8888, IMAGE_FORMAT_RGBA8888 ) )
{
Error( "Couldn't write bitmap data snapshot.\n" );
}
free( pImage );
// async write to disk (this will take ownership of the memory)
char szPathedFileName[_MAX_PATH];
Q_snprintf( szPathedFileName, sizeof(szPathedFileName), "//MOD/%d_%s_%s.tga", s_nRTIndex++, pFilename, IsOSX() ? "OSX" : "PC" );
FileHandle_t fileTGA = filesystem->Open( szPathedFileName, "wb" );
filesystem->Write( buffer.Base(), buffer.TellPut(), fileTGA );
filesystem->Close( fileTGA );
free( pTGA );
}
static bool s_bScreenEffectTextureIsUpdated = false;
static void Generate8BitBloomTexture( IMatRenderContext *pRenderContext, float flBloomScale,
int x, int y, int w, int h )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
pRenderContext->PushRenderTargetAndViewport();
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight(); //,dest_height;
// Counter-Strike: Source uses a different downsample algorithm than other games
#ifdef CSTRIKE_DLL
IMaterial *downsample_mat = materials->FindMaterial( "dev/downsample_non_hdr_cstrike", TEXTURE_GROUP_OTHER, true);
#else
IMaterial *downsample_mat = materials->FindMaterial( "dev/downsample_non_hdr", TEXTURE_GROUP_OTHER, true);
#endif
IMaterial *xblur_mat = materials->FindMaterial( "dev/blurfilterx_nohdr", TEXTURE_GROUP_OTHER, true );
IMaterial *yblur_mat = materials->FindMaterial( "dev/blurfiltery_nohdr", TEXTURE_GROUP_OTHER, true );
ITexture *dest_rt0 = materials->FindTexture( "_rt_SmallFB0", TEXTURE_GROUP_RENDER_TARGET );
ITexture *dest_rt1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET );
// *Everything* in here relies on the small RTs being exactly 1/4 the full FB res
Assert( dest_rt0->GetActualWidth() == pSrc->GetActualWidth() / 4 );
Assert( dest_rt0->GetActualHeight() == pSrc->GetActualHeight() / 4 );
Assert( dest_rt1->GetActualWidth() == pSrc->GetActualWidth() / 4 );
Assert( dest_rt1->GetActualHeight() == pSrc->GetActualHeight() / 4 );
// Downsample fb to rt0
SetRenderTargetAndViewPort( dest_rt0 );
// note the -2's below. Thats because we are downsampling on each axis and the shader
// accesses pixels on both sides of the source coord
pRenderContext->DrawScreenSpaceRectangle( downsample_mat, 0, 0, nSrcWidth/4, nSrcHeight/4,
0, 0, nSrcWidth-2, nSrcHeight-2,
nSrcWidth, nSrcHeight );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt0, 0, NULL, NULL );
}
else if ( g_bDumpRenderTargets )
{
DumpTGAofRenderTarget( nSrcWidth/4, nSrcHeight/4, "QuarterSizeFB" );
}
// Gaussian blur x rt0 to rt1
SetRenderTargetAndViewPort( dest_rt1 );
pRenderContext->DrawScreenSpaceRectangle( xblur_mat, 0, 0, nSrcWidth/4, nSrcHeight/4,
0, 0, nSrcWidth/4-1, nSrcHeight/4-1,
nSrcWidth/4, nSrcHeight/4 );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt1, 0, NULL, NULL );
}
else if ( g_bDumpRenderTargets )
{
DumpTGAofRenderTarget( nSrcWidth/4, nSrcHeight/4, "BlurX" );
}
// Gaussian blur y rt1 to rt0
SetRenderTargetAndViewPort( dest_rt0 );
IMaterialVar *pBloomAmountVar = yblur_mat->FindVar( "$bloomamount", NULL );
pBloomAmountVar->SetFloatValue( flBloomScale );
pRenderContext->DrawScreenSpaceRectangle( yblur_mat, 0, 0, nSrcWidth / 4, nSrcHeight / 4,
0, 0, nSrcWidth / 4 - 1, nSrcHeight / 4 - 1,
nSrcWidth / 4, nSrcHeight / 4 );
if ( IsX360() )
{
pRenderContext->CopyRenderTargetToTextureEx( dest_rt0, 0, NULL, NULL );
}
else if ( g_bDumpRenderTargets )
{
DumpTGAofRenderTarget( nSrcWidth/4, nSrcHeight/4, "BlurYAndBloom" );
}
pRenderContext->PopRenderTargetAndViewport();
}
static void DoPreBloomTonemapping( IMatRenderContext *pRenderContext, int nX, int nY, int nWidth, int nHeight, float flAutoExposureMin, float flAutoExposureMax )
{
// Update HDR histogram before bloom
if ( mat_dynamic_tonemapping.GetInt() || mat_show_histogram.GetInt() )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
if ( s_bScreenEffectTextureIsUpdated == false )
{
// FIXME: nX/nY/nWidth/nHeight are used here, but the equivalent parameters are ignored in Generate8BitBloomTexture
UpdateScreenEffectTexture( 0, nX, nY, nWidth, nHeight, true );
s_bScreenEffectTextureIsUpdated = true;
}
g_HDR_HistogramSystem.Update();
if ( mat_dynamic_tonemapping.GetInt() || mat_show_histogram.GetInt() )
{
float flTargetScalar = g_HDR_HistogramSystem.GetTargetTonemapScalar();
float flTargetScalarClamped = MAX( flAutoExposureMin, MIN( flAutoExposureMax, flTargetScalar ) );
flTargetScalarClamped = MAX( 0.001f, flTargetScalarClamped ); // Don't let this go to 0!
if ( mat_dynamic_tonemapping.GetInt() )
{
SetToneMapScale( pRenderContext, flTargetScalarClamped, flAutoExposureMin, flAutoExposureMax );
}
if ( mat_debug_autoexposure.GetInt() || mat_show_histogram.GetInt() )
{
bool bDrawTextThisFrame = true;
if ( IsX360() )
{
static float s_flLastTimeUpdate = 0.0f;
if ( int( gpGlobals->curtime ) - int( s_flLastTimeUpdate ) >= 2 )
{
s_flLastTimeUpdate = gpGlobals->curtime;
bDrawTextThisFrame = true;
}
else
{
bDrawTextThisFrame = false;
}
}
if ( bDrawTextThisFrame == true )
{
if ( mat_tonemap_algorithm.GetInt() == 0 )
{
engine->Con_NPrintf( 19, "(Original algorithm) Target Scalar = %4.2f Min/Max( %4.2f, %4.2f ) Final Scalar: %4.2f Actual: %4.2f",
flTargetScalar, flAutoExposureMin, flAutoExposureMax, mat_hdr_tonemapscale.GetFloat(), pRenderContext->GetToneMappingScaleLinear().x );
}
else
{
engine->Con_NPrintf( 19, "%.2f%% of pixels above %d%% target @ %4.2f%% Target Scalar = %4.2f Min/Max( %4.2f, %4.2f ) Final Scalar: %4.2f Actual: %4.2f",
mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetInt(),
( g_HDR_HistogramSystem.FindLocationOfPercentBrightPixels( mat_tonemap_percent_bright_pixels.GetFloat(), mat_tonemap_percent_target.GetFloat() ) * 100.0f ),
g_HDR_HistogramSystem.GetTargetTonemapScalar( true ), flAutoExposureMin, flAutoExposureMax, mat_hdr_tonemapscale.GetFloat(), pRenderContext->GetToneMappingScaleLinear().x );
}
}
}
}
}
}
static void DoPostBloomTonemapping( IMatRenderContext *pRenderContext, int nX, int nY, int nWidth, int nHeight, float flAutoExposureMin, float flAutoExposureMax )
{
if ( mat_show_histogram.GetInt() && ( engine->GetDXSupportLevel() >= 90 ) )
{
g_HDR_HistogramSystem.DisplayHistogram();
}
}
static void CenterScaleQuadUVs( Vector4D & quadUVs, const Vector2D & uvScale )
{
Vector2D uvMid = 0.5f*Vector2D( ( quadUVs.z + quadUVs.x ), ( quadUVs.w + quadUVs.y ) );
Vector2D uvRange= 0.5f*Vector2D( ( quadUVs.z - quadUVs.x ), ( quadUVs.w - quadUVs.y ) );
quadUVs.x = uvMid.x - uvScale.x*uvRange.x;
quadUVs.y = uvMid.y - uvScale.y*uvRange.y;
quadUVs.z = uvMid.x + uvScale.x*uvRange.x;
quadUVs.w = uvMid.y + uvScale.y*uvRange.y;
}
typedef struct SPyroSide
{
float m_vCornerPos[ 2 ][ 2 ];
float m_flIntensity;
float m_flIntensityLimit;
float m_flRate;
bool m_bHorizontal;
bool m_bIncreasing;
bool m_bAlive;
} TPyroSide;
#define MAX_PYRO_SIDES 50
#define NUM_PYRO_SEGMENTS 8
#define MIN_PYRO_SIDE_LENGTH 0.5f
#define MAX_PYRO_SIDE_LENGTH 0.90f
#define MIN_PYRO_SIDE_WIDTH 0.25f
#define MAX_PYRO_SIDE_WIDTH 0.95f
static TPyroSide PyroSides[ MAX_PYRO_SIDES ];
ConVar pyro_vignette( "pyro_vignette", "2", FCVAR_ARCHIVE );
ConVar pyro_vignette_distortion( "pyro_vignette_distortion", "1", FCVAR_ARCHIVE );
ConVar pyro_min_intensity( "pyro_min_intensity", "0.1", FCVAR_ARCHIVE );
ConVar pyro_max_intensity( "pyro_max_intensity", "0.35", FCVAR_ARCHIVE );
ConVar pyro_min_rate( "pyro_min_rate", "0.05", FCVAR_ARCHIVE );
ConVar pyro_max_rate( "pyro_max_rate", "0.2", FCVAR_ARCHIVE );
ConVar pyro_min_side_length( "pyro_min_side_length", "0.3", FCVAR_ARCHIVE );
ConVar pyro_max_side_length( "pyro_max_side_length", "0.55", FCVAR_ARCHIVE );
ConVar pyro_min_side_width( "pyro_min_side_width", "0.65", FCVAR_ARCHIVE );
ConVar pyro_max_side_width( "pyro_max_side_width", "0.95", FCVAR_ARCHIVE );
static void CreatePyroSide( int nSide, Vector2D &vMaxSize )
{
int nFound = 0;
for( ; nFound < MAX_PYRO_SIDES; nFound++ )
{
if ( !PyroSides[ nFound ].m_bAlive )
{
break;
}
}
if ( nFound >= MAX_PYRO_SIDES )
{
return;
}
TPyroSide *pSide = &PyroSides[ nFound ];
pSide->m_flIntensity = 0.0f;
pSide->m_flIntensityLimit = RandomFloat( pyro_min_intensity.GetFloat(), pyro_max_intensity.GetFloat() );
pSide->m_flRate = RandomFloat( pyro_min_rate.GetFloat(), pyro_max_rate.GetFloat() );
pSide->m_bIncreasing = true;
pSide->m_bHorizontal = ( ( nSide >> 1 ) & 1 ) == 0;
pSide->m_bAlive = true;
// float flWidth = RandomFloat( MIN_PYRO_SIDE_WIDTH, MAX_PYRO_SIDE_WIDTH ) * 2.0f;
// float flLength = RandomFloat( MIN_PYRO_SIDE_LENGTH, MAX_PYRO_SIDE_LENGTH );
float flWidth = RandomFloat( pyro_min_side_width.GetFloat(), pyro_max_side_width.GetFloat() ) * 2.0f;
float flLength = RandomFloat( pyro_min_side_length.GetFloat(), pyro_max_side_length.GetFloat() );
switch( nSide )
{
case 0:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = -1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = 1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = -1.0f + flWidth;
pSide->m_vCornerPos[ 1 ][ 1 ] = 1.0f - ( flLength * vMaxSize.y );
}
break;
case 1:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = 1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = 1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = 1.0f - flWidth;
pSide->m_vCornerPos[ 1 ][ 1 ] = 1.0f - ( flLength * vMaxSize.y );
}
break;
case 2:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = 1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = 1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = 1.0f - ( flLength * vMaxSize.x );
pSide->m_vCornerPos[ 1 ][ 1 ] = 1.0f - flWidth;
}
break;
case 3:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = 1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = -1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = 1.0f - ( flLength * vMaxSize.x );
pSide->m_vCornerPos[ 1 ][ 1 ] = -1.0f + flWidth;
}
break;
case 4:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = 1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = -1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = 1.0f - flWidth;
pSide->m_vCornerPos[ 1 ][ 1 ] = -1.0f + ( flLength * vMaxSize.y );
}
break;
case 5:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = -1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = -1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = -1.0f + flWidth;
pSide->m_vCornerPos[ 1 ][ 1 ] = -1.0f + ( flLength * vMaxSize.y );
}
break;
case 6:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = -1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = -1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = -1.0f + ( flLength * vMaxSize.x );
pSide->m_vCornerPos[ 1 ][ 1 ] = -1.0f + flWidth;
}
break;
case 7:
{
pSide->m_vCornerPos[ 0 ][ 0 ] = -1.0f;
pSide->m_vCornerPos[ 0 ][ 1 ] = 1.0f;
pSide->m_vCornerPos[ 1 ][ 0 ] = -1.0f + ( flLength * vMaxSize.x );
pSide->m_vCornerPos[ 1 ][ 1 ] = 1.0f - flWidth;
}
break;
}
}
static float PryoVignetteSTHorizontal[ 6 ][ 2 ] =
{
{ 0.0f, 0.0f },
{ 0.0f, 1.0f },
{ 1.0f, 1.0f },
{ 1.0f, 1.0f },
{ 0.0f, 0.0f },
{ 1.0f, 0.0f }
};
static float PryoVignetteSTVertical[ 6 ][ 2 ] =
{
{ 0.0f, 0.0f },
{ 1.0f, 0.0f },
{ 1.0f, 1.0f },
{ 1.0f, 1.0f },
{ 0.0f, 0.0f },
{ 0.0f, 1.0f }
};
static int PryoSideIndexes[ 6 ][ 2 ] =
{
{ 0, 0 },
{ 0, 1 },
{ 1, 1 },
{ 1, 1 },
{ 0, 0 },
{ 1, 0 }
};
static void DrawPyroVignette( int nDestX, int nDestY, int nWidth, int nHeight, // Rect to draw into in screen space
float flSrcTextureX0, float flSrcTextureY0, // which texel you want to appear at destx/y
float flSrcTextureX1, float flSrcTextureY1, // which texel you want to appear at destx+width-1, desty+height-1
void *pClientRenderable )
{
static bool bInit = false;
static int nNextSide = 0;
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
IMaterial *pVignetteBorder = materials->FindMaterial( "dev/pyro_vignette_border", TEXTURE_GROUP_OTHER, true );
IMaterial *pMaterial = materials->FindMaterial( "dev/pyro_vignette", TEXTURE_GROUP_OTHER, true );
ITexture *pRenderTarget = materials->FindTexture( "_rt_ResolvedFullFrameDepth", TEXTURE_GROUP_RENDER_TARGET );
pRenderContext->PushRenderTargetAndViewport( pRenderTarget );
pRenderContext->ClearColor4ub( 0, 0, 0, 0 );
pRenderContext->ClearBuffers( true, false );
int nScreenWidth, nScreenHeight;
pRenderContext->GetRenderTargetDimensions( nScreenWidth, nScreenHeight );
pRenderContext->DrawScreenSpaceRectangle( pVignetteBorder, 0, 0, nScreenWidth, nScreenHeight, 0, 0, nScreenWidth - 1, nScreenHeight - 1, nScreenWidth, nScreenHeight, pClientRenderable );
if ( pyro_vignette.GetInt() > 1 )
{
Vector2D vMaxSize( ( float )nScreenWidth / ( float )nScreenWidth / NUM_PYRO_SEGMENTS * 2.0f, ( float )nScreenHeight / ( float )nScreenHeight / NUM_PYRO_SEGMENTS * 2.0f );
if ( !bInit )
{
for( int i = 0; i < MAX_PYRO_SIDES; i++ )
{
PyroSides[ i ].m_bAlive = false;
}
CreatePyroSide( nNextSide, vMaxSize );
nNextSide = ( nNextSide + 1 ) & 7;
bInit = true;
}
int nNumAlive = 0;
TPyroSide *pSide = &PyroSides[ 0 ];
for( int nIndex = 0; nIndex < MAX_PYRO_SIDES; nIndex++, pSide++ )
{
if ( pSide->m_bAlive )
{
if ( pSide->m_bIncreasing )
{
pSide->m_flIntensity += pSide->m_flRate * gpGlobals->frametime;
if ( pSide->m_flIntensity >= pSide->m_flIntensityLimit )
{
pSide->m_bIncreasing = false;
}
}
else
{
pSide->m_flIntensity -= pSide->m_flRate * gpGlobals->frametime;
if ( pSide->m_flIntensity <= 0.0f )
{
pSide->m_bAlive = false;
}
}
}
if ( pSide->m_bAlive )
{
nNumAlive++;
}
}
if ( nNumAlive > 0 )
{
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->Bind( pMaterial, pClientRenderable );
CMeshBuilder meshBuilder;
IMesh* pMesh = pRenderContext->GetDynamicMesh( true );
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nNumAlive * 2 );
pSide = &PyroSides[ 0 ];
for( int nIndex = 0; nIndex < MAX_PYRO_SIDES; nIndex++, pSide++ )
{
if ( pSide->m_bAlive )
{
for( int i = 0; i < 6; i++ )
{
meshBuilder.Position3f( pSide->m_vCornerPos[ PryoSideIndexes[ i ][ 0 ] ][ 0 ], pSide->m_vCornerPos[ PryoSideIndexes[ i ][ 1 ] ][ 1 ], 0.0f );
meshBuilder.Color4f( pSide->m_flIntensity, pSide->m_flIntensity, pSide->m_flIntensity, 1.0f );
meshBuilder.TexCoord2fv( 0, pSide->m_bHorizontal ? PryoVignetteSTHorizontal[ i ] : PryoVignetteSTVertical[ i ] );
meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f );
meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f );
meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f );
meshBuilder.AdvanceVertex();
}
}
}
meshBuilder.End();
pMesh->Draw();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PopMatrix();
}
if ( nNumAlive < 25 )
{
CreatePyroSide( nNextSide, vMaxSize );
nNextSide = ( nNextSide + 1 ) & 7;
}
}
pRenderContext->PopRenderTargetAndViewport();
}
static void DrawPyroPost( IMaterial *pMaterial,
int nDestX, int nDestY, int nWidth, int nHeight, // Rect to draw into in screen space
float flSrcTextureX0, float flSrcTextureY0, // which texel you want to appear at destx/y
float flSrcTextureX1, float flSrcTextureY1, // which texel you want to appear at destx+width-1, desty+height-1
int nSrcTextureWidth, int nSrcTextureHeight, // needed for fixup
void *pClientRenderable ) // Used to pass to the bind proxies
{
bool bFound = false;
IMaterialVar *pVar = pMaterial->FindVar( "$disabled", &bFound, false );
if ( bFound && pVar->GetIntValue() )
{
return;
}
CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PushMatrix();
pRenderContext->LoadIdentity();
pRenderContext->Bind( pMaterial, pClientRenderable );
int xSegments = NUM_PYRO_SEGMENTS;
int ySegments = NUM_PYRO_SEGMENTS;
CMeshBuilder meshBuilder;
IMesh* pMesh = pRenderContext->GetDynamicMesh( true );
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 4 );
int nScreenWidth, nScreenHeight;
pRenderContext->GetRenderTargetDimensions( nScreenWidth, nScreenHeight );
float flOffset = IsPosix() ? 0.0f : 0.5f;
float flLeftX = nDestX - flOffset;
float flRightX = nDestX + nWidth - flOffset;
float flTopY = nDestY - flOffset;
float flBottomY = nDestY + nHeight - flOffset;
float flSubrectWidth = flSrcTextureX1 - flSrcTextureX0;
float flSubrectHeight = flSrcTextureY1 - flSrcTextureY0;
float flTexelsPerPixelX = ( nWidth > 1 ) ? flSubrectWidth / ( nWidth - 1 ) : 0.0f;
float flTexelsPerPixelY = ( nHeight > 1 ) ? flSubrectHeight / ( nHeight - 1 ) : 0.0f;
float flLeftU = flSrcTextureX0 + 0.5f - ( 0.5f * flTexelsPerPixelX );
float flRightU = flSrcTextureX1 + 0.5f + ( 0.5f * flTexelsPerPixelX );
float flTopV = flSrcTextureY0 + 0.5f - ( 0.5f * flTexelsPerPixelY );
float flBottomV = flSrcTextureY1 + 0.5f + ( 0.5f * flTexelsPerPixelY );
float flOOTexWidth = 1.0f / nSrcTextureWidth;
float flOOTexHeight = 1.0f / nSrcTextureHeight;
flLeftU *= flOOTexWidth;
flRightU *= flOOTexWidth;
flTopV *= flOOTexHeight;
flBottomV *= flOOTexHeight;
// Get the current viewport size
int vx, vy, vw, vh;
pRenderContext->GetViewport( vx, vy, vw, vh );
// map from screen pixel coords to -1..1
flRightX = FLerp( -1, 1, 0, vw, flRightX );
flLeftX = FLerp( -1, 1, 0, vw, flLeftX );
flTopY = FLerp( 1, -1, 0, vh ,flTopY );
flBottomY = FLerp( 1, -1, 0, vh, flBottomY );
// Screen height and width of a subrect
float flWidth = (flRightX - flLeftX) / (float) xSegments;
float flHeight = (flTopY - flBottomY) / (float) ySegments;
// UV height and width of a subrect
float flUWidth = (flRightU - flLeftU) / (float) xSegments;
float flVHeight = (flBottomV - flTopV) / (float) ySegments;
// Top Bar
// Top left
meshBuilder.Position3f( flLeftX + (float) 0 * flWidth, flTopY - (float) 0 * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) 0 * flUWidth, flTopV + (float) 0 * flVHeight);
meshBuilder.AdvanceVertex();
// Top right (x+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments+1) * flWidth, flTopY - (float) 0 * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments+1) * flUWidth, flTopV + (float) 0 * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom right (x+1), (y+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments+1) * flWidth, flTopY - (float) (0+1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments+1) * flUWidth, flTopV + (float)(0+1) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom left (y+1)
meshBuilder.Position3f( flLeftX + (float) 0 * flWidth, flTopY - (float) (0+1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) 0 * flUWidth, flTopV + (float)(0+1) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom Bar
// Top left
meshBuilder.Position3f( flLeftX + (float) 0 * flWidth, flTopY - (float) ( ySegments - 1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) 0 * flUWidth, flTopV + (float) ( ySegments - 1) * flVHeight);
meshBuilder.AdvanceVertex();
// Top right (x+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments) * flWidth, flTopY - (float) ( ySegments - 1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments) * flUWidth, flTopV + (float) ( ySegments - 1 ) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom right (x+1), (y+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments) * flWidth, flTopY - (float) ( ySegments ) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments) * flUWidth, flTopV + (float)( ySegments ) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom left (y+1)
meshBuilder.Position3f( flLeftX + (float) 0 * flWidth, flTopY - (float) ( ySegments ) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) 0 * flUWidth, flTopV + (float)( ySegments ) * flVHeight);
meshBuilder.AdvanceVertex();
// Left Bar
// Top left
meshBuilder.Position3f( flLeftX + (float) 0 * flWidth, flTopY - (float) 1 * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) 0 * flUWidth, flTopV + (float) 1 * flVHeight);
meshBuilder.AdvanceVertex();
// Top right (x+1)
meshBuilder.Position3f( flLeftX + (float) (0+1) * flWidth, flTopY - (float) 1 * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (0+1) * flUWidth, flTopV + (float) 1 * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom right (x+1), (y+1)
meshBuilder.Position3f( flLeftX + (float) (0+1) * flWidth, flTopY - (float) (ySegments - 1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (0+1) * flUWidth, flTopV + (float)(ySegments - 1) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom left (y+1)
meshBuilder.Position3f( flLeftX + (float) 0 * flWidth, flTopY - (float) (ySegments - 1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) 0 * flUWidth, flTopV + (float)(ySegments - 1) * flVHeight);
meshBuilder.AdvanceVertex();
// Right Bar
// Top left
meshBuilder.Position3f( flLeftX + (float) (xSegments - 1) * flWidth, flTopY - (float) 1 * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments - 1) * flUWidth, flTopV + (float) 1 * flVHeight);
meshBuilder.AdvanceVertex();
// Top right (x+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments) * flWidth, flTopY - (float) 1 * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments) * flUWidth, flTopV + (float) 1 * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom right (x+1), (y+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments) * flWidth, flTopY - (float) (ySegments - 1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments) * flUWidth, flTopV + (float)(ySegments - 1) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom left (y+1)
meshBuilder.Position3f( flLeftX + (float) (xSegments - 1) * flWidth, flTopY - (float) (ySegments - 1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (xSegments - 1) * flUWidth, flTopV + (float)(ySegments - 1) * flVHeight);
meshBuilder.AdvanceVertex();
#if 0
for ( int x=0; x < xSegments; x++ )
{
for ( int y=0; y < ySegments; y++ )
{
if ( ( x == 1 || x == 2 ) && ( y == 1 || y == 2 ) )
{ // skip the center 4 segments
continue;
}
// Top left
meshBuilder.Position3f( flLeftX + (float) x * flWidth, flTopY - (float) y * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) x * flUWidth, flTopV + (float) y * flVHeight);
meshBuilder.AdvanceVertex();
// Top right (x+1)
meshBuilder.Position3f( flLeftX + (float) (x+1) * flWidth, flTopY - (float) y * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (x+1) * flUWidth, flTopV + (float) y * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom right (x+1), (y+1)
meshBuilder.Position3f( flLeftX + (float) (x+1) * flWidth, flTopY - (float) (y+1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) (x+1) * flUWidth, flTopV + (float)(y+1) * flVHeight);
meshBuilder.AdvanceVertex();
// Bottom left (y+1)
meshBuilder.Position3f( flLeftX + (float) x * flWidth, flTopY - (float) (y+1) * flHeight, 0.0f );
meshBuilder.TexCoord2f( 0, flLeftU + (float) x * flUWidth, flTopV + (float)(y+1) * flVHeight);
meshBuilder.AdvanceVertex();
}
}
#endif
meshBuilder.End();
pMesh->Draw();
pRenderContext->MatrixMode( MATERIAL_VIEW );
pRenderContext->PopMatrix();
pRenderContext->MatrixMode( MATERIAL_PROJECTION );
pRenderContext->PopMatrix();
}
static ConVar r_queued_post_processing( "r_queued_post_processing", "0" );
// How much to dice up the screen during post-processing on 360
// This has really marginal effects, but 4x1 does seem vaguely better for post-processing
static ConVar mat_postprocess_x( "mat_postprocess_x", "4" );
static ConVar mat_postprocess_y( "mat_postprocess_y", "1" );
void DoEnginePostProcessing( int x, int y, int w, int h, bool bFlashlightIsOn, bool bPostVGui )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
CMatRenderContextPtr pRenderContext( materials );
if ( g_bDumpRenderTargets )
{
g_bDumpRenderTargets = false; // Turn off from previous frame
}
if ( mat_dump_rts.GetBool() )
{
g_bDumpRenderTargets = true; // Dump intermediate render targets this frame
s_nRTIndex = 0; // Used for numbering the TGA files for easy browsing
mat_dump_rts.SetValue( 0 ); // We only want to capture one frame, on rising edge of this convar
DumpTGAofRenderTarget( w, h, "BackBuffer" );
}
#if defined( _X360 )
pRenderContext->PushVertexShaderGPRAllocation( 16 ); //max out pixel shader threads
#endif
if ( r_queued_post_processing.GetInt() )
{
ICallQueue *pCallQueue = pRenderContext->GetCallQueue();
if ( pCallQueue )
{
pCallQueue->QueueCall( DoEnginePostProcessing, x, y, w, h, bFlashlightIsOn, bPostVGui );
return;
}
}
float flBloomScale = GetBloomAmount();
HDRType_t hdrType = g_pMaterialSystemHardwareConfig->GetHDRType();
g_bFlashlightIsOn = bFlashlightIsOn;
// Use the appropriate autoexposure min / max settings.
// Mapmaker's overrides the convar settings.
float flAutoExposureMin;
float flAutoExposureMax;
GetExposureRange( &flAutoExposureMin, &flAutoExposureMax );
if ( mat_debug_bloom.GetInt() == 1 )
{
DrawBloomDebugBoxes( pRenderContext );
}
switch( hdrType )
{
case HDR_TYPE_NONE:
case HDR_TYPE_INTEGER:
{
s_bScreenEffectTextureIsUpdated = false;
if ( hdrType != HDR_TYPE_NONE )
{
DoPreBloomTonemapping( pRenderContext, x, y, w, h, flAutoExposureMin, flAutoExposureMax );
}
// Set software-AA on by default for 360
if ( mat_software_aa_strength.GetFloat() == -1.0f )
{
if ( IsX360() )
{
mat_software_aa_strength.SetValue( 1.0f );
if ( g_pMaterialSystem->GetCurrentConfigForVideoCard().m_VideoMode.m_Height > 480 )
{
mat_software_aa_quality.SetValue( 0 );
}
else
{
// For standard-def, we have fewer pixels so we can afford 'high quality' mode (5->9 taps/pixel)
mat_software_aa_quality.SetValue( 1 );
}
}
else
{
mat_software_aa_strength.SetValue( 0.0f );
}
}
// Same trick for setting up the vgui aa strength
if ( mat_software_aa_strength_vgui.GetFloat() == -1.0f )
{
if ( IsX360() && (g_pMaterialSystem->GetCurrentConfigForVideoCard().m_VideoMode.m_Height == 720) )
{
mat_software_aa_strength_vgui.SetValue( 2.0f );
}
else
{
mat_software_aa_strength_vgui.SetValue( 1.0f );
}
}
float flAAStrength;
// We do a second AA blur pass over the TF intro menus. use mat_software_aa_strength_vgui there instead
if ( IsX360() && bPostVGui )
{
flAAStrength = mat_software_aa_strength_vgui.GetFloat();
}
else
{
flAAStrength = mat_software_aa_strength.GetFloat();
}
// bloom, software-AA and colour-correction (applied in 1 pass, after generation of the bloom texture)
bool bPerformSoftwareAA = IsX360() && ( engine->GetDXSupportLevel() >= 90 ) && ( flAAStrength != 0.0f );
bool bPerformBloom = !bPostVGui && ( flBloomScale > 0.0f ) && ( engine->GetDXSupportLevel() >= 90 );
bool bPerformColCorrect = !bPostVGui &&
( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90) &&
( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_FLOAT ) &&
g_pColorCorrectionMgr->HasNonZeroColorCorrectionWeights() &&
mat_colorcorrection.GetInt();
bool bSplitScreenHDR = mat_show_ab_hdr.GetInt();
pRenderContext->EnableColorCorrection( bPerformColCorrect );
if ( bPerformBloom || bPerformSoftwareAA || bPerformColCorrect )
{
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "ColorCorrection" );
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight();
ITexture *dest_rt1 = materials->FindTexture( "_rt_SmallFB1", TEXTURE_GROUP_RENDER_TARGET );
if ( !s_bScreenEffectTextureIsUpdated )
{
// NOTE: UpdateScreenEffectTexture() uses StretchRect, so _rt_FullFrameFB is always 100%
// filled, even when the viewport is not fullscreen (e.g. with 'mat_viewportscale 0.5')
UpdateScreenEffectTexture( 0, x, y, w, h, true );
s_bScreenEffectTextureIsUpdated = true;
}
if ( bPerformBloom )
{
Generate8BitBloomTexture( pRenderContext, flBloomScale, x, y, w, h );
}
// Now add bloom (dest_rt0) to the framebuffer and perform software anti-aliasing and
// colour correction, all in one pass (improves performance, reduces quantization errors)
//
// First, set up texel coords (in the bloom and fb textures) at the centres of the outer pixel of the viewport:
Vector4D fullViewportPostSrcCorners( 0.0f, -0.5f, nSrcWidth/4-1, nSrcHeight/4-1 );
Vector4D fullViewportPostDestCorners( 0.0f, 0.0f, nSrcWidth - 1, nSrcHeight - 1 );
Rect_t fullViewportPostDestRect = { x, y, w, h };
Vector2D destTexSize( nSrcWidth, nSrcHeight );
// When the viewport is not fullscreen, the UV-space size of a pixel changes
// (due to a stretchrect blit being used in UpdateScreenEffectTexture()), so
// we need to adjust the corner-pixel UVs sent to our drawrect call:
Vector2D uvScale( ( nSrcWidth - ( nSrcWidth / (float)w ) ) / ( nSrcWidth - 1 ),
( nSrcHeight - ( nSrcHeight / (float)h ) ) / ( nSrcHeight - 1 ) );
CenterScaleQuadUVs( fullViewportPostSrcCorners, uvScale );
CenterScaleQuadUVs( fullViewportPostDestCorners, uvScale );
Rect_t partialViewportPostDestRect = fullViewportPostDestRect;
Vector4D partialViewportPostSrcCorners = fullViewportPostSrcCorners;
if ( debug_postproc.GetInt() == 2 )
{
// Restrict the post effects to the centre quarter of the screen
// (we only use a portion of the bloom texture, so this *does* affect bloom texture UVs)
partialViewportPostDestRect.x += 0.25f*fullViewportPostDestRect.width;
partialViewportPostDestRect.y += 0.25f*fullViewportPostDestRect.height;
partialViewportPostDestRect.width -= 0.50f*fullViewportPostDestRect.width;
partialViewportPostDestRect.height -= 0.50f*fullViewportPostDestRect.height;
// This math interprets texel coords as being at corner pixel centers (*not* at corner vertices):
Vector2D uvScale( 1.0f - ( (w / 2) / (float)(w - 1) ),
1.0f - ( (h / 2) / (float)(h - 1) ) );
CenterScaleQuadUVs( partialViewportPostSrcCorners, uvScale );
}
// Temporary hack... Color correction was crashing on the first frame
// when run outside the debugger for some mods (DoD). This forces it to skip
// a frame, ensuring we don't get the weird texture crash we otherwise would.
// FIXME: This will be removed when the true cause is found [added: Main CL 144694]
static bool bFirstFrame = !IsX360();
if( !bFirstFrame || !bPerformColCorrect )
{
bool bFBUpdated = false;
if ( mat_postprocessing_combine.GetInt() )
{
// Perform post-processing in one combined pass
IMaterial *post_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, bPerformSoftwareAA, bPerformBloom, bPerformColCorrect, flAAStrength );
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( partialViewportPostDestRect.width / 2, 0, partialViewportPostDestRect.width, partialViewportPostDestRect.height, true );
}
pRenderContext->DrawScreenSpaceRectangle(post_mat,
// TomF - offset already done by the viewport.
0,0, //partialViewportPostDestRect.x, partialViewportPostDestRect.y,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
partialViewportPostSrcCorners.x, partialViewportPostSrcCorners.y,
partialViewportPostSrcCorners.z, partialViewportPostSrcCorners.w,
dest_rt1->GetActualWidth(),dest_rt1->GetActualHeight(),
GetClientWorldEntity()->GetClientRenderable(),
mat_postprocess_x.GetInt(), mat_postprocess_y.GetInt() );
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( -1, -1, -1, -1, false );
}
bFBUpdated = true;
}
else
{
// Perform post-processing in three separate passes
if ( bPerformSoftwareAA )
{
IMaterial *aa_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, bPerformSoftwareAA, false, false, flAAStrength );
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( partialViewportPostDestRect.width / 2, 0, partialViewportPostDestRect.width, partialViewportPostDestRect.height, true );
}
pRenderContext->DrawScreenSpaceRectangle(aa_mat,
// TODO: check if offsets should be 0,0 here, as with the combined-pass case
partialViewportPostDestRect.x, partialViewportPostDestRect.y,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
partialViewportPostSrcCorners.x, partialViewportPostSrcCorners.y,
partialViewportPostSrcCorners.z, partialViewportPostSrcCorners.w,
dest_rt1->GetActualWidth(),dest_rt1->GetActualHeight(),
GetClientWorldEntity()->GetClientRenderable());
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( -1, -1, -1, -1, false );
}
bFBUpdated = true;
}
if ( bPerformBloom )
{
IMaterial *bloom_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, false, bPerformBloom, false, flAAStrength );
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( partialViewportPostDestRect.width / 2, 0, partialViewportPostDestRect.width, partialViewportPostDestRect.height, true );
}
pRenderContext->DrawScreenSpaceRectangle(bloom_mat,
// TODO: check if offsets should be 0,0 here, as with the combined-pass case
partialViewportPostDestRect.x, partialViewportPostDestRect.y,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
partialViewportPostSrcCorners.x, partialViewportPostSrcCorners.y,
partialViewportPostSrcCorners.z, partialViewportPostSrcCorners.w,
dest_rt1->GetActualWidth(),dest_rt1->GetActualHeight(),
GetClientWorldEntity()->GetClientRenderable());
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( -1, -1, -1, -1, false );
}
bFBUpdated = true;
}
if ( bPerformColCorrect )
{
if ( bFBUpdated )
{
Rect_t actualRect;
UpdateScreenEffectTexture( 0, x, y, w, h, false, &actualRect );
}
IMaterial *colcorrect_mat = CEnginePostMaterialProxy::SetupEnginePostMaterial( fullViewportPostSrcCorners, fullViewportPostDestCorners, destTexSize, false, false, bPerformColCorrect, flAAStrength );
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( partialViewportPostDestRect.width / 2, 0, partialViewportPostDestRect.width, partialViewportPostDestRect.height, true );
}
pRenderContext->DrawScreenSpaceRectangle(colcorrect_mat,
// TODO: check if offsets should be 0,0 here, as with the combined-pass case
partialViewportPostDestRect.x, partialViewportPostDestRect.y,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
partialViewportPostSrcCorners.x, partialViewportPostSrcCorners.y,
partialViewportPostSrcCorners.z, partialViewportPostSrcCorners.w,
dest_rt1->GetActualWidth(),dest_rt1->GetActualHeight(),
GetClientWorldEntity()->GetClientRenderable());
if (bSplitScreenHDR)
{
pRenderContext->SetScissorRect( -1, -1, -1, -1, false );
}
bFBUpdated = true;
}
}
bool bVisionOverride = ( localplayer_visionflags.GetInt() & ( 0x01 ) ); // Pyro-vision Goggles
if ( bVisionOverride && g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_0() && pyro_vignette.GetInt() > 0 )
{
if ( bFBUpdated )
{
Rect_t actualRect;
UpdateScreenEffectTexture( 0, x, y, w, h, false, &actualRect );
}
DrawPyroVignette(
// TODO: check if offsets should be 0,0 here, as with the combined-pass case
partialViewportPostDestRect.x, partialViewportPostDestRect.y,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
partialViewportPostSrcCorners.x, partialViewportPostSrcCorners.y,
partialViewportPostSrcCorners.z, partialViewportPostSrcCorners.w,
GetClientWorldEntity()->GetClientRenderable() );
IMaterial *pPyroVisionPostMaterial = materials->FindMaterial( "dev/pyro_post", TEXTURE_GROUP_OTHER, true);
DrawPyroPost( pPyroVisionPostMaterial,
// TODO: check if offsets should be 0,0 here, as with the combined-pass case
partialViewportPostDestRect.x, partialViewportPostDestRect.y,
partialViewportPostDestRect.width, partialViewportPostDestRect.height,
partialViewportPostSrcCorners.x, partialViewportPostSrcCorners.y,
partialViewportPostSrcCorners.z, partialViewportPostSrcCorners.w,
dest_rt1->GetActualWidth(),dest_rt1->GetActualHeight(),
GetClientWorldEntity()->GetClientRenderable() );
}
if ( g_bDumpRenderTargets )
{
DumpTGAofRenderTarget( partialViewportPostDestRect.width, partialViewportPostDestRect.height, "EnginePost" );
}
}
bFirstFrame = false;
}
if ( hdrType != HDR_TYPE_NONE )
{
DoPostBloomTonemapping( pRenderContext, x, y, w, h, flAutoExposureMin, flAutoExposureMax );
}
}
break;
case HDR_TYPE_FLOAT:
{
int dest_width,dest_height;
pRenderContext->GetRenderTargetDimensions( dest_width, dest_height );
if (mat_dynamic_tonemapping.GetInt() || mat_show_histogram.GetInt())
{
g_HDR_HistogramSystem.Update();
// Warning("avg_lum=%f\n",g_HDR_HistogramSystem.GetTargetTonemapScalar());
if ( mat_dynamic_tonemapping.GetInt() )
{
float avg_lum = MAX( 0.0001, g_HDR_HistogramSystem.GetTargetTonemapScalar() );
float scalevalue = MAX( flAutoExposureMin,
MIN( flAutoExposureMax, 0.18 / avg_lum ));
pRenderContext->SetGoalToneMappingScale( scalevalue );
mat_hdr_tonemapscale.SetValue( scalevalue );
}
}
IMaterial *pBloomMaterial;
pBloomMaterial = materials->FindMaterial( "dev/floattoscreen_combine", "" );
IMaterialVar *pBloomAmountVar = pBloomMaterial->FindVar( "$bloomamount", NULL );
pBloomAmountVar->SetFloatValue( flBloomScale );
PostProcessingPass* selectedHDR;
if ( flBloomScale > 0.0 )
{
selectedHDR = HDRFinal_Float;
}
else
{
selectedHDR = HDRFinal_Float_NoBloom;
}
if (mat_show_ab_hdr.GetInt())
{
ClipBox splitScreenClip;
splitScreenClip.m_minx = splitScreenClip.m_miny = 0;
// Left half
splitScreenClip.m_maxx = dest_width / 2;
splitScreenClip.m_maxy = dest_height - 1;
ApplyPostProcessingPasses(HDRSimulate_NonHDR, &splitScreenClip);
// Right half
splitScreenClip.m_minx = splitScreenClip.m_maxx;
splitScreenClip.m_maxx = dest_width - 1;
ApplyPostProcessingPasses(selectedHDR, &splitScreenClip);
}
else
{
ApplyPostProcessingPasses(selectedHDR);
}
pRenderContext->SetRenderTarget(NULL);
if ( mat_show_histogram.GetInt() && (engine->GetDXSupportLevel()>=90))
g_HDR_HistogramSystem.DisplayHistogram();
if ( mat_dynamic_tonemapping.GetInt() )
{
float avg_lum = MAX( 0.0001, g_HDR_HistogramSystem.GetTargetTonemapScalar() );
float scalevalue = MAX( flAutoExposureMin,
MIN( flAutoExposureMax, 0.023 / avg_lum ));
SetToneMapScale( pRenderContext, scalevalue, flAutoExposureMin, flAutoExposureMax );
}
pRenderContext->SetRenderTarget( NULL );
break;
}
}
#if defined( _X360 )
pRenderContext->PopVertexShaderGPRAllocation();
#endif
}
// Motion Blur Material Proxy =========================================================================================
static float g_vMotionBlurValues[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
class CMotionBlurMaterialProxy : public CEntityMaterialProxy
{
public:
CMotionBlurMaterialProxy();
virtual ~CMotionBlurMaterialProxy();
virtual bool Init( IMaterial *pMaterial, KeyValues *pKeyValues );
virtual void OnBind( C_BaseEntity *pEntity );
virtual IMaterial *GetMaterial();
private:
IMaterialVar *m_pMaterialParam;
};
CMotionBlurMaterialProxy::CMotionBlurMaterialProxy()
{
m_pMaterialParam = NULL;
}
CMotionBlurMaterialProxy::~CMotionBlurMaterialProxy()
{
// Do nothing
}
bool CMotionBlurMaterialProxy::Init( IMaterial *pMaterial, KeyValues *pKeyValues )
{
bool bFoundVar = false;
m_pMaterialParam = pMaterial->FindVar( "$MotionBlurInternal", &bFoundVar, false );
if ( bFoundVar == false)
return false;
return true;
}
void CMotionBlurMaterialProxy::OnBind( C_BaseEntity *pEnt )
{
if ( m_pMaterialParam != NULL )
{
m_pMaterialParam->SetVecValue( g_vMotionBlurValues, 4 );
}
}
IMaterial *CMotionBlurMaterialProxy::GetMaterial()
{
if ( m_pMaterialParam == NULL)
return NULL;
return m_pMaterialParam->GetOwningMaterial();
}
EXPOSE_INTERFACE( CMotionBlurMaterialProxy, IMaterialProxy, "MotionBlur" IMATERIAL_PROXY_INTERFACE_VERSION );
//=====================================================================================================================
// Image-space Motion Blur ============================================================================================
//=====================================================================================================================
ConVar mat_motion_blur_enabled( "mat_motion_blur_enabled", "1", FCVAR_ARCHIVE );
ConVar mat_motion_blur_forward_enabled( "mat_motion_blur_forward_enabled", "0" );
ConVar mat_motion_blur_falling_min( "mat_motion_blur_falling_min", "10.0" );
ConVar mat_motion_blur_falling_max( "mat_motion_blur_falling_max", "20.0" );
ConVar mat_motion_blur_falling_intensity( "mat_motion_blur_falling_intensity", "1.0" );
//ConVar mat_motion_blur_roll_intensity( "mat_motion_blur_roll_intensity", "1.0" );
ConVar mat_motion_blur_rotation_intensity( "mat_motion_blur_rotation_intensity", "1.0" );
ConVar mat_motion_blur_strength( "mat_motion_blur_strength", "1.0" );
void DoImageSpaceMotionBlur( const CViewSetup &view, int x, int y, int w, int h )
{
#ifdef CSS_PERF_TEST
return;
#endif
if ( ( !mat_motion_blur_enabled.GetInt() ) || ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) )
{
return;
}
//======================================================================================================//
// Get these convars here to make it easier to remove them later and to default each client differently //
//======================================================================================================//
float flMotionBlurRotationIntensity = mat_motion_blur_rotation_intensity.GetFloat() * 0.15f; // The default is to not blur past 15% of the range
float flMotionBlurRollIntensity = 0.3f; // * mat_motion_blur_roll_intensity.GetFloat(); // The default is to not blur past 30% of the range
float flMotionBlurFallingIntensity = mat_motion_blur_falling_intensity.GetFloat();
float flMotionBlurFallingMin = mat_motion_blur_falling_min.GetFloat();
float flMotionBlurFallingMax = mat_motion_blur_falling_max.GetFloat();
float flMotionBlurGlobalStrength = mat_motion_blur_strength.GetFloat();
//===============================================================================//
// Set global g_vMotionBlurValues[4] values so material proxy can get the values //
//===============================================================================//
if ( true )
{
//=====================//
// Previous frame data //
//=====================//
static float s_flLastTimeUpdate = 0.0f;
static float s_flPreviousPitch = 0.0f;
static float s_flPreviousYaw = 0.0f;
static float s_vPreviousPositon[3] = { 0.0f, 0.0f, 0.0f };
static matrix3x4_t s_mPreviousFrameBasisVectors;
static float s_flNoRotationalMotionBlurUntil = 0.0f;
//float vPreviousSideVec[3] = { s_mPreviousFrameBasisVectors[0][1], s_mPreviousFrameBasisVectors[1][1], s_mPreviousFrameBasisVectors[2][1] };
//float vPreviousForwardVec[3] = { s_mPreviousFrameBasisVectors[0][0], s_mPreviousFrameBasisVectors[1][0], s_mPreviousFrameBasisVectors[2][0] };
//float vPreviousUpVec[3] = { s_mPreviousFrameBasisVectors[0][2], s_mPreviousFrameBasisVectors[1][2], s_mPreviousFrameBasisVectors[2][2] };
float flTimeElapsed = gpGlobals->realtime - s_flLastTimeUpdate;
//===================================//
// Get current pitch & wrap to +-180 //
//===================================//
float flCurrentPitch = view.angles[PITCH];
while ( flCurrentPitch > 180.0f )
flCurrentPitch -= 360.0f;
while ( flCurrentPitch < -180.0f )
flCurrentPitch += 360.0f;
//=================================//
// Get current yaw & wrap to +-180 //
//=================================//
float flCurrentYaw = view.angles[YAW];
while ( flCurrentYaw > 180.0f )
flCurrentYaw -= 360.0f;
while ( flCurrentYaw < -180.0f )
flCurrentYaw += 360.0f;
//engine->Con_NPrintf( 0, "Blur Pitch: %6.2f Yaw: %6.2f", flCurrentPitch, flCurrentYaw );
//engine->Con_NPrintf( 1, "Blur FOV: %6.2f Aspect: %6.2f Ortho: %s", view.fov, view.m_flAspectRatio, view.m_bOrtho ? "Yes" : "No" );
//===========================//
// Get current basis vectors //
//===========================//
matrix3x4_t mCurrentBasisVectors;
AngleMatrix( view.angles, mCurrentBasisVectors );
float vCurrentSideVec[3] = { mCurrentBasisVectors[0][1], mCurrentBasisVectors[1][1], mCurrentBasisVectors[2][1] };
float vCurrentForwardVec[3] = { mCurrentBasisVectors[0][0], mCurrentBasisVectors[1][0], mCurrentBasisVectors[2][0] };
//float vCurrentUpVec[3] = { mCurrentBasisVectors[0][2], mCurrentBasisVectors[1][2], mCurrentBasisVectors[2][2] };
//======================//
// Get current position //
//======================//
float vCurrentPosition[3] = { view.origin.x, view.origin.y, view.origin.z };
//===============================================================//
// Evaluate change in position to determine if we need to update //
//===============================================================//
float vPositionChange[3] = { 0.0f, 0.0f, 0.0f };
VectorSubtract( s_vPreviousPositon, vCurrentPosition, vPositionChange );
if ( ( VectorLength( vPositionChange ) > 30.0f ) && ( flTimeElapsed >= 0.5f ) )
{
//=======================================================//
// If we moved a far distance in one frame and more than //
// half a second elapsed, disable motion blur this frame //
//=======================================================//
//engine->Con_NPrintf( 8, " Pos change && time > 0.5 seconds %f ", gpGlobals->realtime );
g_vMotionBlurValues[0] = 0.0f;
g_vMotionBlurValues[1] = 0.0f;
g_vMotionBlurValues[2] = 0.0f;
g_vMotionBlurValues[3] = 0.0f;
}
else if ( flTimeElapsed > ( 1.0f / 15.0f ) )
{
//==========================================//
// If slower than 15 fps, don't motion blur //
//==========================================//
g_vMotionBlurValues[0] = 0.0f;
g_vMotionBlurValues[1] = 0.0f;
g_vMotionBlurValues[2] = 0.0f;
g_vMotionBlurValues[3] = 0.0f;
}
else if ( VectorLength( vPositionChange ) > 50.0f )
{
//================================================================================//
// We moved a far distance in a frame, use the same motion blur as last frame //
// because I think we just went through a portal (should we ifdef this behavior?) //
//================================================================================//
//engine->Con_NPrintf( 8, " Position changed %f units @ %.2f time ", VectorLength( vPositionChange ), gpGlobals->realtime );
s_flNoRotationalMotionBlurUntil = gpGlobals->realtime + 1.0f; // Wait a second until the portal craziness calms down
}
else
{
//====================//
// Normal update path //
//====================//
// Compute horizontal and vertical fov
float flHorizontalFov = view.fov;
float flVerticalFov = ( view.m_flAspectRatio <= 0.0f ) ? ( view.fov ) : ( view.fov / view.m_flAspectRatio );
//engine->Con_NPrintf( 2, "Horizontal Fov: %6.2f Vertical Fov: %6.2f", flHorizontalFov, flVerticalFov );
//=====================//
// Forward motion blur //
//=====================//
float flViewDotMotion = DotProduct( vCurrentForwardVec, vPositionChange );
if ( mat_motion_blur_forward_enabled.GetBool() ) // Want forward and falling
g_vMotionBlurValues[2] = flViewDotMotion;
else // Falling only
g_vMotionBlurValues[2] = flViewDotMotion * fabs( vCurrentForwardVec[2] ); // Only want this if we're looking up or down;
//====================================//
// Yaw (Compensate for circle strafe) //
//====================================//
float flSideDotMotion = DotProduct( vCurrentSideVec, vPositionChange );
float flYawDiffOriginal = s_flPreviousYaw - flCurrentYaw;
if ( ( ( s_flPreviousYaw - flCurrentYaw > 180.0f ) || ( s_flPreviousYaw - flCurrentYaw < -180.0f ) ) &&
( ( s_flPreviousYaw + flCurrentYaw > -180.0f ) && ( s_flPreviousYaw + flCurrentYaw < 180.0f ) ) )
flYawDiffOriginal = s_flPreviousYaw + flCurrentYaw;
float flYawDiffAdjusted = flYawDiffOriginal + ( flSideDotMotion / 3.0f ); // Yes, 3.0 is a magic number, sue me
// Make sure the adjustment only lessens the effect, not magnify it or reverse it
if ( flYawDiffOriginal < 0.0f )
flYawDiffAdjusted = clamp ( flYawDiffAdjusted, flYawDiffOriginal, 0.0f );
else
flYawDiffAdjusted = clamp ( flYawDiffAdjusted, 0.0f, flYawDiffOriginal );
// Use pitch to dampen yaw
float flUndampenedYaw = flYawDiffAdjusted / flHorizontalFov;
g_vMotionBlurValues[0] = flUndampenedYaw * ( 1.0f - ( fabs( flCurrentPitch ) / 90.0f ) ); // Dampen horizontal yaw blur based on pitch
//engine->Con_NPrintf( 4, "flSideDotMotion: %6.2f yaw diff: %6.2f ( %6.2f, %6.2f )", flSideDotMotion, ( s_flPreviousYaw - flCurrentYaw ), flYawDiffOriginal, flYawDiffAdjusted );
//=======================================//
// Pitch (Compensate for forward motion) //
//=======================================//
float flPitchCompensateMask = 1.0f - ( ( 1.0f - fabs( vCurrentForwardVec[2] ) ) * ( 1.0f - fabs( vCurrentForwardVec[2] ) ) );
float flPitchDiffOriginal = s_flPreviousPitch - flCurrentPitch;
float flPitchDiffAdjusted = flPitchDiffOriginal;
if ( flCurrentPitch > 0.0f )
flPitchDiffAdjusted = flPitchDiffOriginal - ( ( flViewDotMotion / 2.0f ) * flPitchCompensateMask ); // Yes, 2.0 is a magic number, sue me
else
flPitchDiffAdjusted = flPitchDiffOriginal + ( ( flViewDotMotion / 2.0f ) * flPitchCompensateMask ); // Yes, 2.0 is a magic number, sue me
// Make sure the adjustment only lessens the effect, not magnify it or reverse it
if ( flPitchDiffOriginal < 0.0f )
flPitchDiffAdjusted = clamp ( flPitchDiffAdjusted, flPitchDiffOriginal, 0.0f );
else
flPitchDiffAdjusted = clamp ( flPitchDiffAdjusted, 0.0f, flPitchDiffOriginal );
g_vMotionBlurValues[1] = flPitchDiffAdjusted / flVerticalFov;
//engine->Con_NPrintf( 5, "flViewDotMotion %6.2f, flPitchCompensateMask %6.2f, flPitchDiffOriginal %6.2f, flPitchDiffAdjusted %6.2f, g_vMotionBlurValues[1] %6.2f", flViewDotMotion, flPitchCompensateMask, flPitchDiffOriginal, flPitchDiffAdjusted, g_vMotionBlurValues[1]);
//========================================================//
// Roll (Enabled when we're looking down and yaw changes) //
//========================================================//
g_vMotionBlurValues[3] = flUndampenedYaw; // Roll starts out as undampened yaw intensity and is then scaled by pitch
g_vMotionBlurValues[3] *= ( fabs( flCurrentPitch ) / 90.0f ) * ( fabs( flCurrentPitch ) / 90.0f ) * ( fabs( flCurrentPitch ) / 90.0f ); // Dampen roll based on pitch^3
//engine->Con_NPrintf( 4, "[2] before scale and bias: %6.2f", g_vMotionBlurValues[2] );
//engine->Con_NPrintf( 5, "[3] before scale and bias: %6.2f", g_vMotionBlurValues[3] );
//==============================================================//
// Time-adjust falling effect until we can do something smarter //
//==============================================================//
if ( flTimeElapsed > 0.0f )
g_vMotionBlurValues[2] /= flTimeElapsed * 30.0f; // 1/30th of a second?
else
g_vMotionBlurValues[2] = 0.0f;
// Scale and bias values after time adjustment
g_vMotionBlurValues[2] = clamp( ( fabs( g_vMotionBlurValues[2] ) - flMotionBlurFallingMin ) / ( flMotionBlurFallingMax - flMotionBlurFallingMin ), 0.0f, 1.0f ) * ( g_vMotionBlurValues[2] >= 0.0f ? 1.0f : -1.0f );
g_vMotionBlurValues[2] /= 30.0f; // To counter-adjust for time adjustment above
//=================//
// Apply intensity //
//=================//
g_vMotionBlurValues[0] *= flMotionBlurRotationIntensity * flMotionBlurGlobalStrength;
g_vMotionBlurValues[1] *= flMotionBlurRotationIntensity * flMotionBlurGlobalStrength;
g_vMotionBlurValues[2] *= flMotionBlurFallingIntensity * flMotionBlurGlobalStrength;
g_vMotionBlurValues[3] *= flMotionBlurRollIntensity * flMotionBlurGlobalStrength;
//===============================================================//
// Dampen motion blur from 100%-0% as fps drops from 50fps-30fps //
//===============================================================//
if ( !IsX360() ) // I'm not doing this on the 360 yet since I can't test it
{
float flSlowFps = 30.0f;
float flFastFps = 50.0f;
float flCurrentFps = ( flTimeElapsed > 0.0f ) ? ( 1.0f / flTimeElapsed ) : 0.0f;
float flDampenFactor = clamp( ( ( flCurrentFps - flSlowFps ) / ( flFastFps - flSlowFps ) ), 0.0f, 1.0f );
//engine->Con_NPrintf( 4, "gpGlobals->realtime %.2f gpGlobals->curtime %.2f", gpGlobals->realtime, gpGlobals->curtime );
//engine->Con_NPrintf( 5, "flCurrentFps %.2f", flCurrentFps );
//engine->Con_NPrintf( 7, "flTimeElapsed %.2f", flTimeElapsed );
g_vMotionBlurValues[0] *= flDampenFactor;
g_vMotionBlurValues[1] *= flDampenFactor;
g_vMotionBlurValues[2] *= flDampenFactor;
g_vMotionBlurValues[3] *= flDampenFactor;
//engine->Con_NPrintf( 6, "Dampen: %.2f", flDampenFactor );
}
//engine->Con_NPrintf( 6, "Final values: { %6.2f%%, %6.2f%%, %6.2f%%, %6.2f%% }", g_vMotionBlurValues[0]*100.0f, g_vMotionBlurValues[1]*100.0f, g_vMotionBlurValues[2]*100.0f, g_vMotionBlurValues[3]*100.0f );
}
//============================================//
// Zero out blur if still in that time window //
//============================================//
if ( gpGlobals->realtime < s_flNoRotationalMotionBlurUntil )
{
//engine->Con_NPrintf( 9, " No Rotation @ %f ", gpGlobals->realtime );
// Zero out rotational blur but leave forward/falling blur alone
g_vMotionBlurValues[0] = 0.0f; // X
g_vMotionBlurValues[1] = 0.0f; // Y
g_vMotionBlurValues[3] = 0.0f; // Roll
}
else
{
s_flNoRotationalMotionBlurUntil = 0.0f;
}
//====================================//
// Store current frame for next frame //
//====================================//
VectorCopy( vCurrentPosition, s_vPreviousPositon );
s_mPreviousFrameBasisVectors = mCurrentBasisVectors;
s_flPreviousPitch = flCurrentPitch;
s_flPreviousYaw = flCurrentYaw;
s_flLastTimeUpdate = gpGlobals->realtime;
}
//=============================================================================================//
// Render quad and let material proxy pick up the g_vMotionBlurValues[4] values just set above //
//=============================================================================================//
if ( true )
{
CMatRenderContextPtr pRenderContext( materials );
//pRenderContext->PushRenderTargetAndViewport();
ITexture *pSrc = materials->FindTexture( "_rt_FullFrameFB", TEXTURE_GROUP_RENDER_TARGET );
int nSrcWidth = pSrc->GetActualWidth();
int nSrcHeight = pSrc->GetActualHeight();
int dest_width, dest_height, nDummy;
pRenderContext->GetViewport( nDummy, nDummy, dest_width, dest_height );
if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_FLOAT )
{
UpdateScreenEffectTexture( 0, x, y, w, h, true ); // Do we need to check if we already did this?
}
// Get material pointer
IMaterial *pMatMotionBlur = materials->FindMaterial( "dev/motion_blur", TEXTURE_GROUP_OTHER, true );
//SetRenderTargetAndViewPort( dest_rt0 );
//pRenderContext->PopRenderTargetAndViewport();
if ( pMatMotionBlur != NULL )
{
pRenderContext->DrawScreenSpaceRectangle(
pMatMotionBlur,
0, 0, dest_width, dest_height,
0, 0, nSrcWidth-1, nSrcHeight-1,
nSrcWidth, nSrcHeight, GetClientWorldEntity()->GetClientRenderable() );
if ( g_bDumpRenderTargets )
{
DumpTGAofRenderTarget( dest_width, dest_height, "MotionBlur" );
}
}
}
}