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", FCVAR_ALLOWED_IN_COMPETITIVE );
|
|
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();
|
|
}
|