//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=====================================================================================// #ifdef PROTECTED_THINGS_ENABLE #undef PROTECTED_THINGS_ENABLE #endif #include "platform.h" // HACK: Need ShellExecute for PSD updates #ifdef IS_WINDOWS_PC #include #include #pragma comment ( lib, "shell32" ) #endif #include "materialsystem_global.h" #include "shaderapi/ishaderapi.h" #include "itextureinternal.h" #include "utlsymbol.h" #include "time.h" #include #include #include "bitmap/imageformat.h" #include "bitmap/tgaloader.h" #include "bitmap/tgawriter.h" #ifdef _WIN32 #include "direct.h" #endif #include "colorspace.h" #include "string.h" #include #include #include "utlmemory.h" #include "IHardwareConfigInternal.h" #include "filesystem.h" #include "tier1/strtools.h" #include "vtf/vtf.h" #include "materialsystem/materialsystem_config.h" #include "mempool.h" #include "texturemanager.h" #include "utlbuffer.h" #include "pixelwriter.h" #include "tier1/callqueue.h" #include "tier1/UtlStringMap.h" #include "filesystem/IQueuedLoader.h" #include "tier2/fileutils.h" #include "filesystem.h" #include "tier2/p4helpers.h" #include "tier2/tier2.h" #include "p4lib/ip4.h" #include "ctype.h" #include "ifilelist.h" #include "tier0/icommandline.h" #include "tier0/vprof.h" // NOTE: This must be the last file included!!! #include "tier0/memdbgon.h" // this allows the command line to force the "all mips" flag to on for all textures bool g_bForceTextureAllMips = false; #if defined(IS_WINDOWS_PC) static void ConVarChanged_mat_managedtextures( IConVar *var, const char *pOldValue, float flOldValue ); static ConVar mat_managedtextures( "mat_managedtextures", "1", FCVAR_ARCHIVE, "If set, allows Direct3D to manage texture uploading at the cost of extra system memory", &ConVarChanged_mat_managedtextures ); static void ConVarChanged_mat_managedtextures( IConVar *var, const char *pOldValue, float flOldValue ) { if ( mat_managedtextures.GetBool() != (flOldValue != 0) ) { materials->ReleaseResources(); materials->ReacquireResources(); } } #endif static ConVar mat_spew_on_texture_size( "mat_spew_on_texture_size", "0", 0, "Print warnings about vtf content that isn't of the expected size" ); static ConVar mat_lodin_time( "mat_lodin_time", "5.0", FCVAR_DEVELOPMENTONLY ); static ConVar mat_lodin_hidden_pop( "mat_lodin_hidden_pop", "1", FCVAR_DEVELOPMENTONLY ); #define TEXTURE_FNAME_EXTENSION ".vtf" #define TEXTURE_FNAME_EXTENSION_LEN 4 #define TEXTURE_FNAME_EXTENSION_NORMAL "_normal.vtf" #ifdef STAGING_ONLY ConVar mat_spewalloc( "mat_spewalloc", "0" ); #else ConVar mat_spewalloc( "mat_spewalloc", "0", FCVAR_ARCHIVE | FCVAR_DEVELOPMENTONLY ); #endif struct TexDimensions_t { uint16 m_nWidth; uint16 m_nHeight; uint16 m_nMipCount; uint16 m_nDepth; TexDimensions_t( uint16 nWidth = 0, uint nHeight = 0, uint nMipCount = 0, uint16 nDepth = 1 ) : m_nWidth( nWidth ) , m_nHeight( nHeight ) , m_nMipCount( nMipCount ) , m_nDepth( nDepth ) { } }; #ifdef STAGING_ONLY struct TexInfo_t { CUtlString m_Name; unsigned short m_nWidth; unsigned short m_nHeight; unsigned short m_nDepth; unsigned short m_nMipCount; unsigned short m_nFrameCount; unsigned short m_nCopies; ImageFormat m_Format; uint64 ComputeTexSize() const { uint64 total = 0; unsigned short width = m_nWidth; unsigned short height = m_nHeight; unsigned short depth = m_nDepth; for ( int mip = 0; mip < m_nMipCount; ++mip ) { // Make sure that mip count lines up with the count Assert( width > 1 || height > 1 || depth > 1 || ( mip == ( m_nMipCount - 1 ) ) ); total += ImageLoader::GetMemRequired( width, height, depth, m_Format, false ); width = Max( 1, width >> 1 ); height = Max( 1, height >> 1 ); depth = Max( 1, depth >> 1 ); } return total * Min( (unsigned short) 1, m_nFrameCount ) * Min( (unsigned short) 1, m_nCopies ); } TexInfo_t( const char* name = "", unsigned short w = 0, unsigned short h = 0, unsigned short d = 0, unsigned short mips = 0, unsigned short frames = 0, unsigned short copies = 0, ImageFormat fmt = IMAGE_FORMAT_UNKNOWN ) : m_nWidth( w ) , m_nHeight( h ) , m_nDepth( d ) , m_nMipCount( mips ) , m_nFrameCount( frames ) , m_nCopies( copies ) , m_Format( fmt ) { if ( name && name[0] ) m_Name = name; else m_Name = ""; } }; CUtlMap< ITexture*, TexInfo_t > g_currentTextures( DefLessFunc( ITexture* ) ); #endif //----------------------------------------------------------------------------- // Internal texture flags //----------------------------------------------------------------------------- enum InternalTextureFlags { TEXTUREFLAGSINTERNAL_ERROR = 0x00000001, TEXTUREFLAGSINTERNAL_ALLOCATED = 0x00000002, TEXTUREFLAGSINTERNAL_PRELOADED = 0x00000004, // 360: textures that went through the preload process TEXTUREFLAGSINTERNAL_QUEUEDLOAD = 0x00000008, // 360: load using the queued loader TEXTUREFLAGSINTERNAL_EXCLUDED = 0x00000020, // actual exclusion state TEXTUREFLAGSINTERNAL_SHOULDEXCLUDE = 0x00000040, // desired exclusion state TEXTUREFLAGSINTERNAL_TEMPRENDERTARGET = 0x00000080, // 360: should only allocate texture bits upon first resolve, destroy at level end }; static int GetThreadId(); static bool SLoadTextureBitsFromFile( IVTFTexture **ppOutVtfTexture, FileHandle_t hFile, unsigned int nFlags, TextureLODControlSettings_t* pInOutCachedFileLodSettings, int nDesiredDimensionLimit, unsigned short* pOutStreamedMips, const char* pName, const char* pCacheFileName, TexDimensions_t* pOptOutMappingDims = NULL, TexDimensions_t* pOptOutActualDims = NULL, TexDimensions_t* pOptOutAllocatedDims = NULL, unsigned int* pOptOutStripFlags = NULL ); static int ComputeActualMipCount( const TexDimensions_t& actualDims, unsigned int nFlags ); static int ComputeMipSkipCount( const char* pName, const TexDimensions_t& mappingDims, bool bIgnorePicmip, IVTFTexture *pOptVTFTexture, unsigned int nFlags, int nDesiredDimensionLimit, unsigned short* pOutStreamedMips, TextureLODControlSettings_t* pInOutCachedFileLodSettings, TexDimensions_t* pOptOutActualDims, TexDimensions_t* pOptOutAllocatedDims, unsigned int* pOptOutStripFlags ); static int GetOptimalReadBuffer( CUtlBuffer *pOutOptimalBuffer, FileHandle_t hFile, int nFileSize ); static void FreeOptimalReadBuffer( int nMaxSize ); //----------------------------------------------------------------------------- // Use Warning to show texture flags. //----------------------------------------------------------------------------- static void PrintFlags( unsigned int flags ) { if ( flags & TEXTUREFLAGS_NOMIP ) { Warning( "TEXTUREFLAGS_NOMIP|" ); } if ( flags & TEXTUREFLAGS_NOLOD ) { Warning( "TEXTUREFLAGS_NOLOD|" ); } if ( flags & TEXTUREFLAGS_SRGB ) { Warning( "TEXTUREFLAGS_SRGB|" ); } if ( flags & TEXTUREFLAGS_POINTSAMPLE ) { Warning( "TEXTUREFLAGS_POINTSAMPLE|" ); } if ( flags & TEXTUREFLAGS_TRILINEAR ) { Warning( "TEXTUREFLAGS_TRILINEAR|" ); } if ( flags & TEXTUREFLAGS_CLAMPS ) { Warning( "TEXTUREFLAGS_CLAMPS|" ); } if ( flags & TEXTUREFLAGS_CLAMPT ) { Warning( "TEXTUREFLAGS_CLAMPT|" ); } if ( flags & TEXTUREFLAGS_HINT_DXT5 ) { Warning( "TEXTUREFLAGS_HINT_DXT5|" ); } if ( flags & TEXTUREFLAGS_ANISOTROPIC ) { Warning( "TEXTUREFLAGS_ANISOTROPIC|" ); } if ( flags & TEXTUREFLAGS_PROCEDURAL ) { Warning( "TEXTUREFLAGS_PROCEDURAL|" ); } if ( flags & TEXTUREFLAGS_ALL_MIPS ) { Warning( "TEXTUREFLAGS_ALL_MIPS|" ); } if ( flags & TEXTUREFLAGS_SINGLECOPY ) { Warning( "TEXTUREFLAGS_SINGLECOPY|" ); } if ( flags & TEXTUREFLAGS_STAGING_MEMORY ) { Warning( "TEXTUREFLAGS_STAGING_MEMORY|" ); } if ( flags & TEXTUREFLAGS_IGNORE_PICMIP ) { Warning( "TEXTUREFLAGS_IGNORE_PICMIP|" ); } if ( flags & TEXTUREFLAGS_IMMEDIATE_CLEANUP ) { Warning( "TEXTUREFLAGS_IMMEDIATE_CLEANUP|" ); } } namespace TextureLodOverride { struct OverrideInfo { OverrideInfo() : x( 0 ), y( 0 ) {} OverrideInfo( int8 x_, int8 y_ ) : x( x_ ), y( y_ ) {} int8 x, y; }; // Override map typedef CUtlStringMap< OverrideInfo > OverrideMap_t; OverrideMap_t s_OverrideMap; // Retrieves the override info adjustments OverrideInfo Get( char const *szName ) { UtlSymId_t idx = s_OverrideMap.Find( szName ); if ( idx != s_OverrideMap.InvalidIndex() ) return s_OverrideMap[ idx ]; else return OverrideInfo(); } // Combines the existing override info adjustments with the given one void Add( char const *szName, OverrideInfo oi ) { OverrideInfo oiex = Get( szName ); oiex.x += oi.x; oiex.y += oi.y; s_OverrideMap[ szName ] = oiex; } }; // end namespace TextureLodOverride class CTextureStreamingJob; //----------------------------------------------------------------------------- // Base texture class //----------------------------------------------------------------------------- class CTexture : public ITextureInternal { public: CTexture(); virtual ~CTexture(); virtual const char *GetName( void ) const; const char *GetTextureGroupName( void ) const; // Stats about the texture itself virtual ImageFormat GetImageFormat() const; NormalDecodeMode_t GetNormalDecodeMode() const { return NORMAL_DECODE_NONE; } virtual int GetMappingWidth() const; virtual int GetMappingHeight() const; virtual int GetActualWidth() const; virtual int GetActualHeight() const; virtual int GetNumAnimationFrames() const; virtual bool IsTranslucent() const; virtual void GetReflectivity( Vector& reflectivity ); // Reference counting virtual void IncrementReferenceCount( ); virtual void DecrementReferenceCount( ); virtual int GetReferenceCount( ); // Used to modify the texture bits (procedural textures only) virtual void SetTextureRegenerator( ITextureRegenerator *pTextureRegen ); // Little helper polling methods virtual bool IsNormalMap( ) const; virtual bool IsCubeMap( void ) const; virtual bool IsRenderTarget( ) const; virtual bool IsTempRenderTarget( void ) const; virtual bool IsProcedural() const; virtual bool IsMipmapped() const; virtual bool IsError() const; // For volume textures virtual bool IsVolumeTexture() const; virtual int GetMappingDepth() const; virtual int GetActualDepth() const; // Various ways of initializing the texture void InitFileTexture( const char *pTextureFile, const char *pTextureGroupName ); void InitProceduralTexture( const char *pTextureName, const char *pTextureGroupName, int w, int h, int d, ImageFormat fmt, int nFlags, ITextureRegenerator* generator = NULL ); // Releases the texture's hw memory void ReleaseMemory(); virtual void OnRestore(); // Sets the filtering modes on the texture we're modifying void SetFilteringAndClampingMode( bool bOnlyLodValues = false ); void Download( Rect_t *pRect = NULL, int nAdditionalCreationFlags = 0 ); // Loads up information about the texture virtual void Precache(); // FIXME: Bogus methods... can we please delete these? virtual void GetLowResColorSample( float s, float t, float *color ) const; // Gets texture resource data of the specified type. // Params: // eDataType type of resource to retrieve. // pnumBytes on return is the number of bytes available in the read-only data buffer or is undefined // Returns: // pointer to the resource data, or NULL. Note that the data from this pointer can disappear when // the texture goes away - you want to copy this data! virtual void *GetResourceData( uint32 eDataType, size_t *pNumBytes ) const; virtual int GetApproximateVidMemBytes( void ) const; // Stretch blit the framebuffer into this texture. virtual void CopyFrameBufferToMe( int nRenderTargetID = 0, Rect_t *pSrcRect = NULL, Rect_t *pDstRect = NULL ); virtual void CopyMeToFrameBuffer( int nRenderTargetID = 0, Rect_t *pSrcRect = NULL, Rect_t *pDstRect = NULL ); virtual ITexture *GetEmbeddedTexture( int nIndex ); // Get the shaderapi texture handle associated w/ a particular frame virtual ShaderAPITextureHandle_t GetTextureHandle( int nFrame, int nChannel = 0 ); // Sets the texture as the render target virtual void Bind( Sampler_t sampler ); virtual void Bind( Sampler_t sampler1, int nFrame, Sampler_t sampler2 = (Sampler_t) -1 ); virtual void BindVertexTexture( VertexTextureSampler_t stage, int nFrame ); // Set this texture as a render target bool SetRenderTarget( int nRenderTargetID ); // Set this texture as a render target (optionally set depth texture as depth buffer as well) bool SetRenderTarget( int nRenderTargetID, ITexture *pDepthTexture); virtual void MarkAsPreloaded( bool bSet ); virtual bool IsPreloaded() const; virtual void MarkAsExcluded( bool bSet, int nDimensionsLimit ); virtual bool UpdateExcludedState( void ); // Retrieve the vtf flags mask virtual unsigned int GetFlags( void ) const; virtual void ForceLODOverride( int iNumLodsOverrideUpOrDown ); void GetFilename( char *pOut, int maxLen ) const; virtual void ReloadFilesInList( IFileList *pFilesToReload ); // Save texture to a file. virtual bool SaveToFile( const char *fileName ); // Load the texture from a file. bool AsyncReadTextureFromFile( IVTFTexture* pVTFTexture, unsigned int nAdditionalCreationFlags ); void AsyncCancelReadTexture( ); virtual void Map( void** pOutBits, int* pOutPitch ); virtual void Unmap(); virtual ResidencyType_t GetCurrentResidence() const { return m_residenceCurrent; } virtual ResidencyType_t GetTargetResidence() const { return m_residenceTarget; } virtual bool MakeResident( ResidencyType_t newResidence ); virtual void UpdateLodBias(); protected: bool IsDepthTextureFormat( ImageFormat fmt ); void ReconstructTexture( bool bCopyFromCurrent ); void GetCacheFilename( char* pOutBuffer, int bufferSize ) const; bool GetFileHandle( FileHandle_t *pOutFileHandle, char *pCacheFilename, char **ppResolvedFilename ) const; void ReconstructPartialTexture( const Rect_t *pRect ); bool HasBeenAllocated() const; void WriteDataToShaderAPITexture( int nFrameCount, int nFaceCount, int nFirstFace, int nMipCount, IVTFTexture *pVTFTexture, ImageFormat fmt ); // Initializes/shuts down the texture void Init( int w, int h, int d, ImageFormat fmt, int iFlags, int iFrameCount ); void Shutdown(); // Sets the texture name void SetName( const char* pName ); // Assigns/releases texture IDs for our animation frames void AllocateTextureHandles( ); void ReleaseTextureHandles( ); // Calculates info about whether we can make the texture smaller and by how much // Returns the number of skipped mip levels int ComputeActualSize( bool bIgnorePicmip = false, IVTFTexture *pVTFTexture = NULL, bool bTextureMigration = false ); // Computes the actual format of the texture given a desired src format ImageFormat ComputeActualFormat( ImageFormat srcFormat ); // Creates/releases the shader api texture bool AllocateShaderAPITextures(); void FreeShaderAPITextures(); void MigrateShaderAPITextures(); void NotifyUnloadedFile(); // Download bits void DownloadTexture( Rect_t *pRect, bool bCopyFromCurrent = false ); void ReconstructTextureBits(Rect_t *pRect); // Gets us modifying a particular frame of our texture void Modify( int iFrame ); // Sets the texture clamping state on the currently modified frame void SetWrapState( ); // Sets the texture filtering state on the currently modified frame void SetFilterState(); // Sets the lod state on the currently modified frame void SetLodState(); // Loads the texture bits from a file. Optionally provides absolute path IVTFTexture *LoadTextureBitsFromFile( char *pCacheFileName, char **pResolvedFilename ); IVTFTexture *HandleFileLoadFailedTexture( IVTFTexture *pVTFTexture ); // Generates the procedural bits IVTFTexture *ReconstructProceduralBits( ); IVTFTexture *ReconstructPartialProceduralBits( const Rect_t *pRect, Rect_t *pActualRect ); // Sets up debugging texture bits, if appropriate bool SetupDebuggingTextures( IVTFTexture *pTexture ); // Generate a texture that shows the various mip levels void GenerateShowMipLevelsTextures( IVTFTexture *pTexture ); void Cleanup( void ); // Converts a source image read from disk into its actual format bool ConvertToActualFormat( IVTFTexture *pTexture ); // Builds the low-res image from the texture void LoadLowResTexture( IVTFTexture *pTexture ); void CopyLowResImageToTexture( IVTFTexture *pTexture ); void GetDownloadFaceCount( int &nFirstFace, int &nFaceCount ); void ComputeMipLevelSubRect( const Rect_t* pSrcRect, int nMipLevel, Rect_t *pSubRect ); IVTFTexture *GetScratchVTFTexture( ); void ReleaseScratchVTFTexture( IVTFTexture* tex ); void ApplyRenderTargetSizeMode( int &width, int &height, ImageFormat fmt ); virtual void CopyToStagingTexture( ITexture* pDstTex ); virtual void SetErrorTexture( bool _isErrorTexture ); // Texture streaming void MakeNonResident(); void MakePartiallyResident(); bool MakeFullyResident(); void CancelStreamingJob( bool bJobMustExist = true ); void OnStreamingJobComplete( ResidencyType_t newResidenceCurrent ); protected: #ifdef _DEBUG char *m_pDebugName; #endif // Reflectivity vector Vector m_vecReflectivity; CUtlSymbol m_Name; // What texture group this texture is in (winds up setting counters based on the group name, // then the budget panel views the counters). CUtlSymbol m_TextureGroupName; unsigned int m_nFlags; unsigned int m_nInternalFlags; CInterlockedInt m_nRefCount; // This is the *desired* image format, which may or may not represent reality ImageFormat m_ImageFormat; // mapping dimensions and actual dimensions can/will vary due to user settings, hardware support, etc. // Allocated is what is physically allocated on the hardware at this instant, and considers texture streaming. TexDimensions_t m_dimsMapping; TexDimensions_t m_dimsActual; TexDimensions_t m_dimsAllocated; // This is the iWidth/iHeight for whatever is downloaded to the card, ignoring current streaming settings // Some callers want to know how big the texture is if all data was present, and that's this. // TODO: Rename this before check in. unsigned short m_nFrameCount; // These are the values for what is truly allocated on the card, including streaming settings. unsigned short m_nStreamingMips; unsigned short m_nOriginalRTWidth; // The values they initially specified. We generated a different width unsigned short m_nOriginalRTHeight; // and height based on screen size and the flags they specify. unsigned char m_LowResImageWidth; unsigned char m_LowResImageHeight; unsigned short m_nDesiredDimensionLimit; // part of texture exclusion unsigned short m_nActualDimensionLimit; // value not necessarily accurate, but mismatch denotes dirty state // m_pStreamingJob is refcounted, but it is not safe to call SafeRelease directly on it--you must call // CancelStreamingJob to ensure that releasing it doesn't cause a crash. CTextureStreamingJob* m_pStreamingJob; IVTFTexture* m_pStreamingVTF; ResidencyType_t m_residenceTarget; ResidencyType_t m_residenceCurrent; int m_lodClamp; int m_lastLodBiasAdjustFrame; float m_lodBiasInitial; float m_lodBiasCurrent; double m_lodBiasStartTime; // If the read failed, this will be true. We can't just return from the function because the call may // happen in the async thread. bool m_bStreamingFileReadFailed; // The set of texture ids for each animation frame ShaderAPITextureHandle_t *m_pTextureHandles; TextureLODControlSettings_t m_cachedFileLodSettings; // lowresimage info - used for getting color data from a texture // without having a huge system mem overhead. // FIXME: We should keep this in compressed form. .is currently decompressed at load time. unsigned char *m_pLowResImage; ITextureRegenerator *m_pTextureRegenerator; // Used to help decide whether or not to recreate the render target if AA changes. RenderTargetType_t m_nOriginalRenderTargetType; RenderTargetSizeMode_t m_RenderTargetSizeMode; // Fixed-size allocator // DECLARE_FIXEDSIZE_ALLOCATOR( CTexture ); public: void InitRenderTarget( const char *pRTName, int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, RenderTargetType_t type, unsigned int textureFlags, unsigned int renderTargetFlags ); virtual void DeleteIfUnreferenced(); void FixupTexture( const void *pData, int nSize, LoaderError_t loaderError ); void SwapContents( ITexture *pOther ); protected: // private data, generally from VTF resource extensions struct DataChunk { void Allocate( unsigned int numBytes ) { m_pvData = new unsigned char[ numBytes ]; m_numBytes = numBytes; } void Deallocate() const { delete [] m_pvData; } unsigned int m_eType; unsigned int m_numBytes; unsigned char *m_pvData; }; CUtlVector< DataChunk > m_arrDataChunks; struct ScratchVTF { ScratchVTF( CTexture* _tex ) : m_pParent( _tex ), m_pScratchVTF( _tex->GetScratchVTFTexture( ) ) { } ~ScratchVTF( ) { if ( m_pScratchVTF ) m_pParent->ReleaseScratchVTFTexture( m_pScratchVTF ); m_pScratchVTF = NULL; } IVTFTexture* Get() const { return m_pScratchVTF; } void TakeOwnership() { m_pScratchVTF = NULL; } CTexture* m_pParent; IVTFTexture* m_pScratchVTF; }; friend class CTextureStreamingJob; }; class CTextureStreamingJob : public IAsyncTextureOperationReceiver { public: CTextureStreamingJob( CTexture* pTex ) : m_referenceCount( 0 ), m_pOwner( pTex ) { Assert( m_pOwner != NULL ); m_pOwner->AddRef(); } virtual ~CTextureStreamingJob() { SafeRelease( &m_pOwner ); } virtual int AddRef() OVERRIDE { return ++m_referenceCount; } virtual int Release() OVERRIDE { int retVal = --m_referenceCount; Assert( retVal >= 0 ); if ( retVal == 0 ) { delete this; } return retVal; } virtual int GetRefCount() const OVERRIDE { return m_referenceCount; } virtual void OnAsyncCreateComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { Assert( !"unimpl" ); } virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE; virtual void OnAsyncMapComplete( ITexture* pTex, void* pExtraArgs, void* pMemory, int nPitch ) { Assert( !"unimpl" ); } virtual void OnAsyncReadbackBegin( ITexture* pDst, ITexture* pSrc, void* pExtraArgs ) OVERRIDE { Assert( !"unimpl" ); } void ForgetOwner( ITextureInternal* pTex ) { Assert( pTex == m_pOwner ); SafeRelease( &m_pOwner ); } private: CInterlockedInt m_referenceCount; CTexture* m_pOwner; }; ////////////////////////////////////////////////////////////////////////// // // CReferenceToHandleTexture is a special implementation of ITexture // to be used solely for binding the texture handle when rendering. // It is used when a D3D texture handle is available, but should be used // at a higher level of abstraction requiring an ITexture or ITextureInternal. // ////////////////////////////////////////////////////////////////////////// class CReferenceToHandleTexture : public ITextureInternal { public: CReferenceToHandleTexture(); virtual ~CReferenceToHandleTexture(); virtual const char *GetName( void ) const { return m_Name.String(); } const char *GetTextureGroupName( void ) const { return m_TextureGroupName.String(); } // Stats about the texture itself virtual ImageFormat GetImageFormat() const { return IMAGE_FORMAT_UNKNOWN; } virtual NormalDecodeMode_t GetNormalDecodeMode() const { return NORMAL_DECODE_NONE; } virtual int GetMappingWidth() const { return 1; } virtual int GetMappingHeight() const { return 1; } virtual int GetActualWidth() const { return 1; } virtual int GetActualHeight() const { return 1; } virtual int GetNumAnimationFrames() const { return 1; } virtual bool IsTranslucent() const { return false; } virtual void GetReflectivity( Vector& reflectivity ) { reflectivity.Zero(); } // Reference counting virtual void IncrementReferenceCount( ) { ++ m_nRefCount; } virtual void DecrementReferenceCount( ) { -- m_nRefCount; } virtual int GetReferenceCount( ) { return m_nRefCount; } // Used to modify the texture bits (procedural textures only) virtual void SetTextureRegenerator( ITextureRegenerator *pTextureRegen ) { NULL; } // Little helper polling methods virtual bool IsNormalMap( ) const { return false; } virtual bool IsCubeMap( void ) const { return false; } virtual bool IsRenderTarget( ) const { return false; } virtual bool IsTempRenderTarget( void ) const { return false; } virtual bool IsProcedural() const { return true; } virtual bool IsMipmapped() const { return false; } virtual bool IsError() const { return false; } // For volume textures virtual bool IsVolumeTexture() const { return false; } virtual int GetMappingDepth() const { return 1; } virtual int GetActualDepth() const { return 1; } // Releases the texture's hw memory void ReleaseMemory() { NULL; } virtual void OnRestore() { NULL; } // Sets the filtering modes on the texture we're modifying void SetFilteringAndClampingMode( bool bOnlyLodValues = false ) { NULL; } void Download( Rect_t *pRect = NULL, int nAdditionalCreationFlags = 0 ) { NULL; } // Loads up information about the texture virtual void Precache() { NULL; } // FIXME: Bogus methods... can we please delete these? virtual void GetLowResColorSample( float s, float t, float *color ) const { NULL; } // Gets texture resource data of the specified type. // Params: // eDataType type of resource to retrieve. // pnumBytes on return is the number of bytes available in the read-only data buffer or is undefined // Returns: // pointer to the resource data, or NULL. Note that the data from this pointer can disappear when // the texture goes away - you want to copy this data! virtual void *GetResourceData( uint32 eDataType, size_t *pNumBytes ) const { return NULL; } virtual int GetApproximateVidMemBytes( void ) const { return 32; } // Stretch blit the framebuffer into this texture. virtual void CopyFrameBufferToMe( int nRenderTargetID = 0, Rect_t *pSrcRect = NULL, Rect_t *pDstRect = NULL ) { NULL; } virtual void CopyMeToFrameBuffer( int nRenderTargetID = 0, Rect_t *pSrcRect = NULL, Rect_t *pDstRect = NULL ) { NULL; } virtual ITexture *GetEmbeddedTexture( int nIndex ) { return ( nIndex == 0 ) ? this : NULL; } // Get the shaderapi texture handle associated w/ a particular frame virtual ShaderAPITextureHandle_t GetTextureHandle( int nFrame, int nTextureChannel = 0 ) { return m_hTexture; } // Bind the texture virtual void Bind( Sampler_t sampler ); virtual void Bind( Sampler_t sampler1, int nFrame, Sampler_t sampler2 = (Sampler_t) -1 ); virtual void BindVertexTexture( VertexTextureSampler_t stage, int nFrame ); // Set this texture as a render target bool SetRenderTarget( int nRenderTargetID ) { return SetRenderTarget( nRenderTargetID, NULL ); } // Set this texture as a render target (optionally set depth texture as depth buffer as well) bool SetRenderTarget( int nRenderTargetID, ITexture *pDepthTexture) { return false; } virtual void MarkAsPreloaded( bool bSet ) { NULL; } virtual bool IsPreloaded() const { return true; } virtual void MarkAsExcluded( bool bSet, int nDimensionsLimit ) { NULL; } virtual bool UpdateExcludedState( void ) { return true; } // Retrieve the vtf flags mask virtual unsigned int GetFlags( void ) const { return 0; } virtual void ForceLODOverride( int iNumLodsOverrideUpOrDown ) { NULL; } virtual void ReloadFilesInList( IFileList *pFilesToReload ) {} // Save texture to a file. virtual bool SaveToFile( const char *fileName ) { return false; } virtual bool AsyncReadTextureFromFile( IVTFTexture* pVTFTexture, unsigned int nAdditionalCreationFlags ) { Assert( !"Should never get here." ); return false; } virtual void AsyncCancelReadTexture() { Assert( !"Should never get here." ); } virtual void CopyToStagingTexture( ITexture* pDstTex ) { Assert( !"Should never get here." ); }; // Map and unmap. These can fail. And can cause a very significant perf penalty. Be very careful with them. virtual void Map( void** pOutBits, int* pOutPitch ) { } virtual void Unmap() { } virtual ResidencyType_t GetCurrentResidence() const { return RESIDENT_FULL; } virtual ResidencyType_t GetTargetResidence() const { return RESIDENT_FULL; } virtual bool MakeResident( ResidencyType_t newResidence ) { Assert( !"Unimpl" ); return true; } virtual void UpdateLodBias() {} virtual void SetErrorTexture( bool isErrorTexture ) { } protected: #ifdef _DEBUG char *m_pDebugName; #endif CUtlSymbol m_Name; // What texture group this texture is in (winds up setting counters based on the group name, // then the budget panel views the counters). CUtlSymbol m_TextureGroupName; // The set of texture ids for each animation frame ShaderAPITextureHandle_t m_hTexture; // Refcount int m_nRefCount; public: virtual void DeleteIfUnreferenced(); void FixupTexture( const void *pData, int nSize, LoaderError_t loaderError ) { NULL; } void SwapContents( ITexture *pOther ) { NULL; } public: void SetName( char const *szName ); void InitFromHandle( const char *pTextureName, const char *pTextureGroupName, ShaderAPITextureHandle_t hTexture ); }; CReferenceToHandleTexture::CReferenceToHandleTexture() : m_hTexture( INVALID_SHADERAPI_TEXTURE_HANDLE ), #ifdef _DEBUG m_pDebugName( NULL ), #endif m_nRefCount( 0 ) { NULL; } CReferenceToHandleTexture::~CReferenceToHandleTexture() { #ifdef _DEBUG if ( m_nRefCount != 0 ) { Warning( "Reference Count(%d) != 0 in ~CReferenceToHandleTexture for texture \"%s\"\n", m_nRefCount, m_Name.String() ); } if ( m_pDebugName ) { delete [] m_pDebugName; } #endif } void CReferenceToHandleTexture::SetName( char const *szName ) { // normalize and convert to a symbol char szCleanName[MAX_PATH]; m_Name = NormalizeTextureName( szName, szCleanName, sizeof( szCleanName ) ); #ifdef _DEBUG if ( m_pDebugName ) { delete [] m_pDebugName; } int nLen = V_strlen( szCleanName ) + 1; m_pDebugName = new char[nLen]; V_memcpy( m_pDebugName, szCleanName, nLen ); #endif } void CReferenceToHandleTexture::InitFromHandle( const char *pTextureName, const char *pTextureGroupName, ShaderAPITextureHandle_t hTexture ) { SetName( pTextureName ); m_TextureGroupName = pTextureGroupName; m_hTexture = hTexture; } void CReferenceToHandleTexture::Bind( Sampler_t sampler ) { if ( g_pShaderDevice->IsUsingGraphics() ) { g_pShaderAPI->BindTexture( sampler, m_hTexture ); } } // TODO: make paired textures work with mat_texture_list void CReferenceToHandleTexture::Bind( Sampler_t sampler1, int nFrame, Sampler_t sampler2 /* = -1 */ ) { if ( g_pShaderDevice->IsUsingGraphics() ) { g_pShaderAPI->BindTexture( sampler1, m_hTexture ); } } void CReferenceToHandleTexture::BindVertexTexture( VertexTextureSampler_t sampler, int nFrame ) { if ( g_pShaderDevice->IsUsingGraphics() ) { g_pShaderAPI->BindVertexTexture( sampler, m_hTexture ); } } void CReferenceToHandleTexture::DeleteIfUnreferenced() { if ( m_nRefCount > 0 ) return; TextureManager()->RemoveTexture( this ); } //----------------------------------------------------------------------------- // Fixed-size allocator //----------------------------------------------------------------------------- //DEFINE_FIXEDSIZE_ALLOCATOR( CTexture, 1024, true ); //----------------------------------------------------------------------------- // Static instance of VTF texture //----------------------------------------------------------------------------- #define MAX_RENDER_THREADS 4 // For safety's sake, we allow any of the threads that intersect with rendering // to have their own state vars. In practice, we expect only the matqueue thread // and the main thread to ever hit s_pVTFTexture. static IVTFTexture *s_pVTFTexture[ MAX_RENDER_THREADS ] = { NULL }; // We only expect that the main thread or the matqueue thread to actually touch // these, but we still need a NULL and size of 0 for the other threads. static void *s_pOptimalReadBuffer[ MAX_RENDER_THREADS ] = { NULL }; static int s_nOptimalReadBufferSize[ MAX_RENDER_THREADS ] = { 0 }; //----------------------------------------------------------------------------- // Class factory methods //----------------------------------------------------------------------------- ITextureInternal *ITextureInternal::CreateFileTexture( const char *pFileName, const char *pTextureGroupName ) { CTexture *pTex = new CTexture; pTex->InitFileTexture( pFileName, pTextureGroupName ); return pTex; } ITextureInternal *ITextureInternal::CreateReferenceTextureFromHandle( const char *pTextureName, const char *pTextureGroupName, ShaderAPITextureHandle_t hTexture ) { CReferenceToHandleTexture *pTex = new CReferenceToHandleTexture; pTex->InitFromHandle( pTextureName, pTextureGroupName, hTexture ); return pTex; } ITextureInternal *ITextureInternal::CreateProceduralTexture( const char *pTextureName, const char *pTextureGroupName, int w, int h, int d, ImageFormat fmt, int nFlags, ITextureRegenerator *generator) { CTexture *pTex = new CTexture; pTex->InitProceduralTexture( pTextureName, pTextureGroupName, w, h, d, fmt, nFlags, generator ); pTex->IncrementReferenceCount(); return pTex; } // GR - named RT ITextureInternal *ITextureInternal::CreateRenderTarget( const char *pRTName, int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, RenderTargetType_t type, unsigned int textureFlags, unsigned int renderTargetFlags ) { CTexture *pTex = new CTexture; pTex->InitRenderTarget( pRTName, w, h, sizeMode, fmt, type, textureFlags, renderTargetFlags ); return pTex; } //----------------------------------------------------------------------------- // Rebuild and exisiting render target in place. //----------------------------------------------------------------------------- void ITextureInternal::ChangeRenderTarget( ITextureInternal *pTex, int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, RenderTargetType_t type, unsigned int textureFlags, unsigned int renderTargetFlags ) { pTex->ReleaseMemory(); dynamic_cast< CTexture * >(pTex)->InitRenderTarget( pTex->GetName(), w, h, sizeMode, fmt, type, textureFlags, renderTargetFlags ); } void ITextureInternal::Destroy( ITextureInternal *pTex, bool bSkipTexMgrCheck ) { #ifdef STAGING_ONLY if ( !bSkipTexMgrCheck && TextureManager()->HasPendingTextureDestroys() ) { // Multithreading badness. This will cause a crash later! Grab JohnS or McJohn know! DebuggerBreakIfDebugging_StagingOnly(); } #endif int iIndex = g_pTextureRefList->Find( static_cast( pTex ) ); if ( iIndex != g_pTextureRefList->InvalidIndex () ) { if ( g_pTextureRefList->Element(iIndex) != 0 ) { int currentCount = g_pTextureRefList->Element( iIndex ); Warning( "Destroying a texture that is in the queue: %s (%p): %d!\n", pTex->GetName(), pTex, currentCount ); } } delete pTex; } //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CTexture::CTexture() : m_ImageFormat( IMAGE_FORMAT_UNKNOWN ) { m_dimsActual.m_nMipCount = 0; m_dimsMapping.m_nWidth = 0; m_dimsMapping.m_nHeight = 0; m_dimsMapping.m_nDepth = 1; m_dimsActual.m_nWidth = 0; m_dimsActual.m_nHeight = 0; m_dimsActual.m_nDepth = 1; m_dimsAllocated.m_nWidth = 0; m_dimsAllocated.m_nHeight = 0; m_dimsAllocated.m_nDepth = 0; m_dimsAllocated.m_nMipCount = 0; m_nStreamingMips = 0; m_nRefCount = 0; m_nFlags = 0; m_nInternalFlags = 0; m_pTextureHandles = NULL; m_nFrameCount = 0; VectorClear( m_vecReflectivity ); m_pTextureRegenerator = NULL; m_nOriginalRenderTargetType = NO_RENDER_TARGET; m_RenderTargetSizeMode = RT_SIZE_NO_CHANGE; m_nOriginalRTWidth = m_nOriginalRTHeight = 1; m_LowResImageWidth = 0; m_LowResImageHeight = 0; m_pLowResImage = NULL; m_pStreamingJob = NULL; m_residenceTarget = RESIDENT_NONE; m_residenceCurrent = RESIDENT_NONE; m_lodClamp = 0; m_lodBiasInitial = 0; m_lodBiasCurrent = 0; m_nDesiredDimensionLimit = 0; m_nActualDimensionLimit = 0; memset( &m_cachedFileLodSettings, 0, sizeof( m_cachedFileLodSettings ) ); #ifdef _DEBUG m_pDebugName = NULL; #endif m_pStreamingVTF = NULL; m_bStreamingFileReadFailed = false; } CTexture::~CTexture() { #ifdef _DEBUG if ( m_nRefCount != 0 ) { Warning( "Reference Count(%d) != 0 in ~CTexture for texture \"%s\"\n", (int)m_nRefCount, m_Name.String() ); } #endif Shutdown(); #ifdef _DEBUG if ( m_pDebugName ) { // delete[] m_pDebugName; } #endif // Deliberately stomp our VTable so that we can detect cases where code tries to access freed materials. int *p = (int *)this; *p = 0xdeadbeef; } //----------------------------------------------------------------------------- // Initializes the texture //----------------------------------------------------------------------------- void CTexture::Init( int w, int h, int d, ImageFormat fmt, int iFlags, int iFrameCount ) { Assert( iFrameCount > 0 ); // This is necessary to prevent blowing away the allocated state, // which is necessary for the ReleaseTextureHandles call below to work. SetErrorTexture( false ); // free and release previous data // cannot change to new intialization parameters yet FreeShaderAPITextures(); ReleaseTextureHandles(); // update to new initialization parameters // these are the *desired* new values m_dimsMapping.m_nWidth = w; m_dimsMapping.m_nHeight = h; m_dimsMapping.m_nDepth = d; m_ImageFormat = fmt; m_nFrameCount = iFrameCount; // We don't know the actual width and height until we get it ready to render m_dimsActual.m_nWidth = m_dimsActual.m_nHeight = 0; m_dimsActual.m_nDepth = 1; m_dimsActual.m_nMipCount = 0; m_dimsAllocated.m_nWidth = 0; m_dimsAllocated.m_nHeight = 0; m_dimsAllocated.m_nDepth = 0; m_dimsAllocated.m_nMipCount = 0; m_nStreamingMips = 0; // Clear the m_nFlags bit. If we don't, then m_nFrameCount may end up being 1, and // TEXTUREFLAGS_DEPTHRENDERTARGET could be set. m_nFlags &= ~TEXTUREFLAGS_DEPTHRENDERTARGET; m_nFlags |= iFlags; CancelStreamingJob( false ); m_residenceTarget = RESIDENT_NONE; m_residenceCurrent = RESIDENT_NONE; m_lodClamp = 0; m_lodBiasInitial = 0; m_lodBiasCurrent = 0; AllocateTextureHandles(); } //----------------------------------------------------------------------------- // Shuts down the texture //----------------------------------------------------------------------------- void CTexture::Shutdown() { Assert( m_pStreamingVTF == NULL ); // Clean up the low-res texture delete[] m_pLowResImage; m_pLowResImage = 0; // Clean up the resources data for ( DataChunk const *pDataChunk = m_arrDataChunks.Base(), *pDataChunkEnd = pDataChunk + m_arrDataChunks.Count(); pDataChunk < pDataChunkEnd; ++pDataChunk ) { pDataChunk->Deallocate(); } m_arrDataChunks.RemoveAll(); // Frees the texture regen class if ( m_pTextureRegenerator ) { m_pTextureRegenerator->Release(); m_pTextureRegenerator = NULL; } CancelStreamingJob( false ); m_residenceTarget = RESIDENT_NONE; m_residenceCurrent = RESIDENT_NONE; m_lodClamp = 0; m_lodBiasInitial = 0; m_lodBiasCurrent = 0; // This deletes the textures FreeShaderAPITextures(); ReleaseTextureHandles(); NotifyUnloadedFile(); } void CTexture::ReleaseMemory() { FreeShaderAPITextures(); NotifyUnloadedFile(); } IVTFTexture *CTexture::GetScratchVTFTexture( ) { const bool cbThreadInMatQueue = ( MaterialSystem()->GetRenderThreadId() == ThreadGetCurrentId() ); cbThreadInMatQueue; Assert( cbThreadInMatQueue || ThreadInMainThread() ); const int ti = GetThreadId(); if ( !s_pVTFTexture[ ti ] ) s_pVTFTexture[ ti ] = CreateVTFTexture(); return s_pVTFTexture[ ti ]; } void CTexture::ReleaseScratchVTFTexture( IVTFTexture* tex ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); const bool cbThreadInMatQueue = ( MaterialSystem()->GetRenderThreadId() == ThreadGetCurrentId() ); cbThreadInMatQueue; Assert( cbThreadInMatQueue || ThreadInMainThread() ); Assert( m_pStreamingVTF == NULL || ThreadInMainThread() ); // Can only manipulate m_pStreamingVTF to release safely in main thread. if ( m_pStreamingVTF ) { Assert( tex == m_pStreamingVTF ); TextureManager()->ReleaseAsyncScratchVTF( m_pStreamingVTF ); m_pStreamingVTF = NULL; return; } // Normal scratch main-thread vtf doesn't need to do anything. } //----------------------------------------------------------------------------- // // Various initialization methods // //----------------------------------------------------------------------------- void CTexture::ApplyRenderTargetSizeMode( int &width, int &height, ImageFormat fmt ) { width = m_nOriginalRTWidth; height = m_nOriginalRTHeight; switch ( m_RenderTargetSizeMode ) { case RT_SIZE_FULL_FRAME_BUFFER: { MaterialSystem()->GetRenderTargetFrameBufferDimensions( width, height ); if( !HardwareConfig()->SupportsNonPow2Textures() ) { width = FloorPow2( width + 1 ); height = FloorPow2( height + 1 ); } } break; case RT_SIZE_FULL_FRAME_BUFFER_ROUNDED_UP: { MaterialSystem()->GetRenderTargetFrameBufferDimensions( width, height ); if( !HardwareConfig()->SupportsNonPow2Textures() ) { width = CeilPow2( width ); height = CeilPow2( height ); } } break; case RT_SIZE_PICMIP: { int fbWidth, fbHeight; MaterialSystem()->GetRenderTargetFrameBufferDimensions( fbWidth, fbHeight ); int picmip = g_config.skipMipLevels; while( picmip > 0 ) { width >>= 1; height >>= 1; picmip--; } while( width > fbWidth ) { width >>= 1; } while( height > fbHeight ) { height >>= 1; } } break; case RT_SIZE_DEFAULT: { // Assume that the input is pow2. Assert( ( width & ( width - 1 ) ) == 0 ); Assert( ( height & ( height - 1 ) ) == 0 ); int fbWidth, fbHeight; MaterialSystem()->GetRenderTargetFrameBufferDimensions( fbWidth, fbHeight ); while( width > fbWidth ) { width >>= 1; } while( height > fbHeight ) { height >>= 1; } } break; case RT_SIZE_HDR: { MaterialSystem()->GetRenderTargetFrameBufferDimensions( width, height ); width = width / 4; height = height / 4; } break; case RT_SIZE_OFFSCREEN: { int fbWidth, fbHeight; MaterialSystem()->GetRenderTargetFrameBufferDimensions( fbWidth, fbHeight ); // Shrink the buffer if it's bigger than back buffer. Otherwise, don't mess with it. while( (width > fbWidth) || (height > fbHeight) ) { width >>= 1; height >>= 1; } } break; case RT_SIZE_LITERAL: { // Literal means literally don't mess with the dimensions. Unlike what OFFSCREEN does, // which is totally to mess with the dimensions. } break; case RT_SIZE_LITERAL_PICMIP: { // Don't do anything here, like literal. Later, we will pay attention to picmip settings s.t. // these render targets look like other textures wrt Mapping Dimensions vs Actual Dimensions. } break; case RT_SIZE_REPLAY_SCREENSHOT: { // Compute all possible resolutions if first time we're running this function static bool bReplayInit = false; static int m_aScreenshotWidths[ 3 ][ 2 ]; static ConVarRef replay_screenshotresolution( "replay_screenshotresolution" ); if ( !bReplayInit ) { bReplayInit = true; for ( int iAspect = 0; iAspect < 3; ++iAspect ) { for ( int iRes = 0; iRes < 2; ++iRes ) { int nWidth = (int)FastPow2( 9 + iRes ); m_aScreenshotWidths[ iAspect ][ iRes ] = nWidth; } } } // Get dimensions for unpadded image int nUnpaddedWidth, nUnpaddedHeight; // Figure out the proper screenshot size to use based on the aspect ratio int nScreenWidth, nScreenHeight; MaterialSystem()->GetRenderTargetFrameBufferDimensions( nScreenWidth, nScreenHeight ); float flAspectRatio = (float)nScreenWidth / nScreenHeight; // Get the screenshot res int iRes = clamp( replay_screenshotresolution.GetInt(), 0, 1 ); int iAspect; if ( flAspectRatio == 16.0f/9 ) { iAspect = 0; } else if ( flAspectRatio == 16.0f/10 ) { iAspect = 1; } else { iAspect = 2; // 4:3 } static float s_flInvAspectRatios[3] = { 9.0f/16.0f, 10.0f/16, 3.0f/4 }; nUnpaddedWidth = min( nScreenWidth, m_aScreenshotWidths[ iAspect ][ iRes ] ); nUnpaddedHeight = m_aScreenshotWidths[ iAspect ][ iRes ] * s_flInvAspectRatios[ iAspect ]; // Get dimensions for padded image based on unpadded size - must be power of 2 for a material/texture width = SmallestPowerOfTwoGreaterOrEqual( nUnpaddedWidth ); height = SmallestPowerOfTwoGreaterOrEqual( nUnpaddedHeight ); } break; default: { if ( !HushAsserts() ) { Assert( m_RenderTargetSizeMode == RT_SIZE_NO_CHANGE ); Assert( m_nOriginalRenderTargetType == RENDER_TARGET_NO_DEPTH ); // Only can use NO_CHANGE if we don't have a depth buffer. } } break; } } void CTexture::CopyToStagingTexture( ITexture* pDstTex ) { Assert( pDstTex ); tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // Need to flush any commands in flight on our side of things materials->Flush( false ); CTexture* pDstTexActual = assert_cast< CTexture* >( pDstTex ); // Then do the copy if everything is on the up and up. if ( ( m_pTextureHandles == NULL || m_nFrameCount == 0 ) || ( pDstTexActual->m_pTextureHandles == NULL || pDstTexActual->m_nFrameCount == 0 ) ) { Assert( !"Can't copy to a non-existent texture, may need to generate or something." ); return; } // Make sure we've actually got the right surface types. Assert( m_nFlags & TEXTUREFLAGS_RENDERTARGET ); Assert( pDstTex->GetFlags() & TEXTUREFLAGS_STAGING_MEMORY ); g_pShaderAPI->CopyRenderTargetToScratchTexture( m_pTextureHandles[0], pDstTexActual->m_pTextureHandles[0] ); } void CTexture::Map( void** pOutBits, int* pOutPitch ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // Must be a staging texture to avoid catastrophic perf fail. Assert( m_nFlags & TEXTUREFLAGS_STAGING_MEMORY ); if ( m_pTextureHandles == NULL || m_nFrameCount == 0 ) { Assert( !"Can't map a non-existent texture, may need to generate or something." ); return; } g_pShaderAPI->LockRect( pOutBits, pOutPitch, m_pTextureHandles[ 0 ], 0, 0, 0, GetActualWidth(), GetActualHeight(), false, true ); } void CTexture::Unmap() { if ( m_pTextureHandles == NULL || m_nFrameCount == 0 ) { Assert( !"Can't unmap a non-existent texture, may need to generate or something." ); return; } g_pShaderAPI->UnlockRect( m_pTextureHandles[ 0 ], 0 ); } bool CTexture::MakeResident( ResidencyType_t newResidence ) { Assert( ( GetFlags() & TEXTUREFLAGS_STREAMABLE ) != 0 ); // If we already think we're supposed to go here, nothing to do and we should report success. if ( m_residenceTarget == newResidence ) return true; TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); // What are we moving towards? switch ( newResidence ) { case RESIDENT_NONE: MakeNonResident(); return true; case RESIDENT_PARTIAL: MakePartiallyResident(); return true; case RESIDENT_FULL: return MakeFullyResident(); default: Assert( !"Missing switch statement" ); }; return false; } void CTexture::UpdateLodBias() { if ( m_lodBiasInitial == 0.0f ) return; // Only perform adjustment once per frame. if ( m_lastLodBiasAdjustFrame == g_FrameNum ) return; bool bPopIn = mat_lodin_time.GetFloat() == 0; if ( bPopIn && m_lodBiasInitial == 0.0f ) return; if ( !bPopIn ) m_lodBiasCurrent = m_lodBiasInitial - ( Plat_FloatTime() - m_lodBiasStartTime ) / mat_lodin_time.GetFloat() * m_lodBiasInitial; else m_lodBiasCurrent = m_lodBiasInitial = 0.0f; // If we're supposed to pop in when the object isn't visible and we have the opportunity... if ( mat_lodin_hidden_pop.GetBool() && m_lastLodBiasAdjustFrame != g_FrameNum - 1 ) m_lodBiasCurrent = m_lodBiasInitial = 0.0f; if ( m_lodBiasCurrent <= 0.0f ) { m_lodBiasCurrent = m_lodBiasInitial = 0.0f; m_lodBiasStartTime = 0; } m_lastLodBiasAdjustFrame = g_FrameNum; SetFilteringAndClampingMode( true ); } void CTexture::MakeNonResident() { if ( m_residenceCurrent != RESIDENT_NONE ) Shutdown(); m_residenceCurrent = m_residenceTarget = RESIDENT_NONE; // Clear our the streamable fine flag to ensure we reload properly. m_nFlags &= ~TEXTUREFLAGS_STREAMABLE_FINE; } void CTexture::MakePartiallyResident() { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); ResidencyType_t oldCurrentResidence = m_residenceCurrent; ResidencyType_t oldTargetResidence = m_residenceTarget; m_residenceCurrent = m_residenceTarget = RESIDENT_PARTIAL; if ( oldCurrentResidence == RESIDENT_PARTIAL ) { Assert( oldTargetResidence == RESIDENT_FULL ); oldTargetResidence; // If we are already partially resident, then just cancel our job to stream in, // cause we don't need that data anymore. CancelStreamingJob(); return; } Assert( oldCurrentResidence == RESIDENT_FULL ); // Clear the fine bit. m_nFlags &= ~TEXTUREFLAGS_STREAMABLE_FINE; if ( HardwareConfig()->CanStretchRectFromTextures() ) { m_lodClamp = 0; m_lodBiasInitial = m_lodBiasCurrent = 0; m_lastLodBiasAdjustFrame = g_FrameNum; DownloadTexture( NULL, true ); } else { // Oops. We were overzealous above--restore the residency to what it was. m_residenceCurrent = oldCurrentResidence; // Immediately display it as lower res (for consistency) but if we can't (efficiently) // copy we just have to re-read everything from disk. Lame! m_lodClamp = 3; m_lodBiasInitial = m_lodBiasCurrent = 0; m_lastLodBiasAdjustFrame = g_FrameNum; SetFilteringAndClampingMode( true ); SafeAssign( &m_pStreamingJob, new CTextureStreamingJob( this ) ); MaterialSystem()->AsyncFindTexture( GetName(), GetTextureGroupName(), m_pStreamingJob, (void*) RESIDENT_PARTIAL, false, TEXTUREFLAGS_STREAMABLE_COARSE ); } } bool CTexture::MakeFullyResident() { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); ResidencyType_t oldCurrentResidence = m_residenceCurrent; ResidencyType_t oldTargetResidence = m_residenceTarget; if ( oldCurrentResidence == RESIDENT_FULL ) { // This isn't a requirement, but right now it would be a mistake Assert( !HardwareConfig()->CanStretchRectFromTextures() ); Assert( oldTargetResidence == RESIDENT_PARTIAL ); oldTargetResidence; m_residenceCurrent = m_residenceTarget = RESIDENT_FULL; m_lodClamp = 0; m_lodBiasInitial = m_lodBiasCurrent = 0; m_lastLodBiasAdjustFrame = g_FrameNum; SetFilteringAndClampingMode( true ); CancelStreamingJob(); return true; } Assert( m_residenceTarget == RESIDENT_PARTIAL && m_residenceCurrent == RESIDENT_PARTIAL ); Assert( m_pStreamingJob == NULL ); SafeAssign( &m_pStreamingJob, new CTextureStreamingJob( this ) ); MaterialSystem()->AsyncFindTexture( GetName(), GetTextureGroupName(), m_pStreamingJob, (void*) RESIDENT_FULL, false, TEXTUREFLAGS_STREAMABLE_FINE ); m_residenceTarget = RESIDENT_FULL; return true; } void CTexture::CancelStreamingJob( bool bJobMustExist ) { bJobMustExist; // Only used by asserts ensuring correctness, so reference it for release builds. // Most callers should be aware of whether the job exists, but for cleanup we don't know and we // should be safe in that case. Assert( !bJobMustExist || m_pStreamingJob ); if ( !m_pStreamingJob ) return; // The streaming job and this (this texture) have a circular reference count--each one holds one for the other. // As a result, this means that having the streaming job forget about the texture may cause the texture to go // away completely! So we need to ensure that after we call "ForgetOwner" that we don't touch any instance // variables. CTextureStreamingJob* pJob = m_pStreamingJob; m_pStreamingJob = NULL; pJob->ForgetOwner( this ); SafeRelease( &pJob ); } void CTexture::OnStreamingJobComplete( ResidencyType_t newResidenceCurrent ) { Assert( m_pStreamingJob ); // It's probable that if this assert fires, we should just do nothing in here and return--but I'd // like to see that happen to be sure. Assert( newResidenceCurrent == m_residenceTarget ); m_residenceCurrent = newResidenceCurrent; // Only do lod biasing for stream in. For stream out, just dump to lowest quality right away. if ( m_residenceCurrent == RESIDENT_FULL ) { if ( mat_lodin_time.GetFloat() > 0 ) { m_lodBiasCurrent = m_lodBiasInitial = 1.0 * m_nStreamingMips; m_lodBiasStartTime = Plat_FloatTime(); } else m_lodBiasCurrent = m_lodBiasInitial = 0.0f; m_lastLodBiasAdjustFrame = g_FrameNum; } m_lodClamp = 0; m_nStreamingMips = 0; SetFilteringAndClampingMode( true ); // The job is complete, Cancel handles cleanup correctly. CancelStreamingJob(); } void CTexture::SetErrorTexture( bool bIsErrorTexture ) { if ( bIsErrorTexture ) m_nInternalFlags |= TEXTUREFLAGSINTERNAL_ERROR; else m_nInternalFlags &= ( ~TEXTUREFLAGSINTERNAL_ERROR ); } //----------------------------------------------------------------------------- // Creates named render target texture //----------------------------------------------------------------------------- void CTexture::InitRenderTarget( const char *pRTName, int w, int h, RenderTargetSizeMode_t sizeMode, ImageFormat fmt, RenderTargetType_t type, unsigned int textureFlags, unsigned int renderTargetFlags ) { if ( pRTName ) { SetName( pRTName ); } else { static int id = 0; char pName[128]; Q_snprintf( pName, sizeof( pName ), "__render_target_%d", id ); ++id; SetName( pName ); } if ( renderTargetFlags & CREATERENDERTARGETFLAGS_HDR ) { if ( HardwareConfig()->GetHDRType() == HDR_TYPE_FLOAT ) { // slam the format fmt = IMAGE_FORMAT_RGBA16161616F; } } int nFrameCount = 1; int nFlags = TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_RENDERTARGET; nFlags |= textureFlags; if ( type == RENDER_TARGET_NO_DEPTH ) { nFlags |= TEXTUREFLAGS_NODEPTHBUFFER; } else if ( type == RENDER_TARGET_WITH_DEPTH || type == RENDER_TARGET_ONLY_DEPTH || g_pShaderAPI->DoRenderTargetsNeedSeparateDepthBuffer() ) { nFlags |= TEXTUREFLAGS_DEPTHRENDERTARGET; ++nFrameCount; } if ( renderTargetFlags & CREATERENDERTARGETFLAGS_TEMP ) { m_nInternalFlags |= TEXTUREFLAGSINTERNAL_TEMPRENDERTARGET; } m_nOriginalRenderTargetType = type; m_RenderTargetSizeMode = sizeMode; m_nOriginalRTWidth = w; m_nOriginalRTHeight = h; if ( ImageLoader::ImageFormatInfo(fmt).m_NumAlphaBits > 1 ) { nFlags |= TEXTUREFLAGS_EIGHTBITALPHA; } else if ( ImageLoader::ImageFormatInfo(fmt).m_NumAlphaBits == 1 ) { nFlags |= TEXTUREFLAGS_ONEBITALPHA; } ApplyRenderTargetSizeMode( w, h, fmt ); Init( w, h, 1, fmt, nFlags, nFrameCount ); m_TextureGroupName = TEXTURE_GROUP_RENDER_TARGET; } void CTexture::OnRestore() { // May have to change whether or not we have a depth buffer. // Are we a render target? if ( m_nFlags & TEXTUREFLAGS_RENDERTARGET ) { // Did they not ask for a depth buffer? if ( m_nOriginalRenderTargetType == RENDER_TARGET ) { // But, did we force them to have one, or should we force them to have one this time around? bool bShouldForce = g_pShaderAPI->DoRenderTargetsNeedSeparateDepthBuffer(); bool bDidForce = ((m_nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET) != 0); if ( bShouldForce != bDidForce ) { int nFlags = m_nFlags; int iFrameCount = m_nFrameCount; if ( bShouldForce ) { Assert( !( nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) ); iFrameCount = 2; nFlags |= TEXTUREFLAGS_DEPTHRENDERTARGET; } else { Assert( ( nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) ); iFrameCount = 1; nFlags &= ~TEXTUREFLAGS_DEPTHRENDERTARGET; } Shutdown(); int newWidth, newHeight; ApplyRenderTargetSizeMode( newWidth, newHeight, m_ImageFormat ); Init( newWidth, newHeight, 1, m_ImageFormat, nFlags, iFrameCount ); return; } } // If we didn't recreate it up above, then we may need to resize it anyway if the framebuffer // got smaller than we are. int newWidth, newHeight; ApplyRenderTargetSizeMode( newWidth, newHeight, m_ImageFormat ); if ( newWidth != m_dimsMapping.m_nWidth || newHeight != m_dimsMapping.m_nHeight ) { Shutdown(); Init( newWidth, newHeight, 1, m_ImageFormat, m_nFlags, m_nFrameCount ); return; } } else { if ( m_nFlags & TEXTUREFLAGS_STREAMABLE_FINE ) { MakeResident( RESIDENT_NONE ); } } } //----------------------------------------------------------------------------- // Creates a procedural texture //----------------------------------------------------------------------------- void CTexture::InitProceduralTexture( const char *pTextureName, const char *pTextureGroupName, int w, int h, int d, ImageFormat fmt, int nFlags, ITextureRegenerator* generator ) { // We shouldn't be asking for render targets here Assert( (nFlags & (TEXTUREFLAGS_RENDERTARGET | TEXTUREFLAGS_DEPTHRENDERTARGET)) == 0 ); SetName( pTextureName ); // Eliminate flags that are inappropriate... nFlags &= ~TEXTUREFLAGS_HINT_DXT5 | TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_RENDERTARGET | TEXTUREFLAGS_DEPTHRENDERTARGET; // Insert required flags nFlags |= TEXTUREFLAGS_PROCEDURAL; int nAlphaBits = ImageLoader::ImageFormatInfo(fmt).m_NumAlphaBits; if (nAlphaBits > 1) { nFlags |= TEXTUREFLAGS_EIGHTBITALPHA; } else if (nAlphaBits == 1) { nFlags |= TEXTUREFLAGS_ONEBITALPHA; } // Procedural textures are always one frame only Init( w, h, d, fmt, nFlags, 1 ); SetTextureRegenerator(generator); m_TextureGroupName = pTextureGroupName; } //----------------------------------------------------------------------------- // Creates a file texture //----------------------------------------------------------------------------- void CTexture::InitFileTexture( const char *pTextureFile, const char *pTextureGroupName ) { // For files, we only really know about the file name // At any time, the file contents could change, and we could have // a different size, number of frames, etc. SetName( pTextureFile ); m_TextureGroupName = pTextureGroupName; } //----------------------------------------------------------------------------- // Assigns/releases texture IDs for our animation frames //----------------------------------------------------------------------------- void CTexture::AllocateTextureHandles() { Assert( !m_pTextureHandles ); Assert( m_nFrameCount > 0 ); #ifdef DBGFLAG_ASSERT if( m_nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) { Assert( m_nFrameCount >= 2 ); } #endif m_pTextureHandles = new ShaderAPITextureHandle_t[m_nFrameCount]; for( int i = 0; i != m_nFrameCount; ++i ) m_pTextureHandles[i] = INVALID_SHADERAPI_TEXTURE_HANDLE; } void CTexture::ReleaseTextureHandles() { if ( m_pTextureHandles ) { delete[] m_pTextureHandles; m_pTextureHandles = NULL; } } //----------------------------------------------------------------------------- // Creates the texture //----------------------------------------------------------------------------- bool CTexture::AllocateShaderAPITextures() { Assert( !HasBeenAllocated() ); TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); int nCount = m_nFrameCount; int nCreateFlags = 0; if ( ( m_nFlags & TEXTUREFLAGS_ENVMAP ) && HardwareConfig()->SupportsCubeMaps() ) { nCreateFlags |= TEXTURE_CREATE_CUBEMAP; } bool bIsFloat = ( m_ImageFormat == IMAGE_FORMAT_RGBA16161616F ) || ( m_ImageFormat == IMAGE_FORMAT_R32F ) || ( m_ImageFormat == IMAGE_FORMAT_RGB323232F ) || ( m_ImageFormat == IMAGE_FORMAT_RGBA32323232F ); // Don't do sRGB on floating point textures if ( ( m_nFlags & TEXTUREFLAGS_SRGB ) && !bIsFloat ) { nCreateFlags |= TEXTURE_CREATE_SRGB; // for Posix/GL only } if ( m_nFlags & TEXTUREFLAGS_RENDERTARGET ) { nCreateFlags |= TEXTURE_CREATE_RENDERTARGET; // This here is simply so we can use a different call to // create the depth texture below if ( ( m_nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) && ( nCount == 2 ) ) //nCount must be 2 on pc { --nCount; } } else { // If it's not a render target, use the texture manager in dx if ( m_nFlags & TEXTUREFLAGS_STAGING_MEMORY ) nCreateFlags |= TEXTURE_CREATE_SYSMEM; else { #if defined(IS_WINDOWS_PC) static ConVarRef mat_dxlevel("mat_dxlevel"); if ( mat_dxlevel.GetInt() < 90 || mat_managedtextures.GetBool() ) #endif { nCreateFlags |= TEXTURE_CREATE_MANAGED; } } } if ( m_nFlags & TEXTUREFLAGS_POINTSAMPLE ) { nCreateFlags |= TEXTURE_CREATE_UNFILTERABLE_OK; } if ( m_nFlags & TEXTUREFLAGS_VERTEXTEXTURE ) { nCreateFlags |= TEXTURE_CREATE_VERTEXTEXTURE; } int nCopies = 1; if ( IsProcedural() ) { // This is sort of hacky... should we store the # of copies in the VTF? if ( !( m_nFlags & TEXTUREFLAGS_SINGLECOPY ) ) { // FIXME: That 6 there is heuristically what I came up with what I // need to get eyes not to stall on map alyx3. We need a better way // of determining how many copies of the texture we should store. nCopies = 6; } } // For depth only render target: adjust texture width/height // Currently we just leave it the same size, will update with further testing int nShaderApiCreateTextureDepth = ( ( m_nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) && ( m_nOriginalRenderTargetType == RENDER_TARGET_ONLY_DEPTH ) ) ? 1 : m_dimsAllocated.m_nDepth; // Create all animated texture frames in a single call g_pShaderAPI->CreateTextures( m_pTextureHandles, nCount, m_dimsAllocated.m_nWidth, m_dimsAllocated.m_nHeight, nShaderApiCreateTextureDepth, m_ImageFormat, m_dimsAllocated.m_nMipCount, nCopies, nCreateFlags, GetName(), GetTextureGroupName() ); int accountingCount = nCount; // Create the depth render target buffer if ( m_nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) { MEM_ALLOC_CREDIT(); Assert( nCount == 1 ); char debugName[128]; Q_snprintf( debugName, ARRAYSIZE( debugName ), "%s_ZBuffer", GetName() ); Assert( m_nFrameCount >= 2 ); m_pTextureHandles[1] = g_pShaderAPI->CreateDepthTexture( m_ImageFormat, m_dimsAllocated.m_nWidth, m_dimsAllocated.m_nHeight, debugName, ( m_nOriginalRenderTargetType == RENDER_TARGET_ONLY_DEPTH ) ); accountingCount += 1; } STAGING_ONLY_EXEC( g_currentTextures.InsertOrReplace( this, TexInfo_t( GetName(), m_dimsAllocated.m_nWidth, m_dimsAllocated.m_nHeight, m_dimsAllocated.m_nDepth, m_dimsAllocated.m_nMipCount, accountingCount, nCopies, m_ImageFormat ) ) ); m_nInternalFlags |= TEXTUREFLAGSINTERNAL_ALLOCATED; return true; } //----------------------------------------------------------------------------- // Releases the texture's hardware memory //----------------------------------------------------------------------------- void CTexture::FreeShaderAPITextures() { if ( m_pTextureHandles && HasBeenAllocated() ) { #ifdef STAGING_ONLY // If this hits, there's a leak because we're not deallocating enough textures. Yikes! Assert( g_currentTextures[ g_currentTextures.Find( this ) ].m_nFrameCount == m_nFrameCount ); // Remove ourselves from the list. g_currentTextures.Remove( this ); #endif // Release the frames for ( int i = m_nFrameCount; --i >= 0; ) { if ( g_pShaderAPI->IsTexture( m_pTextureHandles[i] ) ) { #ifdef WIN32 Assert( _heapchk() == _HEAPOK ); #endif g_pShaderAPI->DeleteTexture( m_pTextureHandles[i] ); m_pTextureHandles[i] = INVALID_SHADERAPI_TEXTURE_HANDLE; } } } m_nInternalFlags &= ~TEXTUREFLAGSINTERNAL_ALLOCATED; // Clear texture streaming stuff, too. if ( ( m_nFlags & TEXTUREFLAGS_STREAMABLE ) != 0 ) { m_nFlags &= ~TEXTUREFLAGS_STREAMABLE_FINE; m_residenceCurrent = m_residenceTarget = RESIDENT_NONE; m_lodClamp = 0; m_lodBiasCurrent = m_lodBiasInitial = 0; m_lodBiasStartTime = 0; } } void CTexture::MigrateShaderAPITextures() { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); const int cBytes = m_nFrameCount * sizeof ( ShaderAPITextureHandle_t ); ShaderAPITextureHandle_t *pTextureHandles = ( ShaderAPITextureHandle_t * ) stackalloc( cBytes ); Assert( pTextureHandles ); if ( !pTextureHandles ) return; V_memcpy( pTextureHandles, m_pTextureHandles, cBytes ); // Pretend we haven't been allocated yet. m_nInternalFlags &= ~TEXTUREFLAGSINTERNAL_ALLOCATED; AllocateShaderAPITextures(); for ( int i = 0; i < m_nFrameCount; ++i ) { Assert( g_pShaderAPI->IsTexture( pTextureHandles[ i ] ) == g_pShaderAPI->IsTexture( m_pTextureHandles[ i ] ) ); if ( !g_pShaderAPI->IsTexture( pTextureHandles[ i ] ) ) continue; g_pShaderAPI->CopyTextureToTexture( pTextureHandles[ i ], m_pTextureHandles[ i ] ); } for ( int i = 0; i < m_nFrameCount; ++i ) { if ( !g_pShaderAPI->IsTexture( pTextureHandles[ i ] ) ) continue; g_pShaderAPI->DeleteTexture( pTextureHandles[ i ] ); } } //----------------------------------------------------------------------------- // Computes the actual format of the texture //----------------------------------------------------------------------------- ImageFormat CTexture::ComputeActualFormat( ImageFormat srcFormat ) { ImageFormat dstFormat; bool bIsCompressed = ImageLoader::IsCompressed( srcFormat ); if ( g_config.bCompressedTextures && HardwareConfig()->SupportsCompressedTextures() && bIsCompressed ) { // for the runtime compressed formats the srcFormat won't equal the dstFormat, and we need to return srcFormat here if ( ImageLoader::IsRuntimeCompressed( srcFormat ) ) { return srcFormat; } // don't do anything since we are already in a compressed format. dstFormat = g_pShaderAPI->GetNearestSupportedFormat( srcFormat ); Assert( dstFormat == srcFormat ); return dstFormat; } // NOTE: Below this piece of code is only called when compressed textures are // turned off, or if the source texture is not compressed. #ifdef DX_TO_GL_ABSTRACTION if ( ( srcFormat == IMAGE_FORMAT_UVWQ8888 ) || ( srcFormat == IMAGE_FORMAT_UV88 ) || ( srcFormat == IMAGE_FORMAT_UVLX8888 ) ) { // Danger, this is going to blow up on the Mac. You better know what you're // doing with these exotic formats...which were introduced in 1999 Assert( 0 ); } #endif // We use the TEXTUREFLAGS_EIGHTBITALPHA and TEXTUREFLAGS_ONEBITALPHA flags // to decide how many bits of alpha we need; vtex checks the alpha channel // for all white, etc. if( ( srcFormat == IMAGE_FORMAT_UVWQ8888 ) || ( srcFormat == IMAGE_FORMAT_UV88 ) || ( srcFormat == IMAGE_FORMAT_UVLX8888 ) || ( srcFormat == IMAGE_FORMAT_RGBA16161616 ) || ( srcFormat == IMAGE_FORMAT_RGBA16161616F ) ) { #ifdef DX_TO_GL_ABSTRACTION dstFormat = g_pShaderAPI->GetNearestSupportedFormat( srcFormat, false ); // Stupid HACK! #else dstFormat = g_pShaderAPI->GetNearestSupportedFormat( srcFormat, true ); // Stupid HACK! #endif } else if ( m_nFlags & ( TEXTUREFLAGS_EIGHTBITALPHA | TEXTUREFLAGS_ONEBITALPHA ) ) { dstFormat = g_pShaderAPI->GetNearestSupportedFormat( IMAGE_FORMAT_BGRA8888 ); } else if ( srcFormat == IMAGE_FORMAT_I8 ) { dstFormat = g_pShaderAPI->GetNearestSupportedFormat( IMAGE_FORMAT_I8 ); } else { dstFormat = g_pShaderAPI->GetNearestSupportedFormat( IMAGE_FORMAT_BGR888 ); } return dstFormat; } //----------------------------------------------------------------------------- // Calculates info about whether we can make the texture smaller and by how much //----------------------------------------------------------------------------- int CTexture::ComputeActualSize( bool bIgnorePicmip, IVTFTexture *pVTFTexture, bool bTextureMigration ) { unsigned int stripFlags = 0; return ComputeMipSkipCount( GetName(), m_dimsMapping, bIgnorePicmip, pVTFTexture, m_nFlags, m_nDesiredDimensionLimit, &m_nStreamingMips, &m_cachedFileLodSettings, &m_dimsActual, &m_dimsAllocated, &stripFlags ); Assert( stripFlags == 0 ); // Not necessarily illegal, just needs investigating. } //----------------------------------------------------------------------------- // Used to modify the texture bits (procedural textures only) //----------------------------------------------------------------------------- void CTexture::SetTextureRegenerator( ITextureRegenerator *pTextureRegen ) { // NOTE: These can only be used by procedural textures Assert( IsProcedural() ); if (m_pTextureRegenerator) { m_pTextureRegenerator->Release(); } m_pTextureRegenerator = pTextureRegen; } //----------------------------------------------------------------------------- // Gets us modifying a particular frame of our texture //----------------------------------------------------------------------------- void CTexture::Modify( int iFrame ) { Assert( iFrame >= 0 && iFrame < m_nFrameCount ); Assert( HasBeenAllocated() ); g_pShaderAPI->ModifyTexture( m_pTextureHandles[iFrame] ); } //----------------------------------------------------------------------------- // Sets the texture clamping state on the currently modified frame //----------------------------------------------------------------------------- void CTexture::SetWrapState( ) { // Border clamp applies to all texture coordinates if ( m_nFlags & TEXTUREFLAGS_BORDER ) { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_S, SHADER_TEXWRAPMODE_BORDER ); g_pShaderAPI->TexWrap( SHADER_TEXCOORD_T, SHADER_TEXWRAPMODE_BORDER ); g_pShaderAPI->TexWrap( SHADER_TEXCOORD_U, SHADER_TEXWRAPMODE_BORDER ); return; } // Clamp mode in S if ( m_nFlags & TEXTUREFLAGS_CLAMPS ) { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_S, SHADER_TEXWRAPMODE_CLAMP ); } else { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_S, SHADER_TEXWRAPMODE_REPEAT ); } // Clamp mode in T if ( m_nFlags & TEXTUREFLAGS_CLAMPT ) { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_T, SHADER_TEXWRAPMODE_CLAMP ); } else { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_T, SHADER_TEXWRAPMODE_REPEAT ); } // Clamp mode in U if ( m_nFlags & TEXTUREFLAGS_CLAMPU ) { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_U, SHADER_TEXWRAPMODE_CLAMP ); } else { g_pShaderAPI->TexWrap( SHADER_TEXCOORD_U, SHADER_TEXWRAPMODE_REPEAT ); } } //----------------------------------------------------------------------------- // Sets the texture filtering state on the currently modified frame //----------------------------------------------------------------------------- void CTexture::SetFilterState() { // Turns off filtering when we're point sampling if( m_nFlags & TEXTUREFLAGS_POINTSAMPLE ) { g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_NEAREST ); g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_NEAREST ); return; } // NOTE: config.bMipMapTextures and config.bFilterTextures is handled in ShaderAPIDX8 bool bEnableMipmapping = ( m_nFlags & TEXTUREFLAGS_NOMIP ) ? false : true; if( !bEnableMipmapping ) { g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_LINEAR ); g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_LINEAR ); return; } // Determing the filtering mode bool bIsAnisotropic = false, bIsTrilinear = false; if ( HardwareConfig()->GetDXSupportLevel() >= 80 && (g_config.m_nForceAnisotropicLevel > 1) && (HardwareConfig()->MaximumAnisotropicLevel() > 1) ) { bIsAnisotropic = true; } else if ( g_config.ForceTrilinear() ) { bIsAnisotropic = (( m_nFlags & TEXTUREFLAGS_ANISOTROPIC ) != 0) && (HardwareConfig()->MaximumAnisotropicLevel() > 1); bIsTrilinear = true; } else { bIsAnisotropic = (( m_nFlags & TEXTUREFLAGS_ANISOTROPIC ) != 0) && (HardwareConfig()->MaximumAnisotropicLevel() > 1); bIsTrilinear = ( m_nFlags & TEXTUREFLAGS_TRILINEAR ) != 0; } if ( bIsAnisotropic ) { g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_ANISOTROPIC ); g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_ANISOTROPIC ); } else { // force trilinear if we are on a dx8 card or above if ( bIsTrilinear ) { g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_LINEAR_MIPMAP_LINEAR ); g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_LINEAR ); } else { g_pShaderAPI->TexMinFilter( SHADER_TEXFILTERMODE_LINEAR_MIPMAP_NEAREST ); g_pShaderAPI->TexMagFilter( SHADER_TEXFILTERMODE_LINEAR ); } } SetLodState(); } //----------------------------------------------------------------------------- // Sets the lod state on the currently modified frame //----------------------------------------------------------------------------- void CTexture::SetLodState() { // Set the lod clamping value to ensure we don't see anything we're not supposed to. g_pShaderAPI->TexLodClamp( m_lodClamp ); g_pShaderAPI->TexLodBias( m_lodBiasCurrent ); } //----------------------------------------------------------------------------- // Download bits main entry point!! //----------------------------------------------------------------------------- void CTexture::DownloadTexture( Rect_t *pRect, bool bCopyFromCurrent ) { // No downloading necessary if there's no graphics if ( !g_pShaderDevice->IsUsingGraphics() ) return; // We don't know the actual size of the texture at this stage... if ( !pRect ) { ReconstructTexture( bCopyFromCurrent ); } else { // Not implemented yet. Assert( bCopyFromCurrent == false ); ReconstructPartialTexture( pRect ); } // Iterate over all the frames and set the appropriate wrapping + filtering state SetFilteringAndClampingMode(); // texture bits have been updated, update the exclusion state if ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_SHOULDEXCLUDE ) { m_nInternalFlags |= TEXTUREFLAGSINTERNAL_EXCLUDED; } else { m_nInternalFlags &= ~TEXTUREFLAGSINTERNAL_EXCLUDED; } // texture bits have been picmipped, update the picmip state m_nActualDimensionLimit = m_nDesiredDimensionLimit; } void CTexture::Download( Rect_t *pRect, int nAdditionalCreationFlags /* = 0 */ ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // Only download the bits if we can... if ( g_pShaderAPI->CanDownloadTextures() ) { MaterialLock_t hLock = MaterialSystem()->Lock(); m_nFlags |= nAdditionalCreationFlags; // Path to let stdshaders drive settings like sRGB-ness at creation time DownloadTexture( pRect ); MaterialSystem()->Unlock( hLock ); } } // Save texture to a file. bool CTexture::SaveToFile( const char *fileName ) { bool bRet = false; ITexture *pTexture = materials->FindTexture( "_rt_FullFrameFB1", TEXTURE_GROUP_RENDER_TARGET ); if ( !pTexture ) return bRet; const int width = GetActualWidth(); const int height = GetActualHeight(); if ( pTexture->GetImageFormat() == IMAGE_FORMAT_RGBA8888 || pTexture->GetImageFormat() == IMAGE_FORMAT_ABGR8888 || pTexture->GetImageFormat() == IMAGE_FORMAT_ARGB8888 || pTexture->GetImageFormat() == IMAGE_FORMAT_BGRA8888 || pTexture->GetImageFormat() == IMAGE_FORMAT_BGRX8888 ) { bool bCleanupTexture = false; // Need to allocate a temporarily renderable surface. Sadness. if ( width > pTexture->GetActualWidth() || height > pTexture->GetActualHeight() ) { materials->OverrideRenderTargetAllocation( true ); // This one bumps the ref automatically for us. pTexture = materials->CreateNamedRenderTargetTextureEx( "_rt_savetofile", width, height, RT_SIZE_LITERAL, IMAGE_FORMAT_BGRA8888, MATERIAL_RT_DEPTH_NONE, TEXTUREFLAGS_IMMEDIATE_CLEANUP ); materials->OverrideRenderTargetAllocation( false ); if ( !pTexture || pTexture->IsError() ) { SafeRelease( &pTexture ); Msg( "SaveToFile: texture '_rt_FullFrameFB1' failed. Ptr:%p Format:%d\n", pTexture, ( pTexture ? pTexture->GetImageFormat() : 0 ) ); return false; } bCleanupTexture = true; } Rect_t SrcRect = { 0, 0, width, height }; Rect_t DstRect = SrcRect; if ( ( width > 0 ) && ( height > 0 ) ) { void *pixelValue = malloc( width * height * 2 * sizeof( BGRA8888_t ) ); if( pixelValue ) { CMatRenderContextPtr pRenderContext( MaterialSystem() ); // Set the clear color to opaque black pRenderContext->ClearColor4ub( 0, 0, 0, 0xFF ); pRenderContext->ClearBuffers( true, true, true ); pRenderContext->PushRenderTargetAndViewport( pTexture, 0, 0, width, height ); pRenderContext->CopyTextureToRenderTargetEx( 0, this, &SrcRect, &DstRect ); pRenderContext->ReadPixels( 0, 0, width, height, ( unsigned char * )pixelValue, pTexture->GetImageFormat() ); // Slap the alpha channel at the bottom of the tga file so we don't have to deal with crappy tools that can't // handle rgb + alpha well. This means we can just do a "mat_texture_save_fonts" concommand, and then use // something like Beyond Compare to look at the fonts differences between various platforms, etc. CPixelWriter pixelWriterSrc; CPixelWriter pixelWriterDst; pixelWriterSrc.SetPixelMemory( pTexture->GetImageFormat(), pixelValue, width * sizeof( BGRA8888_t ) ); pixelWriterDst.SetPixelMemory( pTexture->GetImageFormat(), pixelValue, width * sizeof( BGRA8888_t ) ); for (int y = 0; y < height; ++y) { pixelWriterSrc.Seek( 0, y ); pixelWriterDst.Seek( 0, y + height ); for (int x = 0; x < width; ++x) { int r, g, b, a; pixelWriterSrc.ReadPixelNoAdvance( r, g, b, a ); pixelWriterSrc.WritePixel( a, a, a, 255 ); pixelWriterDst.WritePixel( r, g, b, 255 ); } } if ( TGAWriter::WriteTGAFile( fileName, width, height * 2, pTexture->GetImageFormat(), ( uint8 * )pixelValue, width * sizeof( BGRA8888_t ) ) ) { bRet = true; } // restore our previous state pRenderContext->PopRenderTargetAndViewport(); free( pixelValue ); } } if ( bCleanupTexture ) SafeRelease( &pTexture ); } else { Msg( "SaveToFile: texture '_rt_FullFrameFB1' failed. Ptr:%p Format:%d\n", pTexture, ( pTexture ? pTexture->GetImageFormat() : 0 ) ); } return bRet; } bool CTexture::AsyncReadTextureFromFile( IVTFTexture* pVTFTexture, unsigned int nAdditionalCreationFlags ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); m_bStreamingFileReadFailed = false; // Optimism! char pCacheFileName[ MATERIAL_MAX_PATH ]; FileHandle_t fileHandle = FILESYSTEM_INVALID_HANDLE; GetCacheFilename( pCacheFileName, MATERIAL_MAX_PATH ); if ( !GetFileHandle( &fileHandle, pCacheFileName, NULL ) ) { m_bStreamingFileReadFailed = true; return false; } if ( V_strstr( GetName(), "c_sniperrifle_scope" ) ) { int i = 0; i = 3; } tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - %s", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, pCacheFileName ) ); // OSX hackery int nPreserveFlags = nAdditionalCreationFlags; if ( m_nFlags & TEXTUREFLAGS_SRGB ) nPreserveFlags |= TEXTUREFLAGS_SRGB; uint16 dontCareStreamedMips = m_nStreamingMips; TextureLODControlSettings_t settings = m_cachedFileLodSettings; if ( !SLoadTextureBitsFromFile( &pVTFTexture, fileHandle, m_nFlags | nPreserveFlags, &settings, m_nDesiredDimensionLimit, &dontCareStreamedMips, GetName(), pCacheFileName, &m_dimsMapping ) ) { g_pFullFileSystem->Close( fileHandle ); m_bStreamingFileReadFailed = true; return false; } g_pFullFileSystem->Close( fileHandle ); m_pStreamingVTF = pVTFTexture; return true; } void CTexture::AsyncCancelReadTexture( ) { Assert( m_bStreamingFileReadFailed || m_pStreamingVTF != NULL ); if ( m_pStreamingVTF ) { TextureManager()->ReleaseAsyncScratchVTF( m_pStreamingVTF ); m_pStreamingVTF = NULL; } } void CTexture::Bind( Sampler_t sampler ) { Bind( sampler, 0 ); } //----------------------------------------------------------------------------- // Binds a particular texture (possibly paired) //----------------------------------------------------------------------------- void CTexture::Bind( Sampler_t sampler1, int nFrame, Sampler_t sampler2 /* = -1 */ ) { if ( g_pShaderDevice->IsUsingGraphics() ) { TextureManager()->RequestAllMipmaps( this ); if ( nFrame < 0 || nFrame >= m_nFrameCount ) { // FIXME: Use the well-known 'error' id instead of frame 0 nFrame = 0; // Assert(0); } // Make sure we've actually allocated the texture handle if ( HasBeenAllocated() ) { g_pShaderAPI->BindTexture( sampler1, m_pTextureHandles[nFrame] ); } else { ExecuteNTimes( 20, Warning( "Trying to bind texture %s, but texture handles are not valid. Binding a white texture!\n", GetName() ) ); g_pShaderAPI->BindStandardTexture( sampler1, TEXTURE_WHITE ); } } } void CTexture::BindVertexTexture( VertexTextureSampler_t sampler, int nFrame ) { if ( g_pShaderDevice->IsUsingGraphics() ) { if ( nFrame < 0 || nFrame >= m_nFrameCount ) { // FIXME: Use the well-known 'error' id instead of frame 0 nFrame = 0; // Assert(0); } // Make sure we've actually allocated the texture Assert( HasBeenAllocated() ); g_pShaderAPI->BindVertexTexture( sampler, m_pTextureHandles[nFrame] ); } } //----------------------------------------------------------------------------- // Set this texture as a render target //----------------------------------------------------------------------------- bool CTexture::SetRenderTarget( int nRenderTargetID ) { return SetRenderTarget( nRenderTargetID, NULL ); } //----------------------------------------------------------------------------- // Set this texture as a render target // Optionally bind pDepthTexture as depth buffer //----------------------------------------------------------------------------- bool CTexture::SetRenderTarget( int nRenderTargetID, ITexture *pDepthTexture ) { if ( ( m_nFlags & TEXTUREFLAGS_RENDERTARGET ) == 0 ) return false; // Make sure we've actually allocated the texture handles Assert( HasBeenAllocated() ); ShaderAPITextureHandle_t textureHandle = m_pTextureHandles[0]; ShaderAPITextureHandle_t depthTextureHandle = (unsigned int)SHADER_RENDERTARGET_DEPTHBUFFER; if ( m_nFlags & TEXTUREFLAGS_DEPTHRENDERTARGET ) { Assert( m_nFrameCount >= 2 ); depthTextureHandle = m_pTextureHandles[1]; } else if ( m_nFlags & TEXTUREFLAGS_NODEPTHBUFFER ) { // GR - render target without depth buffer depthTextureHandle = (unsigned int)SHADER_RENDERTARGET_NONE; } if ( pDepthTexture) { depthTextureHandle = static_cast(pDepthTexture)->GetTextureHandle(0); } g_pShaderAPI->SetRenderTargetEx( nRenderTargetID, textureHandle, depthTextureHandle ); return true; } //----------------------------------------------------------------------------- // Reference counting //----------------------------------------------------------------------------- void CTexture::IncrementReferenceCount( void ) { ++m_nRefCount; } void CTexture::DecrementReferenceCount( void ) { if ( ( --m_nRefCount <= 0 ) && ( m_nFlags & TEXTUREFLAGS_IMMEDIATE_CLEANUP ) != 0 ) { Assert( m_nRefCount == 0 ); // Just inform the texture manager, it will decide to free us at a later date. TextureManager()->MarkUnreferencedTextureForCleanup( this ); } } int CTexture::GetReferenceCount( ) { return m_nRefCount; } //----------------------------------------------------------------------------- // Various accessor methods //----------------------------------------------------------------------------- const char* CTexture::GetName( ) const { return m_Name.String(); } const char* CTexture::GetTextureGroupName( ) const { return m_TextureGroupName.String(); } void CTexture::SetName( const char* pName ) { // normalize and convert to a symbol char szCleanName[MAX_PATH]; m_Name = NormalizeTextureName( pName, szCleanName, sizeof( szCleanName ) ); #ifdef _DEBUG if ( m_pDebugName ) { delete [] m_pDebugName; } int nLen = V_strlen( szCleanName ) + 1; m_pDebugName = new char[nLen]; V_memcpy( m_pDebugName, szCleanName, nLen ); #endif } ImageFormat CTexture::GetImageFormat() const { return m_ImageFormat; } int CTexture::GetMappingWidth() const { return m_dimsMapping.m_nWidth; } int CTexture::GetMappingHeight() const { return m_dimsMapping.m_nHeight; } int CTexture::GetMappingDepth() const { return m_dimsMapping.m_nDepth; } int CTexture::GetActualWidth() const { return m_dimsActual.m_nWidth; } int CTexture::GetActualHeight() const { return m_dimsActual.m_nHeight; } int CTexture::GetActualDepth() const { return m_dimsActual.m_nDepth; } int CTexture::GetNumAnimationFrames() const { return m_nFrameCount; } void CTexture::GetReflectivity( Vector& reflectivity ) { Precache(); VectorCopy( m_vecReflectivity, reflectivity ); } //----------------------------------------------------------------------------- // Little helper polling methods //----------------------------------------------------------------------------- bool CTexture::IsTranslucent() const { return ( m_nFlags & (TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA) ) != 0; } bool CTexture::IsNormalMap( void ) const { return ( ( m_nFlags & TEXTUREFLAGS_NORMAL ) != 0 ); } bool CTexture::IsCubeMap( void ) const { return ( ( m_nFlags & TEXTUREFLAGS_ENVMAP ) != 0 ); } bool CTexture::IsRenderTarget( void ) const { return ( ( m_nFlags & TEXTUREFLAGS_RENDERTARGET ) != 0 ); } bool CTexture::IsTempRenderTarget( void ) const { return ( ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_TEMPRENDERTARGET ) != 0 ); } bool CTexture::IsProcedural() const { return ( (m_nFlags & TEXTUREFLAGS_PROCEDURAL) != 0 ); } bool CTexture::IsMipmapped() const { return ( (m_nFlags & TEXTUREFLAGS_NOMIP) == 0 ); } unsigned int CTexture::GetFlags() const { return m_nFlags; } void CTexture::ForceLODOverride( int iNumLodsOverrideUpOrDown ) { TextureLodOverride::OverrideInfo oi( iNumLodsOverrideUpOrDown, iNumLodsOverrideUpOrDown ); TextureLodOverride::Add( GetName(), oi ); Download( NULL ); } bool CTexture::IsError() const { return ( (m_nInternalFlags & TEXTUREFLAGSINTERNAL_ERROR) != 0 ); } bool CTexture::HasBeenAllocated() const { return ( (m_nInternalFlags & TEXTUREFLAGSINTERNAL_ALLOCATED) != 0 ); } bool CTexture::IsVolumeTexture() const { return (m_dimsMapping.m_nDepth > 1); } //----------------------------------------------------------------------------- // Sets the filtering + clamping modes on the texture //----------------------------------------------------------------------------- void CTexture::SetFilteringAndClampingMode( bool bOnlyLodValues ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); if( !HasBeenAllocated() ) return; for ( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame ) { Modify( iFrame ); // Indicate we're changing state with respect to a particular frame if ( !bOnlyLodValues ) { SetWrapState(); // Send the appropriate wrap/clamping modes to the shaderapi. SetFilterState(); // Set the filtering mode for the texture after downloading it. // NOTE: Apparently, the filter state cannot be set until after download } else SetLodState(); } } //----------------------------------------------------------------------------- // Loads up the non-fallback information about the texture //----------------------------------------------------------------------------- void CTexture::Precache() { // We only have to do something in the case of a file texture if ( IsRenderTarget() || IsProcedural() ) return; if ( HasBeenAllocated() ) return; // Blow off env_cubemap too... if ( !Q_strnicmp( m_Name.String(), "env_cubemap", 12 )) return; int nAdditionalFlags = 0; if ( ( m_nFlags & TEXTUREFLAGS_STREAMABLE ) != 0 ) { // If we were previously streamed in, make sure we still do this time around. nAdditionalFlags = TEXTUREFLAGS_STREAMABLE_COARSE; Assert( ( m_nFlags & TEXTUREFLAGS_STREAMABLE_FINE ) == 0 ); Assert( m_residenceCurrent == RESIDENT_NONE && m_residenceTarget == RESIDENT_NONE ); Assert( m_lodClamp == 0 ); Assert( m_lodBiasCurrent == 0 && m_lodBiasInitial == 0 ); Assert( m_lodBiasStartTime == 0 ); } ScratchVTF scratch( this ); IVTFTexture *pVTFTexture = scratch.Get(); // The texture name doubles as the relative file name // It's assumed to have already been set by this point // Compute the cache name char pCacheFileName[MATERIAL_MAX_PATH]; Q_snprintf( pCacheFileName, sizeof( pCacheFileName ), "materials/%s" TEXTURE_FNAME_EXTENSION, m_Name.String() ); int nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION ); unsigned char *pMem = (unsigned char *)stackalloc( nHeaderSize ); CUtlBuffer buf( pMem, nHeaderSize ); if ( !g_pFullFileSystem->ReadFile( pCacheFileName, NULL, buf, nHeaderSize ) ) { goto precacheFailed; } if ( !pVTFTexture->Unserialize( buf, true ) ) { Warning( "Error reading material \"%s\"\n", pCacheFileName ); goto precacheFailed; } // NOTE: Don't set the image format in case graphics are active VectorCopy( pVTFTexture->Reflectivity(), m_vecReflectivity ); m_dimsMapping.m_nWidth = pVTFTexture->Width(); m_dimsMapping.m_nHeight = pVTFTexture->Height(); m_dimsMapping.m_nDepth = pVTFTexture->Depth(); m_nFlags = pVTFTexture->Flags() | nAdditionalFlags; m_nFrameCount = pVTFTexture->FrameCount(); if ( !m_pTextureHandles ) { // NOTE: m_nFrameCount and m_pTextureHandles are strongly associated // whenever one is modified the other must also be modified AllocateTextureHandles(); } return; precacheFailed: m_vecReflectivity.Init( 0, 0, 0 ); m_dimsMapping.m_nWidth = 32; m_dimsMapping.m_nHeight = 32; m_dimsMapping.m_nDepth = 1; m_nFlags = TEXTUREFLAGS_NOMIP; SetErrorTexture( true ); m_nFrameCount = 1; if ( !m_pTextureHandles ) { // NOTE: m_nFrameCount and m_pTextureHandles are strongly associated // whenever one is modified the other must also be modified AllocateTextureHandles(); } } //----------------------------------------------------------------------------- // Loads the low-res image from the texture //----------------------------------------------------------------------------- void CTexture::LoadLowResTexture( IVTFTexture *pTexture ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); delete [] m_pLowResImage; m_pLowResImage = NULL; if ( pTexture->LowResWidth() == 0 || pTexture->LowResHeight() == 0 ) { m_LowResImageWidth = m_LowResImageHeight = 0; return; } m_LowResImageWidth = pTexture->LowResWidth(); m_LowResImageHeight = pTexture->LowResHeight(); m_pLowResImage = new unsigned char[m_LowResImageWidth * m_LowResImageHeight * 3]; #ifdef DBGFLAG_ASSERT bool retVal = #endif ImageLoader::ConvertImageFormat( pTexture->LowResImageData(), pTexture->LowResFormat(), m_pLowResImage, IMAGE_FORMAT_RGB888, m_LowResImageWidth, m_LowResImageHeight ); Assert( retVal ); } void *CTexture::GetResourceData( uint32 eDataType, size_t *pnumBytes ) const { for ( DataChunk const *pDataChunk = m_arrDataChunks.Base(), *pDataChunkEnd = pDataChunk + m_arrDataChunks.Count(); pDataChunk < pDataChunkEnd; ++pDataChunk ) { if ( ( pDataChunk->m_eType & ~RSRCF_MASK ) == eDataType ) { if ( ( pDataChunk->m_eType & RSRCF_HAS_NO_DATA_CHUNK ) == 0 ) { if ( pnumBytes) *pnumBytes = pDataChunk->m_numBytes; return pDataChunk->m_pvData; } else { if ( pnumBytes ) *pnumBytes = sizeof( pDataChunk->m_numBytes ); return ( void *)( &pDataChunk->m_numBytes ); } } } if ( pnumBytes ) pnumBytes = 0; return NULL; } #pragma pack(1) struct DXTColBlock { unsigned short col0; unsigned short col1; // no bit fields - use bytes unsigned char row[4]; }; struct DXTAlphaBlock3BitLinear { unsigned char alpha0; unsigned char alpha1; unsigned char stuff[6]; }; #pragma pack() static void FillCompressedTextureWithSingleColor( int red, int green, int blue, int alpha, unsigned char *pImageData, int width, int height, int depth, ImageFormat imageFormat ) { Assert( ( width < 4 ) || !( width % 4 ) ); Assert( ( height < 4 ) || !( height % 4 ) ); Assert( ( depth < 4 ) || !( depth % 4 ) ); if ( width < 4 && width > 0 ) { width = 4; } if ( height < 4 && height > 0 ) { height = 4; } if ( depth < 4 && depth > 1 ) { depth = 4; } int numBlocks = ( width * height ) >> 4; numBlocks *= depth; DXTColBlock colorBlock; memset( &colorBlock, 0, sizeof( colorBlock ) ); ( ( BGR565_t * )&( colorBlock.col0 ) )->Set( red, green, blue ); ( ( BGR565_t * )&( colorBlock.col1 ) )->Set( red, green, blue ); switch( imageFormat ) { case IMAGE_FORMAT_DXT1: case IMAGE_FORMAT_ATI1N: // Invalid block data, but correct memory footprint { int i; for( i = 0; i < numBlocks; i++ ) { memcpy( pImageData + i * 8, &colorBlock, sizeof( colorBlock ) ); } } break; case IMAGE_FORMAT_DXT5: case IMAGE_FORMAT_ATI2N: { int i; for( i = 0; i < numBlocks; i++ ) { // memset( pImageData + i * 16, 0, 16 ); memcpy( pImageData + i * 16 + 8, &colorBlock, sizeof( colorBlock ) ); // memset( pImageData + i * 16 + 8, 0xffff, 8 ); // alpha block } } break; default: Assert( 0 ); break; } } // This table starts out like the programmatic logic that used to be here, // but then has some other colors, so that we don't see repeats. // Also, there is no black, which seems to be an error condition on OpenGL. // There also aren't any zeros in this table, since these colors may get // multiplied with, say, vertex colors which are tinted, resulting in black pixels. int sg_nMipLevelColors[14][3] = { { 64, 255, 64 }, // Green { 255, 64, 64 }, // Red { 255, 255, 64 }, // Yellow { 64, 64, 255 }, // Blue { 64, 255, 255 }, // Cyan { 255, 64, 255 }, // Magenta { 255, 255, 255 }, // White { 255, 150, 150 }, // Light Red { 255, 255, 150 }, // Light Yellow { 150, 150, 255 }, // Light Blue { 150, 255, 255 }, // Light Cyan { 255, 150, 255 }, // Light Magenta { 150, 150, 128 }, // Light Gray { 138, 131, 64 } };// Brown //----------------------------------------------------------------------------- // Generate a texture that shows the various mip levels //----------------------------------------------------------------------------- void CTexture::GenerateShowMipLevelsTextures( IVTFTexture *pTexture ) { if( pTexture->FaceCount() > 1 ) return; switch( pTexture->Format() ) { // These are formats that we don't bother with for generating mip level textures. case IMAGE_FORMAT_RGBA16161616F: case IMAGE_FORMAT_R32F: case IMAGE_FORMAT_RGB323232F: case IMAGE_FORMAT_RGBA32323232F: case IMAGE_FORMAT_UV88: break; default: for (int iFrame = 0; iFrame < pTexture->FrameCount(); ++iFrame ) { for (int iFace = 0; iFace < pTexture->FaceCount(); ++iFace ) { for (int iMip = 0; iMip < pTexture->MipCount(); ++iMip ) { int red = sg_nMipLevelColors[iMip][0];//( ( iMip + 1 ) & 2 ) ? 255 : 0; int green = sg_nMipLevelColors[iMip][1];//( ( iMip + 1 ) & 1 ) ? 255 : 0; int blue = sg_nMipLevelColors[iMip][2];//( ( iMip + 1 ) & 4 ) ? 255 : 0; int nWidth, nHeight, nDepth; pTexture->ComputeMipLevelDimensions( iMip, &nWidth, &nHeight, &nDepth ); if( pTexture->Format() == IMAGE_FORMAT_DXT1 || pTexture->Format() == IMAGE_FORMAT_DXT5 || pTexture->Format() == IMAGE_FORMAT_ATI1N || pTexture->Format() == IMAGE_FORMAT_ATI2N ) { unsigned char *pImageData = pTexture->ImageData( iFrame, iFace, iMip, 0, 0, 0 ); int alpha = 255; FillCompressedTextureWithSingleColor( red, green, blue, alpha, pImageData, nWidth, nHeight, nDepth, pTexture->Format() ); } else { for ( int z = 0; z < nDepth; ++z ) { CPixelWriter pixelWriter; pixelWriter.SetPixelMemory( pTexture->Format(), pTexture->ImageData( iFrame, iFace, iMip, 0, 0, z ), pTexture->RowSizeInBytes( iMip ) ); for (int y = 0; y < nHeight; ++y) { pixelWriter.Seek( 0, y ); for (int x = 0; x < nWidth; ++x) { pixelWriter.WritePixel( red, green, blue, 255 ); } } } } } } } break; } } //----------------------------------------------------------------------------- // Generate a texture that shows the various mip levels //----------------------------------------------------------------------------- void CTexture::CopyLowResImageToTexture( IVTFTexture *pTexture ) { int nFlags = pTexture->Flags(); nFlags |= TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_POINTSAMPLE; nFlags &= ~(TEXTUREFLAGS_TRILINEAR | TEXTUREFLAGS_ANISOTROPIC | TEXTUREFLAGS_HINT_DXT5); nFlags &= ~(TEXTUREFLAGS_NORMAL | TEXTUREFLAGS_ENVMAP); nFlags &= ~(TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA); Assert( pTexture->FrameCount() == 1 ); Init( pTexture->Width(), pTexture->Height(), 1, IMAGE_FORMAT_BGR888, nFlags, 1 ); pTexture->Init( m_LowResImageWidth, m_LowResImageHeight, 1, IMAGE_FORMAT_BGR888, nFlags, 1 ); // Don't bother computing the actual size; it's actually equal to the low-res size // With only one mip level m_dimsActual.m_nWidth = m_LowResImageWidth; m_dimsActual.m_nHeight = m_LowResImageHeight; m_dimsActual.m_nDepth = 1; m_dimsActual.m_nMipCount = 1; // Copy the row-res image into the VTF Texture CPixelWriter pixelWriter; pixelWriter.SetPixelMemory( pTexture->Format(), pTexture->ImageData( 0, 0, 0 ), pTexture->RowSizeInBytes( 0 ) ); for ( int y = 0; y < m_LowResImageHeight; ++y ) { pixelWriter.Seek( 0, y ); for ( int x = 0; x < m_LowResImageWidth; ++x ) { int red = m_pLowResImage[0]; int green = m_pLowResImage[1]; int blue = m_pLowResImage[2]; m_pLowResImage += 3; pixelWriter.WritePixel( red, green, blue, 255 ); } } } //----------------------------------------------------------------------------- // Sets up debugging texture bits, if appropriate //----------------------------------------------------------------------------- bool CTexture::SetupDebuggingTextures( IVTFTexture *pVTFTexture ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); if ( pVTFTexture->Flags() & TEXTUREFLAGS_NODEBUGOVERRIDE ) return false; // The all mips flag is typically used on detail textures, which can // really mess up visualization if we apply the debug-colorized // versions of them to debug-colorized base textures, so skip 'em if ( g_config.nShowMipLevels && !(pVTFTexture->Flags() & TEXTUREFLAGS_ALL_MIPS) ) { // mat_showmiplevels 1 means don't do normal maps if ( ( g_config.nShowMipLevels == 1 ) && ( pVTFTexture->Flags() & ( TEXTUREFLAGS_NORMAL | TEXTUREFLAGS_SSBUMP ) ) ) return false; // mat_showmiplevels 2 means don't do base textures if ( ( g_config.nShowMipLevels == 2 ) && !( pVTFTexture->Flags() & ( TEXTUREFLAGS_NORMAL | TEXTUREFLAGS_SSBUMP ) ) ) return false; // This mode shows the mip levels as different colors GenerateShowMipLevelsTextures( pVTFTexture ); return true; } else if ( g_config.bShowLowResImage && pVTFTexture->FrameCount() == 1 && pVTFTexture->FaceCount() == 1 && ((pVTFTexture->Flags() & TEXTUREFLAGS_NORMAL) == 0) && m_LowResImageWidth != 0 && m_LowResImageHeight != 0 ) { // This mode just uses the low res texture CopyLowResImageToTexture( pVTFTexture ); return true; } return false; } //----------------------------------------------------------------------------- // Converts the texture to the actual format // Returns true if conversion applied, false otherwise //----------------------------------------------------------------------------- bool CTexture::ConvertToActualFormat( IVTFTexture *pVTFTexture ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); if ( !g_pShaderDevice->IsUsingGraphics() ) return false; bool bConverted = false; ImageFormat fmt = m_ImageFormat; ImageFormat dstFormat = ComputeActualFormat( pVTFTexture->Format() ); if ( fmt != dstFormat ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - conversion from (%d to %d)", __FUNCTION__, fmt, dstFormat ); pVTFTexture->ConvertImageFormat( dstFormat, false ); m_ImageFormat = dstFormat; bConverted = true; } else if ( HardwareConfig()->GetHDRType() == HDR_TYPE_INTEGER && fmt == dstFormat && dstFormat == IMAGE_FORMAT_RGBA16161616F ) { // This is to force at most the precision of int16 for fp16 texture when running the integer path. pVTFTexture->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616, false ); pVTFTexture->ConvertImageFormat( IMAGE_FORMAT_RGBA16161616F, false ); bConverted = true; } return bConverted; } void CTexture::GetFilename( char *pOut, int maxLen ) const { const char *pName = m_Name.String(); bool bIsUNCName = ( pName[0] == '/' && pName[1] == '/' && pName[2] != '/' ); if ( !bIsUNCName ) { Q_snprintf( pOut, maxLen, "materials/%s" TEXTURE_FNAME_EXTENSION, pName ); } else { Q_snprintf( pOut, maxLen, "%s" TEXTURE_FNAME_EXTENSION, pName ); } } void CTexture::ReloadFilesInList( IFileList *pFilesToReload ) { if ( IsProcedural() || IsRenderTarget() ) return; char filename[MAX_PATH]; GetFilename( filename, sizeof( filename ) ); if ( pFilesToReload->IsFileInList( filename ) ) { Download(); } } //----------------------------------------------------------------------------- // Loads the texture bits from a file. //----------------------------------------------------------------------------- IVTFTexture *CTexture::LoadTextureBitsFromFile( char *pCacheFileName, char **ppResolvedFilename ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s %s", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, pCacheFileName ) ); if ( m_bStreamingFileReadFailed ) { Assert( m_pStreamingVTF == NULL ); return HandleFileLoadFailedTexture( GetScratchVTFTexture() ); } // OSX hackery int nPreserveFlags = 0; if ( m_nFlags & TEXTUREFLAGS_SRGB ) nPreserveFlags |= TEXTUREFLAGS_SRGB; unsigned int stripFlags = 0; IVTFTexture *pVTFTexture = m_pStreamingVTF; if ( !pVTFTexture ) { pVTFTexture = GetScratchVTFTexture(); FileHandle_t fileHandle = FILESYSTEM_INVALID_HANDLE; if ( !GetFileHandle( &fileHandle, pCacheFileName, ppResolvedFilename ) ) return HandleFileLoadFailedTexture( pVTFTexture ); TextureLODControlSettings_t settings = m_cachedFileLodSettings; if ( !SLoadTextureBitsFromFile( &pVTFTexture, fileHandle, m_nFlags | nPreserveFlags, &settings, m_nDesiredDimensionLimit, &m_nStreamingMips, GetName(), pCacheFileName, &m_dimsMapping, &m_dimsActual, &m_dimsAllocated, &stripFlags ) ) { g_pFullFileSystem->Close( fileHandle ); return HandleFileLoadFailedTexture( pVTFTexture ); } g_pFullFileSystem->Close( fileHandle ); } // Don't reinitialize here if we're streaming in the fine levels, we already have been initialized with coarse. if ( ( m_nFlags & TEXTUREFLAGS_STREAMABLE_FINE ) == 0 ) { // Initing resets these, but we're happy with the values now--so store and restore them around the Init call. TexDimensions_t actual = m_dimsActual, allocated = m_dimsAllocated; // Initialize the texture class with vtf header data before operations Init( m_dimsMapping.m_nWidth, m_dimsMapping.m_nHeight, m_dimsMapping.m_nDepth, pVTFTexture->Format(), pVTFTexture->Flags() | nPreserveFlags, pVTFTexture->FrameCount() ); m_dimsActual = actual; m_dimsAllocated = allocated; m_nFlags &= ~stripFlags; } else { // Not illegal, just needs investigation. Assert( stripFlags == 0 ); } if ( m_pStreamingVTF ) ComputeActualSize( false, pVTFTexture, ( m_nFlags & TEXTUREFLAGS_STREAMABLE ) != 0 ); VectorCopy( pVTFTexture->Reflectivity(), m_vecReflectivity ); // If we've only streamed in coarse but haven't started on fine yet, go ahead and mark us as // partially resident and set up our clamping values. if ( ( m_nFlags & TEXTUREFLAGS_STREAMABLE ) == TEXTUREFLAGS_STREAMABLE_COARSE ) { pVTFTexture->GetMipmapRange( &m_lodClamp, NULL ); m_residenceTarget = RESIDENT_PARTIAL; m_residenceCurrent = RESIDENT_PARTIAL; } // Build the low-res texture LoadLowResTexture( pVTFTexture ); // Load the resources if ( unsigned int uiRsrcCount = pVTFTexture->GetResourceTypes( NULL, 0 ) ) { uint32 *arrRsrcTypes = ( uint32 * )_alloca( uiRsrcCount * sizeof( unsigned int ) ); pVTFTexture->GetResourceTypes( arrRsrcTypes, uiRsrcCount ); m_arrDataChunks.EnsureCapacity( uiRsrcCount ); for ( uint32 *arrRsrcTypesEnd = arrRsrcTypes + uiRsrcCount; arrRsrcTypes < arrRsrcTypesEnd; ++arrRsrcTypes ) { switch ( *arrRsrcTypes ) { case VTF_LEGACY_RSRC_LOW_RES_IMAGE: case VTF_LEGACY_RSRC_IMAGE: // These stock types use specific load routines continue; default: { DataChunk dc; dc.m_eType = *arrRsrcTypes; dc.m_eType &= ~RSRCF_MASK; size_t numBytes; if ( void *pvData = pVTFTexture->GetResourceData( dc.m_eType, &numBytes ) ) { Assert( numBytes >= sizeof( uint32 ) ); if ( numBytes == sizeof( dc.m_numBytes ) ) { dc.m_eType |= RSRCF_HAS_NO_DATA_CHUNK; dc.m_pvData = NULL; memcpy( &dc.m_numBytes, pvData, numBytes ); } else { dc.Allocate( numBytes ); memcpy( dc.m_pvData, pvData, numBytes ); } m_arrDataChunks.AddToTail( dc ); } } } } } // Try to set up debugging textures, if we're in a debugging mode if ( !IsProcedural() ) SetupDebuggingTextures( pVTFTexture ); if ( ConvertToActualFormat( pVTFTexture ) ) pVTFTexture; // STAGING_ONLY_EXEC ( Warning( "\"%s\" not in final format, this is causing stutters or load time bloat!\n", pCacheFileName ) ); return pVTFTexture; } IVTFTexture *CTexture::HandleFileLoadFailedTexture( IVTFTexture *pVTFTexture ) { // create the error texture // This will make a checkerboard texture to indicate failure pVTFTexture->Init( 32, 32, 1, IMAGE_FORMAT_BGRA8888, m_nFlags, 1 ); Init( pVTFTexture->Width(), pVTFTexture->Height(), pVTFTexture->Depth(), pVTFTexture->Format(), pVTFTexture->Flags(), pVTFTexture->FrameCount() ); m_vecReflectivity.Init( 0.5f, 0.5f, 0.5f ); // NOTE: For mat_picmip to work, we must use the same size (32x32) // Which should work since every card can handle textures of that size m_dimsAllocated.m_nWidth = m_dimsActual.m_nWidth = pVTFTexture->Width(); m_dimsAllocated.m_nHeight = m_dimsActual.m_nHeight = pVTFTexture->Height(); m_dimsAllocated.m_nDepth = 1; m_dimsAllocated.m_nMipCount = m_dimsActual.m_nMipCount = 1; m_nStreamingMips = 0; // generate the checkerboard TextureManager()->GenerateErrorTexture( this, pVTFTexture ); ConvertToActualFormat( pVTFTexture ); // Deactivate procedural texture... m_nFlags &= ~TEXTUREFLAGS_PROCEDURAL; SetErrorTexture( true ); return pVTFTexture; } //----------------------------------------------------------------------------- // Computes subrect for a particular miplevel //----------------------------------------------------------------------------- void CTexture::ComputeMipLevelSubRect( const Rect_t* pSrcRect, int nMipLevel, Rect_t *pSubRect ) { if (nMipLevel == 0) { *pSubRect = *pSrcRect; return; } float flInvShrink = 1.0f / (float)(1 << nMipLevel); pSubRect->x = pSrcRect->x * flInvShrink; pSubRect->y = pSrcRect->y * flInvShrink; pSubRect->width = (int)ceil( (pSrcRect->x + pSrcRect->width) * flInvShrink ) - pSubRect->x; pSubRect->height = (int)ceil( (pSrcRect->y + pSrcRect->height) * flInvShrink ) - pSubRect->y; } //----------------------------------------------------------------------------- // Computes the face count + first face //----------------------------------------------------------------------------- void CTexture::GetDownloadFaceCount( int &nFirstFace, int &nFaceCount ) { nFaceCount = 1; nFirstFace = 0; if ( IsCubeMap() ) { if ( HardwareConfig()->SupportsCubeMaps() ) { nFaceCount = CUBEMAP_FACE_COUNT-1; } else { // This will cause us to use the spheremap instead of the cube faces // in the case where we don't support cubemaps nFirstFace = CUBEMAP_FACE_SPHEREMAP; } } } //----------------------------------------------------------------------------- // Fixup a queue loaded texture with the delayed hi-res data //----------------------------------------------------------------------------- void CTexture::FixupTexture( const void *pData, int nSize, LoaderError_t loaderError ) { if ( loaderError != LOADERERROR_NONE ) { // mark as invalid nSize = 0; } m_nInternalFlags &= ~TEXTUREFLAGSINTERNAL_QUEUEDLOAD; // Make sure we've actually allocated the texture handles Assert( HasBeenAllocated() ); } static void QueuedLoaderCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) { reinterpret_cast< CTexture * >( pContext )->FixupTexture( pData, nSize, loaderError ); } //----------------------------------------------------------------------------- // Generates the procedural bits //----------------------------------------------------------------------------- IVTFTexture *CTexture::ReconstructPartialProceduralBits( const Rect_t *pRect, Rect_t *pActualRect ) { // Figure out the actual size for this texture based on the current mode bool bIgnorePicmip = ( m_nFlags & ( TEXTUREFLAGS_STAGING_MEMORY | TEXTUREFLAGS_IGNORE_PICMIP ) ) != 0; ComputeActualSize( bIgnorePicmip ); // Figure out how many mip levels we're skipping... int nSizeFactor = 1; int nWidth = GetActualWidth(); if ( nWidth != 0 ) { nSizeFactor = GetMappingWidth() / nWidth; } int nMipSkipCount = 0; while (nSizeFactor > 1) { nSizeFactor >>= 1; ++nMipSkipCount; } // Determine a rectangle appropriate for the actual size... // It must bound all partially-covered pixels.. ComputeMipLevelSubRect( pRect, nMipSkipCount, pActualRect ); // Create the texture IVTFTexture *pVTFTexture = GetScratchVTFTexture(); // Initialize the texture pVTFTexture->Init( m_dimsActual.m_nWidth, m_dimsActual.m_nHeight, m_dimsActual.m_nDepth, ComputeActualFormat( m_ImageFormat ), m_nFlags, m_nFrameCount ); // Generate the bits from the installed procedural regenerator if ( m_pTextureRegenerator ) { m_pTextureRegenerator->RegenerateTextureBits( this, pVTFTexture, pActualRect ); } else { // In this case, we don't have one, so just use a checkerboard... TextureManager()->GenerateErrorTexture( this, pVTFTexture ); } return pVTFTexture; } //----------------------------------------------------------------------------- // Regenerates the bits of a texture within a particular rectangle //----------------------------------------------------------------------------- void CTexture::ReconstructPartialTexture( const Rect_t *pRect ) { // FIXME: for now, only procedural textures can handle sub-rect specification. Assert( IsProcedural() ); // Also, we need procedural textures that have only a single copy!! // Otherwise this partial upload will not occur on all copies Assert( m_nFlags & TEXTUREFLAGS_SINGLECOPY ); Rect_t vtfRect; IVTFTexture *pVTFTexture = ReconstructPartialProceduralBits( pRect, &vtfRect ); // FIXME: for now, depth textures do not work with this. Assert( pVTFTexture->Depth() == 1 ); // Make sure we've allocated the API textures if ( !HasBeenAllocated() ) { if ( !AllocateShaderAPITextures() ) return; } int nFaceCount, nFirstFace; GetDownloadFaceCount( nFirstFace, nFaceCount ); // Blit down portions of the various VTF frames into the board memory int nStride; Rect_t mipRect; for ( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame ) { Modify( iFrame ); for ( int iFace = 0; iFace < nFaceCount; ++iFace ) { for ( int iMip = 0; iMip < m_dimsActual.m_nMipCount; ++iMip ) { pVTFTexture->ComputeMipLevelSubRect( &vtfRect, iMip, &mipRect ); nStride = pVTFTexture->RowSizeInBytes( iMip ); unsigned char *pBits = pVTFTexture->ImageData( iFrame, iFace + nFirstFace, iMip, mipRect.x, mipRect.y, 0 ); g_pShaderAPI->TexSubImage2D( iMip, iFace, mipRect.x, mipRect.y, 0, mipRect.width, mipRect.height, pVTFTexture->Format(), nStride, false, pBits ); } } } } //----------------------------------------------------------------------------- // Generates the procedural bits //----------------------------------------------------------------------------- IVTFTexture *CTexture::ReconstructProceduralBits() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // Figure out the actual size for this texture based on the current mode bool bIgnorePicmip = ( m_nFlags & ( TEXTUREFLAGS_STAGING_MEMORY | TEXTUREFLAGS_IGNORE_PICMIP ) ) != 0; ComputeActualSize( bIgnorePicmip ); // Create the texture IVTFTexture *pVTFTexture = NULL; { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - GetScratchVTFTexture", __FUNCTION__ ); pVTFTexture = GetScratchVTFTexture(); } { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Init", __FUNCTION__ ); // Initialize the texture pVTFTexture->Init( m_dimsActual.m_nWidth, m_dimsActual.m_nHeight, m_dimsActual.m_nDepth, ComputeActualFormat( m_ImageFormat ), m_nFlags, m_nFrameCount ); } // Generate the bits from the installed procedural regenerator if ( m_pTextureRegenerator ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - RegenerateTextureBits", __FUNCTION__ ); Rect_t rect; rect.x = 0; rect.y = 0; rect.width = m_dimsActual.m_nWidth; rect.height = m_dimsActual.m_nHeight; m_pTextureRegenerator->RegenerateTextureBits( this, pVTFTexture, &rect ); } else { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - GenerateErrorTexture", __FUNCTION__ ); // In this case, we don't have one, so just use a checkerboard... TextureManager()->GenerateErrorTexture( this, pVTFTexture ); } return pVTFTexture; } void CTexture::WriteDataToShaderAPITexture( int nFrameCount, int nFaceCount, int nFirstFace, int nMipCount, IVTFTexture *pVTFTexture, ImageFormat fmt ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // If we're a staging texture, there's nothing to do. if ( ( m_nFlags & TEXTUREFLAGS_STAGING_MEMORY ) != 0 ) return; for ( int iFrame = 0; iFrame < m_nFrameCount; ++iFrame ) { Modify( iFrame ); g_pShaderAPI->TexImageFromVTF( pVTFTexture, iFrame ); } } bool CTexture::IsDepthTextureFormat( ImageFormat fmt ) { return ( ( m_ImageFormat == IMAGE_FORMAT_NV_DST16 ) || ( m_ImageFormat == IMAGE_FORMAT_NV_DST24 ) || ( m_ImageFormat == IMAGE_FORMAT_NV_INTZ ) || ( m_ImageFormat == IMAGE_FORMAT_NV_RAWZ ) || ( m_ImageFormat == IMAGE_FORMAT_ATI_DST16 ) || ( m_ImageFormat == IMAGE_FORMAT_ATI_DST24 ) ); } //----------------------------------------------------------------------------- void CTexture::NotifyUnloadedFile() { // Make sure we have a regular texture that was loaded from a file if ( IsProcedural() || IsRenderTarget() || !m_Name.IsValid() ) return; const char *pName = m_Name.String(); if ( *pName == '\0' ) return; bool bIsUNCName = ( pName[0] == '/' && pName[1] == '/' && pName[2] != '/' ); if ( bIsUNCName ) return; // Generate the filename char pCacheFileName[MATERIAL_MAX_PATH]; Q_snprintf( pCacheFileName, sizeof( pCacheFileName ), "materials/%s" TEXTURE_FNAME_EXTENSION, pName ); // Let filesystem know that the file is uncached, so it knows // what to do with tracking info g_pFullFileSystem->NotifyFileUnloaded( pCacheFileName, "GAME" ); } //----------------------------------------------------------------------------- // Sets or updates the texture bits //----------------------------------------------------------------------------- void CTexture::ReconstructTexture( bool bCopyFromCurrent ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); Assert( !bCopyFromCurrent || HardwareConfig()->CanStretchRectFromTextures() ); int oldWidth = m_dimsAllocated.m_nWidth; int oldHeight = m_dimsAllocated.m_nHeight; int oldDepth = m_dimsAllocated.m_nDepth; int oldMipCount = m_dimsAllocated.m_nMipCount; int oldFrameCount = m_nFrameCount; // FIXME: Should RenderTargets be a special case of Procedural? char *pResolvedFilename = NULL; IVTFTexture *pVTFTexture = NULL; { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Begin", __FUNCTION__ ); if ( IsProcedural() ) { // This will call the installed texture bit regeneration interface pVTFTexture = ReconstructProceduralBits(); } else if ( IsRenderTarget() ) { // Compute the actual size + format based on the current mode bool bIgnorePicmip = m_RenderTargetSizeMode != RT_SIZE_LITERAL_PICMIP; ComputeActualSize( bIgnorePicmip ); } else if ( bCopyFromCurrent ) { ComputeActualSize( false, NULL, true ); } else { NotifyUnloadedFile(); char pCacheFileName[ MATERIAL_MAX_PATH ] = { 0 }; GetCacheFilename( pCacheFileName, ARRAYSIZE( pCacheFileName ) ); // Get the data from disk... // NOTE: Reloading the texture bits can cause the texture size, frames, format, pretty much *anything* can change. pVTFTexture = LoadTextureBitsFromFile( pCacheFileName, &pResolvedFilename ); } } if ( !HasBeenAllocated() || m_dimsAllocated.m_nWidth != oldWidth || m_dimsAllocated.m_nHeight != oldHeight || m_dimsAllocated.m_nDepth != oldDepth || m_dimsAllocated.m_nMipCount != oldMipCount || m_nFrameCount != oldFrameCount ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Allocation", __FUNCTION__ ); const bool cbCanStretchRectTextures = HardwareConfig()->CanStretchRectFromTextures(); const bool cbShouldMigrateTextures = ( ( m_nFlags & TEXTUREFLAGS_STREAMABLE_FINE ) != 0 ) && m_nFrameCount == oldFrameCount; // If we're just streaming in more data--or demoting ourselves, do a migration instead. if ( bCopyFromCurrent || ( cbCanStretchRectTextures && cbShouldMigrateTextures ) ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Migration", __FUNCTION__ ); MigrateShaderAPITextures(); // Ahh--I feel terrible about this, but we genuinely don't need anything else if we're streaming. if ( bCopyFromCurrent ) return; } else { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Deallocate / Allocate", __FUNCTION__ ); // If we're doing a wholesale copy, we need to restore these values that will be cleared by FreeShaderAPITextures. // Record them here, restore them below. unsigned int restoreStreamingFlag = ( m_nFlags & TEXTUREFLAGS_STREAMABLE ); ResidencyType_t restoreResidenceCurrent = m_residenceCurrent; ResidencyType_t restoreResidenceTarget = m_residenceTarget; if ( HasBeenAllocated() ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Deallocate", __FUNCTION__ ); // This is necessary for the reload case, we may discover there // are more frames of a texture animation, for example, which means // we can't rely on having the same number of texture frames. FreeShaderAPITextures(); } tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Allocate", __FUNCTION__ ); // Create the shader api textures if ( !AllocateShaderAPITextures() ) return; // Restored once we successfully allocate the shader api textures, but only if we're // if ( !cbCanStretchRectTextures && cbShouldMigrateTextures ) { m_nFlags |= restoreStreamingFlag; m_residenceCurrent = restoreResidenceCurrent; m_residenceTarget = restoreResidenceTarget; } } } else if ( bCopyFromCurrent ) { Assert( !"We're about to crash, last chance to examine this texture." ); } // Render Targets just need to be cleared, they have no upload if ( IsRenderTarget() ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - RT Stuff", __FUNCTION__ ); // Clear the render target to opaque black // Only clear if we're not a depth-stencil texture if ( !IsDepthTextureFormat( m_ImageFormat ) ) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Clearing", __FUNCTION__ ); CMatRenderContextPtr pRenderContext( MaterialSystem() ); ITexture *pThisTexture = GetEmbeddedTexture( 0 ); pRenderContext->PushRenderTargetAndViewport( pThisTexture ); // Push this texture on the stack g_pShaderAPI->ClearColor4ub( 0, 0, 0, 0xFF ); // Set the clear color to opaque black g_pShaderAPI->ClearBuffers( true, false, false, m_dimsActual.m_nWidth, m_dimsActual.m_nHeight ); // Clear the target pRenderContext->PopRenderTargetAndViewport(); // Pop back to previous target } // no upload return; } // Blit down the texture faces, frames, and mips into the board memory int nFirstFace, nFaceCount; GetDownloadFaceCount( nFirstFace, nFaceCount ); WriteDataToShaderAPITexture( m_nFrameCount, nFaceCount, nFirstFace, m_dimsActual.m_nMipCount, pVTFTexture, m_ImageFormat ); ReleaseScratchVTFTexture( pVTFTexture ); pVTFTexture = NULL; tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - Final Cleanup", __FUNCTION__ ); // allocated by strdup free( pResolvedFilename ); // the pc can afford to persist a large buffer FreeOptimalReadBuffer( 6*1024*1024 ); } void CTexture::GetCacheFilename( char* pOutBuffer, int nBufferSize ) const { Assert( pOutBuffer ); if ( IsProcedural() || IsRenderTarget() ) { pOutBuffer[0] = 0; return; } else { const char *pName; if ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_SHOULDEXCLUDE ) { pName = "dev/dev_exclude_error"; } else { pName = m_Name.String(); } bool bIsUNCName = ( pName[ 0 ] == '/' && pName[ 1 ] == '/' && pName[ 2 ] != '/' ); if ( !bIsUNCName ) { Q_snprintf( pOutBuffer, nBufferSize, "materials/%s" TEXTURE_FNAME_EXTENSION, pName ); } else { Q_snprintf( pOutBuffer, nBufferSize, "%s" TEXTURE_FNAME_EXTENSION, pName ); } } } bool CTexture::GetFileHandle( FileHandle_t *pOutFileHandle, char *pCacheFileName, char **ppResolvedFilename ) const { Assert( pOutFileHandle ); FileHandle_t& fileHandle = *pOutFileHandle; fileHandle = FILESYSTEM_INVALID_HANDLE; while ( fileHandle == FILESYSTEM_INVALID_HANDLE ) // run until found a file or out of rules { fileHandle = g_pFullFileSystem->OpenEx( pCacheFileName, "rb", 0, MaterialSystem()->GetForcedTextureLoadPathID(), ppResolvedFilename ); if ( fileHandle == FILESYSTEM_INVALID_HANDLE ) { // try any fallbacks. char *pHdrExt = Q_stristr( pCacheFileName, ".hdr" TEXTURE_FNAME_EXTENSION ); if ( pHdrExt ) { DevWarning( "A custom HDR cubemap \"%s\": cannot be found on disk.\n" "This really should have a HDR version, trying a fall back to a non-HDR version.\n", pCacheFileName ); strcpy( pHdrExt, TEXTURE_FNAME_EXTENSION ); } else { // no more fallbacks break; } } } if ( fileHandle == FILESYSTEM_INVALID_HANDLE ) { if ( Q_strnicmp( m_Name.String(), "env_cubemap", 12 ) ) { if ( IsPosix() ) { Msg( "\n ##### CTexture::LoadTextureBitsFromFile couldn't find %s\n", pCacheFileName ); } DevWarning( "\"%s\": can't be found on disk\n", pCacheFileName ); } return false; } return true; } // Get the shaderapi texture handle associated w/ a particular frame ShaderAPITextureHandle_t CTexture::GetTextureHandle( int nFrame, int nTextureChannel ) { if ( nFrame < 0 ) { nFrame = 0; Warning( "CTexture::GetTextureHandle(): nFrame is < 0!\n" ); } if ( nFrame >= m_nFrameCount ) { // NOTE: This can happen during alt-tab. If you alt-tab while loading a level then the first local cubemap bind will do this, for example. Assert( nFrame < m_nFrameCount ); return INVALID_SHADERAPI_TEXTURE_HANDLE; } Assert( nTextureChannel < 2 ); // Make sure we've actually allocated the texture handles Assert( m_pTextureHandles ); Assert( HasBeenAllocated() ); if ( m_pTextureHandles == NULL || !HasBeenAllocated() ) { return INVALID_SHADERAPI_TEXTURE_HANDLE; } // Don't get paired handle here...callers of this function don't know about paired textures return m_pTextureHandles[nFrame]; } void CTexture::GetLowResColorSample( float s, float t, float *color ) const { if ( m_LowResImageWidth <= 0 || m_LowResImageHeight <= 0 ) { // Warning( "Programming error: GetLowResColorSample \"%s\": %dx%d\n", m_pName, ( int )m_LowResImageWidth, ( int )m_LowResImageHeight ); return; } // force s and t into [0,1) if ( s < 0.0f ) { s = ( 1.0f - ( float )( int )s ) + s; } if ( t < 0.0f ) { t = ( 1.0f - ( float )( int )t ) + t; } s = s - ( float )( int )s; t = t - ( float )( int )t; s *= m_LowResImageWidth; t *= m_LowResImageHeight; int wholeS, wholeT; wholeS = ( int )s; wholeT = ( int )t; float fracS, fracT; fracS = s - ( float )( int )s; fracT = t - ( float )( int )t; // filter twice in the s dimension. float sColor[2][3]; int wholeSPlusOne = ( wholeS + 1 ) % m_LowResImageWidth; int wholeTPlusOne = ( wholeT + 1 ) % m_LowResImageHeight; sColor[0][0] = ( 1.0f - fracS ) * ( m_pLowResImage[( wholeS + wholeT * m_LowResImageWidth ) * 3 + 0] * ( 1.0f / 255.0f ) ); sColor[0][1] = ( 1.0f - fracS ) * ( m_pLowResImage[( wholeS + wholeT * m_LowResImageWidth ) * 3 + 1] * ( 1.0f / 255.0f ) ); sColor[0][2] = ( 1.0f - fracS ) * ( m_pLowResImage[( wholeS + wholeT * m_LowResImageWidth ) * 3 + 2] * ( 1.0f / 255.0f ) ); sColor[0][0] += fracS * ( m_pLowResImage[( wholeSPlusOne + wholeT * m_LowResImageWidth ) * 3 + 0] * ( 1.0f / 255.0f ) ); sColor[0][1] += fracS * ( m_pLowResImage[( wholeSPlusOne + wholeT * m_LowResImageWidth ) * 3 + 1] * ( 1.0f / 255.0f ) ); sColor[0][2] += fracS * ( m_pLowResImage[( wholeSPlusOne + wholeT * m_LowResImageWidth ) * 3 + 2] * ( 1.0f / 255.0f ) ); sColor[1][0] = ( 1.0f - fracS ) * ( m_pLowResImage[( wholeS + wholeTPlusOne * m_LowResImageWidth ) * 3 + 0] * ( 1.0f / 255.0f ) ); sColor[1][1] = ( 1.0f - fracS ) * ( m_pLowResImage[( wholeS + wholeTPlusOne * m_LowResImageWidth ) * 3 + 1] * ( 1.0f / 255.0f ) ); sColor[1][2] = ( 1.0f - fracS ) * ( m_pLowResImage[( wholeS + wholeTPlusOne * m_LowResImageWidth ) * 3 + 2] * ( 1.0f / 255.0f ) ); sColor[1][0] += fracS * ( m_pLowResImage[( wholeSPlusOne + wholeTPlusOne * m_LowResImageWidth ) * 3 + 0] * ( 1.0f / 255.0f ) ); sColor[1][1] += fracS * ( m_pLowResImage[( wholeSPlusOne + wholeTPlusOne * m_LowResImageWidth ) * 3 + 1] * ( 1.0f / 255.0f ) ); sColor[1][2] += fracS * ( m_pLowResImage[( wholeSPlusOne + wholeTPlusOne * m_LowResImageWidth ) * 3 + 2] * ( 1.0f / 255.0f ) ); color[0] = sColor[0][0] * ( 1.0f - fracT ) + sColor[1][0] * fracT; color[1] = sColor[0][1] * ( 1.0f - fracT ) + sColor[1][1] * fracT; color[2] = sColor[0][2] * ( 1.0f - fracT ) + sColor[1][2] * fracT; } int CTexture::GetApproximateVidMemBytes( void ) const { ImageFormat format = GetImageFormat(); int width = GetActualWidth(); int height = GetActualHeight(); int depth = GetActualDepth(); int numFrames = GetNumAnimationFrames(); bool isMipmapped = IsMipmapped(); return numFrames * ImageLoader::GetMemRequired( width, height, depth, format, isMipmapped ); } void CTexture::CopyFrameBufferToMe( int nRenderTargetID, Rect_t *pSrcRect, Rect_t *pDstRect ) { Assert( m_pTextureHandles && m_nFrameCount >= 1 ); if ( m_pTextureHandles && m_nFrameCount >= 1 ) { g_pShaderAPI->CopyRenderTargetToTextureEx( m_pTextureHandles[0], nRenderTargetID, pSrcRect, pDstRect ); } } void CTexture::CopyMeToFrameBuffer( int nRenderTargetID, Rect_t *pSrcRect, Rect_t *pDstRect ) { Assert( m_pTextureHandles && m_nFrameCount >= 1 ); if ( m_pTextureHandles && m_nFrameCount >= 1 ) { g_pShaderAPI->CopyTextureToRenderTargetEx( nRenderTargetID, m_pTextureHandles[0], pSrcRect, pDstRect ); } } ITexture *CTexture::GetEmbeddedTexture( int nIndex ) { return ( nIndex == 0 ) ? this : NULL; } void CTexture::DeleteIfUnreferenced() { if ( m_nRefCount > 0 ) return; if ( ThreadInMainThread() ) { // Render thread better not be active or bad things can happen. Assert( MaterialSystem()->GetRenderThreadId() == 0xFFFFFFFF ); TextureManager()->RemoveTexture( this ); return; } // Can't actually clean up from render thread--just safely mark this texture as // one we should check for cleanup next EndFrame when it's safe. TextureManager()->MarkUnreferencedTextureForCleanup( this ); } //Swap everything about a texture except the name. Created to support Portal mod's need for swapping out water render targets in recursive stencil views void CTexture::SwapContents( ITexture *pOther ) { if( (pOther == NULL) || (pOther == this) ) return; ICallQueue *pCallQueue = materials->GetRenderContext()->GetCallQueue(); if ( pCallQueue ) { pCallQueue->QueueCall( this, &CTexture::SwapContents, pOther ); return; } AssertMsg( dynamic_cast(pOther) != NULL, "Texture swapping broken" ); CTexture *pOtherAsCTexture = (CTexture *)pOther; CTexture *pTemp = (CTexture *)stackalloc( sizeof( CTexture ) ); //swap everything. Note that this copies the entire object including the // vtable pointer, thus ruining polymorphism. Use with care. // The unnecessary casts to (void*) hint to clang that we know what we // are doing. memcpy( (void*)pTemp, (const void*)this, sizeof( CTexture ) ); memcpy( (void*)this, (const void*)pOtherAsCTexture, sizeof( CTexture ) ); memcpy( (void*)pOtherAsCTexture, (const void*)pTemp, sizeof( CTexture ) ); //we have the other's name, give it back memcpy( &pOtherAsCTexture->m_Name, &m_Name, sizeof( m_Name ) ); //pTemp still has our name memcpy( &m_Name, &pTemp->m_Name, sizeof( m_Name ) ); } void CTexture::MarkAsPreloaded( bool bSet ) { if ( bSet ) { m_nInternalFlags |= TEXTUREFLAGSINTERNAL_PRELOADED; } else { m_nInternalFlags &= ~TEXTUREFLAGSINTERNAL_PRELOADED; } } bool CTexture::IsPreloaded() const { return ( ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_PRELOADED ) != 0 ); } void CTexture::MarkAsExcluded( bool bSet, int nDimensionsLimit ) { if ( bSet ) { // exclusion trumps picmipping m_nInternalFlags |= TEXTUREFLAGSINTERNAL_SHOULDEXCLUDE; m_nDesiredDimensionLimit = 0; } else { // not excluding, but can optionally picmip m_nInternalFlags &= ~TEXTUREFLAGSINTERNAL_SHOULDEXCLUDE; m_nDesiredDimensionLimit = nDimensionsLimit; } } bool CTexture::UpdateExcludedState( void ) { bool bDesired = ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_SHOULDEXCLUDE ) != 0; bool bActual = ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_EXCLUDED ) != 0; if ( ( bDesired == bActual ) && ( m_nDesiredDimensionLimit == m_nActualDimensionLimit ) ) { return false; } if ( m_nInternalFlags & TEXTUREFLAGSINTERNAL_QUEUEDLOAD ) { // already scheduled return true; } // force the texture to re-download, causes the texture bits to match its desired exclusion state Download(); return true; } void CTextureStreamingJob::OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs ) { const int cArgsAsInt = ( int ) pExtraArgs; Assert( m_pOwner == NULL || m_pOwner == pTex ); if ( m_pOwner ) m_pOwner->OnStreamingJobComplete( static_cast( cArgsAsInt ) ); // OnStreamingJobComplete should've cleaned us up Assert( m_pOwner == NULL ); } // ------------------------------------------------------------------------------------------------ int GetThreadId() { TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 ); // Turns the current thread into a 0-based index for use in accessing statics in this file. int retVal = INT_MAX; if ( ThreadInMainThread() ) retVal = 0; else if ( MaterialSystem()->GetRenderThreadId() == ThreadGetCurrentId() ) retVal = 1; else if ( TextureManager()->ThreadInAsyncLoadThread() ) retVal = 2; else if ( TextureManager()->ThreadInAsyncReadThread() ) retVal = 3; else { STAGING_ONLY_EXEC( AssertAlways( !"Unexpected thread in GetThreadId, need to debug this--crash is next. Tell McJohn." ) ); DebuggerBreakIfDebugging_StagingOnly(); } Assert( retVal < MAX_RENDER_THREADS ); return retVal; } // ------------------------------------------------------------------------------------------------ bool SLoadTextureBitsFromFile( IVTFTexture **ppOutVtfTexture, FileHandle_t hFile, unsigned int nFlags, TextureLODControlSettings_t* pInOutCachedFileLodSettings, int nDesiredDimensionLimit, unsigned short* pOutStreamedMips, const char* pName, const char* pCacheFileName, TexDimensions_t* pOptOutDimsMapping, TexDimensions_t* pOptOutDimsActual, TexDimensions_t* pOptOutDimsAllocated, unsigned int* pOptOutStripFlags ) { // NOTE! NOTE! NOTE! If you are making changes to this function, be aware that it has threading // NOTE! NOTE! NOTE! implications. It can be called synchronously by the Main thread, // NOTE! NOTE! NOTE! or by the streaming texture code! Assert( ppOutVtfTexture != NULL && *ppOutVtfTexture != NULL ); if ( V_strstr( pName, "c_rocketlauncher/c_rocketlauncher" ) ) { int i = 0; i = 3; } CUtlBuffer buf; { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - ReadHeaderFromFile", __FUNCTION__ ); int nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION ); // restrict read to the header only! // header provides info to avoid reading the entire file int nBytesOptimalRead = GetOptimalReadBuffer( &buf, hFile, nHeaderSize ); int nBytesRead = g_pFullFileSystem->ReadEx( buf.Base(), nBytesOptimalRead, Min( nHeaderSize, ( int ) g_pFullFileSystem->Size( hFile ) ), hFile ); // only read as much as the file has buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); nBytesRead = nHeaderSize = ( ( VTFFileBaseHeader_t * ) buf.Base() )->headerSize; g_pFullFileSystem->Seek( hFile, nHeaderSize, FILESYSTEM_SEEK_HEAD ); } // Unserialize the header only // need the header first to determine remainder of data if ( !( *ppOutVtfTexture )->Unserialize( buf, true ) ) { Warning( "Error reading texture header \"%s\"\n", pCacheFileName ); return false; } // Need to record this now, before we ask for the trimmed down data to potentially be loaded. TexDimensions_t dimsMappingCurrent( ( *ppOutVtfTexture )->Width(), ( *ppOutVtfTexture )->Height(), ( *ppOutVtfTexture )->MipCount(), ( *ppOutVtfTexture )->Depth() ); if ( pOptOutDimsMapping ) ( *pOptOutDimsMapping ) = dimsMappingCurrent; int nFullFlags = ( *ppOutVtfTexture )->Flags() | nFlags; // Seek the reading back to the front of the buffer buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); // Compute the actual texture dimensions int nMipSkipCount = ComputeMipSkipCount( pName, dimsMappingCurrent, false, *ppOutVtfTexture, nFullFlags, nDesiredDimensionLimit, pOutStreamedMips, pInOutCachedFileLodSettings, pOptOutDimsActual, pOptOutDimsAllocated, pOptOutStripFlags ); tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s - ReadDataFromFile", __FUNCTION__ ); // Determine how much of the file to read in int nFileSize = ( *ppOutVtfTexture )->FileSize( nMipSkipCount ); int nActualFileSize = (int)g_pFullFileSystem->Size( hFile ); if ( nActualFileSize < nFileSize ) { if ( mat_spew_on_texture_size.GetInt() ) DevMsg( "Bad VTF data for %s, expected file size:%d actual file size:%d \n", pCacheFileName, nFileSize, nActualFileSize ); nFileSize = nActualFileSize; } // Read only the portion of the file that we care about g_pFullFileSystem->Seek( hFile, 0, FILESYSTEM_SEEK_HEAD ); int nBytesOptimalRead = GetOptimalReadBuffer( &buf, hFile, nFileSize ); int nBytesRead = g_pFullFileSystem->ReadEx( buf.Base(), nBytesOptimalRead, nFileSize, hFile ); buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); // Some hardware doesn't support copying textures to other textures. For them, we need to reread the // whole file, so if they are doing the final read (the fine levels) then reread everything by stripping // off the flags we are trying to pass in. unsigned int nForceFlags = nFullFlags & TEXTUREFLAGS_STREAMABLE; if ( !HardwareConfig()->CanStretchRectFromTextures() && ( nForceFlags & TEXTUREFLAGS_STREAMABLE_FINE ) ) nForceFlags = 0; // NOTE: Skipping mip levels here will cause the size to be changed bool bRetVal = ( *ppOutVtfTexture )->UnserializeEx( buf, false, nForceFlags, nMipSkipCount ); FreeOptimalReadBuffer( 6*1024*1024 ); if ( !bRetVal ) { Warning( "Error reading texture data \"%s\"\n", pCacheFileName ); } return bRetVal; } //----------------------------------------------------------------------------- // Compute the actual mip count based on the actual size //----------------------------------------------------------------------------- int ComputeActualMipCount( const TexDimensions_t& actualDims, unsigned int nFlags ) { if ( nFlags & TEXTUREFLAGS_ENVMAP ) { if ( !HardwareConfig()->SupportsMipmappedCubemaps() ) { return 1; } } if ( nFlags & TEXTUREFLAGS_NOMIP ) { return 1; } // Unless ALLMIPS is set, we stop mips at 32x32 const int nMaxMipSize = 32; // Clamp border textures on Posix to fix L4D2 flashlight cookie issue #ifdef DX_TO_GL_ABSTRACTION if ( ( false && !g_bForceTextureAllMips && !( nFlags & TEXTUREFLAGS_ALL_MIPS ) ) || ( true && ( nFlags & TEXTUREFLAGS_BORDER ) ) ) #else if ( ( true && !g_bForceTextureAllMips && !( nFlags & TEXTUREFLAGS_ALL_MIPS ) ) || ( false && ( nFlags & TEXTUREFLAGS_BORDER ) ) ) #endif { int nNumMipLevels = 1; int h = actualDims.m_nWidth; int w = actualDims.m_nHeight; while ( MIN( w, h ) > nMaxMipSize ) { ++nNumMipLevels; w >>= 1; h >>= 1; } return nNumMipLevels; } return ImageLoader::GetNumMipMapLevels( actualDims.m_nWidth, actualDims.m_nHeight, actualDims.m_nDepth ); } // ------------------------------------------------------------------------------------------------ int ComputeMipSkipCount( const char* pName, const TexDimensions_t& mappingDims, bool bIgnorePicmip, IVTFTexture *pOptVTFTexture, unsigned int nFlags, int nDesiredDimensionLimit, unsigned short* pOutStreamedMips, TextureLODControlSettings_t* pInOutCachedFileLodSettings, TexDimensions_t* pOptOutActualDims, TexDimensions_t* pOptOutAllocatedDims, unsigned int* pOptOutStripFlags ) { // NOTE! NOTE! NOTE! If you are making changes to this function, be aware that it has threading // NOTE! NOTE! NOTE! implications. It can be called synchronously by the Main thread, // NOTE! NOTE! NOTE! or by the streaming texture code! Assert( pName != NULL ); Assert( pOutStreamedMips != NULL ); Assert( pInOutCachedFileLodSettings != NULL ); TexDimensions_t actualDims = mappingDims, allocatedDims; const bool bTextureMigration = ( nFlags & TEXTUREFLAGS_STREAMABLE ) != 0; unsigned int stripFlags = 0; int nClampX = actualDims.m_nWidth; // no clamping (clamp to texture dimensions) int nClampY = actualDims.m_nHeight; int nClampZ = actualDims.m_nDepth; // Fetch LOD settings from the VTF if available TextureLODControlSettings_t lcs; memset( &lcs, 0, sizeof( lcs ) ); TextureLODControlSettings_t const *pLODInfo = NULL; if ( pOptVTFTexture ) { pLODInfo = reinterpret_cast ( pOptVTFTexture->GetResourceData( VTF_RSRC_TEXTURE_LOD_SETTINGS, NULL ) ); // Texture streaming means there are times we call this where we don't have a VTFTexture, even though // we're a file. So we need to store off the LOD settings whenever we get in here with a file that has them // so that we can use the correct values for when we don't. Otherwise, the texture will be confused about // what size to use and everything will die a horrible, horrible death. if ( pLODInfo ) ( *pInOutCachedFileLodSettings ) = ( *pLODInfo ); } else if ( bTextureMigration ) { pLODInfo = pInOutCachedFileLodSettings; } if ( pLODInfo ) lcs = *pLODInfo; // Prepare the default LOD settings (that essentially result in no clamping) TextureLODControlSettings_t default_lod_settings; memset( &default_lod_settings, 0, sizeof( default_lod_settings ) ); { for ( int w = actualDims.m_nWidth; w > 1; w >>= 1 ) ++ default_lod_settings.m_ResolutionClampX; for ( int h = actualDims.m_nHeight; h > 1; h >>= 1 ) ++ default_lod_settings.m_ResolutionClampY; } // Check for LOD control override { TextureLodOverride::OverrideInfo oi = TextureLodOverride::Get( pName ); if ( oi.x && oi.y && !pLODInfo ) // If overriding texture that doesn't have lod info yet, then use default lcs = default_lod_settings; lcs.m_ResolutionClampX += oi.x; lcs.m_ResolutionClampY += oi.y; if ( int8( lcs.m_ResolutionClampX ) < 0 ) lcs.m_ResolutionClampX = 0; if ( int8( lcs.m_ResolutionClampY ) < 0 ) lcs.m_ResolutionClampY = 0; } // Compute the requested mip0 dimensions if ( lcs.m_ResolutionClampX && lcs.m_ResolutionClampY ) { nClampX = (1 << lcs.m_ResolutionClampX ); nClampY = (1 << lcs.m_ResolutionClampY ); } // In case clamp values exceed texture dimensions, then fix up // the clamping values nClampX = min( nClampX, (int)actualDims.m_nWidth ); nClampY = min( nClampY, (int)actualDims.m_nHeight ); // // Honor dimension limit restrictions // if ( nDesiredDimensionLimit > 0 ) { while ( nClampX > nDesiredDimensionLimit || nClampY > nDesiredDimensionLimit ) { nClampX >>= 1; nClampY >>= 1; } } // // Unless ignoring picmip, reflect the global picmip level in clamp dimensions // if ( !bIgnorePicmip ) { // If picmip requests texture degradation, then honor it // for loddable textures only if ( !( nFlags & TEXTUREFLAGS_NOLOD ) && ( g_config.skipMipLevels > 0 ) ) { for ( int iDegrade = 0; iDegrade < g_config.skipMipLevels; ++ iDegrade ) { // don't go lower than 4, or dxt textures won't work properly if ( nClampX > 4 && nClampY > 4 ) { nClampX >>= 1; nClampY >>= 1; } } } // If picmip requests quality upgrade, then always honor it if ( g_config.skipMipLevels < 0 ) { for ( int iUpgrade = 0; iUpgrade < - g_config.skipMipLevels; ++ iUpgrade ) { if ( nClampX < actualDims.m_nWidth && nClampY < actualDims.m_nHeight ) { nClampX <<= 1; nClampY <<= 1; } else break; } } } // // Now use hardware settings to clamp our "clamping dimensions" // int iHwWidth = HardwareConfig()->MaxTextureWidth(); int iHwHeight = HardwareConfig()->MaxTextureHeight(); int iHwDepth = HardwareConfig()->MaxTextureDepth(); nClampX = min( nClampX, max( iHwWidth, 4 ) ); nClampY = min( nClampY, max( iHwHeight, 4 ) ); nClampZ = min( nClampZ, max( iHwDepth, 1 ) ); // In case clamp values exceed texture dimensions, then fix up // the clamping values. nClampX = min( nClampX, (int)actualDims.m_nWidth ); nClampY = min( nClampY, (int)actualDims.m_nHeight ); nClampZ = min( nClampZ, (int)actualDims.m_nDepth ); // // Clamp to the determined dimensions // int numMipsSkipped = 0; // will compute now when clamping how many mips we drop while ( ( actualDims.m_nWidth > nClampX ) || ( actualDims.m_nHeight > nClampY ) || ( actualDims.m_nDepth > nClampZ ) ) { actualDims.m_nWidth >>= 1; actualDims.m_nHeight >>= 1; actualDims.m_nDepth = Max( 1, actualDims.m_nDepth >> 1 ); ++ numMipsSkipped; } Assert( actualDims.m_nWidth > 0 && actualDims.m_nHeight > 0 && actualDims.m_nDepth > 0 ); // Now that we've got the actual size, we can figure out the mip count actualDims.m_nMipCount = ComputeActualMipCount( actualDims, nFlags ); // If we're streaming, cut down what we're loading. // We can only stream things that have a mipmap pyramid (not just a single mipmap). bool bHasSetAllocation = false; if ( ( nFlags & TEXTUREFLAGS_STREAMABLE ) == TEXTUREFLAGS_STREAMABLE_COARSE ) { if ( actualDims.m_nMipCount > 1 ) { allocatedDims.m_nWidth = actualDims.m_nWidth; allocatedDims.m_nHeight = actualDims.m_nHeight; allocatedDims.m_nDepth = actualDims.m_nDepth; allocatedDims.m_nMipCount = actualDims.m_nMipCount; for ( int i = 0; i < STREAMING_START_MIPMAP; ++i ) { // Stop when width or height is at 4 pixels (or less). We could do better, // but some textures really can't function if they're less than 4 pixels (compressed textures, for example). if ( allocatedDims.m_nWidth <= 4 || allocatedDims.m_nHeight <= 4 ) break; allocatedDims.m_nWidth >>= 1; allocatedDims.m_nHeight >>= 1; allocatedDims.m_nDepth = Max( 1, allocatedDims.m_nDepth >> 1 ); allocatedDims.m_nMipCount = Max( 1, allocatedDims.m_nMipCount - 1 ); ++numMipsSkipped; ++( *pOutStreamedMips ); } bHasSetAllocation = true; } else { // Clear out that we're streaming, this isn't a texture we can stream. stripFlags |= TEXTUREFLAGS_STREAMABLE_COARSE; } } if ( !bHasSetAllocation ) { allocatedDims.m_nWidth = actualDims.m_nWidth; allocatedDims.m_nHeight = actualDims.m_nHeight; allocatedDims.m_nDepth = actualDims.m_nDepth; allocatedDims.m_nMipCount = actualDims.m_nMipCount; } if ( pOptOutActualDims ) *pOptOutActualDims = actualDims; if ( pOptOutAllocatedDims ) *pOptOutAllocatedDims = allocatedDims; if ( pOptOutStripFlags ) ( *pOptOutStripFlags ) = stripFlags; // Returns the number we skipped return numMipsSkipped; } //----------------------------------------------------------------------------- // Get an optimal read buffer, persists and avoids excessive allocations //----------------------------------------------------------------------------- int GetOptimalReadBuffer( CUtlBuffer* pOutOptimalBuffer, FileHandle_t hFile, int nSize ) { // NOTE! NOTE! NOTE! If you are making changes to this function, be aware that it has threading // NOTE! NOTE! NOTE! implications. It can be called synchronously by the Main thread, // NOTE! NOTE! NOTE! or by the streaming texture code! Assert( GetThreadId() < MAX_RENDER_THREADS ); tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (%d bytes)", __FUNCTION__, nSize ); Assert( pOutOptimalBuffer != NULL ); // get an optimal read buffer, only resize if necessary const int minSize = 2 * 1024 * 1024; // Uses 2MB min to avoid fragmentation nSize = max( nSize, minSize ); int nBytesOptimalRead = g_pFullFileSystem->GetOptimalReadSize( hFile, nSize ); const int ti = GetThreadId(); if ( nBytesOptimalRead > s_nOptimalReadBufferSize[ ti ] ) { FreeOptimalReadBuffer( 0 ); s_nOptimalReadBufferSize[ ti ] = nBytesOptimalRead; s_pOptimalReadBuffer[ ti ] = g_pFullFileSystem->AllocOptimalReadBuffer( hFile, nSize ); if ( mat_spewalloc.GetBool() ) { Msg( "Allocated optimal read buffer of %d bytes @ 0x%p for thread %d\n", s_nOptimalReadBufferSize[ ti ], s_pOptimalReadBuffer[ ti ], ti ); } } // set external buffer and reset to empty ( *pOutOptimalBuffer ).SetExternalBuffer( s_pOptimalReadBuffer[ ti ], s_nOptimalReadBufferSize[ ti ], 0 ); // return the optimal read size return nBytesOptimalRead; } //----------------------------------------------------------------------------- // Free the optimal read buffer if it grows too large //----------------------------------------------------------------------------- void FreeOptimalReadBuffer( int nMaxSize ) { // NOTE! NOTE! NOTE! If you are making changes to this function, be aware that it has threading // NOTE! NOTE! NOTE! implications. It can be called synchronously by the Main thread, // NOTE! NOTE! NOTE! or by the streaming texture code! Assert( GetThreadId() < MAX_RENDER_THREADS ); tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); const int ti = GetThreadId(); if ( s_pOptimalReadBuffer[ ti ] && s_nOptimalReadBufferSize[ ti ] >= nMaxSize ) { if ( mat_spewalloc.GetBool() ) { Msg( "Freeing optimal read buffer of %d bytes @ 0x%p for thread %d\n", s_nOptimalReadBufferSize[ ti ], s_pOptimalReadBuffer[ ti ], ti ); } g_pFullFileSystem->FreeOptimalReadBuffer( s_pOptimalReadBuffer[ ti ] ); s_pOptimalReadBuffer[ ti ] = NULL; s_nOptimalReadBufferSize[ ti ] = 0; } } #if defined( STAGING_ONLY ) CON_COMMAND( dumptexallocs, "List currently allocated textures and properties about them" ) { Msg( "Texture Memory Statistics follow:\n" ); uint64 totalTexMemAllocated = 0; FOR_EACH_MAP_FAST( g_currentTextures, i ) { const TexInfo_t& tex = g_currentTextures[ i ]; uint64 thisTexMem = tex.ComputeTexSize(); totalTexMemAllocated += thisTexMem; Msg( "%s: %llu bytes\n", ( const char * ) tex.m_Name, thisTexMem ); } Msg( "Total Memory Allocated: %llu bytes\n", totalTexMemAllocated ); } #endif ////////////////////////////////////////////////////////////////////////// // // Saving all the texture LOD modifications to content // ////////////////////////////////////////////////////////////////////////// #ifdef IS_WINDOWS_PC static bool SetBufferValue( char *chTxtFileBuffer, char const *szLookupKey, char const *szNewValue ) { bool bResult = false; size_t lenTmp = strlen( szNewValue ); size_t nTxtFileBufferLen = strlen( chTxtFileBuffer ); for ( char *pch = chTxtFileBuffer; ( NULL != ( pch = strstr( pch, szLookupKey ) ) ); ++ pch ) { char *val = pch + strlen( szLookupKey ); if ( !V_isspace( *val ) ) continue; else ++ val; char *pValStart = val; // Okay, here comes the value while ( *val && V_isspace( *val ) ) ++ val; while ( *val && !V_isspace( *val ) ) ++ val; char *pValEnd = val; // Okay, here ends the value memmove( pValStart + lenTmp, pValEnd, chTxtFileBuffer + nTxtFileBufferLen + 1 - pValEnd ); memcpy( pValStart, szNewValue, lenTmp ); nTxtFileBufferLen += ( lenTmp - ( pValEnd - pValStart ) ); bResult = true; } if ( !bResult ) { char *pchAdd = chTxtFileBuffer + nTxtFileBufferLen; strcpy( pchAdd + strlen( pchAdd ), "\n" ); strcpy( pchAdd + strlen( pchAdd ), szLookupKey ); strcpy( pchAdd + strlen( pchAdd ), " " ); strcpy( pchAdd + strlen( pchAdd ), szNewValue ); strcpy( pchAdd + strlen( pchAdd ), "\n" ); bResult = true; } return bResult; } // Replaces the first occurrence of "szFindData" with "szNewData" // Returns the remaining buffer past the replaced data or NULL if // no replacement occurred. static char * BufferReplace( char *buf, char const *szFindData, char const *szNewData ) { size_t len = strlen( buf ), lFind = strlen( szFindData ), lNew = strlen( szNewData ); if ( char *pBegin = strstr( buf, szFindData ) ) { memmove( pBegin + lNew, pBegin + lFind, buf + len - ( pBegin + lFind ) ); memmove( pBegin, szNewData, lNew ); return pBegin + lNew; } return NULL; } class CP4Requirement { public: CP4Requirement(); ~CP4Requirement(); protected: bool m_bLoadedModule; CSysModule *m_pP4Module; }; CP4Requirement::CP4Requirement() : m_bLoadedModule( false ), m_pP4Module( NULL ) { #ifdef STAGING_ONLY if ( p4 ) return; // load the p4 lib m_pP4Module = Sys_LoadModule( "p4lib" ); m_bLoadedModule = true; if ( m_pP4Module ) { CreateInterfaceFn factory = Sys_GetFactory( m_pP4Module ); if ( factory ) { p4 = ( IP4 * )factory( P4_INTERFACE_VERSION, NULL ); if ( p4 ) { extern CreateInterfaceFn g_fnMatSystemConnectCreateInterface; p4->Connect( g_fnMatSystemConnectCreateInterface ); p4->Init(); } } } #endif // STAGING_ONLY if ( !p4 ) { Warning( "Can't load p4lib.dll\n" ); } } CP4Requirement::~CP4Requirement() { if ( m_bLoadedModule && m_pP4Module ) { if ( p4 ) { p4->Shutdown(); p4->Disconnect(); } Sys_UnloadModule( m_pP4Module ); m_pP4Module = NULL; p4 = NULL; } } static ConVar mat_texture_list_content_path( "mat_texture_list_content_path", "", FCVAR_ARCHIVE, "The content path to the materialsrc directory. If left unset, it'll assume your content directory is next to the currently running game dir." ); CON_COMMAND_F( mat_texture_list_txlod_sync, "'reset' - resets all run-time changes to LOD overrides, 'save' - saves all changes to material content files", FCVAR_DONTRECORD ) { using namespace TextureLodOverride; if ( args.ArgC() != 2 ) goto usage; char const *szCmd = args.Arg( 1 ); Msg( "mat_texture_list_txlod_sync %s...\n", szCmd ); if ( !stricmp( szCmd, "reset" ) ) { for ( int k = 0; k < s_OverrideMap.GetNumStrings(); ++ k ) { char const *szTx = s_OverrideMap.String( k ); s_OverrideMap[ k ] = OverrideInfo(); // Reset the override info // Force the texture LOD override to get re-processed if ( ITexture *pTx = materials->FindTexture( szTx, "" ) ) pTx->ForceLODOverride( 0 ); else Warning( " mat_texture_list_txlod_sync reset - texture '%s' no longer found.\n", szTx ); } s_OverrideMap.Purge(); Msg("mat_texture_list_txlod_sync reset : completed.\n"); return; } else if ( !stricmp( szCmd, "save" ) ) { CP4Requirement p4req; if ( !p4 ) g_p4factory->SetDummyMode( true ); for ( int k = 0; k < s_OverrideMap.GetNumStrings(); ++ k ) { char const *szTx = s_OverrideMap.String( k ); OverrideInfo oi = s_OverrideMap[ k ]; ITexture *pTx = materials->FindTexture( szTx, "" ); if ( !oi.x || !oi.y ) continue; if ( !pTx ) { Warning( " mat_texture_list_txlod_sync save - texture '%s' no longer found.\n", szTx ); continue; } int iMaxWidth = pTx->GetActualWidth(), iMaxHeight = pTx->GetActualHeight(); // Save maxwidth and maxheight char chMaxWidth[20], chMaxHeight[20]; sprintf( chMaxWidth, "%d", iMaxWidth ), sprintf( chMaxHeight, "%d", iMaxHeight ); // We have the texture and path to its content char chResolveName[ MAX_PATH ] = {0}, chResolveNameArg[ MAX_PATH ] = {0}; Q_snprintf( chResolveNameArg, sizeof( chResolveNameArg ) - 1, "materials/%s" TEXTURE_FNAME_EXTENSION, szTx ); char *szTextureContentPath; if ( !mat_texture_list_content_path.GetString()[0] ) { szTextureContentPath = const_cast< char * >( g_pFullFileSystem->RelativePathToFullPath( chResolveNameArg, "game", chResolveName, sizeof( chResolveName ) - 1 ) ); if ( !szTextureContentPath ) { Warning( " mat_texture_list_txlod_sync save - texture '%s' is not loaded from file system.\n", szTx ); continue; } if ( !BufferReplace( szTextureContentPath, "\\game\\", "\\content\\" ) || !BufferReplace( szTextureContentPath, "\\materials\\", "\\materialsrc\\" ) ) { Warning( " mat_texture_list_txlod_sync save - texture '%s' cannot be mapped to content directory.\n", szTx ); continue; } } else { V_strncpy( chResolveName, mat_texture_list_content_path.GetString(), MAX_PATH ); V_strncat( chResolveName, "/", MAX_PATH ); V_strncat( chResolveName, szTx, MAX_PATH ); V_strncat( chResolveName, TEXTURE_FNAME_EXTENSION, MAX_PATH ); szTextureContentPath = chResolveName; } // Figure out what kind of source content is there: // 1. look for TGA - if found, get the txt file (if txt file missing, create one) // 2. otherwise look for PSD - affecting psdinfo // 3. else error char *pExtPut = szTextureContentPath + strlen( szTextureContentPath ) - strlen( TEXTURE_FNAME_EXTENSION ); // compensating the TEXTURE_FNAME_EXTENSION(.vtf) extension // 1.tga sprintf( pExtPut, ".tga" ); if ( g_pFullFileSystem->FileExists( szTextureContentPath ) ) { // Have tga - pump in the txt file sprintf( pExtPut, ".txt" ); CUtlBuffer bufTxtFileBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); g_pFullFileSystem->ReadFile( szTextureContentPath, 0, bufTxtFileBuffer ); for ( int kCh = 0; kCh < 1024; ++kCh ) bufTxtFileBuffer.PutChar( 0 ); // Now fix maxwidth/maxheight settings SetBufferValue( ( char * ) bufTxtFileBuffer.Base(), "maxwidth", chMaxWidth ); SetBufferValue( ( char * ) bufTxtFileBuffer.Base(), "maxheight", chMaxHeight ); bufTxtFileBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, strlen( ( char * ) bufTxtFileBuffer.Base() ) ); // Check out or add the file g_p4factory->SetOpenFileChangeList( "Texture LOD Autocheckout" ); CP4AutoEditFile autop4_edit( szTextureContentPath ); // Save the file contents if ( g_pFullFileSystem->WriteFile( szTextureContentPath, 0, bufTxtFileBuffer ) ) { Msg(" '%s' : saved.\n", szTextureContentPath ); CP4AutoAddFile autop4_add( szTextureContentPath ); } else { Warning( " '%s' : failed to save - set \"maxwidth %d maxheight %d\" manually.\n", szTextureContentPath, iMaxWidth, iMaxHeight ); } continue; } // 2.psd sprintf( pExtPut, ".psd" ); if ( g_pFullFileSystem->FileExists( szTextureContentPath ) ) { char chCommand[MAX_PATH]; char szTxtFileName[MAX_PATH] = {0}; GetModSubdirectory( "tmp_lod_psdinfo.txt", szTxtFileName, sizeof( szTxtFileName ) ); sprintf( chCommand, "/C psdinfo \"%s\" > \"%s\"", szTextureContentPath, szTxtFileName); ShellExecute( NULL, NULL, "cmd.exe", chCommand, NULL, SW_HIDE ); Sleep( 200 ); CUtlBuffer bufTxtFileBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); g_pFullFileSystem->ReadFile( szTxtFileName, 0, bufTxtFileBuffer ); for ( int kCh = 0; kCh < 1024; ++ kCh ) bufTxtFileBuffer.PutChar( 0 ); // Now fix maxwidth/maxheight settings SetBufferValue( ( char * ) bufTxtFileBuffer.Base(), "maxwidth", chMaxWidth ); SetBufferValue( ( char * ) bufTxtFileBuffer.Base(), "maxheight", chMaxHeight ); bufTxtFileBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, strlen( ( char * ) bufTxtFileBuffer.Base() ) ); // Check out or add the file // Save the file contents if ( g_pFullFileSystem->WriteFile( szTxtFileName, 0, bufTxtFileBuffer ) ) { g_p4factory->SetOpenFileChangeList( "Texture LOD Autocheckout" ); CP4AutoEditFile autop4_edit( szTextureContentPath ); sprintf( chCommand, "/C psdinfo -write \"%s\" < \"%s\"", szTextureContentPath, szTxtFileName ); Sleep( 200 ); ShellExecute( NULL, NULL, "cmd.exe", chCommand, NULL, SW_HIDE ); Sleep( 200 ); Msg(" '%s' : saved.\n", szTextureContentPath ); CP4AutoAddFile autop4_add( szTextureContentPath ); } else { Warning( " '%s' : failed to save - set \"maxwidth %d maxheight %d\" manually.\n", szTextureContentPath, iMaxWidth, iMaxHeight ); } continue; } // 3. - error sprintf( pExtPut, "" ); { Warning( " '%s' : doesn't specify a valid TGA or PSD file!\n", szTextureContentPath ); continue; } } Msg("mat_texture_list_txlod_sync save : completed.\n"); return; } else goto usage; return; usage: Warning( "Usage:\n" " mat_texture_list_txlod_sync reset - resets all run-time changes to LOD overrides;\n" " mat_texture_list_txlod_sync save - saves all changes to material content files.\n" ); } #endif