Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2222 lines
59 KiB

//========== Copyright © 2005, Valve Corporation, All rights reserved. ========
//
// Purpose: Texture heap.
//
//=============================================================================
#include "tier1/mempool.h"
#include "tier1/convar.h"
#include "tier1/utlmap.h"
#include "shaderapidx8.h"
#include "texturedx8.h"
#include "textureheap.h"
#include "shaderapidx8_global.h"
#include "filesystem.h"
#include "vstdlib/jobthread.h"
#include "tier0/icommandline.h"
#include "tier0/memdbgon.h"
struct THFreeBlock_t
{
THInfo_t heapInfo;
THFreeBlock_t *pPrevFree, *pNextFree;
};
#define BASE_COLOR_BEFORE 0xBB // funky color to show texture while i/o in progress
#define BASE_COLOR_AFTER 0xCC // funky color to show mip0 texture
enum TextureHeapDebug_t
{
THD_OFF = 0,
THD_COLORIZE_BEFOREIO, // mip0 is colorized until i/o replaces with real bits
THD_COLORIZE_AFTERIO, // mip0 is colorized instead of i/o real bits
THD_SPEW
};
ConVar texture_heap_debug( "texture_heap_debug", "0", 0, "0:Off, 1:Color Before I/O 2:Color After I/O 3:Spew" );
bool g_bUseStandardAllocator = true; // mixed texture heap is not yet viable
bool g_bUseBasePools = true; // do texture streaming
void GetCommandLineArgs()
{
#if defined( SUPPORTS_TEXTURE_STREAMING )
static bool bReadCommandLine;
if ( !bReadCommandLine )
{
bReadCommandLine = true;
if ( g_pFullFileSystem->IsDVDHosted() )
{
g_bUseBasePools = false;
}
if ( CommandLine()->FindParm( "-notextureheap" ) )
{
g_bUseStandardAllocator = true;
}
if ( CommandLine()->FindParm( "-notexturestreaming" ) )
{
g_bUseBasePools = false;
}
}
#else
g_bUseStandardAllocator = true;
g_bUseBasePools = false;
#endif
}
bool UseStandardAllocator()
{
GetCommandLineArgs();
return g_bUseStandardAllocator;
}
bool UseBasePoolsForStreaming()
{
GetCommandLineArgs();
return g_bUseBasePools;
}
#if !defined( _RELEASE ) && !defined( _CERT )
#define StrongAssert( expr ) if ( (expr) ) ; else { DebuggerBreak(); }
#else
#define StrongAssert( expr ) ((void)0)
#endif
//-----------------------------------------------------------------------------
// Set Texture HW bases
//-----------------------------------------------------------------------------
inline void SetD3DTextureBasePtr( IDirect3DBaseTexture* pTex, void *pBaseBuffer )
{
pTex->Format.BaseAddress = ((unsigned int)pBaseBuffer) >> 12;
}
inline void SetD3DTextureMipPtr( IDirect3DBaseTexture* pTex, void *pMipBuffer )
{
pTex->Format.MipAddress = ((unsigned int)pMipBuffer) >> 12;
}
MEMALLOC_DEFINE_EXTERNAL_TRACKING(CD3DPoolAllocator);
// only used for the pool allocators
class CD3DPoolAllocator
{
public:
static void *Alloc( int bytes )
{
DWORD attributes = MAKE_XALLOC_ATTRIBUTES(
0,
false,
TRUE,
FALSE,
eXALLOCAllocatorId_D3D,
XALLOC_PHYSICAL_ALIGNMENT_4K,
XALLOC_MEMPROTECT_WRITECOMBINE,
FALSE,
XALLOC_MEMTYPE_PHYSICAL );
void *retval = XMemAlloc( bytes, attributes );
MemAlloc_RegisterExternalAllocation( CD3DPoolAllocator, retval, XPhysicalSize( retval ) );
return retval;
}
static void Free( void *p )
{
DWORD attributes = MAKE_XALLOC_ATTRIBUTES(
0,
false,
TRUE,
FALSE,
eXALLOCAllocatorId_D3D,
XALLOC_PHYSICAL_ALIGNMENT_4K,
XALLOC_MEMPROTECT_WRITECOMBINE,
FALSE,
XALLOC_MEMTYPE_PHYSICAL );
MemAlloc_RegisterExternalDeallocation( CD3DPoolAllocator, p, XPhysicalSize( p ) );
XMemFree( p, attributes );
}
};
MEMALLOC_DEFINE_EXTERNAL_TRACKING(CD3DStandardAllocator);
class CD3DStandardAllocator
{
public:
static void *Alloc( int bytes )
{
DWORD attributes = MAKE_XALLOC_ATTRIBUTES(
0,
false,
TRUE,
FALSE,
eXALLOCAllocatorId_D3D,
XALLOC_PHYSICAL_ALIGNMENT_4K,
XALLOC_MEMPROTECT_WRITECOMBINE,
FALSE,
XALLOC_MEMTYPE_PHYSICAL );
m_nTotalAllocations++;
m_nTotalSize += AlignValue( bytes, 4096 );
void *retval = XMemAlloc( bytes, attributes );
MemAlloc_RegisterExternalAllocation( CD3DStandardAllocator, retval, XPhysicalSize( retval ) );
return retval;
}
static void Free( void *p )
{
DWORD attributes = MAKE_XALLOC_ATTRIBUTES(
0,
false,
TRUE,
FALSE,
eXALLOCAllocatorId_D3D,
XALLOC_PHYSICAL_ALIGNMENT_4K,
XALLOC_MEMPROTECT_WRITECOMBINE,
FALSE,
XALLOC_MEMTYPE_PHYSICAL );
m_nTotalAllocations--;
m_nTotalSize -= XMemSize( p, attributes );
MemAlloc_RegisterExternalDeallocation( CD3DStandardAllocator, p, XPhysicalSize( p ) );
XMemFree( p, attributes );
}
static int GetAllocations()
{
return m_nTotalAllocations;
}
static int GetSize()
{
return m_nTotalSize;
}
static int m_nTotalSize;
static int m_nTotalAllocations;
};
int CD3DStandardAllocator::m_nTotalSize;
int CD3DStandardAllocator::m_nTotalAllocations;
void SetD3DTextureImmobile( IDirect3DBaseTexture *pTexture, bool bImmobile )
{
if ( pTexture->GetType() == D3DRTYPE_TEXTURE )
{
(( CXboxTexture *)pTexture)->bImmobile = bImmobile;
}
}
CXboxTexture *GetTexture( THInfo_t *pInfo )
{
if ( !pInfo->bFree && !pInfo->bNonTexture )
{
return (CXboxTexture *)((byte *)pInfo - offsetof( CXboxTexture, m_fAllocator ));
}
return NULL;
}
inline THFreeBlock_t *GetFreeBlock( THInfo_t *pInfo )
{
if ( pInfo->bFree )
{
return (THFreeBlock_t *)((byte *)pInfo - offsetof( THFreeBlock_t, heapInfo ));
}
return NULL;
}
MEMALLOC_DEFINE_EXTERNAL_TRACKING(CMixedTextureHeap);
class CMixedTextureHeap
{
enum
{
SIZE_ALIGNMENT = XBOX_HDD_SECTORSIZE,
MIN_BLOCK_SIZE = 1024,
};
public:
CMixedTextureHeap() :
m_nLogicalBytes( 0 ),
m_nActualBytes( 0 ),
m_nAllocs( 0 ),
m_nOldBytes( 0 ),
m_nNonTextureAllocs( 0 ),
m_nBytesTotal( 0 ),
m_pBase( NULL ),
m_pFirstFree( NULL )
{
}
void Init()
{
extern ConVar mat_texturecachesize;
MEM_ALLOC_CREDIT_( "CMixedTextureHeap" );
m_nBytesTotal = ( mat_texturecachesize.GetInt() * 1024 * 1024 );
#if 0
m_nBytesTotal = AlignValue( m_nBytesTotal, SIZE_ALIGNMENT );
m_pBase = CD3DStandardAllocator::Alloc( m_nBytesTotal );
#else
m_nBytesTotal = AlignValue( m_nBytesTotal, 16*1024*1024 );
m_pBase = XPhysicalAlloc( m_nBytesTotal, MAXULONG_PTR, 4096, PAGE_READWRITE | PAGE_WRITECOMBINE | MEM_16MB_PAGES );
MemAlloc_RegisterExternalAllocation( CMixedTextureHeap, m_pBase, XPhysicalSize( m_pBase ) );
#endif
m_pFirstFree = (THFreeBlock_t *)m_pBase;
m_pFirstFree->heapInfo.bFree = true;
m_pFirstFree->heapInfo.bNonTexture = false;
m_pFirstFree->heapInfo.nBytes = m_nBytesTotal;
m_pFirstFree->heapInfo.pNext = NULL;
m_pFirstFree->heapInfo.pPrev = NULL;
m_pFirstFree->pNextFree = NULL;
m_pFirstFree->pPrevFree = NULL;
m_pLastFree = m_pFirstFree;
}
void *Alloc( int bytes, THInfo_t *pInfo, bool bNonTexture = false )
{
pInfo->nBytes = AlignValue( bytes, SIZE_ALIGNMENT );
if ( !m_pBase )
{
Init();
}
if ( bNonTexture && m_nNonTextureAllocs == 0 )
{
Compact();
}
void *p = FindBlock( pInfo );
if ( !p )
{
p = ExpandToFindBlock( pInfo );
}
if ( p )
{
pInfo->nLogicalBytes = bytes;
pInfo->bNonTexture = bNonTexture;
m_nLogicalBytes += bytes;
m_nOldBytes += AlignValue( bytes, 4096 );
m_nActualBytes += pInfo->nBytes;
m_nAllocs++;
if ( bNonTexture )
{
m_nNonTextureAllocs++;
}
}
return p;
}
void Free( void *p, THInfo_t *pInfo )
{
if ( !p )
{
return;
}
m_nOldBytes -= AlignValue( pInfo->nLogicalBytes, 4096 );
if ( pInfo->bNonTexture )
{
m_nNonTextureAllocs--;
}
m_nLogicalBytes -= pInfo->nLogicalBytes;
m_nAllocs--;
m_nActualBytes -= pInfo->nBytes;
THFreeBlock_t *pFree = (THFreeBlock_t *)p;
pFree->heapInfo = *pInfo;
pFree->heapInfo.bFree = true;
AddToBlocksList( &pFree->heapInfo, pFree->heapInfo.pPrev, pFree->heapInfo.pNext );
pFree = MergeLeft( pFree );
pFree = MergeRight( pFree );
AddToFreeList( pFree );
if ( pInfo->bNonTexture && m_nNonTextureAllocs == 0 )
{
Compact();
}
}
int Size( void *p, THInfo_t *pInfo )
{
return AlignValue( pInfo->nBytes, SIZE_ALIGNMENT );
}
bool IsOwner( void *p )
{
return ( m_pBase && p >= m_pBase && p < (byte *)m_pBase + m_nBytesTotal );
}
//-----------------------------------------------------
void *FindBlock( THInfo_t *pInfo )
{
THFreeBlock_t *pCurrent = m_pFirstFree;
int nBytesDesired = pInfo->nBytes;
// Find the first block big enough to hold, then split it if appropriate
while ( pCurrent && pCurrent->heapInfo.nBytes < nBytesDesired )
{
pCurrent = pCurrent->pNextFree;
}
if ( pCurrent )
{
return ClaimBlock( pCurrent, pInfo );
}
return NULL;
}
void AddToFreeList( THFreeBlock_t *pFreeBlock )
{
pFreeBlock->heapInfo.nLogicalBytes = 0;
if ( m_pFirstFree )
{
THFreeBlock_t *pPrev = NULL;
THFreeBlock_t *pNext = m_pFirstFree;
int nBytes = pFreeBlock->heapInfo.nBytes;
while ( pNext && pNext->heapInfo.nBytes < nBytes )
{
pPrev = pNext;
pNext = pNext->pNextFree;
}
pFreeBlock->pPrevFree = pPrev;
pFreeBlock->pNextFree = pNext;
if ( pPrev )
{
pPrev->pNextFree = pFreeBlock;
}
else
{
m_pFirstFree = pFreeBlock;
}
if ( pNext )
{
pNext->pPrevFree = pFreeBlock;
}
else
{
m_pLastFree = pFreeBlock;
}
}
else
{
pFreeBlock->pPrevFree = pFreeBlock->pNextFree = NULL;
m_pLastFree = m_pFirstFree = pFreeBlock;
}
}
void RemoveFromFreeList( THFreeBlock_t *pFreeBlock )
{
if ( m_pFirstFree == pFreeBlock )
{
m_pFirstFree = m_pFirstFree->pNextFree;
}
else if ( pFreeBlock->pPrevFree )
{
pFreeBlock->pPrevFree->pNextFree = pFreeBlock->pNextFree;
}
if ( m_pLastFree == pFreeBlock )
{
m_pLastFree = pFreeBlock->pPrevFree;
}
else if ( pFreeBlock->pNextFree )
{
pFreeBlock->pNextFree->pPrevFree = pFreeBlock->pPrevFree;
}
pFreeBlock->pPrevFree = pFreeBlock->pNextFree = NULL;
}
THFreeBlock_t *GetLastFree()
{
return m_pLastFree;
}
void AddToBlocksList( THInfo_t *pBlock, THInfo_t *pPrev, THInfo_t *pNext )
{
if ( pPrev )
{
pPrev->pNext = pBlock;
}
if ( pNext)
{
pNext->pPrev = pBlock;
}
pBlock->pPrev = pPrev;
pBlock->pNext = pNext;
}
void RemoveFromBlocksList( THInfo_t *pBlock )
{
if ( pBlock->pPrev )
{
pBlock->pPrev->pNext = pBlock->pNext;
}
if ( pBlock->pNext )
{
pBlock->pNext->pPrev = pBlock->pPrev;
}
}
//-----------------------------------------------------
void *ClaimBlock( THFreeBlock_t *pFreeBlock, THInfo_t *pInfo )
{
RemoveFromFreeList( pFreeBlock );
int nBytesDesired = pInfo->nBytes;
int nBytesRemainder = pFreeBlock->heapInfo.nBytes - nBytesDesired;
*pInfo = pFreeBlock->heapInfo;
pInfo->bFree = false;
pInfo->bNonTexture = false;
if ( nBytesRemainder >= MIN_BLOCK_SIZE )
{
pInfo->nBytes = nBytesDesired;
THFreeBlock_t *pRemainder = (THFreeBlock_t *)(((byte *)(pFreeBlock)) + nBytesDesired);
pRemainder->heapInfo.bFree = true;
pRemainder->heapInfo.nBytes = nBytesRemainder;
AddToBlocksList( &pRemainder->heapInfo, pInfo, pInfo->pNext );
AddToFreeList( pRemainder );
}
AddToBlocksList( pInfo, pInfo->pPrev, pInfo->pNext );
return pFreeBlock;
}
THFreeBlock_t *MergeLeft( THFreeBlock_t *pFree )
{
THInfo_t *pPrev = pFree->heapInfo.pPrev;
if ( pPrev && pPrev->bFree )
{
pPrev->nBytes += pFree->heapInfo.nBytes;
RemoveFromBlocksList( &pFree->heapInfo );
pFree = GetFreeBlock( pPrev );
RemoveFromFreeList( pFree );
}
return pFree;
}
THFreeBlock_t *MergeRight( THFreeBlock_t *pFree )
{
THInfo_t *pNext = pFree->heapInfo.pNext;
if ( pNext && pNext->bFree )
{
pFree->heapInfo.nBytes += pNext->nBytes;
RemoveFromBlocksList( pNext );
RemoveFromFreeList( GetFreeBlock( pNext ) );
}
return pFree;
}
//-----------------------------------------------------
bool GetExpansionList( THFreeBlock_t *pFreeBlock, THInfo_t **ppStart, THInfo_t **ppEnd, int depth = 1 )
{
THInfo_t *pStart;
THInfo_t *pEnd;
int i;
pStart = &pFreeBlock->heapInfo;
pEnd = &pFreeBlock->heapInfo;
if ( m_nNonTextureAllocs > 0 )
{
return false;
}
// Walk backwards to start of expansion
i = depth;
while ( i > 0 && pStart->pPrev)
{
THInfo_t *pScan = pStart->pPrev;
while ( i > 0 && pScan && !pScan->bFree && GetTexture( pScan )->CanRelocate() )
{
pScan = pScan->pPrev;
i--;
}
if ( !pScan || !pScan->bFree )
{
break;
}
pStart = pScan;
}
// Walk forwards to start of expansion
i = depth;
while ( i > 0 && pEnd->pNext)
{
THInfo_t *pScan = pStart->pNext;
while ( i > 0 && pScan && !pScan->bFree && GetTexture( pScan )->CanRelocate() )
{
pScan = pScan->pNext;
i--;
}
if ( !pScan || !pScan->bFree )
{
break;
}
pEnd = pScan;
}
*ppStart = pStart;
*ppEnd = pEnd;
return ( pStart != pEnd );
}
THFreeBlock_t *CompactExpansionList( THInfo_t *pStart, THInfo_t *pEnd )
{
// X360TBD:
Assert( 0 );
return NULL;
#if 0
#ifdef TH_PARANOID
Validate();
#endif
StrongAssert( pStart->bFree );
StrongAssert( pEnd->bFree );
byte *pNextBlock = (byte *)pStart;
THInfo_t *pTextureBlock = pStart;
THInfo_t *pLastBlock = pStart->pPrev;
while ( pTextureBlock != pEnd )
{
CXboxTexture *pTexture = GetTexture( pTextureBlock );
// If it's a texture, move it and thread it on. Otherwise, discard it
if ( pTexture )
{
void *pTextureBits = GetD3DTextureBasePtr( pTexture );
int nBytes = pTextureBlock->nBytes;
if ( pNextBlock + nBytes <= pTextureBits)
{
memcpy( pNextBlock, pTextureBits, nBytes );
}
else
{
memmove( pNextBlock, pTextureBits, nBytes );
}
pTexture->Data = 0;
pTexture->Register( pNextBlock );
pNextBlock += nBytes;
if ( pLastBlock)
{
pLastBlock->pNext = pTextureBlock;
}
pTextureBlock->pPrev = pLastBlock;
pLastBlock = pTextureBlock;
}
else
{
StrongAssert( pTextureBlock->bFree );
RemoveFromFreeList( GetFreeBlock( pTextureBlock ) );
}
pTextureBlock = pTextureBlock->pNext;
}
RemoveFromFreeList( GetFreeBlock( pEnd ) );
// Make a new block and fix up the block lists
THFreeBlock_t *pFreeBlock = (THFreeBlock_t *)pNextBlock;
pFreeBlock->heapInfo.pPrev = pLastBlock;
pLastBlock->pNext = &pFreeBlock->heapInfo;
pFreeBlock->heapInfo.pNext = pEnd->pNext;
if ( pEnd->pNext )
{
pEnd->pNext->pPrev = &pFreeBlock->heapInfo;
}
pFreeBlock->heapInfo.bFree = true;
pFreeBlock->heapInfo.nBytes = ( (byte *)pEnd - pNextBlock ) + pEnd->nBytes;
AddToFreeList( pFreeBlock );
#ifdef TH_PARANOID
Validate();
#endif
return pFreeBlock;
#endif
}
THFreeBlock_t *ExpandBlock( THFreeBlock_t *pFreeBlock, int depth = 1 )
{
THInfo_t *pStart;
THInfo_t *pEnd;
if ( GetExpansionList( pFreeBlock, &pStart, &pEnd, depth ) )
{
return CompactExpansionList( pStart, pEnd );
}
return pFreeBlock;
}
THFreeBlock_t *ExpandBlockToFit( THFreeBlock_t *pFreeBlock, unsigned bytes )
{
if ( pFreeBlock )
{
THInfo_t *pStart;
THInfo_t *pEnd;
if ( GetExpansionList( pFreeBlock, &pStart, &pEnd, 2 ) )
{
unsigned sum = 0;
THInfo_t *pCurrent = pStart;
while( pCurrent != pEnd->pNext )
{
if ( pCurrent->bFree )
{
sum += pCurrent->nBytes;
}
pCurrent = pCurrent->pNext;
}
if ( sum >= bytes )
{
pFreeBlock = CompactExpansionList( pStart, pEnd );
}
}
}
return pFreeBlock;
}
void *ExpandToFindBlock( THInfo_t *pInfo )
{
THFreeBlock_t *pFreeBlock = ExpandBlockToFit( GetLastFree(), pInfo->nBytes );
if ( pFreeBlock && pFreeBlock->heapInfo.nBytes >= pInfo->nBytes )
{
return ClaimBlock( pFreeBlock, pInfo );
}
return NULL;
}
void Compact()
{
if ( m_nNonTextureAllocs > 0 )
{
return;
}
for (;;)
{
THFreeBlock_t *pCurrent = m_pFirstFree;
THFreeBlock_t *pNew;
while ( pCurrent )
{
int nBytesOld = pCurrent->heapInfo.nBytes;
pNew = ExpandBlock( pCurrent, 999999 );
if ( pNew != pCurrent || pNew->heapInfo.nBytes != nBytesOld )
{
#ifdef TH_PARANOID
Validate();
#endif
break;
}
#ifdef TH_PARANOID
pNew = ExpandBlock( pCurrent, 999999 );
StrongAssert( pNew == pCurrent && pNew->heapInfo.nBytes == nBytesOld );
#endif
pCurrent = pCurrent->pNextFree;
}
if ( !pCurrent )
{
break;
}
}
}
void Validate()
{
if ( !m_pFirstFree )
{
return;
}
if ( m_nNonTextureAllocs > 0 )
{
return;
}
THInfo_t *pLast = NULL;
THInfo_t *pInfo = &m_pFirstFree->heapInfo;
while ( pInfo->pPrev )
{
pInfo = pInfo->pPrev;
}
void *pNextExpectedAddress = m_pBase;
while ( pInfo )
{
byte *pCurrentAddress = (byte *)(( pInfo->bFree ) ? GetFreeBlock( pInfo ) : GetD3DTextureBasePtr( GetTexture( pInfo ) ) );
StrongAssert( pCurrentAddress == pNextExpectedAddress );
StrongAssert( pInfo->pPrev == pLast );
pNextExpectedAddress = pCurrentAddress + pInfo->nBytes;
pLast = pInfo;
pInfo = pInfo->pNext;
}
THFreeBlock_t *pFree = m_pFirstFree;
THFreeBlock_t *pLastFree = NULL;
int nBytesHeap;
nBytesHeap = XPhysicalSize( m_pBase );
while ( pFree )
{
StrongAssert( pFree->pPrevFree == pLastFree );
StrongAssert( (void *)pFree >= m_pBase && (void *)pFree < (byte *)m_pBase + nBytesHeap );
StrongAssert( !pFree->pPrevFree || ( (void *)pFree->pPrevFree >= m_pBase && (void *)pFree->pPrevFree < (byte *)m_pBase + nBytesHeap ) );
StrongAssert( !pFree->pNextFree || ( (void *)pFree->pNextFree >= m_pBase && (void *)pFree->pNextFree < (byte *)m_pBase + nBytesHeap ) );
StrongAssert( !pFree->pPrevFree || pFree->pPrevFree->heapInfo.nBytes <= pFree->heapInfo.nBytes );
pLastFree = pFree;
pFree = pFree->pNextFree;
}
}
//-----------------------------------------------------
THFreeBlock_t *m_pFirstFree;
THFreeBlock_t *m_pLastFree;
void *m_pBase;
int m_nLogicalBytes;
int m_nActualBytes;
int m_nAllocs;
int m_nOldBytes;
int m_nNonTextureAllocs;
int m_nBytesTotal;
};
CMixedTextureHeap g_MixedTextureHeap;
CAlignedMemPool< 1024*1024, D3DTEXTURE_ALIGNMENT, BASEPOOL1024_SIZE, CD3DPoolAllocator > g_TextureBasePool_1024;
CAlignedMemPool< 512*1024, D3DTEXTURE_ALIGNMENT, BASEPOOL512_SIZE, CD3DPoolAllocator > g_TextureBasePool_512;
CAlignedMemPool< 256*1024, D3DTEXTURE_ALIGNMENT, BASEPOOL256_SIZE, CD3DPoolAllocator > g_TextureBasePool_256;
CAlignedMemPool< 128*1024, D3DTEXTURE_ALIGNMENT, BASEPOOL128_SIZE, CD3DPoolAllocator > g_TextureBasePool_128;
CAlignedMemPool< 64*1024, D3DTEXTURE_ALIGNMENT, BASEPOOL64_SIZE, CD3DPoolAllocator > g_TextureBasePool_64;
CAlignedMemPool< 32*1024, D3DTEXTURE_ALIGNMENT, BASEPOOL32_SIZE, CD3DPoolAllocator > g_TextureBasePool_32;
CTextureHeap g_TextureHeap;
//-----------------------------------------------------------------------------
// Resolve texture parameters into sizes and allocator
//-----------------------------------------------------------------------------
TextureAllocator_t TextureParamsToAllocator( int width, int height, int levels, DWORD usage, D3DFORMAT d3dFormat, unsigned int *pBaseSize, unsigned int *pMipSize )
{
// determine texture component sizes
XGSetTextureHeaderEx(
width,
height,
levels,
usage,
d3dFormat,
0,
0,
0,
0,
0,
NULL,
pBaseSize,
pMipSize );
// based on "Xbox 360 Texture Storage"
// can truncate the terminal level due to using tiled and packed tails
// the terminal level must be at 32x32 or 16x16 packed
if ( width == height && levels != 0 )
{
unsigned int nReduction = 0;
int terminalWidth = width >> (levels - 1);
if ( d3dFormat == D3DFMT_DXT1 )
{
if ( terminalWidth <= 32 )
{
nReduction = 4*1024;
}
}
else if ( d3dFormat == D3DFMT_DXT5 )
{
if ( terminalWidth == 32 )
{
nReduction = 8*1024;
}
else if ( terminalWidth <= 16 )
{
nReduction = 12*1024;
}
}
if ( levels == 1 )
{
*pBaseSize -= nReduction;
}
else
{
*pMipSize -= nReduction;
}
}
if ( UseBasePoolsForStreaming() )
{
if ( *pMipSize && *pBaseSize >= 32*1024 )
{
if ( *pBaseSize <= 32*1024 )
{
return TA_BASEPOOL_32;
}
if ( *pBaseSize <= 64*1024 )
{
return TA_BASEPOOL_64;
}
if ( *pBaseSize <= 128*1024 )
{
return TA_BASEPOOL_128;
}
if ( *pBaseSize <= 256*1024 )
{
return TA_BASEPOOL_256;
}
if ( *pBaseSize <= 512*1024 && g_TextureBasePool_512.BytesTotal() )
{
// only allow the pool if the pool has been warmed (proper conditions cause init)
return TA_BASEPOOL_512;
}
if ( *pBaseSize <= 1024*1024 && g_TextureBasePool_1024.BytesTotal() )
{
// only allow the pool if the pool has been warmed (proper conditions cause init)
return TA_BASEPOOL_1024;
}
}
}
return TA_STANDARD;
}
CON_COMMAND( texture_heap_stats, "" )
{
Msg( "Texture heap stats:\n" );
int nActualTotal = 0;
for ( int i = 0; i < TA_MAX; i++ )
{
const char *pName = "???";
int nAllocated = 0;
int nSize = 0;
int nTotal = 0;
switch ( i )
{
case TA_BASEPOOL_1024:
pName = "Mip0 - 1024K";
nAllocated = g_TextureBasePool_1024.NumAllocated();
nSize = g_TextureBasePool_1024.BytesAllocated();
nTotal = g_TextureBasePool_1024.BytesTotal();
break;
case TA_BASEPOOL_512:
pName = "Mip0 - 512K";
nAllocated = g_TextureBasePool_512.NumAllocated();
nSize = g_TextureBasePool_512.BytesAllocated();
nTotal = g_TextureBasePool_512.BytesTotal();
break;
case TA_BASEPOOL_256:
pName = "Mip0 - 256K";
nAllocated = g_TextureBasePool_256.NumAllocated();
nSize = g_TextureBasePool_256.BytesAllocated();
nTotal = g_TextureBasePool_256.BytesTotal();
break;
case TA_BASEPOOL_128:
pName = "Mip0 - 128K";
nAllocated = g_TextureBasePool_128.NumAllocated();
nSize = g_TextureBasePool_128.BytesAllocated();
nTotal = g_TextureBasePool_128.BytesTotal();
break;
case TA_BASEPOOL_64:
pName = "Mip0 - 64K";
nAllocated = g_TextureBasePool_64.NumAllocated();
nSize = g_TextureBasePool_64.BytesAllocated();
nTotal = g_TextureBasePool_64.BytesTotal();
break;
case TA_BASEPOOL_32:
pName = "Mip0 - 32K";
nAllocated = g_TextureBasePool_32.NumAllocated();
nSize = g_TextureBasePool_32.BytesAllocated();
nTotal = g_TextureBasePool_32.BytesTotal();
break;
case TA_MIXED:
continue;
case TA_STANDARD:
pName = "Standard";
nAllocated = CD3DStandardAllocator::GetAllocations();
nSize = CD3DStandardAllocator::GetSize();
nTotal = nSize;
break;
}
Msg( "Pool:%-12s Allocations:%4d Used:%6.2f MB Total:%6.2fMB\n", pName, nAllocated, nSize/( 1024.0f * 1024.0f ), nTotal/( 1024.0f * 1024.0f ) );
nActualTotal += nTotal;
}
Msg( "Total: %.2f MB\n", nActualTotal/( 1024.0f * 1024.0f ) );
if ( !UseStandardAllocator() )
{
Msg( "Mixed textures: %dk/%dk allocated in %d textures\n", g_MixedTextureHeap.m_nLogicalBytes/1024, g_MixedTextureHeap.m_nActualBytes/1024, g_MixedTextureHeap.m_nAllocs );
float oldFootprint, newFootprint;
oldFootprint = g_MixedTextureHeap.m_nOldBytes;
newFootprint = g_MixedTextureHeap.m_nActualBytes;
Msg( "\n Old: %.3fmb, New: %.3fmb\n", oldFootprint / (1024.0*1024.0), newFootprint / (1024.0*1024.0) );
}
}
CON_COMMAND( texture_heap_compact, "" )
{
if ( UseStandardAllocator() )
{
return;
}
Msg( "Validating mixed texture heap...\n" );
g_MixedTextureHeap.Validate();
Msg( "Compacting mixed texture heap...\n" );
unsigned int oldLargest, newLargest;
oldLargest = ( g_MixedTextureHeap.GetLastFree() ) ? g_MixedTextureHeap.GetLastFree()->heapInfo.nBytes : 0;
g_MixedTextureHeap.Compact();
newLargest = ( g_MixedTextureHeap.GetLastFree() ) ? g_MixedTextureHeap.GetLastFree()->heapInfo.nBytes : 0;
Msg( "\n Old largest block: %.3fk, New largest block: %.3fk\n\n", oldLargest / 1024.0, newLargest / 1024.0 );
Msg( "Validating mixed texture heap...\n" );
g_MixedTextureHeap.Validate();
Msg( "Done.\n" );
}
CON_COMMAND( texture_heap_flushlru, "" )
{
g_TextureHeap.FlushTextureCache();
}
CON_COMMAND( texture_heap_spewlru, "" )
{
g_TextureHeap.SpewTextureCache();
}
void CompactTextureHeap()
{
unsigned oldLargest, newLargest;
oldLargest = ( g_MixedTextureHeap.GetLastFree() ) ? g_MixedTextureHeap.GetLastFree()->heapInfo.nBytes : 0;
g_MixedTextureHeap.Compact();
newLargest = ( g_MixedTextureHeap.GetLastFree() ) ? g_MixedTextureHeap.GetLastFree()->heapInfo.nBytes : 0;
DevMsg( "Compacted texture heap. Old largest block: %.3fk, New largest block: %.3fk\n", oldLargest / 1024.0, newLargest / 1024.0 );
}
//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
CTextureHeap::CTextureHeap()
{
}
//-----------------------------------------------------------------------------
// Return the allocator used to create the texture
//-----------------------------------------------------------------------------
inline TextureAllocator_t GetTextureAllocator( IDirect3DBaseTexture9 *pTexture )
{
return ( pTexture->GetType() == D3DRTYPE_CUBETEXTURE ) ? (( CXboxCubeTexture *)pTexture)->m_fAllocator : (( CXboxTexture *)pTexture)->m_fAllocator;
}
//-----------------------------------------------------------------------------
// Build and alloc a texture resource
//-----------------------------------------------------------------------------
IDirect3DTexture *CTextureHeap::AllocTexture( int width, int height, int levels, DWORD usage, D3DFORMAT d3dFormat, bool bNoD3DMemory, bool bCacheable )
{
#if defined( SUPPORTS_TEXTURE_STREAMING )
static bool bInitializedPools;
if ( !bInitializedPools )
{
// Warm the pools, now once
// Pools that should not exist due to state, will inhibit any further potential latent use
bInitializedPools = true;
if ( !g_pFullFileSystem->IsDVDHosted() )
{
MEM_ALLOC_CREDIT_( __FILE__ ": Base, Pooled D3D" );
bool bNo1024 = CommandLine()->FindParm( "-no1024" ) && !CommandLine()->FindParm( "-allow1024" );
if ( !bNo1024 )
{
g_TextureBasePool_1024.Free( g_TextureBasePool_1024.Alloc() );
g_TextureBasePool_512.Free( g_TextureBasePool_512.Alloc() );
}
g_TextureBasePool_256.Free( g_TextureBasePool_256.Alloc() );
g_TextureBasePool_128.Free( g_TextureBasePool_128.Alloc() );
g_TextureBasePool_64.Free( g_TextureBasePool_64.Alloc() );
g_TextureBasePool_32.Free( g_TextureBasePool_32.Alloc() );
}
}
#endif
// determine allocator and texture component sizes
unsigned int nBaseSize;
unsigned int nMipSize;
TextureAllocator_t nAllocator = TextureParamsToAllocator( width, height, levels, usage, d3dFormat, &nBaseSize, &nMipSize );
if ( bCacheable && nAllocator == TA_STANDARD )
{
// texture doesn't meet the cacheing guidelines
bCacheable = false;
}
int mipOffset;
if ( !bCacheable )
{
// a non-cacheable texture is setup with a contiguous base
nAllocator = TA_STANDARD;
mipOffset = XGHEADER_CONTIGUOUS_MIP_OFFSET;
nBaseSize += nMipSize;
nMipSize = 0;
}
else
{
// a cacheable texture has a non-contiguous base and mips
mipOffset = 0;
Assert( nBaseSize && nMipSize );
}
void *pBaseBuffer = NULL;
void *pMipBuffer = NULL;
if ( !bNoD3DMemory )
{
if ( !bCacheable )
{
MEM_ALLOC_CREDIT_( __FILE__ ": Standard D3D" );
// the base and mips are contiguous
// the base is mandatory
pBaseBuffer = CD3DStandardAllocator::Alloc( nBaseSize );
if ( !pBaseBuffer )
{
// shouldn't happen, out of memory
return NULL;
}
}
else
{
{
MEM_ALLOC_CREDIT_( __FILE__ ": Base, Pooled D3D" );
// base goes into its own seperate pool seperate from mips
// pools may be full, a failed pool allocation is permissible
switch ( nAllocator )
{
case TA_BASEPOOL_1024:
pBaseBuffer = g_TextureBasePool_1024.Alloc();
break;
case TA_BASEPOOL_512:
pBaseBuffer = g_TextureBasePool_512.Alloc();
break;
case TA_BASEPOOL_256:
pBaseBuffer = g_TextureBasePool_256.Alloc();
break;
case TA_BASEPOOL_128:
pBaseBuffer = g_TextureBasePool_128.Alloc();
break;
case TA_BASEPOOL_64:
pBaseBuffer = g_TextureBasePool_64.Alloc();
break;
case TA_BASEPOOL_32:
pBaseBuffer = g_TextureBasePool_32.Alloc();
break;
}
}
{
MEM_ALLOC_CREDIT_( __FILE__ ": Mips, Standard D3D" );
// the mips go elsewhere
// the mips are mandatory
pMipBuffer = CD3DStandardAllocator::Alloc( nMipSize );
if ( !pMipBuffer )
{
// shouldn't happen, out of memory
return NULL;
}
}
}
}
MEM_ALLOC_CREDIT_( __FILE__ ": Texture Headers" );
CXboxTexture* pXboxTexture = new CXboxTexture;
XGSetTextureHeaderEx(
width,
height,
levels,
usage,
d3dFormat,
0,
0,
0,
mipOffset,
0,
(IDirect3DTexture*)pXboxTexture,
NULL,
NULL );
pXboxTexture->m_fAllocator = nAllocator;
pXboxTexture->m_nBaseSize = nBaseSize;
pXboxTexture->m_nMipSize = nMipSize;
pXboxTexture->m_bBaseAllocated = ( pBaseBuffer != NULL );
pXboxTexture->m_bMipAllocated = ( pMipBuffer != NULL );
// assuming that a texture that allocates now is using the synchronous loader
// and is about to blit textures
pXboxTexture->m_BaseValid = pXboxTexture->m_bBaseAllocated;
if ( bCacheable )
{
pXboxTexture->m_tcHandle = m_TextureCache.AddToTail( (IDirect3DTexture*)pXboxTexture );
}
if ( !bNoD3DMemory )
{
if ( !bCacheable )
{
// non cacheable texture, base and mip are contiguous
SetD3DTextureBasePtr( (IDirect3DTexture*)pXboxTexture, pBaseBuffer );
// retrieve offset and fixup
void *pMipOffset = GetD3DTextureMipPtr( (IDirect3DTexture*)pXboxTexture );
if ( pMipOffset )
{
SetD3DTextureMipPtr( (IDirect3DTexture*)pXboxTexture, (unsigned char *)pBaseBuffer + (unsigned int)pMipOffset );
}
}
else
{
// cacheable texture, base may or may not be allocated now
if ( !pBaseBuffer )
{
// d3d error checking requires we stuff a valid, but bogus base pointer
// the base pointer gets properly set when this cacheable texture is touched
SetD3DTextureBasePtr( (IDirect3DTexture*)pXboxTexture, pMipBuffer );
SetD3DTextureMipPtr( (IDirect3DTexture*)pXboxTexture, pMipBuffer );
}
else
{
SetD3DTextureBasePtr( (IDirect3DTexture*)pXboxTexture, pBaseBuffer );
SetD3DTextureMipPtr( (IDirect3DTexture*)pXboxTexture, pMipBuffer );
}
}
}
return (IDirect3DTexture*)pXboxTexture;
}
//-----------------------------------------------------------------------------
// Build and alloc a cube texture resource
//-----------------------------------------------------------------------------
IDirect3DCubeTexture *CTextureHeap::AllocCubeTexture( int width, int levels, DWORD usage, D3DFORMAT d3dFormat, bool bNoD3DMemory )
{
MEM_ALLOC_CREDIT_( __FILE__ ": Texture Headers" );
CXboxCubeTexture* pXboxCubeTexture = new CXboxCubeTexture;
// create a cube texture with contiguous mips and packed tails
DWORD dwTextureSize = XGSetCubeTextureHeaderEx(
width,
levels,
usage,
d3dFormat,
0,
0,
0,
XGHEADER_CONTIGUOUS_MIP_OFFSET,
(IDirect3DCubeTexture*)pXboxCubeTexture,
NULL,
NULL );
pXboxCubeTexture->m_fAllocator = TA_STANDARD;
pXboxCubeTexture->m_nBaseSize = dwTextureSize;
pXboxCubeTexture->m_bBaseAllocated = ( bNoD3DMemory == false );
if ( bNoD3DMemory )
{
return (IDirect3DCubeTexture*)pXboxCubeTexture;
}
void *pBits;
{
MEM_ALLOC_CREDIT_( __FILE__ ": Cubemap, Standard D3D" );
pBits = CD3DStandardAllocator::Alloc( dwTextureSize );
if ( !pBits )
{
delete pXboxCubeTexture;
return NULL;
}
}
// base and mips are contiguous
SetD3DTextureBasePtr( (IDirect3DTexture*)pXboxCubeTexture, pBits );
// retrieve offset and fixup
void *pMipOffset = GetD3DTextureMipPtr( (IDirect3DTexture*)pXboxCubeTexture );
if ( pMipOffset )
{
SetD3DTextureMipPtr( (IDirect3DTexture*)pXboxCubeTexture, (unsigned char *)pBits + (unsigned int)pMipOffset );
}
return (IDirect3DCubeTexture*)pXboxCubeTexture;
}
//-----------------------------------------------------------------------------
// Allocate an Volume Texture
//-----------------------------------------------------------------------------
IDirect3DVolumeTexture *CTextureHeap::AllocVolumeTexture( int width, int height, int depth, int levels, DWORD usage, D3DFORMAT d3dFormat )
{
MEM_ALLOC_CREDIT_( __FILE__ ": Texture Headers" );
CXboxVolumeTexture *pXboxVolumeTexture = new CXboxVolumeTexture;
// create a cube texture with contiguous mips and packed tails
DWORD dwTextureSize = XGSetVolumeTextureHeaderEx(
width,
height,
depth,
levels,
usage,
d3dFormat,
0,
0,
0,
XGHEADER_CONTIGUOUS_MIP_OFFSET,
(IDirect3DVolumeTexture *)pXboxVolumeTexture,
NULL,
NULL );
void *pBits;
{
MEM_ALLOC_CREDIT_( __FILE__ ": Volume, Standard D3D" );
pBits = CD3DStandardAllocator::Alloc( dwTextureSize );
if ( !pBits )
{
delete pXboxVolumeTexture;
return NULL;
}
}
pXboxVolumeTexture->m_fAllocator = TA_STANDARD;
pXboxVolumeTexture->m_nBaseSize = dwTextureSize;
pXboxVolumeTexture->m_bBaseAllocated = true;
// base and mips are contiguous
SetD3DTextureBasePtr( (IDirect3DTexture*)pXboxVolumeTexture, pBits );
// retrieve offset and fixup
void *pMipOffset = GetD3DTextureMipPtr( (IDirect3DTexture*)pXboxVolumeTexture );
if ( pMipOffset )
{
SetD3DTextureMipPtr( (IDirect3DTexture*)pXboxVolumeTexture, (unsigned char *)pBits + (unsigned int)pMipOffset );
}
return pXboxVolumeTexture;
}
//-----------------------------------------------------------------------------
// Get current backbuffer multisample type (used in AllocRenderTargetSurface() )
//-----------------------------------------------------------------------------
D3DMULTISAMPLE_TYPE CTextureHeap::GetBackBufferMultiSampleType()
{
int backWidth, backHeight;
ShaderAPI()->GetBackBufferDimensions( backWidth, backHeight );
// 2xMSAA at 640x480 and 848x480 are the only supported multisample mode on 360 (2xMSAA for 720p would
// use predicated tiling, which would require a rewrite of *all* our render target code)
// FIXME: shuffle the EDRAM surfaces to allow 4xMSAA for standard def
// (they would overlap & trash each other with the current allocation scheme)
D3DMULTISAMPLE_TYPE backBufferMultiSampleType = g_pShaderDevice->IsAAEnabled() ? D3DMULTISAMPLE_2_SAMPLES : D3DMULTISAMPLE_NONE;
Assert( ( g_pShaderDevice->IsAAEnabled() == false ) || (backHeight == 480) );
return backBufferMultiSampleType;
}
//-----------------------------------------------------------------------------
// Allocate an EDRAM surface
//-----------------------------------------------------------------------------
IDirect3DSurface *CTextureHeap::AllocRenderTargetSurface( int width, int height, D3DFORMAT d3dFormat, RTMultiSampleCount360_t multiSampleCount, int base )
{
// render target surfaces don't need to exist simultaneously
// force their allocations to overlap at the end of back buffer and zbuffer
// this should leave 3MB (of 10) free assuming 1280x720 (and 5MB with 640x480@2xMSAA)
D3DMULTISAMPLE_TYPE backBufferMultiSampleType = GetBackBufferMultiSampleType();
D3DMULTISAMPLE_TYPE multiSampleType = D3DMULTISAMPLE_NONE;
switch ( multiSampleCount )
{
case RT_MULTISAMPLE_MATCH_BACKBUFFER:
multiSampleType = backBufferMultiSampleType;
break;
case RT_MULTISAMPLE_NONE:
multiSampleType = D3DMULTISAMPLE_NONE;
break;
case RT_MULTISAMPLE_2_SAMPLES:
multiSampleType = D3DMULTISAMPLE_2_SAMPLES;
break;
case RT_MULTISAMPLE_4_SAMPLES:
multiSampleType = D3DMULTISAMPLE_4_SAMPLES;
break;
default:
Assert( !"Invalid multisample count" );
multiSampleType = D3DMULTISAMPLE_NONE;
}
if ( base < 0 )
{
int backWidth, backHeight;
ShaderAPI()->GetBackBufferDimensions( backWidth, backHeight );
D3DFORMAT backBufferFormat = ImageLoader::ImageFormatToD3DFormat( g_pShaderDevice->GetBackBufferFormat() );
// this is the size of back+depthbuffer in EDRAM
base = 2*XGSurfaceSize( backWidth, backHeight, backBufferFormat, backBufferMultiSampleType );
}
D3DSURFACE_PARAMETERS surfParameters;
V_memset( &surfParameters, 0, sizeof( surfParameters ) );
surfParameters.Base = base;
surfParameters.ColorExpBias = 0;
surfParameters.HiZFunc = D3DHIZFUNC_DEFAULT;
if ( ( d3dFormat == D3DFMT_D24FS8 ) || ( d3dFormat == D3DFMT_D24S8 ) || ( d3dFormat == D3DFMT_D16 ) )
{
surfParameters.HierarchicalZBase = 0;
if ( ( surfParameters.HierarchicalZBase + XGHierarchicalZSize( width, height, multiSampleType ) ) > GPU_HIERARCHICAL_Z_TILES )
{
// overflow, can't hold the tiles so disable
surfParameters.HierarchicalZBase = 0xFFFFFFFF;
}
}
else
{
// not using
surfParameters.HierarchicalZBase = 0xFFFFFFFF;
}
HRESULT hr;
IDirect3DSurface9 *pSurface = NULL;
hr = Dx9Device()->CreateRenderTarget( width, height, d3dFormat, multiSampleType, 0, FALSE, &pSurface, &surfParameters );
Assert( !FAILED( hr ) );
return pSurface;
}
//-----------------------------------------------------------------------------
// Perform the real d3d allocation, returns true if succesful, false otherwise.
// Only valid for a texture created with no d3d bits, otherwise no-op.
//-----------------------------------------------------------------------------
bool CTextureHeap::FixupAllocD3DMemory( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return false;
}
if ( pD3DTexture->GetType() == D3DRTYPE_SURFACE )
{
// there are no d3d bits for a surface
return false;
}
if ( GetD3DTextureBasePtr( pD3DTexture ) )
{
// already allocated
return true;
}
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
if ( ((CXboxTexture *)pD3DTexture)->m_bMipAllocated )
{
// cacheable texture has already been setup
return true;
}
int nAllocator = ((CXboxTexture *)pD3DTexture)->m_fAllocator;
unsigned int nBaseSize = ((CXboxTexture *)pD3DTexture)->m_nBaseSize;
unsigned int nMipSize = ((CXboxTexture *)pD3DTexture)->m_nMipSize;
bool bCacheable = ( nAllocator != TA_STANDARD );
void *pBaseBuffer = NULL;
void *pMipBuffer = NULL;
if ( !bCacheable )
{
MEM_ALLOC_CREDIT_( __FILE__ ": Standard D3D" );
// base and mips are contiguous
Assert( nBaseSize && nMipSize == 0 );
pBaseBuffer = CD3DStandardAllocator::Alloc( nBaseSize );
if ( !pBaseBuffer )
{
// base is required, out of memory
return false;
}
}
else
{
{
MEM_ALLOC_CREDIT_( __FILE__ ": Mips, Standard D3D" );
// base and mips are non-contiguous
Assert( nBaseSize && nMipSize );
pMipBuffer = CD3DStandardAllocator::Alloc( nMipSize );
if ( !pMipBuffer )
{
// mips are required, out of memory
return false;
}
}
{
MEM_ALLOC_CREDIT_( __FILE__ ": Base, Pooled D3D" );
// base goes into its own seperate pool seperate from mips
// pools my be filled, failure is allowed
switch ( nAllocator )
{
case TA_BASEPOOL_1024:
pBaseBuffer = g_TextureBasePool_1024.Alloc();
break;
case TA_BASEPOOL_512:
pBaseBuffer = g_TextureBasePool_512.Alloc();
break;
case TA_BASEPOOL_256:
pBaseBuffer = g_TextureBasePool_256.Alloc();
break;
case TA_BASEPOOL_128:
pBaseBuffer = g_TextureBasePool_128.Alloc();
break;
case TA_BASEPOOL_64:
pBaseBuffer = g_TextureBasePool_64.Alloc();
break;
case TA_BASEPOOL_32:
pBaseBuffer = g_TextureBasePool_32.Alloc();
break;
}
}
}
((CXboxTexture *)pD3DTexture)->m_bBaseAllocated = ( pBaseBuffer != NULL );
((CXboxTexture *)pD3DTexture)->m_bMipAllocated = ( pMipBuffer != NULL );
if ( !bCacheable )
{
// non cacheable texture, base and mip are contiguous
SetD3DTextureBasePtr( pD3DTexture, pBaseBuffer );
// retrieve offset and fixup
void *pMipOffset = GetD3DTextureMipPtr( pD3DTexture );
if ( pMipOffset )
{
SetD3DTextureMipPtr( pD3DTexture, (unsigned char *)pBaseBuffer + (unsigned int)pMipOffset );
}
// the async queued loader is about to blit textures, so mark valid for render
((CXboxTexture *)pD3DTexture)->m_BaseValid = 1;
}
else
{
// cacheable texture, base may or may not be allocated now
if ( !pBaseBuffer )
{
// d3d error checking requires we stuff a valid, but bogus base pointer
// the base pointer gets properly set when this cacheable texture is touched
SetD3DTextureBasePtr( pD3DTexture, pMipBuffer );
SetD3DTextureMipPtr( pD3DTexture, pMipBuffer );
}
else
{
SetD3DTextureBasePtr( pD3DTexture, pBaseBuffer );
SetD3DTextureMipPtr( pD3DTexture, pMipBuffer );
// the async queued loader is about to blit textures, so mark valid for render
((CXboxTexture *)pD3DTexture)->m_BaseValid = 1;
}
}
return true;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_CUBETEXTURE )
{
MEM_ALLOC_CREDIT_( __FILE__ ": Cubemap, Standard D3D" );
void *pBaseBuffer = CD3DStandardAllocator::Alloc( ((CXboxCubeTexture *)pD3DTexture)->m_nBaseSize );
if ( !pBaseBuffer )
{
// base is required, out of memory
return false;
}
SetD3DTextureBasePtr( pD3DTexture, pBaseBuffer );
// retrieve offset and fixup
void *pMipOffset = GetD3DTextureMipPtr( pD3DTexture );
if ( pMipOffset )
{
SetD3DTextureMipPtr( pD3DTexture, (unsigned char *)pBaseBuffer + (unsigned int)pMipOffset );
}
((CXboxCubeTexture *)pD3DTexture)->m_bBaseAllocated = true;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
// Release the allocated store
//-----------------------------------------------------------------------------
void CTextureHeap::FreeTexture( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return;
}
if ( pD3DTexture->GetType() == D3DRTYPE_SURFACE )
{
// texture heap doesn't own render target surfaces
// allow callers to call through for less higher level detection
int ref = ((IDirect3DSurface*)pD3DTexture)->Release();
Assert( ref == 0 );
ref = ref;
return;
}
if ( IsBaseAllocated( pD3DTexture ) )
{
byte *pBaseBits = (byte *)GetD3DTextureBasePtr( pD3DTexture );
if ( pBaseBits )
{
switch ( GetTextureAllocator( pD3DTexture ) )
{
case TA_BASEPOOL_1024:
g_TextureBasePool_1024.Free( pBaseBits );
break;
case TA_BASEPOOL_512:
g_TextureBasePool_512.Free( pBaseBits );
break;
case TA_BASEPOOL_256:
g_TextureBasePool_256.Free( pBaseBits );
break;
case TA_BASEPOOL_128:
g_TextureBasePool_128.Free( pBaseBits );
break;
case TA_BASEPOOL_64:
g_TextureBasePool_64.Free( pBaseBits );
break;
case TA_BASEPOOL_32:
g_TextureBasePool_32.Free( pBaseBits );
break;
case TA_STANDARD:
CD3DStandardAllocator::Free( pBaseBits );
break;
case TA_MIXED:
g_MixedTextureHeap.Free( pBaseBits, ((CXboxTexture *)pD3DTexture) );
break;
}
}
}
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
if ( ((CXboxTexture *)pD3DTexture)->m_tcHandle != INVALID_TEXTURECACHE_HANDLE )
{
m_TextureCache.Remove( ((CXboxTexture *)pD3DTexture)->m_tcHandle );
}
if ( ((CXboxTexture *)pD3DTexture)->m_bMipAllocated )
{
// not an offset, but a true pointer
void *pMipBits = GetD3DTextureMipPtr( pD3DTexture );
if ( pMipBits )
{
CD3DStandardAllocator::Free( pMipBits );
}
}
delete (CXboxTexture *)pD3DTexture;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_VOLUMETEXTURE )
{
delete (CXboxVolumeTexture *)pD3DTexture;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_CUBETEXTURE )
{
delete (CXboxCubeTexture *)pD3DTexture;
}
}
//-----------------------------------------------------------------------------
// Returns the allocated footprint.
//-----------------------------------------------------------------------------
int CTextureHeap::GetSize( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return 0;
}
if ( pD3DTexture->GetType() == D3DRTYPE_SURFACE )
{
D3DSURFACE_DESC surfaceDesc;
HRESULT hr = ((IDirect3DSurface*)pD3DTexture)->GetDesc( &surfaceDesc );
Assert( !FAILED( hr ) );
hr = hr;
int size = ImageLoader::GetMemRequired(
surfaceDesc.Width,
surfaceDesc.Height,
0,
ImageLoader::D3DFormatToImageFormat( surfaceDesc.Format ),
false );
return size;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
return ((CXboxTexture *)pD3DTexture)->m_nBaseSize + ((CXboxTexture *)pD3DTexture)->m_nMipSize;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_CUBETEXTURE )
{
return ((CXboxCubeTexture *)pD3DTexture)->m_nBaseSize;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_VOLUMETEXTURE )
{
return ((CXboxVolumeTexture *)pD3DTexture)->m_nBaseSize;
}
return 0;
}
//-----------------------------------------------------------------------------
// Returns the amount of memory needed just for the cacheable component.
//-----------------------------------------------------------------------------
int CTextureHeap::GetCacheableSize( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return 0;
}
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE && ((CXboxTexture *)pD3DTexture)->m_fAllocator != TA_STANDARD )
{
// the base is the cacheable component
return ((CXboxTexture *)pD3DTexture)->m_nBaseSize;
}
return 0;
}
//-----------------------------------------------------------------------------
// Crunch the pools
//-----------------------------------------------------------------------------
void CTextureHeap::Compact()
{
g_MixedTextureHeap.Compact();
}
//-----------------------------------------------------------------------------
// Query to determine if texture was setup for cacheing.
//-----------------------------------------------------------------------------
bool CTextureHeap::IsTextureCacheManaged( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return false;
}
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
return ((CXboxTexture *)pD3DTexture)->m_tcHandle != INVALID_TEXTURECACHE_HANDLE;
}
return false;
}
//-----------------------------------------------------------------------------
// Query to determine if texture has an allocated base.
//-----------------------------------------------------------------------------
bool CTextureHeap::IsBaseAllocated( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return false;
}
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
return ((CXboxTexture *)pD3DTexture)->m_bBaseAllocated;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_CUBETEXTURE )
{
return ((CXboxCubeTexture *)pD3DTexture)->m_bBaseAllocated;
}
else if ( pD3DTexture->GetType() == D3DRTYPE_VOLUMETEXTURE )
{
return ((CXboxVolumeTexture *)pD3DTexture)->m_bBaseAllocated;
}
return true;
}
//-----------------------------------------------------------------------------
// Query to determine if texture is valid for hi-res rendering.
//-----------------------------------------------------------------------------
bool CTextureHeap::IsTextureResident( IDirect3DBaseTexture *pD3DTexture )
{
if ( !pD3DTexture )
{
return false;
}
// only the simple texture type streams and can be evicted
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
return ( ((CXboxTexture *)pD3DTexture)->m_BaseValid != 0 );
}
// all other types, cube, volume, are defined as resident
return true;
}
//-----------------------------------------------------------------------------
// Used for debugging purposes only!!! Forceful eviction. Can cause system lockups
// under repeated use due to desired forced behavior and ignoring GPU.
//-----------------------------------------------------------------------------
void CTextureHeap::FlushTextureCache()
{
TextureCacheHandle_t tcHandle;
for ( tcHandle = m_TextureCache.Head(); tcHandle != m_TextureCache.InvalidIndex(); tcHandle = m_TextureCache.Next( tcHandle ) )
{
CXboxTexture *pPurgeCandidate = (CXboxTexture *)m_TextureCache[tcHandle];
if ( !pPurgeCandidate->m_BaseValid )
{
continue;
}
byte *pBaseBits = (byte *)GetD3DTextureBasePtr( pPurgeCandidate );
Assert( pBaseBits );
if ( pBaseBits )
{
switch ( pPurgeCandidate->m_fAllocator )
{
case TA_BASEPOOL_1024:
g_TextureBasePool_1024.Free( pBaseBits );
break;
case TA_BASEPOOL_512:
g_TextureBasePool_512.Free( pBaseBits );
break;
case TA_BASEPOOL_256:
g_TextureBasePool_256.Free( pBaseBits );
break;
case TA_BASEPOOL_128:
g_TextureBasePool_128.Free( pBaseBits );
break;
case TA_BASEPOOL_64:
g_TextureBasePool_64.Free( pBaseBits );
break;
case TA_BASEPOOL_32:
g_TextureBasePool_32.Free( pBaseBits );
break;
}
}
pPurgeCandidate->m_bBaseAllocated = false;
pPurgeCandidate->m_BaseValid = 0;
}
}
//-----------------------------------------------------------------------------
// Computation job to do work after IO, runs callback
//-----------------------------------------------------------------------------
static void IOComputationJob( IDirect3DBaseTexture *pD3DTexture, void *pData, int nDataSize, TextureLoadError_t loaderError )
{
CXboxTexture *pXboxTexture = (CXboxTexture *)pD3DTexture;
if ( texture_heap_debug.GetInt() == THD_SPEW )
{
char szFilename[MAX_PATH];
g_pFullFileSystem->String( pXboxTexture->m_hFilename, szFilename, sizeof( szFilename ) );
Msg( "Arrived: size:%d thread:0x%8.8x %s\n", nDataSize, ThreadGetCurrentId(), szFilename );
}
if ( texture_heap_debug.GetInt() == THD_COLORIZE_AFTERIO )
{
// colorize the base to use during the i/o latency
V_memset( GetD3DTextureBasePtr( pD3DTexture ), BASE_COLOR_AFTER, pXboxTexture->m_nBaseSize );
}
else if ( pData && nDataSize )
{
// get a unique vtf and mount texture
// vtf can expect non-volatile buffer data to be stable through vtf lifetime
// this prevents redundant copious amounts of image memory transfers
IVTFTexture *pVTFTexture = CreateVTFTexture();
CUtlBuffer vtfBuffer;
vtfBuffer.SetExternalBuffer( (void *)pData, nDataSize, nDataSize );
if ( pVTFTexture->UnserializeFromBuffer( vtfBuffer, false, false, false, 0 ) )
{
// provided vtf buffer is all mips, determine top mip due to possible picmip
int mipWidth, mipHeight, mipDepth;
pVTFTexture->ComputeMipLevelDimensions( pXboxTexture->m_nMipSkipCount, &mipWidth, &mipHeight, &mipDepth );
// blit the hi-res texture bits into d3d memory
unsigned char *pSourceBits = pVTFTexture->ImageData( 0, 0, pXboxTexture->m_nMipSkipCount, 0, 0, 0 );
TextureLoadInfo_t info;
info.m_TextureHandle = INVALID_SHADERAPI_TEXTURE_HANDLE;
info.m_pTexture = pD3DTexture;
info.m_nLevel = 0;
info.m_nCopy = 0;
info.m_CubeFaceID = (D3DCUBEMAP_FACES)0;
info.m_nWidth = mipWidth;
info.m_nHeight = mipHeight;
info.m_nZOffset = 0;
info.m_SrcFormat = pVTFTexture->Format();
info.m_pSrcData = pSourceBits;
info.m_bSrcIsTiled = pVTFTexture->IsPreTiled();
info.m_bCanConvertFormat = false;
LoadTexture( info );
}
if ( pVTFTexture )
{
DestroyVTFTexture( pVTFTexture );
}
}
if ( pData )
{
g_pFullFileSystem->FreeOptimalReadBuffer( pData );
}
g_pFullFileSystem->AsyncRelease( pXboxTexture->m_hAsyncControl );
pXboxTexture->m_hAsyncControl = NULL;
// ready for render
pXboxTexture->m_BaseValid = 1;
}
//-----------------------------------------------------------------------------
// Callback from I/O job thread. Purposely lightweight as possible to keep i/o from stalling.
//-----------------------------------------------------------------------------
static void IOAsyncCallback( const FileAsyncRequest_t &asyncRequest, int numReadBytes, FSAsyncStatus_t asyncStatus )
{
IDirect3DBaseTexture *pD3DTexture = (IDirect3DBaseTexture *)asyncRequest.pContext;
// interpret the async error
TextureLoadError_t loaderError;
switch ( asyncStatus )
{
case FSASYNC_OK:
loaderError = TEXLOADERROR_NONE;
break;
case FSASYNC_ERR_FILEOPEN:
loaderError = TEXLOADERROR_FILEOPEN;
break;
default:
loaderError = TEXLOADERROR_READING;
}
// have data or error, do callback as a computation job
CJob *pComputationJob = new CFunctorJob( CreateFunctor( IOComputationJob, pD3DTexture, asyncRequest.pData, numReadBytes, loaderError ) );
pComputationJob->SetServiceThread( 1 );
pComputationJob->SetFlags( pComputationJob->GetFlags() | JF_QUEUE );
g_pThreadPool->AddJob( pComputationJob );
pComputationJob->Release();
}
//-----------------------------------------------------------------------------
// Attempts to restore a cacheable texture. An async i/o operation may be kicked
// off.
//-----------------------------------------------------------------------------
bool CTextureHeap::RestoreCacheableTexture( IDirect3DBaseTexture *pD3DTexture )
{
static unsigned int s_failedAllocator[TA_MAX];
unsigned int nFrameCount = g_pShaderAPIDX8->GetCurrentFrameCounter();
CXboxTexture *pXboxTexture = (CXboxTexture *)pD3DTexture;
if ( s_failedAllocator[pXboxTexture->m_fAllocator] == nFrameCount )
{
// allocator has already failed an eviction this frame
// avoid costly pointless search, retry next frame
return false;
}
void *pBaseBuffer = NULL;
TextureCacheHandle_t tcHandle = m_TextureCache.Head();
int numAttempts = 0;
while ( numAttempts < 2 )
{
{
MEM_ALLOC_CREDIT_( __FILE__ ": Base, Pooled D3D" );
switch ( pXboxTexture->m_fAllocator )
{
case TA_BASEPOOL_1024:
pBaseBuffer = g_TextureBasePool_1024.Alloc();
break;
case TA_BASEPOOL_512:
pBaseBuffer = g_TextureBasePool_512.Alloc();
break;
case TA_BASEPOOL_256:
pBaseBuffer = g_TextureBasePool_256.Alloc();
break;
case TA_BASEPOOL_128:
pBaseBuffer = g_TextureBasePool_128.Alloc();
break;
case TA_BASEPOOL_64:
pBaseBuffer = g_TextureBasePool_64.Alloc();
break;
case TA_BASEPOOL_32:
pBaseBuffer = g_TextureBasePool_32.Alloc();
break;
}
}
if ( pBaseBuffer )
{
// found memory!
break;
}
// squeeze lru for memory
for ( ; tcHandle != m_TextureCache.InvalidIndex(); tcHandle = m_TextureCache.Next( tcHandle ) )
{
if ( tcHandle == pXboxTexture->m_tcHandle )
{
// skip self
continue;
}
CXboxTexture *pPurgeCandidate = (CXboxTexture *)m_TextureCache[tcHandle];
if ( !pPurgeCandidate->m_BaseValid ||
pPurgeCandidate->m_fAllocator != pXboxTexture->m_fAllocator ||
pPurgeCandidate->m_nFrameCount >= nFrameCount-1 )
{
// only allowing eviction from the expected pool
// using frame counter as the cheapest method to cull GPU busy resources
continue;
}
byte *pBaseBits = (byte *)GetD3DTextureBasePtr( pPurgeCandidate );
Assert( pBaseBits );
if ( pBaseBits )
{
switch ( pPurgeCandidate->m_fAllocator )
{
case TA_BASEPOOL_1024:
g_TextureBasePool_1024.Free( pBaseBits );
break;
case TA_BASEPOOL_512:
g_TextureBasePool_512.Free( pBaseBits );
break;
case TA_BASEPOOL_256:
g_TextureBasePool_256.Free( pBaseBits );
break;
case TA_BASEPOOL_128:
g_TextureBasePool_128.Free( pBaseBits );
break;
case TA_BASEPOOL_64:
g_TextureBasePool_64.Free( pBaseBits );
break;
case TA_BASEPOOL_32:
g_TextureBasePool_32.Free( pBaseBits );
break;
}
}
pPurgeCandidate->m_bBaseAllocated = false;
pPurgeCandidate->m_BaseValid = 0;
if ( texture_heap_debug.GetInt() == THD_SPEW )
{
char szFilename[MAX_PATH];
g_pFullFileSystem->String( pXboxTexture->m_hFilename, szFilename, sizeof( szFilename ) );
Msg( "Evicted: %s\n", szFilename );
}
// retry allocation
break;
}
numAttempts++;
}
if ( !pBaseBuffer )
{
// no eviction occured
// mark which allocator failed
s_failedAllocator[pXboxTexture->m_fAllocator] = nFrameCount;
return false;
}
pXboxTexture->m_bBaseAllocated = true;
SetD3DTextureBasePtr( pD3DTexture, pBaseBuffer );
// setup i/o
char szFilename[MAX_PATH];
g_pFullFileSystem->String( pXboxTexture->m_hFilename, szFilename, sizeof( szFilename ) );
if ( texture_heap_debug.GetInt() == THD_COLORIZE_BEFOREIO || texture_heap_debug.GetInt() == THD_COLORIZE_AFTERIO )
{
// colorize the base to use during the i/o latency
V_memset( pBaseBuffer, BASE_COLOR_BEFORE, pXboxTexture->m_nBaseSize );
}
if ( texture_heap_debug.GetInt() == THD_SPEW )
{
Msg( "Queued: %s\n", szFilename );
}
FileAsyncRequest_t asyncRequest;
asyncRequest.pszFilename = szFilename;
asyncRequest.priority = -1;
asyncRequest.flags = FSASYNC_FLAGS_ALLOCNOFREE;
asyncRequest.pContext = (void *)pD3DTexture;
asyncRequest.pfnCallback = IOAsyncCallback;
g_pFullFileSystem->AsyncRead( asyncRequest, &pXboxTexture->m_hAsyncControl );
return true;
}
//-----------------------------------------------------------------------------
// Moves to head of LRU. Allocates and queues for loading if evicted. Returns
// true if texture is resident, false otherwise.
//-----------------------------------------------------------------------------
bool CTextureHeap::TouchTexture( CXboxTexture *pXboxTexture )
{
bool bValid = true;
if ( pXboxTexture->m_tcHandle != INVALID_TEXTURECACHE_HANDLE )
{
// touch
m_TextureCache.LinkToTail( pXboxTexture->m_tcHandle );
bValid = ( pXboxTexture->m_BaseValid != 0 );
if ( !bValid && !pXboxTexture->m_bBaseAllocated )
{
RestoreCacheableTexture( (IDirect3DBaseTexture *)pXboxTexture );
}
if ( texture_heap_debug.GetInt() == THD_COLORIZE_BEFOREIO || texture_heap_debug.GetInt() == THD_COLORIZE_AFTERIO )
{
// debug mode allows the render to proceed before the i/o completes
bValid = pXboxTexture->m_bBaseAllocated;
}
}
return bValid;
}
//-----------------------------------------------------------------------------
// Save file info for d3d texture restore process.
//-----------------------------------------------------------------------------
void CTextureHeap::SetCacheableTextureParams( IDirect3DBaseTexture *pD3DTexture, const char *pFilename, int mipSkipCount )
{
if ( !IsTextureCacheManaged( pD3DTexture ) )
{
// wasn't setup for cacheing
return;
}
if ( pD3DTexture->GetType() == D3DRTYPE_TEXTURE )
{
// store compact absolute filename
((CXboxTexture *)pD3DTexture)->m_hFilename = g_pFullFileSystem->FindOrAddFileName( pFilename );
((CXboxTexture *)pD3DTexture)->m_nMipSkipCount = mipSkipCount;
}
}
void CTextureHeap::SpewTextureCache()
{
Msg( "LRU:\n" );
TextureCacheHandle_t tcHandle;
for ( tcHandle = m_TextureCache.Head(); tcHandle != m_TextureCache.InvalidIndex(); tcHandle = m_TextureCache.Next( tcHandle ) )
{
CXboxTexture *pXboxTexture = (CXboxTexture *)m_TextureCache[tcHandle];
char szFilename[MAX_PATH];
g_pFullFileSystem->String( pXboxTexture->m_hFilename, szFilename, sizeof( szFilename ) );
const char *pState = "???";
if ( pXboxTexture->m_BaseValid )
{
pState = "Valid";
}
else
{
if ( !pXboxTexture->m_bBaseAllocated )
{
pState = "Evicted";
}
else if ( pXboxTexture->m_hAsyncControl )
{
pState = "Loading";
}
}
Msg( "0x%8.8x (%8s) %s\n", pXboxTexture->m_nFrameCount, pState, szFilename );
}
}
//-----------------------------------------------------------------------------
// Return the total amount of memory allocated for the base pools.
//-----------------------------------------------------------------------------
int CTextureHeap::GetCacheableHeapSize()
{
int nTotal = 0;
for ( int i = 0; i < TA_MAX; i++ )
{
switch ( i )
{
case TA_BASEPOOL_1024:
nTotal += g_TextureBasePool_1024.BytesTotal();
break;
case TA_BASEPOOL_512:
nTotal += g_TextureBasePool_512.BytesTotal();
break;
case TA_BASEPOOL_256:
nTotal += g_TextureBasePool_256.BytesTotal();
break;
case TA_BASEPOOL_128:
nTotal += g_TextureBasePool_128.BytesTotal();
break;
case TA_BASEPOOL_64:
nTotal += g_TextureBasePool_64.BytesTotal();
break;
case TA_BASEPOOL_32:
nTotal += g_TextureBasePool_32.BytesTotal();
break;
default:
continue;
}
}
return nTotal;
}