From 3bf9df6b2785fa6d951086978a3e66f49427166a Mon Sep 17 00:00:00 2001 From: FluorescentCIAAfricanAmerican <0934gj3049fk@protonmail.com> Date: Wed, 22 Apr 2020 12:56:21 -0400 Subject: 1 --- materialsystem/ctexture.cpp | 4959 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4959 insertions(+) create mode 100644 materialsystem/ctexture.cpp (limited to 'materialsystem/ctexture.cpp') diff --git a/materialsystem/ctexture.cpp b/materialsystem/ctexture.cpp new file mode 100644 index 0000000..dbfac95 --- /dev/null +++ b/materialsystem/ctexture.cpp @@ -0,0 +1,4959 @@ +//========= 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 -- cgit v1.2.3