summaryrefslogtreecommitdiff
path: root/datacache
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /datacache
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'datacache')
-rw-r--r--datacache/datacache.cpp1385
-rw-r--r--datacache/datacache.h384
-rw-r--r--datacache/datacache.vpc55
-rw-r--r--datacache/datacache_common.h31
-rw-r--r--datacache/mdlcache.cpp3924
-rw-r--r--datacache/xbox/xbox.def3
6 files changed, 5782 insertions, 0 deletions
diff --git a/datacache/datacache.cpp b/datacache/datacache.cpp
new file mode 100644
index 0000000..fc96b7a
--- /dev/null
+++ b/datacache/datacache.cpp
@@ -0,0 +1,1385 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "datacache/idatacache.h"
+
+#ifdef _LINUX
+#include <malloc.h>
+#endif
+
+#include "tier0/vprof.h"
+#include "basetypes.h"
+#include "convar.h"
+#include "interface.h"
+#include "datamanager.h"
+#include "utlrbtree.h"
+#include "utlhash.h"
+#include "utlmap.h"
+#include "generichash.h"
+#include "filesystem.h"
+#include "datacache.h"
+#include "utlvector.h"
+#include "fmtstr.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+// Singleton
+//-----------------------------------------------------------------------------
+CDataCache g_DataCache;
+
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CDataCache, IDataCache, DATACACHE_INTERFACE_VERSION, g_DataCache );
+
+
+//-----------------------------------------------------------------------------
+//
+// Data Cache class implemenations
+//
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Console commands
+//-----------------------------------------------------------------------------
+ConVar developer( "developer", "0", FCVAR_INTERNAL_USE );
+static ConVar mem_force_flush( "mem_force_flush", "0", FCVAR_CHEAT, "Force cache flush of unlocked resources on every alloc" );
+static int g_iDontForceFlush;
+
+//-----------------------------------------------------------------------------
+// DataCacheItem_t
+//-----------------------------------------------------------------------------
+
+DEFINE_FIXEDSIZE_ALLOCATOR_MT( DataCacheItem_t, 4096/sizeof(DataCacheItem_t), CUtlMemoryPool::GROW_SLOW );
+
+void DataCacheItem_t::DestroyResource()
+{
+ if ( pSection )
+ {
+ pSection->DiscardItemData( this, DC_AGE_DISCARD );
+ }
+ delete this;
+}
+
+
+//-----------------------------------------------------------------------------
+// CDataCacheSection
+//-----------------------------------------------------------------------------
+
+CDataCacheSection::CDataCacheSection( CDataCache *pSharedCache, IDataCacheClient *pClient, const char *pszName )
+ : m_pClient( pClient ),
+ m_LRU( pSharedCache->m_LRU ),
+ m_mutex( pSharedCache->m_mutex ),
+ m_pSharedCache( pSharedCache ),
+ m_nFrameUnlockCounter( 0 ),
+ m_options( 0 )
+{
+ memset( &m_status, 0, sizeof(m_status) );
+ AssertMsg1( strlen(pszName) <= DC_MAX_CLIENT_NAME, "Cache client name too long \"%s\"", pszName );
+ Q_strncpy( szName, pszName, sizeof(szName) );
+
+ for ( int i = 0; i < DC_MAX_THREADS_FRAMELOCKED; i++ )
+ {
+ FrameLock_t *pFrameLock = new FrameLock_t;
+ pFrameLock->m_iThread = i;
+ m_FreeFrameLocks.Push( pFrameLock );
+ }
+}
+
+CDataCacheSection::~CDataCacheSection()
+{
+ FrameLock_t *pFrameLock;
+ while ( ( pFrameLock = m_FreeFrameLocks.Pop() ) != NULL )
+ {
+ delete pFrameLock;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Controls cache size.
+//-----------------------------------------------------------------------------
+void CDataCacheSection::SetLimits( const DataCacheLimits_t &limits )
+{
+ m_limits = limits;
+ AssertMsg( m_limits.nMinBytes == 0 && m_limits.nMinItems == 0, "Cache minimums not yet implemented" );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+const DataCacheLimits_t &CDataCacheSection::GetLimits()
+{
+ return m_limits;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Controls cache options.
+//-----------------------------------------------------------------------------
+void CDataCacheSection::SetOptions( unsigned options )
+{
+ m_options = options;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the current state of the section
+//-----------------------------------------------------------------------------
+void CDataCacheSection::GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits )
+{
+ if ( pStatus )
+ {
+ *pStatus = m_status;
+ }
+
+ if ( pLimits )
+ {
+ *pLimits = m_limits;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CDataCacheSection::EnsureCapacity( unsigned nBytes, unsigned nItems )
+{
+ VPROF( "CDataCacheSection::EnsureCapacity" );
+
+ if ( m_limits.nMaxItems != (unsigned)-1 || m_limits.nMaxBytes != (unsigned)-1 )
+ {
+ unsigned nNewSectionBytes = GetNumBytes() + nBytes;
+
+ if ( nNewSectionBytes > m_limits.nMaxBytes )
+ {
+ Purge( nNewSectionBytes - m_limits.nMaxBytes );
+ }
+
+ if ( GetNumItems() >= m_limits.nMaxItems )
+ {
+ PurgeItems( ( GetNumItems() - m_limits.nMaxItems ) + 1 );
+ }
+ }
+
+ m_pSharedCache->EnsureCapacity( nBytes );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add an item to the cache. Purges old items if over budget, returns false if item was already in cache.
+//-----------------------------------------------------------------------------
+bool CDataCacheSection::Add( DataCacheClientID_t clientId, const void *pItemData, unsigned size, DataCacheHandle_t *pHandle )
+{
+ return AddEx( clientId, pItemData, size, DCAF_DEFAULT, pHandle );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add an item to the cache. Purges old items if over budget, returns false if item was already in cache.
+//-----------------------------------------------------------------------------
+bool CDataCacheSection::AddEx( DataCacheClientID_t clientId, const void *pItemData, unsigned size, unsigned flags, DataCacheHandle_t *pHandle )
+{
+ VPROF( "CDataCacheSection::Add" );
+
+ if ( mem_force_flush.GetBool() )
+ {
+ m_pSharedCache->Flush();
+ }
+
+ if ( ( m_options & DC_VALIDATE ) && Find( clientId ) )
+ {
+ Error( "Duplicate add to data cache\n" );
+ return false;
+ }
+
+ EnsureCapacity( size );
+
+ DataCacheItemData_t itemData =
+ {
+ pItemData,
+ size,
+ clientId,
+ this
+ };
+
+ memhandle_t hMem = m_LRU.CreateResource( itemData, true );
+
+ Assert( hMem != (memhandle_t)0 && hMem != (memhandle_t)DC_INVALID_HANDLE );
+
+ AccessItem( hMem )->hLRU = hMem;
+
+ if ( pHandle )
+ {
+ *pHandle = (DataCacheHandle_t)hMem;
+ }
+
+ NoteAdd( size );
+
+ OnAdd( clientId, (DataCacheHandle_t)hMem );
+
+ g_iDontForceFlush++;
+
+ if ( flags & DCAF_LOCK )
+ {
+ Lock( (DataCacheHandle_t)hMem );
+ }
+ // Add implies a frame lock. A no-op if not in frame lock
+ FrameLock( (DataCacheHandle_t)hMem );
+
+ g_iDontForceFlush--;
+
+ m_LRU.UnlockResource( hMem );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds an item in the cache, returns NULL if item is not in cache.
+//-----------------------------------------------------------------------------
+DataCacheHandle_t CDataCacheSection::Find( DataCacheClientID_t clientId )
+{
+ VPROF( "CDataCacheSection::Find" );
+
+ m_status.nFindRequests++;
+
+ DataCacheHandle_t hResult = DoFind( clientId );
+
+ if ( hResult != DC_INVALID_HANDLE )
+ {
+ m_status.nFindHits++;
+ }
+
+ return hResult;
+}
+
+//---------------------------------------------------------
+DataCacheHandle_t CDataCacheSection::DoFind( DataCacheClientID_t clientId )
+{
+ AUTO_LOCK( m_mutex );
+ memhandle_t hCurrent;
+
+ hCurrent = GetFirstUnlockedItem();
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ if ( AccessItem( hCurrent )->clientId == clientId )
+ {
+ m_status.nFindHits++;
+ return (DataCacheHandle_t)hCurrent;
+ }
+ hCurrent = GetNextItem( hCurrent );
+ }
+
+ hCurrent = GetFirstLockedItem();
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ if ( AccessItem( hCurrent )->clientId == clientId )
+ {
+ m_status.nFindHits++;
+ return (DataCacheHandle_t)hCurrent;
+ }
+ hCurrent = GetNextItem( hCurrent );
+ }
+
+ return DC_INVALID_HANDLE;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get an item out of the cache and remove it. No callbacks are executed.
+//-----------------------------------------------------------------------------
+DataCacheRemoveResult_t CDataCacheSection::Remove( DataCacheHandle_t handle, const void **ppItemData, unsigned *pItemSize, bool bNotify )
+{
+ VPROF( "CDataCacheSection::Remove" );
+
+ if ( handle != DC_INVALID_HANDLE )
+ {
+ memhandle_t lruHandle = (memhandle_t)handle;
+ if ( m_LRU.LockCount( lruHandle ) > 0 )
+ {
+ return DC_LOCKED;
+ }
+
+ AUTO_LOCK( m_mutex );
+
+ DataCacheItem_t *pItem = AccessItem( lruHandle );
+ if ( pItem )
+ {
+ if ( ppItemData )
+ {
+ *ppItemData = pItem->pItemData;
+ }
+
+ if ( pItemSize )
+ {
+ *pItemSize = pItem->size;
+ }
+
+ DiscardItem( lruHandle, ( bNotify ) ? DC_REMOVED : DC_NONE );
+
+ return DC_OK;
+ }
+ }
+
+ return DC_NOT_FOUND;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CDataCacheSection::IsPresent( DataCacheHandle_t handle )
+{
+ return ( m_LRU.GetResource_NoLockNoLRUTouch( (memhandle_t)handle ) != NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lock an item in the cache, returns NULL if item is not in the cache.
+//-----------------------------------------------------------------------------
+void *CDataCacheSection::Lock( DataCacheHandle_t handle )
+{
+ VPROF( "CDataCacheSection::Lock" );
+
+ if ( mem_force_flush.GetBool() && !g_iDontForceFlush)
+ Flush();
+
+ if ( handle != DC_INVALID_HANDLE )
+ {
+ DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle );
+ if ( pItem )
+ {
+ if ( m_LRU.LockCount( (memhandle_t)handle ) == 1 )
+ {
+ NoteLock( pItem->size );
+ }
+ return const_cast<void *>(pItem->pItemData);
+ }
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Unlock a previous lock.
+//-----------------------------------------------------------------------------
+int CDataCacheSection::Unlock( DataCacheHandle_t handle )
+{
+ VPROF( "CDataCacheSection::Unlock" );
+
+ int iNewLockCount = 0;
+ if ( handle != DC_INVALID_HANDLE )
+ {
+ AssertMsg( AccessItem( (memhandle_t)handle ) != NULL, "Attempted to unlock nonexistent cache entry" );
+ unsigned nBytesUnlocked = 0;
+ m_mutex.Lock();
+ iNewLockCount = m_LRU.UnlockResource( (memhandle_t)handle );
+ if ( iNewLockCount == 0 )
+ {
+ nBytesUnlocked = AccessItem( (memhandle_t)handle )->size;
+ }
+ m_mutex.Unlock();
+ if ( nBytesUnlocked )
+ {
+ NoteUnlock( nBytesUnlocked );
+ EnsureCapacity( 0 );
+ }
+ }
+ return iNewLockCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lock the mutex
+//-----------------------------------------------------------------------------
+void CDataCacheSection::LockMutex()
+{
+ g_iDontForceFlush++;
+ m_mutex.Lock();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Unlock the mutex
+//-----------------------------------------------------------------------------
+void CDataCacheSection::UnlockMutex()
+{
+ g_iDontForceFlush--;
+ m_mutex.Unlock();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Get without locking
+//-----------------------------------------------------------------------------
+void *CDataCacheSection::Get( DataCacheHandle_t handle, bool bFrameLock )
+{
+ VPROF( "CDataCacheSection::Get" );
+
+ if ( mem_force_flush.GetBool() && !g_iDontForceFlush)
+ Flush();
+
+ if ( handle != DC_INVALID_HANDLE )
+ {
+ if ( bFrameLock && IsFrameLocking() )
+ return FrameLock( handle );
+
+ AUTO_LOCK( m_mutex );
+ DataCacheItem_t *pItem = m_LRU.GetResource_NoLock( (memhandle_t)handle );
+ if ( pItem )
+ {
+ return const_cast<void *>( pItem->pItemData );
+ }
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get without locking
+//-----------------------------------------------------------------------------
+void *CDataCacheSection::GetNoTouch( DataCacheHandle_t handle, bool bFrameLock )
+{
+ VPROF( "CDataCacheSection::GetNoTouch" );
+
+ if ( handle != DC_INVALID_HANDLE )
+ {
+ if ( bFrameLock && IsFrameLocking() )
+ return FrameLock( handle );
+
+ AUTO_LOCK( m_mutex );
+ DataCacheItem_t *pItem = m_LRU.GetResource_NoLockNoLRUTouch( (memhandle_t)handle );
+ if ( pItem )
+ {
+ return const_cast<void *>( pItem->pItemData );
+ }
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: "Frame locking" (not game frame). A crude way to manage locks over relatively
+// short periods. Does not affect normal locks/unlocks
+//-----------------------------------------------------------------------------
+int CDataCacheSection::BeginFrameLocking()
+{
+ FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
+ if ( pFrameLock )
+ {
+ pFrameLock->m_iLock++;
+ }
+ else
+ {
+ while ( ( pFrameLock = m_FreeFrameLocks.Pop() ) == NULL )
+ {
+ ThreadPause();
+ ThreadSleep( 1 );
+ }
+ pFrameLock->m_iLock = 1;
+ pFrameLock->m_pFirst = NULL;
+ m_ThreadFrameLock.Set( pFrameLock );
+ }
+ return pFrameLock->m_iLock;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CDataCacheSection::IsFrameLocking()
+{
+ FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
+ return ( pFrameLock != NULL );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void *CDataCacheSection::FrameLock( DataCacheHandle_t handle )
+{
+ VPROF( "CDataCacheSection::FrameLock" );
+
+ if ( mem_force_flush.GetBool() && !g_iDontForceFlush)
+ Flush();
+
+ void *pResult = NULL;
+ FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
+ if ( pFrameLock )
+ {
+ DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle );
+
+ if ( pItem )
+ {
+ int iThread = pFrameLock->m_iThread;
+ if ( pItem->pNextFrameLocked[iThread] == DC_NO_NEXT_LOCKED )
+ {
+ pItem->pNextFrameLocked[iThread] = pFrameLock->m_pFirst;
+ pFrameLock->m_pFirst = pItem;
+ Lock( handle );
+ }
+
+ pResult = const_cast<void *>(pItem->pItemData);
+ m_LRU.UnlockResource( (memhandle_t)handle );
+ }
+ }
+
+ return pResult;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int CDataCacheSection::EndFrameLocking()
+{
+ FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
+ Assert( pFrameLock->m_iLock > 0 );
+
+ if ( pFrameLock->m_iLock == 1 )
+ {
+ VPROF( "CDataCacheSection::EndFrameLocking" );
+
+ DataCacheItem_t *pItem = pFrameLock->m_pFirst;
+ DataCacheItem_t *pNext;
+ int iThread = pFrameLock->m_iThread;
+ while ( pItem )
+ {
+ pNext = pItem->pNextFrameLocked[iThread];
+ pItem->pNextFrameLocked[iThread] = DC_NO_NEXT_LOCKED;
+ Unlock( pItem->hLRU );
+ pItem = pNext;
+ }
+
+ m_FreeFrameLocks.Push( pFrameLock );
+ m_ThreadFrameLock.Set( NULL );
+ return 0;
+ }
+ else
+ {
+ pFrameLock->m_iLock--;
+ }
+ return pFrameLock->m_iLock;
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int *CDataCacheSection::GetFrameUnlockCounterPtr()
+{
+ return &m_nFrameUnlockCounter;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Lock management, not for the feint of heart
+//-----------------------------------------------------------------------------
+int CDataCacheSection::GetLockCount( DataCacheHandle_t handle )
+{
+ return m_LRU.LockCount( (memhandle_t)handle );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int CDataCacheSection::BreakLock( DataCacheHandle_t handle )
+{
+ return m_LRU.BreakLock( (memhandle_t)handle );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Explicitly mark an item as "recently used"
+//-----------------------------------------------------------------------------
+bool CDataCacheSection::Touch( DataCacheHandle_t handle )
+{
+ m_LRU.TouchResource( (memhandle_t)handle );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Explicitly mark an item as "least recently used".
+//-----------------------------------------------------------------------------
+bool CDataCacheSection::Age( DataCacheHandle_t handle )
+{
+ m_LRU.MarkAsStale( (memhandle_t)handle );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Empty the cache. Returns bytes released, will remove locked items if force specified
+//-----------------------------------------------------------------------------
+unsigned CDataCacheSection::Flush( bool bUnlockedOnly, bool bNotify )
+{
+ VPROF( "CDataCacheSection::Flush" );
+
+ AUTO_LOCK( m_mutex );
+
+ DataCacheNotificationType_t notificationType = ( bNotify )? DC_FLUSH_DISCARD : DC_NONE;
+
+ memhandle_t hCurrent;
+ memhandle_t hNext;
+
+ unsigned nBytesFlushed = 0;
+ unsigned nBytesCurrent = 0;
+
+ hCurrent = GetFirstUnlockedItem();
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ hNext = GetNextItem( hCurrent );
+ nBytesCurrent = AccessItem( hCurrent )->size;
+
+ if ( DiscardItem( hCurrent, notificationType ) )
+ {
+ nBytesFlushed += nBytesCurrent;
+ }
+ hCurrent = hNext;
+ }
+
+ if ( !bUnlockedOnly )
+ {
+ hCurrent = GetFirstLockedItem();
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ hNext = GetNextItem( hCurrent );
+ nBytesCurrent = AccessItem( hCurrent )->size;
+
+ if ( DiscardItem( hCurrent, notificationType ) )
+ {
+ nBytesFlushed += nBytesCurrent;
+ }
+ hCurrent = hNext;
+ }
+ }
+
+ return nBytesFlushed;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dump the oldest items to free the specified amount of memory. Returns amount actually freed
+//-----------------------------------------------------------------------------
+unsigned CDataCacheSection::Purge( unsigned nBytes )
+{
+ VPROF( "CDataCacheSection::Purge" );
+
+ AUTO_LOCK( m_mutex );
+
+ unsigned nBytesPurged = 0;
+ unsigned nBytesCurrent = 0;
+
+ memhandle_t hCurrent = GetFirstUnlockedItem();
+ memhandle_t hNext;
+
+ while ( hCurrent != INVALID_MEMHANDLE && nBytes > 0 )
+ {
+ hNext = GetNextItem( hCurrent );
+ nBytesCurrent = AccessItem( hCurrent )->size;
+
+ if ( DiscardItem( hCurrent, DC_FLUSH_DISCARD ) )
+ {
+ nBytesPurged += nBytesCurrent;
+ nBytes -= min( nBytesCurrent, nBytes );
+ }
+ hCurrent = hNext;
+ }
+
+ return nBytesPurged;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Dump the oldest items to free the specified number of items. Returns number actually freed
+//-----------------------------------------------------------------------------
+unsigned CDataCacheSection::PurgeItems( unsigned nItems )
+{
+ AUTO_LOCK( m_mutex );
+
+ unsigned nPurged = 0;
+
+ memhandle_t hCurrent = GetFirstUnlockedItem();
+ memhandle_t hNext;
+
+ while ( hCurrent != INVALID_MEMHANDLE && nItems )
+ {
+ hNext = GetNextItem( hCurrent );
+
+ if ( DiscardItem( hCurrent, DC_FLUSH_DISCARD ) )
+ {
+ nItems--;
+ nPurged++;
+ }
+ hCurrent = hNext;
+ }
+
+ return nPurged;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Output the state of the section
+//-----------------------------------------------------------------------------
+void CDataCacheSection::OutputReport( DataCacheReportType_t reportType )
+{
+ m_pSharedCache->OutputReport( reportType, GetName() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Updates the size of a specific item
+// Input : handle -
+// newSize -
+//-----------------------------------------------------------------------------
+void CDataCacheSection::UpdateSize( DataCacheHandle_t handle, unsigned int nNewSize )
+{
+ DataCacheItem_t *pItem = m_LRU.LockResource( (memhandle_t)handle );
+ if ( !pItem )
+ {
+ // If it's gone from memory, size is already irrelevant
+ return;
+ }
+
+ unsigned oldSize = pItem->size;
+
+ if ( oldSize != nNewSize )
+ {
+ // Update the size
+ pItem->size = nNewSize;
+
+ int bytesAdded = nNewSize - oldSize;
+ // If change would grow cache size, then purge items until we have room
+ if ( bytesAdded > 0 )
+ {
+ m_pSharedCache->EnsureCapacity( bytesAdded );
+ }
+
+ m_LRU.NotifySizeChanged( (memhandle_t)handle, oldSize, nNewSize );
+ NoteSizeChanged( oldSize, nNewSize );
+ }
+
+ m_LRU.UnlockResource( (memhandle_t)handle );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+memhandle_t CDataCacheSection::GetFirstUnlockedItem()
+{
+ memhandle_t hCurrent;
+
+ hCurrent = m_LRU.GetFirstUnlocked();
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ if ( AccessItem( hCurrent )->pSection == this )
+ {
+ return hCurrent;
+ }
+ hCurrent = m_LRU.GetNext( hCurrent );
+ }
+ return INVALID_MEMHANDLE;
+}
+
+
+memhandle_t CDataCacheSection::GetFirstLockedItem()
+{
+ memhandle_t hCurrent;
+
+ hCurrent = m_LRU.GetFirstLocked();
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ if ( AccessItem( hCurrent )->pSection == this )
+ {
+ return hCurrent;
+ }
+ hCurrent = m_LRU.GetNext( hCurrent );
+ }
+ return INVALID_MEMHANDLE;
+}
+
+
+memhandle_t CDataCacheSection::GetNextItem( memhandle_t hCurrent )
+{
+ hCurrent = m_LRU.GetNext( hCurrent );
+
+ while ( hCurrent != INVALID_MEMHANDLE )
+ {
+ if ( AccessItem( hCurrent )->pSection == this )
+ {
+ return hCurrent;
+ }
+ hCurrent = m_LRU.GetNext( hCurrent );
+ }
+ return INVALID_MEMHANDLE;
+}
+
+bool CDataCacheSection::DiscardItem( memhandle_t hItem, DataCacheNotificationType_t type )
+{
+ DataCacheItem_t *pItem = AccessItem( hItem );
+ if ( DiscardItemData( pItem, type ) )
+ {
+ if ( m_LRU.LockCount( hItem ) )
+ {
+ m_LRU.BreakLock( hItem );
+ NoteUnlock( pItem->size );
+ }
+
+ FrameLock_t *pFrameLock = m_ThreadFrameLock.Get();
+ if ( pFrameLock )
+ {
+ int iThread = pFrameLock->m_iThread;
+ if ( pItem->pNextFrameLocked[iThread] != DC_NO_NEXT_LOCKED )
+ {
+ if ( pFrameLock->m_pFirst == pItem )
+ {
+ pFrameLock->m_pFirst = pItem->pNextFrameLocked[iThread];
+ }
+ else
+ {
+ DataCacheItem_t *pCurrent = pFrameLock->m_pFirst;
+ while ( pCurrent )
+ {
+ if ( pCurrent->pNextFrameLocked[iThread] == pItem )
+ {
+ pCurrent->pNextFrameLocked[iThread] = pItem->pNextFrameLocked[iThread];
+ break;
+ }
+ pCurrent = pCurrent->pNextFrameLocked[iThread];
+ }
+ }
+ pItem->pNextFrameLocked[iThread] = DC_NO_NEXT_LOCKED;
+ }
+
+ }
+
+#ifdef _DEBUG
+ for ( int i = 0; i < DC_MAX_THREADS_FRAMELOCKED; i++ )
+ {
+ if ( pItem->pNextFrameLocked[i] != DC_NO_NEXT_LOCKED )
+ {
+ DebuggerBreak(); // higher level code needs to handle better
+ }
+ }
+#endif
+
+ pItem->pSection = NULL; // inhibit callbacks from lower level resource system
+ m_LRU.DestroyResource( hItem );
+ return true;
+ }
+ return false;
+}
+
+bool CDataCacheSection::DiscardItemData( DataCacheItem_t *pItem, DataCacheNotificationType_t type )
+{
+ if ( pItem )
+ {
+ if ( type != DC_NONE )
+ {
+ Assert( type == DC_AGE_DISCARD || type == DC_FLUSH_DISCARD || DC_REMOVED );
+
+ if ( type == DC_AGE_DISCARD && m_pSharedCache->IsInFlush() )
+ type = DC_FLUSH_DISCARD;
+
+ DataCacheNotification_t notification =
+ {
+ type,
+ GetName(),
+ pItem->clientId,
+ pItem->pItemData,
+ pItem->size
+ };
+
+ bool bResult = m_pClient->HandleCacheNotification( notification );
+ AssertMsg( bResult, "Refusal of cache drop not yet implemented!" );
+
+ if ( bResult )
+ {
+ NoteRemove( pItem->size );
+ }
+
+ return bResult;
+ }
+
+ OnRemove( pItem->clientId );
+
+ pItem->pSection = NULL;
+ pItem->pItemData = NULL,
+ pItem->clientId = 0;
+
+ NoteRemove( pItem->size );
+
+ return true;
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// CDataCacheSectionFastFind
+//-----------------------------------------------------------------------------
+DataCacheHandle_t CDataCacheSectionFastFind::DoFind( DataCacheClientID_t clientId )
+{
+ AUTO_LOCK( m_mutex );
+ UtlHashFastHandle_t hHash = m_Handles.Find( Hash4( &clientId ) );
+ if( hHash != m_Handles.InvalidHandle() )
+ return m_Handles[hHash];
+ return DC_INVALID_HANDLE;
+}
+
+
+void CDataCacheSectionFastFind::OnAdd( DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem )
+{
+ AUTO_LOCK( m_mutex );
+ Assert( m_Handles.Find( Hash4( &clientId ) ) == m_Handles.InvalidHandle());
+ m_Handles.FastInsert( Hash4( &clientId ), hCacheItem );
+}
+
+
+void CDataCacheSectionFastFind::OnRemove( DataCacheClientID_t clientId )
+{
+ AUTO_LOCK( m_mutex );
+ UtlHashFastHandle_t hHash = m_Handles.Find( Hash4( &clientId ) );
+ Assert( hHash != m_Handles.InvalidHandle());
+ if( hHash != m_Handles.InvalidHandle() )
+ return m_Handles.Remove( hHash );
+}
+
+
+//-----------------------------------------------------------------------------
+// CDataCache
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Convar callback to change data cache
+//-----------------------------------------------------------------------------
+void DataCacheSize_f( IConVar *pConVar, const char *pOldString, float flOldValue )
+{
+ ConVarRef var( pConVar );
+ int nOldValue = (int)flOldValue;
+ if ( var.GetInt() != nOldValue )
+ {
+ g_DataCache.SetSize( var.GetInt() * 1024 * 1024 );
+ }
+}
+ConVar datacachesize( "datacachesize", "64", FCVAR_INTERNAL_USE, "Size in MB.", true, 32, true, 512, DataCacheSize_f );
+
+//-----------------------------------------------------------------------------
+// Connect, disconnect
+//-----------------------------------------------------------------------------
+bool CDataCache::Connect( CreateInterfaceFn factory )
+{
+ if ( !BaseClass::Connect( factory ) )
+ return false;
+
+ g_DataCache.SetSize( datacachesize.GetInt() * 1024 * 1024 );
+ g_pDataCache = this;
+
+ return true;
+}
+
+void CDataCache::Disconnect()
+{
+ g_pDataCache = NULL;
+ BaseClass::Disconnect();
+}
+
+
+//-----------------------------------------------------------------------------
+// Init, Shutdown
+//-----------------------------------------------------------------------------
+InitReturnVal_t CDataCache::Init( void )
+{
+ return BaseClass::Init();
+}
+
+void CDataCache::Shutdown( void )
+{
+ Flush( false, false );
+ BaseClass::Shutdown();
+}
+
+
+//-----------------------------------------------------------------------------
+// Query interface
+//-----------------------------------------------------------------------------
+void *CDataCache::QueryInterface( const char *pInterfaceName )
+{
+ // Loading the datacache DLL mounts *all* interfaces
+ // This includes the backward-compatible interfaces + IStudioDataCache
+ CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary
+ return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing.
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+CDataCache::CDataCache()
+ : m_mutex( m_LRU.AccessMutex() )
+{
+ memset( &m_status, 0, sizeof(m_status) );
+ m_bInFlush = false;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Controls cache size.
+//-----------------------------------------------------------------------------
+void CDataCache::SetSize( int nMaxBytes )
+{
+ m_LRU.SetTargetSize( nMaxBytes );
+ m_LRU.FlushToTargetSize();
+
+ nMaxBytes /= 1024 * 1024;
+
+ if ( datacachesize.GetInt() != nMaxBytes )
+ {
+ datacachesize.SetValue( nMaxBytes );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Controls cache options.
+//-----------------------------------------------------------------------------
+void CDataCache::SetOptions( unsigned options )
+{
+ for ( int i = 0; m_Sections.Count(); i++ )
+ {
+ m_Sections[i]->SetOptions( options );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Controls cache section size.
+//-----------------------------------------------------------------------------
+void CDataCache::SetSectionLimits( const char *pszSectionName, const DataCacheLimits_t &limits )
+{
+ IDataCacheSection *pSection = FindSection( pszSectionName );
+
+ if ( !pSection )
+ {
+ DevMsg( "Cannot find requested cache section \"%s\"", pszSectionName );
+ return;
+ }
+
+ pSection->SetLimits( limits );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Get the current state of the cache
+//-----------------------------------------------------------------------------
+void CDataCache::GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits )
+{
+ if ( pStatus )
+ {
+ *pStatus = m_status;
+ }
+
+ if ( pLimits )
+ {
+ Construct( pLimits );
+ pLimits->nMaxBytes = m_LRU.TargetSize();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Add a section to the cache
+//-----------------------------------------------------------------------------
+IDataCacheSection *CDataCache::AddSection( IDataCacheClient *pClient, const char *pszSectionName, const DataCacheLimits_t &limits, bool bSupportFastFind )
+{
+ CDataCacheSection *pSection;
+
+ pSection = (CDataCacheSection *)FindSection( pszSectionName );
+ if ( pSection )
+ {
+ AssertMsg1( pSection->GetClient() == pClient, "Duplicate cache section name \"%s\"", pszSectionName );
+ return pSection;
+ }
+
+ if ( !bSupportFastFind )
+ pSection = new CDataCacheSection( this, pClient, pszSectionName );
+ else
+ pSection = new CDataCacheSectionFastFind( this, pClient, pszSectionName );
+
+ pSection->SetLimits( limits );
+
+ m_Sections.AddToTail( pSection );
+ return pSection;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove a section from the cache
+//-----------------------------------------------------------------------------
+void CDataCache::RemoveSection( const char *pszClientName, bool bCallFlush )
+{
+ int iSection = FindSectionIndex( pszClientName );
+
+ if ( iSection != m_Sections.InvalidIndex() )
+ {
+ if ( bCallFlush )
+ {
+ m_Sections[iSection]->Flush( false );
+ }
+ delete m_Sections[iSection];
+ m_Sections.FastRemove( iSection );
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find a section of the cache
+//-----------------------------------------------------------------------------
+IDataCacheSection *CDataCache::FindSection( const char *pszClientName )
+{
+ int iSection = FindSectionIndex( pszClientName );
+
+ if ( iSection != m_Sections.InvalidIndex() )
+ {
+ return m_Sections[iSection];
+ }
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CDataCache::EnsureCapacity( unsigned nBytes )
+{
+ VPROF( "CDataCache::EnsureCapacity" );
+
+ m_LRU.EnsureCapacity( nBytes );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Dump the oldest items to free the specified amount of memory. Returns amount actually freed
+//-----------------------------------------------------------------------------
+unsigned CDataCache::Purge( unsigned nBytes )
+{
+ VPROF( "CDataCache::Purge" );
+
+ return m_LRU.Purge( nBytes );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Empty the cache. Returns bytes released, will remove locked items if force specified
+//-----------------------------------------------------------------------------
+unsigned CDataCache::Flush( bool bUnlockedOnly, bool bNotify )
+{
+ VPROF( "CDataCache::Flush" );
+
+ unsigned result;
+
+ if ( m_bInFlush )
+ {
+ return 0;
+ }
+
+ m_bInFlush = true;
+
+ if ( bUnlockedOnly )
+ {
+ result = m_LRU.FlushAllUnlocked();
+ }
+ else
+ {
+ result = m_LRU.FlushAll();
+ }
+
+ m_bInFlush = false;
+
+ return result;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Output the state of the cache
+//-----------------------------------------------------------------------------
+void CDataCache::OutputReport( DataCacheReportType_t reportType, const char *pszSection )
+{
+ int i;
+
+ AUTO_LOCK( m_mutex );
+ int bytesUsed = m_LRU.UsedSize();
+ int bytesTotal = m_LRU.TargetSize();
+
+ float percent = 100.0f * (float)bytesUsed / (float)bytesTotal;
+
+ CUtlVector<memhandle_t> lruList, lockedlist;
+
+ m_LRU.GetLockHandleList( lockedlist );
+ m_LRU.GetLRUHandleList( lruList );
+
+ CDataCacheSection *pSection = NULL;
+ if ( pszSection )
+ {
+ pSection = (CDataCacheSection *)FindSection( pszSection );
+ if ( !pSection )
+ {
+ Msg( "Unknown cache section %s\n", pszSection );
+ return;
+ }
+ }
+
+ if ( reportType == DC_DETAIL_REPORT )
+ {
+ CUtlRBTree< memhandle_t, int > sortedbysize( 0, 0, SortMemhandlesBySizeLessFunc );
+ for ( i = 0; i < lockedlist.Count(); ++i )
+ {
+ if ( !pSection || AccessItem( lockedlist[ i ] )->pSection == pSection )
+ sortedbysize.Insert( lockedlist[ i ] );
+ }
+
+ for ( i = 0; i < lruList.Count(); ++i )
+ {
+ if ( !pSection || AccessItem( lruList[ i ] )->pSection == pSection )
+ sortedbysize.Insert( lruList[ i ] );
+ }
+
+ for ( i = sortedbysize.FirstInorder(); i != sortedbysize.InvalidIndex(); i = sortedbysize.NextInorder( i ) )
+ {
+ OutputItemReport( sortedbysize[ i ] );
+ }
+ OutputReport( DC_SUMMARY_REPORT, pszSection );
+ }
+ else if ( reportType == DC_DETAIL_REPORT_LRU )
+ {
+ for ( i = 0; i < lockedlist.Count(); ++i )
+ {
+ if ( !pSection || AccessItem( lockedlist[ i ] )->pSection == pSection )
+ OutputItemReport( lockedlist[ i ] );
+ }
+
+ for ( i = 0; i < lruList.Count(); ++i )
+ {
+ if ( !pSection || AccessItem( lruList[ i ] )->pSection == pSection )
+ OutputItemReport( lruList[ i ] );
+ }
+ OutputReport( DC_SUMMARY_REPORT, pszSection );
+ }
+ else if ( reportType == DC_SUMMARY_REPORT )
+ {
+ if ( !pszSection )
+ {
+ // summary for all of the sections
+ for ( int i = 0; i < m_Sections.Count(); ++i )
+ {
+ if ( m_Sections[i]->GetName() )
+ {
+ OutputReport( DC_SUMMARY_REPORT, m_Sections[i]->GetName() );
+ }
+ }
+ Msg( "Summary: %i resources total %s, %.2f %% of capacity\n", lockedlist.Count() + lruList.Count(), Q_pretifymem( bytesUsed, 2, true ), percent );
+ }
+ else
+ {
+ // summary for the specified section
+ DataCacheItem_t *pItem;
+ int sectionBytes = 0;
+ int sectionCount = 0;
+ for ( i = 0; i < lockedlist.Count(); ++i )
+ {
+ if ( AccessItem( lockedlist[ i ] )->pSection == pSection )
+ {
+ pItem = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lockedlist[i] );
+ sectionBytes += pItem->size;
+ sectionCount++;
+ }
+ }
+ for ( i = 0; i < lruList.Count(); ++i )
+ {
+ if ( AccessItem( lruList[ i ] )->pSection == pSection )
+ {
+ pItem = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lruList[i] );
+ sectionBytes += pItem->size;
+ sectionCount++;
+ }
+ }
+ int sectionSize = 1;
+ float sectionPercent;
+ if ( pSection->GetLimits().nMaxBytes == (unsigned int)-1 )
+ {
+ // section unrestricted, base on total size
+ sectionSize = bytesTotal;
+ }
+ else if ( pSection->GetLimits().nMaxBytes )
+ {
+ sectionSize = pSection->GetLimits().nMaxBytes;
+ }
+ sectionPercent = 100.0f * (float)sectionBytes/(float)sectionSize;
+ Msg( "Section [%s]: %i resources total %s, %.2f %% of limit (%s)\n", pszSection, sectionCount, Q_pretifymem( sectionBytes, 2, true ), sectionPercent, Q_pretifymem( sectionSize, 2, true ) );
+ }
+ }
+}
+
+//-------------------------------------
+
+void CDataCache::OutputItemReport( memhandle_t hItem )
+{
+ AUTO_LOCK( m_mutex );
+ DataCacheItem_t *pItem = m_LRU.GetResource_NoLockNoLRUTouch( hItem );
+ if ( !pItem )
+ return;
+
+ CDataCacheSection *pSection = pItem->pSection;
+
+ char name[DC_MAX_ITEM_NAME+1];
+
+ name[0] = 0;
+ pSection->GetClient()->GetItemName( pItem->clientId, pItem->pItemData, name, DC_MAX_ITEM_NAME );
+
+ Msg( "\t%16.16s : %12s : 0x%08x, 0x%p, 0x%p : %s : %s\n",
+ Q_pretifymem( pItem->size, 2, true ),
+ pSection->GetName(),
+ pItem->clientId, pItem->pItemData, hItem,
+ ( name[0] ) ? name : "unknown",
+ ( m_LRU.LockCount( hItem ) ) ? CFmtStr( "Locked %d", m_LRU.LockCount( hItem ) ).operator const char*() : "" );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int CDataCache::FindSectionIndex( const char *pszSection )
+{
+ for ( int i = 0; i < m_Sections.Count(); i++ )
+ {
+ if ( stricmp( m_Sections[i]->GetName(), pszSection ) == 0 )
+ return i;
+ }
+ return m_Sections.InvalidIndex();
+}
+
+
+//-----------------------------------------------------------------------------
+// Sorting utility used by the data cache report
+//-----------------------------------------------------------------------------
+bool CDataCache::SortMemhandlesBySizeLessFunc( const memhandle_t& lhs, const memhandle_t& rhs )
+{
+ DataCacheItem_t *pItem1 = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( lhs );
+ DataCacheItem_t *pItem2 = g_DataCache.m_LRU.GetResource_NoLockNoLRUTouch( rhs );
+
+ Assert( pItem1 );
+ Assert( pItem2 );
+
+ return pItem1->size < pItem2->size;
+}
+
+
diff --git a/datacache/datacache.h b/datacache/datacache.h
new file mode 100644
index 0000000..56b7710
--- /dev/null
+++ b/datacache/datacache.h
@@ -0,0 +1,384 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#ifndef DATACACHE_H
+#define DATACACHE_H
+
+#ifdef _WIN32
+#pragma once
+#endif
+
+#include "datamanager.h"
+#include "utlhash.h"
+#include "mempool.h"
+#include "tier0/tslist.h"
+#include "datacache_common.h"
+#include "tier3/tier3.h"
+
+
+//-----------------------------------------------------------------------------
+//
+// Data Cache class declarations
+//
+//-----------------------------------------------------------------------------
+class CDataCache;
+class CDataCacheSection;
+
+//-----------------------------------------------------------------------------
+
+struct DataCacheItemData_t
+{
+ const void * pItemData;
+ unsigned size;
+ DataCacheClientID_t clientId;
+ CDataCacheSection * pSection;
+};
+
+//-------------------------------------
+
+#define DC_NO_NEXT_LOCKED ((DataCacheItem_t *)0xffffffff)
+#define DC_MAX_THREADS_FRAMELOCKED 4
+
+struct DataCacheItem_t : DataCacheItemData_t
+{
+ DataCacheItem_t( const DataCacheItemData_t &data )
+ : DataCacheItemData_t( data ),
+ hLRU( INVALID_MEMHANDLE )
+ {
+ memset( pNextFrameLocked, 0xff, sizeof(pNextFrameLocked) );
+ }
+
+ static DataCacheItem_t *CreateResource( const DataCacheItemData_t &data ) { return new DataCacheItem_t(data); }
+ static unsigned int EstimatedSize( const DataCacheItemData_t &data ) { return data.size; }
+ void DestroyResource();
+ DataCacheItem_t *GetData() { return this; }
+ unsigned int Size() { return size; }
+
+ memhandle_t hLRU;
+ DataCacheItem_t *pNextFrameLocked[DC_MAX_THREADS_FRAMELOCKED];
+
+ DECLARE_FIXEDSIZE_ALLOCATOR_MT(DataCacheItem_t);
+};
+
+//-------------------------------------
+
+typedef CDataManager<DataCacheItem_t, DataCacheItemData_t, DataCacheItem_t *, CThreadFastMutex> CDataCacheLRU;
+
+//-----------------------------------------------------------------------------
+// CDataCacheSection
+//
+// Purpose: Implements a sub-section of the global cache. Subsections are
+// areas of the cache with thier own memory constraints and common
+// management.
+//-----------------------------------------------------------------------------
+class CDataCacheSection : public IDataCacheSection
+{
+public:
+ CDataCacheSection( CDataCache *pSharedCache, IDataCacheClient *pClient, const char *pszName );
+ ~CDataCacheSection();
+
+ IDataCache *GetSharedCache();
+ IDataCacheClient *GetClient() { return m_pClient; }
+ const char *GetName() { return szName; }
+
+ //--------------------------------------------------------
+ // IDataCacheSection methods
+ //--------------------------------------------------------
+ virtual void SetLimits( const DataCacheLimits_t &limits );
+ const DataCacheLimits_t &GetLimits();
+ virtual void SetOptions( unsigned options );
+ virtual void GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits = NULL );
+
+ inline unsigned GetNumBytes() { return m_status.nBytes; }
+ inline unsigned GetNumItems() { return m_status.nItems; }
+
+ inline unsigned GetNumBytesLocked() { return m_status.nBytesLocked; }
+ inline unsigned GetNumItemsLocked() { return m_status.nItemsLocked; }
+
+ inline unsigned GetNumBytesUnlocked() { return m_status.nBytes - m_status.nBytesLocked; }
+ inline unsigned GetNumItemsUnlocked() { return m_status.nItems - m_status.nItemsLocked; }
+
+ virtual void EnsureCapacity( unsigned nBytes, unsigned nItems = 1 );
+
+ //--------------------------------------------------------
+
+ virtual bool Add( DataCacheClientID_t clientId, const void *pItemData, unsigned size, DataCacheHandle_t *pHandle );
+ virtual bool AddEx( DataCacheClientID_t clientId, const void *pItemData, unsigned size, unsigned flags, DataCacheHandle_t *pHandle );
+ virtual DataCacheHandle_t Find( DataCacheClientID_t clientId );
+ virtual DataCacheRemoveResult_t Remove( DataCacheHandle_t handle, const void **ppItemData = NULL, unsigned *pItemSize = NULL, bool bNotify = false );
+ virtual bool IsPresent( DataCacheHandle_t handle );
+
+ //--------------------------------------------------------
+
+ virtual void *Lock( DataCacheHandle_t handle );
+ virtual int Unlock( DataCacheHandle_t handle );
+ virtual void *Get( DataCacheHandle_t handle, bool bFrameLock = false );
+ virtual void *GetNoTouch( DataCacheHandle_t handle, bool bFrameLock = false );
+ virtual void LockMutex();
+ virtual void UnlockMutex();
+
+ //--------------------------------------------------------
+
+ virtual int BeginFrameLocking();
+ virtual bool IsFrameLocking();
+ virtual void *FrameLock( DataCacheHandle_t handle );
+ virtual int EndFrameLocking();
+
+ //--------------------------------------------------------
+
+ virtual int GetLockCount( DataCacheHandle_t handle );
+ virtual int BreakLock( DataCacheHandle_t handle );
+
+ //--------------------------------------------------------
+
+ virtual int *GetFrameUnlockCounterPtr();
+ int m_nFrameUnlockCounter;
+
+ //--------------------------------------------------------
+
+ virtual bool Touch( DataCacheHandle_t handle );
+ virtual bool Age( DataCacheHandle_t handle );
+
+ //--------------------------------------------------------
+
+ virtual unsigned Flush( bool bUnlockedOnly = true, bool bNotify = true );
+ virtual unsigned Purge( unsigned nBytes );
+ unsigned PurgeItems( unsigned nItems );
+
+ //--------------------------------------------------------
+
+ virtual void OutputReport( DataCacheReportType_t reportType = DC_SUMMARY_REPORT );
+
+ virtual void UpdateSize( DataCacheHandle_t handle, unsigned int nNewSize );
+
+private:
+ friend void DataCacheItem_t::DestroyResource();
+
+ virtual void OnAdd( DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem ) {}
+ virtual DataCacheHandle_t DoFind( DataCacheClientID_t clientId );
+ virtual void OnRemove( DataCacheClientID_t clientId ) {}
+
+ memhandle_t GetFirstUnlockedItem();
+ memhandle_t GetFirstLockedItem();
+ memhandle_t GetNextItem( memhandle_t );
+ DataCacheItem_t *AccessItem( memhandle_t hCurrent );
+ bool DiscardItem( memhandle_t hItem, DataCacheNotificationType_t type );
+ bool DiscardItemData( DataCacheItem_t *pItem, DataCacheNotificationType_t type );
+ void NoteAdd( int size );
+ void NoteRemove( int size );
+ void NoteLock( int size );
+ void NoteUnlock( int size );
+ void NoteSizeChanged( int oldSize, int newSize );
+
+ struct FrameLock_t
+ {
+ //$ WARNING: This needs a TSLNodeBase_t as the first item in here.
+ TSLNodeBase_t base;
+ int m_iLock;
+ DataCacheItem_t *m_pFirst;
+ int m_iThread;
+ };
+ typedef CThreadLocal<FrameLock_t *> CThreadFrameLock;
+
+ CDataCacheLRU & m_LRU;
+ CThreadFrameLock m_ThreadFrameLock;
+ DataCacheStatus_t m_status;
+ DataCacheLimits_t m_limits;
+ IDataCacheClient * m_pClient;
+ unsigned m_options;
+ CDataCache * m_pSharedCache;
+ char szName[DC_MAX_CLIENT_NAME + 1];
+ CTSSimpleList<FrameLock_t> m_FreeFrameLocks;
+
+protected:
+ CThreadFastMutex & m_mutex;
+};
+
+
+//-----------------------------------------------------------------------------
+// CDataCacheSectionFastFind
+//
+// Purpose: A section variant that allows clients to have cache support tracking
+// efficiently (a true cache, not just an LRU)
+//-----------------------------------------------------------------------------
+class CDataCacheSectionFastFind : public CDataCacheSection
+{
+public:
+ CDataCacheSectionFastFind(CDataCache *pSharedCache, IDataCacheClient *pClient, const char *pszName )
+ : CDataCacheSection( pSharedCache, pClient, pszName )
+ {
+ m_Handles.Init( 1024 );
+ }
+
+private:
+ virtual DataCacheHandle_t DoFind( DataCacheClientID_t clientId );
+ virtual void OnAdd( DataCacheClientID_t clientId, DataCacheHandle_t hCacheItem );
+ virtual void OnRemove( DataCacheClientID_t clientId );
+
+ CUtlHashFast<DataCacheHandle_t> m_Handles;
+};
+
+
+//-----------------------------------------------------------------------------
+// CDataCache
+//
+// Purpose: The global shared cache. Manages sections and overall budgets.
+//
+//-----------------------------------------------------------------------------
+class CDataCache : public CTier3AppSystem< IDataCache >
+{
+ typedef CTier3AppSystem< IDataCache > BaseClass;
+
+public:
+ CDataCache();
+
+ //--------------------------------------------------------
+ // IAppSystem methods
+ //--------------------------------------------------------
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual InitReturnVal_t Init();
+ virtual void Shutdown();
+
+ //--------------------------------------------------------
+ // IDataCache methods
+ //--------------------------------------------------------
+
+ virtual void SetSize( int nMaxBytes );
+ virtual void SetOptions( unsigned options );
+ virtual void SetSectionLimits( const char *pszSectionName, const DataCacheLimits_t &limits );
+ virtual void GetStatus( DataCacheStatus_t *pStatus, DataCacheLimits_t *pLimits = NULL );
+
+ //--------------------------------------------------------
+
+ virtual IDataCacheSection *AddSection( IDataCacheClient *pClient, const char *pszSectionName, const DataCacheLimits_t &limits = DataCacheLimits_t(), bool bSupportFastFind = false );
+ virtual void RemoveSection( const char *pszClientName, bool bCallFlush = true );
+ virtual IDataCacheSection *FindSection( const char *pszClientName );
+
+ //--------------------------------------------------------
+
+ void EnsureCapacity( unsigned nBytes );
+ virtual unsigned Purge( unsigned nBytes );
+ virtual unsigned Flush( bool bUnlockedOnly = true, bool bNotify = true );
+
+ //--------------------------------------------------------
+
+ virtual void OutputReport( DataCacheReportType_t reportType = DC_SUMMARY_REPORT, const char *pszSection = NULL );
+
+ //--------------------------------------------------------
+
+ inline unsigned GetNumBytes() { return m_status.nBytes; }
+ inline unsigned GetNumItems() { return m_status.nItems; }
+
+ inline unsigned GetNumBytesLocked() { return m_status.nBytesLocked; }
+ inline unsigned GetNumItemsLocked() { return m_status.nItemsLocked; }
+
+ inline unsigned GetNumBytesUnlocked() { return m_status.nBytes - m_status.nBytesLocked; }
+ inline unsigned GetNumItemsUnlocked() { return m_status.nItems - m_status.nItemsLocked; }
+
+private:
+ //-----------------------------------------------------
+
+ friend class CDataCacheSection;
+
+ //-----------------------------------------------------
+
+ DataCacheItem_t *AccessItem( memhandle_t hCurrent );
+
+ bool IsInFlush() { return m_bInFlush; }
+ int FindSectionIndex( const char *pszSection );
+
+ // Utilities used by the data cache report
+ void OutputItemReport( memhandle_t hItem );
+ static bool SortMemhandlesBySizeLessFunc( const memhandle_t& lhs, const memhandle_t& rhs );
+
+ //-----------------------------------------------------
+
+ CDataCacheLRU m_LRU;
+ DataCacheStatus_t m_status;
+ CUtlVector<CDataCacheSection *> m_Sections;
+ bool m_bInFlush;
+ CThreadFastMutex & m_mutex;
+};
+
+//---------------------------------------------------------
+
+extern CDataCache g_DataCache;
+
+//-----------------------------------------------------------------------------
+
+inline DataCacheItem_t *CDataCache::AccessItem( memhandle_t hCurrent )
+{
+ return m_LRU.GetResource_NoLockNoLRUTouch( hCurrent );
+}
+
+//-----------------------------------------------------------------------------
+
+inline IDataCache *CDataCacheSection::GetSharedCache()
+{
+ return m_pSharedCache;
+}
+
+inline DataCacheItem_t *CDataCacheSection::AccessItem( memhandle_t hCurrent )
+{
+ return m_pSharedCache->AccessItem( hCurrent );
+}
+
+// Note: if status updates are moved out of a mutexed section, will need to change these to use interlocked instructions
+
+inline void CDataCacheSection::NoteSizeChanged( int oldSize, int newSize )
+{
+ int nBytes = ( newSize - oldSize );
+
+ m_status.nBytes += nBytes;
+ m_status.nBytesLocked += nBytes;
+ ThreadInterlockedExchangeAdd( &m_pSharedCache->m_status.nBytes, nBytes );
+ ThreadInterlockedExchangeAdd( &m_pSharedCache->m_status.nBytesLocked, nBytes );
+}
+
+inline void CDataCacheSection::NoteAdd( int size )
+{
+ m_status.nBytes += size;
+ m_status.nItems++;
+
+ ThreadInterlockedExchangeAdd( &m_pSharedCache->m_status.nBytes, size );
+ ThreadInterlockedIncrement( &m_pSharedCache->m_status.nItems );
+}
+
+inline void CDataCacheSection::NoteRemove( int size )
+{
+ m_status.nBytes -= size;
+ m_status.nItems--;
+
+ ThreadInterlockedExchangeAdd( &m_pSharedCache->m_status.nBytes, -size );
+ ThreadInterlockedDecrement( &m_pSharedCache->m_status.nItems );
+}
+
+inline void CDataCacheSection::NoteLock( int size )
+{
+ m_status.nBytesLocked += size;
+ m_status.nItemsLocked++;
+
+ ThreadInterlockedExchangeAdd( &m_pSharedCache->m_status.nBytesLocked, size );
+ ThreadInterlockedIncrement( &m_pSharedCache->m_status.nItemsLocked );
+}
+
+inline void CDataCacheSection::NoteUnlock( int size )
+{
+ m_status.nBytesLocked -= size;
+ m_status.nItemsLocked--;
+
+ ThreadInterlockedExchangeAdd( &m_pSharedCache->m_status.nBytesLocked, -size );
+ ThreadInterlockedDecrement( &m_pSharedCache->m_status.nItemsLocked );
+
+ // something has been unlocked, assume cached pointers are now invalid
+ m_nFrameUnlockCounter++;
+}
+
+//-----------------------------------------------------------------------------
+
+#endif // DATACACHE_H \ No newline at end of file
diff --git a/datacache/datacache.vpc b/datacache/datacache.vpc
new file mode 100644
index 0000000..27a64f5
--- /dev/null
+++ b/datacache/datacache.vpc
@@ -0,0 +1,55 @@
+//-----------------------------------------------------------------------------
+// DATACACHE.VPC
+//
+// Project Script
+//-----------------------------------------------------------------------------
+
+$macro SRCDIR ".."
+$Macro OUTBINDIR "$SRCDIR\..\game\bin"
+
+$include "$SRCDIR\vpc_scripts\source_dll_base.vpc"
+
+$Configuration
+{
+ $Linker
+ {
+ $SystemLibraries "iconv" [$OSXALL]
+ }
+ $Compiler
+ {
+ $PreprocessorDefinitions "$BASE;MDLCACHE_DLL_EXPORT"
+ }
+}
+
+
+$Project "datacache"
+{
+ $Folder "Source Files"
+ {
+ $File "datacache.cpp"
+ $File "mdlcache.cpp"
+ $File "$SRCDIR\public\studio.cpp"
+ $File "$SRCDIR\public\studio_virtualmodel.cpp"
+ $File "..\common\studiobyteswap.cpp"
+ }
+
+ $Folder "Header Files"
+ {
+ $File "datacache.h"
+ $File "datacache_common.h"
+ $File "$SRCDIR\public\studio.h"
+ $File "..\common\studiobyteswap.h"
+ }
+
+ $Folder "Interface"
+ {
+ $File "$SRCDIR\public\datacache\idatacache.h"
+ $File "$SRCDIR\public\datacache\imdlcache.h"
+ }
+
+ $folder "Link Libraries"
+ {
+ $Lib tier2
+ $Lib tier3
+ }
+}
diff --git a/datacache/datacache_common.h b/datacache/datacache_common.h
new file mode 100644
index 0000000..7140511
--- /dev/null
+++ b/datacache/datacache_common.h
@@ -0,0 +1,31 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+#ifndef DATACACHE_COMMON_H
+#define DATACACHE_COMMON_H
+
+#if defined( _WIN32 )
+#pragma once
+#endif
+
+#include "tier3/tier3.h"
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class IDataCacheSection;
+FORWARD_DECLARE_HANDLE( memhandle_t );
+typedef memhandle_t DataCacheHandle_t;
+
+
+//-----------------------------------------------------------------------------
+// Console commands
+//-----------------------------------------------------------------------------
+extern ConVar developer;
+
+
+#endif // DATACACHE_COMMON_H
diff --git a/datacache/mdlcache.cpp b/datacache/mdlcache.cpp
new file mode 100644
index 0000000..8f1ff36
--- /dev/null
+++ b/datacache/mdlcache.cpp
@@ -0,0 +1,3924 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// The copyright to the contents herein is the property of Valve, L.L.C.
+// The contents may be used and/or copied only with the written permission of
+// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
+// the agreement/contract under which the contents have been supplied.
+//
+// model loading and caching
+//
+//===========================================================================//
+
+#include <memory.h>
+#include "tier0/vprof.h"
+#include "tier0/icommandline.h"
+#include "tier1/utllinkedlist.h"
+#include "tier1/utlmap.h"
+#include "datacache/imdlcache.h"
+#include "istudiorender.h"
+#include "filesystem.h"
+#include "optimize.h"
+#include "materialsystem/imaterialsystemhardwareconfig.h"
+#include "materialsystem/imesh.h"
+#include "datacache/idatacache.h"
+#include "studio.h"
+#include "vcollide.h"
+#include "utldict.h"
+#include "convar.h"
+#include "datacache_common.h"
+#include "mempool.h"
+#include "vphysics_interface.h"
+#include "phyfile.h"
+#include "studiobyteswap.h"
+#include "tier2/fileutils.h"
+#include "filesystem/IQueuedLoader.h"
+#include "tier1/lzmaDecoder.h"
+#include "functors.h"
+
+// XXX remove this later. (henryg)
+#if 0 && defined(_DEBUG) && defined(_WIN32) && !defined(_X360)
+typedef struct LARGE_INTEGER { unsigned long long QuadPart; } LARGE_INTEGER;
+extern "C" void __stdcall OutputDebugStringA( const char *lpOutputString );
+extern "C" long __stdcall QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount );
+extern "C" long __stdcall QueryPerformanceFrequency( LARGE_INTEGER *lpPerformanceCount );
+namespace {
+ class CDebugMicroTimer
+ {
+ public:
+ CDebugMicroTimer(const char* n) : name(n) { QueryPerformanceCounter(&start); }
+ ~CDebugMicroTimer() {
+ LARGE_INTEGER end;
+ char outbuf[128];
+ QueryPerformanceCounter(&end);
+ if (!freq) QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
+ V_snprintf(outbuf, 128, "%s %6d us\n", name, (int)((end.QuadPart - start.QuadPart) * 1000000 / freq));
+ OutputDebugStringA(outbuf);
+ }
+ LARGE_INTEGER start;
+ const char* name;
+ static long long freq;
+ };
+ long long CDebugMicroTimer::freq = 0;
+}
+#define DEBUG_SCOPE_TIMER(name) CDebugMicroTimer dbgLocalTimer(#name)
+#else
+#define DEBUG_SCOPE_TIMER(name) (void)0
+#endif
+
+#ifdef _RETAIL
+#define NO_LOG_MDLCACHE 1
+#endif
+
+#ifdef NO_LOG_MDLCACHE
+#define LogMdlCache() 0
+#else
+#define LogMdlCache() mod_trace_load.GetBool()
+#endif
+
+#define MdlCacheMsg if ( !LogMdlCache() ) ; else Msg
+#define MdlCacheWarning if ( !LogMdlCache() ) ; else Warning
+
+#if defined( _X360 )
+#define AsyncMdlCache() 0 // Explicitly OFF for 360 (incompatible)
+#else
+#define AsyncMdlCache() 0
+#endif
+
+#define ERROR_MODEL "models/error.mdl"
+#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I')
+
+#define MakeCacheID( handle, type ) ( ( (uint)(handle) << 16 ) | (uint)(type) )
+#define HandleFromCacheID( id) ( (MDLHandle_t)((id) >> 16) )
+#define TypeFromCacheID( id ) ( (MDLCacheDataType_t)((id) & 0xffff) )
+
+enum
+{
+ STUDIODATA_FLAGS_STUDIOMESH_LOADED = 0x0001,
+ STUDIODATA_FLAGS_VCOLLISION_LOADED = 0x0002,
+ STUDIODATA_ERROR_MODEL = 0x0004,
+ STUDIODATA_FLAGS_NO_STUDIOMESH = 0x0008,
+ STUDIODATA_FLAGS_NO_VERTEX_DATA = 0x0010,
+ STUDIODATA_FLAGS_VCOLLISION_SHARED = 0x0020,
+ STUDIODATA_FLAGS_LOCKED_MDL = 0x0040,
+};
+
+// only models with type "mod_studio" have this data
+struct studiodata_t
+{
+ // The .mdl file
+ DataCacheHandle_t m_MDLCache;
+
+ // the vphysics.dll collision model
+ vcollide_t m_VCollisionData;
+
+ studiohwdata_t m_HardwareData;
+#if defined( USE_HARDWARE_CACHE )
+ DataCacheHandle_t m_HardwareDataCache;
+#endif
+
+ unsigned short m_nFlags;
+
+ short m_nRefCount;
+
+ // pointer to the virtual version of the model
+ virtualmodel_t *m_pVirtualModel;
+
+ // array of cache handles to demand loaded virtual model data
+ int m_nAnimBlockCount;
+ DataCacheHandle_t *m_pAnimBlock;
+ unsigned long *m_iFakeAnimBlockStall;
+
+ // vertex data is usually compressed to save memory (model decal code only needs some data)
+ DataCacheHandle_t m_VertexCache;
+ bool m_VertexDataIsCompressed;
+
+ int m_nAutoplaySequenceCount;
+ unsigned short *m_pAutoplaySequenceList;
+
+ void *m_pUserData;
+
+ DECLARE_FIXEDSIZE_ALLOCATOR_MT( studiodata_t );
+};
+
+DEFINE_FIXEDSIZE_ALLOCATOR_MT( studiodata_t, 128, CUtlMemoryPool::GROW_SLOW );
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+class CTempAllocHelper
+{
+public:
+ CTempAllocHelper()
+ {
+ m_pData = NULL;
+ }
+
+ ~CTempAllocHelper()
+ {
+ Free();
+ }
+
+ void *Get()
+ {
+ return m_pData;
+ }
+
+ void Alloc( int nSize )
+ {
+ m_pData = malloc( nSize );
+ }
+
+ void Free()
+ {
+ if ( m_pData )
+ {
+ free( m_pData );
+ m_pData = NULL;
+ }
+ }
+private:
+ void *m_pData;
+};
+
+//-----------------------------------------------------------------------------
+// ConVars
+//-----------------------------------------------------------------------------
+static ConVar r_rootlod( "r_rootlod", "0", FCVAR_ARCHIVE );
+static ConVar mod_forcedata( "mod_forcedata", ( AsyncMdlCache() ) ? "0" : "1", 0, "Forces all model file data into cache on model load." );
+static ConVar mod_test_not_available( "mod_test_not_available", "0", FCVAR_CHEAT );
+static ConVar mod_test_mesh_not_available( "mod_test_mesh_not_available", "0", FCVAR_CHEAT );
+static ConVar mod_test_verts_not_available( "mod_test_verts_not_available", "0", FCVAR_CHEAT );
+static ConVar mod_load_mesh_async( "mod_load_mesh_async", ( AsyncMdlCache() ) ? "1" : "0" );
+static ConVar mod_load_anims_async( "mod_load_anims_async", ( IsX360() || AsyncMdlCache() ) ? "1" : "0" );
+static ConVar mod_load_vcollide_async( "mod_load_vcollide_async", ( AsyncMdlCache() ) ? "1" : "0" );
+static ConVar mod_trace_load( "mod_trace_load", "0" );
+static ConVar mod_lock_mdls_on_load( "mod_lock_mdls_on_load", ( IsX360() ) ? "1" : "0" );
+static ConVar mod_load_fakestall( "mod_load_fakestall", "0", 0, "Forces all ANI file loading to stall for specified ms\n");
+
+//-----------------------------------------------------------------------------
+// Utility functions
+//-----------------------------------------------------------------------------
+
+#if defined( USE_HARDWARE_CACHE )
+unsigned ComputeHardwareDataSize( studiohwdata_t *pData )
+{
+ unsigned size = 0;
+ for ( int i = pData->m_RootLOD; i < pData->m_NumLODs; i++ )
+ {
+ studioloddata_t *pLOD = &pData->m_pLODs[i];
+ for ( int j = 0; j < pData->m_NumStudioMeshes; j++ )
+ {
+ studiomeshdata_t *pMeshData = &pLOD->m_pMeshData[j];
+ for ( int k = 0; k < pMeshData->m_NumGroup; k++ )
+ {
+ size += pMeshData->m_pMeshGroup[k].m_pMesh->ComputeMemoryUsed();
+ }
+ }
+ }
+ return size;
+}
+#endif
+
+//-----------------------------------------------------------------------------
+// Async support
+//-----------------------------------------------------------------------------
+
+#define MDLCACHE_NONE ((MDLCacheDataType_t)-1)
+
+struct AsyncInfo_t
+{
+ AsyncInfo_t() : hControl( NULL ), hModel( MDLHANDLE_INVALID ), type( MDLCACHE_NONE ), iAnimBlock( 0 ) {}
+
+ FSAsyncControl_t hControl;
+ MDLHandle_t hModel;
+ MDLCacheDataType_t type;
+ int iAnimBlock;
+};
+
+const int NO_ASYNC = CUtlLinkedList< AsyncInfo_t >::InvalidIndex();
+
+//-------------------------------------
+
+CUtlMap<int, int> g_AsyncInfoMap( DefLessFunc( int ) );
+CThreadFastMutex g_AsyncInfoMapMutex;
+
+inline int MakeAsyncInfoKey( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock )
+{
+ Assert( type <= 7 && iAnimBlock < 8*1024 );
+ return ( ( ( (int)hModel) << 16 ) | ( (int)type << 13 ) | iAnimBlock );
+}
+
+inline int GetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock = 0 )
+{
+ AUTO_LOCK( g_AsyncInfoMapMutex );
+ int key = MakeAsyncInfoKey( hModel, type, iAnimBlock );
+ int i = g_AsyncInfoMap.Find( key );
+ if ( i == g_AsyncInfoMap.InvalidIndex() )
+ {
+ return NO_ASYNC;
+ }
+ return g_AsyncInfoMap[i];
+}
+
+inline int SetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int iAnimBlock, int index )
+{
+ AUTO_LOCK( g_AsyncInfoMapMutex );
+ Assert( index == NO_ASYNC || GetAsyncInfoIndex( hModel, type, iAnimBlock ) == NO_ASYNC );
+ int key = MakeAsyncInfoKey( hModel, type, iAnimBlock );
+ if ( index == NO_ASYNC )
+ {
+ g_AsyncInfoMap.Remove( key );
+ }
+ else
+ {
+ g_AsyncInfoMap.Insert( key, index );
+ }
+
+ return index;
+}
+
+inline int SetAsyncInfoIndex( MDLHandle_t hModel, MDLCacheDataType_t type, int index )
+{
+ return SetAsyncInfoIndex( hModel, type, 0, index );
+}
+
+//-----------------------------------------------------------------------------
+// QUEUED LOADING
+// Populates the cache by pushing expected MDL's (and all of their data).
+// The Model cache i/o behavior is unchanged during gameplay, ideally the cache
+// should yield miss free behaviour.
+//-----------------------------------------------------------------------------
+
+struct ModelParts_t
+{
+ enum BufferType_t
+ {
+ BUFFER_MDL = 0,
+ BUFFER_VTX = 1,
+ BUFFER_VVD = 2,
+ BUFFER_PHY = 3,
+ BUFFER_MAXPARTS,
+ };
+
+ ModelParts_t()
+ {
+ nLoadedParts = 0;
+ nExpectedParts = 0;
+ hMDL = MDLHANDLE_INVALID;
+ hFileCache = 0;
+ bHeaderLoaded = false;
+ bMaterialsPending = false;
+ bTexturesPending = false;
+ }
+
+ // thread safe, only one thread will get a positive result
+ bool DoFinalProcessing()
+ {
+ // indicates that all buffers have arrived
+ // when all parts are present, returns true ( guaranteed once ), and marked as completed
+ return nLoadedParts.AssignIf( nExpectedParts, nExpectedParts | 0x80000000 );
+ }
+
+ CUtlBuffer Buffers[BUFFER_MAXPARTS];
+ MDLHandle_t hMDL;
+
+ // async material loading on PC
+ FileCacheHandle_t hFileCache;
+ bool bHeaderLoaded;
+ bool bMaterialsPending;
+ bool bTexturesPending;
+ CUtlVector< IMaterial* > Materials;
+
+ // bit flags
+ CInterlockedInt nLoadedParts;
+ int nExpectedParts;
+
+private:
+ ModelParts_t(const ModelParts_t&); // no impl
+ ModelParts_t& operator=(const ModelParts_t&); // no impl
+};
+
+struct CleanupModelParts_t
+{
+ FileCacheHandle_t hFileCache;
+ CUtlVector< IMaterial* > Materials;
+};
+
+//-----------------------------------------------------------------------------
+// Implementation of the simple studio data cache (no caching)
+//-----------------------------------------------------------------------------
+class CMDLCache : public CTier3AppSystem< IMDLCache >, public IStudioDataCache, public CDefaultDataCacheClient
+{
+ typedef CTier3AppSystem< IMDLCache > BaseClass;
+
+public:
+ CMDLCache();
+
+ // Inherited from IAppSystem
+ virtual bool Connect( CreateInterfaceFn factory );
+ virtual void Disconnect();
+ virtual void *QueryInterface( const char *pInterfaceName );
+ virtual InitReturnVal_t Init();
+ virtual void Shutdown();
+
+ // Inherited from IStudioDataCache
+ bool VerifyHeaders( studiohdr_t *pStudioHdr );
+ vertexFileHeader_t *CacheVertexData( studiohdr_t *pStudioHdr );
+
+ // Inherited from IMDLCache
+ virtual MDLHandle_t FindMDL( const char *pMDLRelativePath );
+ virtual int AddRef( MDLHandle_t handle );
+ virtual int Release( MDLHandle_t handle );
+ virtual int GetRef( MDLHandle_t handle );
+ virtual void MarkAsLoaded(MDLHandle_t handle);
+
+ virtual studiohdr_t *GetStudioHdr( MDLHandle_t handle );
+ virtual studiohwdata_t *GetHardwareData( MDLHandle_t handle );
+ virtual vcollide_t *GetVCollide( MDLHandle_t handle ) { return GetVCollideEx( handle, true); }
+ virtual vcollide_t *GetVCollideEx( MDLHandle_t handle, bool synchronousLoad = true );
+ virtual unsigned char *GetAnimBlock( MDLHandle_t handle, int nBlock );
+ virtual virtualmodel_t *GetVirtualModel( MDLHandle_t handle );
+ virtual virtualmodel_t *GetVirtualModelFast( const studiohdr_t *pStudioHdr, MDLHandle_t handle );
+ virtual int GetAutoplayList( MDLHandle_t handle, unsigned short **pOut );
+ virtual void TouchAllData( MDLHandle_t handle );
+ virtual void SetUserData( MDLHandle_t handle, void* pData );
+ virtual void *GetUserData( MDLHandle_t handle );
+ virtual bool IsErrorModel( MDLHandle_t handle );
+ virtual void SetCacheNotify( IMDLCacheNotify *pNotify );
+ virtual vertexFileHeader_t *GetVertexData( MDLHandle_t handle );
+ virtual void Flush( MDLCacheFlush_t nFlushFlags = MDLCACHE_FLUSH_ALL );
+ virtual void Flush( MDLHandle_t handle, int nFlushFlags = MDLCACHE_FLUSH_ALL );
+ virtual const char *GetModelName( MDLHandle_t handle );
+
+ IDataCacheSection *GetCacheSection( MDLCacheDataType_t type )
+ {
+ switch ( type )
+ {
+ case MDLCACHE_STUDIOHWDATA:
+ case MDLCACHE_VERTEXES:
+ // meshes and vertexes are isolated to their own section
+ return m_pMeshCacheSection;
+
+ case MDLCACHE_ANIMBLOCK:
+ // anim blocks have their own section
+ return m_pAnimBlockCacheSection;
+
+ default:
+ // everybody else
+ return m_pModelCacheSection;
+ }
+ }
+
+ void *AllocData( MDLCacheDataType_t type, int size );
+ void FreeData( MDLCacheDataType_t type, void *pData );
+ void CacheData( DataCacheHandle_t *c, void *pData, int size, const char *name, MDLCacheDataType_t type, DataCacheClientID_t id = (DataCacheClientID_t)-1 );
+ void *CheckData( DataCacheHandle_t c, MDLCacheDataType_t type );
+ void *CheckDataNoTouch( DataCacheHandle_t c, MDLCacheDataType_t type );
+ void UncacheData( DataCacheHandle_t c, MDLCacheDataType_t type, bool bLockedOk = false );
+
+ void DisableAsync() { mod_load_mesh_async.SetValue( 0 ); mod_load_anims_async.SetValue( 0 ); }
+
+ virtual void BeginLock();
+ virtual void EndLock();
+ virtual int *GetFrameUnlockCounterPtrOLD();
+ virtual int *GetFrameUnlockCounterPtr( MDLCacheDataType_t type );
+
+ virtual void FinishPendingLoads();
+
+ // Task switch
+ void ReleaseMaterialSystemObjects();
+ void RestoreMaterialSystemObjects( int nChangeFlags );
+ virtual bool GetVCollideSize( MDLHandle_t handle, int *pVCollideSize );
+
+ virtual void BeginMapLoad();
+ virtual void EndMapLoad();
+
+ virtual void InitPreloadData( bool rebuild );
+ virtual void ShutdownPreloadData();
+
+ virtual bool IsDataLoaded( MDLHandle_t handle, MDLCacheDataType_t type );
+
+ virtual studiohdr_t *LockStudioHdr( MDLHandle_t handle );
+ virtual void UnlockStudioHdr( MDLHandle_t handle );
+
+ virtual bool PreloadModel( MDLHandle_t handle );
+ virtual void ResetErrorModelStatus( MDLHandle_t handle );
+
+ virtual void MarkFrame();
+
+ // Queued loading
+ void ProcessQueuedData( ModelParts_t *pModelParts, bool bHeaderOnly = false );
+ static void QueuedLoaderCallback_MDL( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError );
+ static void ProcessDynamicLoad( ModelParts_t *pModelParts );
+ static void CleanupDynamicLoad( CleanupModelParts_t *pCleanup );
+
+private:
+ // Inits, shuts downs studiodata_t
+ void InitStudioData( MDLHandle_t handle );
+ void ShutdownStudioData( MDLHandle_t handle );
+
+ // Returns the *actual* name of the model (could be an error model if the requested model didn't load)
+ const char *GetActualModelName( MDLHandle_t handle );
+
+ // Constructs a filename based on a model handle
+ void MakeFilename( MDLHandle_t handle, const char *pszExtension, char *pszFileName, int nMaxLength );
+
+ // Inform filesystem that we unloaded a particular file
+ void NotifyFileUnloaded( MDLHandle_t handle, const char *pszExtension );
+
+ // Attempts to load a MDL file, validates that it's ok.
+ bool ReadMDLFile( MDLHandle_t handle, const char *pMDLFileName, CUtlBuffer &buf );
+
+ // Unserializes the VCollide file associated w/ models (the vphysics representation)
+ void UnserializeVCollide( MDLHandle_t handle, bool synchronousLoad );
+
+ // Destroys the VCollide associated w/ models
+ void DestroyVCollide( MDLHandle_t handle );
+
+ // Unserializes the MDL
+ studiohdr_t *UnserializeMDL( MDLHandle_t handle, void *pData, int nDataSize, bool bDataValid );
+
+ // Unserializes an animation block from disk
+ unsigned char *UnserializeAnimBlock( MDLHandle_t handle, int nBlock );
+
+ // Allocates/frees the anim blocks
+ void AllocateAnimBlocks( studiodata_t *pStudioData, int nCount );
+ void FreeAnimBlocks( MDLHandle_t handle );
+
+ // Allocates/frees the virtual model
+ void AllocateVirtualModel( MDLHandle_t handle );
+ void FreeVirtualModel( MDLHandle_t handle );
+
+ // Purpose: Pulls all submodels/.ani file models into the cache
+ void UnserializeAllVirtualModelsAndAnimBlocks( MDLHandle_t handle );
+
+ // Loads/unloads the static meshes
+ bool LoadHardwareData( MDLHandle_t handle ); // returns false if not ready
+ void UnloadHardwareData( MDLHandle_t handle, bool bCacheRemove = true, bool bLockedOk = false );
+
+ // Allocates/frees autoplay sequence list
+ void AllocateAutoplaySequences( studiodata_t *pStudioData, int nCount );
+ void FreeAutoplaySequences( studiodata_t *pStudioData );
+
+ FSAsyncStatus_t LoadData( const char *pszFilename, const char *pszPathID, bool bAsync, FSAsyncControl_t *pControl ) { return LoadData( pszFilename, pszPathID, NULL, 0, 0, bAsync, pControl ); }
+ FSAsyncStatus_t LoadData( const char *pszFilename, const char *pszPathID, void *pDest, int nBytes, int nOffset, bool bAsync, FSAsyncControl_t *pControl );
+ vertexFileHeader_t *LoadVertexData( studiohdr_t *pStudioHdr );
+ vertexFileHeader_t *BuildAndCacheVertexData( studiohdr_t *pStudioHdr, vertexFileHeader_t *pRawVvdHdr );
+ bool BuildHardwareData( MDLHandle_t handle, studiodata_t *pStudioData, studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr );
+ void ConvertFlexData( studiohdr_t *pStudioHdr );
+
+ int ProcessPendingAsync( int iAsync );
+ void ProcessPendingAsyncs( MDLCacheDataType_t type = MDLCACHE_NONE );
+ bool ClearAsync( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, bool bAbort = false );
+
+ const char *GetVTXExtension();
+
+ virtual bool HandleCacheNotification( const DataCacheNotification_t &notification );
+ virtual bool GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen );
+
+ virtual bool GetAsyncLoad( MDLCacheDataType_t type );
+ virtual bool SetAsyncLoad( MDLCacheDataType_t type, bool bAsync );
+
+ // Creates the 360 file if it doesn't exist or is out of date
+ int UpdateOrCreate( studiohdr_t *pHdr, const char *pFilename, char *pX360Filename, int maxLen, const char *pPathID, bool bForce = false );
+
+ // Attempts to read the platform native file - on 360 it can read and swap Win32 file as a fallback
+ bool ReadFileNative( char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes = 0 );
+
+ // Creates a thin cache entry (to be used for model decals) from fat vertex data
+ vertexFileHeader_t * CreateThinVertexes( vertexFileHeader_t * originalData, const studiohdr_t * pStudioHdr, int * cacheLength );
+
+ // Processes raw data (from an I/O source) into the cache. Sets the cache state as expected for bad data.
+ bool ProcessDataIntoCache( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, void *pData, int nDataSize, bool bDataValid );
+
+ void BreakFrameLock( bool bModels = true, bool bMesh = true );
+ void RestoreFrameLock();
+
+private:
+ IDataCacheSection *m_pModelCacheSection;
+ IDataCacheSection *m_pMeshCacheSection;
+ IDataCacheSection *m_pAnimBlockCacheSection;
+
+ int m_nModelCacheFrameLocks;
+ int m_nMeshCacheFrameLocks;
+
+ CUtlDict< studiodata_t*, MDLHandle_t > m_MDLDict;
+
+ IMDLCacheNotify *m_pCacheNotify;
+
+ CUtlFixedLinkedList< AsyncInfo_t > m_PendingAsyncs;
+
+ CThreadFastMutex m_QueuedLoadingMutex;
+ CThreadFastMutex m_AsyncMutex;
+
+ bool m_bLostVideoMemory : 1;
+ bool m_bConnected : 1;
+ bool m_bInitialized : 1;
+};
+
+//-----------------------------------------------------------------------------
+// Singleton interface
+//-----------------------------------------------------------------------------
+static CMDLCache g_MDLCache;
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMDLCache, IMDLCache, MDLCACHE_INTERFACE_VERSION, g_MDLCache );
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CMDLCache, IStudioDataCache, STUDIO_DATA_CACHE_INTERFACE_VERSION, g_MDLCache );
+
+
+//-----------------------------------------------------------------------------
+// Task switch
+//-----------------------------------------------------------------------------
+static void ReleaseMaterialSystemObjects( )
+{
+ g_MDLCache.ReleaseMaterialSystemObjects();
+}
+
+static void RestoreMaterialSystemObjects( int nChangeFlags )
+{
+ g_MDLCache.RestoreMaterialSystemObjects( nChangeFlags );
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CMDLCache::CMDLCache() : BaseClass( false )
+{
+ m_bLostVideoMemory = false;
+ m_bConnected = false;
+ m_bInitialized = false;
+ m_pCacheNotify = NULL;
+ m_pModelCacheSection = NULL;
+ m_pMeshCacheSection = NULL;
+ m_pAnimBlockCacheSection = NULL;
+ m_nModelCacheFrameLocks = 0;
+ m_nMeshCacheFrameLocks = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Connect, disconnect
+//-----------------------------------------------------------------------------
+bool CMDLCache::Connect( CreateInterfaceFn factory )
+{
+ // Connect can be called twice, because this inherits from 2 appsystems.
+ if ( m_bConnected )
+ return true;
+
+ if ( !BaseClass::Connect( factory ) )
+ return false;
+
+ if ( !g_pMaterialSystemHardwareConfig || !g_pPhysicsCollision || !g_pStudioRender || !g_pMaterialSystem )
+ return false;
+
+ m_bConnected = true;
+ if( g_pMaterialSystem )
+ {
+ g_pMaterialSystem->AddReleaseFunc( ::ReleaseMaterialSystemObjects );
+ g_pMaterialSystem->AddRestoreFunc( ::RestoreMaterialSystemObjects );
+ }
+
+ return true;
+}
+
+void CMDLCache::Disconnect()
+{
+ if ( g_pMaterialSystem && m_bConnected )
+ {
+ g_pMaterialSystem->RemoveReleaseFunc( ::ReleaseMaterialSystemObjects );
+ g_pMaterialSystem->RemoveRestoreFunc( ::RestoreMaterialSystemObjects );
+ m_bConnected = false;
+ }
+
+ BaseClass::Disconnect();
+}
+
+
+//-----------------------------------------------------------------------------
+// Query Interface
+//-----------------------------------------------------------------------------
+void *CMDLCache::QueryInterface( const char *pInterfaceName )
+{
+ if (!Q_strncmp( pInterfaceName, STUDIO_DATA_CACHE_INTERFACE_VERSION, Q_strlen(STUDIO_DATA_CACHE_INTERFACE_VERSION) + 1))
+ return (IStudioDataCache*)this;
+
+ if (!Q_strncmp( pInterfaceName, MDLCACHE_INTERFACE_VERSION, Q_strlen(MDLCACHE_INTERFACE_VERSION) + 1))
+ return (IMDLCache*)this;
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Init/Shutdown
+//-----------------------------------------------------------------------------
+
+#define MODEL_CACHE_MODEL_SECTION_NAME "ModelData"
+#define MODEL_CACHE_MESH_SECTION_NAME "ModelMesh"
+#define MODEL_CACHE_ANIMBLOCK_SECTION_NAME "AnimBlock"
+
+// #define ENABLE_CACHE_WATCH 1
+
+#if defined( ENABLE_CACHE_WATCH )
+static ConVar cache_watch( "cache_watch", "", 0 );
+
+static void CacheLog( const char *fileName, const char *accessType )
+{
+ if ( Q_stristr( fileName, cache_watch.GetString() ) )
+ {
+ Msg( "%s access to %s\n", accessType, fileName );
+ }
+}
+#endif
+
+InitReturnVal_t CMDLCache::Init()
+{
+ // Can be called twice since it inherits from 2 appsystems
+ if ( m_bInitialized )
+ return INIT_OK;
+
+ InitReturnVal_t nRetVal = BaseClass::Init();
+ if ( nRetVal != INIT_OK )
+ return nRetVal;
+
+ if ( !m_pModelCacheSection )
+ {
+ m_pModelCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_MODEL_SECTION_NAME );
+ }
+
+ if ( !m_pMeshCacheSection )
+ {
+ unsigned int meshLimit = (unsigned)-1;
+ DataCacheLimits_t limits( meshLimit, (unsigned)-1, 0, 0 );
+ m_pMeshCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_MESH_SECTION_NAME, limits );
+ }
+
+ if ( !m_pAnimBlockCacheSection )
+ {
+ // 360 tuned to worst case, ep_outland_12a, less than 6 MB is not a viable working set
+ unsigned int animBlockLimit = IsX360() ? 6*1024*1024 : (unsigned)-1;
+ DataCacheLimits_t limits( animBlockLimit, (unsigned)-1, 0, 0 );
+ m_pAnimBlockCacheSection = g_pDataCache->AddSection( this, MODEL_CACHE_ANIMBLOCK_SECTION_NAME, limits );
+ }
+
+ if ( IsX360() )
+ {
+ // By default, source data is assumed to be non-native to the 360.
+ StudioByteSwap::ActivateByteSwapping( true );
+ StudioByteSwap::SetCollisionInterface( g_pPhysicsCollision );
+ }
+ m_bLostVideoMemory = false;
+ m_bInitialized = true;
+
+#if defined( ENABLE_CACHE_WATCH )
+ g_pFullFileSystem->AddLoggingFunc( &CacheLog );
+#endif
+
+ return INIT_OK;
+}
+
+void CMDLCache::Shutdown()
+{
+ if ( !m_bInitialized )
+ return;
+#if defined( ENABLE_CACHE_WATCH )
+ g_pFullFileSystem->RemoveLoggingFunc( CacheLog );
+#endif
+ m_bInitialized = false;
+
+ if ( m_pModelCacheSection || m_pMeshCacheSection )
+ {
+ // Free all MDLs that haven't been cleaned up
+ MDLHandle_t i = m_MDLDict.First();
+ while ( i != m_MDLDict.InvalidIndex() )
+ {
+ ShutdownStudioData( i );
+ i = m_MDLDict.Next( i );
+ }
+
+ m_MDLDict.Purge();
+
+ if ( m_pModelCacheSection )
+ {
+ g_pDataCache->RemoveSection( MODEL_CACHE_MODEL_SECTION_NAME );
+ m_pModelCacheSection = NULL;
+ }
+ if ( m_pMeshCacheSection )
+ {
+ g_pDataCache->RemoveSection( MODEL_CACHE_MESH_SECTION_NAME );
+ m_pMeshCacheSection = NULL;
+ }
+ }
+
+ if ( m_pAnimBlockCacheSection )
+ {
+ g_pDataCache->RemoveSection( MODEL_CACHE_ANIMBLOCK_SECTION_NAME );
+ m_pAnimBlockCacheSection = NULL;
+ }
+
+ BaseClass::Shutdown();
+}
+
+
+//-----------------------------------------------------------------------------
+// Flushes an MDLHandle_t
+//-----------------------------------------------------------------------------
+void CMDLCache::Flush( MDLHandle_t handle, int nFlushFlags )
+{
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ Assert( pStudioData != NULL );
+
+ bool bIgnoreLock = ( nFlushFlags & MDLCACHE_FLUSH_IGNORELOCK ) != 0;
+
+ // release the hardware portion
+ if ( nFlushFlags & MDLCACHE_FLUSH_STUDIOHWDATA )
+ {
+ if ( ClearAsync( handle, MDLCACHE_STUDIOHWDATA, 0, true ) )
+ {
+ m_pMeshCacheSection->Unlock( pStudioData->m_VertexCache );
+ }
+ UnloadHardwareData( handle, true, bIgnoreLock );
+ }
+
+ // free collision
+ if ( nFlushFlags & MDLCACHE_FLUSH_VCOLLIDE )
+ {
+ DestroyVCollide( handle );
+ }
+
+ // Free animations
+ if ( nFlushFlags & MDLCACHE_FLUSH_VIRTUALMODEL )
+ {
+ FreeVirtualModel( handle );
+ }
+
+ if ( nFlushFlags & MDLCACHE_FLUSH_ANIMBLOCK )
+ {
+ FreeAnimBlocks( handle );
+ }
+
+ if ( nFlushFlags & MDLCACHE_FLUSH_AUTOPLAY )
+ {
+ // Free autoplay sequences
+ FreeAutoplaySequences( pStudioData );
+ }
+
+ if ( nFlushFlags & MDLCACHE_FLUSH_STUDIOHDR )
+ {
+ MdlCacheMsg( "MDLCache: Free studiohdr %s\n", GetModelName( handle ) );
+
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL )
+ {
+ GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( pStudioData->m_MDLCache );
+ pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_LOCKED_MDL;
+ }
+ UncacheData( pStudioData->m_MDLCache, MDLCACHE_STUDIOHDR, bIgnoreLock );
+ pStudioData->m_MDLCache = NULL;
+ }
+
+ if ( nFlushFlags & MDLCACHE_FLUSH_VERTEXES )
+ {
+ MdlCacheMsg( "MDLCache: Free VVD %s\n", GetModelName( handle ) );
+
+ ClearAsync( handle, MDLCACHE_VERTEXES, 0, true );
+
+ UncacheData( pStudioData->m_VertexCache, MDLCACHE_VERTEXES, bIgnoreLock );
+ pStudioData->m_VertexCache = NULL;
+ }
+
+ // Now check whatever files are not loaded, make sure file system knows
+ // that we don't have them loaded.
+ if ( !IsDataLoaded( handle, MDLCACHE_STUDIOHDR ) )
+ NotifyFileUnloaded( handle, ".mdl" );
+
+ if ( !IsDataLoaded( handle, MDLCACHE_STUDIOHWDATA ) )
+ NotifyFileUnloaded( handle, GetVTXExtension() );
+
+ if ( !IsDataLoaded( handle, MDLCACHE_VERTEXES ) )
+ NotifyFileUnloaded( handle, ".vvd" );
+
+ if ( !IsDataLoaded( handle, MDLCACHE_VCOLLIDE ) )
+ NotifyFileUnloaded( handle, ".phy" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Inits, shuts downs studiodata_t
+//-----------------------------------------------------------------------------
+void CMDLCache::InitStudioData( MDLHandle_t handle )
+{
+ Assert( m_MDLDict[handle] == NULL );
+
+ studiodata_t *pStudioData = new studiodata_t;
+ m_MDLDict[handle] = pStudioData;
+ memset( pStudioData, 0, sizeof( studiodata_t ) );
+}
+
+void CMDLCache::ShutdownStudioData( MDLHandle_t handle )
+{
+ Flush( handle );
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ Assert( pStudioData != NULL );
+ delete pStudioData;
+ m_MDLDict[handle] = NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets the cache notify
+//-----------------------------------------------------------------------------
+void CMDLCache::SetCacheNotify( IMDLCacheNotify *pNotify )
+{
+ m_pCacheNotify = pNotify;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the name of the model
+//-----------------------------------------------------------------------------
+const char *CMDLCache::GetModelName( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return ERROR_MODEL;
+
+ return m_MDLDict.GetElementName( handle );
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the *actual* name of the model (could be an error model)
+//-----------------------------------------------------------------------------
+const char *CMDLCache::GetActualModelName( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return ERROR_MODEL;
+
+ if ( m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL )
+ return ERROR_MODEL;
+
+ return m_MDLDict.GetElementName( handle );
+}
+
+
+//-----------------------------------------------------------------------------
+// Constructs a filename based on a model handle
+//-----------------------------------------------------------------------------
+void CMDLCache::MakeFilename( MDLHandle_t handle, const char *pszExtension, char *pszFileName, int nMaxLength )
+{
+ Q_strncpy( pszFileName, GetActualModelName( handle ), nMaxLength );
+ Q_SetExtension( pszFileName, pszExtension, nMaxLength );
+ Q_FixSlashes( pszFileName );
+#ifdef _LINUX
+ Q_strlower( pszFileName );
+#endif
+}
+
+//-----------------------------------------------------------------------------
+void CMDLCache::NotifyFileUnloaded( MDLHandle_t handle, const char *pszExtension )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return;
+ if ( !m_MDLDict.IsValidIndex( handle ) )
+ return;
+
+ char szFilename[MAX_PATH];
+ V_strcpy_safe( szFilename, m_MDLDict.GetElementName( handle ) );
+ V_SetExtension( szFilename, pszExtension, sizeof(szFilename) );
+ V_FixSlashes( szFilename );
+ g_pFullFileSystem->NotifyFileUnloaded( szFilename, "game" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Finds an MDL
+//-----------------------------------------------------------------------------
+MDLHandle_t CMDLCache::FindMDL( const char *pMDLRelativePath )
+{
+ // can't trust provided path
+ // ensure provided path correctly resolves (Dictionary is case-insensitive)
+ char szFixedName[MAX_PATH];
+ V_strncpy( szFixedName, pMDLRelativePath, sizeof( szFixedName ) );
+ V_RemoveDotSlashes( szFixedName, '/' );
+
+ MDLHandle_t handle = m_MDLDict.Find( szFixedName );
+ if ( handle == m_MDLDict.InvalidIndex() )
+ {
+ handle = m_MDLDict.Insert( szFixedName, NULL );
+ InitStudioData( handle );
+ }
+
+ AddRef( handle );
+ return handle;
+}
+
+//-----------------------------------------------------------------------------
+// Reference counting
+//-----------------------------------------------------------------------------
+int CMDLCache::AddRef( MDLHandle_t handle )
+{
+ return ++m_MDLDict[handle]->m_nRefCount;
+}
+
+int CMDLCache::Release( MDLHandle_t handle )
+{
+ // Deal with shutdown order issues (i.e. datamodel shutting down after mdlcache)
+ if ( !m_bInitialized )
+ return 0;
+
+ // NOTE: It can be null during shutdown because multiple studiomdls
+ // could be referencing the same virtual model
+ if ( !m_MDLDict[handle] )
+ return 0;
+
+ Assert( m_MDLDict[handle]->m_nRefCount > 0 );
+
+ int nRefCount = --m_MDLDict[handle]->m_nRefCount;
+ if ( nRefCount <= 0 )
+ {
+ ShutdownStudioData( handle );
+ m_MDLDict.RemoveAt( handle );
+ }
+
+ return nRefCount;
+}
+
+int CMDLCache::GetRef( MDLHandle_t handle )
+{
+ if ( !m_bInitialized )
+ return 0;
+
+ if ( !m_MDLDict[handle] )
+ return 0;
+
+ return m_MDLDict[handle]->m_nRefCount;
+}
+
+//-----------------------------------------------------------------------------
+// Unserializes the PHY file associated w/ models (the vphysics representation)
+//-----------------------------------------------------------------------------
+void CMDLCache::UnserializeVCollide( MDLHandle_t handle, bool synchronousLoad )
+{
+ VPROF( "CMDLCache::UnserializeVCollide" );
+
+ // FIXME: Should the vcollde be played into cacheable memory?
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_VCOLLIDE );
+
+ if ( iAsync == NO_ASYNC )
+ {
+ // clear existing data
+ pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_VCOLLISION_LOADED;
+ memset( &pStudioData->m_VCollisionData, 0, sizeof( pStudioData->m_VCollisionData ) );
+
+#if 0
+ // FIXME: ywb
+ // If we don't ask for the virtual model to load, then we can get a hitch later on after startup
+ // Should we async load the sub .mdls during startup assuming they'll all be resident by the time the level can actually
+ // start drawing?
+ if ( pStudioData->m_pVirtualModel || synchronousLoad )
+#endif
+ {
+ virtualmodel_t *pVirtualModel = GetVirtualModel( handle );
+ if ( pVirtualModel )
+ {
+ for ( int i = 1; i < pVirtualModel->m_group.Count(); i++ )
+ {
+ MDLHandle_t sharedHandle = (MDLHandle_t) (int)pVirtualModel->m_group[i].cache & 0xffff;
+ studiodata_t *pData = m_MDLDict[sharedHandle];
+ if ( !(pData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED) )
+ {
+ UnserializeVCollide( sharedHandle, synchronousLoad );
+ }
+ if ( pData->m_VCollisionData.solidCount > 0 )
+ {
+ pStudioData->m_VCollisionData = pData->m_VCollisionData;
+ pStudioData->m_nFlags |= STUDIODATA_FLAGS_VCOLLISION_SHARED;
+ return;
+ }
+ }
+ }
+ }
+
+ char pFileName[MAX_PATH];
+ MakeFilename( handle, ".phy", pFileName, sizeof(pFileName) );
+ if ( IsX360() )
+ {
+ char pX360Filename[MAX_PATH];
+ UpdateOrCreate( NULL, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
+ Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
+ }
+
+ bool bAsyncLoad = mod_load_vcollide_async.GetBool() && !synchronousLoad;
+
+ MdlCacheMsg( "MDLCache: %s load vcollide %s\n", bAsyncLoad ? "Async" : "Sync", GetModelName( handle ) );
+
+ AsyncInfo_t info;
+ if ( IsDebug() )
+ {
+ memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
+ }
+ info.hModel = handle;
+ info.type = MDLCACHE_VCOLLIDE;
+ info.iAnimBlock = 0;
+ info.hControl = NULL;
+ LoadData( pFileName, "GAME", bAsyncLoad, &info.hControl );
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ iAsync = SetAsyncInfoIndex( handle, MDLCACHE_VCOLLIDE, m_PendingAsyncs.AddToTail( info ) );
+ }
+ }
+ else if ( synchronousLoad )
+ {
+ AsyncInfo_t *pInfo;
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ pInfo = &m_PendingAsyncs[iAsync];
+ }
+ if ( pInfo->hControl )
+ {
+ g_pFullFileSystem->AsyncFinish( pInfo->hControl, true );
+ }
+ }
+
+ ProcessPendingAsync( iAsync );
+}
+
+
+//-----------------------------------------------------------------------------
+// Free model's collision data
+//-----------------------------------------------------------------------------
+void CMDLCache::DestroyVCollide( MDLHandle_t handle )
+{
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_SHARED )
+ return;
+
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED )
+ {
+ pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_VCOLLISION_LOADED;
+ if ( pStudioData->m_VCollisionData.solidCount )
+ {
+ if ( m_pCacheNotify )
+ {
+ m_pCacheNotify->OnDataUnloaded( MDLCACHE_VCOLLIDE, handle );
+ }
+
+ MdlCacheMsg("MDLCache: Unload vcollide %s\n", GetModelName( handle ) );
+
+ g_pPhysicsCollision->VCollideUnload( &pStudioData->m_VCollisionData );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Unserializes the PHY file associated w/ models (the vphysics representation)
+//-----------------------------------------------------------------------------
+vcollide_t *CMDLCache::GetVCollideEx( MDLHandle_t handle, bool synchronousLoad /*= true*/ )
+{
+ if ( mod_test_not_available.GetBool() )
+ return NULL;
+
+ if ( handle == MDLHANDLE_INVALID )
+ return NULL;
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ if ( ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) == 0 )
+ {
+ UnserializeVCollide( handle, synchronousLoad );
+ }
+
+ // We've loaded an empty collision file or no file was found, so return NULL
+ if ( !pStudioData->m_VCollisionData.solidCount )
+ return NULL;
+
+ return &pStudioData->m_VCollisionData;
+}
+
+
+bool CMDLCache::GetVCollideSize( MDLHandle_t handle, int *pVCollideSize )
+{
+ *pVCollideSize = 0;
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ if ( ( pStudioData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) == 0 )
+ return false;
+
+ vcollide_t *pCollide = &pStudioData->m_VCollisionData;
+ for ( int j = 0; j < pCollide->solidCount; j++ )
+ {
+ *pVCollideSize += g_pPhysicsCollision->CollideSize( pCollide->solids[j] );
+ }
+ *pVCollideSize += pCollide->descSize;
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Allocates/frees the anim blocks
+//-----------------------------------------------------------------------------
+void CMDLCache::AllocateAnimBlocks( studiodata_t *pStudioData, int nCount )
+{
+ Assert( pStudioData->m_pAnimBlock == NULL );
+
+ pStudioData->m_nAnimBlockCount = nCount;
+ pStudioData->m_pAnimBlock = new DataCacheHandle_t[pStudioData->m_nAnimBlockCount];
+
+ memset( pStudioData->m_pAnimBlock, 0, sizeof(DataCacheHandle_t) * pStudioData->m_nAnimBlockCount );
+
+ pStudioData->m_iFakeAnimBlockStall = new unsigned long [pStudioData->m_nAnimBlockCount];
+ memset( pStudioData->m_iFakeAnimBlockStall, 0, sizeof( unsigned long ) * pStudioData->m_nAnimBlockCount );
+}
+
+void CMDLCache::FreeAnimBlocks( MDLHandle_t handle )
+{
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ if ( pStudioData->m_pAnimBlock )
+ {
+ for (int i = 0; i < pStudioData->m_nAnimBlockCount; ++i )
+ {
+ MdlCacheMsg( "MDLCache: Free Anim block: %d\n", i );
+
+ ClearAsync( handle, MDLCACHE_ANIMBLOCK, i, true );
+ if ( pStudioData->m_pAnimBlock[i] )
+ {
+ UncacheData( pStudioData->m_pAnimBlock[i], MDLCACHE_ANIMBLOCK, true );
+ }
+ }
+
+ delete[] pStudioData->m_pAnimBlock;
+ pStudioData->m_pAnimBlock = NULL;
+
+ delete[] pStudioData->m_iFakeAnimBlockStall;
+ pStudioData->m_iFakeAnimBlockStall = NULL;
+ }
+
+ pStudioData->m_nAnimBlockCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Unserializes an animation block from disk
+//-----------------------------------------------------------------------------
+unsigned char *CMDLCache::UnserializeAnimBlock( MDLHandle_t handle, int nBlock )
+{
+ VPROF( "CMDLCache::UnserializeAnimBlock" );
+
+ if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
+ {
+ // anim block i/o is not allowed at this stage
+ return NULL;
+ }
+
+ // Block 0 is never used!!!
+ Assert( nBlock > 0 );
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_ANIMBLOCK, nBlock );
+
+ if ( iAsync == NO_ASYNC )
+ {
+ studiohdr_t *pStudioHdr = GetStudioHdr( handle );
+
+ // FIXME: For consistency, the block name maybe shouldn't have 'model' in it.
+ char const *pModelName = pStudioHdr->pszAnimBlockName();
+ mstudioanimblock_t *pBlock = pStudioHdr->pAnimBlock( nBlock );
+ int nSize = pBlock->dataend - pBlock->datastart;
+ if ( nSize == 0 )
+ return NULL;
+
+ // allocate space in the cache
+ pStudioData->m_pAnimBlock[nBlock] = NULL;
+
+ char pFileName[MAX_PATH];
+ Q_strncpy( pFileName, pModelName, sizeof(pFileName) );
+ Q_FixSlashes( pFileName );
+#ifdef _LINUX
+ Q_strlower( pFileName );
+#endif
+ if ( IsX360() )
+ {
+ char pX360Filename[MAX_PATH];
+ UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
+ Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
+ }
+
+ MdlCacheMsg( "MDLCache: Begin load Anim Block %s (block %i)\n", GetModelName( handle ), nBlock );
+
+ AsyncInfo_t info;
+ if ( IsDebug() )
+ {
+ memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
+ }
+ info.hModel = handle;
+ info.type = MDLCACHE_ANIMBLOCK;
+ info.iAnimBlock = nBlock;
+ info.hControl = NULL;
+ LoadData( pFileName, "GAME", NULL, nSize, pBlock->datastart, mod_load_anims_async.GetBool(), &info.hControl );
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ iAsync = SetAsyncInfoIndex( handle, MDLCACHE_ANIMBLOCK, nBlock, m_PendingAsyncs.AddToTail( info ) );
+ }
+ }
+
+ ProcessPendingAsync( iAsync );
+
+ return ( unsigned char * )CheckData( pStudioData->m_pAnimBlock[nBlock], MDLCACHE_ANIMBLOCK );
+}
+
+//-----------------------------------------------------------------------------
+// Gets at an animation block associated with an MDL
+//-----------------------------------------------------------------------------
+unsigned char *CMDLCache::GetAnimBlock( MDLHandle_t handle, int nBlock )
+{
+ if ( mod_test_not_available.GetBool() )
+ return NULL;
+
+ if ( handle == MDLHANDLE_INVALID )
+ return NULL;
+
+ // Allocate animation blocks if we don't have them yet
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ if ( pStudioData->m_pAnimBlock == NULL )
+ {
+ studiohdr_t *pStudioHdr = GetStudioHdr( handle );
+ AllocateAnimBlocks( pStudioData, pStudioHdr->numanimblocks );
+ }
+
+ // check for request being in range
+ if ( nBlock < 0 || nBlock >= pStudioData->m_nAnimBlockCount)
+ return NULL;
+
+ // Check the cache to see if the animation is in memory
+ unsigned char *pData = ( unsigned char * )CheckData( pStudioData->m_pAnimBlock[nBlock], MDLCACHE_ANIMBLOCK );
+ if ( !pData )
+ {
+ pStudioData->m_pAnimBlock[nBlock] = NULL;
+
+ // It's not in memory, read it off of disk
+ pData = UnserializeAnimBlock( handle, nBlock );
+ }
+
+ if (mod_load_fakestall.GetInt())
+ {
+ unsigned int t = Plat_MSTime();
+ if (pStudioData->m_iFakeAnimBlockStall[nBlock] == 0 || pStudioData->m_iFakeAnimBlockStall[nBlock] > t)
+ {
+ pStudioData->m_iFakeAnimBlockStall[nBlock] = t;
+ }
+
+ if ((int)(t - pStudioData->m_iFakeAnimBlockStall[nBlock]) < mod_load_fakestall.GetInt())
+ {
+ return NULL;
+ }
+ }
+ return pData;
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocates/frees autoplay sequence list
+//-----------------------------------------------------------------------------
+void CMDLCache::AllocateAutoplaySequences( studiodata_t *pStudioData, int nCount )
+{
+ FreeAutoplaySequences( pStudioData );
+
+ pStudioData->m_nAutoplaySequenceCount = nCount;
+ pStudioData->m_pAutoplaySequenceList = new unsigned short[nCount];
+}
+
+void CMDLCache::FreeAutoplaySequences( studiodata_t *pStudioData )
+{
+ if ( pStudioData->m_pAutoplaySequenceList )
+ {
+ delete[] pStudioData->m_pAutoplaySequenceList;
+ pStudioData->m_pAutoplaySequenceList = NULL;
+ }
+
+ pStudioData->m_nAutoplaySequenceCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets the autoplay list
+//-----------------------------------------------------------------------------
+int CMDLCache::GetAutoplayList( MDLHandle_t handle, unsigned short **pAutoplayList )
+{
+ if ( pAutoplayList )
+ {
+ *pAutoplayList = NULL;
+ }
+
+ if ( handle == MDLHANDLE_INVALID )
+ return 0;
+
+ virtualmodel_t *pVirtualModel = GetVirtualModel( handle );
+ if ( pVirtualModel )
+ {
+ if ( pAutoplayList && pVirtualModel->m_autoplaySequences.Count() )
+ {
+ *pAutoplayList = pVirtualModel->m_autoplaySequences.Base();
+ }
+ return pVirtualModel->m_autoplaySequences.Count();
+ }
+
+ // FIXME: Should we cache autoplay info here on demand instead of in unserializeMDL?
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ if ( pAutoplayList )
+ {
+ *pAutoplayList = pStudioData->m_pAutoplaySequenceList;
+ }
+
+ return pStudioData->m_nAutoplaySequenceCount;
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocates/frees the virtual model
+//-----------------------------------------------------------------------------
+void CMDLCache::AllocateVirtualModel( MDLHandle_t handle )
+{
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ Assert( pStudioData->m_pVirtualModel == NULL );
+ pStudioData->m_pVirtualModel = new virtualmodel_t;
+
+ // FIXME: The old code slammed these; could have leaked memory?
+ Assert( pStudioData->m_nAnimBlockCount == 0 );
+ Assert( pStudioData->m_pAnimBlock == NULL );
+}
+
+void CMDLCache::FreeVirtualModel( MDLHandle_t handle )
+{
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ if ( pStudioData && pStudioData->m_pVirtualModel )
+ {
+ int nGroupCount = pStudioData->m_pVirtualModel->m_group.Count();
+ Assert( (nGroupCount >= 1) && pStudioData->m_pVirtualModel->m_group[0].cache == (void*)(uintp)handle );
+
+ // NOTE: Start at *1* here because the 0th element contains a reference to *this* handle
+ for ( int i = 1; i < nGroupCount; ++i )
+ {
+ MDLHandle_t h = (MDLHandle_t)(int)pStudioData->m_pVirtualModel->m_group[i].cache&0xffff;
+ FreeVirtualModel( h );
+ Release( h );
+ }
+
+ delete pStudioData->m_pVirtualModel;
+ pStudioData->m_pVirtualModel = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the virtual model
+//-----------------------------------------------------------------------------
+virtualmodel_t *CMDLCache::GetVirtualModel( MDLHandle_t handle )
+{
+ if ( mod_test_not_available.GetBool() )
+ return NULL;
+
+ if ( handle == MDLHANDLE_INVALID )
+ return NULL;
+
+ studiohdr_t *pStudioHdr = GetStudioHdr( handle );
+
+ if ( pStudioHdr == NULL )
+ return NULL;
+
+ return GetVirtualModelFast( pStudioHdr, handle );
+}
+
+virtualmodel_t *CMDLCache::GetVirtualModelFast( const studiohdr_t *pStudioHdr, MDLHandle_t handle )
+{
+ if (pStudioHdr->numincludemodels == 0)
+ return NULL;
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ if ( !pStudioData )
+ return NULL;
+
+ if ( !pStudioData->m_pVirtualModel )
+ {
+ DevMsg( 2, "Loading virtual model for %s\n", pStudioHdr->pszName() );
+
+ CMDLCacheCriticalSection criticalSection( this );
+
+ AllocateVirtualModel( handle );
+
+ // Group has to be zero to ensure refcounting is correct
+ int nGroup = pStudioData->m_pVirtualModel->m_group.AddToTail( );
+ Assert( nGroup == 0 );
+ pStudioData->m_pVirtualModel->m_group[nGroup].cache = (void *)(uintp)handle;
+
+ // Add all dependent data
+ pStudioData->m_pVirtualModel->AppendModels( 0, pStudioHdr );
+ }
+
+ return pStudioData->m_pVirtualModel;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Pulls all submodels/.ani file models into the cache
+// to avoid runtime hitches and load animations at load time, set mod_forcedata to be 1
+//-----------------------------------------------------------------------------
+void CMDLCache::UnserializeAllVirtualModelsAndAnimBlocks( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return;
+
+ // might be re-loading, discard old virtualmodel to force rebuild
+ // unfortunately, the virtualmodel does build data into the cacheable studiohdr
+ FreeVirtualModel( handle );
+
+ if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
+ {
+ // queued loading has to do it
+ return;
+ }
+
+ // don't load the submodel data
+ if ( !mod_forcedata.GetBool() )
+ return;
+
+ // if not present, will instance and load the submodels
+ GetVirtualModel( handle );
+
+ if ( IsX360() )
+ {
+ // 360 does not drive the anims into its small cache section
+ return;
+ }
+
+ // Note that the animblocks start at 1!!!
+ studiohdr_t *pStudioHdr = GetStudioHdr( handle );
+ for ( int i = 1 ; i < (int)pStudioHdr->numanimblocks; ++i )
+ {
+ GetAnimBlock( handle, i );
+ }
+
+ ProcessPendingAsyncs( MDLCACHE_ANIMBLOCK );
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the static meshes
+//-----------------------------------------------------------------------------
+bool CMDLCache::LoadHardwareData( MDLHandle_t handle )
+{
+ Assert( handle != MDLHANDLE_INVALID );
+
+ // Don't try to load VTX files if we don't have focus...
+ if ( m_bLostVideoMemory )
+ return false;
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ CMDLCacheCriticalSection criticalSection( this );
+
+ // Load up the model
+ studiohdr_t *pStudioHdr = GetStudioHdr( handle );
+ if ( !pStudioHdr || !pStudioHdr->numbodyparts )
+ {
+ pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
+ return true;
+ }
+
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH )
+ {
+ return false;
+ }
+
+ if ( LogMdlCache() &&
+ GetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA ) == NO_ASYNC &&
+ GetAsyncInfoIndex( handle, MDLCACHE_VERTEXES ) == NO_ASYNC )
+ {
+ MdlCacheMsg( "MDLCache: Begin load studiomdl %s\n", GetModelName( handle ) );
+ }
+
+ // Vertex data is required to call LoadModel(), so make sure that's ready
+ if ( !GetVertexData( handle ) )
+ {
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_VERTEX_DATA )
+ {
+ pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
+ }
+ return false;
+ }
+
+ int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA );
+
+ if ( iAsync == NO_ASYNC )
+ {
+ m_pMeshCacheSection->Lock( pStudioData->m_VertexCache );
+
+ // load and persist the vtx file
+ // use model name for correct path
+ char pFileName[MAX_PATH];
+ MakeFilename( handle, GetVTXExtension(), pFileName, sizeof(pFileName) );
+ if ( IsX360() )
+ {
+ char pX360Filename[MAX_PATH];
+ UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
+ Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
+ }
+
+ MdlCacheMsg("MDLCache: Begin load VTX %s\n", GetModelName( handle ) );
+
+ AsyncInfo_t info;
+ if ( IsDebug() )
+ {
+ memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
+ }
+ info.hModel = handle;
+ info.type = MDLCACHE_STUDIOHWDATA;
+ info.iAnimBlock = 0;
+ info.hControl = NULL;
+ LoadData( pFileName, "GAME", mod_load_mesh_async.GetBool(), &info.hControl );
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ iAsync = SetAsyncInfoIndex( handle, MDLCACHE_STUDIOHWDATA, m_PendingAsyncs.AddToTail( info ) );
+ }
+ }
+
+ if ( ProcessPendingAsync( iAsync ) > 0 )
+ {
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH )
+ {
+ return false;
+ }
+
+ return ( pStudioData->m_HardwareData.m_NumStudioMeshes != 0 );
+ }
+
+ return false;
+}
+
+void CMDLCache::ConvertFlexData( studiohdr_t *pStudioHdr )
+{
+ float flVertAnimFixedPointScale = pStudioHdr->VertAnimFixedPointScale();
+
+ for ( int i = 0; i < pStudioHdr->numbodyparts; i++ )
+ {
+ mstudiobodyparts_t *pBody = pStudioHdr->pBodypart( i );
+ for ( int j = 0; j < pBody->nummodels; j++ )
+ {
+ mstudiomodel_t *pModel = pBody->pModel( j );
+ for ( int k = 0; k < pModel->nummeshes; k++ )
+ {
+ mstudiomesh_t *pMesh = pModel->pMesh( k );
+ for ( int l = 0; l < pMesh->numflexes; l++ )
+ {
+ mstudioflex_t *pFlex = pMesh->pFlex( l );
+ bool bIsWrinkleAnim = ( pFlex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE );
+ for ( int m = 0; m < pFlex->numverts; m++ )
+ {
+ mstudiovertanim_t *pVAnim = bIsWrinkleAnim ?
+ pFlex->pVertanimWrinkle( m ) : pFlex->pVertanim( m );
+ pVAnim->ConvertToFixed( flVertAnimFixedPointScale );
+ }
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+bool CMDLCache::BuildHardwareData( MDLHandle_t handle, studiodata_t *pStudioData, studiohdr_t *pStudioHdr, OptimizedModel::FileHeader_t *pVtxHdr )
+{
+ if ( pVtxHdr )
+ {
+ MdlCacheMsg("MDLCache: Alloc VTX %s\n", pStudioHdr->pszName() );
+
+ // check header
+ if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION )
+ {
+ Warning( "Error Index File for '%s' version %d should be %d\n", pStudioHdr->pszName(), pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION );
+ pVtxHdr = NULL;
+ }
+ else if ( pVtxHdr->checkSum != pStudioHdr->checksum )
+ {
+ Warning( "Error Index File for '%s' checksum %d should be %d\n", pStudioHdr->pszName(), pVtxHdr->checkSum, pStudioHdr->checksum );
+ pVtxHdr = NULL;
+ }
+ }
+
+ if ( !pVtxHdr )
+ {
+ pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
+ return false;
+ }
+
+ CTempAllocHelper pOriginalData;
+ if ( IsX360() )
+ {
+ unsigned char *pInputData = (unsigned char *)pVtxHdr + sizeof( OptimizedModel::FileHeader_t );
+ if ( CLZMA::IsCompressed( pInputData ) )
+ {
+ // vtx arrives compressed, decode and cache the results
+ unsigned int nOriginalSize = CLZMA::GetActualSize( pInputData );
+ pOriginalData.Alloc( sizeof( OptimizedModel::FileHeader_t ) + nOriginalSize );
+ V_memcpy( pOriginalData.Get(), pVtxHdr, sizeof( OptimizedModel::FileHeader_t ) );
+ unsigned int nOutputSize = CLZMA::Uncompress( pInputData, sizeof( OptimizedModel::FileHeader_t ) + (unsigned char *)pOriginalData.Get() );
+ if ( nOutputSize != nOriginalSize )
+ {
+ // decoder failure
+ return false;
+ }
+
+ pVtxHdr = (OptimizedModel::FileHeader_t *)pOriginalData.Get();
+ }
+ }
+
+ MdlCacheMsg( "MDLCache: Load studiomdl %s\n", pStudioHdr->pszName() );
+
+ Assert( GetVertexData( handle ) );
+
+ BeginLock();
+ bool bLoaded = g_pStudioRender->LoadModel( pStudioHdr, pVtxHdr, &pStudioData->m_HardwareData );
+ EndLock();
+
+ if ( bLoaded )
+ {
+ pStudioData->m_nFlags |= STUDIODATA_FLAGS_STUDIOMESH_LOADED;
+ }
+ else
+ {
+ pStudioData->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
+ }
+
+ if ( m_pCacheNotify )
+ {
+ m_pCacheNotify->OnDataLoaded( MDLCACHE_STUDIOHWDATA, handle );
+ }
+
+#if defined( USE_HARDWARE_CACHE )
+ GetCacheSection( MDLCACHE_STUDIOHWDATA )->Add( MakeCacheID( handle, MDLCACHE_STUDIOHWDATA ), &pStudioData->m_HardwareData, ComputeHardwareDataSize( &pStudioData->m_HardwareData ), &pStudioData->m_HardwareDataCache );
+#endif
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Loads the static meshes
+//-----------------------------------------------------------------------------
+void CMDLCache::UnloadHardwareData( MDLHandle_t handle, bool bCacheRemove, bool bLockedOk )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return;
+
+ // Don't load it if it's loaded
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_STUDIOMESH_LOADED )
+ {
+#if defined( USE_HARDWARE_CACHE )
+ if ( bCacheRemove )
+ {
+ if ( GetCacheSection( MDLCACHE_STUDIOHWDATA )->BreakLock( pStudioData->m_HardwareDataCache ) && !bLockedOk )
+ {
+ DevMsg( "Warning: freed a locked resource\n" );
+ Assert( 0 );
+ }
+
+ GetCacheSection( MDLCACHE_STUDIOHWDATA )->Remove( pStudioData->m_HardwareDataCache );
+ }
+#endif
+
+ if ( m_pCacheNotify )
+ {
+ m_pCacheNotify->OnDataUnloaded( MDLCACHE_STUDIOHWDATA, handle );
+ }
+
+ MdlCacheMsg("MDLCache: Unload studiomdl %s\n", GetModelName( handle ) );
+
+ g_pStudioRender->UnloadModel( &pStudioData->m_HardwareData );
+ memset( &pStudioData->m_HardwareData, 0, sizeof( pStudioData->m_HardwareData ) );
+ pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_STUDIOMESH_LOADED;
+
+ NotifyFileUnloaded( handle, ".mdl" );
+
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the hardware data associated with an MDL
+//-----------------------------------------------------------------------------
+studiohwdata_t *CMDLCache::GetHardwareData( MDLHandle_t handle )
+{
+ if ( mod_test_not_available.GetBool() )
+ return NULL;
+
+ if ( mod_test_mesh_not_available.GetBool() )
+ return NULL;
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ m_pMeshCacheSection->LockMutex();
+ if ( ( pStudioData->m_nFlags & (STUDIODATA_FLAGS_STUDIOMESH_LOADED | STUDIODATA_FLAGS_NO_STUDIOMESH) ) == 0 )
+ {
+ m_pMeshCacheSection->UnlockMutex();
+ if ( !LoadHardwareData( handle ) )
+ {
+ return NULL;
+ }
+ }
+ else
+ {
+#if defined( USE_HARDWARE_CACHE )
+ CheckData( pStudioData->m_HardwareDataCache, MDLCACHE_STUDIOHWDATA );
+#endif
+ m_pMeshCacheSection->UnlockMutex();
+ }
+
+ // didn't load, don't return an empty pointer
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH )
+ return NULL;
+
+ return &pStudioData->m_HardwareData;
+}
+
+
+//-----------------------------------------------------------------------------
+// Task switch
+//-----------------------------------------------------------------------------
+void CMDLCache::ReleaseMaterialSystemObjects()
+{
+ Assert( !m_bLostVideoMemory );
+ m_bLostVideoMemory = true;
+
+ BreakFrameLock( false );
+
+ // Free all hardware data
+ MDLHandle_t i = m_MDLDict.First();
+ while ( i != m_MDLDict.InvalidIndex() )
+ {
+ UnloadHardwareData( i );
+ i = m_MDLDict.Next( i );
+ }
+
+ RestoreFrameLock();
+}
+
+void CMDLCache::RestoreMaterialSystemObjects( int nChangeFlags )
+{
+ Assert( m_bLostVideoMemory );
+ m_bLostVideoMemory = false;
+
+ BreakFrameLock( false );
+
+ // Restore all hardware data
+ MDLHandle_t i = m_MDLDict.First();
+ while ( i != m_MDLDict.InvalidIndex() )
+ {
+ studiodata_t *pStudioData = m_MDLDict[i];
+
+ bool bIsMDLInMemory = GetCacheSection( MDLCACHE_STUDIOHDR )->IsPresent( pStudioData->m_MDLCache );
+
+ // If the vertex format changed, we have to free the data because we may be using different .vtx files.
+ if ( nChangeFlags & MATERIAL_RESTORE_VERTEX_FORMAT_CHANGED )
+ {
+ MdlCacheMsg( "MDLCache: Free studiohdr\n" );
+ MdlCacheMsg( "MDLCache: Free VVD\n" );
+ MdlCacheMsg( "MDLCache: Free VTX\n" );
+
+ // FIXME: Do we have to free m_MDLCache + m_VertexCache?
+ // Certainly we have to free m_IndexCache, cause that's a dx-level specific vtx file.
+ ClearAsync( i, MDLCACHE_STUDIOHWDATA, 0, true );
+
+ Flush( i, MDLCACHE_FLUSH_VERTEXES );
+ }
+
+ // Only restore the hardware data of those studiohdrs which are currently in memory
+ if ( bIsMDLInMemory )
+ {
+ GetHardwareData( i );
+ }
+
+ i = m_MDLDict.Next( i );
+ }
+
+ RestoreFrameLock();
+}
+
+
+void CMDLCache::MarkAsLoaded(MDLHandle_t handle)
+{
+ if ( mod_lock_mdls_on_load.GetBool() )
+ {
+ g_MDLCache.GetStudioHdr(handle);
+ if ( !( m_MDLDict[handle]->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL ) )
+ {
+ m_MDLDict[handle]->m_nFlags |= STUDIODATA_FLAGS_LOCKED_MDL;
+ GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Callback for UpdateOrCreate utility function - swaps any studiomdl file type.
+//-----------------------------------------------------------------------------
+static bool MdlcacheCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pHdr )
+{
+ // Missing studio files are permissible and not spewed as errors
+ bool retval = false;
+ CUtlBuffer sourceBuf;
+ bool bOk = g_pFullFileSystem->ReadFile( pSourceName, NULL, sourceBuf );
+ if ( bOk )
+ {
+ CUtlBuffer targetBuf;
+ targetBuf.EnsureCapacity( sourceBuf.TellPut() + BYTESWAP_ALIGNMENT_PADDING );
+
+ int bytes = StudioByteSwap::ByteswapStudioFile( pTargetName, targetBuf.Base(), sourceBuf.Base(), sourceBuf.TellPut(), (studiohdr_t*)pHdr );
+ if ( bytes )
+ {
+ // If the file was an .mdl, attempt to swap the .ani as well
+ if ( Q_stristr( pSourceName, ".mdl" ) )
+ {
+ char szANISourceName[ MAX_PATH ];
+ Q_StripExtension( pSourceName, szANISourceName, sizeof( szANISourceName ) );
+ Q_strncat( szANISourceName, ".ani", sizeof( szANISourceName ), COPY_ALL_CHARACTERS );
+ UpdateOrCreate( szANISourceName, NULL, 0, pPathID, MdlcacheCreateCallback, true, targetBuf.Base() );
+ }
+
+ targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, bytes );
+ g_pFullFileSystem->WriteFile( pTargetName, pPathID, targetBuf );
+ retval = true;
+ }
+ else
+ {
+ Warning( "Failed to create %s\n", pTargetName );
+ }
+ }
+ return retval;
+}
+
+//-----------------------------------------------------------------------------
+// Calls utility function to create .360 version of a file.
+//-----------------------------------------------------------------------------
+int CMDLCache::UpdateOrCreate( studiohdr_t *pHdr, const char *pSourceName, char *pTargetName, int targetLen, const char *pPathID, bool bForce )
+{
+ return ::UpdateOrCreate( pSourceName, pTargetName, targetLen, pPathID, MdlcacheCreateCallback, bForce, pHdr );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to read a file native to the current platform
+//-----------------------------------------------------------------------------
+bool CMDLCache::ReadFileNative( char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes )
+{
+ bool bOk = false;
+
+ if ( IsX360() )
+ {
+ // Read the 360 version
+ char pX360Filename[ MAX_PATH ];
+ UpdateOrCreate( NULL, pFileName, pX360Filename, sizeof( pX360Filename ), pPath );
+ bOk = g_pFullFileSystem->ReadFile( pX360Filename, pPath, buf, nMaxBytes );
+ }
+ else
+ {
+ // Read the PC version
+ bOk = g_pFullFileSystem->ReadFile( pFileName, pPath, buf, nMaxBytes );
+ }
+
+ return bOk;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+studiohdr_t *CMDLCache::UnserializeMDL( MDLHandle_t handle, void *pData, int nDataSize, bool bDataValid )
+{
+ if ( !bDataValid || nDataSize <= 0 || pData == NULL)
+ {
+ return NULL;
+ }
+
+ CTempAllocHelper pOriginalData;
+ if ( IsX360() )
+ {
+ if ( CLZMA::IsCompressed( (unsigned char *)pData ) )
+ {
+ // mdl arrives compressed, decode and cache the results
+ unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData );
+ pOriginalData.Alloc( nOriginalSize );
+ unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData.Get() );
+ if ( nOutputSize != nOriginalSize )
+ {
+ // decoder failure
+ return NULL;
+ }
+
+ pData = pOriginalData.Get();
+ nDataSize = nOriginalSize;
+ }
+ }
+
+ studiohdr_t *pStudioHdrIn = (studiohdr_t *)pData;
+
+ if ( r_rootlod.GetInt() > 0 )
+ {
+ // raw data is already setup for lod 0, override otherwise
+ Studio_SetRootLOD( pStudioHdrIn, r_rootlod.GetInt() );
+ }
+
+ // critical! store a back link to our data
+ // this is fetched when re-establishing dependent cached data (vtx/vvd)
+ pStudioHdrIn->virtualModel = (void *)(uintp)handle;
+
+ MdlCacheMsg( "MDLCache: Alloc studiohdr %s\n", GetModelName( handle ) );
+
+ // allocate cache space
+ MemAlloc_PushAllocDbgInfo( "Models:StudioHdr", 0);
+ studiohdr_t *pHdr = (studiohdr_t *)AllocData( MDLCACHE_STUDIOHDR, pStudioHdrIn->length );
+ MemAlloc_PopAllocDbgInfo();
+ if ( !pHdr )
+ return NULL;
+
+ CacheData( &m_MDLDict[handle]->m_MDLCache, pHdr, pStudioHdrIn->length, GetModelName( handle ), MDLCACHE_STUDIOHDR, MakeCacheID( handle, MDLCACHE_STUDIOHDR) );
+
+ if ( mod_lock_mdls_on_load.GetBool() )
+ {
+ GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache );
+ m_MDLDict[handle]->m_nFlags |= STUDIODATA_FLAGS_LOCKED_MDL;
+ }
+
+ // FIXME: Is there any way we can compute the size to load *before* loading in
+ // and read directly into cache memory? It would be nice to reduce cache overhead here.
+ // move the complete, relocatable model to the cache
+ memcpy( pHdr, pStudioHdrIn, pStudioHdrIn->length );
+
+ // On first load, convert the flex deltas from fp16 to 16-bit fixed-point
+ if ( (pHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED) == 0 )
+ {
+ ConvertFlexData( pHdr );
+
+ // Mark as converted so it only happens once
+ pHdr->flags |= STUDIOHDR_FLAGS_FLEXES_CONVERTED;
+ }
+
+ if ( m_pCacheNotify )
+ {
+ m_pCacheNotify->OnDataLoaded( MDLCACHE_STUDIOHDR, handle );
+ }
+
+ return pHdr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Attempts to load a MDL file, validates that it's ok.
+//-----------------------------------------------------------------------------
+bool CMDLCache::ReadMDLFile( MDLHandle_t handle, const char *pMDLFileName, CUtlBuffer &buf )
+{
+ VPROF( "CMDLCache::ReadMDLFile" );
+
+ char pFileName[ MAX_PATH ];
+ Q_strncpy( pFileName, pMDLFileName, sizeof( pFileName ) );
+ Q_FixSlashes( pFileName );
+#ifdef _LINUX
+ Q_strlower( pFileName );
+#endif
+
+ MdlCacheMsg( "MDLCache: Load studiohdr %s\n", pFileName );
+
+ MEM_ALLOC_CREDIT();
+
+ bool bOk = ReadFileNative( pFileName, "GAME", buf );
+ if ( !bOk )
+ {
+ DevWarning( "Failed to load %s!\n", pMDLFileName );
+ return false;
+ }
+
+ if ( IsX360() )
+ {
+ if ( CLZMA::IsCompressed( (unsigned char *)buf.PeekGet() ) )
+ {
+ // mdl arrives compressed, decode and cache the results
+ unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)buf.PeekGet() );
+ void *pOriginalData = malloc( nOriginalSize );
+ unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)buf.PeekGet(), (unsigned char *)pOriginalData );
+ if ( nOutputSize != nOriginalSize )
+ {
+ // decoder failure
+ free( pOriginalData );
+ return false;
+ }
+
+ // replace caller's buffer
+ buf.Purge();
+ buf.Put( pOriginalData, nOriginalSize );
+ free( pOriginalData );
+ }
+ }
+
+ studiohdr_t *pStudioHdr = (studiohdr_t*)buf.PeekGet();
+ if ( !pStudioHdr )
+ {
+ DevWarning( "Failed to read model %s from buffer!\n", pMDLFileName );
+ return false;
+ }
+ if ( pStudioHdr->id != IDSTUDIOHEADER )
+ {
+ DevWarning( "Model %s not a .MDL format file!\n", pMDLFileName );
+ return false;
+ }
+
+ // critical! store a back link to our data
+ // this is fetched when re-establishing dependent cached data (vtx/vvd)
+ pStudioHdr->virtualModel = (void*)(uintp)handle;
+
+ // Make sure all dependent files are valid
+ if ( !VerifyHeaders( pStudioHdr ) )
+ {
+ DevWarning( "Model %s has mismatched .vvd + .vtx files!\n", pMDLFileName );
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+studiohdr_t *CMDLCache::LockStudioHdr( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ {
+ return NULL;
+ }
+
+ CMDLCacheCriticalSection cacheCriticalSection( this );
+ studiohdr_t *pStdioHdr = GetStudioHdr( handle );
+ // @TODO (toml 9/12/2006) need this?: AddRef( handle );
+ if ( !pStdioHdr )
+ {
+ return NULL;
+ }
+
+ GetCacheSection( MDLCACHE_STUDIOHDR )->Lock( m_MDLDict[handle]->m_MDLCache );
+ return pStdioHdr;
+}
+
+void CMDLCache::UnlockStudioHdr( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ {
+ return;
+ }
+
+ CMDLCacheCriticalSection cacheCriticalSection( this );
+ studiohdr_t *pStdioHdr = GetStudioHdr( handle );
+ if ( pStdioHdr )
+ {
+ GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( m_MDLDict[handle]->m_MDLCache );
+ }
+ // @TODO (toml 9/12/2006) need this?: Release( handle );
+}
+
+//-----------------------------------------------------------------------------
+// Loading the data in
+//-----------------------------------------------------------------------------
+studiohdr_t *CMDLCache::GetStudioHdr( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return NULL;
+
+ // Returning a pointer to data inside the cache when it's unlocked is just a bad idea.
+ // It's technically legal, but the pointer can get invalidated if anything else looks at the cache.
+ // Don't do that.
+ // Assert( m_pModelCacheSection->IsFrameLocking() );
+ // Assert( m_pMeshCacheSection->IsFrameLocking() );
+
+#if _DEBUG
+ VPROF_INCREMENT_COUNTER( "GetStudioHdr", 1 );
+#endif
+ studiohdr_t *pHdr = (studiohdr_t*)CheckData( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR );
+ if ( !pHdr )
+ {
+ m_MDLDict[handle]->m_MDLCache = NULL;
+
+ CMDLCacheCriticalSection cacheCriticalSection( this );
+
+ // load the file
+ const char *pModelName = GetActualModelName( handle );
+ if ( developer.GetInt() > 1 )
+ {
+ DevMsg( "Loading %s\n", pModelName );
+ }
+
+ // Load file to temporary space
+ CUtlBuffer buf;
+ if ( !ReadMDLFile( handle, pModelName, buf ) )
+ {
+ bool bOk = false;
+ if ( ( m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL ) == 0 )
+ {
+ buf.Clear(); // clear buffer for next file read
+
+ m_MDLDict[handle]->m_nFlags |= STUDIODATA_ERROR_MODEL;
+ bOk = ReadMDLFile( handle, ERROR_MODEL, buf );
+ }
+
+ if ( !bOk )
+ {
+ if (IsOSX())
+ {
+ // rbarris wants this to go somewhere like the console.log prior to crashing, which is what the Error call will do next
+ printf("\n ##### Model %s not found and %s couldn't be loaded", pModelName, ERROR_MODEL );
+ fflush( stdout );
+ }
+ Error( "Model %s not found and %s couldn't be loaded", pModelName, ERROR_MODEL );
+ return NULL;
+ }
+ }
+
+ // put it in the cache
+ if ( ProcessDataIntoCache( handle, MDLCACHE_STUDIOHDR, 0, buf.Base(), buf.TellMaxPut(), true ) )
+ {
+ pHdr = (studiohdr_t*)CheckData( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR );
+ }
+ }
+
+ return pHdr;
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets/sets user data associated with the MDL
+//-----------------------------------------------------------------------------
+void CMDLCache::SetUserData( MDLHandle_t handle, void* pData )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return;
+
+ m_MDLDict[handle]->m_pUserData = pData;
+}
+
+void *CMDLCache::GetUserData( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return NULL;
+ return m_MDLDict[handle]->m_pUserData;
+}
+
+
+//-----------------------------------------------------------------------------
+// Polls information about a particular mdl
+//-----------------------------------------------------------------------------
+bool CMDLCache::IsErrorModel( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return false;
+
+ return (m_MDLDict[handle]->m_nFlags & STUDIODATA_ERROR_MODEL) != 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Brings all data associated with an MDL into memory
+//-----------------------------------------------------------------------------
+void CMDLCache::TouchAllData( MDLHandle_t handle )
+{
+ studiohdr_t *pStudioHdr = GetStudioHdr( handle );
+ virtualmodel_t *pVModel = GetVirtualModel( handle );
+ if ( pVModel )
+ {
+ // skip self, start at children
+ // ensure all sub models are cached
+ for ( int i=1; i<pVModel->m_group.Count(); ++i )
+ {
+ MDLHandle_t childHandle = (MDLHandle_t)(int)pVModel->m_group[i].cache&0xffff;
+ if ( childHandle != MDLHANDLE_INVALID )
+ {
+ // FIXME: Should this be calling TouchAllData on the child?
+ GetStudioHdr( childHandle );
+ }
+ }
+ }
+
+ if ( !IsX360() )
+ {
+ // cache the anims
+ // Note that the animblocks start at 1!!!
+ for ( int i=1; i< (int)pStudioHdr->numanimblocks; ++i )
+ {
+ pStudioHdr->GetAnimBlock( i );
+ }
+ }
+
+ // cache the vertexes
+ if ( pStudioHdr->numbodyparts )
+ {
+ CacheVertexData( pStudioHdr );
+ GetHardwareData( handle );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Flushes all data
+//-----------------------------------------------------------------------------
+void CMDLCache::Flush( MDLCacheFlush_t nFlushFlags )
+{
+ // Free all MDLs that haven't been cleaned up
+ MDLHandle_t i = m_MDLDict.First();
+ while ( i != m_MDLDict.InvalidIndex() )
+ {
+ Flush( i, nFlushFlags );
+ i = m_MDLDict.Next( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Cache handlers
+//-----------------------------------------------------------------------------
+static const char *g_ppszTypes[] =
+{
+ "studiohdr", // MDLCACHE_STUDIOHDR
+ "studiohwdata", // MDLCACHE_STUDIOHWDATA
+ "vcollide", // MDLCACHE_VCOLLIDE
+ "animblock", // MDLCACHE_ANIMBLOCK
+ "virtualmodel", // MDLCACHE_VIRTUALMODEL
+ "vertexes", // MDLCACHE_VERTEXES
+};
+
+bool CMDLCache::HandleCacheNotification( const DataCacheNotification_t &notification )
+{
+ switch ( notification.type )
+ {
+ case DC_AGE_DISCARD:
+ case DC_FLUSH_DISCARD:
+ case DC_REMOVED:
+ {
+ MdlCacheMsg( "MDLCache: Data cache discard %s %s\n", g_ppszTypes[TypeFromCacheID( notification.clientId )], GetModelName( HandleFromCacheID( notification.clientId ) ) );
+
+ if ( (DataCacheClientID_t)notification.pItemData == notification.clientId ||
+ TypeFromCacheID(notification.clientId) != MDLCACHE_STUDIOHWDATA )
+ {
+ Assert( notification.pItemData );
+ FreeData( TypeFromCacheID(notification.clientId), (void *)notification.pItemData );
+ }
+ else
+ {
+ UnloadHardwareData( HandleFromCacheID( notification.clientId ), false );
+ }
+ return true;
+ }
+ }
+
+ return CDefaultDataCacheClient::HandleCacheNotification( notification );
+}
+
+bool CMDLCache::GetItemName( DataCacheClientID_t clientId, const void *pItem, char *pDest, unsigned nMaxLen )
+{
+ if ( (DataCacheClientID_t)pItem == clientId )
+ {
+ return false;
+ }
+
+ MDLHandle_t handle = HandleFromCacheID( clientId );
+ MDLCacheDataType_t type = TypeFromCacheID( clientId );
+
+ Q_snprintf( pDest, nMaxLen, "%s - %s", g_ppszTypes[type], GetModelName( handle ) );
+
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Flushes all data
+//-----------------------------------------------------------------------------
+void CMDLCache::BeginLock()
+{
+ m_pModelCacheSection->BeginFrameLocking();
+ m_pMeshCacheSection->BeginFrameLocking();
+}
+
+//-----------------------------------------------------------------------------
+// Flushes all data
+//-----------------------------------------------------------------------------
+void CMDLCache::EndLock()
+{
+ m_pModelCacheSection->EndFrameLocking();
+ m_pMeshCacheSection->EndFrameLocking();
+}
+
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CMDLCache::BreakFrameLock( bool bModels, bool bMesh )
+{
+ if ( bModels )
+ {
+ if ( m_pModelCacheSection->IsFrameLocking() )
+ {
+ Assert( !m_nModelCacheFrameLocks );
+ m_nModelCacheFrameLocks = 0;
+ do
+ {
+ m_nModelCacheFrameLocks++;
+ } while ( m_pModelCacheSection->EndFrameLocking() );
+ }
+
+ }
+
+ if ( bMesh )
+ {
+ if ( m_pMeshCacheSection->IsFrameLocking() )
+ {
+ Assert( !m_nMeshCacheFrameLocks );
+ m_nMeshCacheFrameLocks = 0;
+ do
+ {
+ m_nMeshCacheFrameLocks++;
+ } while ( m_pMeshCacheSection->EndFrameLocking() );
+ }
+ }
+
+}
+
+void CMDLCache::RestoreFrameLock()
+{
+ while ( m_nModelCacheFrameLocks )
+ {
+ m_pModelCacheSection->BeginFrameLocking();
+ m_nModelCacheFrameLocks--;
+ }
+ while ( m_nMeshCacheFrameLocks )
+ {
+ m_pMeshCacheSection->BeginFrameLocking();
+ m_nMeshCacheFrameLocks--;
+ }
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+int *CMDLCache::GetFrameUnlockCounterPtrOLD()
+{
+ return GetCacheSection( MDLCACHE_STUDIOHDR )->GetFrameUnlockCounterPtr();
+}
+
+int *CMDLCache::GetFrameUnlockCounterPtr( MDLCacheDataType_t type )
+{
+ return GetCacheSection( type )->GetFrameUnlockCounterPtr();
+}
+
+//-----------------------------------------------------------------------------
+// Completes all pending async operations
+//-----------------------------------------------------------------------------
+void CMDLCache::FinishPendingLoads()
+{
+ if ( !ThreadInMainThread() )
+ {
+ return;
+ }
+
+ AUTO_LOCK( m_AsyncMutex );
+
+ // finish just our known jobs
+ int iAsync = m_PendingAsyncs.Head();
+ while ( iAsync != m_PendingAsyncs.InvalidIndex() )
+ {
+ AsyncInfo_t &info = m_PendingAsyncs[iAsync];
+ if ( info.hControl )
+ {
+ g_pFullFileSystem->AsyncFinish( info.hControl, true );
+ }
+ iAsync = m_PendingAsyncs.Next( iAsync );
+ }
+
+ ProcessPendingAsyncs();
+}
+
+//-----------------------------------------------------------------------------
+// Notify map load has started
+//-----------------------------------------------------------------------------
+void CMDLCache::BeginMapLoad()
+{
+ BreakFrameLock();
+
+ studiodata_t *pStudioData;
+
+ // Unlock prior map MDLs prior to load
+ MDLHandle_t i = m_MDLDict.First();
+ while ( i != m_MDLDict.InvalidIndex() )
+ {
+ pStudioData = m_MDLDict[i];
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL )
+ {
+ GetCacheSection( MDLCACHE_STUDIOHDR )->Unlock( pStudioData->m_MDLCache );
+ pStudioData->m_nFlags &= ~STUDIODATA_FLAGS_LOCKED_MDL;
+ }
+ i = m_MDLDict.Next( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Notify map load is complete
+//-----------------------------------------------------------------------------
+void CMDLCache::EndMapLoad()
+{
+ FinishPendingLoads();
+
+ // Remove all stray MDLs not referenced during load
+ if ( mod_lock_mdls_on_load.GetBool() )
+ {
+ studiodata_t *pStudioData;
+ MDLHandle_t i = m_MDLDict.First();
+ while ( i != m_MDLDict.InvalidIndex() )
+ {
+ pStudioData = m_MDLDict[i];
+ if ( !(pStudioData->m_nFlags & STUDIODATA_FLAGS_LOCKED_MDL) )
+ {
+ Flush( i, MDLCACHE_FLUSH_STUDIOHDR );
+ }
+ i = m_MDLDict.Next( i );
+ }
+ }
+
+ RestoreFrameLock();
+}
+
+
+//-----------------------------------------------------------------------------
+// Is a particular part of the model data loaded?
+//-----------------------------------------------------------------------------
+bool CMDLCache::IsDataLoaded( MDLHandle_t handle, MDLCacheDataType_t type )
+{
+ if ( handle == MDLHANDLE_INVALID || !m_MDLDict.IsValidIndex( handle ) )
+ return false;
+
+ studiodata_t *pData = m_MDLDict[ handle ];
+ switch( type )
+ {
+ case MDLCACHE_STUDIOHDR:
+ return GetCacheSection( MDLCACHE_STUDIOHDR )->IsPresent( pData->m_MDLCache );
+
+ case MDLCACHE_STUDIOHWDATA:
+ return ( pData->m_nFlags & STUDIODATA_FLAGS_STUDIOMESH_LOADED ) != 0;
+
+ case MDLCACHE_VCOLLIDE:
+ return ( pData->m_nFlags & STUDIODATA_FLAGS_VCOLLISION_LOADED ) != 0;
+
+ case MDLCACHE_ANIMBLOCK:
+ {
+ if ( !pData->m_pAnimBlock )
+ return false;
+
+ for (int i = 0; i < pData->m_nAnimBlockCount; ++i )
+ {
+ if ( !pData->m_pAnimBlock[i] )
+ return false;
+
+ if ( !GetCacheSection( type )->IsPresent( pData->m_pAnimBlock[i] ) )
+ return false;
+ }
+ return true;
+ }
+
+ case MDLCACHE_VIRTUALMODEL:
+ return ( pData->m_pVirtualModel != 0 );
+
+ case MDLCACHE_VERTEXES:
+ return m_pMeshCacheSection->IsPresent( pData->m_VertexCache );
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the correct extension for our dx
+//-----------------------------------------------------------------------------
+const char *CMDLCache::GetVTXExtension()
+{
+ if ( IsPC() )
+ {
+ if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 )
+ {
+ return ".dx90.vtx";
+ }
+ else if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80 )
+ {
+ return ".dx80.vtx";
+ }
+ else
+ {
+ return ".sw.vtx";
+ }
+ }
+
+ return ".dx90.vtx";
+}
+
+//-----------------------------------------------------------------------------
+// Minimal presence and header validation, no data loads
+// Return true if successful, false otherwise.
+//-----------------------------------------------------------------------------
+bool CMDLCache::VerifyHeaders( studiohdr_t *pStudioHdr )
+{
+ VPROF( "CMDLCache::VerifyHeaders" );
+
+ if ( developer.GetInt() < 2 )
+ {
+ return true;
+ }
+
+ // model has no vertex data
+ if ( !pStudioHdr->numbodyparts )
+ {
+ // valid
+ return true;
+ }
+
+ char pFileName[ MAX_PATH ];
+ MDLHandle_t handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff;
+
+ MakeFilename( handle, ".vvd", pFileName, sizeof(pFileName) );
+
+ MdlCacheMsg("MDLCache: Load VVD (verify) %s\n", pFileName );
+
+ // vvd header only
+ CUtlBuffer vvdHeader( 0, sizeof(vertexFileHeader_t) );
+ if ( !ReadFileNative( pFileName, "GAME", vvdHeader, sizeof(vertexFileHeader_t) ) )
+ {
+ return false;
+ }
+
+ vertexFileHeader_t *pVertexHdr = (vertexFileHeader_t*)vvdHeader.PeekGet();
+
+ // check
+ if (( pVertexHdr->id != MODEL_VERTEX_FILE_ID ) ||
+ ( pVertexHdr->version != MODEL_VERTEX_FILE_VERSION ) ||
+ ( pVertexHdr->checksum != pStudioHdr->checksum ))
+ {
+ return false;
+ }
+
+ // load the VTX file
+ // use model name for correct path
+ MakeFilename( handle, GetVTXExtension(), pFileName, sizeof(pFileName) );
+
+ MdlCacheMsg("MDLCache: Load VTX (verify) %s\n", pFileName );
+
+ // vtx header only
+ CUtlBuffer vtxHeader( 0, sizeof(OptimizedModel::FileHeader_t) );
+ if ( !ReadFileNative( pFileName, "GAME", vtxHeader, sizeof(OptimizedModel::FileHeader_t) ) )
+ {
+ return false;
+ }
+
+ // check
+ OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t*)vtxHeader.PeekGet();
+ if (( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) ||
+ ( pVtxHdr->checkSum != pStudioHdr->checksum ))
+ {
+ return false;
+ }
+
+ // valid
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Cache model's specified dynamic data
+//-----------------------------------------------------------------------------
+vertexFileHeader_t *CMDLCache::CacheVertexData( studiohdr_t *pStudioHdr )
+{
+ VPROF( "CMDLCache::CacheVertexData" );
+
+ vertexFileHeader_t *pVvdHdr;
+ MDLHandle_t handle;
+
+ Assert( pStudioHdr );
+
+ handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff;
+ Assert( handle != MDLHANDLE_INVALID );
+
+ pVvdHdr = (vertexFileHeader_t *)CheckData( m_MDLDict[handle]->m_VertexCache, MDLCACHE_VERTEXES );
+ if ( pVvdHdr )
+ {
+ return pVvdHdr;
+ }
+
+ m_MDLDict[handle]->m_VertexCache = NULL;
+
+ return LoadVertexData( pStudioHdr );
+}
+
+//-----------------------------------------------------------------------------
+// Start an async transfer
+//-----------------------------------------------------------------------------
+FSAsyncStatus_t CMDLCache::LoadData( const char *pszFilename, const char *pszPathID, void *pDest, int nBytes, int nOffset, bool bAsync, FSAsyncControl_t *pControl )
+{
+ if ( !*pControl )
+ {
+ if ( IsX360() && g_pQueuedLoader->IsMapLoading() )
+ {
+ DevWarning( "CMDLCache: Non-Optimal loading path for %s\n", pszFilename );
+ }
+
+ FileAsyncRequest_t asyncRequest;
+ asyncRequest.pszFilename = pszFilename;
+ asyncRequest.pszPathID = pszPathID;
+ asyncRequest.pData = pDest;
+ asyncRequest.nBytes = nBytes;
+ asyncRequest.nOffset = nOffset;
+
+ if ( !pDest )
+ {
+ asyncRequest.flags = FSASYNC_FLAGS_ALLOCNOFREE;
+ }
+
+ if ( !bAsync )
+ {
+ asyncRequest.flags |= FSASYNC_FLAGS_SYNC;
+ }
+
+ MEM_ALLOC_CREDIT();
+ return g_pFullFileSystem->AsyncRead( asyncRequest, pControl );
+ }
+
+ return FSASYNC_ERR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// Determine the maximum number of 'real' bone influences used by any vertex in a model
+// (100% binding to bone zero doesn't count)
+//-----------------------------------------------------------------------------
+int ComputeMaxRealBoneInfluences( vertexFileHeader_t * vertexFile, int lod )
+{
+ const mstudiovertex_t * verts = vertexFile->GetVertexData();
+ int numVerts = vertexFile->numLODVertexes[ lod ];
+ Assert(verts);
+
+ int maxWeights = 0;
+ for (int i = 0;i < numVerts;i++)
+ {
+ if ( verts[i].m_BoneWeights.numbones > 0 )
+ {
+ int numWeights = 0;
+ for (int j = 0;j < MAX_NUM_BONES_PER_VERT;j++)
+ {
+ if ( verts[i].m_BoneWeights.weight[j] > 0 )
+ numWeights = j + 1;
+ }
+ if ( ( numWeights == 1 ) && ( verts[i].m_BoneWeights.bone[0] == 0 ) )
+ {
+ // 100% binding to first bone - not really skinned (the first bone is just the model transform)
+ numWeights = 0;
+ }
+ maxWeights = max( numWeights, maxWeights );
+ }
+ }
+ return maxWeights;
+}
+
+//-----------------------------------------------------------------------------
+// Generate thin vertices (containing just the data needed to do model decals)
+//-----------------------------------------------------------------------------
+vertexFileHeader_t * CMDLCache::CreateThinVertexes( vertexFileHeader_t * originalData, const studiohdr_t * pStudioHdr, int * cacheLength )
+{
+ int rootLod = min( (int)pStudioHdr->rootLOD, ( originalData->numLODs - 1 ) );
+ int numVerts = originalData->numLODVertexes[ rootLod ] + 1; // Add 1 vert to support prefetch during array access
+
+ int numBoneInfluences = ComputeMaxRealBoneInfluences( originalData, rootLod );
+ // Only store (N-1) weights (all N weights sum to 1, so we can re-compute the Nth weight later)
+ int numStoredWeights = max( 0, ( numBoneInfluences - 1 ) );
+
+ int vertexSize = 2*sizeof( Vector ) + numBoneInfluences*sizeof( unsigned char ) + numStoredWeights*sizeof( float );
+ *cacheLength = sizeof( vertexFileHeader_t ) + sizeof( thinModelVertices_t ) + numVerts*vertexSize;
+
+ // Allocate cache space for the thin data
+ MemAlloc_PushAllocDbgInfo( "Models:Vertex data", 0);
+ vertexFileHeader_t * pNewVvdHdr = (vertexFileHeader_t *)AllocData( MDLCACHE_VERTEXES, *cacheLength );
+ MemAlloc_PopAllocDbgInfo();
+
+ Assert( pNewVvdHdr );
+ if ( pNewVvdHdr )
+ {
+ // Copy the header and set it up to hold thin vertex data
+ memcpy( (void *)pNewVvdHdr, (void *)originalData, sizeof( vertexFileHeader_t ) );
+ pNewVvdHdr->id = MODEL_VERTEX_FILE_THIN_ID;
+ pNewVvdHdr->numFixups = 0;
+ pNewVvdHdr->fixupTableStart = 0;
+ pNewVvdHdr->tangentDataStart = 0;
+ pNewVvdHdr->vertexDataStart = sizeof( vertexFileHeader_t );
+
+ // Set up the thin vertex structure
+ thinModelVertices_t * pNewThinVerts = (thinModelVertices_t *)( pNewVvdHdr + 1 );
+ Vector * pPositions = (Vector *)( pNewThinVerts + 1 );
+ float * pBoneWeights = (float *)( pPositions + numVerts );
+ // Alloc the (short) normals here to avoid mis-aligning the float data
+ unsigned short * pNormals = (unsigned short *)( pBoneWeights + numVerts*numStoredWeights );
+ // Alloc the (char) indices here to avoid mis-aligning the float/short data
+ char * pBoneIndices = (char *)( pNormals + numVerts );
+ if ( numStoredWeights == 0 )
+ pBoneWeights = NULL;
+ if ( numBoneInfluences == 0 )
+ pBoneIndices = NULL;
+ pNewThinVerts->Init( numBoneInfluences, pPositions, pNormals, pBoneWeights, pBoneIndices );
+
+ // Copy over the original data
+ const mstudiovertex_t * srcVertexData = originalData->GetVertexData();
+ for ( int i = 0; i < numVerts; i++ )
+ {
+ pNewThinVerts->SetPosition( i, srcVertexData[ i ].m_vecPosition );
+ pNewThinVerts->SetNormal( i, srcVertexData[ i ].m_vecNormal );
+ if ( numBoneInfluences > 0 )
+ {
+ mstudioboneweight_t boneWeights;
+ boneWeights.numbones = numBoneInfluences;
+ for ( int j = 0; j < numStoredWeights; j++ )
+ {
+ boneWeights.weight[ j ] = srcVertexData[ i ].m_BoneWeights.weight[ j ];
+ }
+ for ( int j = 0; j < numBoneInfluences; j++ )
+ {
+ boneWeights.bone[ j ] = srcVertexData[ i ].m_BoneWeights.bone[ j ];
+ }
+ pNewThinVerts->SetBoneWeights( i, boneWeights );
+ }
+ }
+ }
+
+ return pNewVvdHdr;
+}
+
+//-----------------------------------------------------------------------------
+// Process the provided raw data into the cache. Distributes to low level
+// unserialization or build methods.
+//-----------------------------------------------------------------------------
+bool CMDLCache::ProcessDataIntoCache( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, void *pData, int nDataSize, bool bDataValid )
+{
+ studiohdr_t *pStudioHdrCurrent = NULL;
+ if ( type != MDLCACHE_STUDIOHDR )
+ {
+ // can only get the studiohdr once the header has been processed successfully into the cache
+ // causes a ProcessDataIntoCache() with the studiohdr data
+ pStudioHdrCurrent = GetStudioHdr( handle );
+ if ( !pStudioHdrCurrent )
+ {
+ return false;
+ }
+ }
+
+ studiodata_t *pStudioDataCurrent = m_MDLDict[handle];
+
+ if ( !pStudioDataCurrent )
+ {
+ return false;
+ }
+
+ switch ( type )
+ {
+ case MDLCACHE_STUDIOHDR:
+ {
+ pStudioHdrCurrent = UnserializeMDL( handle, pData, nDataSize, bDataValid );
+ if ( !pStudioHdrCurrent )
+ {
+ return false;
+ }
+
+ if (!Studio_ConvertStudioHdrToNewVersion( pStudioHdrCurrent ))
+ {
+ Warning( "MDLCache: %s needs to be recompiled\n", pStudioHdrCurrent->pszName() );
+ }
+
+ if ( pStudioHdrCurrent->numincludemodels == 0 )
+ {
+ // perf optimization, calculate once and cache off the autoplay sequences
+ int nCount = pStudioHdrCurrent->CountAutoplaySequences();
+ if ( nCount )
+ {
+ AllocateAutoplaySequences( m_MDLDict[handle], nCount );
+ pStudioHdrCurrent->CopyAutoplaySequences( m_MDLDict[handle]->m_pAutoplaySequenceList, nCount );
+ }
+ }
+
+ // Load animations
+ UnserializeAllVirtualModelsAndAnimBlocks( handle );
+ break;
+ }
+
+ case MDLCACHE_VERTEXES:
+ {
+ if ( bDataValid )
+ {
+ BuildAndCacheVertexData( pStudioHdrCurrent, (vertexFileHeader_t *)pData );
+ }
+ else
+ {
+ pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_NO_VERTEX_DATA;
+ if ( pStudioHdrCurrent->numbodyparts )
+ {
+ // expected data not valid
+ Warning( "MDLCache: Failed load of .VVD data for %s\n", pStudioHdrCurrent->pszName() );
+ return false;
+ }
+ }
+ break;
+ }
+
+ case MDLCACHE_STUDIOHWDATA:
+ {
+ if ( bDataValid )
+ {
+ BuildHardwareData( handle, pStudioDataCurrent, pStudioHdrCurrent, (OptimizedModel::FileHeader_t *)pData );
+ }
+ else
+ {
+ pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_NO_STUDIOMESH;
+ if ( pStudioHdrCurrent->numbodyparts )
+ {
+ // expected data not valid
+ Warning( "MDLCache: Failed load of .VTX data for %s\n", pStudioHdrCurrent->pszName() );
+ return false;
+ }
+ }
+
+ m_pMeshCacheSection->Unlock( pStudioDataCurrent->m_VertexCache );
+ m_pMeshCacheSection->Age( pStudioDataCurrent->m_VertexCache );
+
+ // FIXME: thin VVD data on PC too (have to address alt-tab, various DX8/DX7/debug software paths in studiorender, tools, etc)
+ static bool bCompressedVVDs = CommandLine()->CheckParm( "-no_compressed_vvds" ) == NULL;
+ if ( IsX360() && !( pStudioDataCurrent->m_nFlags & STUDIODATA_FLAGS_NO_STUDIOMESH ) && bCompressedVVDs )
+ {
+ // Replace the cached vertex data with a thin version (used for model decals).
+ // Flexed meshes require the fat data to remain, for CPU mesh anim.
+ if ( pStudioHdrCurrent->numflexdesc == 0 )
+ {
+ vertexFileHeader_t *originalVertexData = GetVertexData( handle );
+ Assert( originalVertexData );
+ if ( originalVertexData )
+ {
+ int thinVertexDataSize = 0;
+ vertexFileHeader_t *thinVertexData = CreateThinVertexes( originalVertexData, pStudioHdrCurrent, &thinVertexDataSize );
+ Assert( thinVertexData && ( thinVertexDataSize > 0 ) );
+ if ( thinVertexData && ( thinVertexDataSize > 0 ) )
+ {
+ // Remove the original cache entry (and free it)
+ Flush( handle, MDLCACHE_FLUSH_VERTEXES | MDLCACHE_FLUSH_IGNORELOCK );
+ // Add the new one
+ CacheData( &pStudioDataCurrent->m_VertexCache, thinVertexData, thinVertexDataSize, pStudioHdrCurrent->pszName(), MDLCACHE_VERTEXES, MakeCacheID( handle, MDLCACHE_VERTEXES) );
+ }
+ }
+ }
+ }
+
+ break;
+ }
+
+ case MDLCACHE_ANIMBLOCK:
+ {
+ MEM_ALLOC_CREDIT_( __FILE__ ": Anim Blocks" );
+
+ if ( bDataValid )
+ {
+ MdlCacheMsg( "MDLCache: Finish load anim block %s (block %i)\n", pStudioHdrCurrent->pszName(), iAnimBlock );
+
+ char pCacheName[MAX_PATH];
+ Q_snprintf( pCacheName, MAX_PATH, "%s (block %i)", pStudioHdrCurrent->pszName(), iAnimBlock );
+
+ if ( IsX360() )
+ {
+ if ( CLZMA::IsCompressed( (unsigned char *)pData ) )
+ {
+ // anim block arrives compressed, decode and cache the results
+ unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData );
+
+ // get a "fake" (not really aligned) optimal read buffer, as expected by the free logic
+ void *pOriginalData = g_pFullFileSystem->AllocOptimalReadBuffer( FILESYSTEM_INVALID_HANDLE, nOriginalSize, 0 );
+ unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData );
+ if ( nOutputSize != nOriginalSize )
+ {
+ // decoder failure
+ g_pFullFileSystem->FreeOptimalReadBuffer( pOriginalData );
+ return false;
+ }
+
+ // input i/o buffer is now unused
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+
+ // datacache will now own the data
+ pData = pOriginalData;
+ nDataSize = nOriginalSize;
+ }
+ }
+
+ CacheData( &pStudioDataCurrent->m_pAnimBlock[iAnimBlock], pData, nDataSize, pCacheName, MDLCACHE_ANIMBLOCK, MakeCacheID( handle, MDLCACHE_ANIMBLOCK) );
+ }
+ else
+ {
+ MdlCacheMsg( "MDLCache: Failed load anim block %s (block %i)\n", pStudioHdrCurrent->pszName(), iAnimBlock );
+ if ( pStudioDataCurrent->m_pAnimBlock )
+ {
+ pStudioDataCurrent->m_pAnimBlock[iAnimBlock] = NULL;
+ }
+ return false;
+ }
+ break;
+ }
+
+ case MDLCACHE_VCOLLIDE:
+ {
+ // always marked as loaded, vcollides are not present for every model
+ pStudioDataCurrent->m_nFlags |= STUDIODATA_FLAGS_VCOLLISION_LOADED;
+
+ if ( bDataValid )
+ {
+ MdlCacheMsg( "MDLCache: Finish load vcollide for %s\n", pStudioHdrCurrent->pszName() );
+
+ CTempAllocHelper pOriginalData;
+ if ( IsX360() )
+ {
+ if ( CLZMA::IsCompressed( (unsigned char *)pData ) )
+ {
+ // phy arrives compressed, decode and cache the results
+ unsigned int nOriginalSize = CLZMA::GetActualSize( (unsigned char *)pData );
+ pOriginalData.Alloc( nOriginalSize );
+ unsigned int nOutputSize = CLZMA::Uncompress( (unsigned char *)pData, (unsigned char *)pOriginalData.Get() );
+ if ( nOutputSize != nOriginalSize )
+ {
+ // decoder failure
+ return NULL;
+ }
+
+ pData = pOriginalData.Get();
+ nDataSize = nOriginalSize;
+ }
+ }
+
+ CUtlBuffer buf( pData, nDataSize, CUtlBuffer::READ_ONLY );
+ buf.SeekPut( CUtlBuffer::SEEK_HEAD, nDataSize );
+
+ phyheader_t header;
+ buf.Get( &header, sizeof( phyheader_t ) );
+ if ( ( header.size == sizeof( header ) ) && header.solidCount > 0 )
+ {
+ int nBufSize = buf.TellMaxPut() - buf.TellGet();
+ vcollide_t *pCollide = &pStudioDataCurrent->m_VCollisionData;
+ g_pPhysicsCollision->VCollideLoad( pCollide, header.solidCount, (const char*)buf.PeekGet(), nBufSize );
+ if ( m_pCacheNotify )
+ {
+ m_pCacheNotify->OnDataLoaded( MDLCACHE_VCOLLIDE, handle );
+ }
+ }
+ }
+ else
+ {
+ MdlCacheWarning( "MDLCache: Failed load of .PHY data for %s\n", pStudioHdrCurrent->pszName() );
+ return false;
+ }
+ break;
+ }
+
+ default:
+ Assert( 0 );
+ }
+
+ // success
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Returns:
+// <0: indeterminate at this time
+// =0: pending
+// >0: completed
+//-----------------------------------------------------------------------------
+int CMDLCache::ProcessPendingAsync( int iAsync )
+{
+ if ( !ThreadInMainThread() )
+ {
+ return -1;
+ }
+
+ ASSERT_NO_REENTRY();
+
+ void *pData = NULL;
+ int nBytesRead = 0;
+
+ AsyncInfo_t *pInfo;
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ pInfo = &m_PendingAsyncs[iAsync];
+ }
+ Assert( pInfo->hControl );
+
+ FSAsyncStatus_t status = g_pFullFileSystem->AsyncGetResult( pInfo->hControl, &pData, &nBytesRead );
+ if ( status == FSASYNC_STATUS_PENDING )
+ {
+ return 0;
+ }
+
+ AsyncInfo_t info = *pInfo;
+ pInfo = &info;
+ ClearAsync( pInfo->hModel, pInfo->type, pInfo->iAnimBlock );
+
+ switch ( pInfo->type )
+ {
+ case MDLCACHE_VERTEXES:
+ case MDLCACHE_STUDIOHWDATA:
+ case MDLCACHE_VCOLLIDE:
+ {
+ ProcessDataIntoCache( pInfo->hModel, pInfo->type, 0, pData, nBytesRead, status == FSASYNC_OK );
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ break;
+ }
+
+ case MDLCACHE_ANIMBLOCK:
+ {
+ // cache assumes ownership of valid async'd data
+ if ( !ProcessDataIntoCache( pInfo->hModel, MDLCACHE_ANIMBLOCK, pInfo->iAnimBlock, pData, nBytesRead, status == FSASYNC_OK ) )
+ {
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ }
+ break;
+ }
+
+ default:
+ Assert( 0 );
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CMDLCache::ProcessPendingAsyncs( MDLCacheDataType_t type )
+{
+ if ( !ThreadInMainThread() )
+ {
+ return;
+ }
+
+ if ( !m_PendingAsyncs.Count() )
+ {
+ return;
+ }
+
+ static bool bReentering;
+ if ( bReentering )
+ {
+ return;
+ }
+ bReentering = true;
+
+ AUTO_LOCK( m_AsyncMutex );
+
+ // Process all of the completed loads that were requested before a new one. This ensures two
+ // things -- the LRU is in correct order, and it catches precached items lurking
+ // in the async queue that have only been requested once (thus aren't being cached
+ // and might lurk forever, e.g., wood gibs in the citadel)
+ int current = m_PendingAsyncs.Head();
+ while ( current != m_PendingAsyncs.InvalidIndex() )
+ {
+ int next = m_PendingAsyncs.Next( current );
+
+ if ( type == MDLCACHE_NONE || m_PendingAsyncs[current].type == type )
+ {
+ // process, also removes from list
+ if ( ProcessPendingAsync( current ) <= 0 )
+ {
+ // indeterminate or pending
+ break;
+ }
+ }
+
+ current = next;
+ }
+
+ bReentering = false;
+}
+
+//-----------------------------------------------------------------------------
+// Cache model's specified dynamic data
+//-----------------------------------------------------------------------------
+bool CMDLCache::ClearAsync( MDLHandle_t handle, MDLCacheDataType_t type, int iAnimBlock, bool bAbort )
+{
+ int iAsyncInfo = GetAsyncInfoIndex( handle, type, iAnimBlock );
+ if ( iAsyncInfo != NO_ASYNC )
+ {
+ AsyncInfo_t *pInfo;
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ pInfo = &m_PendingAsyncs[iAsyncInfo];
+ }
+ if ( pInfo->hControl )
+ {
+ if ( bAbort )
+ {
+ g_pFullFileSystem->AsyncAbort( pInfo->hControl );
+ void *pData;
+ int ignored;
+ if ( g_pFullFileSystem->AsyncGetResult( pInfo->hControl, &pData, &ignored ) == FSASYNC_OK )
+ {
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ }
+ }
+ g_pFullFileSystem->AsyncRelease( pInfo->hControl );
+ pInfo->hControl = NULL;
+ }
+
+ SetAsyncInfoIndex( handle, type, iAnimBlock, NO_ASYNC );
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ m_PendingAsyncs.Remove( iAsyncInfo );
+ }
+
+ return true;
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CMDLCache::GetAsyncLoad( MDLCacheDataType_t type )
+{
+ switch ( type )
+ {
+ case MDLCACHE_STUDIOHDR:
+ return false;
+ case MDLCACHE_STUDIOHWDATA:
+ return mod_load_mesh_async.GetBool();
+ case MDLCACHE_VCOLLIDE:
+ return mod_load_vcollide_async.GetBool();
+ case MDLCACHE_ANIMBLOCK:
+ return mod_load_anims_async.GetBool();
+ case MDLCACHE_VIRTUALMODEL:
+ return false;
+ case MDLCACHE_VERTEXES:
+ return mod_load_mesh_async.GetBool();
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CMDLCache::SetAsyncLoad( MDLCacheDataType_t type, bool bAsync )
+{
+ bool bRetVal = false;
+ switch ( type )
+ {
+ case MDLCACHE_STUDIOHDR:
+ break;
+ case MDLCACHE_STUDIOHWDATA:
+ bRetVal = mod_load_mesh_async.GetBool();
+ mod_load_mesh_async.SetValue( bAsync );
+ break;
+ case MDLCACHE_VCOLLIDE:
+ bRetVal = mod_load_vcollide_async.GetBool();
+ mod_load_vcollide_async.SetValue( bAsync );
+ break;
+ case MDLCACHE_ANIMBLOCK:
+ bRetVal = mod_load_anims_async.GetBool();
+ mod_load_anims_async.SetValue( bAsync );
+ break;
+ case MDLCACHE_VIRTUALMODEL:
+ return false;
+ break;
+ case MDLCACHE_VERTEXES:
+ bRetVal = mod_load_mesh_async.GetBool();
+ mod_load_mesh_async.SetValue( bAsync );
+ break;
+ }
+ return bRetVal;
+}
+
+//-----------------------------------------------------------------------------
+// Cache model's specified dynamic data
+//-----------------------------------------------------------------------------
+vertexFileHeader_t *CMDLCache::BuildAndCacheVertexData( studiohdr_t *pStudioHdr, vertexFileHeader_t *pRawVvdHdr )
+{
+ MDLHandle_t handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff;
+ vertexFileHeader_t *pVvdHdr;
+
+ MdlCacheMsg( "MDLCache: Load VVD for %s\n", pStudioHdr->pszName() );
+
+ Assert( pRawVvdHdr );
+
+ // check header
+ if ( pRawVvdHdr->id != MODEL_VERTEX_FILE_ID )
+ {
+ Warning( "Error Vertex File for '%s' id %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->id, MODEL_VERTEX_FILE_ID );
+ return NULL;
+ }
+ if ( pRawVvdHdr->version != MODEL_VERTEX_FILE_VERSION )
+ {
+ Warning( "Error Vertex File for '%s' version %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->version, MODEL_VERTEX_FILE_VERSION );
+ return NULL;
+ }
+ if ( pRawVvdHdr->checksum != pStudioHdr->checksum )
+ {
+ Warning( "Error Vertex File for '%s' checksum %d should be %d\n", pStudioHdr->pszName(), pRawVvdHdr->checksum, pStudioHdr->checksum );
+ return NULL;
+ }
+
+ Assert( pRawVvdHdr->numLODs );
+ if ( !pRawVvdHdr->numLODs )
+ {
+ return NULL;
+ }
+
+ CTempAllocHelper pOriginalData;
+ if ( IsX360() )
+ {
+ unsigned char *pInput = (unsigned char *)pRawVvdHdr + sizeof( vertexFileHeader_t );
+ if ( CLZMA::IsCompressed( pInput ) )
+ {
+ // vvd arrives compressed, decode and cache the results
+ unsigned int nOriginalSize = CLZMA::GetActualSize( pInput );
+ pOriginalData.Alloc( sizeof( vertexFileHeader_t ) + nOriginalSize );
+ V_memcpy( pOriginalData.Get(), pRawVvdHdr, sizeof( vertexFileHeader_t ) );
+ unsigned int nOutputSize = CLZMA::Uncompress( pInput, sizeof( vertexFileHeader_t ) + (unsigned char *)pOriginalData.Get() );
+ if ( nOutputSize != nOriginalSize )
+ {
+ // decoder failure
+ return NULL;
+ }
+
+ pRawVvdHdr = (vertexFileHeader_t *)pOriginalData.Get();
+ }
+ }
+
+ bool bNeedsTangentS = IsX360() || (g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 80);
+ int rootLOD = min( (int)pStudioHdr->rootLOD, pRawVvdHdr->numLODs - 1 );
+
+ // determine final cache footprint, possibly truncated due to lod
+ int cacheLength = Studio_VertexDataSize( pRawVvdHdr, rootLOD, bNeedsTangentS );
+
+ MdlCacheMsg("MDLCache: Alloc VVD %s\n", GetModelName( handle ) );
+
+ // allocate cache space
+ MemAlloc_PushAllocDbgInfo( "Models:Vertex data", 0);
+ pVvdHdr = (vertexFileHeader_t *)AllocData( MDLCACHE_VERTEXES, cacheLength );
+ MemAlloc_PopAllocDbgInfo();
+
+ GetCacheSection( MDLCACHE_VERTEXES )->BeginFrameLocking();
+
+ CacheData( &m_MDLDict[handle]->m_VertexCache, pVvdHdr, cacheLength, pStudioHdr->pszName(), MDLCACHE_VERTEXES, MakeCacheID( handle, MDLCACHE_VERTEXES) );
+
+ // expected 32 byte alignment
+ Assert( ((int64)pVvdHdr & 0x1F) == 0 );
+
+ // load minimum vertexes and fixup
+ Studio_LoadVertexes( pRawVvdHdr, pVvdHdr, rootLOD, bNeedsTangentS );
+
+ GetCacheSection( MDLCACHE_VERTEXES )->EndFrameLocking();
+
+ return pVvdHdr;
+}
+
+//-----------------------------------------------------------------------------
+// Load and cache model's specified dynamic data
+//-----------------------------------------------------------------------------
+vertexFileHeader_t *CMDLCache::LoadVertexData( studiohdr_t *pStudioHdr )
+{
+ char pFileName[MAX_PATH];
+ MDLHandle_t handle;
+
+ Assert( pStudioHdr );
+ handle = (MDLHandle_t)(int)pStudioHdr->virtualModel&0xffff;
+ Assert( !m_MDLDict[handle]->m_VertexCache );
+
+ studiodata_t *pStudioData = m_MDLDict[handle];
+
+ if ( pStudioData->m_nFlags & STUDIODATA_FLAGS_NO_VERTEX_DATA )
+ {
+ return NULL;
+ }
+
+ int iAsync = GetAsyncInfoIndex( handle, MDLCACHE_VERTEXES );
+
+ if ( iAsync == NO_ASYNC )
+ {
+ // load the VVD file
+ // use model name for correct path
+ MakeFilename( handle, ".vvd", pFileName, sizeof(pFileName) );
+ if ( IsX360() )
+ {
+ char pX360Filename[MAX_PATH];
+ UpdateOrCreate( pStudioHdr, pFileName, pX360Filename, sizeof( pX360Filename ), "GAME" );
+ Q_strncpy( pFileName, pX360Filename, sizeof(pX360Filename) );
+ }
+
+ MdlCacheMsg( "MDLCache: Begin load VVD %s\n", pFileName );
+
+ AsyncInfo_t info;
+ if ( IsDebug() )
+ {
+ memset( &info, 0xdd, sizeof( AsyncInfo_t ) );
+ }
+ info.hModel = handle;
+ info.type = MDLCACHE_VERTEXES;
+ info.iAnimBlock = 0;
+ info.hControl = NULL;
+ LoadData( pFileName, "GAME", mod_load_mesh_async.GetBool(), &info.hControl );
+ {
+ AUTO_LOCK( m_AsyncMutex );
+ iAsync = SetAsyncInfoIndex( handle, MDLCACHE_VERTEXES, m_PendingAsyncs.AddToTail( info ) );
+ }
+ }
+
+ ProcessPendingAsync( iAsync );
+
+ return (vertexFileHeader_t *)CheckData( m_MDLDict[handle]->m_VertexCache, MDLCACHE_VERTEXES );
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+vertexFileHeader_t *CMDLCache::GetVertexData( MDLHandle_t handle )
+{
+ if ( mod_test_not_available.GetBool() )
+ return NULL;
+
+ if ( mod_test_verts_not_available.GetBool() )
+ return NULL;
+
+ return CacheVertexData( GetStudioHdr( handle ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocates a cacheable item
+//-----------------------------------------------------------------------------
+void *CMDLCache::AllocData( MDLCacheDataType_t type, int size )
+{
+ void *pData = _aligned_malloc( size, 32 );
+
+ if ( !pData )
+ {
+ Error( "CMDLCache:: Out of memory" );
+ return NULL;
+ }
+
+ return pData;
+}
+
+
+//-----------------------------------------------------------------------------
+// Caches an item
+//-----------------------------------------------------------------------------
+void CMDLCache::CacheData( DataCacheHandle_t *c, void *pData, int size, const char *name, MDLCacheDataType_t type, DataCacheClientID_t id )
+{
+ if ( !pData )
+ {
+ return;
+ }
+
+ if ( id == (DataCacheClientID_t)-1 )
+ id = (DataCacheClientID_t)pData;
+
+ GetCacheSection( type )->Add(id, pData, size, c );
+}
+
+//-----------------------------------------------------------------------------
+// returns the cached data, and moves to the head of the LRU list
+// if present, otherwise returns NULL
+//-----------------------------------------------------------------------------
+void *CMDLCache::CheckData( DataCacheHandle_t c, MDLCacheDataType_t type )
+{
+ return GetCacheSection( type )->Get( c, true );
+}
+
+//-----------------------------------------------------------------------------
+// returns the cached data, if present, otherwise returns NULL
+//-----------------------------------------------------------------------------
+void *CMDLCache::CheckDataNoTouch( DataCacheHandle_t c, MDLCacheDataType_t type )
+{
+ return GetCacheSection( type )->GetNoTouch( c, true );
+}
+
+//-----------------------------------------------------------------------------
+// Frees a cache item
+//-----------------------------------------------------------------------------
+void CMDLCache::UncacheData( DataCacheHandle_t c, MDLCacheDataType_t type, bool bLockedOk )
+{
+ if ( c == DC_INVALID_HANDLE )
+ return;
+
+ if ( !GetCacheSection( type )->IsPresent( c ) )
+ return;
+
+ if ( GetCacheSection( type )->BreakLock( c ) && !bLockedOk )
+ {
+ DevMsg( "Warning: freed a locked resource\n" );
+ Assert( 0 );
+ }
+
+ const void *pItemData;
+ GetCacheSection( type )->Remove( c, &pItemData );
+
+ FreeData( type, (void *)pItemData );
+}
+
+
+//-----------------------------------------------------------------------------
+// Frees memory for an item
+//-----------------------------------------------------------------------------
+void CMDLCache::FreeData( MDLCacheDataType_t type, void *pData )
+{
+ if ( type != MDLCACHE_ANIMBLOCK )
+ {
+ _aligned_free( (void *)pData );
+ }
+ else
+ {
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ }
+}
+
+
+void CMDLCache::InitPreloadData( bool rebuild )
+{
+}
+
+void CMDLCache::ShutdownPreloadData()
+{
+}
+
+//-----------------------------------------------------------------------------
+// Work function for processing a model delivered by the queued loader.
+// ProcessDataIntoCache() is invoked for each MDL datum.
+//-----------------------------------------------------------------------------
+void CMDLCache::ProcessQueuedData( ModelParts_t *pModelParts, bool bHeaderOnly )
+{
+ void *pData;
+ int nSize;
+
+ // the studiohdr is critical, ensure it's setup as expected
+ MDLHandle_t handle = pModelParts->hMDL;
+ studiohdr_t *pStudioHdr = NULL;
+ if ( !pModelParts->bHeaderLoaded && ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_MDL ) ) )
+ {
+ DEBUG_SCOPE_TIMER(mdl);
+ pData = pModelParts->Buffers[ModelParts_t::BUFFER_MDL].Base();
+ nSize = pModelParts->Buffers[ModelParts_t::BUFFER_MDL].TellMaxPut();
+ ProcessDataIntoCache( handle, MDLCACHE_STUDIOHDR, 0, pData, nSize, nSize != 0 );
+ LockStudioHdr( handle );
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ pModelParts->bHeaderLoaded = true;
+ }
+
+ if ( bHeaderOnly )
+ {
+ return;
+ }
+
+ bool bAbort = false;
+ pStudioHdr = (studiohdr_t *)CheckDataNoTouch( m_MDLDict[handle]->m_MDLCache, MDLCACHE_STUDIOHDR );
+ if ( !pStudioHdr )
+ {
+ // The header is expected to be loaded and locked, everything depends on it!
+ // but if the async read fails, we might not have it
+ //Assert( 0 );
+ DevWarning( "CMDLCache:: Error MDLCACHE_STUDIOHDR not present for '%s'\n", GetModelName( handle ) );
+
+ // cannot unravel any of this model's dependant data, abort any further processing
+ bAbort = true;
+ }
+
+ if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_PHY ) )
+ {
+ DEBUG_SCOPE_TIMER(phy);
+ // regardless of error, call job callback so caller can do cleanup of their context
+ pData = pModelParts->Buffers[ModelParts_t::BUFFER_PHY].Base();
+ nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_PHY].TellMaxPut();
+ ProcessDataIntoCache( handle, MDLCACHE_VCOLLIDE, 0, pData, nSize, nSize != 0 );
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ }
+
+ // vvd vertexes before vtx
+ if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_VVD ) )
+ {
+ DEBUG_SCOPE_TIMER(vvd);
+ pData = pModelParts->Buffers[ModelParts_t::BUFFER_VVD].Base();
+ nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_VVD].TellMaxPut();
+ ProcessDataIntoCache( handle, MDLCACHE_VERTEXES, 0, pData, nSize, nSize != 0 );
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ }
+
+ // can construct meshes after vvd and vtx vertexes arrive
+ if ( pModelParts->nLoadedParts & ( 1 << ModelParts_t::BUFFER_VTX ) )
+ {
+ DEBUG_SCOPE_TIMER(vtx);
+ pData = pModelParts->Buffers[ModelParts_t::BUFFER_VTX].Base();
+ nSize = bAbort ? 0 : pModelParts->Buffers[ModelParts_t::BUFFER_VTX].TellMaxPut();
+
+ // ProcessDataIntoCache() will do an unlock, so lock
+ studiodata_t *pStudioData = m_MDLDict[handle];
+ GetCacheSection( MDLCACHE_STUDIOHWDATA )->Lock( pStudioData->m_VertexCache );
+ {
+ // constructing the static meshes isn't thread safe
+ AUTO_LOCK( m_QueuedLoadingMutex );
+ ProcessDataIntoCache( handle, MDLCACHE_STUDIOHWDATA, 0, pData, nSize, nSize != 0 );
+ }
+ g_pFullFileSystem->FreeOptimalReadBuffer( pData );
+ }
+
+ UnlockStudioHdr( handle );
+ delete pModelParts;
+}
+
+//-----------------------------------------------------------------------------
+// Journals each of the incoming MDL components until all arrive (or error).
+// Not all components exist, but that information is not known at job submission.
+//-----------------------------------------------------------------------------
+void CMDLCache::QueuedLoaderCallback_MDL( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError )
+{
+ // validity is denoted by a nonzero buffer
+ nSize = ( loaderError == LOADERERROR_NONE ) ? nSize : 0;
+
+ // journal each incoming buffer
+ ModelParts_t *pModelParts = (ModelParts_t *)pContext;
+ ModelParts_t::BufferType_t bufferType = static_cast< ModelParts_t::BufferType_t >((int)pContext2);
+ pModelParts->Buffers[bufferType].SetExternalBuffer( (void *)pData, nSize, nSize, CUtlBuffer::READ_ONLY );
+ pModelParts->nLoadedParts += (1 << bufferType);
+
+ // wait for all components
+ if ( pModelParts->DoFinalProcessing() )
+ {
+ if ( !IsPC() )
+ {
+ // now have all components, process the raw data into the cache
+ g_MDLCache.ProcessQueuedData( pModelParts );
+ }
+ else
+ {
+ // PC background load path. pull in material dependencies on the fly.
+ Assert( ThreadInMainThread() );
+
+ g_MDLCache.ProcessQueuedData( pModelParts, true );
+
+ // preload all possible paths to VMTs
+ {
+ DEBUG_SCOPE_TIMER(findvmt);
+ MaterialLock_t hMatLock = materials->Lock();
+ if ( studiohdr_t * pHdr = g_MDLCache.GetStudioHdr( pModelParts->hMDL ) )
+ {
+ if ( !(pHdr->flags & STUDIOHDR_FLAGS_OBSOLETE) )
+ {
+ char buf[MAX_PATH];
+ V_strcpy( buf, "materials/" );
+ int prefixLen = V_strlen( buf );
+
+ for ( int t = 0; t < pHdr->numtextures; ++t )
+ {
+ // XXX this does not take remaps from vtxdata into account;
+ // right now i am not caring about that. we will hitch if any
+ // LODs remap to materials that are not in the header. (henryg)
+ const char *pTexture = pHdr->pTexture(t)->pszName();
+ pTexture += ( pTexture[0] == CORRECT_PATH_SEPARATOR || pTexture[0] == INCORRECT_PATH_SEPARATOR );
+ for ( int cd = 0; cd < pHdr->numcdtextures; ++cd )
+ {
+ const char *pCdTexture = pHdr->pCdtexture( cd );
+ pCdTexture += ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR );
+ V_ComposeFileName( pCdTexture, pTexture, buf + prefixLen, MAX_PATH - prefixLen );
+ V_strncat( buf, ".vmt", MAX_PATH, COPY_ALL_CHARACTERS );
+ pModelParts->bMaterialsPending = true;
+ const char *pbuf = buf;
+ g_pFullFileSystem->AddFilesToFileCache( pModelParts->hFileCache, &pbuf, 1, "GAME" );
+ if ( materials->IsMaterialLoaded( buf + prefixLen ) )
+ {
+ // found a loaded one. still cache it in case it unloads,
+ // but we can stop adding more potential paths to the cache
+ // since this one is known to be valid.
+ break;
+ }
+ }
+ }
+ }
+ }
+ materials->Unlock(hMatLock);
+ }
+
+ // queue functor which will start polling every frame by re-queuing itself
+ g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
+ }
+ }
+}
+
+void CMDLCache::ProcessDynamicLoad( ModelParts_t *pModelParts )
+{
+ Assert( IsPC() && ThreadInMainThread() );
+
+ if ( !g_pFullFileSystem->IsFileCacheLoaded( pModelParts->hFileCache ) )
+ {
+ // poll again next frame...
+ g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
+ return;
+ }
+
+ if ( pModelParts->bMaterialsPending )
+ {
+ DEBUG_SCOPE_TIMER(processvmt);
+ pModelParts->bMaterialsPending = false;
+ pModelParts->bTexturesPending = true;
+
+ MaterialLock_t hMatLock = materials->Lock();
+ materials->SetAsyncTextureLoadCache( pModelParts->hFileCache );
+
+ // Load all the materials
+ studiohdr_t * pHdr = g_MDLCache.GetStudioHdr( pModelParts->hMDL );
+ if ( pHdr && !(pHdr->flags & STUDIOHDR_FLAGS_OBSOLETE) )
+ {
+ // build strings inside a buffer that already contains a materials/ prefix
+ char buf[MAX_PATH];
+ V_strcpy( buf, "materials/" );
+ int prefixLen = V_strlen( buf );
+
+ // XXX this does not take remaps from vtxdata into account;
+ // right now i am not caring about that. we will hitch if any
+ // LODs remap to materials that are not in the header. (henryg)
+ for ( int t = 0; t < pHdr->numtextures; ++t )
+ {
+ const char *pTexture = pHdr->pTexture(t)->pszName();
+ pTexture += ( pTexture[0] == CORRECT_PATH_SEPARATOR || pTexture[0] == INCORRECT_PATH_SEPARATOR );
+ for ( int cd = 0; cd < pHdr->numcdtextures; ++cd )
+ {
+ const char *pCdTexture = pHdr->pCdtexture( cd );
+ pCdTexture += ( pCdTexture[0] == CORRECT_PATH_SEPARATOR || pCdTexture[0] == INCORRECT_PATH_SEPARATOR );
+ V_ComposeFileName( pCdTexture, pTexture, buf + prefixLen, MAX_PATH - prefixLen );
+ IMaterial* pMaterial = materials->FindMaterial( buf + prefixLen, TEXTURE_GROUP_MODEL, false );
+ if ( !IsErrorMaterial( pMaterial ) && !pMaterial->IsPrecached() )
+ {
+ pModelParts->Materials.AddToTail( pMaterial );
+ pMaterial->IncrementReferenceCount();
+ // Force texture loads while material system is set to capture
+ // them and redirect to an error texture... this will populate
+ // the file cache with all the requested textures
+ pMaterial->RefreshPreservingMaterialVars();
+ break;
+ }
+ }
+ }
+ }
+
+ materials->SetAsyncTextureLoadCache( NULL );
+ materials->Unlock( hMatLock );
+
+ // poll again next frame... dont want to do too much work right now
+ g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
+ return;
+ }
+
+ if ( pModelParts->bTexturesPending )
+ {
+ DEBUG_SCOPE_TIMER(matrefresh);
+ pModelParts->bTexturesPending = false;
+
+ // Perform the real material loads now while raw texture files are cached.
+ FOR_EACH_VEC( pModelParts->Materials, i )
+ {
+ IMaterial* pMaterial = pModelParts->Materials[i];
+ if ( !IsErrorMaterial( pMaterial ) && pMaterial->IsPrecached() )
+ {
+ // Do a full reload to get the correct textures and computed flags
+ pMaterial->Refresh();
+ }
+ }
+
+ // poll again next frame... dont want to do too much work right now
+ g_pQueuedLoader->QueueDynamicLoadFunctor( CreateFunctor( ProcessDynamicLoad, pModelParts ) );
+ return;
+ }
+
+ // done. finish and clean up.
+ Assert( !pModelParts->bTexturesPending && !pModelParts->bMaterialsPending );
+
+ // pull out cached items we want to overlap with final processing
+ CleanupModelParts_t *pCleanup = new CleanupModelParts_t;
+ pCleanup->hFileCache = pModelParts->hFileCache;
+ pCleanup->Materials.Swap( pModelParts->Materials );
+ g_pQueuedLoader->QueueCleanupDynamicLoadFunctor( CreateFunctor( CleanupDynamicLoad, pCleanup ) );
+
+ {
+ DEBUG_SCOPE_TIMER(processall);
+ g_MDLCache.ProcessQueuedData( pModelParts ); // pModelParts is deleted here
+ }
+}
+
+void CMDLCache::CleanupDynamicLoad( CleanupModelParts_t *pCleanup )
+{
+ Assert( IsPC() && ThreadInMainThread() );
+
+ // remove extra material refs, unload cached files
+ FOR_EACH_VEC( pCleanup->Materials, i )
+ {
+ pCleanup->Materials[i]->DecrementReferenceCount();
+ }
+
+ g_pFullFileSystem->DestroyFileCache( pCleanup->hFileCache );
+
+ delete pCleanup;
+}
+
+//-----------------------------------------------------------------------------
+// Build a queued loader job to get the MDL ant all of its components into the cache.
+//-----------------------------------------------------------------------------
+bool CMDLCache::PreloadModel( MDLHandle_t handle )
+{
+ if ( g_pQueuedLoader->IsDynamic() == false )
+ {
+ if ( !IsX360() )
+ {
+ return false;
+ }
+
+ if ( !g_pQueuedLoader->IsMapLoading() || handle == MDLHANDLE_INVALID )
+ {
+ return false;
+ }
+ }
+
+ if ( !g_pQueuedLoader->IsBatching() )
+ {
+ // batching must be active, following code depends on its behavior
+ DevWarning( "CMDLCache:: Late preload of model '%s'\n", GetModelName( handle ) );
+ return false;
+ }
+
+ // determine existing presence
+ // actual necessity is not established here, allowable absent files need their i/o error to occur
+ bool bNeedsMDL = !IsDataLoaded( handle, MDLCACHE_STUDIOHDR );
+ bool bNeedsVTX = !IsDataLoaded( handle, MDLCACHE_STUDIOHWDATA );
+ bool bNeedsVVD = !IsDataLoaded( handle, MDLCACHE_VERTEXES );
+ bool bNeedsPHY = !IsDataLoaded( handle, MDLCACHE_VCOLLIDE );
+ if ( !bNeedsMDL && !bNeedsVTX && !bNeedsVVD && !bNeedsPHY )
+ {
+ // nothing to do
+ return true;
+ }
+
+ char szFilename[MAX_PATH];
+ char szNameOnDisk[MAX_PATH];
+ V_strncpy( szFilename, GetActualModelName( handle ), sizeof( szFilename ) );
+ V_StripExtension( szFilename, szFilename, sizeof( szFilename ) );
+
+ // need to gather all model parts (mdl, vtx, vvd, phy, ani)
+ ModelParts_t *pModelParts = new ModelParts_t;
+ pModelParts->hMDL = handle;
+ pModelParts->hFileCache = g_pFullFileSystem->CreateFileCache();
+
+ // create multiple loader jobs to perform gathering i/o operations
+ LoaderJob_t loaderJob;
+ loaderJob.m_pPathID = "GAME";
+ loaderJob.m_pCallback = QueuedLoaderCallback_MDL;
+ loaderJob.m_pContext = (void *)pModelParts;
+ loaderJob.m_Priority = LOADERPRIORITY_DURINGPRELOAD;
+ loaderJob.m_bPersistTargetData = true;
+
+ if ( bNeedsMDL )
+ {
+ V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.mdl", szFilename, GetPlatformExt() );
+ loaderJob.m_pFilename = szNameOnDisk;
+ loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_MDL;
+ g_pQueuedLoader->AddJob( &loaderJob );
+ pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_MDL;
+ }
+
+ if ( bNeedsVTX )
+ {
+ // vtx extensions are .xxx.vtx, need to re-form as, ???.xxx.yyy.vtx
+ char szTempName[MAX_PATH];
+ V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s", szFilename, GetVTXExtension() );
+ V_StripExtension( szNameOnDisk, szTempName, sizeof( szTempName ) );
+ V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.vtx", szTempName, GetPlatformExt() );
+ loaderJob.m_pFilename = szNameOnDisk;
+ loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_VTX;
+ g_pQueuedLoader->AddJob( &loaderJob );
+ pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_VTX;
+ }
+
+ if ( bNeedsVVD )
+ {
+ V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.vvd", szFilename, GetPlatformExt() );
+ loaderJob.m_pFilename = szNameOnDisk;
+ loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_VVD;
+ g_pQueuedLoader->AddJob( &loaderJob );
+ pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_VVD;
+ }
+
+ if ( bNeedsPHY )
+ {
+ V_snprintf( szNameOnDisk, sizeof( szNameOnDisk ), "%s%s.phy", szFilename, GetPlatformExt() );
+ loaderJob.m_pFilename = szNameOnDisk;
+ loaderJob.m_pContext2 = (void *)ModelParts_t::BUFFER_PHY;
+ g_pQueuedLoader->AddJob( &loaderJob );
+ pModelParts->nExpectedParts |= 1 << ModelParts_t::BUFFER_PHY;
+ }
+
+ if ( !pModelParts->nExpectedParts )
+ {
+ g_pFullFileSystem->DestroyFileCache( pModelParts->hFileCache );
+ delete pModelParts;
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Clear the STUDIODATA_ERROR_MODEL flag.
+//-----------------------------------------------------------------------------
+void CMDLCache::ResetErrorModelStatus( MDLHandle_t handle )
+{
+ if ( handle == MDLHANDLE_INVALID )
+ return;
+
+ m_MDLDict[handle]->m_nFlags &= ~STUDIODATA_ERROR_MODEL;
+}
+
+//-----------------------------------------------------------------------------
+//
+//-----------------------------------------------------------------------------
+void CMDLCache::MarkFrame()
+{
+ ProcessPendingAsyncs();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: bind studiohdr_t support functions to the mdlcacher
+//-----------------------------------------------------------------------------
+const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const
+{
+ MDLHandle_t handle = g_MDLCache.FindMDL( pModelName );
+ *cache = (void*)(uintp)handle;
+ return g_MDLCache.GetStudioHdr( handle );
+}
+
+virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const
+{
+ if (numincludemodels == 0)
+ return NULL;
+
+ return g_MDLCache.GetVirtualModelFast( this, (MDLHandle_t)(int)virtualModel&0xffff );
+}
+
+byte *studiohdr_t::GetAnimBlock( int i ) const
+{
+ return g_MDLCache.GetAnimBlock( (MDLHandle_t)(int)virtualModel&0xffff, i );
+}
+
+int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const
+{
+ return g_MDLCache.GetAutoplayList( (MDLHandle_t)(int)virtualModel&0xffff, pOut );
+}
+
+const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const
+{
+ return g_MDLCache.GetStudioHdr( (MDLHandle_t)(int)cache&0xffff );
+}
+
diff --git a/datacache/xbox/xbox.def b/datacache/xbox/xbox.def
new file mode 100644
index 0000000..2050c49
--- /dev/null
+++ b/datacache/xbox/xbox.def
@@ -0,0 +1,3 @@
+LIBRARY datacache_360.dll
+EXPORTS
+ CreateInterface @1 \ No newline at end of file