diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /datacache | |
| download | archived-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.cpp | 1385 | ||||
| -rw-r--r-- | datacache/datacache.h | 384 | ||||
| -rw-r--r-- | datacache/datacache.vpc | 55 | ||||
| -rw-r--r-- | datacache/datacache_common.h | 31 | ||||
| -rw-r--r-- | datacache/mdlcache.cpp | 3924 | ||||
| -rw-r--r-- | datacache/xbox/xbox.def | 3 |
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 ¬ification ); + 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 ¬ification ) +{ + 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 |