* 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
882 lines
27 KiB
C++
882 lines
27 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//
|
|
//===========================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "c_pixel_visibility.h"
|
|
#include "materialsystem/imesh.h"
|
|
#include "materialsystem/imaterial.h"
|
|
#include "clienteffectprecachesystem.h"
|
|
#include "view.h"
|
|
#include "viewrender.h"
|
|
#include "utlmultilist.h"
|
|
#include "vprof.h"
|
|
#include "icommandline.h"
|
|
#include "sourcevr/isourcevirtualreality.h"
|
|
|
|
static void PixelvisDrawChanged( IConVar *pPixelvisVar, const char *pOld, float flOldValue );
|
|
|
|
ConVar r_pixelvisibility_partial( "r_pixelvisibility_partial", "1" );
|
|
ConVar r_dopixelvisibility( "r_dopixelvisibility", "1" );
|
|
ConVar r_drawpixelvisibility( "r_drawpixelvisibility", "0", 0, "Show the occlusion proxies", PixelvisDrawChanged );
|
|
ConVar r_pixelvisibility_spew( "r_pixelvisibility_spew", "0" );
|
|
|
|
#ifdef OSX
|
|
// GLMgr will set this one to "1" if it senses the new post-10.6.4 driver (m_hasPerfPackage1)
|
|
ConVar gl_can_query_fast( "gl_can_query_fast", "0" );
|
|
|
|
static bool HasFastQueries( void )
|
|
{
|
|
return gl_can_query_fast.GetBool();
|
|
}
|
|
#else
|
|
// non OSX path
|
|
static bool HasFastQueries( void )
|
|
{
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
extern ConVar building_cubemaps;
|
|
|
|
#ifndef _X360
|
|
const float MIN_PROXY_PIXELS = 5.0f;
|
|
#else
|
|
const float MIN_PROXY_PIXELS = 25.0f;
|
|
#endif
|
|
|
|
float PixelVisibility_DrawProxy( IMatRenderContext *pRenderContext, OcclusionQueryObjectHandle_t queryHandle, Vector origin, float scale, float proxyAspect, IMaterial *pMaterial, bool screenspace )
|
|
{
|
|
Vector point;
|
|
|
|
// don't expand this with distance to fit pixels or the sprite will poke through
|
|
// only expand the parts perpendicular to the view
|
|
float forwardScale = scale;
|
|
// draw a pyramid of points touching a sphere of radius "scale" at origin
|
|
float pixelsPerUnit = pRenderContext->ComputePixelDiameterOfSphere( origin, 1.0f );
|
|
pixelsPerUnit = MAX( pixelsPerUnit, 1e-4f );
|
|
if ( screenspace )
|
|
{
|
|
// Force this to be the size of a sphere of diameter "scale" at some reference distance (1.0 unit)
|
|
float pixelsPerUnit2 = pRenderContext->ComputePixelDiameterOfSphere( CurrentViewOrigin() + CurrentViewForward()*1.0f, scale*0.5f );
|
|
// force drawing of "scale" pixels
|
|
scale = pixelsPerUnit2 / pixelsPerUnit;
|
|
}
|
|
else
|
|
{
|
|
float pixels = scale * pixelsPerUnit;
|
|
|
|
// make the radius larger to ensure a minimum screen space size of the proxy geometry
|
|
if ( pixels < MIN_PROXY_PIXELS )
|
|
{
|
|
scale = MIN_PROXY_PIXELS / pixelsPerUnit;
|
|
}
|
|
}
|
|
|
|
// collapses the pyramid to a plane - so this could be a quad instead
|
|
Vector dir = origin - CurrentViewOrigin();
|
|
VectorNormalize(dir);
|
|
origin -= dir * forwardScale;
|
|
forwardScale = 0.0f;
|
|
//
|
|
|
|
Vector verts[5];
|
|
const float sqrt2 = 0.707106781f; // sqrt(2) - keeps all vectors the same length from origin
|
|
scale *= sqrt2;
|
|
float scale45x = scale;
|
|
float scale45y = scale / proxyAspect;
|
|
verts[0] = origin - CurrentViewForward() * forwardScale; // the apex of the pyramid
|
|
verts[1] = origin + CurrentViewUp() * scale45y - CurrentViewRight() * scale45x; // these four form the base
|
|
verts[2] = origin + CurrentViewUp() * scale45y + CurrentViewRight() * scale45x; // the pyramid is a sprite with a point that
|
|
verts[3] = origin - CurrentViewUp() * scale45y + CurrentViewRight() * scale45x; // pokes back toward the camera through any nearby
|
|
verts[4] = origin - CurrentViewUp() * scale45y - CurrentViewRight() * scale45x; // geometry
|
|
|
|
// get screen coords of edges
|
|
Vector screen[4];
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
extern int ScreenTransform( const Vector& point, Vector& screen );
|
|
if ( ScreenTransform( verts[i+1], screen[i] ) )
|
|
return -1;
|
|
}
|
|
|
|
// compute area and screen-clipped area
|
|
float w = screen[1].x - screen[0].x;
|
|
float h = screen[0].y - screen[3].y;
|
|
float ws = MIN(1.0f, screen[1].x) - MAX(-1.0f, screen[0].x);
|
|
float hs = MIN(1.0f, screen[0].y) - MAX(-1.0f, screen[3].y);
|
|
float area = w*h; // area can be zero when we ALT-TAB
|
|
float areaClipped = ws*hs;
|
|
float ratio = 0.0f;
|
|
if ( area != 0 )
|
|
{
|
|
// compute the ratio of the area not clipped by the frustum to total area
|
|
ratio = areaClipped / area;
|
|
ratio = clamp(ratio, 0.0f, 1.0f);
|
|
}
|
|
|
|
pRenderContext->BeginOcclusionQueryDrawing( queryHandle );
|
|
CMeshBuilder meshBuilder;
|
|
IMesh* pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, pMaterial );
|
|
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 4 );
|
|
// draw a pyramid
|
|
for ( int i = 0; i < 4; i++ )
|
|
{
|
|
int a = i+1;
|
|
int b = (a%4)+1;
|
|
meshBuilder.Position3fv( verts[0].Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
meshBuilder.Position3fv( verts[a].Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
meshBuilder.Position3fv( verts[b].Base() );
|
|
meshBuilder.AdvanceVertex();
|
|
}
|
|
meshBuilder.End();
|
|
pMesh->Draw();
|
|
|
|
// sprite/quad proxy
|
|
#if 0
|
|
meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 );
|
|
|
|
VectorMA (origin, -scale, CurrentViewUp(), point);
|
|
VectorMA (point, -scale, CurrentViewRight(), point);
|
|
meshBuilder.Position3fv (point.Base());
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
VectorMA (origin, scale, CurrentViewUp(), point);
|
|
VectorMA (point, -scale, CurrentViewRight(), point);
|
|
meshBuilder.Position3fv (point.Base());
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
VectorMA (origin, scale, CurrentViewUp(), point);
|
|
VectorMA (point, scale, CurrentViewRight(), point);
|
|
meshBuilder.Position3fv (point.Base());
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
VectorMA (origin, -scale, CurrentViewUp(), point);
|
|
VectorMA (point, scale, CurrentViewRight(), point);
|
|
meshBuilder.Position3fv (point.Base());
|
|
meshBuilder.AdvanceVertex();
|
|
|
|
meshBuilder.End();
|
|
pMesh->Draw();
|
|
#endif
|
|
pRenderContext->EndOcclusionQueryDrawing( queryHandle );
|
|
|
|
// fraction clipped by frustum
|
|
return ratio;
|
|
}
|
|
|
|
class CPixelVisSet
|
|
{
|
|
public:
|
|
void Init( const pixelvis_queryparams_t ¶ms );
|
|
void MarkActive();
|
|
bool IsActive();
|
|
CPixelVisSet()
|
|
{
|
|
frameIssued = 0;
|
|
serial = 0;
|
|
queryList = 0xFFFF;
|
|
sizeIsScreenSpace = false;
|
|
}
|
|
|
|
public:
|
|
float proxySize;
|
|
float proxyAspect;
|
|
float fadeTimeInv;
|
|
unsigned short queryList;
|
|
unsigned short serial;
|
|
bool sizeIsScreenSpace;
|
|
private:
|
|
int frameIssued;
|
|
};
|
|
|
|
|
|
void CPixelVisSet::Init( const pixelvis_queryparams_t ¶ms )
|
|
{
|
|
Assert( params.bSetup );
|
|
proxySize = params.proxySize;
|
|
proxyAspect = params.proxyAspect;
|
|
if ( params.fadeTime > 0.0f )
|
|
{
|
|
fadeTimeInv = 1.0f / params.fadeTime;
|
|
}
|
|
else
|
|
{
|
|
// fade in over 0.125 seconds
|
|
fadeTimeInv = 1.0f / 0.125f;
|
|
}
|
|
frameIssued = 0;
|
|
sizeIsScreenSpace = params.bSizeInScreenspace;
|
|
}
|
|
|
|
void CPixelVisSet::MarkActive()
|
|
{
|
|
frameIssued = gpGlobals->framecount;
|
|
}
|
|
|
|
bool CPixelVisSet::IsActive()
|
|
{
|
|
return (gpGlobals->framecount - frameIssued) > 1 ? false : true;
|
|
}
|
|
|
|
class CPixelVisibilityQuery
|
|
{
|
|
public:
|
|
CPixelVisibilityQuery();
|
|
~CPixelVisibilityQuery();
|
|
bool IsValid();
|
|
bool IsForView( int viewID );
|
|
bool IsActive();
|
|
float GetFractionVisible( float fadeTimeInv );
|
|
void IssueQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace );
|
|
void IssueCountingQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace );
|
|
void ResetOcclusionQueries();
|
|
void SetView( int viewID )
|
|
{
|
|
m_viewID = viewID;
|
|
m_brightnessTarget = 0.0f;
|
|
m_clipFraction = 1.0f;
|
|
m_frameIssued = -1;
|
|
m_failed = false;
|
|
m_wasQueriedThisFrame = false;
|
|
m_hasValidQueryResults = false;
|
|
}
|
|
|
|
public:
|
|
Vector m_origin;
|
|
int m_frameIssued;
|
|
private:
|
|
float m_brightnessTarget;
|
|
float m_clipFraction;
|
|
OcclusionQueryObjectHandle_t m_queryHandle;
|
|
OcclusionQueryObjectHandle_t m_queryHandleCount;
|
|
unsigned short m_wasQueriedThisFrame : 1;
|
|
unsigned short m_failed : 1;
|
|
unsigned short m_hasValidQueryResults : 1;
|
|
unsigned short m_pad : 13;
|
|
unsigned short m_viewID;
|
|
|
|
friend void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID ); //need direct access to private data to make shifting smooth
|
|
};
|
|
|
|
CPixelVisibilityQuery::CPixelVisibilityQuery()
|
|
{
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
SetView( 0xFFFF );
|
|
m_queryHandle = pRenderContext->CreateOcclusionQueryObject();
|
|
m_queryHandleCount = pRenderContext->CreateOcclusionQueryObject();
|
|
}
|
|
|
|
CPixelVisibilityQuery::~CPixelVisibilityQuery()
|
|
{
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
if ( m_queryHandle != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE )
|
|
{
|
|
pRenderContext->DestroyOcclusionQueryObject( m_queryHandle );
|
|
}
|
|
if ( m_queryHandleCount != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE )
|
|
{
|
|
pRenderContext->DestroyOcclusionQueryObject( m_queryHandleCount );
|
|
}
|
|
}
|
|
|
|
void CPixelVisibilityQuery::ResetOcclusionQueries()
|
|
{
|
|
// NOTE: Since we're keeping the CPixelVisibilityQuery objects around in a pool
|
|
// and not actually deleting them, this means that our material system occlusion queries are
|
|
// not being deleted either. Which means that if a CPixelVisibilityQuery is
|
|
// put into the free list and then immediately re-used, then we have an opportunity for
|
|
// a bug: What can happen on the first frame of the material system query
|
|
// is that if the query isn't done yet, it will use the last queried value
|
|
// which will happen to be set to the value of the last query done
|
|
// for the previous CPixelVisSet the CPixelVisibilityQuery happened to be associated with
|
|
// which makes queries have an invalid value for the first frame
|
|
|
|
// This will mark the occlusion query objects as not ever having been read from before
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
if ( m_queryHandle != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE )
|
|
{
|
|
pRenderContext->ResetOcclusionQueryObject( m_queryHandle );
|
|
}
|
|
if ( m_queryHandleCount != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE )
|
|
{
|
|
pRenderContext->ResetOcclusionQueryObject( m_queryHandleCount );
|
|
}
|
|
}
|
|
|
|
bool CPixelVisibilityQuery::IsValid()
|
|
{
|
|
return (m_queryHandle != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE) ? true : false;
|
|
}
|
|
bool CPixelVisibilityQuery::IsForView( int viewID )
|
|
{
|
|
return m_viewID == viewID ? true : false;
|
|
}
|
|
|
|
bool CPixelVisibilityQuery::IsActive()
|
|
{
|
|
return (gpGlobals->framecount - m_frameIssued) > 1 ? false : true;
|
|
}
|
|
|
|
float CPixelVisibilityQuery::GetFractionVisible( float fadeTimeInv )
|
|
{
|
|
if ( !IsValid() )
|
|
return 0.0f;
|
|
|
|
if ( !m_wasQueriedThisFrame )
|
|
{
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
m_wasQueriedThisFrame = true;
|
|
int pixels = -1;
|
|
int pixelsPossible = -1;
|
|
if ( r_pixelvisibility_partial.GetBool() )
|
|
{
|
|
if ( m_frameIssued != -1 )
|
|
{
|
|
pixelsPossible = pRenderContext->OcclusionQuery_GetNumPixelsRendered( m_queryHandleCount );
|
|
pixels = pRenderContext->OcclusionQuery_GetNumPixelsRendered( m_queryHandle );
|
|
}
|
|
|
|
if ( r_pixelvisibility_spew.GetBool() && CurrentViewID() == 0 )
|
|
{
|
|
DevMsg( 1, "Pixels visible: %d (qh:%d) Pixels possible: %d (qh:%d) (frame:%d)\n", pixels, (int)m_queryHandle, pixelsPossible, (int)m_queryHandleCount, gpGlobals->framecount );
|
|
}
|
|
|
|
if ( pixels < 0 || pixelsPossible < 0 )
|
|
{
|
|
m_failed = ( m_frameIssued >= 0 ) ? true : false;
|
|
return m_brightnessTarget * m_clipFraction;
|
|
}
|
|
m_hasValidQueryResults = true;
|
|
|
|
if ( pixelsPossible > 0 )
|
|
{
|
|
float target = (float)pixels / (float)pixelsPossible;
|
|
target = (target >= 0.95f) ? 1.0f : (target < 0.0f) ? 0.0f : target;
|
|
float rate = gpGlobals->frametime * fadeTimeInv;
|
|
m_brightnessTarget = Approach( target, m_brightnessTarget, rate ); // fade in / out
|
|
}
|
|
else
|
|
{
|
|
m_brightnessTarget = 0.0f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( m_frameIssued != -1 )
|
|
{
|
|
pixels = pRenderContext->OcclusionQuery_GetNumPixelsRendered( m_queryHandle );
|
|
}
|
|
|
|
if ( r_pixelvisibility_spew.GetBool() && CurrentViewID() == 0 )
|
|
{
|
|
DevMsg( 1, "Pixels visible: %d (qh:%d) (frame:%d)\n", pixels, (int)m_queryHandle, gpGlobals->framecount );
|
|
}
|
|
|
|
if ( pixels < 0 )
|
|
{
|
|
m_failed = ( m_frameIssued >= 0 ) ? true : false;
|
|
return m_brightnessTarget * m_clipFraction;
|
|
}
|
|
m_hasValidQueryResults = true;
|
|
if ( m_frameIssued == gpGlobals->framecount-1 )
|
|
{
|
|
float rate = gpGlobals->frametime * fadeTimeInv;
|
|
float target = 0.0f;
|
|
if ( pixels > 0 )
|
|
{
|
|
// fade in slower than you fade out
|
|
rate *= 0.5f;
|
|
target = 1.0f;
|
|
}
|
|
m_brightnessTarget = Approach( target, m_brightnessTarget, rate ); // fade in / out
|
|
}
|
|
else
|
|
{
|
|
m_brightnessTarget = 0.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
return m_brightnessTarget * m_clipFraction;
|
|
}
|
|
|
|
void CPixelVisibilityQuery::IssueQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace )
|
|
{
|
|
if ( !m_failed )
|
|
{
|
|
Assert( IsValid() );
|
|
|
|
if ( r_pixelvisibility_spew.GetBool() && CurrentViewID() == 0 )
|
|
{
|
|
DevMsg( 1, "Draw Proxy: qh:%d org:<%d,%d,%d> (frame:%d)\n", (int)m_queryHandle, (int)m_origin[0], (int)m_origin[1], (int)m_origin[2], gpGlobals->framecount );
|
|
}
|
|
|
|
m_clipFraction = PixelVisibility_DrawProxy( pRenderContext, m_queryHandle, m_origin, proxySize, proxyAspect, pMaterial, sizeIsScreenSpace );
|
|
if ( m_clipFraction < 0 )
|
|
{
|
|
// NOTE: In this case, the proxy wasn't issued cause it was offscreen
|
|
// can't set the m_frameissued field since that would cause it to get marked as failed
|
|
m_clipFraction = 0;
|
|
m_wasQueriedThisFrame = false;
|
|
m_failed = false;
|
|
return;
|
|
}
|
|
}
|
|
#ifndef PORTAL // FIXME: In portal we query visibility multiple times per frame because of portal renders!
|
|
Assert ( ( m_frameIssued != gpGlobals->framecount ) || UseVR() );
|
|
#endif
|
|
|
|
m_frameIssued = gpGlobals->framecount;
|
|
m_wasQueriedThisFrame = false;
|
|
m_failed = false;
|
|
}
|
|
|
|
void CPixelVisibilityQuery::IssueCountingQuery( IMatRenderContext *pRenderContext, float proxySize, float proxyAspect, IMaterial *pMaterial, bool sizeIsScreenSpace )
|
|
{
|
|
if ( !m_failed )
|
|
{
|
|
Assert( IsValid() );
|
|
#if 0
|
|
// this centers it on the screen.
|
|
// This is nice because it makes the glows fade as they get partially clipped by the view frustum
|
|
// But it introduces sub-pixel errors (off by one row/column of pixels) so the glows shimmer
|
|
// UNDONE: Compute an offset center coord that matches sub-pixel coords with the real glow position
|
|
// UNDONE: Or frustum clip the sphere/geometry and fade based on proxy size
|
|
Vector origin = m_origin - CurrentViewOrigin();
|
|
float dot = DotProduct(CurrentViewForward(), origin);
|
|
origin = CurrentViewOrigin() + dot * CurrentViewForward();
|
|
#endif
|
|
PixelVisibility_DrawProxy( pRenderContext, m_queryHandleCount, m_origin, proxySize, proxyAspect, pMaterial, sizeIsScreenSpace );
|
|
}
|
|
}
|
|
|
|
//Precache the effects
|
|
CLIENTEFFECT_REGISTER_BEGIN( PrecacheOcclusionProxy )
|
|
CLIENTEFFECT_MATERIAL( "engine/occlusionproxy" )
|
|
CLIENTEFFECT_MATERIAL( "engine/occlusionproxy_countdraw" )
|
|
CLIENTEFFECT_REGISTER_END()
|
|
|
|
class CPixelVisibilitySystem : public CAutoGameSystem
|
|
{
|
|
public:
|
|
|
|
// GameSystem: Level init, shutdown
|
|
virtual void LevelInitPreEntity();
|
|
virtual void LevelShutdownPostEntity();
|
|
|
|
// locals
|
|
CPixelVisibilitySystem();
|
|
float GetFractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle );
|
|
void EndView();
|
|
void EndScene();
|
|
unsigned short FindQueryForView( CPixelVisSet *pSet, int viewID );
|
|
unsigned short FindOrCreateQueryForView( CPixelVisSet *pSet, int viewID );
|
|
|
|
void DeleteUnusedQueries( CPixelVisSet *pSet, bool bDeleteAll );
|
|
void DeleteUnusedSets( bool bDeleteAll );
|
|
void ShowQueries( bool show );
|
|
unsigned short AllocQuery();
|
|
unsigned short AllocSet();
|
|
void FreeSet( unsigned short node );
|
|
CPixelVisSet *FindOrCreatePixelVisSet( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle );
|
|
bool SupportsOcclusion() { return m_hwCanTestGlows; }
|
|
void DebugInfo()
|
|
{
|
|
Msg("Pixel vis system using %d sets total (%d in free list), %d queries total (%d in free list)\n",
|
|
m_setList.TotalCount(), m_setList.Count(m_freeSetsList), m_queryList.TotalCount(), m_queryList.Count( m_freeQueriesList ) );
|
|
}
|
|
|
|
private:
|
|
CUtlMultiList< CPixelVisSet, unsigned short > m_setList;
|
|
CUtlMultiList<CPixelVisibilityQuery, unsigned short> m_queryList;
|
|
unsigned short m_freeQueriesList;
|
|
unsigned short m_activeSetsList;
|
|
unsigned short m_freeSetsList;
|
|
unsigned short m_pad0;
|
|
|
|
IMaterial *m_pProxyMaterial;
|
|
IMaterial *m_pDrawMaterial;
|
|
bool m_hwCanTestGlows;
|
|
bool m_drawQueries;
|
|
|
|
|
|
friend void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID ); //need direct access to private data to make shifting smooth
|
|
};
|
|
|
|
static CPixelVisibilitySystem g_PixelVisibilitySystem;
|
|
|
|
CPixelVisibilitySystem::CPixelVisibilitySystem() : CAutoGameSystem( "CPixelVisibilitySystem" )
|
|
{
|
|
m_hwCanTestGlows = true;
|
|
m_drawQueries = false;
|
|
}
|
|
// Level init, shutdown
|
|
void CPixelVisibilitySystem::LevelInitPreEntity()
|
|
{
|
|
bool fastqueries = HasFastQueries();
|
|
// printf("\n ** fast queries: %s **", fastqueries?"true":"false" );
|
|
|
|
m_hwCanTestGlows = r_dopixelvisibility.GetBool() && fastqueries && engine->GetDXSupportLevel() >= 80;
|
|
if ( m_hwCanTestGlows )
|
|
{
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
OcclusionQueryObjectHandle_t query = pRenderContext->CreateOcclusionQueryObject();
|
|
if ( query != INVALID_OCCLUSION_QUERY_OBJECT_HANDLE )
|
|
{
|
|
pRenderContext->DestroyOcclusionQueryObject( query );
|
|
}
|
|
else
|
|
{
|
|
m_hwCanTestGlows = false;
|
|
}
|
|
}
|
|
|
|
m_pProxyMaterial = materials->FindMaterial("engine/occlusionproxy", TEXTURE_GROUP_CLIENT_EFFECTS);
|
|
m_pProxyMaterial->IncrementReferenceCount();
|
|
m_pDrawMaterial = materials->FindMaterial("engine/occlusionproxy_countdraw", TEXTURE_GROUP_CLIENT_EFFECTS);
|
|
m_pDrawMaterial->IncrementReferenceCount();
|
|
m_freeQueriesList = m_queryList.CreateList();
|
|
m_activeSetsList = m_setList.CreateList();
|
|
m_freeSetsList = m_setList.CreateList();
|
|
}
|
|
|
|
void CPixelVisibilitySystem::LevelShutdownPostEntity()
|
|
{
|
|
m_pProxyMaterial->DecrementReferenceCount();
|
|
m_pProxyMaterial = NULL;
|
|
m_pDrawMaterial->DecrementReferenceCount();
|
|
m_pDrawMaterial = NULL;
|
|
DeleteUnusedSets(true);
|
|
m_setList.Purge();
|
|
m_queryList.Purge();
|
|
m_freeQueriesList = m_queryList.InvalidIndex();
|
|
m_activeSetsList = m_setList.InvalidIndex();
|
|
m_freeSetsList = m_setList.InvalidIndex();
|
|
}
|
|
|
|
float CPixelVisibilitySystem::GetFractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle )
|
|
{
|
|
if ( !m_hwCanTestGlows || building_cubemaps.GetBool() )
|
|
{
|
|
return GlowSightDistance( params.position, true ) > 0 ? 1.0f : 0.0f;
|
|
}
|
|
if ( CurrentViewID() < 0 )
|
|
return 0.0f;
|
|
|
|
CPixelVisSet *pSet = FindOrCreatePixelVisSet( params, queryHandle );
|
|
Assert( pSet );
|
|
unsigned short node = FindOrCreateQueryForView( pSet, CurrentViewID() );
|
|
m_queryList[node].m_origin = params.position;
|
|
float fraction = m_queryList[node].GetFractionVisible( pSet->fadeTimeInv );
|
|
pSet->MarkActive();
|
|
return fraction;
|
|
}
|
|
|
|
void CPixelVisibilitySystem::EndView()
|
|
{
|
|
if ( !PixelVisibility_IsAvailable() && CurrentViewID() >= 0 )
|
|
return;
|
|
|
|
if ( m_setList.Head( m_activeSetsList ) == m_setList.InvalidIndex() )
|
|
return;
|
|
|
|
CMatRenderContextPtr pRenderContext( materials );
|
|
|
|
IMaterial *pProxy = m_drawQueries ? m_pDrawMaterial : m_pProxyMaterial;
|
|
pRenderContext->Bind( pProxy );
|
|
|
|
// BUGBUG: If you draw both queries, the measure query fails for some reason.
|
|
if ( r_pixelvisibility_partial.GetBool() && !m_drawQueries )
|
|
{
|
|
pRenderContext->DepthRange( 0.0f, 0.01f );
|
|
unsigned short node = m_setList.Head( m_activeSetsList );
|
|
while( node != m_setList.InvalidIndex() )
|
|
{
|
|
CPixelVisSet *pSet = &m_setList[node];
|
|
unsigned short queryNode = FindQueryForView( pSet, CurrentViewID() );
|
|
if ( queryNode != m_queryList.InvalidIndex() )
|
|
{
|
|
m_queryList[queryNode].IssueCountingQuery( pRenderContext, pSet->proxySize, pSet->proxyAspect, pProxy, pSet->sizeIsScreenSpace );
|
|
}
|
|
node = m_setList.Next( node );
|
|
}
|
|
pRenderContext->DepthRange( 0.0f, 1.0f );
|
|
}
|
|
|
|
{
|
|
unsigned short node = m_setList.Head( m_activeSetsList );
|
|
while( node != m_setList.InvalidIndex() )
|
|
{
|
|
CPixelVisSet *pSet = &m_setList[node];
|
|
unsigned short queryNode = FindQueryForView( pSet, CurrentViewID() );
|
|
if ( queryNode != m_queryList.InvalidIndex() )
|
|
{
|
|
m_queryList[queryNode].IssueQuery( pRenderContext, pSet->proxySize, pSet->proxyAspect, pProxy, pSet->sizeIsScreenSpace );
|
|
}
|
|
node = m_setList.Next( node );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPixelVisibilitySystem::EndScene()
|
|
{
|
|
DeleteUnusedSets(false);
|
|
}
|
|
|
|
unsigned short CPixelVisibilitySystem::FindQueryForView( CPixelVisSet *pSet, int viewID )
|
|
{
|
|
unsigned short node = m_queryList.Head( pSet->queryList );
|
|
while ( node != m_queryList.InvalidIndex() )
|
|
{
|
|
if ( m_queryList[node].IsForView( viewID ) )
|
|
return node;
|
|
node = m_queryList.Next( node );
|
|
}
|
|
return m_queryList.InvalidIndex();
|
|
}
|
|
unsigned short CPixelVisibilitySystem::FindOrCreateQueryForView( CPixelVisSet *pSet, int viewID )
|
|
{
|
|
unsigned short node = FindQueryForView( pSet, viewID );
|
|
if ( node != m_queryList.InvalidIndex() )
|
|
return node;
|
|
|
|
node = AllocQuery();
|
|
m_queryList.LinkToHead( pSet->queryList, node );
|
|
m_queryList[node].SetView( viewID );
|
|
return node;
|
|
}
|
|
|
|
|
|
void CPixelVisibilitySystem::DeleteUnusedQueries( CPixelVisSet *pSet, bool bDeleteAll )
|
|
{
|
|
unsigned short node = m_queryList.Head( pSet->queryList );
|
|
while ( node != m_queryList.InvalidIndex() )
|
|
{
|
|
unsigned short next = m_queryList.Next( node );
|
|
if ( bDeleteAll || !m_queryList[node].IsActive() )
|
|
{
|
|
m_queryList.Unlink( pSet->queryList, node);
|
|
m_queryList.LinkToHead( m_freeQueriesList, node );
|
|
}
|
|
node = next;
|
|
}
|
|
}
|
|
void CPixelVisibilitySystem::DeleteUnusedSets( bool bDeleteAll )
|
|
{
|
|
unsigned short node = m_setList.Head( m_activeSetsList );
|
|
while ( node != m_setList.InvalidIndex() )
|
|
{
|
|
unsigned short next = m_setList.Next( node );
|
|
CPixelVisSet *pSet = &m_setList[node];
|
|
if ( bDeleteAll || !m_setList[node].IsActive() )
|
|
{
|
|
DeleteUnusedQueries( pSet, true );
|
|
}
|
|
else
|
|
{
|
|
DeleteUnusedQueries( pSet, false );
|
|
}
|
|
if ( m_queryList.Head(pSet->queryList) == m_queryList.InvalidIndex() )
|
|
{
|
|
FreeSet( node );
|
|
}
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
void CPixelVisibilitySystem::ShowQueries( bool show )
|
|
{
|
|
m_drawQueries = show;
|
|
}
|
|
|
|
unsigned short CPixelVisibilitySystem::AllocQuery()
|
|
{
|
|
unsigned short node = m_queryList.Head(m_freeQueriesList);
|
|
if ( node != m_queryList.InvalidIndex() )
|
|
{
|
|
m_queryList.Unlink( m_freeQueriesList, node );
|
|
m_queryList[node].ResetOcclusionQueries();
|
|
}
|
|
else
|
|
{
|
|
node = m_queryList.Alloc();
|
|
}
|
|
return node;
|
|
}
|
|
|
|
unsigned short CPixelVisibilitySystem::AllocSet()
|
|
{
|
|
unsigned short node = m_setList.Head(m_freeSetsList);
|
|
if ( node != m_setList.InvalidIndex() )
|
|
{
|
|
m_setList.Unlink( m_freeSetsList, node );
|
|
}
|
|
else
|
|
{
|
|
node = m_setList.Alloc();
|
|
m_setList[node].queryList = m_queryList.CreateList();
|
|
}
|
|
m_setList.LinkToHead( m_activeSetsList, node );
|
|
return node;
|
|
}
|
|
|
|
void CPixelVisibilitySystem::FreeSet( unsigned short node )
|
|
{
|
|
m_setList.Unlink( m_activeSetsList, node );
|
|
m_setList.LinkToHead( m_freeSetsList, node );
|
|
m_setList[node].serial++;
|
|
}
|
|
|
|
CPixelVisSet *CPixelVisibilitySystem::FindOrCreatePixelVisSet( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle )
|
|
{
|
|
if ( queryHandle[0] )
|
|
{
|
|
unsigned short handle = queryHandle[0] & 0xFFFF;
|
|
handle--;
|
|
unsigned short serial = queryHandle[0] >> 16;
|
|
if ( m_setList.IsValidIndex(handle) && m_setList[handle].serial == serial )
|
|
{
|
|
return &m_setList[handle];
|
|
}
|
|
}
|
|
|
|
unsigned short node = AllocSet();
|
|
m_setList[node].Init( params );
|
|
unsigned int out = m_setList[node].serial;
|
|
unsigned short nodeHandle = node + 1;
|
|
out <<= 16;
|
|
out |= nodeHandle;
|
|
queryHandle[0] = out;
|
|
return &m_setList[node];
|
|
}
|
|
|
|
|
|
void PixelvisDrawChanged( IConVar *pPixelvisVar, const char *pOld, float flOldValue )
|
|
{
|
|
ConVarRef var( pPixelvisVar );
|
|
g_PixelVisibilitySystem.ShowQueries( var.GetBool() );
|
|
}
|
|
|
|
class CTraceFilterGlow : public CTraceFilterSimple
|
|
{
|
|
public:
|
|
DECLARE_CLASS( CTraceFilterGlow, CTraceFilterSimple );
|
|
|
|
CTraceFilterGlow( const IHandleEntity *passentity, int collisionGroup ) : CTraceFilterSimple(passentity, collisionGroup) {}
|
|
virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
|
|
{
|
|
IClientUnknown *pUnk = (IClientUnknown*)pHandleEntity;
|
|
ICollideable *pCollide = pUnk->GetCollideable();
|
|
if ( pCollide->GetSolid() != SOLID_VPHYSICS && pCollide->GetSolid() != SOLID_BSP )
|
|
return false;
|
|
return BaseClass::ShouldHitEntity( pHandleEntity, contentsMask );
|
|
}
|
|
};
|
|
float GlowSightDistance( const Vector &glowOrigin, bool bShouldTrace )
|
|
{
|
|
float dist = (glowOrigin - CurrentViewOrigin()).Length();
|
|
C_BasePlayer *local = C_BasePlayer::GetLocalPlayer();
|
|
if ( local )
|
|
{
|
|
dist *= local->GetFOVDistanceAdjustFactor();
|
|
}
|
|
|
|
if ( bShouldTrace )
|
|
{
|
|
Vector end = glowOrigin;
|
|
// HACKHACK: trace 4" from destination in case the glow is inside some parent object
|
|
// allow a little error...
|
|
if ( dist > 4 )
|
|
{
|
|
end -= CurrentViewForward()*4;
|
|
}
|
|
int traceFlags = MASK_OPAQUE|CONTENTS_MONSTER|CONTENTS_DEBRIS;
|
|
|
|
CTraceFilterGlow filter(NULL, COLLISION_GROUP_NONE);
|
|
trace_t tr;
|
|
UTIL_TraceLine( CurrentViewOrigin(), end, traceFlags, &filter, &tr );
|
|
if ( tr.fraction != 1.0f )
|
|
return -1;
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
void PixelVisibility_EndCurrentView()
|
|
{
|
|
g_PixelVisibilitySystem.EndView();
|
|
}
|
|
|
|
void PixelVisibility_EndScene()
|
|
{
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
|
|
|
|
g_PixelVisibilitySystem.EndScene();
|
|
}
|
|
|
|
float PixelVisibility_FractionVisible( const pixelvis_queryparams_t ¶ms, pixelvis_handle_t *queryHandle )
|
|
{
|
|
if ( !queryHandle )
|
|
{
|
|
return GlowSightDistance( params.position, true ) > 0.0f ? 1.0f : 0.0f;
|
|
}
|
|
else
|
|
{
|
|
return g_PixelVisibilitySystem.GetFractionVisible( params, queryHandle );
|
|
}
|
|
}
|
|
|
|
bool PixelVisibility_IsAvailable()
|
|
{
|
|
bool fastqueries = HasFastQueries();
|
|
return r_dopixelvisibility.GetBool() && fastqueries && g_PixelVisibilitySystem.SupportsOcclusion();
|
|
}
|
|
|
|
//this originally called a class function of CPixelVisibiltySystem to keep the work clean, but that function needed friend access to CPixelVisibilityQuery
|
|
//and I didn't want to make the whole class a friend or shift all the functions and class declarations around in this file
|
|
void PixelVisibility_ShiftVisibilityViews( int iSourceViewID, int iDestViewID )
|
|
{
|
|
unsigned short node = g_PixelVisibilitySystem.m_setList.Head( g_PixelVisibilitySystem.m_activeSetsList );
|
|
while ( node != g_PixelVisibilitySystem.m_setList.InvalidIndex() )
|
|
{
|
|
unsigned short next = g_PixelVisibilitySystem.m_setList.Next( node );
|
|
CPixelVisSet *pSet = &g_PixelVisibilitySystem.m_setList[node];
|
|
|
|
unsigned short iSourceQueryNode = g_PixelVisibilitySystem.FindQueryForView( pSet, iSourceViewID );
|
|
unsigned short iDestQueryNode = g_PixelVisibilitySystem.FindQueryForView( pSet, iDestViewID );
|
|
|
|
if( iDestQueryNode != g_PixelVisibilitySystem.m_queryList.InvalidIndex() )
|
|
{
|
|
//delete the destination if found
|
|
g_PixelVisibilitySystem.m_queryList.Unlink( pSet->queryList, iDestQueryNode );
|
|
g_PixelVisibilitySystem.m_queryList.LinkToHead( g_PixelVisibilitySystem.m_freeQueriesList, iDestQueryNode );
|
|
|
|
if ( g_PixelVisibilitySystem.m_queryList.Head(pSet->queryList) == g_PixelVisibilitySystem.m_queryList.InvalidIndex() )
|
|
{
|
|
g_PixelVisibilitySystem.FreeSet( node );
|
|
}
|
|
}
|
|
|
|
if( iSourceQueryNode != g_PixelVisibilitySystem.m_queryList.InvalidIndex() )
|
|
{
|
|
//make the source believe it's the destination
|
|
g_PixelVisibilitySystem.m_queryList[iSourceQueryNode].m_viewID = iDestViewID;
|
|
}
|
|
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
CON_COMMAND( pixelvis_debug, "Dump debug info" )
|
|
{
|
|
g_PixelVisibilitySystem.DebugInfo();
|
|
}
|