summaryrefslogtreecommitdiff
path: root/materialsystem/texturemanager.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /materialsystem/texturemanager.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'materialsystem/texturemanager.cpp')
-rw-r--r--materialsystem/texturemanager.cpp3102
1 files changed, 3102 insertions, 0 deletions
diff --git a/materialsystem/texturemanager.cpp b/materialsystem/texturemanager.cpp
new file mode 100644
index 0000000..eb5be14
--- /dev/null
+++ b/materialsystem/texturemanager.cpp
@@ -0,0 +1,3102 @@
+//========= 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:
+ 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;
+}
+