You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4945 lines
154 KiB
4945 lines
154 KiB
//========= 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 <windows.h> |
|
#include <shellapi.h> |
|
#pragma comment ( lib, "shell32" ) |
|
#endif |
|
|
|
#include "materialsystem_global.h" |
|
#include "shaderapi/ishaderapi.h" |
|
#include "itextureinternal.h" |
|
#include "utlsymbol.h" |
|
#include "time.h" |
|
#include <sys/types.h> |
|
#include <sys/stat.h> |
|
#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 <stdlib.h> |
|
#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 = "<unnamed>"; |
|
} |
|
}; |
|
|
|
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<ITexture*>( 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; |
|
} |
|
|
|
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 = (uintp)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 = (uintp)SHADER_RENDERTARGET_NONE; |
|
} |
|
|
|
if ( pDepthTexture) |
|
{ |
|
depthTextureHandle = static_cast<ITextureInternal *>(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() == (uintp)-1 ); |
|
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<CTexture *>(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 intp cArgsAsInt = ( intp ) pExtraArgs; |
|
|
|
Assert( m_pOwner == NULL || m_pOwner == pTex ); |
|
if ( m_pOwner ) |
|
m_pOwner->OnStreamingJobComplete( static_cast<ResidencyType_t>( 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 ); |
|
|
|
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<TextureLODControlSettings_t const *> ( |
|
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
|
|
|