sourcearena/mp/src/game/client/c_pixel_visibility.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

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 &params );
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 &params )
{
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 &params, 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 &params, 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 &params, 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 &params, 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 &params, 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();
}