You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
881 lines
27 KiB
881 lines
27 KiB
//========= 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)(intp)m_queryHandle, pixelsPossible, (int)(intp)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)(intp)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)(intp)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(); |
|
}
|
|
|