//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $Revision: $ // $NoKeywords: $ // // This file contains code to allow us to associate client data with bsp leaves. //===========================================================================// #include "cbase.h" #include "clientleafsystem.h" #include "utlbidirectionalset.h" #include "model_types.h" #include "ivrenderview.h" #include "tier0/vprof.h" #include "bsptreedata.h" #include "detailobjectsystem.h" #include "engine/IStaticPropMgr.h" #include "engine/ivdebugoverlay.h" #include "vstdlib/jobthread.h" #include "tier1/utllinkedlist.h" #include "datacache/imdlcache.h" #include "view.h" #include "viewrender.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" class VMatrix; // forward decl static ConVar cl_drawleaf("cl_drawleaf", "-1", FCVAR_CHEAT ); static ConVar r_PortalTestEnts( "r_PortalTestEnts", "1", FCVAR_CHEAT, "Clip entities against portal frustums." ); static ConVar r_portalsopenall( "r_portalsopenall", "0", FCVAR_CHEAT, "Open all portals" ); static ConVar cl_threaded_client_leaf_system("cl_threaded_client_leaf_system", "0" ); DEFINE_FIXEDSIZE_ALLOCATOR( CClientRenderablesList, 1, CUtlMemoryPool::GROW_SLOW ); //----------------------------------------------------------------------------- // Threading helpers //----------------------------------------------------------------------------- static void FrameLock() { mdlcache->BeginLock(); } static void FrameUnlock() { mdlcache->EndLock(); } static void CallComputeFXBlend( IClientRenderable *&pRenderable ) { pRenderable->ComputeFxBlend(); } //----------------------------------------------------------------------------- // The client leaf system //----------------------------------------------------------------------------- class CClientLeafSystem : public IClientLeafSystem, public ISpatialLeafEnumerator { public: virtual char const *Name() { return "CClientLeafSystem"; } // constructor, destructor CClientLeafSystem(); virtual ~CClientLeafSystem(); // Methods of IClientSystem bool Init() { return true; } void PostInit() {} void Shutdown() {} virtual bool IsPerFrame() { return true; } void PreRender(); void PostRender() { } void Update( float frametime ) { } void LevelInitPreEntity(); void LevelInitPostEntity() {} void LevelShutdownPreEntity(); void LevelShutdownPostEntity(); virtual void OnSave() {} virtual void OnRestore() {} virtual void SafeRemoveIfDesired() {} // Methods of IClientLeafSystem public: virtual void AddRenderable( IClientRenderable* pRenderable, RenderGroup_t group ); virtual bool IsRenderableInPVS( IClientRenderable *pRenderable ); virtual void CreateRenderableHandle( IClientRenderable* pRenderable, bool bIsStaticProp ); virtual void RemoveRenderable( ClientRenderHandle_t handle ); virtual void SetSubSystemDataInLeaf( int leaf, int nSubSystemIdx, CClientLeafSubSystemData *pData ); virtual CClientLeafSubSystemData *GetSubSystemDataInLeaf( int leaf, int nSubSystemIdx ); // FIXME: There's an incestuous relationship between DetailObjectSystem // and the ClientLeafSystem. Maybe they should be the same system? virtual void GetDetailObjectsInLeaf( int leaf, int& firstDetailObject, int& detailObjectCount ); virtual void SetDetailObjectsInLeaf( int leaf, int firstDetailObject, int detailObjectCount ); virtual void DrawDetailObjectsInLeaf( int leaf, int frameNumber, int& nFirstDetailObject, int& nDetailObjectCount ); virtual bool ShouldDrawDetailObjectsInLeaf( int leaf, int frameNumber ); virtual void RenderableChanged( ClientRenderHandle_t handle ); virtual void SetRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ); virtual void ComputeTranslucentRenderLeaf( int count, const LeafIndex_t *pLeafList, const LeafFogVolume_t *pLeafFogVolumeList, int frameNumber, int viewID ); virtual void CollateViewModelRenderables( CUtlVector< IClientRenderable * >& opaque, CUtlVector< IClientRenderable * >& translucent ); virtual void BuildRenderablesList( const SetupRenderInfo_t &info ); void CollateRenderablesInLeaf( int leaf, int worldListLeafIndex, const SetupRenderInfo_t &info ); virtual void DrawStaticProps( bool enable ); virtual void DrawSmallEntities( bool enable ); virtual void EnableAlternateSorting( ClientRenderHandle_t handle, bool bEnable ); // Adds a renderable to a set of leaves virtual void AddRenderableToLeaves( ClientRenderHandle_t handle, int nLeafCount, unsigned short *pLeaves ); // The following methods are related to shadows... virtual ClientLeafShadowHandle_t AddShadow( ClientShadowHandle_t userId, unsigned short flags ); virtual void RemoveShadow( ClientLeafShadowHandle_t h ); virtual void ProjectShadow( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ); virtual void ProjectFlashlight( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ); // Find all shadow casters in a set of leaves virtual void EnumerateShadowsInLeaves( int leafCount, LeafIndex_t* pLeaves, IClientLeafShadowEnum* pEnum ); // methods of ISpatialLeafEnumerator public: bool EnumerateLeaf( int leaf, int context ); // Adds a shadow to a leaf void AddShadowToLeaf( int leaf, ClientLeafShadowHandle_t handle ); // Fill in a list of the leaves this renderable is in. // Returns -1 if the handle is invalid. int GetRenderableLeaves( ClientRenderHandle_t handle, int leaves[128] ); // Get leaves this renderable is in virtual bool GetRenderableLeaf ( ClientRenderHandle_t handle, int* pOutLeaf, const int* pInIterator = 0, int* pOutIterator = 0 ); // Singleton instance... static CClientLeafSystem s_ClientLeafSystem; private: // Creates a new renderable void NewRenderable( IClientRenderable* pRenderable, RenderGroup_t type, int flags = 0 ); // Adds a renderable to the list of renderables void AddRenderableToLeaf( int leaf, ClientRenderHandle_t handle ); void SortEntities( const Vector &vecRenderOrigin, const Vector &vecRenderForward, CClientRenderablesList::CEntry *pEntities, int nEntities ); // Returns -1 if the renderable spans more than one area. If it's totally in one area, then this returns the leaf. short GetRenderableArea( ClientRenderHandle_t handle ); // remove renderables from leaves void InsertIntoTree( ClientRenderHandle_t &handle ); void RemoveFromTree( ClientRenderHandle_t handle ); // Returns if it's a view model render group inline bool IsViewModelRenderGroup( RenderGroup_t group ) const; // Adds, removes renderables from view model list void AddToViewModelList( ClientRenderHandle_t handle ); void RemoveFromViewModelList( ClientRenderHandle_t handle ); // Insert translucent renderables into list of translucent objects void InsertTranslucentRenderable( IClientRenderable* pRenderable, int& count, IClientRenderable** pList, float* pDist ); // Used to change renderables from translucent to opaque // Only really used by the static prop fading... void ChangeRenderableRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ); // Adds a shadow to a leaf/removes shadow from renderable void AddShadowToRenderable( ClientRenderHandle_t renderHandle, ClientLeafShadowHandle_t shadowHandle ); void RemoveShadowFromRenderables( ClientLeafShadowHandle_t handle ); // Adds a shadow to a leaf/removes shadow from renderable bool ShouldRenderableReceiveShadow( ClientRenderHandle_t renderHandle, int nShadowFlags ); // Adds a shadow to a leaf/removes shadow from leaf void RemoveShadowFromLeaves( ClientLeafShadowHandle_t handle ); // Methods associated with the various bi-directional sets static unsigned int& FirstRenderableInLeaf( int leaf ) { return s_ClientLeafSystem.m_Leaf[leaf].m_FirstElement; } static unsigned int& FirstLeafInRenderable( unsigned short renderable ) { return s_ClientLeafSystem.m_Renderables[renderable].m_LeafList; } static unsigned short& FirstShadowInLeaf( int leaf ) { return s_ClientLeafSystem.m_Leaf[leaf].m_FirstShadow; } static unsigned short& FirstLeafInShadow( ClientLeafShadowHandle_t shadow ) { return s_ClientLeafSystem.m_Shadows[shadow].m_FirstLeaf; } static unsigned short& FirstShadowOnRenderable( unsigned short renderable ) { return s_ClientLeafSystem.m_Renderables[renderable].m_FirstShadow; } static unsigned short& FirstRenderableInShadow( ClientLeafShadowHandle_t shadow ) { return s_ClientLeafSystem.m_Shadows[shadow].m_FirstRenderable; } void FrameLock() { mdlcache->BeginLock(); } void FrameUnlock() { mdlcache->EndLock(); } private: enum { RENDER_FLAGS_TWOPASS = 0x01, RENDER_FLAGS_STATIC_PROP = 0x02, RENDER_FLAGS_BRUSH_MODEL = 0x04, RENDER_FLAGS_STUDIO_MODEL = 0x08, RENDER_FLAGS_HASCHANGED = 0x10, RENDER_FLAGS_ALTERNATE_SORTING = 0x20, }; // All the information associated with a particular handle struct RenderableInfo_t { IClientRenderable* m_pRenderable; int m_RenderFrame; // which frame did I render it in? int m_RenderFrame2; int m_EnumCount; // Have I been added to a particular shadow yet? int m_TranslucencyCalculated; unsigned int m_LeafList; // What leafs is it in? unsigned int m_RenderLeaf; // What leaf do I render in? unsigned char m_Flags; // rendering flags unsigned char m_RenderGroup; // RenderGroup_t type unsigned short m_FirstShadow; // The first shadow caster that cast on it short m_Area; // -1 if the renderable spans multiple areas. signed char m_TranslucencyCalculatedView; }; // The leaf contains an index into a list of renderables struct ClientLeaf_t { unsigned int m_FirstElement; unsigned short m_FirstShadow; unsigned short m_FirstDetailProp; unsigned short m_DetailPropCount; int m_DetailPropRenderFrame; CClientLeafSubSystemData *m_pSubSystemData[N_CLSUBSYSTEMS]; }; // Shadow information struct ShadowInfo_t { unsigned short m_FirstLeaf; unsigned short m_FirstRenderable; int m_EnumCount; ClientShadowHandle_t m_Shadow; unsigned short m_Flags; }; struct EnumResult_t { int leaf; EnumResult_t *pNext; }; struct EnumResultList_t { EnumResult_t *pHead; ClientRenderHandle_t handle; }; // Stores data associated with each leaf. CUtlVector< ClientLeaf_t > m_Leaf; // Stores all unique non-detail renderables CUtlLinkedList< RenderableInfo_t, ClientRenderHandle_t, false, unsigned int > m_Renderables; // Information associated with shadows registered with the client leaf system CUtlLinkedList< ShadowInfo_t, ClientLeafShadowHandle_t, false, unsigned int > m_Shadows; // Maintains the list of all renderables in a particular leaf CBidirectionalSet< int, ClientRenderHandle_t, unsigned int, unsigned int > m_RenderablesInLeaf; // Maintains a list of all shadows in a particular leaf CBidirectionalSet< int, ClientLeafShadowHandle_t, unsigned short, unsigned int > m_ShadowsInLeaf; // Maintains a list of all shadows cast on a particular renderable CBidirectionalSet< ClientRenderHandle_t, ClientLeafShadowHandle_t, unsigned short, unsigned int > m_ShadowsOnRenderable; // Dirty list of renderables CUtlVector< ClientRenderHandle_t > m_DirtyRenderables; // List of renderables in view model render groups CUtlVector< ClientRenderHandle_t > m_ViewModels; // Should I draw static props? bool m_DrawStaticProps; bool m_DrawSmallObjects; // A little enumerator to help us when adding shadows to renderables int m_ShadowEnum; CTSList m_DeferredInserts; }; //----------------------------------------------------------------------------- // Expose IClientLeafSystem to the client dll. //----------------------------------------------------------------------------- CClientLeafSystem CClientLeafSystem::s_ClientLeafSystem; IClientLeafSystem *g_pClientLeafSystem = &CClientLeafSystem::s_ClientLeafSystem; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CClientLeafSystem, IClientLeafSystem, CLIENTLEAFSYSTEM_INTERFACE_VERSION, CClientLeafSystem::s_ClientLeafSystem ); void CalcRenderableWorldSpaceAABB_Fast( IClientRenderable *pRenderable, Vector &absMin, Vector &absMax ); //----------------------------------------------------------------------------- // Helper functions. //----------------------------------------------------------------------------- void DefaultRenderBoundsWorldspace( IClientRenderable *pRenderable, Vector &absMins, Vector &absMaxs ) { // Tracker 37433: This fixes a bug where if the stunstick is being wielded by a combine soldier, the fact that the stick was // attached to the soldier's hand would move it such that it would get frustum culled near the edge of the screen. IClientUnknown *pUnk = pRenderable->GetIClientUnknown(); C_BaseEntity *pEnt = pUnk->GetBaseEntity(); if ( pEnt && pEnt->IsFollowingEntity() ) { C_BaseEntity *pParent = pEnt->GetFollowedEntity(); if ( pParent ) { // Get the parent's abs space world bounds. CalcRenderableWorldSpaceAABB_Fast( pParent, absMins, absMaxs ); // Add the maximum of our local render bounds. This is making the assumption that we can be at any // point and at any angle within the parent's world space bounds. Vector vAddMins, vAddMaxs; pEnt->GetRenderBounds( vAddMins, vAddMaxs ); // if our origin is actually farther away than that, expand again float radius = pEnt->GetLocalOrigin().Length(); float flBloatSize = MAX( vAddMins.Length(), vAddMaxs.Length() ); flBloatSize = MAX(flBloatSize, radius); absMins -= Vector( flBloatSize, flBloatSize, flBloatSize ); absMaxs += Vector( flBloatSize, flBloatSize, flBloatSize ); return; } } Vector mins, maxs; pRenderable->GetRenderBounds( mins, maxs ); // FIXME: Should I just use a sphere here? // Another option is to pass the OBB down the tree; makes for a better fit // Generate a world-aligned AABB const QAngle& angles = pRenderable->GetRenderAngles(); const Vector& origin = pRenderable->GetRenderOrigin(); if (angles == vec3_angle) { VectorAdd( mins, origin, absMins ); VectorAdd( maxs, origin, absMaxs ); } else { matrix3x4_t boxToWorld; AngleMatrix( angles, origin, boxToWorld ); TransformAABB( boxToWorld, mins, maxs, absMins, absMaxs ); } Assert( absMins.IsValid() && absMaxs.IsValid() ); } // Figure out a world space bounding box that encloses the entity's local render bounds in world space. inline void CalcRenderableWorldSpaceAABB( IClientRenderable *pRenderable, Vector &absMins, Vector &absMaxs ) { pRenderable->GetRenderBoundsWorldspace( absMins, absMaxs ); } // This gets an AABB for the renderable, but it doesn't cause a parent's bones to be setup. // This is used for placement in the leaves, but the more expensive version is used for culling. void CalcRenderableWorldSpaceAABB_Fast( IClientRenderable *pRenderable, Vector &absMin, Vector &absMax ) { C_BaseEntity *pEnt = pRenderable->GetIClientUnknown()->GetBaseEntity(); if ( pEnt && pEnt->IsFollowingEntity() ) { C_BaseEntity *pParent = pEnt->GetMoveParent(); Assert( pParent ); // Get the parent's abs space world bounds. CalcRenderableWorldSpaceAABB_Fast( pParent, absMin, absMax ); // Add the maximum of our local render bounds. This is making the assumption that we can be at any // point and at any angle within the parent's world space bounds. Vector vAddMins, vAddMaxs; pEnt->GetRenderBounds( vAddMins, vAddMaxs ); // if our origin is actually farther away than that, expand again float radius = pEnt->GetLocalOrigin().Length(); float flBloatSize = MAX( vAddMins.Length(), vAddMaxs.Length() ); flBloatSize = MAX(flBloatSize, radius); absMin -= Vector( flBloatSize, flBloatSize, flBloatSize ); absMax += Vector( flBloatSize, flBloatSize, flBloatSize ); } else { // Start out with our own render bounds. Since we don't have a parent, this won't incur any nasty CalcRenderableWorldSpaceAABB( pRenderable, absMin, absMax ); } } //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- CClientLeafSystem::CClientLeafSystem() : m_DrawStaticProps(true), m_DrawSmallObjects(true) { // Set up the bi-directional lists... m_RenderablesInLeaf.Init( FirstRenderableInLeaf, FirstLeafInRenderable ); m_ShadowsInLeaf.Init( FirstShadowInLeaf, FirstLeafInShadow ); m_ShadowsOnRenderable.Init( FirstShadowOnRenderable, FirstRenderableInShadow ); } CClientLeafSystem::~CClientLeafSystem() { } //----------------------------------------------------------------------------- // Activate, deactivate static props //----------------------------------------------------------------------------- void CClientLeafSystem::DrawStaticProps( bool enable ) { m_DrawStaticProps = enable; } void CClientLeafSystem::DrawSmallEntities( bool enable ) { m_DrawSmallObjects = enable; } //----------------------------------------------------------------------------- // Level init, shutdown //----------------------------------------------------------------------------- void CClientLeafSystem::LevelInitPreEntity() { MEM_ALLOC_CREDIT(); m_Renderables.EnsureCapacity( 1024 ); m_RenderablesInLeaf.EnsureCapacity( 1024 ); m_ShadowsInLeaf.EnsureCapacity( 256 ); m_ShadowsOnRenderable.EnsureCapacity( 256 ); m_DirtyRenderables.EnsureCapacity( 256 ); // Add all the leaves we'll need int leafCount = engine->LevelLeafCount(); m_Leaf.EnsureCapacity( leafCount ); ClientLeaf_t newLeaf; newLeaf.m_FirstElement = m_RenderablesInLeaf.InvalidIndex(); newLeaf.m_FirstShadow = m_ShadowsInLeaf.InvalidIndex(); memset( newLeaf.m_pSubSystemData, 0, sizeof( newLeaf.m_pSubSystemData ) ); newLeaf.m_FirstDetailProp = 0; newLeaf.m_DetailPropCount = 0; newLeaf.m_DetailPropRenderFrame = -1; while ( --leafCount >= 0 ) { m_Leaf.AddToTail( newLeaf ); } } void CClientLeafSystem::LevelShutdownPreEntity() { } void CClientLeafSystem::LevelShutdownPostEntity() { m_ViewModels.Purge(); m_Renderables.Purge(); m_RenderablesInLeaf.Purge(); m_Shadows.Purge(); // delete subsystem data for( int i = 0; i < m_Leaf.Count() ; i++ ) { for( int j = 0 ; j < ARRAYSIZE( m_Leaf[i].m_pSubSystemData ) ; j++ ) { if ( m_Leaf[i].m_pSubSystemData[j] ) { delete m_Leaf[i].m_pSubSystemData[j]; m_Leaf[i].m_pSubSystemData[j] = NULL; } } } m_Leaf.Purge(); m_ShadowsInLeaf.Purge(); m_ShadowsOnRenderable.Purge(); m_DirtyRenderables.Purge(); } //----------------------------------------------------------------------------- // This is what happens before rendering a particular view //----------------------------------------------------------------------------- void CClientLeafSystem::PreRender() { VPROF_BUDGET( "CClientLeafSystem::PreRender", "PreRender" ); int i; int nIterations = 0; while ( m_DirtyRenderables.Count() ) { if ( ++nIterations > 10 ) { Warning( "Too many dirty renderables!\n" ); break; } int nDirty = m_DirtyRenderables.Count(); for ( i = nDirty; --i >= 0; ) { ClientRenderHandle_t handle = m_DirtyRenderables[i]; Assert( m_Renderables[ handle ].m_Flags & RENDER_FLAGS_HASCHANGED ); // Update position in leaf system RemoveFromTree( handle ); } bool bThreaded = false;//( nDirty > 5 && cl_threaded_client_leaf_system.GetBool() && g_pThreadPool->NumThreads() ); if ( !bThreaded ) { for ( i = nDirty; --i >= 0; ) { InsertIntoTree( m_DirtyRenderables[i] ); } } else { // InsertIntoTree can result in new renderables being added, so copy: ClientRenderHandle_t *pDirtyRenderables = (ClientRenderHandle_t *)alloca( sizeof(ClientRenderHandle_t) * nDirty ); memcpy( pDirtyRenderables, m_DirtyRenderables.Base(), sizeof(ClientRenderHandle_t) * nDirty ); ParallelProcess( "CClientLeafSystem::PreRender", pDirtyRenderables, nDirty, this, &CClientLeafSystem::InsertIntoTree, &CClientLeafSystem::FrameLock, &CClientLeafSystem::FrameUnlock ); } if ( m_DeferredInserts.Count() ) { EnumResultList_t enumResultList; while ( m_DeferredInserts.PopItem( &enumResultList ) ) { m_ShadowEnum++; while ( enumResultList.pHead ) { EnumResult_t *p = enumResultList.pHead; enumResultList.pHead = p->pNext; AddRenderableToLeaf( p->leaf, enumResultList.handle ); delete p; } } } for ( i = nDirty; --i >= 0; ) { // Cache off the area it's sitting in. ClientRenderHandle_t handle = m_DirtyRenderables[i]; RenderableInfo_t& renderable = m_Renderables[ handle ]; renderable.m_Flags &= ~RENDER_FLAGS_HASCHANGED; m_Renderables[handle].m_Area = GetRenderableArea( handle ); } m_DirtyRenderables.RemoveMultiple( 0, nDirty ); } } //----------------------------------------------------------------------------- // Creates a new renderable //----------------------------------------------------------------------------- void CClientLeafSystem::NewRenderable( IClientRenderable* pRenderable, RenderGroup_t type, int flags ) { Assert( pRenderable ); Assert( pRenderable->RenderHandle() == INVALID_CLIENT_RENDER_HANDLE ); ClientRenderHandle_t handle = m_Renderables.AddToTail(); RenderableInfo_t &info = m_Renderables[handle]; // We need to know if it's a brush model for shadows int modelType = modelinfo->GetModelType( pRenderable->GetModel() ); if (modelType == mod_brush) { flags |= RENDER_FLAGS_BRUSH_MODEL; } else if ( modelType == mod_studio ) { flags |= RENDER_FLAGS_STUDIO_MODEL; } info.m_pRenderable = pRenderable; info.m_RenderFrame = -1; info.m_RenderFrame2 = -1; info.m_TranslucencyCalculated = -1; info.m_TranslucencyCalculatedView = VIEW_ILLEGAL; info.m_FirstShadow = m_ShadowsOnRenderable.InvalidIndex(); info.m_LeafList = m_RenderablesInLeaf.InvalidIndex(); info.m_Flags = flags; info.m_RenderGroup = (unsigned char)type; info.m_EnumCount = 0; info.m_RenderLeaf = m_RenderablesInLeaf.InvalidIndex(); if ( IsViewModelRenderGroup( (RenderGroup_t)info.m_RenderGroup ) ) { AddToViewModelList( handle ); } pRenderable->RenderHandle() = handle; } void CClientLeafSystem::CreateRenderableHandle( IClientRenderable* pRenderable, bool bIsStaticProp ) { // FIXME: The argument is unnecessary if we could get this next line to work // the reason why we can't is because currently there are IClientRenderables // which don't correctly implement GetRefEHandle. //bool bIsStaticProp = staticpropmgr->IsStaticProp( pRenderable->GetIClientUnknown() ); // Add the prop to all the leaves it lies in RenderGroup_t group = pRenderable->IsTransparent() ? RENDER_GROUP_TRANSLUCENT_ENTITY : RENDER_GROUP_OPAQUE_ENTITY; bool bTwoPass = false; if ( group == RENDER_GROUP_TRANSLUCENT_ENTITY ) { bTwoPass = pRenderable->IsTwoPass( ); } int flags = 0; if ( bIsStaticProp ) { flags = RENDER_FLAGS_STATIC_PROP; if ( group == RENDER_GROUP_OPAQUE_ENTITY ) { group = RENDER_GROUP_OPAQUE_STATIC; } } if (bTwoPass) { flags |= RENDER_FLAGS_TWOPASS; } NewRenderable( pRenderable, group, flags ); } //----------------------------------------------------------------------------- // Used to change renderables from translucent to opaque //----------------------------------------------------------------------------- void CClientLeafSystem::ChangeRenderableRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ) { RenderableInfo_t &info = m_Renderables[handle]; info.m_RenderGroup = (unsigned char)group; } //----------------------------------------------------------------------------- // Use alternate translucent sorting algorithm (draw translucent objects in the furthest leaf they lie in) //----------------------------------------------------------------------------- void CClientLeafSystem::EnableAlternateSorting( ClientRenderHandle_t handle, bool bEnable ) { RenderableInfo_t &info = m_Renderables[handle]; if ( bEnable ) { info.m_Flags |= RENDER_FLAGS_ALTERNATE_SORTING; } else { info.m_Flags &= ~RENDER_FLAGS_ALTERNATE_SORTING; } } //----------------------------------------------------------------------------- // Add/remove renderable //----------------------------------------------------------------------------- void CClientLeafSystem::AddRenderable( IClientRenderable* pRenderable, RenderGroup_t group ) { // force a relink we we try to draw it for the first time int flags = RENDER_FLAGS_HASCHANGED; if ( group == RENDER_GROUP_TWOPASS ) { group = RENDER_GROUP_TRANSLUCENT_ENTITY; flags |= RENDER_FLAGS_TWOPASS; } NewRenderable( pRenderable, group, flags ); ClientRenderHandle_t handle = pRenderable->RenderHandle(); m_DirtyRenderables.AddToTail( handle ); } void CClientLeafSystem::RemoveRenderable( ClientRenderHandle_t handle ) { // This can happen upon level shutdown if (!m_Renderables.IsValidIndex(handle)) return; // Reset the render handle in the entity. IClientRenderable *pRenderable = m_Renderables[handle].m_pRenderable; Assert( handle == pRenderable->RenderHandle() ); pRenderable->RenderHandle() = INVALID_CLIENT_RENDER_HANDLE; // Reemove the renderable from the dirty list if ( m_Renderables[handle].m_Flags & RENDER_FLAGS_HASCHANGED ) { // NOTE: This isn't particularly fast (linear search), // but I'm assuming it's an unusual case where we remove // renderables that are changing or that m_DirtyRenderables usually // only has a couple entries int i = m_DirtyRenderables.Find( handle ); Assert( i != m_DirtyRenderables.InvalidIndex() ); m_DirtyRenderables.FastRemove( i ); } if ( IsViewModelRenderGroup( (RenderGroup_t)m_Renderables[handle].m_RenderGroup ) ) { RemoveFromViewModelList( handle ); } RemoveFromTree( handle ); m_Renderables.Remove( handle ); } int CClientLeafSystem::GetRenderableLeaves( ClientRenderHandle_t handle, int leaves[128] ) { if ( !m_Renderables.IsValidIndex( handle ) ) return -1; RenderableInfo_t *pRenderable = &m_Renderables[handle]; if ( pRenderable->m_LeafList == m_RenderablesInLeaf.InvalidIndex() ) return -1; int nLeaves = 0; for ( int i=m_RenderablesInLeaf.FirstBucket( handle ); i != m_RenderablesInLeaf.InvalidIndex(); i = m_RenderablesInLeaf.NextBucket( i ) ) { leaves[nLeaves++] = m_RenderablesInLeaf.Bucket( i ); if ( nLeaves >= 128 ) break; } return nLeaves; } //----------------------------------------------------------------------------- // Retrieve leaf handles to leaves a renderable is in // the pOutLeaf parameter is filled with the leaf the renderable is in. // If pInIterator is not specified, pOutLeaf is the first leaf in the list. // if pInIterator is specified, that iterator is used to return the next leaf // in the list in pOutLeaf. // the pOutIterator parameter is filled with the iterater which index to the pOutLeaf returned. // // Returns false on failure cases where pOutLeaf will be invalid. CHECK THE RETURN! //----------------------------------------------------------------------------- bool CClientLeafSystem::GetRenderableLeaf(ClientRenderHandle_t handle, int* pOutLeaf, const int* pInIterator /* = 0 */, int* pOutIterator /* = 0 */) { // bail on invalid handle if ( !m_Renderables.IsValidIndex( handle ) ) return false; // bail on no output value pointer if ( !pOutLeaf ) return false; // an iterator was specified if ( pInIterator ) { int iter = *pInIterator; // test for invalid iterator if ( iter == m_RenderablesInLeaf.InvalidIndex() ) return false; int iterNext = m_RenderablesInLeaf.NextBucket( iter ); // test for end of list if ( iterNext == m_RenderablesInLeaf.InvalidIndex() ) return false; // Give the caller the iterator used if ( pOutIterator ) { *pOutIterator = iterNext; } // set output value to the next leaf *pOutLeaf = m_RenderablesInLeaf.Bucket( iterNext ); } else // no iter param, give them the first bucket in the renderable's list { int iter = m_RenderablesInLeaf.FirstBucket( handle ); if ( iter == m_RenderablesInLeaf.InvalidIndex() ) return false; // Set output value to this leaf *pOutLeaf = m_RenderablesInLeaf.Bucket( iter ); // give this iterator to caller if ( pOutIterator ) { *pOutIterator = iter; } } return true; } bool CClientLeafSystem::IsRenderableInPVS( IClientRenderable *pRenderable ) { ClientRenderHandle_t handle = pRenderable->RenderHandle(); int leaves[128]; int nLeaves = GetRenderableLeaves( handle, leaves ); if ( nLeaves == -1 ) return false; // Ask the engine if this guy is visible. return render->AreAnyLeavesVisible( leaves, nLeaves ); } short CClientLeafSystem::GetRenderableArea( ClientRenderHandle_t handle ) { int leaves[128]; int nLeaves = GetRenderableLeaves( handle, leaves ); if ( nLeaves == -1 ) return 0; // Now ask the return engine->GetLeavesArea( leaves, nLeaves ); } void CClientLeafSystem::SetSubSystemDataInLeaf( int leaf, int nSubSystemIdx, CClientLeafSubSystemData *pData ) { assert( nSubSystemIdx < N_CLSUBSYSTEMS ); if ( m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx] ) delete m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx]; m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx] = pData; } CClientLeafSubSystemData *CClientLeafSystem::GetSubSystemDataInLeaf( int leaf, int nSubSystemIdx ) { assert( nSubSystemIdx < N_CLSUBSYSTEMS ); return m_Leaf[leaf].m_pSubSystemData[nSubSystemIdx]; } //----------------------------------------------------------------------------- // Indicates which leaves detail objects are in //----------------------------------------------------------------------------- void CClientLeafSystem::SetDetailObjectsInLeaf( int leaf, int firstDetailObject, int detailObjectCount ) { m_Leaf[leaf].m_FirstDetailProp = firstDetailObject; m_Leaf[leaf].m_DetailPropCount = detailObjectCount; } //----------------------------------------------------------------------------- // Returns the detail objects in a leaf //----------------------------------------------------------------------------- void CClientLeafSystem::GetDetailObjectsInLeaf( int leaf, int& firstDetailObject, int& detailObjectCount ) { firstDetailObject = m_Leaf[leaf].m_FirstDetailProp; detailObjectCount = m_Leaf[leaf].m_DetailPropCount; } //----------------------------------------------------------------------------- // Create/destroy shadows... //----------------------------------------------------------------------------- ClientLeafShadowHandle_t CClientLeafSystem::AddShadow( ClientShadowHandle_t userId, unsigned short flags ) { ClientLeafShadowHandle_t idx = m_Shadows.AddToTail(); m_Shadows[idx].m_Shadow = userId; m_Shadows[idx].m_FirstLeaf = m_ShadowsInLeaf.InvalidIndex(); m_Shadows[idx].m_FirstRenderable = m_ShadowsOnRenderable.InvalidIndex(); m_Shadows[idx].m_EnumCount = 0; m_Shadows[idx].m_Flags = flags; return idx; } void CClientLeafSystem::RemoveShadow( ClientLeafShadowHandle_t handle ) { // Remove the shadow from all leaves + renderables... RemoveShadowFromLeaves( handle ); RemoveShadowFromRenderables( handle ); // Blow away the handle m_Shadows.Remove( handle ); } //----------------------------------------------------------------------------- // Adds a shadow to a leaf/removes shadow from renderable //----------------------------------------------------------------------------- inline bool CClientLeafSystem::ShouldRenderableReceiveShadow( ClientRenderHandle_t renderHandle, int nShadowFlags ) { RenderableInfo_t &renderable = m_Renderables[renderHandle]; if( !( renderable.m_Flags & ( RENDER_FLAGS_BRUSH_MODEL | RENDER_FLAGS_STATIC_PROP | RENDER_FLAGS_STUDIO_MODEL ) ) ) return false; return renderable.m_pRenderable->ShouldReceiveProjectedTextures( nShadowFlags ); } //----------------------------------------------------------------------------- // Adds a shadow to a leaf/removes shadow from renderable //----------------------------------------------------------------------------- void CClientLeafSystem::AddShadowToRenderable( ClientRenderHandle_t renderHandle, ClientLeafShadowHandle_t shadowHandle ) { // Check if this renderable receives the type of projected texture that shadowHandle refers to. int nShadowFlags = m_Shadows[shadowHandle].m_Flags; if ( !ShouldRenderableReceiveShadow( renderHandle, nShadowFlags ) ) return; m_ShadowsOnRenderable.AddElementToBucket( renderHandle, shadowHandle ); // Also, do some stuff specific to the particular types of renderables // If the renderable is a brush model, then add this shadow to it if (m_Renderables[renderHandle].m_Flags & RENDER_FLAGS_BRUSH_MODEL) { IClientRenderable* pRenderable = m_Renderables[renderHandle].m_pRenderable; g_pClientShadowMgr->AddShadowToReceiver( m_Shadows[shadowHandle].m_Shadow, pRenderable, SHADOW_RECEIVER_BRUSH_MODEL ); } else if( m_Renderables[renderHandle].m_Flags & RENDER_FLAGS_STATIC_PROP ) { IClientRenderable* pRenderable = m_Renderables[renderHandle].m_pRenderable; g_pClientShadowMgr->AddShadowToReceiver( m_Shadows[shadowHandle].m_Shadow, pRenderable, SHADOW_RECEIVER_STATIC_PROP ); } else if( m_Renderables[renderHandle].m_Flags & RENDER_FLAGS_STUDIO_MODEL ) { IClientRenderable* pRenderable = m_Renderables[renderHandle].m_pRenderable; g_pClientShadowMgr->AddShadowToReceiver( m_Shadows[shadowHandle].m_Shadow, pRenderable, SHADOW_RECEIVER_STUDIO_MODEL ); } } void CClientLeafSystem::RemoveShadowFromRenderables( ClientLeafShadowHandle_t handle ) { m_ShadowsOnRenderable.RemoveElement( handle ); } //----------------------------------------------------------------------------- // Adds a shadow to a leaf/removes shadow from leaf //----------------------------------------------------------------------------- void CClientLeafSystem::AddShadowToLeaf( int leaf, ClientLeafShadowHandle_t shadow ) { m_ShadowsInLeaf.AddElementToBucket( leaf, shadow ); // Add the shadow exactly once to all renderables in the leaf unsigned int i = m_RenderablesInLeaf.FirstElement( leaf ); while ( i != m_RenderablesInLeaf.InvalidIndex() ) { ClientRenderHandle_t renderable = m_RenderablesInLeaf.Element(i); RenderableInfo_t& info = m_Renderables[renderable]; // Add each shadow exactly once to each renderable if (info.m_EnumCount != m_ShadowEnum) { AddShadowToRenderable( renderable, shadow ); info.m_EnumCount = m_ShadowEnum; } Assert( m_ShadowsInLeaf.NumAllocated() < 2000 ); i = m_RenderablesInLeaf.NextElement(i); } } void CClientLeafSystem::RemoveShadowFromLeaves( ClientLeafShadowHandle_t handle ) { m_ShadowsInLeaf.RemoveElement( handle ); } //----------------------------------------------------------------------------- // Adds a shadow to all leaves listed //----------------------------------------------------------------------------- void CClientLeafSystem::ProjectShadow( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ) { // Remove the shadow from any leaves it current exists in RemoveShadowFromLeaves( handle ); RemoveShadowFromRenderables( handle ); Assert( ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) == SHADOW_FLAGS_SHADOW ); // This will help us to avoid adding the shadow multiple times to a renderable ++m_ShadowEnum; for ( int i = 0; i < nLeafCount; ++i ) { AddShadowToLeaf( pLeafList[i], handle ); } } void CClientLeafSystem::ProjectFlashlight( ClientLeafShadowHandle_t handle, int nLeafCount, const int *pLeafList ) { VPROF_BUDGET( "CClientLeafSystem::ProjectFlashlight", VPROF_BUDGETGROUP_SHADOW_DEPTH_TEXTURING ); // Remove the shadow from any leaves it current exists in RemoveShadowFromLeaves( handle ); RemoveShadowFromRenderables( handle ); Assert( ( m_Shadows[handle].m_Flags & SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) == SHADOW_FLAGS_FLASHLIGHT ); // This will help us to avoid adding the shadow multiple times to a renderable ++m_ShadowEnum; for ( int i = 0; i < nLeafCount; ++i ) { AddShadowToLeaf( pLeafList[i], handle ); } } //----------------------------------------------------------------------------- // Find all shadow casters in a set of leaves //----------------------------------------------------------------------------- void CClientLeafSystem::EnumerateShadowsInLeaves( int leafCount, LeafIndex_t* pLeaves, IClientLeafShadowEnum* pEnum ) { if (leafCount == 0) return; // This will help us to avoid enumerating the shadow multiple times ++m_ShadowEnum; for (int i = 0; i < leafCount; ++i) { int leaf = pLeaves[i]; unsigned short j = m_ShadowsInLeaf.FirstElement( leaf ); while ( j != m_ShadowsInLeaf.InvalidIndex() ) { ClientLeafShadowHandle_t shadow = m_ShadowsInLeaf.Element(j); ShadowInfo_t& info = m_Shadows[shadow]; if (info.m_EnumCount != m_ShadowEnum) { pEnum->EnumShadow(info.m_Shadow); info.m_EnumCount = m_ShadowEnum; } j = m_ShadowsInLeaf.NextElement(j); } } } //----------------------------------------------------------------------------- // Adds a renderable to a leaf //----------------------------------------------------------------------------- void CClientLeafSystem::AddRenderableToLeaf( int leaf, ClientRenderHandle_t renderable ) { #ifdef VALIDATE_CLIENT_LEAF_SYSTEM m_RenderablesInLeaf.ValidateAddElementToBucket( leaf, renderable ); #endif #ifdef DUMP_RENDERABLE_LEAFS static uint32 count = 0; if (count < m_RenderablesInLeaf.NumAllocated()) { count = m_RenderablesInLeaf.NumAllocated(); Msg("********** frame: %d count:%u ***************\n", gpGlobals->framecount, count ); if (count >= 20000) { for (int j = 0; j < m_RenderablesInLeaf.NumAllocated(); j++) { const ClientRenderHandle_t& renderable = m_RenderablesInLeaf.Element(j); RenderableInfo_t& info = m_Renderables[renderable]; char pTemp[256]; const char *pClassName = ""; C_BaseEntity *pEnt = info.m_pRenderable->GetIClientUnknown()->GetBaseEntity(); if ( pEnt ) { pClassName = pEnt->GetClassname(); } else { CNewParticleEffect *pEffect = dynamic_cast< CNewParticleEffect*>( info.m_pRenderable ); if ( pEffect ) { Vector mins, maxs; pEffect->GetRenderBounds(mins, maxs); Q_snprintf( pTemp, sizeof(pTemp), "ps: %s %.2f,%.2f", pEffect->GetEffectName(), maxs.x - mins.x, maxs.y - mins.y ); pClassName = pTemp; } else if ( dynamic_cast< CParticleEffectBinding* >( info.m_pRenderable ) ) { pClassName = ""; } } Msg(" %d: %p group:%d %s %d %d TransCalc:%d renderframe:%d\n", j, info.m_pRenderable, info.m_RenderGroup, pClassName, info.m_LeafList, info.m_RenderLeaf, info.m_TranslucencyCalculated, info.m_RenderFrame); } DebuggerBreak(); } } #endif // DUMP_RENDERABLE_LEAFS m_RenderablesInLeaf.AddElementToBucket(leaf, renderable); if ( !ShouldRenderableReceiveShadow( renderable, SHADOW_FLAGS_PROJECTED_TEXTURE_TYPE_MASK ) ) return; // Add all shadows in the leaf to the renderable... unsigned short i = m_ShadowsInLeaf.FirstElement( leaf ); while (i != m_ShadowsInLeaf.InvalidIndex() ) { ClientLeafShadowHandle_t shadow = m_ShadowsInLeaf.Element(i); ShadowInfo_t& info = m_Shadows[shadow]; // Add each shadow exactly once to each renderable if (info.m_EnumCount != m_ShadowEnum) { AddShadowToRenderable( renderable, shadow ); info.m_EnumCount = m_ShadowEnum; } i = m_ShadowsInLeaf.NextElement(i); } } //----------------------------------------------------------------------------- // Adds a renderable to a set of leaves //----------------------------------------------------------------------------- void CClientLeafSystem::AddRenderableToLeaves( ClientRenderHandle_t handle, int nLeafCount, unsigned short *pLeaves ) { for (int j = 0; j < nLeafCount; ++j) { AddRenderableToLeaf( pLeaves[j], handle ); } m_Renderables[handle].m_Area = GetRenderableArea( handle ); } //----------------------------------------------------------------------------- // Inserts an element into the tree //----------------------------------------------------------------------------- bool CClientLeafSystem::EnumerateLeaf( int leaf, int context ) { EnumResultList_t *pList = (EnumResultList_t *)context; if ( ThreadInMainThread() ) { AddRenderableToLeaf( leaf, pList->handle ); } else { EnumResult_t *p = new EnumResult_t; p->leaf = leaf; p->pNext = pList->pHead; pList->pHead = p; } return true; } void CClientLeafSystem::InsertIntoTree( ClientRenderHandle_t &handle ) { if ( ThreadInMainThread() ) { // When we insert into the tree, increase the shadow enumerator // to make sure each shadow is added exactly once to each renderable m_ShadowEnum++; } EnumResultList_t list = { NULL, handle }; // NOTE: The render bounds here are relative to the renderable's coordinate system IClientRenderable* pRenderable = m_Renderables[handle].m_pRenderable; Vector absMins, absMaxs; CalcRenderableWorldSpaceAABB_Fast( pRenderable, absMins, absMaxs ); Assert( absMins.IsValid() && absMaxs.IsValid() ); ISpatialQuery* pQuery = engine->GetBSPTreeQuery(); pQuery->EnumerateLeavesInBox( absMins, absMaxs, this, (int)&list ); if ( list.pHead ) { m_DeferredInserts.PushItem( list ); } } //----------------------------------------------------------------------------- // Removes an element from the tree //----------------------------------------------------------------------------- void CClientLeafSystem::RemoveFromTree( ClientRenderHandle_t handle ) { m_RenderablesInLeaf.RemoveElement( handle ); // Remove all shadows cast onto the object m_ShadowsOnRenderable.RemoveBucket( handle ); // If the renderable is a brush model, then remove all shadows from it if (m_Renderables[handle].m_Flags & RENDER_FLAGS_BRUSH_MODEL) { g_pClientShadowMgr->RemoveAllShadowsFromReceiver( m_Renderables[handle].m_pRenderable, SHADOW_RECEIVER_BRUSH_MODEL ); } else if( m_Renderables[handle].m_Flags & RENDER_FLAGS_STUDIO_MODEL ) { g_pClientShadowMgr->RemoveAllShadowsFromReceiver( m_Renderables[handle].m_pRenderable, SHADOW_RECEIVER_STUDIO_MODEL ); } } //----------------------------------------------------------------------------- // Call this when the renderable moves //----------------------------------------------------------------------------- void CClientLeafSystem::RenderableChanged( ClientRenderHandle_t handle ) { Assert ( handle != INVALID_CLIENT_RENDER_HANDLE ); Assert( m_Renderables.IsValidIndex( handle ) ); if ( !m_Renderables.IsValidIndex( handle ) ) return; if ( (m_Renderables[handle].m_Flags & RENDER_FLAGS_HASCHANGED ) == 0 ) { m_Renderables[handle].m_Flags |= RENDER_FLAGS_HASCHANGED; m_DirtyRenderables.AddToTail( handle ); } #if _DEBUG else { // It had better be in the list Assert( m_DirtyRenderables.Find( handle ) != m_DirtyRenderables.InvalidIndex() ); } #endif } //----------------------------------------------------------------------------- // Returns if it's a view model render group //----------------------------------------------------------------------------- inline bool CClientLeafSystem::IsViewModelRenderGroup( RenderGroup_t group ) const { return (group == RENDER_GROUP_VIEW_MODEL_TRANSLUCENT) || (group == RENDER_GROUP_VIEW_MODEL_OPAQUE); } //----------------------------------------------------------------------------- // Adds, removes renderables from view model list //----------------------------------------------------------------------------- void CClientLeafSystem::AddToViewModelList( ClientRenderHandle_t handle ) { MEM_ALLOC_CREDIT(); Assert( m_ViewModels.Find( handle ) == m_ViewModels.InvalidIndex() ); m_ViewModels.AddToTail( handle ); } void CClientLeafSystem::RemoveFromViewModelList( ClientRenderHandle_t handle ) { int i = m_ViewModels.Find( handle ); Assert( i != m_ViewModels.InvalidIndex() ); m_ViewModels.FastRemove( i ); } //----------------------------------------------------------------------------- // Call this to change the render group //----------------------------------------------------------------------------- void CClientLeafSystem::SetRenderGroup( ClientRenderHandle_t handle, RenderGroup_t group ) { RenderableInfo_t *pInfo = &m_Renderables[handle]; bool twoPass = false; if ( group == RENDER_GROUP_TWOPASS ) { twoPass = true; group = RENDER_GROUP_TRANSLUCENT_ENTITY; } if ( twoPass ) { pInfo->m_Flags |= RENDER_FLAGS_TWOPASS; } else { pInfo->m_Flags &= ~RENDER_FLAGS_TWOPASS; } bool bOldViewModelRenderGroup = IsViewModelRenderGroup( (RenderGroup_t)pInfo->m_RenderGroup ); bool bNewViewModelRenderGroup = IsViewModelRenderGroup( group ); if ( bOldViewModelRenderGroup != bNewViewModelRenderGroup ) { if ( bOldViewModelRenderGroup ) { RemoveFromViewModelList( handle ); } else { AddToViewModelList( handle ); } } pInfo->m_RenderGroup = group; } //----------------------------------------------------------------------------- // Detail system marks //----------------------------------------------------------------------------- void CClientLeafSystem::DrawDetailObjectsInLeaf( int leaf, int nFrameNumber, int& nFirstDetailObject, int& nDetailObjectCount ) { ClientLeaf_t &leafInfo = m_Leaf[leaf]; leafInfo.m_DetailPropRenderFrame = nFrameNumber; nFirstDetailObject = leafInfo.m_FirstDetailProp; nDetailObjectCount = leafInfo.m_DetailPropCount; } //----------------------------------------------------------------------------- // Are we close enough to this leaf to draw detail props *and* are there any props in the leaf? //----------------------------------------------------------------------------- bool CClientLeafSystem::ShouldDrawDetailObjectsInLeaf( int leaf, int frameNumber ) { ClientLeaf_t &leafInfo = m_Leaf[leaf]; return ( (leafInfo.m_DetailPropRenderFrame == frameNumber ) && ( ( leafInfo.m_DetailPropCount != 0 ) || ( leafInfo.m_pSubSystemData[CLSUBSYSTEM_DETAILOBJECTS] ) ) ); } //----------------------------------------------------------------------------- // Compute which leaf the translucent renderables should render in //----------------------------------------------------------------------------- void CClientLeafSystem::ComputeTranslucentRenderLeaf( int count, const LeafIndex_t *pLeafList, const LeafFogVolume_t *pLeafFogVolumeList, int frameNumber, int viewID ) { ASSERT_NO_REENTRY(); VPROF_BUDGET( "CClientLeafSystem::ComputeTranslucentRenderLeaf", "ComputeTranslucentRenderLeaf" ); #define LeafToMarker( leaf ) reinterpret_cast(( (leaf) << 1 ) | 1) #define IsLeafMarker( p ) (bool)((reinterpret_cast(p)) & 1) #define MarkerToLeaf( p ) (int)((reinterpret_cast(p)) >> 1) // For better sorting, we're gonna choose the leaf that is closest to the camera. // The leaf list passed in here is sorted front to back bool bThreaded = false;//( cl_threaded_client_leaf_system.GetBool() && g_pThreadPool->NumThreads() ); int globalFrameCount = gpGlobals->framecount; int i; static CUtlVector orderedList; // @MULTICORE (toml 8/30/2006): will need to make non-static if thread this function static CUtlVector renderablesToUpdate; int leaf = 0; for ( i = 0; i < count; ++i ) { leaf = pLeafList[i]; orderedList.AddToTail( LeafToMarker( leaf ) ); // iterate over all elements in this leaf unsigned int idx = m_RenderablesInLeaf.FirstElement(leaf); while (idx != m_RenderablesInLeaf.InvalidIndex()) { RenderableInfo_t& info = m_Renderables[m_RenderablesInLeaf.Element(idx)]; if ( info.m_TranslucencyCalculated != globalFrameCount || info.m_TranslucencyCalculatedView != viewID ) { // Compute translucency if ( bThreaded ) { renderablesToUpdate.AddToTail( info.m_pRenderable ); } else { info.m_pRenderable->ComputeFxBlend(); } info.m_TranslucencyCalculated = globalFrameCount; info.m_TranslucencyCalculatedView = viewID; } orderedList.AddToTail( &info ); idx = m_RenderablesInLeaf.NextElement(idx); } } if ( bThreaded ) { ParallelProcess( "CClientLeafSystem::ComputeTranslucentRenderLeaf", renderablesToUpdate.Base(), renderablesToUpdate.Count(), &CallComputeFXBlend, &::FrameLock, &::FrameUnlock ); renderablesToUpdate.RemoveAll(); } for ( i = 0; i != orderedList.Count(); i++ ) { RenderableInfo_t *pInfo = orderedList[i]; if ( !IsLeafMarker( pInfo ) ) { if( pInfo->m_RenderFrame != frameNumber ) { if( pInfo->m_RenderGroup == RENDER_GROUP_TRANSLUCENT_ENTITY ) { pInfo->m_RenderLeaf = leaf; } pInfo->m_RenderFrame = frameNumber; } else if ( pInfo->m_Flags & RENDER_FLAGS_ALTERNATE_SORTING ) { if( pInfo->m_RenderGroup == RENDER_GROUP_TRANSLUCENT_ENTITY ) { pInfo->m_RenderLeaf = leaf; } } } else { leaf = MarkerToLeaf( pInfo ); } } orderedList.RemoveAll(); } //----------------------------------------------------------------------------- // Adds a renderable to the list of renderables to render this frame //----------------------------------------------------------------------------- inline void AddRenderableToRenderList( CClientRenderablesList &renderList, IClientRenderable *pRenderable, int iLeaf, RenderGroup_t group, ClientRenderHandle_t renderHandle, bool bTwoPass = false ) { #ifdef _DEBUG if (cl_drawleaf.GetInt() >= 0) { if (iLeaf != cl_drawleaf.GetInt()) return; } #endif Assert( group >= 0 && group < RENDER_GROUP_COUNT ); int &curCount = renderList.m_RenderGroupCounts[group]; if ( curCount < CClientRenderablesList::MAX_GROUP_ENTITIES ) { Assert( (iLeaf >= 0) && (iLeaf <= 65535) ); CClientRenderablesList::CEntry *pEntry = &renderList.m_RenderGroups[group][curCount]; pEntry->m_pRenderable = pRenderable; pEntry->m_iWorldListInfoLeaf = iLeaf; pEntry->m_TwoPass = bTwoPass; pEntry->m_RenderHandle = renderHandle; curCount++; } else { engine->Con_NPrintf( 10, "Warning: overflowed CClientRenderablesList group %d", group ); } } //----------------------------------------------------------------------------- // Purpose: // Input : renderList - // renderGroup - //----------------------------------------------------------------------------- void CClientLeafSystem::CollateViewModelRenderables( CUtlVector< IClientRenderable * >& opaque, CUtlVector< IClientRenderable * >& translucent ) { for ( int i = m_ViewModels.Count()-1; i >= 0; --i ) { ClientRenderHandle_t handle = m_ViewModels[i]; RenderableInfo_t& renderable = m_Renderables[handle]; // NOTE: In some cases, this removes the entity from the view model list renderable.m_pRenderable->ComputeFxBlend(); // That's why we need to test RENDER_GROUP_OPAQUE_ENTITY - it may have changed in ComputeFXBlend() if ( renderable.m_RenderGroup == RENDER_GROUP_VIEW_MODEL_OPAQUE || renderable.m_RenderGroup == RENDER_GROUP_OPAQUE_ENTITY ) { opaque.AddToTail( renderable.m_pRenderable ); } else { translucent.AddToTail( renderable.m_pRenderable ); } } } static RenderGroup_t DetectBucketedRenderGroup( RenderGroup_t group, float fDimension ) { float const arrThresholds[ 3 ] = { 200.f, // tree size 80.f, // player size 30.f, // crate size }; Assert( ARRAYSIZE( arrThresholds ) + 1 >= RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS ); Assert( group >= RENDER_GROUP_OPAQUE_STATIC && group <= RENDER_GROUP_OPAQUE_ENTITY ); int bucketedGroupIndex; if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS <= 2 || fDimension >= arrThresholds[1] ) { if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS <= 1 || fDimension >= arrThresholds[0] ) bucketedGroupIndex = 0; else bucketedGroupIndex = 1; } else { if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS <= 3 || fDimension >= arrThresholds[2] ) bucketedGroupIndex = 2; else bucketedGroupIndex = 3; } // Determine the new bucketed group RenderGroup_t bucketedGroup = RenderGroup_t( group - ( ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS - 1 ) - bucketedGroupIndex ) * 2 ); Assert( bucketedGroup >= RENDER_GROUP_OPAQUE_STATIC_HUGE && bucketedGroup <= RENDER_GROUP_OPAQUE_ENTITY ); return bucketedGroup; } void CClientLeafSystem::CollateRenderablesInLeaf( int leaf, int worldListLeafIndex, const SetupRenderInfo_t &info ) { bool portalTestEnts = r_PortalTestEnts.GetBool() && !r_portalsopenall.GetBool(); // Place a fake entity for static/opaque ents in this leaf AddRenderableToRenderList( *info.m_pRenderList, NULL, worldListLeafIndex, RENDER_GROUP_OPAQUE_STATIC, NULL ); AddRenderableToRenderList( *info.m_pRenderList, NULL, worldListLeafIndex, RENDER_GROUP_OPAQUE_ENTITY, NULL ); // Collate everything. unsigned int idx = m_RenderablesInLeaf.FirstElement(leaf); for ( ;idx != m_RenderablesInLeaf.InvalidIndex(); idx = m_RenderablesInLeaf.NextElement(idx) ) { ClientRenderHandle_t handle = m_RenderablesInLeaf.Element(idx); RenderableInfo_t& renderable = m_Renderables[handle]; // Early out on static props if we don't want to render them if ((!m_DrawStaticProps) && (renderable.m_Flags & RENDER_FLAGS_STATIC_PROP)) continue; // Early out if we're told to not draw small objects (top view only, /* that's why we don't check the z component). if (!m_DrawSmallObjects) { CCachedRenderInfo& cachedInfo = m_CachedRenderInfos[renderable.m_CachedRenderInfo]; float sizeX = cachedInfo.m_Maxs.x - cachedInfo.m_Mins.x; float sizeY = cachedInfo.m_Maxs.y - cachedInfo.m_Mins.y; if ((sizeX < 50.f) && (sizeY < 50.f)) continue; }*/ Assert( m_DrawSmallObjects ); // MOTODO // Don't hit the same ent in multiple leaves twice. if ( renderable.m_RenderGroup != RENDER_GROUP_TRANSLUCENT_ENTITY ) { if ( renderable.m_RenderFrame2 == info.m_nRenderFrame ) continue; renderable.m_RenderFrame2 = info.m_nRenderFrame; } else // translucent { // Shadow depth skips ComputeTranslucentRenderLeaf! // Translucent entities already have had ComputeTranslucentRenderLeaf called on them // so m_RenderLeaf should be set to the nearest leaf, so that's what we want here. if ( renderable.m_RenderLeaf != leaf ) continue; } unsigned char nAlpha = 255; if ( info.m_bDrawTranslucentObjects ) { // Prevent culling if the renderable is invisible // NOTE: OPAQUE objects can have alpha == 0. // They are made to be opaque because they don't have to be sorted. nAlpha = renderable.m_pRenderable->GetFxBlend(); if ( nAlpha == 0 ) continue; } Vector absMins, absMaxs; CalcRenderableWorldSpaceAABB( renderable.m_pRenderable, absMins, absMaxs ); // If the renderable is inside an area, cull it using the frustum for that area. if ( portalTestEnts && renderable.m_Area != -1 ) { VPROF( "r_PortalTestEnts" ); if ( !engine->DoesBoxTouchAreaFrustum( absMins, absMaxs, renderable.m_Area ) ) continue; } else { // cull with main frustum if ( engine->CullBox( absMins, absMaxs ) ) continue; } // UNDONE: Investigate speed tradeoffs of occlusion culling brush models too? if ( renderable.m_Flags & RENDER_FLAGS_STUDIO_MODEL ) { // test to see if this renderable is occluded by the engine's occlusion system if ( engine->IsOccluded( absMins, absMaxs ) ) continue; } #ifdef INVASION_CLIENT_DLL if (info.m_flRenderDistSq != 0.0f) { Vector mins, maxs; renderable.m_pRenderable->GetRenderBounds( mins, maxs ); if ((maxs.z - mins.z) < 100) { Vector vCenter; VectorLerp( mins, maxs, 0.5f, vCenter ); vCenter += renderable.m_pRenderable->GetRenderOrigin(); float flDistSq = info.m_vecRenderOrigin.DistToSqr( vCenter ); if (info.m_flRenderDistSq <= flDistSq) continue; } } #endif if( renderable.m_RenderGroup != RENDER_GROUP_TRANSLUCENT_ENTITY ) { RenderGroup_t group = (RenderGroup_t)renderable.m_RenderGroup; // Determine object group offset if ( RENDER_GROUP_CFG_NUM_OPAQUE_ENT_BUCKETS > 1 && group >= RENDER_GROUP_OPAQUE_STATIC && group <= RENDER_GROUP_OPAQUE_ENTITY ) { Vector dims; VectorSubtract( absMaxs, absMins, dims ); float const fDimension = MAX( MAX( fabs(dims.x), fabs(dims.y) ), fabs(dims.z) ); group = DetectBucketedRenderGroup( group, fDimension ); Assert( group >= RENDER_GROUP_OPAQUE_STATIC_HUGE && group <= RENDER_GROUP_OPAQUE_ENTITY ); } AddRenderableToRenderList( *info.m_pRenderList, renderable.m_pRenderable, worldListLeafIndex, group, handle); } else { bool bTwoPass = ((renderable.m_Flags & RENDER_FLAGS_TWOPASS) != 0) && ( nAlpha == 255 ); // Two pass? // Add to appropriate list if drawing translucent objects (shadow depth mapping will skip this) if ( info.m_bDrawTranslucentObjects ) { AddRenderableToRenderList( *info.m_pRenderList, renderable.m_pRenderable, worldListLeafIndex, (RenderGroup_t)renderable.m_RenderGroup, handle, bTwoPass ); } if ( bTwoPass ) // Also add to opaque list if it's a two-pass model... { AddRenderableToRenderList( *info.m_pRenderList, renderable.m_pRenderable, worldListLeafIndex, RENDER_GROUP_OPAQUE_ENTITY, handle, bTwoPass ); } } } // Do detail objects. // These don't have render handles! if ( info.m_bDrawDetailObjects && ShouldDrawDetailObjectsInLeaf( leaf, info.m_nDetailBuildFrame ) ) { idx = m_Leaf[leaf].m_FirstDetailProp; int count = m_Leaf[leaf].m_DetailPropCount; while( --count >= 0 ) { IClientRenderable* pRenderable = DetailObjectSystem()->GetDetailModel(idx); // FIXME: This if check here is necessary because the detail object system also maintains lists of sprites... if (pRenderable) { if( pRenderable->IsTransparent() ) { if ( info.m_bDrawTranslucentObjects ) // Don't draw translucent objects into shadow depth maps { // Lots of the detail entities are invisible so avoid sorting them and all that. if( pRenderable->GetFxBlend() > 0 ) { AddRenderableToRenderList( *info.m_pRenderList, pRenderable, worldListLeafIndex, RENDER_GROUP_TRANSLUCENT_ENTITY, DETAIL_PROP_RENDER_HANDLE ); } } } else { AddRenderableToRenderList( *info.m_pRenderList, pRenderable, worldListLeafIndex, RENDER_GROUP_OPAQUE_ENTITY, DETAIL_PROP_RENDER_HANDLE ); } } ++idx; } } } //----------------------------------------------------------------------------- // Sort entities in a back-to-front ordering //----------------------------------------------------------------------------- void CClientLeafSystem::SortEntities( const Vector &vecRenderOrigin, const Vector &vecRenderForward, CClientRenderablesList::CEntry *pEntities, int nEntities ) { // Don't sort if we only have 1 entity if ( nEntities <= 1 ) return; float dists[CClientRenderablesList::MAX_GROUP_ENTITIES]; // First get a distance for each entity. int i; for( i=0; i < nEntities; i++ ) { IClientRenderable *pRenderable = pEntities[i].m_pRenderable; // Compute the center of the object (needed for translucent brush models) Vector boxcenter; Vector mins,maxs; pRenderable->GetRenderBounds( mins, maxs ); VectorAdd( mins, maxs, boxcenter ); VectorMA( pRenderable->GetRenderOrigin(), 0.5f, boxcenter, boxcenter ); // Compute distance... Vector delta; VectorSubtract( boxcenter, vecRenderOrigin, delta ); dists[i] = DotProduct( delta, vecRenderForward ); } // H-sort. int stepSize = 4; while( stepSize ) { int end = nEntities - stepSize; for( i=0; i < end; i += stepSize ) { if( dists[i] > dists[i+stepSize] ) { ::V_swap( pEntities[i], pEntities[i+stepSize] ); ::V_swap( dists[i], dists[i+stepSize] ); if( i == 0 ) { i = -stepSize; } else { i -= stepSize << 1; } } } stepSize >>= 1; } } void CClientLeafSystem::BuildRenderablesList( const SetupRenderInfo_t &info ) { VPROF_BUDGET( "BuildRenderablesList", "BuildRenderablesList" ); int leafCount = info.m_pWorldListInfo->m_LeafCount; const Vector &vecRenderOrigin = info.m_vecRenderOrigin; const Vector &vecRenderForward = info.m_vecRenderForward; CClientRenderablesList::CEntry *pTranslucentEntries = info.m_pRenderList->m_RenderGroups[RENDER_GROUP_TRANSLUCENT_ENTITY]; int &nTranslucentEntries = info.m_pRenderList->m_RenderGroupCounts[RENDER_GROUP_TRANSLUCENT_ENTITY]; for( int i = 0; i < leafCount; i++ ) { int nTranslucent = nTranslucentEntries; // Add renderables from this leaf... CollateRenderablesInLeaf( info.m_pWorldListInfo->m_pLeafList[i], i, info ); int nNewTranslucent = nTranslucentEntries - nTranslucent; if( (nNewTranslucent != 0 ) && info.m_bDrawTranslucentObjects ) { // Sort the new translucent entities. SortEntities( vecRenderOrigin, vecRenderForward, &pTranslucentEntries[nTranslucent], nNewTranslucent ); } } }