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.
3106 lines
91 KiB
3106 lines
91 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//===========================================================================// |
|
|
|
#include <stdlib.h> |
|
#include <malloc.h> |
|
#include "materialsystem_global.h" |
|
#include "string.h" |
|
#include "shaderapi/ishaderapi.h" |
|
#include "materialsystem/materialsystem_config.h" |
|
#include "IHardwareConfigInternal.h" |
|
#include "texturemanager.h" |
|
#include "materialsystem/imaterialvar.h" |
|
#include "materialsystem/IColorCorrection.h" |
|
#include "tier1/strtools.h" |
|
#include "utlvector.h" |
|
#include "utldict.h" |
|
#include "itextureinternal.h" |
|
#include "vtf/vtf.h" |
|
#include "pixelwriter.h" |
|
#include "basetypes.h" |
|
#include "utlbuffer.h" |
|
#include "filesystem.h" |
|
#include "materialsystem/imesh.h" |
|
#include "materialsystem/ishaderapi.h" |
|
#include "vstdlib/random.h" |
|
#include "imorphinternal.h" |
|
#include "tier1/utlrbtree.h" |
|
#include "tier1/utlpair.h" |
|
#include "ctype.h" |
|
#include "utlqueue.h" |
|
#include "tier0/icommandline.h" |
|
#include "ctexturecompositor.h" |
|
|
|
#include "vprof_telemetry.h" |
|
|
|
// Need lightmaps access here |
|
#define MATSYS_INTERNAL |
|
#include "cmatlightmaps.h" |
|
#include "cmaterialsystem.h" |
|
#undef MATSYS_INTERNAL |
|
|
|
#include "tier0/memdbgon.h" |
|
|
|
#define ERROR_TEXTURE_SIZE 32 |
|
#define WHITE_TEXTURE_SIZE 1 |
|
#define BLACK_TEXTURE_SIZE 1 |
|
#define GREY_TEXTURE_SIZE 1 |
|
#define NORMALIZATION_CUBEMAP_SIZE 32 |
|
|
|
struct AsyncLoadJob_t; |
|
struct AsyncReadJob_t; |
|
class AsyncLoader; |
|
class AsyncReader; |
|
|
|
#define MAX_READS_OUTSTANDING 2 |
|
|
|
static ImageFormat GetImageFormatRawReadback( ImageFormat fmt ); |
|
|
|
#ifdef STAGING_ONLY |
|
static ConVar mat_texture_list_dump( "mat_texture_list_dump", "0" ); |
|
#endif |
|
|
|
const char* cTextureCachePathDir = "__texture_cache"; |
|
|
|
// TODO: Relocate this somewhere else. It works like python's "strip" function, |
|
// removing leading and trailing whitespace, including newlines. Whitespace between |
|
// non-whitespace characters is preserved. |
|
void V_StripWhitespace( char* pBuffer ) |
|
{ |
|
Assert( pBuffer ); |
|
|
|
char* pSrc = pBuffer; |
|
char* pDst = pBuffer; |
|
char* pDstFirstTrailingWhitespace = NULL; |
|
|
|
// Remove leading whitespace |
|
bool leading = true; |
|
while ( *pSrc ) |
|
{ |
|
if ( leading ) |
|
{ |
|
if ( V_isspace( *pSrc ) ) |
|
{ |
|
++pSrc; |
|
continue; |
|
} |
|
else |
|
{ |
|
leading = false; |
|
// Drop through |
|
} |
|
} |
|
|
|
if ( pDst != pSrc ) |
|
*pDst = *pSrc; |
|
|
|
if ( !leading && V_isspace( *pDst ) && pDstFirstTrailingWhitespace == NULL ) |
|
pDstFirstTrailingWhitespace = pDst; |
|
else if ( !leading && !V_isspace( *pDst ) && pDstFirstTrailingWhitespace != NULL ) |
|
pDstFirstTrailingWhitespace = NULL; |
|
|
|
++pSrc; |
|
++pDst; |
|
} |
|
|
|
(*pDst) = 0; |
|
|
|
if ( pDstFirstTrailingWhitespace ) |
|
( *pDstFirstTrailingWhitespace ) = 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Various procedural texture regeneration classes |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a checkerboard texture |
|
//----------------------------------------------------------------------------- |
|
class CCheckerboardTexture : public ITextureRegenerator |
|
{ |
|
public: |
|
CCheckerboardTexture( int nCheckerSize, color32 color1, color32 color2 ) : |
|
m_nCheckerSize( nCheckerSize ), m_Color1(color1), m_Color2(color2) |
|
{ |
|
} |
|
|
|
virtual ~CCheckerboardTexture() { } |
|
|
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) |
|
{ |
|
for (int iFrame = 0; iFrame < pVTFTexture->FrameCount(); ++iFrame ) |
|
{ |
|
for (int iFace = 0; iFace < pVTFTexture->FaceCount(); ++iFace ) |
|
{ |
|
int nWidth = pVTFTexture->Width(); |
|
int nHeight = pVTFTexture->Height(); |
|
int nDepth = pVTFTexture->Depth(); |
|
for (int z = 0; z < nDepth; ++z) |
|
{ |
|
// Fill mip 0 with a checkerboard |
|
CPixelWriter pixelWriter; |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), |
|
pVTFTexture->ImageData( iFrame, iFace, 0, 0, 0, z ), pVTFTexture->RowSizeInBytes( 0 ) ); |
|
|
|
for (int y = 0; y < nHeight; ++y) |
|
{ |
|
pixelWriter.Seek( 0, y ); |
|
for (int x = 0; x < nWidth; ++x) |
|
{ |
|
if ( ((x & m_nCheckerSize) ^ (y & m_nCheckerSize)) ^ (z & m_nCheckerSize) ) |
|
{ |
|
pixelWriter.WritePixel( m_Color1.r, m_Color1.g, m_Color1.b, m_Color1.a ); |
|
} |
|
else |
|
{ |
|
pixelWriter.WritePixel( m_Color2.r, m_Color2.g, m_Color2.b, m_Color2.a ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
virtual void Release() |
|
{ |
|
delete this; |
|
} |
|
|
|
private: |
|
int m_nCheckerSize; |
|
color32 m_Color1; |
|
color32 m_Color2; |
|
}; |
|
|
|
static void CreateCheckerboardTexture( ITextureInternal *pTexture, int nCheckerSize, color32 color1, color32 color2 ) |
|
{ |
|
ITextureRegenerator *pRegen = new CCheckerboardTexture( nCheckerSize, color1, color2 ); |
|
pTexture->SetTextureRegenerator( pRegen ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a solid texture |
|
//----------------------------------------------------------------------------- |
|
class CSolidTexture : public ITextureRegenerator |
|
{ |
|
public: |
|
CSolidTexture( color32 color ) : m_Color(color) |
|
{ |
|
} |
|
|
|
virtual ~CSolidTexture() { } |
|
|
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) |
|
{ |
|
int nMipCount = pTexture->IsMipmapped() ? pVTFTexture->MipCount() : 1; |
|
for (int iFrame = 0; iFrame < pVTFTexture->FrameCount(); ++iFrame ) |
|
{ |
|
for (int iFace = 0; iFace < pVTFTexture->FaceCount(); ++iFace ) |
|
{ |
|
for (int iMip = 0; iMip < nMipCount; ++iMip ) |
|
{ |
|
int nWidth, nHeight, nDepth; |
|
pVTFTexture->ComputeMipLevelDimensions( iMip, &nWidth, &nHeight, &nDepth ); |
|
for (int z = 0; z < nDepth; ++z) |
|
{ |
|
CPixelWriter pixelWriter; |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), |
|
pVTFTexture->ImageData( iFrame, iFace, iMip, 0, 0, z ), pVTFTexture->RowSizeInBytes( iMip ) ); |
|
|
|
for (int y = 0; y < nHeight; ++y) |
|
{ |
|
pixelWriter.Seek( 0, y ); |
|
for (int x = 0; x < nWidth; ++x) |
|
{ |
|
pixelWriter.WritePixel( m_Color.r, m_Color.g, m_Color.b, m_Color.a ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
virtual void Release() |
|
{ |
|
delete this; |
|
} |
|
|
|
private: |
|
color32 m_Color; |
|
}; |
|
|
|
static void CreateSolidTexture( ITextureInternal *pTexture, color32 color ) |
|
{ |
|
ITextureRegenerator *pRegen = new CSolidTexture( color ); |
|
pTexture->SetTextureRegenerator( pRegen ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a normalization cubemap texture |
|
//----------------------------------------------------------------------------- |
|
|
|
class CNormalizationCubemap : public ITextureRegenerator |
|
{ |
|
public: |
|
|
|
// TODO(nillerusr): broken here with togl /= (maybe here) |
|
|
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) |
|
{ |
|
// Normalization cubemap doesn't make sense on low-end hardware |
|
// So we won't construct a spheremap out of this |
|
CPixelWriter pixelWriter; |
|
|
|
Vector direction; |
|
for (int iFace = 0; iFace < 6; ++iFace) |
|
{ |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), |
|
pVTFTexture->ImageData( 0, iFace, 0 ), pVTFTexture->RowSizeInBytes( 0 ) ); |
|
|
|
int nWidth = pVTFTexture->Width(); |
|
int nHeight = pVTFTexture->Height(); |
|
|
|
float flInvWidth = 2.0f / (float)(nWidth-1); |
|
float flInvHeight = 2.0f / (float)(nHeight-1); |
|
|
|
for (int y = 0; y < nHeight; ++y) |
|
{ |
|
float v = y * flInvHeight - 1.0f; |
|
|
|
pixelWriter.Seek( 0, y ); |
|
for (int x = 0; x < nWidth; ++x) |
|
{ |
|
float u = x * flInvWidth - 1.0f; |
|
float oow = 1.0f / sqrt( 1.0f + u*u + v*v ); |
|
|
|
int ix = (int)(255.0f * 0.5f * (u*oow + 1.0f) + 0.5f); |
|
ix = clamp( ix, 0, 255 ); |
|
int iy = (int)(255.0f * 0.5f * (v*oow + 1.0f) + 0.5f); |
|
iy = clamp( iy, 0, 255 ); |
|
int iz = (int)(255.0f * 0.5f * (oow + 1.0f) + 0.5f); |
|
iz = clamp( iz, 0, 255 ); |
|
|
|
switch (iFace) |
|
{ |
|
case CUBEMAP_FACE_RIGHT: |
|
pixelWriter.WritePixel( iz, 255 - iy, 255 - ix, 255 ); |
|
break; |
|
case CUBEMAP_FACE_LEFT: |
|
pixelWriter.WritePixel( 255 - iz, 255 - iy, ix, 255 ); |
|
break; |
|
case CUBEMAP_FACE_BACK: |
|
pixelWriter.WritePixel( ix, iz, iy, 255 ); |
|
break; |
|
case CUBEMAP_FACE_FRONT: |
|
pixelWriter.WritePixel( ix, 255 - iz, 255 - iy, 255 ); |
|
break; |
|
case CUBEMAP_FACE_UP: |
|
pixelWriter.WritePixel( ix, 255 - iy, iz, 255 ); |
|
break; |
|
case CUBEMAP_FACE_DOWN: |
|
pixelWriter.WritePixel( 255 - ix, 255 - iy, 255 - iz, 255 ); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
// NOTE: The normalization cubemap regenerator is stateless |
|
// so there's no need to allocate + deallocate them |
|
virtual void Release() {} |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a normalization cubemap texture |
|
//----------------------------------------------------------------------------- |
|
class CSignedNormalizationCubemap : public ITextureRegenerator |
|
{ |
|
public: |
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) |
|
{ |
|
// Normalization cubemap doesn't make sense on low-end hardware |
|
// So we won't construct a spheremap out of this |
|
CPixelWriter pixelWriter; |
|
|
|
Vector direction; |
|
for (int iFace = 0; iFace < 6; ++iFace) |
|
{ |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), |
|
pVTFTexture->ImageData( 0, iFace, 0 ), pVTFTexture->RowSizeInBytes( 0 ) ); |
|
|
|
int nWidth = pVTFTexture->Width(); |
|
int nHeight = pVTFTexture->Height(); |
|
|
|
float flInvWidth = 2.0f / (float)(nWidth-1); |
|
float flInvHeight = 2.0f / (float)(nHeight-1); |
|
|
|
for (int y = 0; y < nHeight; ++y) |
|
{ |
|
float v = y * flInvHeight - 1.0f; |
|
|
|
pixelWriter.Seek( 0, y ); |
|
for (int x = 0; x < nWidth; ++x) |
|
{ |
|
float u = x * flInvWidth - 1.0f; |
|
float oow = 1.0f / sqrt( 1.0f + u*u + v*v ); |
|
|
|
#ifdef DX_TO_GL_ABSTRACTION |
|
float flX = (255.0f * 0.5 * (u*oow + 1.0f) + 0.5f); |
|
float flY = (255.0f * 0.5 * (v*oow + 1.0f) + 0.5f); |
|
float flZ = (255.0f * 0.5 * (oow + 1.0f) + 0.5f); |
|
|
|
switch (iFace) |
|
{ |
|
case CUBEMAP_FACE_RIGHT: |
|
flX = 255.0f - flX; |
|
flY = 255.0f - flY; |
|
break; |
|
case CUBEMAP_FACE_LEFT: |
|
flY = 255.0f - flY; |
|
flZ = 255.0f - flZ; |
|
break; |
|
case CUBEMAP_FACE_BACK: |
|
break; |
|
case CUBEMAP_FACE_FRONT: |
|
flY = 255.0f - flY; |
|
flZ = 255.0f - flZ; |
|
break; |
|
case CUBEMAP_FACE_UP: |
|
flY = 255.0f - flY; |
|
break; |
|
case CUBEMAP_FACE_DOWN: |
|
flX = 255.0f - flX; |
|
flY = 255.0f - flY; |
|
flZ = 255.0f - flZ; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
flX -= 128.0f; |
|
flY -= 128.0f; |
|
flZ -= 128.0f; |
|
|
|
flX /= 128.0f; |
|
flY /= 128.0f; |
|
flZ /= 128.0f; |
|
|
|
switch ( iFace ) |
|
{ |
|
case CUBEMAP_FACE_RIGHT: |
|
pixelWriter.WritePixelF( flZ, flY, flX, 0.0f ); |
|
break; |
|
case CUBEMAP_FACE_LEFT: |
|
pixelWriter.WritePixelF( flZ, flY, flX, 0.0f ); |
|
break; |
|
case CUBEMAP_FACE_BACK: |
|
pixelWriter.WritePixelF( flX, flZ, flY, 0.0f ); |
|
break; |
|
case CUBEMAP_FACE_FRONT: |
|
pixelWriter.WritePixelF( flX, flZ, flY, 0.0f ); |
|
break; |
|
case CUBEMAP_FACE_UP: |
|
pixelWriter.WritePixelF( flX, flY, flZ, 0.0f ); |
|
break; |
|
case CUBEMAP_FACE_DOWN: |
|
pixelWriter.WritePixelF( flX, flY, flZ, 0.0f ); |
|
break; |
|
default: |
|
break; |
|
} |
|
#else |
|
int ix = (int)(255 * 0.5 * (u*oow + 1.0f) + 0.5f); |
|
ix = clamp( ix, 0, 255 ); |
|
int iy = (int)(255 * 0.5 * (v*oow + 1.0f) + 0.5f); |
|
iy = clamp( iy, 0, 255 ); |
|
int iz = (int)(255 * 0.5 * (oow + 1.0f) + 0.5f); |
|
iz = clamp( iz, 0, 255 ); |
|
|
|
switch (iFace) |
|
{ |
|
case CUBEMAP_FACE_RIGHT: |
|
ix = 255 - ix; |
|
iy = 255 - iy; |
|
break; |
|
case CUBEMAP_FACE_LEFT: |
|
iy = 255 - iy; |
|
iz = 255 - iz; |
|
break; |
|
case CUBEMAP_FACE_BACK: |
|
break; |
|
case CUBEMAP_FACE_FRONT: |
|
iy = 255 - iy; |
|
iz = 255 - iz; |
|
break; |
|
case CUBEMAP_FACE_UP: |
|
iy = 255 - iy; |
|
break; |
|
case CUBEMAP_FACE_DOWN: |
|
ix = 255 - ix; |
|
iy = 255 - iy; |
|
iz = 255 - iz; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
ix -= 128; |
|
iy -= 128; |
|
iz -= 128; |
|
|
|
Assert( ix >= -128 && ix <= 127 ); |
|
Assert( iy >= -128 && iy <= 127 ); |
|
Assert( iz >= -128 && iz <= 127 ); |
|
|
|
switch (iFace) |
|
{ |
|
case CUBEMAP_FACE_RIGHT: |
|
// correct |
|
// pixelWriter.WritePixelSigned( -128, -128, -128, 0 ); |
|
pixelWriter.WritePixelSigned( iz, iy, ix, 0 ); |
|
break; |
|
case CUBEMAP_FACE_LEFT: |
|
// correct |
|
// pixelWriter.WritePixelSigned( -128, -128, -128, 0 ); |
|
pixelWriter.WritePixelSigned( iz, iy, ix, 0 ); |
|
break; |
|
case CUBEMAP_FACE_BACK: |
|
// wrong |
|
// pixelWriter.WritePixelSigned( -128, -128, -128, 0 ); |
|
pixelWriter.WritePixelSigned( ix, iz, iy, 0 ); |
|
// pixelWriter.WritePixelSigned( -127, -127, 127, 0 ); |
|
break; |
|
case CUBEMAP_FACE_FRONT: |
|
// wrong |
|
// pixelWriter.WritePixelSigned( -128, -128, -128, 0 ); |
|
pixelWriter.WritePixelSigned( ix, iz, iy, 0 ); |
|
break; |
|
case CUBEMAP_FACE_UP: |
|
// correct |
|
// pixelWriter.WritePixelSigned( -128, -128, -128, 0 ); |
|
pixelWriter.WritePixelSigned( ix, iy, iz, 0 ); |
|
break; |
|
case CUBEMAP_FACE_DOWN: |
|
// correct |
|
// pixelWriter.WritePixelSigned( -128, -128, -128, 0 ); |
|
pixelWriter.WritePixelSigned( ix, iy, iz, 0 ); |
|
break; |
|
default: |
|
break; |
|
} |
|
#endif |
|
} // x |
|
} // y |
|
} // iFace |
|
} |
|
|
|
// NOTE: The normalization cubemap regenerator is stateless |
|
// so there's no need to allocate + deallocate them |
|
virtual void Release() {} |
|
}; |
|
|
|
static void CreateNormalizationCubemap( ITextureInternal *pTexture ) |
|
{ |
|
// NOTE: The normalization cubemap regenerator is stateless |
|
// so there's no need to allocate + deallocate them |
|
static CNormalizationCubemap s_NormalizationCubemap; |
|
pTexture->SetTextureRegenerator( &s_NormalizationCubemap ); |
|
} |
|
|
|
static void CreateSignedNormalizationCubemap( ITextureInternal *pTexture ) |
|
{ |
|
// NOTE: The normalization cubemap regenerator is stateless |
|
// so there's no need to allocate + deallocate them |
|
static CSignedNormalizationCubemap s_SignedNormalizationCubemap; |
|
pTexture->SetTextureRegenerator( &s_SignedNormalizationCubemap ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a color correction texture |
|
//----------------------------------------------------------------------------- |
|
class CColorCorrectionTexture : public ITextureRegenerator |
|
{ |
|
public: |
|
CColorCorrectionTexture( ColorCorrectionHandle_t handle ) : m_ColorCorrectionHandle(handle) |
|
{ |
|
} |
|
|
|
virtual ~CColorCorrectionTexture() { } |
|
|
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pSubRect ) |
|
{ |
|
int nWidth = pVTFTexture->Width(); |
|
int nHeight = pVTFTexture->Height(); |
|
int nDepth = pVTFTexture->Depth(); |
|
Assert( nWidth == COLOR_CORRECTION_TEXTURE_SIZE && nHeight == COLOR_CORRECTION_TEXTURE_SIZE && nDepth == COLOR_CORRECTION_TEXTURE_SIZE ); |
|
|
|
for ( int z = 0; z < nDepth; ++z ) |
|
{ |
|
CPixelWriter pixelWriter; |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), |
|
pVTFTexture->ImageData( 0, 0, 0, 0, 0, z ), pVTFTexture->RowSizeInBytes( 0 ) ); |
|
|
|
for ( int y = 0; y < nHeight; ++y ) |
|
{ |
|
pixelWriter.Seek( 0, y ); |
|
for (int x = 0; x < nWidth; ++x) |
|
{ |
|
RGBX5551_t inColor; |
|
inColor.r = x; |
|
inColor.g = y; |
|
inColor.b = z; |
|
|
|
color24 col = ColorCorrectionSystem()->GetLookup( m_ColorCorrectionHandle, inColor ); |
|
pixelWriter.WritePixel( col.r, col.g, col.b, 255 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
virtual void Release() |
|
{ |
|
delete this; |
|
} |
|
|
|
private: |
|
ColorCorrectionHandle_t m_ColorCorrectionHandle; |
|
}; |
|
|
|
|
|
void CreateColorCorrectionTexture( ITextureInternal *pTexture, ColorCorrectionHandle_t handle ) |
|
{ |
|
ITextureRegenerator *pRegen = new CColorCorrectionTexture( handle ); |
|
pTexture->SetTextureRegenerator( pRegen ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Implementation of the texture manager |
|
//----------------------------------------------------------------------------- |
|
class CTextureManager : public ITextureManager |
|
{ |
|
public: |
|
CTextureManager( void ); |
|
|
|
// Initialization + shutdown |
|
virtual void Init( int nFlags ) OVERRIDE; |
|
virtual void Shutdown(); |
|
|
|
virtual void AllocateStandardRenderTargets( ); |
|
virtual void FreeStandardRenderTargets(); |
|
|
|
virtual void CacheExternalStandardRenderTargets(); |
|
|
|
virtual ITextureInternal *CreateProceduralTexture( const char *pTextureName, const char *pTextureGroupName, int w, int h, int d, ImageFormat fmt, int nFlags, ITextureRegenerator* generator = NULL ); |
|
virtual ITextureInternal *FindOrLoadTexture( const char *textureName, const char *pTextureGroupName, int nAdditionalCreationFlags = 0 ); |
|
virtual bool IsTextureLoaded( const char *pTextureName ); |
|
|
|
virtual void AddTextureAlias( const char *pAlias, const char *pRealName ); |
|
virtual void RemoveTextureAlias( const char *pAlias ); |
|
|
|
virtual void SetExcludedTextures( const char *pScriptName ); |
|
virtual void UpdateExcludedTextures(); |
|
|
|
virtual void ResetTextureFilteringState(); |
|
void ReloadTextures( void ); |
|
|
|
// These are used when we lose our video memory due to a mode switch etc |
|
void ReleaseTextures( void ); |
|
void RestoreNonRenderTargetTextures( void ); |
|
void RestoreRenderTargets( void ); |
|
|
|
// Suspend or resume texture streaming requests |
|
void SuspendTextureStreaming( void ); |
|
void ResumeTextureStreaming( void ); |
|
|
|
// delete any texture that has a refcount <= 0 |
|
void RemoveUnusedTextures( void ); |
|
void DebugPrintUsedTextures( void ); |
|
|
|
// Request a texture ID |
|
virtual int RequestNextTextureID(); |
|
|
|
// Get at a couple standard textures |
|
virtual ITextureInternal *ErrorTexture(); |
|
virtual ITextureInternal *NormalizationCubemap(); |
|
virtual ITextureInternal *SignedNormalizationCubemap(); |
|
virtual ITextureInternal *ShadowNoise2D(); |
|
virtual ITextureInternal *IdentityLightWarp(); |
|
virtual ITextureInternal *ColorCorrectionTexture( int i ); |
|
virtual ITextureInternal *FullFrameDepthTexture(); |
|
virtual ITextureInternal *DebugLuxels2D(); |
|
|
|
|
|
// Generates an error texture pattern |
|
virtual void GenerateErrorTexture( ITexture *pTexture, IVTFTexture *pVTFTexture ); |
|
|
|
// Updates the color correction state |
|
virtual void SetColorCorrectionTexture( int i, ITextureInternal *pTexture ); |
|
|
|
virtual void ForceAllTexturesIntoHardware( void ); |
|
|
|
virtual ITextureInternal *CreateRenderTargetTexture( |
|
const char *pRTName, // NULL for auto-generated name |
|
int w, |
|
int h, |
|
RenderTargetSizeMode_t sizeMode, |
|
ImageFormat fmt, |
|
RenderTargetType_t type, |
|
unsigned int textureFlags, |
|
unsigned int renderTargetFlags ); |
|
|
|
virtual bool HasPendingTextureDestroys() const; |
|
virtual void MarkUnreferencedTextureForCleanup( ITextureInternal *pTexture ); |
|
virtual void RemoveTexture( ITextureInternal *pTexture ); |
|
virtual void ReloadFilesInList( IFileList *pFilesToReload ); |
|
|
|
// start with -1, list terminates with -1 |
|
virtual int FindNext( int iIndex, ITextureInternal **ppTexture ); |
|
|
|
virtual void ReleaseTempRenderTargetBits( void ); |
|
|
|
// Called once per frame by material system "somewhere." |
|
virtual void Update(); |
|
|
|
// Load a texture asynchronously and then call the provided callback. |
|
virtual void AsyncFindOrLoadTexture( const char *pTextureName, const char *pTextureGroupName, IAsyncTextureOperationReceiver* pRecipient, void* pExtraArgs, bool bComplain, int nAdditionalCreationFlags ); |
|
void CompleteAsyncLoad( AsyncLoadJob_t* pJob ); |
|
|
|
virtual void AsyncCreateTextureFromRenderTarget( ITexture* pSrcRt, const char* pDstName, ImageFormat dstFmt, bool bGenMips, int nAdditionalCreationFlags, IAsyncTextureOperationReceiver* pRecipient, void* pExtraArgs ); |
|
void CompleteAsyncRead( AsyncReadJob_t* pJob ); |
|
|
|
ITextureInternal* AcquireReadbackTexture( int w, int h, ImageFormat fmt ); |
|
void ReleaseReadbackTexture( ITextureInternal* pTex ); |
|
|
|
void WarmTextureCache(); |
|
void CoolTextureCache(); |
|
|
|
virtual void RequestAllMipmaps( ITextureInternal* pTex ); |
|
virtual void EvictAllTextures(); |
|
virtual void UpdatePostAsync(); |
|
|
|
virtual void ReleaseAsyncScratchVTF( IVTFTexture* pScratchVTF ); |
|
|
|
virtual bool ThreadInAsyncLoadThread() const; |
|
virtual bool ThreadInAsyncReadThread() const; |
|
|
|
virtual bool AddTextureCompositorTemplate( const char* pName, KeyValues* pTmplDesc ) OVERRIDE; |
|
virtual bool VerifyTextureCompositorTemplates() OVERRIDE; |
|
|
|
virtual CTextureCompositorTemplate* FindTextureCompositorTemplate( const char* pName ) OVERRIDE; |
|
|
|
protected: |
|
ITextureInternal *FindTexture( const char *textureName ); |
|
ITextureInternal *LoadTexture( const char *textureName, const char *pTextureGroupName, int nAdditionalCreationFlags = 0, bool bDownload = true ); |
|
|
|
void AsyncLoad( const AsyncLoadJob_t& job ); |
|
void AsyncReadTexture( AsyncReadJob_t* job ); |
|
|
|
// Restores a single texture |
|
void RestoreTexture( ITextureInternal* pTex ); |
|
|
|
void CleanupPossiblyUnreferencedTextures(); |
|
|
|
#ifdef STAGING_ONLY |
|
void DumpTextureList( ); |
|
#endif |
|
|
|
void FindFilesToLoad( CUtlDict< int >* pOutFilesToLoad, const char* pFilename ); |
|
void ReadFilesToLoad( CUtlDict< int >* pOutFilesToLoad, const char* pFilename ); |
|
|
|
CUtlDict< ITextureInternal *, unsigned short > m_TextureList; |
|
CUtlDict< const char *, unsigned short > m_TextureAliases; |
|
CUtlDict< int, unsigned short > m_TextureExcludes; |
|
CUtlDict< CCopyableUtlVector<AsyncLoadJob_t> > m_PendingAsyncLoads; |
|
CUtlVector< ITextureInternal* > m_ReadbackTextures; |
|
CUtlVector< ITextureInternal* > m_preloadedTextures; |
|
CUtlMap< ITextureInternal*, int > m_textureStreamingRequests; |
|
CTSQueue< ITextureInternal* > m_asyncStreamingRequests; |
|
CTSQueue< ITextureInternal * > m_PossiblyUnreferencedTextures; |
|
|
|
CUtlDict< CTextureCompositorTemplate *, unsigned short > m_TexCompTemplates; |
|
|
|
|
|
int m_iNextTexID; |
|
int m_nFlags; |
|
|
|
ITextureInternal *m_pErrorTexture; |
|
ITextureInternal *m_pBlackTexture; |
|
ITextureInternal *m_pWhiteTexture; |
|
ITextureInternal *m_pGreyTexture; |
|
ITextureInternal *m_pGreyAlphaZeroTexture; |
|
ITextureInternal *m_pNormalizationCubemap; |
|
ITextureInternal *m_pFullScreenTexture; |
|
ITextureInternal *m_pSignedNormalizationCubemap; |
|
ITextureInternal *m_pShadowNoise2D; |
|
ITextureInternal *m_pIdentityLightWarp; |
|
ITextureInternal *m_pColorCorrectionTextures[ COLOR_CORRECTION_MAX_TEXTURES ]; |
|
ITextureInternal *m_pFullScreenDepthTexture; |
|
ITextureInternal *m_pDebugLuxels2D; |
|
|
|
// Used to generate various error texture patterns when necessary |
|
CCheckerboardTexture *m_pErrorRegen; |
|
|
|
friend class AsyncLoader; |
|
AsyncLoader* m_pAsyncLoader; |
|
|
|
friend class AsyncReader; |
|
AsyncReader* m_pAsyncReader; |
|
|
|
uint m_nAsyncLoadThread; |
|
uint m_nAsyncReadThread; |
|
|
|
int m_iSuspendTextureStreaming; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Singleton instance |
|
//----------------------------------------------------------------------------- |
|
static CTextureManager s_TextureManager; |
|
ITextureManager *g_pTextureManager = &s_TextureManager; |
|
|
|
struct AsyncLoadJob_t |
|
{ |
|
CUtlString m_TextureName; |
|
CUtlString m_TextureGroupName; |
|
IAsyncTextureOperationReceiver* m_pRecipient; |
|
void* m_pExtraArgs; |
|
bool m_bComplain; |
|
int m_nAdditionalCreationFlags; |
|
ITextureInternal* m_pResultData; |
|
|
|
AsyncLoadJob_t() |
|
: m_pRecipient( NULL ) |
|
, m_pExtraArgs( NULL ) |
|
, m_bComplain( false ) |
|
, m_nAdditionalCreationFlags( 0 ) |
|
, m_pResultData( NULL ) |
|
{ } |
|
|
|
AsyncLoadJob_t( const char *pTextureName, const char *pTextureGroupName, IAsyncTextureOperationReceiver* pRecipient, void* pExtraArgs, bool bComplain, int nAdditionalCreationFlags ) |
|
: m_TextureName( pTextureName ) |
|
, m_TextureGroupName( pTextureGroupName ) |
|
, m_pRecipient( pRecipient ) |
|
, m_pExtraArgs( pExtraArgs ) |
|
, m_bComplain( bComplain ) |
|
, m_nAdditionalCreationFlags( nAdditionalCreationFlags ) |
|
, m_pResultData( NULL ) |
|
{ |
|
|
|
} |
|
}; |
|
|
|
|
|
class CAsyncCopyRequest : public IAsyncTextureOperationReceiver |
|
{ |
|
public: |
|
CAsyncCopyRequest() |
|
: m_nReferenceCount( 0 ) |
|
, m_bSignalled( false ) |
|
{ } |
|
|
|
virtual ~CAsyncCopyRequest() { } |
|
|
|
virtual int AddRef() OVERRIDE{ return ++m_nReferenceCount; } |
|
virtual int Release() OVERRIDE |
|
{ |
|
int retVal = --m_nReferenceCount; |
|
if ( retVal == 0 ) |
|
delete this; |
|
|
|
return retVal; |
|
} |
|
|
|
virtual int GetRefCount() const OVERRIDE{ return m_nReferenceCount; } |
|
|
|
virtual void OnAsyncCreateComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { } |
|
virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { } |
|
virtual void OnAsyncMapComplete( ITexture* pTex, void* pExtraArgs, void* pMemory, int nPitch ) OVERRIDE { } |
|
virtual void OnAsyncReadbackBegin( ITexture* pDst, ITexture* pSrc, void* pExtraArgs ) OVERRIDE |
|
{ |
|
m_bSignalled = true; |
|
} |
|
|
|
bool IsSignalled() const { return m_bSignalled; } |
|
|
|
private: |
|
CInterlockedInt m_nReferenceCount; |
|
volatile bool m_bSignalled; |
|
}; |
|
|
|
class CAsyncMapResult : public IAsyncTextureOperationReceiver |
|
{ |
|
public: |
|
CAsyncMapResult( ITextureInternal* pTex ) |
|
: m_pTexToMap( pTex ) |
|
, m_nReferenceCount( 0 ) |
|
, m_pMemory( NULL ) |
|
, m_nPitch( 0 ) |
|
, m_bSignalled( false ) |
|
{ } |
|
|
|
virtual ~CAsyncMapResult() { } |
|
|
|
virtual int AddRef() OVERRIDE { return ++m_nReferenceCount; } |
|
virtual int Release() OVERRIDE |
|
{ |
|
int retVal = --m_nReferenceCount; |
|
if ( retVal == 0 ) |
|
delete this; |
|
|
|
return retVal; |
|
} |
|
|
|
virtual int GetRefCount() const OVERRIDE{ return m_nReferenceCount; } |
|
|
|
virtual void OnAsyncCreateComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { } |
|
virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { } |
|
virtual void OnAsyncMapComplete( ITexture* pTex, void* pExtraArgs, void* pMemory, int nPitch ) OVERRIDE |
|
{ |
|
Assert( pTex == m_pTexToMap ); |
|
m_pMemory = pMemory; |
|
m_nPitch = nPitch; |
|
m_bSignalled = true; |
|
} |
|
|
|
virtual void OnAsyncReadbackBegin( ITexture* pDst, ITexture* pSrc, void* pExtraArgs ) OVERRIDE { } |
|
|
|
|
|
bool IsSignalled() const { return m_bSignalled; } |
|
|
|
ITextureInternal* const m_pTexToMap; |
|
CInterlockedInt m_nReferenceCount; |
|
volatile void* m_pMemory; |
|
volatile int m_nPitch; |
|
|
|
private: |
|
volatile bool m_bSignalled; |
|
}; |
|
|
|
struct AsyncReadJob_t |
|
{ |
|
ITexture* m_pSrcRt; |
|
ITextureInternal* m_pSysmemTex; |
|
CAsyncCopyRequest* m_pAsyncRead; |
|
CAsyncMapResult* m_pAsyncMap; |
|
const char* m_pDstName; |
|
ImageFormat m_dstFmt; |
|
bool m_bGenMips; |
|
int m_nAdditionalCreationFlags; |
|
IAsyncTextureOperationReceiver* m_pRecipient; |
|
void* m_pExtraArgs; |
|
|
|
CUtlMemory<unsigned char> m_finalTexelData; |
|
|
|
AsyncReadJob_t() |
|
: m_pSrcRt( NULL ) |
|
, m_pSysmemTex( NULL ) |
|
, m_pAsyncRead( NULL ) |
|
, m_pAsyncMap( NULL ) |
|
, m_pDstName( NULL ) |
|
, m_dstFmt( IMAGE_FORMAT_UNKNOWN ) |
|
, m_bGenMips( false ) |
|
, m_nAdditionalCreationFlags( 0 ) |
|
, m_pRecipient( NULL ) |
|
, m_pExtraArgs( NULL ) |
|
{ } |
|
|
|
AsyncReadJob_t( ITexture* pSrcRt, const char* pDstName, ImageFormat dstFmt, bool bGenMips, int nAdditionalCreationFlags, IAsyncTextureOperationReceiver* pRecipient, void* pExtraArgs ) |
|
: m_pSrcRt( pSrcRt ) |
|
, m_pSysmemTex( NULL ) |
|
, m_pAsyncRead( NULL ) |
|
, m_pAsyncMap( NULL ) |
|
, m_pDstName( pDstName ) // We take ownership of this string. |
|
, m_dstFmt( dstFmt ) |
|
, m_bGenMips( bGenMips ) |
|
, m_nAdditionalCreationFlags( nAdditionalCreationFlags ) |
|
, m_pRecipient( pRecipient ) |
|
, m_pExtraArgs( pExtraArgs ) |
|
{ |
|
|
|
} |
|
|
|
~AsyncReadJob_t() |
|
{ |
|
Assert( ThreadInMainThread() ); |
|
|
|
delete [] m_pDstName; |
|
|
|
SafeRelease( &m_pRecipient ); |
|
|
|
if ( m_pSysmemTex ) |
|
{ |
|
if ( m_pAsyncMap ) |
|
{ |
|
extern CMaterialSystem g_MaterialSystem; |
|
g_MaterialSystem.GetRenderContextInternal()->AsyncUnmap( m_pSysmemTex ); |
|
} |
|
|
|
assert_cast< CTextureManager* >( g_pTextureManager )->ReleaseReadbackTexture( m_pSysmemTex ); |
|
m_pSysmemTex = NULL; |
|
} |
|
|
|
SafeRelease( &m_pAsyncMap ); |
|
} |
|
|
|
}; |
|
|
|
bool IsJobCancelled( AsyncReadJob_t* pJob ) |
|
{ |
|
Assert( pJob != NULL ); |
|
|
|
// The texture manager holds a reference to the object, so if we're the only one who is holding a ref |
|
// then the job has been abandoned. This gives us the opportunity to cleanup and skip some work. |
|
if ( pJob->m_pRecipient->GetRefCount() == 1 ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool IsJobCancelled( AsyncLoadJob_t* pJob ) |
|
{ |
|
Assert( pJob != NULL ); |
|
|
|
// The texture manager holds a reference to the object, so if we're the only one who is holding a ref |
|
// then the job has been abandoned. This gives us the opportunity to cleanup and skip some work. |
|
if ( pJob->m_pRecipient->GetRefCount() == 1 ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Functions can be called from any thread, unless they are prefixed with a thread name. |
|
class AsyncLoader |
|
{ |
|
public: |
|
AsyncLoader() |
|
: m_bQuit( false ) |
|
{ |
|
for ( int i = 0; i < MAX_READS_OUTSTANDING; ++i ) |
|
{ |
|
m_asyncScratchVTFs.PushItem( CreateVTFTexture() ); |
|
} |
|
|
|
// Do this after everything else. |
|
m_LoaderThread = CreateSimpleThread( AsyncLoader::LoaderMain, this ); |
|
} |
|
|
|
~AsyncLoader() |
|
{ |
|
Assert( m_asyncScratchVTFs.Count() == MAX_READS_OUTSTANDING ); |
|
while ( m_asyncScratchVTFs.Count() > 0 ) |
|
{ |
|
IVTFTexture* pScratchVTF = NULL; |
|
m_asyncScratchVTFs.PopItem( &pScratchVTF ); |
|
delete pScratchVTF; |
|
} |
|
} |
|
|
|
void AsyncLoad( const AsyncLoadJob_t& job ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
// TODO: This could be made faster by keeping a pool of these things. |
|
m_pendingJobs.PushItem( new AsyncLoadJob_t( job ) ); |
|
} |
|
|
|
void Shutdown() |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
m_bQuit = true; |
|
ThreadJoin( m_LoaderThread ); |
|
} |
|
|
|
void ThreadMain_Update() |
|
{ |
|
Assert( ThreadInMainThread() ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
AsyncLoadJob_t *pJob = NULL; |
|
if ( m_completedJobs.PopItem( &pJob ) ) |
|
{ |
|
Assert( pJob != NULL ); |
|
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - CompleteAsyncLoad", __FUNCTION__ ); |
|
// Complete the load, then make the callback. |
|
assert_cast< CTextureManager* >( g_pTextureManager )->CompleteAsyncLoad( pJob ); |
|
delete pJob; |
|
pJob = NULL; |
|
} |
|
} |
|
|
|
void ReleaseAsyncReadBuffer( IVTFTexture *pScratchVTF ) |
|
{ |
|
Assert( pScratchVTF != NULL ); |
|
m_asyncScratchVTFs.PushItem( pScratchVTF ); |
|
} |
|
|
|
private: |
|
inline bool ThreadInLoaderThread() |
|
{ |
|
return s_TextureManager.ThreadInAsyncLoadThread(); |
|
} |
|
|
|
void ThreadLoader_Main( ) |
|
{ |
|
Assert( ThreadInLoaderThread() ); |
|
|
|
while ( !m_bQuit ) |
|
{ |
|
AsyncLoadJob_t *pJob = NULL; |
|
IVTFTexture *pScratchVTF = NULL; |
|
while ( !m_pendingJobs.PopItem( &pJob ) ) |
|
{ |
|
// "awhile" |
|
ThreadSleep( 8 ); |
|
if ( m_bQuit ) |
|
return; |
|
} |
|
Assert( pJob != NULL ); |
|
|
|
while ( !m_asyncScratchVTFs.PopItem( &pScratchVTF ) ) |
|
{ |
|
// Also awhile, but not as long.. |
|
ThreadSleep( 4 ); |
|
if ( m_bQuit ) |
|
return; |
|
} |
|
Assert( pScratchVTF != NULL ); |
|
|
|
ThreadLoader_ProcessLoad( pJob, pScratchVTF ); |
|
} |
|
} |
|
|
|
void ThreadLoader_ProcessLoad( AsyncLoadJob_t *pJob, IVTFTexture* pScratchVTF ) |
|
{ |
|
Assert( ThreadInLoaderThread() ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
Assert( pJob->m_pResultData ); |
|
|
|
if ( !pJob->m_pResultData->AsyncReadTextureFromFile( pScratchVTF, pJob->m_nAdditionalCreationFlags ) ) |
|
m_asyncScratchVTFs.PushItem( pScratchVTF ); |
|
|
|
m_completedJobs.PushItem( pJob ); |
|
} |
|
|
|
static unsigned LoaderMain( void* _this ) |
|
{ |
|
ThreadSetDebugName( "Loader" ); |
|
|
|
s_TextureManager.m_nAsyncLoadThread = ThreadGetCurrentId(); |
|
( ( AsyncLoader* )_this )->ThreadLoader_Main(); |
|
s_TextureManager.m_nAsyncLoadThread = 0xFFFFFFFF; |
|
return 0; |
|
} |
|
|
|
ThreadHandle_t m_LoaderThread; |
|
volatile bool m_bQuit; |
|
|
|
CTSQueue< AsyncLoadJob_t *> m_pendingJobs; |
|
CTSQueue< AsyncLoadJob_t *> m_completedJobs; |
|
CTSQueue< IVTFTexture *> m_asyncScratchVTFs; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Functions can be called from any thread, unless they are prefixed with a thread name. |
|
class AsyncReader |
|
{ |
|
public: |
|
AsyncReader() |
|
: m_bQuit( false ) |
|
{ |
|
|
|
// Do this after everything else. |
|
m_HelperThread = CreateSimpleThread( AsyncReader::ReaderMain, this ); |
|
} |
|
|
|
void AsyncReadback( AsyncReadJob_t* job ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
m_requestedCopies.PushItem( job ); |
|
} |
|
|
|
void Shutdown() |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
m_bQuit = true; |
|
ThreadJoin( m_HelperThread ); |
|
} |
|
|
|
void ThreadMain_Update() |
|
{ |
|
Assert( ThreadInMainThread() ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
while ( !m_queuedMaps.IsEmpty() ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "CompleteMap" ); |
|
AsyncReadJob_t* pMapped = m_queuedMaps.Head(); |
|
Assert( pMapped != NULL ); |
|
{ |
|
if ( IsJobCancelled( pMapped ) ) |
|
{ |
|
// Remove the head, which is pMapped |
|
m_queuedMaps.RemoveAtHead(); |
|
delete pMapped; |
|
continue; |
|
} |
|
|
|
if ( pMapped->m_pAsyncMap->IsSignalled() ) |
|
{ |
|
if ( pMapped->m_pAsyncMap->m_pMemory != 0 && pMapped->m_pAsyncMap->m_nPitch != 0 ) |
|
{ |
|
// Stick it in the queue for the other thread to work on it. |
|
m_pendingJobs.PushItem( pMapped ); |
|
} |
|
else |
|
{ |
|
Assert( !"Failed to perform a map that shouldn't fail, need to deal with this if it ever happens." ); |
|
DevWarning( "Failed to perform a map that shouldn't fail, need to deal with this if it ever happens." ); |
|
} |
|
|
|
// Remove the head, which is pMapped |
|
m_queuedMaps.RemoveAtHead(); |
|
} |
|
|
|
// Stop as soon as we complete one, regardless of success. |
|
break; |
|
} |
|
|
|
} |
|
|
|
// This is ugly, but basically we need to do map and unmap on the main thread. Other |
|
// stuff can (mostly) happen on the async thread |
|
while ( !m_queuedReads.IsEmpty() ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "CompleteQueuedRead" ); |
|
|
|
AsyncReadJob_t* pRead = NULL; |
|
if ( m_queuedReads.RemoveAtHead( pRead ) ) |
|
{ |
|
if ( IsJobCancelled( pRead ) ) |
|
{ |
|
delete pRead; |
|
continue; |
|
} |
|
|
|
SafeAssign( &pRead->m_pAsyncMap, new CAsyncMapResult( pRead->m_pSysmemTex ) ); |
|
// Trigger the map. |
|
extern CMaterialSystem g_MaterialSystem; |
|
g_MaterialSystem.GetRenderContextInternal()->AsyncMap( pRead->m_pSysmemTex, pRead->m_pAsyncMap, NULL ); |
|
m_queuedMaps.Insert( pRead ); |
|
|
|
// Stop as soon as we complete one successfully. |
|
break; |
|
} |
|
} |
|
|
|
if ( !m_scheduledReads.IsEmpty() ) |
|
{ |
|
if ( m_scheduledReads.Head()->m_pAsyncRead->IsSignalled() ) |
|
{ |
|
AsyncReadJob_t* pScheduledRead = m_scheduledReads.RemoveAtHead(); |
|
SafeRelease( &pScheduledRead->m_pAsyncRead ); |
|
|
|
m_queuedReads.Insert( pScheduledRead ); |
|
} |
|
} |
|
|
|
AsyncReadJob_t* pRequestCopy = NULL; |
|
if ( m_requestedCopies.PopItem( &pRequestCopy ) ) |
|
{ |
|
SafeAssign( &pRequestCopy->m_pAsyncRead, new CAsyncCopyRequest ); |
|
extern CMaterialSystem g_MaterialSystem; |
|
g_MaterialSystem.GetRenderContextInternal()->AsyncCopyRenderTargetToStagingTexture( pRequestCopy->m_pSysmemTex, pRequestCopy->m_pSrcRt, pRequestCopy->m_pAsyncRead, NULL ); |
|
|
|
m_scheduledReads.Insert( pRequestCopy ); |
|
} |
|
|
|
while ( m_completedJobs.Count() > 0 ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "CreateTextureFromBits" ); |
|
|
|
AsyncReadJob_t* pCreate = NULL; |
|
if ( m_completedJobs.PopItem( &pCreate ) ) |
|
{ |
|
// Check after we do the unmap, we need to do that here. |
|
if ( IsJobCancelled( pCreate ) ) |
|
{ |
|
delete pCreate; |
|
continue; |
|
} |
|
|
|
extern CMaterialSystem g_MaterialSystem; |
|
g_MaterialSystem.GetRenderContextInternal()->AsyncUnmap( pCreate->m_pSysmemTex ); |
|
SafeRelease( &pCreate->m_pAsyncMap ); |
|
|
|
assert_cast< CTextureManager* >( g_pTextureManager )->CompleteAsyncRead( pCreate ); |
|
delete pCreate; |
|
pCreate = NULL; |
|
// Stop as soon as we complete one successfully. |
|
break; |
|
} |
|
} |
|
} |
|
|
|
private: |
|
inline bool ThreadInReaderThread() |
|
{ |
|
return s_TextureManager.ThreadInAsyncReadThread(); |
|
} |
|
|
|
void ThreadReader_Main() |
|
{ |
|
Assert( ThreadInReaderThread() ); |
|
|
|
while ( !m_bQuit ) |
|
{ |
|
AsyncReadJob_t *pJob = NULL; |
|
if ( m_pendingJobs.PopItem( &pJob ) ) |
|
{ |
|
Assert( pJob != NULL ); |
|
ThreadReader_ProcessRead( pJob ); |
|
} |
|
else |
|
{ |
|
// "awhile" |
|
ThreadSleep( 8 ); |
|
} |
|
} |
|
} |
|
|
|
void ThreadReader_ProcessRead( AsyncReadJob_t *pJob ) |
|
{ |
|
Assert( ThreadInReaderThread() ); |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
// This code does a few things: |
|
// 1. Reads from a previously mapped scratch buffer texture and performs byte swapping (if necessary). |
|
// 2. Uses byteswapped data to generate mipmaps |
|
// 3. Encodes mipmapped data into the destination format. |
|
|
|
const int h = pJob->m_pSysmemTex->GetActualHeight(); |
|
const int w = pJob->m_pSysmemTex->GetActualWidth(); |
|
const ImageFormat srcFmt = pJob->m_pSysmemTex->GetImageFormat(); |
|
|
|
// Convert the data |
|
CUtlMemory< unsigned char > srcBufferFinestMip; |
|
CUtlMemory< unsigned char > srcBufferAllMips; |
|
const int srcFinestMemRequired = ImageLoader::GetMemRequired( w, h, 1, srcFmt, false ); |
|
const int srcAllMemRequired = ImageLoader::GetMemRequired( w, h, 1, srcFmt, pJob->m_bGenMips ); |
|
const int srcPitch = ImageLoader::GetMemRequired( w, 1, 1, srcFmt, false ); |
|
|
|
const ImageFormat dstFmt = pJob->m_dstFmt; |
|
CUtlMemory< unsigned char > dstBufferAllMips; |
|
const int dstMemRequried = ImageLoader::GetMemRequired( w, h, 1, dstFmt, pJob->m_bGenMips ); |
|
|
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s-Allocations", __FUNCTION__ ); |
|
srcBufferFinestMip.EnsureCapacity( srcFinestMemRequired ); |
|
if ( srcFinestMemRequired != srcAllMemRequired ) |
|
{ |
|
srcBufferAllMips.EnsureCapacity( srcAllMemRequired ); |
|
} |
|
else |
|
{ |
|
Assert( !pJob->m_bGenMips ); |
|
} |
|
|
|
if ( srcFmt != dstFmt ) |
|
{ |
|
dstBufferAllMips.EnsureCapacity( dstMemRequried ); |
|
} |
|
} |
|
|
|
// If this fires, you will get data corruption below. We can fix this case, it just doesn't seem |
|
// to be needed right now. |
|
Assert( pJob->m_pAsyncMap->m_nPitch == srcPitch ); |
|
srcPitch; // Hush compiler. |
|
|
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s-ByteSwapInPlace", __FUNCTION__ ); |
|
ImageLoader::ConvertImageFormat( (unsigned char*) pJob->m_pAsyncMap->m_pMemory, GetImageFormatRawReadback( srcFmt ), srcBufferFinestMip.Base(), srcFmt, w, h ); |
|
} |
|
|
|
if ( pJob->m_bGenMips ) |
|
{ |
|
GenerateMipmaps( &srcBufferAllMips, srcBufferFinestMip.Base(), w, h, srcFmt ); |
|
} |
|
else |
|
{ |
|
// If we're not generating mips, then allmips == finest mip, but the code below expects everything to |
|
// be in all mips. |
|
srcBufferAllMips.Swap( srcBufferFinestMip ); |
|
} |
|
|
|
// Code below expects that the data is here one way or another. |
|
Assert( srcBufferAllMips.Count() == srcAllMemRequired ); |
|
|
|
if ( srcFmt != dstFmt ) |
|
{ |
|
ConvertTexelData( &dstBufferAllMips, dstFmt, srcBufferAllMips, w, h, srcFmt, pJob->m_bGenMips ); |
|
pJob->m_finalTexelData.Swap( dstBufferAllMips ); |
|
} |
|
else |
|
{ |
|
// Just swap out the buffers. |
|
pJob->m_finalTexelData.Swap( srcBufferAllMips ); |
|
} |
|
|
|
// At this point, the data should be ready to go. Quick sanity check. |
|
Assert( pJob->m_finalTexelData.Count() == dstMemRequried ); |
|
|
|
m_completedJobs.PushItem( pJob ); |
|
} |
|
|
|
void GenerateMipmaps( CUtlMemory< unsigned char >* outBuffer, unsigned char* pSrc, int w, int h, ImageFormat fmt ) const |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
ImageLoader::GenerateMipmapLevelsLQ( pSrc, outBuffer->Base(), w, h, fmt, 0 ); |
|
} |
|
|
|
void ConvertTexelData( CUtlMemory< unsigned char > *outBuffer, ImageFormat dstFmt, /* const */ CUtlMemory< unsigned char > &inBuffer, int w, int h, ImageFormat srcFmt, bool bGenMips ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
const int mipmapCount = bGenMips ? ImageLoader::GetNumMipMapLevels( w, h ) : 1; |
|
|
|
unsigned char* pSrc = inBuffer.Base(); |
|
unsigned char* pDst = (*outBuffer).Base(); |
|
int mip_w = w; |
|
int mip_h = h; |
|
|
|
for ( int i = 0; i < mipmapCount; ++i ) |
|
{ |
|
ImageLoader::ConvertImageFormat( pSrc, srcFmt, pDst, dstFmt, mip_w, mip_h ); |
|
|
|
pSrc += ImageLoader::GetMemRequired( mip_w, mip_h, 1, srcFmt, false ); |
|
pDst += ImageLoader::GetMemRequired( mip_w, mip_h, 1, dstFmt, false ); |
|
|
|
mip_w = Max( 1, mip_w >> 1 ); |
|
mip_h = Max( 1, mip_h >> 1 ); |
|
} |
|
} |
|
static unsigned ReaderMain( void* _this ) |
|
{ |
|
ThreadSetDebugName( "Helper" ); |
|
|
|
s_TextureManager.m_nAsyncReadThread = ThreadGetCurrentId(); |
|
( ( AsyncReader* ) _this )->ThreadReader_Main(); |
|
s_TextureManager.m_nAsyncReadThread = 0xFFFFFFFF; |
|
return 0; |
|
} |
|
|
|
ThreadHandle_t m_HelperThread; |
|
volatile bool m_bQuit; |
|
|
|
CTSQueue< AsyncReadJob_t*> m_requestedCopies; |
|
CUtlQueue< AsyncReadJob_t* > m_queuedReads; |
|
CUtlQueue< AsyncReadJob_t* > m_scheduledReads; |
|
CUtlQueue< AsyncReadJob_t* > m_queuedMaps; |
|
|
|
CTSQueue< AsyncReadJob_t* > m_pendingJobs; |
|
CTSQueue< AsyncReadJob_t* > m_completedJobs; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Texture manager |
|
//----------------------------------------------------------------------------- |
|
CTextureManager::CTextureManager( void ) |
|
: m_TextureList( true ) |
|
, m_TextureAliases( true ) |
|
, m_TextureExcludes( true ) |
|
, m_PendingAsyncLoads( true ) |
|
, m_textureStreamingRequests( DefLessFunc( ITextureInternal* ) ) |
|
, m_nAsyncLoadThread( 0xFFFFFFFF ) |
|
, m_nAsyncReadThread( 0xFFFFFFFF ) |
|
{ |
|
m_pErrorTexture = NULL; |
|
m_pBlackTexture = NULL; |
|
m_pWhiteTexture = NULL; |
|
m_pGreyTexture = NULL; |
|
m_pGreyAlphaZeroTexture = NULL; |
|
m_pNormalizationCubemap = NULL; |
|
m_pErrorRegen = NULL; |
|
m_pFullScreenTexture = NULL; |
|
m_pSignedNormalizationCubemap = NULL; |
|
m_pShadowNoise2D = NULL; |
|
m_pIdentityLightWarp = NULL; |
|
m_pFullScreenDepthTexture = NULL; |
|
m_pDebugLuxels2D = NULL; |
|
m_pAsyncLoader = new AsyncLoader; |
|
m_pAsyncReader = new AsyncReader; |
|
m_iSuspendTextureStreaming = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Initialization + shutdown |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::Init( int nFlags ) |
|
{ |
|
m_nFlags = nFlags; |
|
color32 color, color2; |
|
m_iNextTexID = 4096; |
|
|
|
// setup the checkerboard generator for failed texture loading |
|
color.r = color.g = color.b = 0; color.a = 128; |
|
color2.r = color2.b = color2.a = 255; color2.g = 0; |
|
m_pErrorRegen = new CCheckerboardTexture( 4, color, color2 ); |
|
|
|
// Create an error texture |
|
m_pErrorTexture = CreateProceduralTexture( "error", TEXTURE_GROUP_OTHER, |
|
ERROR_TEXTURE_SIZE, ERROR_TEXTURE_SIZE, 1, IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); |
|
CreateCheckerboardTexture( m_pErrorTexture, 4, color, color2 ); |
|
m_pErrorTexture->SetErrorTexture( true ); |
|
|
|
// Create a white texture |
|
m_pWhiteTexture = CreateProceduralTexture( "white", TEXTURE_GROUP_OTHER, |
|
WHITE_TEXTURE_SIZE, WHITE_TEXTURE_SIZE, 1, IMAGE_FORMAT_BGRX8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); |
|
color.r = color.g = color.b = color.a = 255; |
|
CreateSolidTexture( m_pWhiteTexture, color ); |
|
|
|
// Create a black texture |
|
m_pBlackTexture = CreateProceduralTexture( "black", TEXTURE_GROUP_OTHER, |
|
BLACK_TEXTURE_SIZE, BLACK_TEXTURE_SIZE, 1, IMAGE_FORMAT_BGRX8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); |
|
color.r = color.g = color.b = 0; |
|
CreateSolidTexture( m_pBlackTexture, color ); |
|
|
|
// Create a grey texture |
|
m_pGreyTexture = CreateProceduralTexture( "grey", TEXTURE_GROUP_OTHER, |
|
GREY_TEXTURE_SIZE, GREY_TEXTURE_SIZE, 1, IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); |
|
color.r = color.g = color.b = 128; |
|
color.a = 255; |
|
CreateSolidTexture( m_pGreyTexture, color ); |
|
|
|
// Create a grey texture |
|
m_pGreyAlphaZeroTexture = CreateProceduralTexture( "greyalphazero", TEXTURE_GROUP_OTHER, |
|
GREY_TEXTURE_SIZE, GREY_TEXTURE_SIZE, 1, IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY ); |
|
color.r = color.g = color.b = 128; |
|
color.a = 0; |
|
CreateSolidTexture( m_pGreyAlphaZeroTexture, color ); |
|
|
|
if ( HardwareConfig()->GetMaxDXSupportLevel() >= 80 ) |
|
{ |
|
// Create a normalization cubemap |
|
m_pNormalizationCubemap = CreateProceduralTexture( "normalize", TEXTURE_GROUP_CUBE_MAP, |
|
NORMALIZATION_CUBEMAP_SIZE, NORMALIZATION_CUBEMAP_SIZE, 1, IMAGE_FORMAT_BGRX8888, |
|
TEXTUREFLAGS_ENVMAP | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY | |
|
TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_CLAMPU ); |
|
CreateNormalizationCubemap( m_pNormalizationCubemap ); |
|
} |
|
|
|
if ( HardwareConfig()->GetMaxDXSupportLevel() >= 90 ) |
|
{ |
|
// In GL, we have poor format support, so we ask for signed float |
|
ImageFormat fmt = IsOpenGL() ? IMAGE_FORMAT_RGBA16161616F : IMAGE_FORMAT_UVWQ8888; |
|
|
|
int nTextureFlags = TEXTUREFLAGS_ENVMAP | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_CLAMPU; |
|
|
|
#ifdef OSX |
|
// JasonM - ridiculous hack around R500 lameness...we never use this texture on OSX anyways (right?) |
|
// Now assuming this was an OSX specific workaround. |
|
nTextureFlags |= TEXTUREFLAGS_POINTSAMPLE; |
|
#endif |
|
|
|
// Create a normalization cubemap |
|
m_pSignedNormalizationCubemap = CreateProceduralTexture( "normalizesigned", TEXTURE_GROUP_CUBE_MAP, |
|
NORMALIZATION_CUBEMAP_SIZE, NORMALIZATION_CUBEMAP_SIZE, 1, fmt, nTextureFlags ); |
|
CreateSignedNormalizationCubemap( m_pSignedNormalizationCubemap ); |
|
|
|
m_pIdentityLightWarp = FindOrLoadTexture( "dev/IdentityLightWarp", TEXTURE_GROUP_OTHER ); |
|
m_pIdentityLightWarp->IncrementReferenceCount(); |
|
} |
|
|
|
// High end hardware needs this texture for shadow mapping |
|
if ( HardwareConfig()->ActuallySupportsPixelShaders_2_b() ) |
|
{ |
|
m_pShadowNoise2D = FindOrLoadTexture( "engine/NormalizedRandomDirections2D", TEXTURE_GROUP_OTHER ); |
|
m_pShadowNoise2D->IncrementReferenceCount(); |
|
} |
|
|
|
m_pDebugLuxels2D = FindOrLoadTexture( "debug/debugluxelsnoalpha", TEXTURE_GROUP_OTHER ); |
|
m_pDebugLuxels2D->IncrementReferenceCount(); |
|
} |
|
|
|
void CTextureManager::Shutdown() |
|
{ |
|
// Clean up any textures we have hanging around that are waiting to go. |
|
CleanupPossiblyUnreferencedTextures(); |
|
|
|
// Cool the texture cache first to drop all the refs back to 0 for the streamable things. |
|
CoolTextureCache(); |
|
|
|
if ( m_pAsyncLoader ) |
|
{ |
|
m_pAsyncLoader->Shutdown(); |
|
delete m_pAsyncLoader; |
|
m_pAsyncLoader = NULL; |
|
} |
|
|
|
if ( m_pAsyncReader ) |
|
{ |
|
m_pAsyncReader->Shutdown(); |
|
delete m_pAsyncReader; |
|
m_pAsyncReader = NULL; |
|
} |
|
|
|
FreeStandardRenderTargets(); |
|
|
|
FOR_EACH_VEC( m_ReadbackTextures, i ) |
|
{ |
|
m_ReadbackTextures[ i ]->Release(); |
|
} |
|
|
|
if ( m_pDebugLuxels2D ) |
|
{ |
|
m_pDebugLuxels2D->DecrementReferenceCount(); |
|
m_pDebugLuxels2D = NULL; |
|
} |
|
|
|
// These checks added because it's possible for shutdown to be called before the material system is |
|
// fully initialized. |
|
if ( m_pWhiteTexture ) |
|
{ |
|
m_pWhiteTexture->DecrementReferenceCount(); |
|
m_pWhiteTexture = NULL; |
|
} |
|
|
|
if ( m_pBlackTexture ) |
|
{ |
|
m_pBlackTexture->DecrementReferenceCount(); |
|
m_pBlackTexture = NULL; |
|
} |
|
|
|
if ( m_pGreyTexture ) |
|
{ |
|
m_pGreyTexture->DecrementReferenceCount(); |
|
m_pGreyTexture = NULL; |
|
} |
|
|
|
if ( m_pGreyAlphaZeroTexture ) |
|
{ |
|
m_pGreyAlphaZeroTexture->DecrementReferenceCount(); |
|
m_pGreyAlphaZeroTexture = NULL; |
|
} |
|
|
|
if ( m_pNormalizationCubemap ) |
|
{ |
|
m_pNormalizationCubemap->DecrementReferenceCount(); |
|
m_pNormalizationCubemap = NULL; |
|
} |
|
|
|
if ( m_pSignedNormalizationCubemap ) |
|
{ |
|
m_pSignedNormalizationCubemap->DecrementReferenceCount(); |
|
m_pSignedNormalizationCubemap = NULL; |
|
} |
|
|
|
if ( m_pShadowNoise2D ) |
|
{ |
|
m_pShadowNoise2D->DecrementReferenceCount(); |
|
m_pShadowNoise2D = NULL; |
|
} |
|
|
|
if ( m_pIdentityLightWarp ) |
|
{ |
|
m_pIdentityLightWarp->DecrementReferenceCount(); |
|
m_pIdentityLightWarp = NULL; |
|
} |
|
|
|
if ( m_pErrorTexture ) |
|
{ |
|
m_pErrorTexture->DecrementReferenceCount(); |
|
m_pErrorTexture = NULL; |
|
} |
|
|
|
ReleaseTextures(); |
|
|
|
if ( m_pErrorRegen ) |
|
{ |
|
m_pErrorRegen->Release(); |
|
m_pErrorRegen = NULL; |
|
} |
|
|
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
ITextureInternal::Destroy( m_TextureList[i], true ); |
|
} |
|
m_TextureList.RemoveAll(); |
|
|
|
for( int i = m_TextureAliases.First(); i != m_TextureAliases.InvalidIndex(); i = m_TextureAliases.Next( i ) ) |
|
{ |
|
delete []m_TextureAliases[i]; |
|
} |
|
m_TextureAliases.RemoveAll(); |
|
|
|
m_TextureExcludes.RemoveAll(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Allocate, free standard render target textures |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::AllocateStandardRenderTargets( ) |
|
{ |
|
bool bAllocateFullscreenTexture = ( m_nFlags & MATERIAL_INIT_ALLOCATE_FULLSCREEN_TEXTURE ) != 0; |
|
bool bAllocateMorphAccumTexture = g_pMorphMgr->ShouldAllocateScratchTextures(); |
|
|
|
if ( IsPC() && ( bAllocateFullscreenTexture || bAllocateMorphAccumTexture ) ) |
|
{ |
|
MaterialSystem()->BeginRenderTargetAllocation(); |
|
|
|
// A offscreen render target which is the size + format of the back buffer (*not* HDR format!) |
|
if ( bAllocateFullscreenTexture ) |
|
{ |
|
m_pFullScreenTexture = CreateRenderTargetTexture( "_rt_FullScreen", 1, 1, RT_SIZE_FULL_FRAME_BUFFER_ROUNDED_UP, |
|
MaterialSystem()->GetBackBufferFormat(), RENDER_TARGET, TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT, 0 ); |
|
m_pFullScreenTexture->IncrementReferenceCount(); |
|
} |
|
|
|
// This texture is the one we accumulate morph deltas into |
|
if ( bAllocateMorphAccumTexture ) |
|
{ |
|
g_pMorphMgr->AllocateScratchTextures(); |
|
g_pMorphMgr->AllocateMaterials(); |
|
} |
|
|
|
MaterialSystem()->EndRenderTargetAllocation(); |
|
} |
|
} |
|
|
|
|
|
void CTextureManager::FreeStandardRenderTargets() |
|
{ |
|
if ( m_pFullScreenTexture ) |
|
{ |
|
m_pFullScreenTexture->DecrementReferenceCount(); |
|
m_pFullScreenTexture = NULL; |
|
} |
|
|
|
g_pMorphMgr->FreeMaterials(); |
|
g_pMorphMgr->FreeScratchTextures(); |
|
} |
|
|
|
|
|
void CTextureManager::CacheExternalStandardRenderTargets() |
|
{ |
|
m_pFullScreenDepthTexture = FindTexture( "_rt_FullFrameDepth" ); //created/destroyed in engine/matsys_interface.cpp to properly track hdr changes |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Generates an error texture pattern |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::GenerateErrorTexture( ITexture *pTexture, IVTFTexture *pVTFTexture ) |
|
{ |
|
m_pErrorRegen->RegenerateTextureBits( pTexture, pVTFTexture, NULL ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Updates the color correction state |
|
//----------------------------------------------------------------------------- |
|
ITextureInternal *CTextureManager::ColorCorrectionTexture( int i ) |
|
{ |
|
Assert( i<COLOR_CORRECTION_MAX_TEXTURES ); |
|
return m_pColorCorrectionTextures[ i ]; |
|
} |
|
|
|
void CTextureManager::SetColorCorrectionTexture( int i, ITextureInternal *pTexture ) |
|
{ |
|
Assert( i<COLOR_CORRECTION_MAX_TEXTURES ); |
|
|
|
if( m_pColorCorrectionTextures[i] ) |
|
{ |
|
m_pColorCorrectionTextures[i]->DecrementReferenceCount(); |
|
} |
|
|
|
m_pColorCorrectionTextures[i] = pTexture; |
|
if( pTexture ) |
|
pTexture->IncrementReferenceCount(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Releases all textures (cause we've lost video memory) |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::ReleaseTextures( void ) |
|
{ |
|
g_pShaderAPI->SetFullScreenTextureHandle( INVALID_SHADERAPI_TEXTURE_HANDLE ); |
|
|
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
// Release the texture... |
|
m_TextureList[i]->ReleaseMemory(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Request a texture ID |
|
//----------------------------------------------------------------------------- |
|
int CTextureManager::RequestNextTextureID() |
|
{ |
|
// FIXME: Deal better with texture ids |
|
// The range between 19000 and 21000 are used for standard textures + lightmaps |
|
if (m_iNextTexID == 19000) |
|
{ |
|
m_iNextTexID = 21000; |
|
} |
|
|
|
return m_iNextTexID++; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Restores a single texture |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::RestoreTexture( ITextureInternal* pTexture ) |
|
{ |
|
// Put the texture back onto the board |
|
pTexture->OnRestore(); // Give render targets a chance to reinitialize themselves if necessary (due to AA changes). |
|
pTexture->Download(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purges our complete list of textures that might currently be unreferenced |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::CleanupPossiblyUnreferencedTextures() |
|
{ |
|
if ( !ThreadInMainThread() || MaterialSystem()->GetRenderThreadId() != 0xFFFFFFFF ) |
|
{ |
|
Assert( !"CTextureManager::CleanupPossiblyUnreferencedTextures should never be called here" ); |
|
// This is catastrophically bad, don't do this. Someone needs to fix this. See JohnS or McJohn |
|
DebuggerBreakIfDebugging_StagingOnly(); |
|
return; |
|
} |
|
|
|
// It is perfectly valid for a texture to become referenced again (it lives on in our texture list, and can be |
|
// re-loaded) and then free'd again, so ensure we don't have any duplicates in queue. |
|
CUtlVector< ITextureInternal * > texturesToDelete( /* growSize */ 0, /* initialSize */ m_PossiblyUnreferencedTextures.Count() ); |
|
ITextureInternal *pMaybeUnreferenced = NULL; |
|
while ( m_PossiblyUnreferencedTextures.PopItem( &pMaybeUnreferenced ) ) |
|
{ |
|
Assert( pMaybeUnreferenced->GetReferenceCount() >= 0 ); |
|
if ( pMaybeUnreferenced->GetReferenceCount() == 0 && texturesToDelete.Find( pMaybeUnreferenced ) == texturesToDelete.InvalidIndex() ) |
|
{ |
|
texturesToDelete.AddToTail( pMaybeUnreferenced ); |
|
} |
|
} |
|
|
|
// Free them |
|
FOR_EACH_VEC( texturesToDelete, i ) |
|
{ |
|
RemoveTexture( texturesToDelete[ i ] ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Restore all textures (cause we've got video memory again) |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::RestoreNonRenderTargetTextures( ) |
|
{ |
|
// 360 should not have gotten here |
|
Assert( !IsX360() ); |
|
|
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
if ( !m_TextureList[i]->IsRenderTarget() ) |
|
{ |
|
RestoreTexture( m_TextureList[i] ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Restore just the render targets (cause we've got video memory again) |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::RestoreRenderTargets() |
|
{ |
|
// 360 should not have gotten here |
|
Assert( !IsX360() ); |
|
|
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
if ( m_TextureList[i]->IsRenderTarget() ) |
|
{ |
|
RestoreTexture( m_TextureList[i] ); |
|
} |
|
} |
|
|
|
if ( m_pFullScreenTexture ) |
|
{ |
|
g_pShaderAPI->SetFullScreenTextureHandle( m_pFullScreenTexture->GetTextureHandle( 0 ) ); |
|
} |
|
|
|
CacheExternalStandardRenderTargets(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Reloads all textures |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::ReloadTextures() |
|
{ |
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
// Put the texture back onto the board |
|
m_TextureList[i]->Download(); |
|
} |
|
} |
|
|
|
static void ForceTextureIntoHardware( ITexture *pTexture, IMaterial *pMaterial, IMaterialVar *pBaseTextureVar ) |
|
{ |
|
if ( IsX360() ) |
|
return; |
|
|
|
pBaseTextureVar->SetTextureValue( pTexture ); |
|
|
|
CMatRenderContextPtr pRenderContext( MaterialSystem()->GetRenderContext() ); |
|
pRenderContext->Bind( pMaterial ); |
|
IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); |
|
|
|
CMeshBuilder meshBuilder; |
|
meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 1 ); |
|
|
|
meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); |
|
meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); |
|
meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); |
|
meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); |
|
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); |
|
meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); |
|
meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); |
|
meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); |
|
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.Position3f( 0.0f, 0.0f, 0.0f ); |
|
meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); |
|
meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); |
|
meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); |
|
meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); |
|
meshBuilder.AdvanceVertex(); |
|
|
|
meshBuilder.End(); |
|
pMesh->Draw(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Reloads all textures |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::ForceAllTexturesIntoHardware( void ) |
|
{ |
|
if ( IsX360() ) |
|
return; |
|
|
|
IMaterial *pMaterial = MaterialSystem()->FindMaterial( "engine/preloadtexture", "texture preload" ); |
|
pMaterial = ((IMaterialInternal *)pMaterial)->GetRealTimeVersion(); //always work with the realtime material internally |
|
pMaterial->IncrementReferenceCount(); |
|
bool bFound; |
|
IMaterialVar *pBaseTextureVar = pMaterial->FindVar( "$basetexture", &bFound ); |
|
if( !bFound ) |
|
{ |
|
return; |
|
} |
|
|
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
// Put the texture back onto the board |
|
ForceTextureIntoHardware( m_TextureList[i], pMaterial, pBaseTextureVar ); |
|
} |
|
pMaterial->DecrementReferenceCount(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Get at a couple standard textures |
|
//----------------------------------------------------------------------------- |
|
ITextureInternal *CTextureManager::ErrorTexture() |
|
{ |
|
return m_pErrorTexture; |
|
} |
|
|
|
ITextureInternal *CTextureManager::NormalizationCubemap() |
|
{ |
|
return m_pNormalizationCubemap; |
|
} |
|
|
|
ITextureInternal *CTextureManager::SignedNormalizationCubemap() |
|
{ |
|
return m_pSignedNormalizationCubemap; |
|
} |
|
|
|
ITextureInternal *CTextureManager::ShadowNoise2D() |
|
{ |
|
return m_pShadowNoise2D; |
|
} |
|
|
|
ITextureInternal *CTextureManager::IdentityLightWarp() |
|
{ |
|
return m_pIdentityLightWarp; |
|
} |
|
|
|
ITextureInternal *CTextureManager::FullFrameDepthTexture() |
|
{ |
|
return m_pFullScreenDepthTexture; |
|
} |
|
|
|
ITextureInternal *CTextureManager::DebugLuxels2D() |
|
{ |
|
return m_pDebugLuxels2D; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a procedural texture |
|
//----------------------------------------------------------------------------- |
|
ITextureInternal *CTextureManager::CreateProceduralTexture( |
|
const char *pTextureName, |
|
const char *pTextureGroupName, |
|
int w, |
|
int h, |
|
int d, |
|
ImageFormat fmt, |
|
int nFlags, |
|
ITextureRegenerator *generator ) |
|
{ |
|
ITextureInternal *pNewTexture = ITextureInternal::CreateProceduralTexture( pTextureName, pTextureGroupName, w, h, d, fmt, nFlags, generator ); |
|
if ( !pNewTexture ) |
|
return NULL; |
|
|
|
// Add it to the list of textures so it can be restored, etc. |
|
m_TextureList.Insert( pNewTexture->GetName(), pNewTexture ); |
|
|
|
// NOTE: This will download the texture only if the shader api is ready |
|
pNewTexture->Download(); |
|
|
|
return pNewTexture; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// FIXME: Need some better understanding of when textures should be added to |
|
// the texture dictionary here. Is it only for files, for example? |
|
// Texture dictionary... |
|
//----------------------------------------------------------------------------- |
|
ITextureInternal *CTextureManager::LoadTexture( const char *pTextureName, const char *pTextureGroupName, int nAdditionalCreationFlags /* = 0 */, bool bDownload /* = true */ ) |
|
{ |
|
ITextureInternal *pNewTexture = ITextureInternal::CreateFileTexture( pTextureName, pTextureGroupName ); |
|
if ( pNewTexture ) |
|
{ |
|
int iIndex = m_TextureExcludes.Find( pNewTexture->GetName() ); |
|
if ( m_TextureExcludes.IsValidIndex( iIndex ) ) |
|
{ |
|
// mark the new texture as excluded |
|
int nDimensionsLimit = m_TextureExcludes[iIndex]; |
|
pNewTexture->MarkAsExcluded( ( nDimensionsLimit == 0 ), nDimensionsLimit ); |
|
} |
|
|
|
// Stick the texture onto the board |
|
if ( bDownload ) |
|
pNewTexture->Download( NULL, nAdditionalCreationFlags ); |
|
|
|
// FIXME: If there's been an error loading, we don't also want this error... |
|
} |
|
|
|
return pNewTexture; |
|
} |
|
|
|
ITextureInternal *CTextureManager::FindTexture( const char *pTextureName ) |
|
{ |
|
if ( !pTextureName || pTextureName[0] == 0 ) |
|
return NULL; |
|
|
|
char szCleanName[MAX_PATH]; |
|
NormalizeTextureName( pTextureName, szCleanName, sizeof( szCleanName ) ); |
|
|
|
int i = m_TextureList.Find( szCleanName ); |
|
if ( i != m_TextureList.InvalidIndex() ) |
|
{ |
|
return m_TextureList[i]; |
|
} |
|
|
|
i = m_TextureAliases.Find( szCleanName ); |
|
if ( i != m_TextureAliases.InvalidIndex() ) |
|
{ |
|
return FindTexture( m_TextureAliases[i] ); |
|
} |
|
|
|
// Special handling: lightmaps |
|
if ( char const *szLightMapNum = StringAfterPrefix( szCleanName, "[lightmap" ) ) |
|
{ |
|
int iLightMapNum = atoi( szLightMapNum ); |
|
extern CMaterialSystem g_MaterialSystem; |
|
CMatLightmaps *plm = g_MaterialSystem.GetLightmaps(); |
|
if ( iLightMapNum >= 0 && |
|
iLightMapNum < plm->GetNumLightmapPages() ) |
|
{ |
|
ShaderAPITextureHandle_t hTex = plm->GetLightmapPageTextureHandle( iLightMapNum ); |
|
if ( hTex != INVALID_SHADERAPI_TEXTURE_HANDLE ) |
|
{ |
|
// Establish the lookup linking in the dictionary |
|
ITextureInternal *pTxInt = ITextureInternal::CreateReferenceTextureFromHandle( pTextureName, TEXTURE_GROUP_LIGHTMAP, hTex ); |
|
m_TextureList.Insert( pTextureName, pTxInt ); |
|
return pTxInt; |
|
} |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CTextureManager::AddTextureAlias( const char *pAlias, const char *pRealName ) |
|
{ |
|
if ( (pAlias == NULL) || (pRealName == NULL) ) |
|
return; //invalid alias |
|
|
|
char szCleanName[MAX_PATH]; |
|
int index = m_TextureAliases.Find( NormalizeTextureName( pAlias, szCleanName, sizeof( szCleanName ) ) ); |
|
|
|
if ( index != m_TextureAliases.InvalidIndex() ) |
|
{ |
|
AssertMsg( Q_stricmp( pRealName, m_TextureAliases[index] ) == 0, "Trying to use one name to alias two different textures." ); |
|
RemoveTextureAlias( pAlias ); //remove the old alias to make room for the new one. |
|
} |
|
|
|
size_t iRealNameLength = strlen( pRealName ) + 1; |
|
char *pRealNameCopy = new char [iRealNameLength]; |
|
memcpy( pRealNameCopy, pRealName, iRealNameLength ); |
|
|
|
m_TextureAliases.Insert( szCleanName, pRealNameCopy ); |
|
} |
|
|
|
void CTextureManager::RemoveTextureAlias( const char *pAlias ) |
|
{ |
|
if ( pAlias == NULL ) |
|
return; |
|
|
|
char szCleanName[MAX_PATH]; |
|
int index = m_TextureAliases.Find( NormalizeTextureName( pAlias, szCleanName, sizeof( szCleanName ) ) ); |
|
if ( index == m_TextureAliases.InvalidIndex() ) |
|
return; //not found |
|
|
|
delete []m_TextureAliases[index]; |
|
m_TextureAliases.RemoveAt( index ); |
|
} |
|
|
|
void CTextureManager::SetExcludedTextures( const char *pScriptName ) |
|
{ |
|
// clear all exisiting texture's exclusion |
|
for ( int i = m_TextureExcludes.First(); i != m_TextureExcludes.InvalidIndex(); i = m_TextureExcludes.Next( i ) ) |
|
{ |
|
ITextureInternal *pTexture = FindTexture( m_TextureExcludes.GetElementName( i ) ); |
|
if ( pTexture ) |
|
{ |
|
pTexture->MarkAsExcluded( false, 0 ); |
|
} |
|
} |
|
m_TextureExcludes.RemoveAll(); |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
// get optional script |
|
CUtlBuffer excludeBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
if ( g_pFullFileSystem->ReadFile( pScriptName, NULL, excludeBuffer ) ) |
|
{ |
|
char szToken[MAX_PATH]; |
|
while ( 1 ) |
|
{ |
|
// must support spaces in names without quotes |
|
// have to brute force parse up to a valid line |
|
while ( 1 ) |
|
{ |
|
excludeBuffer.EatWhiteSpace(); |
|
if ( !excludeBuffer.EatCPPComment() ) |
|
{ |
|
// not a comment |
|
break; |
|
} |
|
} |
|
excludeBuffer.GetLine( szToken, sizeof( szToken ) ); |
|
int tokenLength = strlen( szToken ); |
|
if ( !tokenLength ) |
|
{ |
|
// end of list |
|
break; |
|
} |
|
|
|
// remove all trailing whitespace |
|
while ( tokenLength > 0 ) |
|
{ |
|
tokenLength--; |
|
if ( isgraph( szToken[tokenLength] ) ) |
|
{ |
|
break; |
|
} |
|
szToken[tokenLength] = '\0'; |
|
} |
|
|
|
// first optional token may be a dimension limit hint |
|
int nDimensionsLimit = 0; |
|
char *pTextureName = szToken; |
|
if ( pTextureName[0] != 0 && isdigit( pTextureName[0] ) ) |
|
{ |
|
nDimensionsLimit = atoi( pTextureName ); |
|
|
|
// skip forward to name |
|
for ( ;; ) |
|
{ |
|
char ch = *pTextureName; |
|
if ( !ch || ( !isdigit( ch ) && !isspace( ch ) ) ) |
|
{ |
|
break; |
|
} |
|
pTextureName++; |
|
} |
|
} |
|
|
|
char szCleanName[MAX_PATH]; |
|
NormalizeTextureName( pTextureName, szCleanName, sizeof( szCleanName ) ); |
|
|
|
if ( m_TextureExcludes.Find( szCleanName ) != m_TextureExcludes.InvalidIndex() ) |
|
{ |
|
// avoid duplicates |
|
continue; |
|
} |
|
|
|
m_TextureExcludes.Insert( szCleanName, nDimensionsLimit ); |
|
|
|
// set any existing texture's exclusion |
|
// textures that don't exist yet will get caught during their creation path |
|
ITextureInternal *pTexture = FindTexture( szCleanName ); |
|
if ( pTexture ) |
|
{ |
|
pTexture->MarkAsExcluded( ( nDimensionsLimit == 0 ), nDimensionsLimit ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CTextureManager::UpdateExcludedTextures( void ) |
|
{ |
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
m_TextureList[i]->UpdateExcludedState(); |
|
} |
|
} |
|
|
|
ITextureInternal *CTextureManager::FindOrLoadTexture( const char *pTextureName, const char *pTextureGroupName, int nAdditionalCreationFlags /* = 0 */ ) |
|
{ |
|
ITextureInternal *pTexture = FindTexture( pTextureName ); |
|
if ( !pTexture ) |
|
{ |
|
pTexture = LoadTexture( pTextureName, pTextureGroupName, nAdditionalCreationFlags ); |
|
if ( pTexture ) |
|
{ |
|
// insert into the dictionary using the processed texture name |
|
m_TextureList.Insert( pTexture->GetName(), pTexture ); |
|
} |
|
} |
|
|
|
return pTexture; |
|
} |
|
|
|
bool CTextureManager::IsTextureLoaded( const char *pTextureName ) |
|
{ |
|
ITextureInternal *pTexture = FindTexture( pTextureName ); |
|
return ( pTexture != NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a texture that's a render target |
|
//----------------------------------------------------------------------------- |
|
ITextureInternal *CTextureManager::CreateRenderTargetTexture( |
|
const char *pRTName, // NULL for auto-generated name |
|
int w, |
|
int h, |
|
RenderTargetSizeMode_t sizeMode, |
|
ImageFormat fmt, |
|
RenderTargetType_t type, |
|
unsigned int textureFlags, |
|
unsigned int renderTargetFlags ) |
|
{ |
|
MEM_ALLOC_CREDIT_( __FILE__ ": Render target" ); |
|
|
|
ITextureInternal *pTexture; |
|
if ( pRTName ) |
|
{ |
|
// caller is re-initing or changing |
|
pTexture = FindTexture( pRTName ); |
|
if ( pTexture ) |
|
{ |
|
// Changing the underlying render target, but leaving the pointer and refcount |
|
// alone fixes callers that have exisiting references to this object. |
|
ITextureInternal::ChangeRenderTarget( pTexture, w, h, sizeMode, fmt, type, |
|
textureFlags, renderTargetFlags ); |
|
|
|
// download if ready |
|
pTexture->Download(); |
|
return pTexture; |
|
} |
|
} |
|
|
|
pTexture = ITextureInternal::CreateRenderTarget( pRTName, w, h, sizeMode, fmt, type, |
|
textureFlags, renderTargetFlags ); |
|
if ( !pTexture ) |
|
return NULL; |
|
|
|
// Add the render target to the list of textures |
|
// that way it'll get cleaned up correctly in case of a task switch |
|
m_TextureList.Insert( pTexture->GetName(), pTexture ); |
|
|
|
// NOTE: This will download the texture only if the shader api is ready |
|
pTexture->Download(); |
|
|
|
return pTexture; |
|
} |
|
|
|
void CTextureManager::ResetTextureFilteringState( ) |
|
{ |
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
m_TextureList[i]->SetFilteringAndClampingMode(); |
|
} |
|
} |
|
|
|
void CTextureManager::SuspendTextureStreaming( void ) |
|
{ |
|
m_iSuspendTextureStreaming++; |
|
} |
|
|
|
void CTextureManager::ResumeTextureStreaming( void ) |
|
{ |
|
AssertMsg( m_iSuspendTextureStreaming, "Mismatched Suspend/Resume texture streaming calls" ); |
|
if ( m_iSuspendTextureStreaming ) |
|
{ |
|
m_iSuspendTextureStreaming--; |
|
} |
|
} |
|
|
|
void CTextureManager::RemoveUnusedTextures( void ) |
|
{ |
|
// First, need to flush all of our textures that are pending cleanup. |
|
CleanupPossiblyUnreferencedTextures(); |
|
|
|
int iNext; |
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = m_TextureList.Next( i ); |
|
|
|
#ifdef _DEBUG |
|
if ( m_TextureList[i]->GetReferenceCount() < 0 ) |
|
{ |
|
Warning( "RemoveUnusedTextures: pTexture->m_referenceCount < 0 for %s\n", m_TextureList[i]->GetName() ); |
|
} |
|
#endif |
|
if ( m_TextureList[i]->GetReferenceCount() <= 0 ) |
|
{ |
|
ITextureInternal::Destroy( m_TextureList[i] ); |
|
m_TextureList.RemoveAt( i ); |
|
} |
|
} |
|
} |
|
|
|
void CTextureManager::MarkUnreferencedTextureForCleanup( ITextureInternal *pTexture ) |
|
{ |
|
Assert( pTexture->GetReferenceCount() == 0 ); |
|
m_PossiblyUnreferencedTextures.PushItem( pTexture ); |
|
} |
|
|
|
void CTextureManager::RemoveTexture( ITextureInternal *pTexture ) |
|
{ |
|
TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); |
|
|
|
Assert( pTexture->GetReferenceCount() <= 0 ); |
|
|
|
if ( !ThreadInMainThread() || MaterialSystem()->GetRenderThreadId() != 0xFFFFFFFF ) |
|
{ |
|
Assert( !"CTextureManager::RemoveTexture should never be called here"); |
|
// This is catastrophically bad, don't do this. Someone needs to fix this. |
|
DebuggerBreakIfDebugging_StagingOnly(); |
|
return; |
|
} |
|
|
|
bool bTextureFound = false; |
|
|
|
// If the queue'd rendering thread is running, RemoveTexture() is going to explode. If it isn't, calling |
|
// RemoveTexture while still dealing with immediate removal textures seems fishy, but could be legit, in which case |
|
// this assert could be softened. |
|
int nUnreferencedQueue = m_PossiblyUnreferencedTextures.Count(); |
|
if ( nUnreferencedQueue ) |
|
{ |
|
Assert( !"RemoveTexture() being called while textures sitting in possibly unreferenced queue" ); |
|
// Assuming that this is all a wholesome main-thread misunderstanding, we can try to continue after filtering |
|
// this texture from the queue. |
|
ITextureInternal *pPossiblyUnreferenced = NULL; |
|
for ( int i = 0; i < nUnreferencedQueue && m_PossiblyUnreferencedTextures.PopItem( &pPossiblyUnreferenced ); i++ ) |
|
{ |
|
m_PossiblyUnreferencedTextures.PushItem( pPossiblyUnreferenced ); |
|
|
|
if ( pPossiblyUnreferenced == pTexture ) |
|
{ |
|
bTextureFound = true; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( bTextureFound ) |
|
{ |
|
Assert( !"CTextureManager::RemoveTexture has been called for a texture that has already requested cleanup. That's a paddlin'." ); |
|
// This is catastrophically bad, don't do this. Someone needs to fix this. |
|
DebuggerBreakIfDebugging_StagingOnly(); |
|
return; |
|
} |
|
|
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
// search by object |
|
if ( m_TextureList[i] == pTexture ) |
|
{ |
|
// This code is always sure that the texture we're tryign to clean up is no longer in the the possibly unreferenced list, |
|
// So let Destroy work without checking. |
|
ITextureInternal::Destroy( m_TextureList[i], true ); |
|
m_TextureList.RemoveAt( i ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
void CTextureManager::ReloadFilesInList( IFileList *pFilesToReload ) |
|
{ |
|
if ( !IsPC() ) |
|
return; |
|
|
|
for ( int i=m_TextureList.First(); i != m_TextureList.InvalidIndex(); i=m_TextureList.Next( i ) ) |
|
{ |
|
ITextureInternal *pTex = m_TextureList[i]; |
|
|
|
pTex->ReloadFilesInList( pFilesToReload ); |
|
} |
|
} |
|
|
|
void CTextureManager::ReleaseTempRenderTargetBits( void ) |
|
{ |
|
if( IsX360() ) //only sane on 360 |
|
{ |
|
int iNext; |
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = iNext ) |
|
{ |
|
iNext = m_TextureList.Next( i ); |
|
|
|
if ( m_TextureList[i]->IsTempRenderTarget() ) |
|
{ |
|
m_TextureList[i]->ReleaseMemory(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CTextureManager::DebugPrintUsedTextures( void ) |
|
{ |
|
for ( int i = m_TextureList.First(); i != m_TextureList.InvalidIndex(); i = m_TextureList.Next( i ) ) |
|
{ |
|
ITextureInternal *pTexture = m_TextureList[i]; |
|
Msg( "Texture: '%s' RefCount: %d\n", pTexture->GetName(), pTexture->GetReferenceCount() ); |
|
} |
|
|
|
if ( m_TextureExcludes.Count() ) |
|
{ |
|
Msg( "\nExcluded Textures: (%d)\n", m_TextureExcludes.Count() ); |
|
for ( int i = m_TextureExcludes.First(); i != m_TextureExcludes.InvalidIndex(); i = m_TextureExcludes.Next( i ) ) |
|
{ |
|
char buff[256]; |
|
const char *pName = m_TextureExcludes.GetElementName( i ); |
|
V_snprintf( buff, sizeof( buff ), "Excluded: %d '%s' \n", m_TextureExcludes[i], pName ); |
|
|
|
// an excluded texture is valid, but forced tiny |
|
if ( IsTextureLoaded( pName ) ) |
|
{ |
|
Msg( "%s", buff ); |
|
} |
|
else |
|
{ |
|
// warn as unknown, could be a spelling error |
|
Warning( "%s", buff ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
int CTextureManager::FindNext( int iIndex, ITextureInternal **pTexInternal ) |
|
{ |
|
if ( iIndex == -1 && m_TextureList.Count() ) |
|
{ |
|
iIndex = m_TextureList.First(); |
|
} |
|
else if ( !m_TextureList.Count() || !m_TextureList.IsValidIndex( iIndex ) ) |
|
{ |
|
*pTexInternal = NULL; |
|
return -1; |
|
} |
|
|
|
*pTexInternal = m_TextureList[iIndex]; |
|
|
|
iIndex = m_TextureList.Next( iIndex ); |
|
if ( iIndex == m_TextureList.InvalidIndex() ) |
|
{ |
|
// end of list |
|
iIndex = -1; |
|
} |
|
|
|
return iIndex; |
|
} |
|
|
|
void CTextureManager::Update() |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
#ifdef STAGING_ONLY |
|
if ( mat_texture_list_dump.GetBool() ) |
|
{ |
|
DumpTextureList(); |
|
mat_texture_list_dump.SetValue( 0 ); |
|
} |
|
#endif |
|
|
|
if ( m_pAsyncReader ) |
|
m_pAsyncReader->ThreadMain_Update(); |
|
} |
|
|
|
// Load a texture asynchronously and then call the provided callback. |
|
void CTextureManager::AsyncFindOrLoadTexture( const char *pTextureName, const char *pTextureGroupName, IAsyncTextureOperationReceiver* pRecipient, void* pExtraArgs, bool bComplain, int nAdditionalCreationFlags ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
bool bStreamingRequest = ( nAdditionalCreationFlags & TEXTUREFLAGS_STREAMABLE ) != 0; |
|
|
|
ITextureInternal* pLoadedTex = FindTexture( pTextureName ); |
|
// It'd be weird to indicate that we're streaming and not actually have a texture that already exists. |
|
Assert( !bStreamingRequest || pLoadedTex != NULL ); |
|
|
|
if ( pLoadedTex ) |
|
{ |
|
if ( !bStreamingRequest ) |
|
{ |
|
if ( pLoadedTex->IsError() && bComplain ) |
|
DevWarning( "Texture '%s' not found.\n", pTextureName ); |
|
pRecipient->OnAsyncFindComplete( pLoadedTex, pExtraArgs ); |
|
SafeRelease( pRecipient ); |
|
return; |
|
} |
|
} |
|
|
|
AsyncLoadJob_t asyncLoad( pTextureName, pTextureGroupName, pRecipient, pExtraArgs, bComplain, nAdditionalCreationFlags ); |
|
|
|
// If this is the first person asking to load this, then remember so we don't load the same thing over and over again. |
|
int pendingIndex = m_PendingAsyncLoads.Find( pTextureName ); |
|
if ( pendingIndex == m_PendingAsyncLoads.InvalidIndex() ) |
|
{ |
|
// Create the texture here, we'll load the data in the async thread. Load is a misnomer, because it doesn't actually |
|
// load the data--Download does. |
|
if ( bStreamingRequest ) |
|
asyncLoad.m_pResultData = pLoadedTex; |
|
else |
|
asyncLoad.m_pResultData = LoadTexture( pTextureName, pTextureGroupName, nAdditionalCreationFlags, false ); |
|
AsyncLoad( asyncLoad ); |
|
pendingIndex = m_PendingAsyncLoads.Insert( pTextureName ); |
|
} |
|
else |
|
{ |
|
// If this is a thing we've seen before, just note that we also need it. |
|
m_PendingAsyncLoads[ pendingIndex ].AddToTail( asyncLoad ); |
|
} |
|
} |
|
|
|
void CTextureManager::CompleteAsyncLoad( AsyncLoadJob_t* pJob ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
Assert( pJob ); |
|
bool bDownloaded = false; |
|
|
|
if ( !IsJobCancelled( pJob ) ) |
|
{ |
|
// Perform the download. We did the read already. |
|
pJob->m_pResultData->Download( NULL, pJob->m_nAdditionalCreationFlags ); |
|
bDownloaded = true; |
|
} |
|
|
|
// Then notify the caller that they're finished. |
|
pJob->m_pRecipient->OnAsyncFindComplete( pJob->m_pResultData, pJob->m_pExtraArgs ); |
|
|
|
// Finally, deal with any other stragglers that asked for the same surface we did. |
|
int pendingIndex = m_PendingAsyncLoads.Find( pJob->m_TextureName.Get() ); |
|
Assert( pendingIndex != m_PendingAsyncLoads.InvalidIndex() ); |
|
|
|
FOR_EACH_VEC( m_PendingAsyncLoads[ pendingIndex ], i ) |
|
{ |
|
AsyncLoadJob_t& straggler = m_PendingAsyncLoads[ pendingIndex ][ i ]; |
|
straggler.m_pResultData = pJob->m_pResultData; |
|
|
|
if ( !bDownloaded && !IsJobCancelled( &straggler ) ) |
|
{ |
|
bDownloaded = true; |
|
straggler.m_pResultData->Download( NULL, straggler.m_nAdditionalCreationFlags ); |
|
} |
|
|
|
straggler.m_pRecipient->OnAsyncFindComplete( straggler.m_pResultData, straggler.m_pExtraArgs ); |
|
SafeRelease( &straggler.m_pRecipient ); |
|
} |
|
|
|
// Add ourselves to the list of loaded things. |
|
if ( bDownloaded ) |
|
{ |
|
// The texture list has to be protected by the materials lock. |
|
MaterialLock_t hMaterialLock = materials->Lock(); |
|
|
|
// It's possible that the texture wasn't actually unloaded, so we may have reloaded something unnecessarily. |
|
// If so, just don't re-add it. |
|
if ( m_TextureList.Find( pJob->m_pResultData->GetName() ) == m_TextureList.InvalidIndex() ) |
|
m_TextureList.Insert( pJob->m_pResultData->GetName(), pJob->m_pResultData ); |
|
|
|
materials->Unlock( hMaterialLock ); |
|
} |
|
else |
|
{ |
|
// If we didn't download, need to clean up the leftover file data that we loaded on the other thread |
|
pJob->m_pResultData->AsyncCancelReadTexture(); |
|
} |
|
|
|
// Can't release the Recipient until after we tell the stragglers, because the recipient may be the only |
|
// ref to the texture, and cleaning it up may clean up the texture but leave us with a seemingly valid pointer. |
|
SafeRelease( &pJob->m_pRecipient ); |
|
|
|
// Dump out the whole lot. |
|
m_PendingAsyncLoads.RemoveAt( pendingIndex ); |
|
} |
|
|
|
void CTextureManager::AsyncLoad( const AsyncLoadJob_t& job ) |
|
{ |
|
Assert( m_pAsyncLoader ); |
|
m_pAsyncLoader->AsyncLoad( job ); |
|
} |
|
|
|
void CTextureManager::AsyncCreateTextureFromRenderTarget( ITexture* pSrcRt, const char* pDstName, ImageFormat dstFmt, bool bGenMips, int nAdditionalCreationFlags, IAsyncTextureOperationReceiver* pRecipient, void* pExtraArgs ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
Assert( pSrcRt ); |
|
|
|
AsyncReadJob_t* pAsyncRead = new AsyncReadJob_t( pSrcRt, pDstName, dstFmt, bGenMips, nAdditionalCreationFlags, pRecipient, pExtraArgs ); |
|
AsyncReadTexture( pAsyncRead ); |
|
} |
|
|
|
void CTextureManager::CompleteAsyncRead( AsyncReadJob_t* pJob ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
// Release the texture back into the pool. |
|
ReleaseReadbackTexture( pJob->m_pSysmemTex ); |
|
pJob->m_pSysmemTex = NULL; |
|
|
|
int w = pJob->m_pSrcRt->GetActualWidth(); |
|
int h = pJob->m_pSrcRt->GetActualHeight(); |
|
|
|
int mips = pJob->m_bGenMips ? ImageLoader::GetNumMipMapLevels( w, h ) : 1; |
|
|
|
int nFlags = pJob->m_nAdditionalCreationFlags |
|
| TEXTUREFLAGS_SINGLECOPY |
|
| TEXTUREFLAGS_IGNORE_PICMIP |
|
| ( mips > 1 |
|
? TEXTUREFLAGS_ALL_MIPS |
|
: TEXTUREFLAGS_NOMIP |
|
) |
|
; |
|
|
|
// Create the texture |
|
ITexture* pFinalTex = materials->CreateNamedTextureFromBitsEx( pJob->m_pDstName, TEXTURE_GROUP_RUNTIME_COMPOSITE, w, h, mips, pJob->m_dstFmt, pJob->m_finalTexelData.Count(), pJob->m_finalTexelData.Base(), nFlags ); |
|
Assert( pFinalTex ); |
|
|
|
// Make the callback! |
|
pJob->m_pRecipient->OnAsyncCreateComplete( pFinalTex, pJob->m_pExtraArgs ); |
|
SafeRelease( &pJob->m_pSrcRt ); |
|
SafeRelease( &pJob->m_pRecipient ); |
|
SafeRelease( &pFinalTex ); |
|
} |
|
|
|
void CTextureManager::AsyncReadTexture( AsyncReadJob_t* pJob ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
Assert( m_pAsyncReader ); |
|
Assert( pJob ); |
|
|
|
pJob->m_pSysmemTex = AcquireReadbackTexture( pJob->m_pSrcRt->GetActualWidth(), pJob->m_pSrcRt->GetActualHeight(), pJob->m_pSrcRt->GetImageFormat() ); |
|
Assert( pJob->m_pSysmemTex ); |
|
|
|
if ( !pJob->m_pSysmemTex ) |
|
{ |
|
Assert( !"Need to deal with this error case" ); // TODOERROR |
|
return; |
|
} |
|
|
|
m_pAsyncReader->AsyncReadback( pJob ); |
|
} |
|
|
|
|
|
ITextureInternal* CTextureManager::AcquireReadbackTexture( int w, int h, ImageFormat fmt ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s-TryExisting", __FUNCTION__ ); |
|
MaterialLock_t hMaterialLock = materials->Lock(); |
|
|
|
FOR_EACH_VEC( m_ReadbackTextures, i ) |
|
{ |
|
ITextureInternal* pTex = m_ReadbackTextures[ i ]; |
|
Assert( pTex ); |
|
|
|
if ( pTex->GetActualWidth() == w |
|
&& pTex->GetActualHeight() == h |
|
&& pTex->GetImageFormat() == fmt ) |
|
{ |
|
// Found one in the cache already |
|
pTex->AddRef(); |
|
m_ReadbackTextures.Remove( i ); |
|
|
|
materials->Unlock( hMaterialLock ); |
|
return pTex; |
|
} |
|
} |
|
|
|
materials->Unlock( hMaterialLock ); |
|
} |
|
|
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s-CreateNew", __FUNCTION__ ); |
|
ITextureInternal* stagingTex = CreateProceduralTexture( "readbacktex", TEXTURE_GROUP_OTHER, w, h, 1, fmt, TEXTUREFLAGS_STAGING_MEMORY | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_IMMEDIATE_CLEANUP ); |
|
// AddRef here for caller. |
|
stagingTex->AddRef(); |
|
return stagingTex; |
|
} |
|
|
|
void CTextureManager::ReleaseReadbackTexture( ITextureInternal* pTex ) |
|
{ |
|
Assert( pTex ); |
|
|
|
MaterialLock_t hMaterialLock = materials->Lock(); |
|
// Release matching AddRef in AcquireReadbackTexture |
|
pTex->Release(); |
|
m_ReadbackTextures.AddToTail( pTex ); |
|
materials->Unlock( hMaterialLock ); |
|
} |
|
|
|
#ifdef STAGING_ONLY |
|
static int SortTexturesForDump( const CUtlPair< CUtlString, void* >* sz1, const CUtlPair< CUtlString, void* >* sz2 ) |
|
{ |
|
int sortVal = CUtlString::SortCaseSensitive( &sz1->first, &sz2->first ); |
|
if ( sortVal != 0 ) |
|
return sortVal; |
|
|
|
return int( ( int ) sz1->second - ( int ) sz2->second ); |
|
} |
|
|
|
void CTextureManager::DumpTextureList() |
|
{ |
|
CUtlVector< CUtlPair< CUtlString, void* > > textures; |
|
MaterialLock_t hMaterialLock = materials->Lock(); |
|
FOR_EACH_DICT( m_TextureList, i ) |
|
{ |
|
textures.AddToTail( MakeUtlPair( CUtlString( m_TextureList[i]->GetName() ), (void*) m_TextureList[i] ) ); |
|
} |
|
materials->Unlock( hMaterialLock ); |
|
|
|
// Now dump them out, sorted first by the texture name, then by address. |
|
textures.Sort( SortTexturesForDump ); |
|
FOR_EACH_VEC( textures, i ) |
|
{ |
|
CUtlPair< CUtlString, void* >& pair = textures[i]; |
|
Warning( "[%p]: %s\n", pair.second, pair.first.Get() ) ; |
|
} |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Warms the texture cache from a vpk. This will cause coarse mipmaps to be |
|
// available all the time, starting with mipmap level 3. This allows us to have |
|
// all the textures available all the time, but we only pay for fine levels when |
|
// we actually need them. |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::WarmTextureCache() |
|
{ |
|
// Disable cache for osx/linux for now. |
|
if ( CommandLine()->CheckParm( "-no_texture_stream" ) ) |
|
return; |
|
MemoryInformation memInfo; |
|
if ( GetMemoryInformation( &memInfo ) ) |
|
{ |
|
if ( memInfo.m_nPhysicalRamMbTotal <= 3584 ) |
|
return; |
|
} |
|
|
|
COM_TimestampedLog( "WarmTextureCache() - Begin" ); |
|
|
|
// If this fires, we need to relocate this elsewhere--there's no point in doing the loading |
|
// if we're not going to be able to download them right now. |
|
Assert( g_pShaderAPI->CanDownloadTextures() ); |
|
|
|
g_pFullFileSystem->AddSearchPath( "tf2_texture_cache.vpk", cTextureCachePathDir, PATH_ADD_TO_TAIL ); |
|
|
|
CUtlDict< int > filesToLoad( k_eDictCompareTypeCaseSensitive ); |
|
|
|
// TODO: Maybe work directly with VPK (still need to add to the filesystem for LoadTexture)? |
|
// CPackFile |
|
|
|
// Add the pak and then walk through the contents. |
|
FindFilesToLoad( &filesToLoad, "*.*" ); |
|
|
|
// Then add the list of files from the cache, which will deal with running without a VPK and also |
|
// allow us to add late stragglers. |
|
ReadFilesToLoad( &filesToLoad, "texture_preload_list.txt" ); |
|
|
|
if ( filesToLoad.Count() == 0 ) |
|
{ |
|
COM_TimestampedLog( "WarmTextureCache() - End (No files loaded)" ); |
|
return; |
|
} |
|
|
|
Assert( filesToLoad.Count() > 0 ); |
|
|
|
// Now read all of the files. |
|
// TODO: This needs to read in specific order to ensure peak performance. |
|
FOR_EACH_DICT( filesToLoad, i ) |
|
{ |
|
const char* pFilename = filesToLoad.GetElementName( i ); |
|
|
|
// Load the texture. This will only load the lower mipmap levels because that's the file we'll find now. |
|
ITextureInternal* pTex = LoadTexture( pFilename, TEXTURE_GROUP_PRECACHED, TEXTUREFLAGS_STREAMABLE_COARSE ); |
|
COM_TimestampedLog( "WarmTextureCache(): LoadTexture( %s ): Complete", pFilename ); |
|
|
|
if ( ( pTex->GetFlags() & TEXTUREFLAGS_STREAMABLE ) == 0 ) |
|
{ |
|
STAGING_ONLY_EXEC( Warning( "%s is listed in texture_preload_list.txt or is otherwise marked for streaming. It cannot be streamed and should be removed from the streaming system.\n", pFilename ) ); |
|
ITextureInternal::Destroy( pTex ); |
|
continue; |
|
} |
|
|
|
if ( !pTex->IsError() ) |
|
{ |
|
m_TextureList.Insert( pTex->GetName(), pTex ); |
|
pTex->AddRef(); |
|
m_preloadedTextures.AddToTail( pTex ); |
|
} |
|
else |
|
{ |
|
// Don't preload broken textures |
|
ITextureInternal::Destroy( pTex ); |
|
} |
|
} |
|
|
|
g_pFullFileSystem->RemoveSearchPath( "tf2_texture_cache.vpk", cTextureCachePathDir ); |
|
|
|
COM_TimestampedLog( "WarmTextureCache() - End" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Reads the list of files contained in the vpk loaded above, and adds them to the |
|
// list of files we need to load (passing in as pOutFilesToLoad). The map contains |
|
// the |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::FindFilesToLoad( CUtlDict< int >* pOutFilesToLoad, const char* pFilename ) |
|
{ |
|
Assert( pOutFilesToLoad != NULL ); |
|
|
|
FileFindHandle_t fh; |
|
pFilename = g_pFullFileSystem->FindFirstEx( pFilename, cTextureCachePathDir, &fh ); |
|
|
|
while ( pFilename != NULL ) |
|
{ |
|
if ( g_pFullFileSystem->FindIsDirectory( fh ) ) |
|
{ |
|
if ( pFilename[0] != '.' ) |
|
{ |
|
char childFilename[_MAX_PATH]; |
|
V_sprintf_safe( childFilename, "%s/*.*", pFilename ); |
|
FindFilesToLoad( pOutFilesToLoad, childFilename ); |
|
} |
|
} |
|
else |
|
{ |
|
char filenameNoExtension[_MAX_PATH]; |
|
V_StripExtension( pFilename, filenameNoExtension, _MAX_PATH ); |
|
// Add the file to the list, which we will later traverse in order to ensure we're hitting these in the expected order for the VPK. |
|
( *pOutFilesToLoad ).Insert( CUtlString( filenameNoExtension ), 0 ); |
|
} |
|
|
|
pFilename = g_pFullFileSystem->FindNext( fh ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Read the contents of pFilename, which should just be a list of texture names |
|
// that we should load. |
|
//----------------------------------------------------------------------------- |
|
void CTextureManager::ReadFilesToLoad( CUtlDict< int >* pOutFilesToLoad, const char* pFilename ) |
|
{ |
|
Assert( pOutFilesToLoad != NULL ); |
|
|
|
FileHandle_t fh = g_pFullFileSystem->Open( pFilename, "r" ); |
|
if ( !fh ) |
|
return; |
|
|
|
CUtlBuffer fileContents( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
if ( !g_pFullFileSystem->ReadToBuffer( fh, fileContents ) ) |
|
goto cleanup; |
|
|
|
char buffer[_MAX_PATH + 1]; |
|
while ( 1 ) |
|
{ |
|
fileContents.GetLine( buffer, _MAX_PATH ); |
|
if ( buffer[ 0 ] == 0 ) |
|
break; |
|
|
|
V_StripWhitespace( buffer ); |
|
|
|
if ( buffer[ 0 ] == 0 ) |
|
continue; |
|
|
|
// If it's not in the map already, add it. |
|
if ( pOutFilesToLoad->Find( buffer ) == pOutFilesToLoad->InvalidIndex() ) |
|
( *pOutFilesToLoad ).Insert( buffer, 0 ); |
|
} |
|
|
|
cleanup: |
|
g_pFullFileSystem->Close( fh ); |
|
} |
|
|
|
void CTextureManager::UpdatePostAsync() |
|
{ |
|
TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); |
|
|
|
// Update the async loader, which affects streaming in (streaming out is handled below). |
|
// Both stream in and stream out have to happen while the async job is not running because |
|
// they muck with shaderapi texture handles which could be in use if the async job is currently |
|
// being run |
|
if ( m_pAsyncLoader ) |
|
m_pAsyncLoader->ThreadMain_Update(); |
|
|
|
// First, move everything from the async request queue to active list |
|
ITextureInternal* pRequest = NULL; |
|
while ( m_asyncStreamingRequests.PopItem( &pRequest ) ) |
|
{ |
|
Assert( pRequest != NULL ); |
|
|
|
// Update the LOD bias to smoothly stream the texture in. We only need to do this on frames that |
|
// we actually have been requested to draw--other frames it doesn't matter (see, because we're not drawing?) |
|
pRequest->UpdateLodBias(); |
|
m_textureStreamingRequests.InsertOrReplace( pRequest, g_FrameNum ); |
|
} |
|
|
|
// Then update streaming |
|
const int cThirtySecondsOrSoInFrames = 2000; |
|
|
|
// First, remove old stuff. |
|
FOR_EACH_MAP_FAST( m_textureStreamingRequests, i ) |
|
{ |
|
if ( m_textureStreamingRequests[ i ] + cThirtySecondsOrSoInFrames < g_FrameNum ) |
|
{ |
|
ITextureInternal* pTex = m_textureStreamingRequests.Key( i ); |
|
|
|
// It's been awhile since we were asked to full res this texture, so let's evict |
|
// if it's still full res. |
|
|
|
if ( pTex->GetTargetResidence() == RESIDENT_FULL ) |
|
pTex->MakeResident( RESIDENT_PARTIAL ); |
|
|
|
m_textureStreamingRequests.RemoveAt( i ); |
|
} |
|
} |
|
|
|
// Then, start allowing new stuff to ask for data. |
|
FOR_EACH_MAP_FAST( m_textureStreamingRequests, i ) |
|
{ |
|
int requestFrame = m_textureStreamingRequests[ i ]; |
|
|
|
if ( g_FrameNum == requestFrame ) |
|
{ |
|
ITextureInternal* pTex = m_textureStreamingRequests.Key( i ); |
|
|
|
if ( pTex->GetTargetResidence() == RESIDENT_FULL ) |
|
continue; |
|
|
|
// TODO: What to do if this fails? Auto-reask next frame? |
|
pTex->MakeResident( RESIDENT_FULL ); |
|
} |
|
} |
|
|
|
// Finally, flush any immediate release textures marked for cleanup that are still unreferenced. |
|
CleanupPossiblyUnreferencedTextures(); |
|
} |
|
|
|
void CTextureManager::ReleaseAsyncScratchVTF( IVTFTexture *pScratchVTF ) |
|
{ |
|
Assert( m_pAsyncLoader != NULL && pScratchVTF != NULL ); |
|
m_pAsyncLoader->ReleaseAsyncReadBuffer( pScratchVTF ); |
|
} |
|
|
|
bool CTextureManager::ThreadInAsyncLoadThread() const |
|
{ |
|
return ThreadGetCurrentId() == m_nAsyncLoadThread; |
|
} |
|
|
|
bool CTextureManager::ThreadInAsyncReadThread() const |
|
{ |
|
return ThreadGetCurrentId() == m_nAsyncReadThread; |
|
} |
|
|
|
bool CTextureManager::AddTextureCompositorTemplate( const char* pName, KeyValues* pTmplDesc ) |
|
{ |
|
Assert( pName && pTmplDesc ); |
|
|
|
int ndx = m_TexCompTemplates.Find( pName ); |
|
if ( ndx != m_TexCompTemplates.InvalidIndex() ) |
|
{ |
|
// Later definitions stomp earlier ones. This lets the GC win. |
|
delete m_TexCompTemplates[ ndx ]; |
|
m_TexCompTemplates.RemoveAt( ndx ); |
|
} |
|
|
|
CTextureCompositorTemplate* pNewTmpl = CTextureCompositorTemplate::Create( pName, pTmplDesc ); |
|
|
|
// If this is the case, the logging has already been done. |
|
if ( pNewTmpl == NULL ) |
|
return false; |
|
|
|
m_TexCompTemplates.Insert( pName, pNewTmpl ); |
|
return true; |
|
} |
|
|
|
bool CTextureManager::VerifyTextureCompositorTemplates() |
|
{ |
|
TM_ZONE_DEFAULT( TELEMETRY_LEVEL1 ); |
|
|
|
bool allSuccess = true; |
|
|
|
FOR_EACH_DICT_FAST( m_TexCompTemplates, i ) |
|
{ |
|
if ( m_TexCompTemplates[ i ]->ResolveDependencies() ) |
|
{ |
|
if ( m_TexCompTemplates[ i ]->HasDependencyCycles() ) |
|
{ |
|
allSuccess = false; |
|
} |
|
} |
|
else |
|
{ |
|
allSuccess = false; |
|
} |
|
} |
|
|
|
return allSuccess; |
|
} |
|
|
|
|
|
CTextureCompositorTemplate* CTextureManager::FindTextureCompositorTemplate( const char* pName ) |
|
{ |
|
unsigned short i = m_TexCompTemplates.Find( pName ); |
|
if ( m_TexCompTemplates.IsValidIndex( i ) ) |
|
return m_TexCompTemplates[ i ]; |
|
|
|
return NULL; |
|
} |
|
|
|
bool CTextureManager::HasPendingTextureDestroys() const |
|
{ |
|
return m_PossiblyUnreferencedTextures.Count() != 0; |
|
} |
|
|
|
void CTextureManager::CoolTextureCache() |
|
{ |
|
FOR_EACH_VEC( m_preloadedTextures, i ) |
|
{ |
|
m_preloadedTextures[ i ]->Release(); |
|
} |
|
|
|
m_preloadedTextures.RemoveAll(); |
|
} |
|
|
|
void CTextureManager::RequestAllMipmaps( ITextureInternal* pTex ) |
|
{ |
|
Assert( pTex ); |
|
|
|
// Don't mark these for load if suspended |
|
if ( m_iSuspendTextureStreaming ) |
|
return; |
|
|
|
unsigned int nTexFlags = pTex->GetFlags(); |
|
|
|
// If this isn't a streamable texture or if there are no mipmaps, there's nothing to do. |
|
if ( !( nTexFlags & TEXTUREFLAGS_STREAMABLE ) || ( nTexFlags & TEXTUREFLAGS_NOMIP ) ) |
|
return; |
|
|
|
m_asyncStreamingRequests.PushItem( pTex ); |
|
} |
|
|
|
void CTextureManager::EvictAllTextures() |
|
{ |
|
FOR_EACH_DICT_FAST( m_TextureList, i ) |
|
{ |
|
ITextureInternal* pTex = m_TextureList[ i ]; |
|
if ( !pTex ) |
|
continue; |
|
|
|
// If the fine mipmaps are present |
|
if ( ( ( pTex->GetFlags() & TEXTUREFLAGS_STREAMABLE ) != 0 ) && pTex->GetTargetResidence() == RESIDENT_FULL ) |
|
pTex->MakeResident( RESIDENT_PARTIAL ); |
|
} |
|
} |
|
|
|
CON_COMMAND( mat_evict_all, "Evict all fine mipmaps from the gpu" ) |
|
{ |
|
TextureManager()->EvictAllTextures(); |
|
} |
|
|
|
// ------------------------------------------------------------------------------------------------ |
|
// ------------------------------------------------------------------------------------------------ |
|
// ------------------------------------------------------------------------------------------------ |
|
static ImageFormat GetImageFormatRawReadback( ImageFormat fmt ) |
|
{ |
|
switch ( fmt ) |
|
{ |
|
case IMAGE_FORMAT_RGBA8888: |
|
return IMAGE_FORMAT_BGRA8888; |
|
case IMAGE_FORMAT_BGRA8888: |
|
return IMAGE_FORMAT_BGRA8888; |
|
default: |
|
Assert( !"Unsupported format in GetImageFormatRawReadback, this will likely result in color-swapped textures" ); |
|
}; |
|
|
|
return fmt; |
|
} |
|
|
|
|