source-engine/materialsystem/cmaterial.cpp
2022-11-30 14:07:51 +03:00

3605 lines
98 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implementation of a material
//
//===========================================================================//
#include "imaterialinternal.h"
#include "bitmap/tgaloader.h"
#include "colorspace.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/itexture.h"
#include <string.h>
#include "materialsystem_global.h"
#include "shaderapi/ishaderapi.h"
#include "materialsystem/imaterialproxy.h"
#include "shadersystem.h"
#include "materialsystem/imaterialproxyfactory.h"
#include "IHardwareConfigInternal.h"
#include "utlsymbol.h"
#ifdef OSX
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#include "filesystem.h"
#include <KeyValues.h>
#include "mempool.h"
#include "shaderapi/ishaderutil.h"
#include "vtf/vtf.h"
#include "tier1/strtools.h"
#include <ctype.h>
#include "utlbuffer.h"
#include "mathlib/vmatrix.h"
#include "texturemanager.h"
#include "itextureinternal.h"
#include "mempool.h"
#include "tier1/callqueue.h"
#include "cmaterial_queuefriendly.h"
#include "ifilelist.h"
#include "tier0/icommandline.h"
#include "tier0/minidump.h"
// #define PROXY_TRACK_NAMES
//-----------------------------------------------------------------------------
// Material implementation
//-----------------------------------------------------------------------------
class CMaterial : public IMaterialInternal
{
public:
// Members of the IMaterial interface
const char *GetName() const;
const char *GetTextureGroupName() const;
PreviewImageRetVal_t GetPreviewImageProperties( int *width, int *height,
ImageFormat *imageFormat, bool* isTranslucent ) const;
PreviewImageRetVal_t GetPreviewImage( unsigned char *data, int width, int height,
ImageFormat imageFormat ) const;
int GetMappingWidth( );
int GetMappingHeight( );
int GetNumAnimationFrames( );
bool InMaterialPage( void ) { return false; }
void GetMaterialOffset( float *pOffset );
void GetMaterialScale( float *pOffset );
IMaterial *GetMaterialPage( void ) { return NULL; }
void IncrementReferenceCount( );
void DecrementReferenceCount( );
int GetEnumerationID( ) const;
void GetLowResColorSample( float s, float t, float *color ) const;
IMaterialVar * FindVar( char const *varName, bool *found, bool complain = true );
IMaterialVar * FindVarFast( char const *pVarName, unsigned int *pToken );
// Sets new VMT shader parameters for the material
virtual void SetShaderAndParams( KeyValues *pKeyValues );
bool UsesEnvCubemap( void );
bool NeedsSoftwareSkinning( void );
virtual bool NeedsSoftwareLighting( void );
bool NeedsTangentSpace( void );
bool NeedsPowerOfTwoFrameBufferTexture( bool bCheckSpecificToThisFrame = true );
bool NeedsFullFrameBufferTexture( bool bCheckSpecificToThisFrame = true );
virtual bool IsUsingVertexID( ) const;
// GR - Is lightmap alpha needed?
bool NeedsLightmapBlendAlpha( void );
virtual void AlphaModulate( float alpha );
virtual void ColorModulate( float r, float g, float b );
virtual float GetAlphaModulation();
virtual void GetColorModulation( float *r, float *g, float *b );
// Gets the morph format
virtual MorphFormat_t GetMorphFormat() const;
void SetMaterialVarFlag( MaterialVarFlags_t flag, bool on );
bool GetMaterialVarFlag( MaterialVarFlags_t flag ) const;
bool IsTranslucent();
bool IsTranslucentInternal( float fAlphaModulation ) const; //need to centralize the logic without relying on the *current* alpha modulation being that which is stored in m_pShaderParams[ALPHA].
bool IsAlphaTested();
bool IsVertexLit();
virtual bool IsSpriteCard();
void GetReflectivity( Vector& reflect );
bool GetPropertyFlag( MaterialPropertyTypes_t type );
// Is the material visible from both sides?
bool IsTwoSided();
int GetNumPasses( void );
int GetTextureMemoryBytes( void );
void SetUseFixedFunctionBakedLighting( bool bEnable );
virtual bool IsPrecached( ) const;
public:
// stuff that is visible only from within the material system
// constructor, destructor
CMaterial( char const* materialName, const char *pTextureGroupName, KeyValues *pVMTKeyValues );
virtual ~CMaterial();
void DrawMesh( VertexCompressionType_t vertexCompression );
int GetReferenceCount( ) const;
void Uncache( bool bPreserveVars = false );
void Precache();
void ReloadTextures( void );
// If provided, pKeyValues and pPatchKeyValues should come from LoadVMTFile()
bool PrecacheVars( KeyValues *pKeyValues = NULL, KeyValues *pPatchKeyValues = NULL, CUtlVector<FileNameHandle_t> *pIncludes = NULL, int nFindContext = MATERIAL_FINDCONTEXT_NONE );
void SetMinLightmapPageID( int pageID );
void SetMaxLightmapPageID( int pageID );
int GetMinLightmapPageID( ) const;
int GetMaxLightmapPageID( ) const;
void SetNeedsWhiteLightmap( bool val );
bool GetNeedsWhiteLightmap( ) const;
bool IsPrecachedVars( ) const;
IShader * GetShader() const;
const char *GetShaderName() const;
virtual void DeleteIfUnreferenced();
void SetEnumerationID( int id );
void CallBindProxy( void *proxyData );
virtual IMaterial *CheckProxyReplacement( void *proxyData );
bool HasProxy( void ) const;
// Sets the shader associated with the material
void SetShader( const char *pShaderName );
// Can we override this material in debug?
bool NoDebugOverride() const;
// Gets the vertex format
VertexFormat_t GetVertexFormat() const;
// diffuse bump lightmap?
bool IsUsingDiffuseBumpedLighting() const;
// lightmap?
bool IsUsingLightmap() const;
// Gets the vertex usage flags
VertexFormat_t GetVertexUsage() const;
// Debugs this material
bool PerformDebugTrace() const;
// Are we suppressed?
bool IsSuppressed() const;
// Do we use fog?
bool UseFog( void ) const;
// Should we draw?
void ToggleSuppression();
void ToggleDebugTrace();
// Refresh material based on current var values
void Refresh();
void RefreshPreservingMaterialVars();
// This computes the state snapshots for this material
void RecomputeStateSnapshots();
// Gets at the shader parameters
virtual int ShaderParamCount() const;
virtual IMaterialVar **GetShaderParams( void );
virtual void AddMaterialVar( IMaterialVar *pMaterialVar );
virtual bool IsErrorMaterial() const;
// Was this manually created (not read from a file?)
virtual bool IsManuallyCreated() const;
virtual bool NeedsFixedFunctionFlashlight() const;
virtual void MarkAsPreloaded( bool bSet );
virtual bool IsPreloaded() const;
virtual void ArtificialAddRef( void );
virtual void ArtificialRelease( void );
virtual void ReportVarChanged( IMaterialVar *pVar )
{
m_ChangeID++;
}
virtual void ClearContextData( void );
virtual uint32 GetChangeID() const { return m_ChangeID; }
virtual bool IsRealTimeVersion( void ) const { return true; }
virtual IMaterialInternal *GetRealTimeVersion( void ) { return this; }
virtual IMaterialInternal *GetQueueFriendlyVersion( void ) { return &m_QueueFriendlyVersion; }
void DecideShouldReloadFromWhitelist( IFileList *pFilesToReload );
void ReloadFromWhitelistIfMarked();
bool WasReloadedFromWhitelist();
private:
// Initializes, cleans up the shader params
void CleanUpShaderParams();
// Sets up an error shader when we run into problems.
void SetupErrorShader();
// Does this material have a UNC-file name?
bool UsesUNCFileName() const;
// Prints material flags.
void PrintMaterialFlags( int flags, int flagsDefined );
// Parses material flags
bool ParseMaterialFlag( KeyValues* pParseValue, IMaterialVar* pFlagVar,
IMaterialVar* pFlagDefinedVar, bool parsingOverrides, int& flagMask, int& overrideMask );
// Computes the material vars for the shader
int ParseMaterialVars( IShader* pShader, KeyValues& keyValues,
KeyValues* pOverride, bool modelDefault, IMaterialVar** ppVars, int nFindContext = MATERIAL_FINDCONTEXT_NONE );
// Figures out the preview image for worldcraft
char const* GetPreviewImageName( );
char const* GetPreviewImageFileName( void ) const;
// Hooks up the shader, returns keyvalues of fallback that was used
KeyValues* InitializeShader( KeyValues &keyValues, KeyValues &patchKeyValues, int nFindContext = MATERIAL_FINDCONTEXT_NONE );
// Finds the flag associated with a particular flag name
int FindMaterialVarFlag( char const* pFlagName ) const;
// Initializes, cleans up the state snapshots
bool InitializeStateSnapshots();
void CleanUpStateSnapshots();
// Initializes, cleans up the material proxy
void InitializeMaterialProxy( KeyValues* pFallbackKeyValues );
void CleanUpMaterialProxy();
void DetermineProxyReplacements( KeyValues *pFallbackKeyValues );
// Creates, destroys snapshots
RenderPassList_t *CreateRenderPassList();
void DestroyRenderPassList( RenderPassList_t *pPassList );
// Grabs the texture width and height from the var list for faster access
void PrecacheMappingDimensions( );
// Gets the renderstate
virtual ShaderRenderState_t *GetRenderState();
// Do we have a valid renderstate?
bool IsValidRenderState() const;
// Get the material var flags
int GetMaterialVarFlags() const;
void SetMaterialVarFlags( int flags, bool on );
int GetMaterialVarFlags2() const;
void SetMaterialVarFlags2( int flags, bool on );
// Returns a dummy material variable
IMaterialVar* GetDummyVariable();
IMaterialVar* GetShaderParam( int id );
void FindRepresentativeTexture( void );
bool ShouldSkipVar( KeyValues *pMaterialVar, bool * pWasConditional );
// Fixed-size allocator
DECLARE_FIXEDSIZE_ALLOCATOR( CMaterial );
private:
enum
{
MATERIAL_NEEDS_WHITE_LIGHTMAP = 0x1,
MATERIAL_IS_PRECACHED = 0x2,
MATERIAL_VARS_IS_PRECACHED = 0x4,
MATERIAL_VALID_RENDERSTATE = 0x8,
MATERIAL_IS_MANUALLY_CREATED = 0x10,
MATERIAL_USES_UNC_FILENAME = 0x20,
MATERIAL_IS_PRELOADED = 0x40,
MATERIAL_ARTIFICIAL_REFCOUNT = 0x80,
};
int m_iEnumerationID;
int m_minLightmapPageID;
int m_maxLightmapPageID;
unsigned short m_MappingWidth;
unsigned short m_MappingHeight;
IShader *m_pShader;
CUtlSymbol m_Name;
// Any textures created for this material go under this texture group.
CUtlSymbol m_TextureGroupName;
CInterlockedInt m_RefCount;
unsigned short m_Flags;
unsigned char m_VarCount;
CUtlVector< IMaterialProxy * > m_ProxyInfo;
#ifdef PROXY_TRACK_NAMES
// Array to track names of above material proxies. Useful for tracking down issues with proxies.
CUtlVector< CUtlString > m_ProxyInfoNames;
#endif
IMaterialVar** m_pShaderParams;
IMaterialProxy *m_pReplacementProxy;
ShaderRenderState_t m_ShaderRenderState;
// This remembers filenames of VMTs that we included so we can sv_pure/flush ourselves if any of them need to be reloaded.
CUtlVector<FileNameHandle_t> m_VMTIncludes;
bool m_bShouldReloadFromWhitelist; // Tells us if the material decided it should be reloaded due to sv_pure whitelist changes.
ITextureInternal *m_representativeTexture;
Vector m_Reflectivity;
uint32 m_ChangeID;
// Used only by procedural materials; it essentially is an in-memory .VMT file
KeyValues *m_pVMTKeyValues;
#if defined( _DEBUG )
// Makes it easier to see what's going on
char *m_pDebugName;
#endif
protected:
CMaterial_QueueFriendly m_QueueFriendlyVersion;
};
// NOTE: This must be the last file included
// Has to exist *after* fixed size allocator declaration
#include "tier0/memdbgon.h"
// Forward decls of helper functions for dealing with patch vmts.
static void ApplyPatchKeyValues( KeyValues &keyValues, KeyValues &patchKeyValues );
static bool AccumulateRecursiveVmtPatches( KeyValues &patchKeyValuesOut, KeyValues **ppBaseKeyValuesOut,
const KeyValues& keyValues, const char *pPathID, CUtlVector<FileNameHandle_t> *pIncludes );
//-----------------------------------------------------------------------------
// Parser utilities
//-----------------------------------------------------------------------------
static inline bool IsWhitespace( char c )
{
return c == ' ' || c == '\t';
}
static inline bool IsEndline( char c )
{
return c == '\n' || c == '\0';
}
static inline bool IsVector( char const* v )
{
while (IsWhitespace(*v))
{
++v;
if (IsEndline(*v))
return false;
}
return *v == '[' || *v == '{';
}
//-----------------------------------------------------------------------------
// Methods to create state snapshots
//-----------------------------------------------------------------------------
#include "tier0/memdbgoff.h"
#ifndef _CONSOLE
struct EditorRenderStateList_t
{
// Store combo of alpha, color, fixed-function baked lighting, flashlight, editor mode
RenderPassList_t m_Snapshots[SNAPSHOT_COUNT_EDITOR];
DECLARE_FIXEDSIZE_ALLOCATOR( EditorRenderStateList_t );
};
#endif
struct StandardRenderStateList_t
{
// Store combo of alpha, color, fixed-function baked lighting, flashlight
RenderPassList_t m_Snapshots[SNAPSHOT_COUNT_NORMAL];
DECLARE_FIXEDSIZE_ALLOCATOR( StandardRenderStateList_t );
};
#include "tier0/memdbgon.h"
#ifndef _CONSOLE
DEFINE_FIXEDSIZE_ALLOCATOR( EditorRenderStateList_t, 256, true );
#endif
DEFINE_FIXEDSIZE_ALLOCATOR( StandardRenderStateList_t, 256, true );
//-----------------------------------------------------------------------------
// class factory methods
//-----------------------------------------------------------------------------
DEFINE_FIXEDSIZE_ALLOCATOR( CMaterial, 256, true );
IMaterialInternal* IMaterialInternal::CreateMaterial( char const* pMaterialName, const char *pTextureGroupName, KeyValues *pVMTKeyValues )
{
MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
IMaterialInternal *pResult = new CMaterial( pMaterialName, pTextureGroupName, pVMTKeyValues );
MaterialSystem()->Unlock( hMaterialLock );
return pResult;
}
void IMaterialInternal::DestroyMaterial( IMaterialInternal* pMaterial )
{
MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
if (pMaterial)
{
Assert( pMaterial->IsRealTimeVersion() );
CMaterial* pMatImp = static_cast<CMaterial*>(pMaterial);
// Deletion of the error material is deferred until after all other materials have been deleted.
// See CMaterialSystem::CleanUpErrorMaterial() in cmaterialsystem.cpp.
if ( !pMatImp->IsErrorMaterial() )
{
delete pMatImp;
}
}
MaterialSystem()->Unlock( hMaterialLock );
}
//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CMaterial::CMaterial( char const* materialName, const char *pTextureGroupName, KeyValues *pKeyValues )
{
m_Reflectivity.Init( 0.2f, 0.2f, 0.2f );
int len = Q_strlen(materialName);
char* pTemp = (char*)_alloca( len + 1 );
// Strip off the extension
Q_StripExtension( materialName, pTemp, len+1 );
Q_strlower( pTemp );
#if defined( _X360 )
// material names are expected to be forward slashed for correct sort and find behavior!
// assert now to track alternate or regressed path that is source of inconsistency
Assert( strchr( pTemp, '\\' ) == NULL );
#endif
// Convert it to a symbol
m_Name = pTemp;
#if defined( _DEBUG )
m_pDebugName = new char[strlen(pTemp) + 1];
Q_strncpy( m_pDebugName, pTemp, strlen(pTemp) + 1 );
#endif
m_bShouldReloadFromWhitelist = false;
m_Flags = 0;
m_pShader = NULL;
m_pShaderParams = NULL;
m_RefCount = 0;
m_representativeTexture = NULL;
m_pReplacementProxy = NULL;
m_VarCount = 0;
m_MappingWidth = m_MappingHeight = 0;
m_iEnumerationID = 0;
m_minLightmapPageID = m_maxLightmapPageID = 0;
m_TextureGroupName = pTextureGroupName;
m_pVMTKeyValues = pKeyValues;
if (m_pVMTKeyValues)
{
m_Flags |= MATERIAL_IS_MANUALLY_CREATED;
}
if ( pTemp[0] == '/' && pTemp[1] == '/' && pTemp[2] != '/' )
{
m_Flags |= MATERIAL_USES_UNC_FILENAME;
}
// Initialize the renderstate to something indicating nothing should be drawn
m_ShaderRenderState.m_Flags = 0;
m_ShaderRenderState.m_VertexFormat = m_ShaderRenderState.m_VertexUsage = 0;
m_ShaderRenderState.m_MorphFormat = 0;
m_ShaderRenderState.m_pSnapshots = CreateRenderPassList();
m_ChangeID = 0;
m_QueueFriendlyVersion.SetRealTimeVersion( this );
}
CMaterial::~CMaterial()
{
MaterialSystem()->UnbindMaterial( this );
Uncache();
if ( m_RefCount != 0 )
{
DevWarning( 2, "Reference Count for Material %s (%d) != 0\n", GetName(), (int) m_RefCount );
}
if ( m_pVMTKeyValues )
{
m_pVMTKeyValues->deleteThis();
m_pVMTKeyValues = NULL;
}
DestroyRenderPassList( m_ShaderRenderState.m_pSnapshots );
m_representativeTexture = NULL;
#if defined( _DEBUG )
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 = 0xc0dedbad;
}
void CMaterial::ClearContextData( void )
{
int nSnapshotCount = SnapshotTypeCount();
for( int i = 0 ; i < nSnapshotCount ; i++ )
for( int j = 0 ; j < m_ShaderRenderState.m_pSnapshots[i].m_nPassCount; j++ )
{
if ( m_ShaderRenderState.m_pSnapshots[i].m_pContextData[j] )
{
delete m_ShaderRenderState.m_pSnapshots[i].m_pContextData[j];
m_ShaderRenderState.m_pSnapshots[i].m_pContextData[j] = NULL;
}
}
}
//-----------------------------------------------------------------------------
// Sets new VMT shader parameters for the material
//-----------------------------------------------------------------------------
void CMaterial::SetShaderAndParams( KeyValues *pKeyValues )
{
Uncache();
if ( m_pVMTKeyValues )
{
m_pVMTKeyValues->deleteThis();
m_pVMTKeyValues = NULL;
}
m_pVMTKeyValues = pKeyValues ? pKeyValues->MakeCopy() : NULL;
if (m_pVMTKeyValues)
{
m_Flags |= MATERIAL_IS_MANUALLY_CREATED;
}
// Apply patches
const char *pMaterialName = GetName();
char pFileName[MAX_PATH];
const char *pPathID = "GAME";
if ( !UsesUNCFileName() )
{
Q_snprintf( pFileName, sizeof( pFileName ), "materials/%s.vmt", pMaterialName );
}
else
{
Q_snprintf( pFileName, sizeof( pFileName ), "%s.vmt", pMaterialName );
if ( pMaterialName[0] == '/' && pMaterialName[1] == '/' && pMaterialName[2] != '/' )
{
// UNC, do full search
pPathID = NULL;
}
}
KeyValues *pLoadedKeyValues = new KeyValues( "vmt" );
if ( pLoadedKeyValues->LoadFromFile( g_pFullFileSystem, pFileName, pPathID ) )
{
// Load succeeded, check if it's a patch file
if ( V_stricmp( pLoadedKeyValues->GetName(), "patch" ) == 0 )
{
// it's a patch file, recursively build up patch keyvalues
KeyValues *pPatchKeyValues = new KeyValues( "vmt_patch" );
bool bSuccess = AccumulateRecursiveVmtPatches( *pPatchKeyValues, NULL, *pLoadedKeyValues, pPathID, NULL );
if ( bSuccess )
{
// Apply accumulated patches to final vmt
ApplyPatchKeyValues( *m_pVMTKeyValues, *pPatchKeyValues );
}
pPatchKeyValues->deleteThis();
}
}
pLoadedKeyValues->deleteThis();
if ( g_pShaderDevice->IsUsingGraphics() )
{
Precache();
}
}
//-----------------------------------------------------------------------------
// Creates, destroys snapshots
//-----------------------------------------------------------------------------
RenderPassList_t *CMaterial::CreateRenderPassList()
{
RenderPassList_t *pRenderPassList;
if ( IsConsole() || !MaterialSystem()->CanUseEditorMaterials() )
{
StandardRenderStateList_t *pList = new StandardRenderStateList_t;
pRenderPassList = (RenderPassList_t*)pList->m_Snapshots;
}
#ifndef _CONSOLE
else
{
EditorRenderStateList_t *pList = new EditorRenderStateList_t;
pRenderPassList = (RenderPassList_t*)pList->m_Snapshots;
}
#endif
int nSnapshotCount = SnapshotTypeCount();
memset( pRenderPassList, 0, nSnapshotCount * sizeof(RenderPassList_t) );
return pRenderPassList;
}
void CMaterial::DestroyRenderPassList( RenderPassList_t *pPassList )
{
if ( !pPassList )
return;
int nSnapshotCount = SnapshotTypeCount();
for( int i = 0 ; i < nSnapshotCount ; i++ )
for( int j = 0 ; j < pPassList[i].m_nPassCount; j++ )
{
if ( pPassList[i].m_pContextData[j] )
{
delete pPassList[i].m_pContextData[j];
pPassList[i].m_pContextData[j] = NULL;
}
}
if ( IsConsole() || !MaterialSystem()->CanUseEditorMaterials() )
{
StandardRenderStateList_t *pList = (StandardRenderStateList_t*)pPassList;
delete pList;
}
#ifndef _CONSOLE
else
{
EditorRenderStateList_t *pList = (EditorRenderStateList_t*)pPassList;
delete pList;
}
#endif
}
//-----------------------------------------------------------------------------
// Gets the renderstate
//-----------------------------------------------------------------------------
ShaderRenderState_t *CMaterial::GetRenderState()
{
Precache();
return &m_ShaderRenderState;
}
//-----------------------------------------------------------------------------
// Returns a dummy material variable
//-----------------------------------------------------------------------------
IMaterialVar* CMaterial::GetDummyVariable()
{
static IMaterialVar* pDummyVar = 0;
if (!pDummyVar)
pDummyVar = IMaterialVar::Create( 0, "$dummyVar", 0 );
return pDummyVar;
}
//-----------------------------------------------------------------------------
// Are vars precached?
//-----------------------------------------------------------------------------
bool CMaterial::IsPrecachedVars( ) const
{
return (m_Flags & MATERIAL_VARS_IS_PRECACHED) != 0;
}
//-----------------------------------------------------------------------------
// Are we precached?
//-----------------------------------------------------------------------------
bool CMaterial::IsPrecached( ) const
{
return (m_Flags & MATERIAL_IS_PRECACHED) != 0;
}
//-----------------------------------------------------------------------------
// Cleans up shader parameters
//-----------------------------------------------------------------------------
void CMaterial::CleanUpShaderParams()
{
if( m_pShaderParams )
{
for (int i = 0; i < m_VarCount; ++i)
{
IMaterialVar::Destroy( m_pShaderParams[i] );
}
free( m_pShaderParams );
m_pShaderParams = 0;
}
m_VarCount = 0;
}
//-----------------------------------------------------------------------------
// Initializes the material proxy
//-----------------------------------------------------------------------------
void CMaterial::InitializeMaterialProxy( KeyValues* pFallbackKeyValues )
{
IMaterialProxyFactory *pMaterialProxyFactory;
pMaterialProxyFactory = MaterialSystem()->GetMaterialProxyFactory();
if( !pMaterialProxyFactory )
return;
DetermineProxyReplacements( pFallbackKeyValues );
if ( m_pReplacementProxy )
{
m_ProxyInfo.AddToTail( m_pReplacementProxy );
#ifdef PROXY_TRACK_NAMES
m_ProxyInfoNames.AddToTail( "__replacementproxy" );
#endif
}
// See if we've got a proxy section; obey fallbacks
KeyValues* pProxySection = pFallbackKeyValues->FindKey("Proxies");
if ( pProxySection )
{
// Iterate through the section + create all of the proxies
KeyValues* pProxyKey = pProxySection->GetFirstSubKey();
for ( ; pProxyKey; pProxyKey = pProxyKey->GetNextKey() )
{
// Each of the proxies should themselves be databases
IMaterialProxy* pProxy = pMaterialProxyFactory->CreateProxy( pProxyKey->GetName() );
if (!pProxy)
{
Warning( "Error: Material \"%s\" : proxy \"%s\" not found!\n", GetName(), pProxyKey->GetName() );
continue;
}
if (!pProxy->Init( this->GetQueueFriendlyVersion(), pProxyKey ))
{
pMaterialProxyFactory->DeleteProxy( pProxy );
Warning( "Error: Material \"%s\" : proxy \"%s\" unable to initialize!\n", GetName(), pProxyKey->GetName() );
}
else
{
m_ProxyInfo.AddToTail( pProxy );
#ifdef PROXY_TRACK_NAMES
m_ProxyInfoNames.AddToTail( pProxyKey->GetName() );
#endif
}
}
}
}
//-----------------------------------------------------------------------------
// Cleans up the material proxy
//-----------------------------------------------------------------------------
void CMaterial::CleanUpMaterialProxy()
{
if ( !m_ProxyInfo.Count() )
return;
IMaterialProxyFactory *pMaterialProxyFactory;
pMaterialProxyFactory = MaterialSystem()->GetMaterialProxyFactory();
if ( !pMaterialProxyFactory )
return;
// Clean up material proxies
for ( int i = m_ProxyInfo.Count() - 1; i >= 0; i-- )
{
IMaterialProxy *pProxy = m_ProxyInfo[ i ];
pMaterialProxyFactory->DeleteProxy( pProxy );
}
m_ProxyInfo.RemoveAll();
#ifdef PROXY_TRACK_NAMES
m_ProxyInfoNames.RemoveAll();
#endif
}
void CMaterial::DetermineProxyReplacements( KeyValues *pFallbackKeyValues )
{
m_pReplacementProxy = MaterialSystem()->DetermineProxyReplacements( this, pFallbackKeyValues );
}
static char const *GetVarName( KeyValues *pVar )
{
char const *pVarName = pVar->GetName();
char const *pQuestion = strchr( pVarName, '?' );
if (! pQuestion )
return pVarName;
else
return pQuestion + 1;
}
//-----------------------------------------------------------------------------
// Finds the index of the material var associated with a var
//-----------------------------------------------------------------------------
static int FindMaterialVar( IShader* pShader, char const* pVarName )
{
if ( !pShader )
return -1;
// Strip preceeding spaces
pVarName += strspn( pVarName, " \t" );
for (int i = pShader->GetNumParams(); --i >= 0; )
{
// Makes the parser a little more lenient.. strips off bogus spaces in the var name.
const char *pParamName = pShader->GetParamName(i);
const char *pFound = Q_stristr( pVarName, pParamName );
// The found string had better start with the first non-whitespace character
if ( pFound != pVarName )
continue;
// Strip spaces at the end
int nLen = Q_strlen( pParamName );
pFound += nLen;
while ( true )
{
if ( !pFound[0] )
return i;
if ( !IsWhitespace( pFound[0] ) )
break;
++pFound;
}
}
return -1;
}
//-----------------------------------------------------------------------------
// Creates a vector material var
//-----------------------------------------------------------------------------
int ParseVectorFromKeyValueString( KeyValues *pKeyValue, const char *pMaterialName, float vecVal[4] )
{
char const* pScan = pKeyValue->GetString();
bool divideBy255 = false;
// skip whitespace
while( IsWhitespace(*pScan) )
{
++pScan;
}
if( *pScan == '{' )
{
divideBy255 = true;
}
else
{
Assert( *pScan == '[' );
}
// skip the '['
++pScan;
int i;
for( i = 0; i < 4; i++ )
{
// skip whitespace
while( IsWhitespace(*pScan) )
{
++pScan;
}
if( IsEndline(*pScan) || *pScan == ']' || *pScan == '}' )
{
if (*pScan != ']' && *pScan != '}')
{
Warning( "Warning in .VMT file (%s): no ']' or '}' found in vector key \"%s\".\n"
"Did you forget to surround the vector with \"s?\n", pMaterialName, pKeyValue->GetName() );
}
// allow for vec2's, etc.
vecVal[i] = 0.0f;
break;
}
char* pEnd;
vecVal[i] = strtod( pScan, &pEnd );
if (pScan == pEnd)
{
Warning( "Error in .VMT file: error parsing vector element \"%s\" in \"%s\"\n", pKeyValue->GetName(), pMaterialName );
return 0;
}
pScan = pEnd;
}
if( divideBy255 )
{
vecVal[0] *= ( 1.0f / 255.0f );
vecVal[1] *= ( 1.0f / 255.0f );
vecVal[2] *= ( 1.0f / 255.0f );
vecVal[3] *= ( 1.0f / 255.0f );
}
return i;
}
static IMaterialVar* CreateVectorMaterialVarFromKeyValue( IMaterial* pMaterial, KeyValues* pKeyValue )
{
char const *pszName = GetVarName( pKeyValue );
float vecVal[4];
int nDim = ParseVectorFromKeyValueString( pKeyValue, pszName, vecVal );
if ( nDim == 0 )
return NULL;
// Create the variable!
return IMaterialVar::Create( pMaterial, pszName, vecVal, nDim );
}
//-----------------------------------------------------------------------------
// Creates a vector material var
//-----------------------------------------------------------------------------
static IMaterialVar* CreateMatrixMaterialVarFromKeyValue( IMaterial* pMaterial, KeyValues* pKeyValue )
{
char const* pScan = pKeyValue->GetString();
char const *pszName = GetVarName( pKeyValue );
// Matrices can be specified one of two ways:
// [ # # # # # # # # # # # # # # # # ]
// or
// center # # scale # # rotate # translate # #
VMatrix mat;
int count = sscanf( pScan, " [ %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f %f ]",
&mat.m[0][0], &mat.m[0][1], &mat.m[0][2], &mat.m[0][3],
&mat.m[1][0], &mat.m[1][1], &mat.m[1][2], &mat.m[1][3],
&mat.m[2][0], &mat.m[2][1], &mat.m[2][2], &mat.m[2][3],
&mat.m[3][0], &mat.m[3][1], &mat.m[3][2], &mat.m[3][3] );
if (count == 16)
{
return IMaterialVar::Create( pMaterial, pszName, mat );
}
Vector2D scale, center;
float angle;
Vector2D translation;
count = sscanf( pScan, " center %f %f scale %f %f rotate %f translate %f %f",
&center.x, &center.y, &scale.x, &scale.y, &angle, &translation.x, &translation.y );
if (count != 7)
return NULL;
VMatrix temp;
MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
MatrixBuildScale( temp, scale.x, scale.y, 1.0f );
MatrixMultiply( temp, mat, mat );
MatrixBuildRotateZ( temp, angle );
MatrixMultiply( temp, mat, mat );
MatrixBuildTranslation( temp, center.x + translation.x, center.y + translation.y, 0.0f );
MatrixMultiply( temp, mat, mat );
// Create the variable!
return IMaterialVar::Create( pMaterial, pszName, mat );
}
//-----------------------------------------------------------------------------
// Creates a material var from a key value
//-----------------------------------------------------------------------------
static IMaterialVar* CreateMaterialVarFromKeyValue( IMaterial* pMaterial, KeyValues* pKeyValue )
{
char const *pszName = GetVarName( pKeyValue );
switch( pKeyValue->GetDataType() )
{
case KeyValues::TYPE_INT:
return IMaterialVar::Create( pMaterial, pszName, pKeyValue->GetInt() );
case KeyValues::TYPE_FLOAT:
return IMaterialVar::Create( pMaterial, pszName, pKeyValue->GetFloat() );
case KeyValues::TYPE_STRING:
{
char const* pString = pKeyValue->GetString();
if (!pString || !pString[0])
return 0;
// Look for matrices
IMaterialVar *pMatrixVar = CreateMatrixMaterialVarFromKeyValue( pMaterial, pKeyValue );
if (pMatrixVar)
return pMatrixVar;
// Look for vectors
if (!IsVector(pString))
return IMaterialVar::Create( pMaterial, pszName, pString );
// Parse the string as a vector...
return CreateVectorMaterialVarFromKeyValue( pMaterial, pKeyValue );
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Reads out common flags, prevents them from becoming material vars
//-----------------------------------------------------------------------------
int CMaterial::FindMaterialVarFlag( char const* pFlagName ) const
{
// Strip preceeding spaces
while ( pFlagName[0] )
{
if ( !IsWhitespace( pFlagName[0] ) )
break;
++pFlagName;
}
for( int i = 0; *ShaderSystem()->ShaderStateString(i); ++i )
{
const char *pStateString = ShaderSystem()->ShaderStateString(i);
const char *pFound = Q_stristr( pFlagName, pStateString );
// The found string had better start with the first non-whitespace character
if ( pFound != pFlagName )
continue;
// Strip spaces at the end
int nLen = Q_strlen( pStateString );
pFound += nLen;
while ( true )
{
if ( !pFound[0] )
return (1 << i);
if ( !IsWhitespace( pFound[0] ) )
break;
++pFound;
}
}
return 0;
}
//-----------------------------------------------------------------------------
// Print material flags
//-----------------------------------------------------------------------------
void CMaterial::PrintMaterialFlags( int flags, int flagsDefined )
{
int i;
for( i = 0; *ShaderSystem()->ShaderStateString(i); i++ )
{
if( flags & ( 1<<i ) )
{
Warning( "%s|", ShaderSystem()->ShaderStateString(i) );
}
}
Warning( "\n" );
}
//-----------------------------------------------------------------------------
// Parses material flags
//-----------------------------------------------------------------------------
bool CMaterial::ParseMaterialFlag( KeyValues* pParseValue, IMaterialVar* pFlagVar,
IMaterialVar* pFlagDefinedVar, bool parsingOverrides, int& flagMask, int& overrideMask )
{
// See if the var is a flag...
int flagbit = FindMaterialVarFlag( GetVarName( pParseValue ) );
if (!flagbit)
return false;
// Allow for flag override
int testMask = parsingOverrides ? overrideMask : flagMask;
if (testMask & flagbit)
{
Warning("Error! Flag \"%s\" is multiply defined in material \"%s\"!\n", pParseValue->GetName(), GetName() );
return true;
}
// Make sure overrides win
if (overrideMask & flagbit)
return true;
if (parsingOverrides)
overrideMask |= flagbit;
else
flagMask |= flagbit;
// If so, then set the flag bit
if (pParseValue->GetInt())
pFlagVar->SetIntValue( pFlagVar->GetIntValue() | flagbit );
else
pFlagVar->SetIntValue( pFlagVar->GetIntValue() & (~flagbit) );
// Mark the flag as being defined
pFlagDefinedVar->SetIntValue( pFlagDefinedVar->GetIntValue() | flagbit );
/*
if( stristr( m_pDebugName, "glasswindow064a" ) )
{
Warning( "flags\n" );
PrintMaterialFlags( pFlagVar->GetIntValue(), pFlagDefinedVar->GetIntValue() );
}
*/
return true;
}
ConVar mat_reduceparticles( "mat_reduceparticles", "0", FCVAR_ALLOWED_IN_COMPETITIVE );
bool CMaterial::ShouldSkipVar( KeyValues *pVar, bool *pWasConditional )
{
char const *pVarName = pVar->GetName();
char const *pQuestion = strchr( pVarName, '?' );
if ( ( ! pQuestion ) || (pQuestion == pVarName ) )
{
*pWasConditional = false; // unconditional var
return false;
}
else
{
bool bShouldSkip = true;
*pWasConditional = true;
// parse the conditional part
char pszConditionName[256];
V_strncpy( pszConditionName, pVarName, 1+pQuestion-pVarName );
char const *pCond = pszConditionName;
bool bToggle = false;
if ( pCond[0] == '!' )
{
pCond++;
bToggle = true;
}
if ( ! stricmp( pCond, "lowfill" ) )
{
bShouldSkip = !mat_reduceparticles.GetBool();
}
else if ( ! stricmp( pCond, "hdr" ) )
{
bShouldSkip = false; //( HardwareConfig()->GetHDRType() == HDR_TYPE_NONE );
}
else if ( ! stricmp( pCond, "srgb" ) )
{
bShouldSkip = ( !HardwareConfig()->UsesSRGBCorrectBlending() );
}
else if ( ! stricmp( pCond, "ldr" ) )
{
bShouldSkip = ( HardwareConfig()->GetHDRType() != HDR_TYPE_NONE );
}
else if ( ! stricmp( pCond, "360" ) )
{
bShouldSkip = !IsX360();
}
else if ( ! stricmp( pCond, "gameconsole" ) )
{
bShouldSkip = !IsGameConsole();
}
else
{
Warning( "unrecognized conditional test %s in %s\n", pVarName, GetName() );
}
return bShouldSkip ^ bToggle;
}
}
//-----------------------------------------------------------------------------
// Computes the material vars for the shader
//-----------------------------------------------------------------------------
int CMaterial::ParseMaterialVars( IShader* pShader, KeyValues& keyValues,
KeyValues* pOverrideKeyValues, bool modelDefault, IMaterialVar** ppVars, int nFindContext )
{
IMaterialVar* pNewVar;
bool pOverride[256];
bool bWasConditional[256];
int overrideMask = 0;
int flagMask = 0;
memset( ppVars, 0, 256 * sizeof(IMaterialVar*) );
memset( pOverride, 0, sizeof( pOverride ) );
memset( bWasConditional, 0, sizeof( bWasConditional ) );
// Create the flag var...
// Set model mode if we fell back from a model mode shader
int modelFlag = modelDefault ? MATERIAL_VAR_MODEL : 0;
ppVars[FLAGS] = IMaterialVar::Create( this, "$flags", modelFlag );
ppVars[FLAGS_DEFINED] = IMaterialVar::Create( this, "$flags_defined", modelFlag );
ppVars[FLAGS2] = IMaterialVar::Create( this, "$flags2", 0 );
ppVars[FLAGS_DEFINED2] = IMaterialVar::Create( this, "$flags_defined2", 0 );
int numParams = pShader ? pShader->GetNumParams() : 0;
int varCount = numParams;
bool parsingOverrides = (pOverrideKeyValues != 0);
KeyValues* pVar = pOverrideKeyValues ? pOverrideKeyValues->GetFirstSubKey() : keyValues.GetFirstSubKey();
const char *pszMatName = pVar ? pVar->GetString() : "Unknown";
while( pVar )
{
bool bProcessThisOne = true;
bool bIsConditionalVar;
const char *pszVarName = GetVarName( pVar );
if ( (nFindContext == MATERIAL_FINDCONTEXT_ISONAMODEL) && pszVarName && pszVarName[0] )
{
// Prevent ignorez models
// Should we do 'nofog' too? For now, decided not to.
if ( Q_stristr(pszVarName,"$ignorez") )
{
Warning("Ignoring material flag '%s' on material '%s'.\n", pszVarName, pszMatName );
goto nextVar;
}
}
// See if the var is a flag...
if (
ShouldSkipVar( pVar, &bIsConditionalVar ) || // should skip?
((pVar->GetName()[0] == '%') && (g_pShaderDevice->IsUsingGraphics()) && (!MaterialSystem()->CanUseEditorMaterials() ) ) || // is an editor var?
ParseMaterialFlag( pVar, ppVars[FLAGS], ppVars[FLAGS_DEFINED], parsingOverrides, flagMask, overrideMask ) || // is a flag?
ParseMaterialFlag( pVar, ppVars[FLAGS2], ppVars[FLAGS_DEFINED2], parsingOverrides, flagMask, overrideMask )
)
bProcessThisOne = false;
if ( bProcessThisOne )
{
// See if the var is one of the shader params
int varIdx = FindMaterialVar( pShader, pszVarName );
// Check for multiply defined or overridden
if (varIdx >= 0)
{
if (ppVars[varIdx] && (! bIsConditionalVar ) )
{
if ( !pOverride[varIdx] || parsingOverrides )
{
Warning("Error! Variable \"%s\" is multiply defined in material \"%s\"!\n", pVar->GetName(), GetName() );
}
goto nextVar;
}
}
else
{
int i;
for ( i = numParams; i < varCount; ++i)
{
Assert( ppVars[i] );
if (!stricmp( ppVars[i]->GetName(), pVar->GetName() ))
break;
}
if (i != varCount)
{
if ( !pOverride[i] || parsingOverrides )
{
Warning("Error! Variable \"%s\" is multiply defined in material \"%s\"!\n", pVar->GetName(), GetName() );
}
goto nextVar;
}
}
// Create a material var for this dudely dude; could be zero...
pNewVar = CreateMaterialVarFromKeyValue( this, pVar );
if (!pNewVar)
goto nextVar;
if (varIdx < 0)
{
varIdx = varCount++;
}
if ( ppVars[varIdx] )
{
IMaterialVar::Destroy( ppVars[varIdx] );
}
ppVars[varIdx] = pNewVar;
if (parsingOverrides)
pOverride[varIdx] = true;
bWasConditional[varIdx] = bIsConditionalVar;
}
nextVar:
pVar = pVar->GetNextKey();
if (!pVar && parsingOverrides)
{
pVar = keyValues.GetFirstSubKey();
parsingOverrides = false;
}
}
// Create undefined vars for all the actual material vars
for (int i = 0; i < numParams; ++i)
{
if (!ppVars[i])
ppVars[i] = IMaterialVar::Create( this, pShader->GetParamName(i) );
}
return varCount;
}
static KeyValues *CheckConditionalFakeShaderName( char const *pShaderName, char const *pSuffixName,
KeyValues *pKeyValues )
{
KeyValues *pFallbackSection = pKeyValues->FindKey( pSuffixName );
if (pFallbackSection)
return pFallbackSection;
char nameBuf[256];
V_snprintf( nameBuf, sizeof(nameBuf), "%s_%s", pShaderName, pSuffixName );
pFallbackSection = pKeyValues->FindKey( nameBuf );
if (pFallbackSection)
return pFallbackSection;
return NULL;
}
static KeyValues *FindBuiltinFallbackBlock( char const *pShaderName, KeyValues *pKeyValues )
{
// handle "fake" shader fallbacks which are conditional upon mode. like _hdr_dx9, etc
if ( HardwareConfig()->GetDXSupportLevel() < 90 )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<DX90", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() < 95 )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<DX95", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() < 90 || !HardwareConfig()->SupportsPixelShaders_2_b() )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<DX90_20b", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() >= 90 && HardwareConfig()->SupportsPixelShaders_2_b() )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,">=DX90_20b", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() <= 90 )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<=DX90", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() >= 90 )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,">=DX90", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() > 90 )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,">DX90", pKeyValues );
if ( pRet )
return pRet;
}
// if ( HardwareConfig()->GetHDRType() != HDR_TYPE_NONE )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"hdr_dx9", pKeyValues );
if ( pRet )
return pRet;
pRet = CheckConditionalFakeShaderName( pShaderName,"hdr", pKeyValues );
if ( pRet )
return pRet;
}
if( HardwareConfig()->GetHDRType() == HDR_TYPE_NONE )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"ldr", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->UsesSRGBCorrectBlending() )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"srgb", pKeyValues );
if ( pRet )
return pRet;
}
if ( HardwareConfig()->GetDXSupportLevel() >= 90 )
{
KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"dx9", pKeyValues );
if ( pRet )
return pRet;
}
return NULL;
}
inline const char *MissingShaderName()
{
return (IsWindows() && !IsEmulatingGL()) ? "Wireframe_DX8" : "Wireframe_DX9";
}
//-----------------------------------------------------------------------------
// Hooks up the shader
//-----------------------------------------------------------------------------
KeyValues* CMaterial::InitializeShader( KeyValues &keyValues, KeyValues &patchKeyValues, int nFindContext )
{
MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
KeyValues* pCurrentFallback = &keyValues;
KeyValues* pFallbackSection = 0;
char szShaderName[MAX_PATH];
char const* pShaderName = pCurrentFallback->GetName();
if ( !pShaderName )
{
// I'm not quite sure how this can happen, but we'll see...
Warning( "Shader not specified in material %s\nUsing wireframe instead...\n", GetName() );
Assert( 0 );
pShaderName = MissingShaderName();
}
else
{
// can't pass a stable reference to the key values name around
// naive leaf functions can cause KV system to re-alloc
V_strncpy( szShaderName, pShaderName, sizeof( szShaderName ) );
pShaderName = szShaderName;
}
IShader* pShader;
IMaterialVar* ppVars[256];
char pFallbackShaderNameBuf[256];
char pFallbackMaterialNameBuf[256];
int varCount = 0;
bool modelDefault = false;
// Keep going until there's no more fallbacks...
while( true )
{
// Find the shader for this material. Note that this may not be
// the actual shader we use due to fallbacks...
pShader = ShaderSystem()->FindShader( pShaderName );
if ( !pShader )
{
if ( g_pShaderDevice->IsUsingGraphics() )
{
Warning( "Error: Material \"%s\" uses unknown shader \"%s\"\n", GetName(), pShaderName );
//hushed Assert( 0 );
}
pShaderName = MissingShaderName();
pShader = ShaderSystem()->FindShader( pShaderName );
if ( !HushAsserts() )
{
AssertMsg( pShader, "pShader==NULL. Shader: %s", GetName() );
}
#ifndef DEDICATED
if ( !pShader )
{
#ifdef LINUX
// Exit out here. We're running into issues where this material is returned in a horribly broken
// state and you wind up crashing in LockMesh() because the vertex and index buffer pointers
// are NULL. You can repro this by not dying here and showing the intro movie. This happens on
// Linux when you run from a symlink'd SteamApps directory.
Error( "Shader '%s' for material '%s' not found.\n",
pCurrentFallback->GetName() ? pCurrentFallback->GetName() : pShaderName, GetName() );
#endif
MaterialSystem()->Unlock( hMaterialLock );
return NULL;
}
#endif
}
bool bHasBuiltinFallbackBlock = false;
if ( !pFallbackSection )
{
pFallbackSection = FindBuiltinFallbackBlock( pShaderName, &keyValues );
if( pFallbackSection )
{
bHasBuiltinFallbackBlock = true;
pFallbackSection->ChainKeyValue( &keyValues );
pCurrentFallback = pFallbackSection;
}
}
// Here we must set up all flags + material vars that the shader needs
// because it may look at them when choosing shader fallback.
varCount = ParseMaterialVars( pShader, keyValues, pFallbackSection, modelDefault, ppVars, nFindContext );
if ( !pShader )
break;
// Make sure we set default values before the fallback is looked for
ShaderSystem()->InitShaderParameters( pShader, ppVars, GetName() );
// Now that the material vars are parsed, see if there's a fallback
// But only if we're not in the tools
/*
if (!g_pShaderAPI->IsUsingGraphics())
break;
*/
// Check for a fallback; if not, we're done
pShaderName = pShader->GetFallbackShader( ppVars );
if (!pShaderName)
{
break;
}
// Copy off the shader name, as it may be in a materialvar in the shader
// because we're about to delete all materialvars
Q_strncpy( pFallbackShaderNameBuf, pShaderName, 256 );
pShaderName = pFallbackShaderNameBuf;
// Remember the model flag if we're on dx7 or higher...
if (HardwareConfig()->SupportsVertexAndPixelShaders())
{
modelDefault = ( ppVars[FLAGS]->GetIntValue() & MATERIAL_VAR_MODEL ) != 0;
}
// Try to get the section associated with the fallback shader
// Then chain it to the base data so it can override the
// values if it wants to
if( !bHasBuiltinFallbackBlock )
{
pFallbackSection = keyValues.FindKey( pShaderName );
if (pFallbackSection)
{
pFallbackSection->ChainKeyValue( &keyValues );
pCurrentFallback = pFallbackSection;
}
}
// Now, blow away all of the material vars + try again...
for (int i = 0; i < varCount; ++i)
{
Assert( ppVars[i] );
IMaterialVar::Destroy( ppVars[i] );
}
// Check the KeyValues for '$fallbackmaterial'
// Note we have to do this *after* we chain the keyvalues
// based on the fallback shader since the names of the fallback material
// must lie within the shader-specific block usually.
const char *pFallbackMaterial = pCurrentFallback->GetString( "$fallbackmaterial" );
if ( pFallbackMaterial[0] )
{
// Don't fallback to ourselves
if ( Q_stricmp( GetName(), pFallbackMaterial ) )
{
// Gotta copy it off; clearing the keyvalues will blow the string away
Q_strncpy( pFallbackMaterialNameBuf, pFallbackMaterial, 256 );
keyValues.Clear();
if( !LoadVMTFile( keyValues, patchKeyValues, pFallbackMaterialNameBuf, UsesUNCFileName(), NULL ) )
{
Warning( "CMaterial::PrecacheVars: error loading vmt file %s for %s\n", pFallbackMaterialNameBuf, GetName() );
keyValues = *(((CMaterial *)g_pErrorMaterial)->m_pVMTKeyValues);
}
}
else
{
Warning( "CMaterial::PrecacheVars: fallback material for vmt file %s is itself!\n", GetName() );
keyValues = *(((CMaterial *)g_pErrorMaterial)->m_pVMTKeyValues);
}
pCurrentFallback = &keyValues;
pFallbackSection = NULL;
// I'm not quite sure how this can happen, but we'll see...
pShaderName = pCurrentFallback->GetName();
if (!pShaderName)
{
Warning("Shader not specified in material %s (fallback %s)\nUsing wireframe instead...\n", GetName(), pFallbackMaterialNameBuf );
pShaderName = MissingShaderName();
}
}
}
// Store off the shader
m_pShader = pShader;
// Store off the material vars + flags
m_VarCount = varCount;
m_pShaderParams = (IMaterialVar**)malloc( varCount * sizeof(IMaterialVar*) );
memcpy( m_pShaderParams, ppVars, varCount * sizeof(IMaterialVar*) );
#ifdef _DEBUG
for (int i = 0; i < varCount; ++i)
{
Assert( ppVars[i] );
}
#endif
MaterialSystem()->Unlock( hMaterialLock );
return pCurrentFallback;
}
//-----------------------------------------------------------------------------
// Gets the texturemap size
//-----------------------------------------------------------------------------
void CMaterial::PrecacheMappingDimensions( )
{
// Cache mapping width and mapping height
if (!m_representativeTexture)
{
#ifdef PARANOID
Warning( "No representative texture on material: \"%s\"\n", GetName() );
#endif
m_MappingWidth = 64;
m_MappingHeight = 64;
}
else
{
m_MappingWidth = m_representativeTexture->GetMappingWidth();
m_MappingHeight = m_representativeTexture->GetMappingHeight();
}
}
//-----------------------------------------------------------------------------
// Initialize the state snapshot
//-----------------------------------------------------------------------------
bool CMaterial::InitializeStateSnapshots()
{
if (IsPrecached())
{
if ( MaterialSystem()->GetCurrentMaterial() == this)
{
g_pShaderAPI->FlushBufferedPrimitives();
}
// Default state
CleanUpStateSnapshots();
if ( m_pShader && !ShaderSystem()->InitRenderState( m_pShader, m_VarCount, m_pShaderParams, &m_ShaderRenderState, GetName() ))
{
m_Flags &= ~MATERIAL_VALID_RENDERSTATE;
return false;
}
m_Flags |= MATERIAL_VALID_RENDERSTATE;
}
return true;
}
void CMaterial::CleanUpStateSnapshots()
{
if (IsValidRenderState())
{
ShaderSystem()->CleanupRenderState(&m_ShaderRenderState);
// -- THIS CANNOT BE HERE: m_Flags &= ~MATERIAL_VALID_RENDERSTATE;
// -- because it will cause a crash when main thread asks for material
// -- sort group it can temporarily see material in invalid render state
// -- and crash in DecalSurfaceAdd(msurface2_t*, int)
}
}
//-----------------------------------------------------------------------------
// This sets up a debugging/error shader...
//-----------------------------------------------------------------------------
void CMaterial::SetupErrorShader()
{
// Preserve the model flags
int flags = 0;
if ( m_pShaderParams && m_pShaderParams[FLAGS] )
{
flags = (m_pShaderParams[FLAGS]->GetIntValue() & MATERIAL_VAR_MODEL);
}
CleanUpShaderParams();
CleanUpMaterialProxy();
// We had a failure; replace it with a valid shader...
m_pShader = ShaderSystem()->FindShader( MissingShaderName() );
Assert( m_pShader );
// Create undefined vars for all the actual material vars
m_VarCount = m_pShader->GetNumParams();
m_pShaderParams = (IMaterialVar**)malloc( m_VarCount * sizeof(IMaterialVar*) );
for (int i = 0; i < m_VarCount; ++i)
{
m_pShaderParams[i] = IMaterialVar::Create( this, m_pShader->GetParamName(i) );
}
// Store the model flags
SetMaterialVarFlags( flags, true );
// Set the default values
ShaderSystem()->InitShaderParameters( m_pShader, m_pShaderParams, "Error" );
// Invokes the SHADER_INIT block in the various shaders,
ShaderSystem()->InitShaderInstance( m_pShader, m_pShaderParams, "Error", GetTextureGroupName() );
#ifdef DBGFLAG_ASSERT
bool ok =
#endif
InitializeStateSnapshots();
m_QueueFriendlyVersion.UpdateToRealTime();
Assert(ok);
}
//-----------------------------------------------------------------------------
// This computes the state snapshots for this material
//-----------------------------------------------------------------------------
void CMaterial::RecomputeStateSnapshots()
{
CMatCallQueue *pCallQueue = MaterialSystem()->GetRenderCallQueue();
if ( pCallQueue )
{
pCallQueue->QueueCall( this, &CMaterial::RecomputeStateSnapshots );
return;
}
bool ok = InitializeStateSnapshots();
// compute the state snapshots
if (!ok)
{
SetupErrorShader();
}
}
//-----------------------------------------------------------------------------
// Are we valid
//-----------------------------------------------------------------------------
inline bool CMaterial::IsValidRenderState() const
{
return (m_Flags & MATERIAL_VALID_RENDERSTATE) != 0;
}
//-----------------------------------------------------------------------------
// Gets/sets material var flags
//-----------------------------------------------------------------------------
inline int CMaterial::GetMaterialVarFlags() const
{
if ( m_pShaderParams && m_pShaderParams[FLAGS] )
{
return m_pShaderParams[FLAGS]->GetIntValueFast();
}
else
{
return 0;
}
}
inline void CMaterial::SetMaterialVarFlags( int flags, bool on )
{
if ( !m_pShaderParams )
{
Assert( 0 ); // are we hanging onto a material that has been cleaned up or isn't ready?
return;
}
if (on)
{
m_pShaderParams[FLAGS]->SetIntValue( GetMaterialVarFlags() | flags );
}
else
{
m_pShaderParams[FLAGS]->SetIntValue( GetMaterialVarFlags() & (~flags) );
}
// Mark it as being defined...
m_pShaderParams[FLAGS_DEFINED]->SetIntValue( m_pShaderParams[FLAGS_DEFINED]->GetIntValueFast() | flags );
}
inline int CMaterial::GetMaterialVarFlags2() const
{
if ( m_pShaderParams && m_VarCount > FLAGS2 && m_pShaderParams[FLAGS2] )
{
return m_pShaderParams[FLAGS2]->GetIntValueFast();
}
else
{
return 0;
}
}
inline void CMaterial::SetMaterialVarFlags2( int flags, bool on )
{
if ( m_pShaderParams && m_VarCount > FLAGS2 && m_pShaderParams[FLAGS2] )
{
if (on)
m_pShaderParams[FLAGS2]->SetIntValue( GetMaterialVarFlags2() | flags );
else
m_pShaderParams[FLAGS2]->SetIntValue( GetMaterialVarFlags2() & (~flags) );
}
if ( m_pShaderParams && m_VarCount > FLAGS_DEFINED2 && m_pShaderParams[FLAGS_DEFINED2] )
{
// Mark it as being defined...
m_pShaderParams[FLAGS_DEFINED2]->SetIntValue(
m_pShaderParams[FLAGS_DEFINED2]->GetIntValueFast() | flags );
}
}
//-----------------------------------------------------------------------------
// Gets the morph format
//-----------------------------------------------------------------------------
MorphFormat_t CMaterial::GetMorphFormat() const
{
const_cast<CMaterial*>(this)->Precache();
Assert( IsValidRenderState() );
return m_ShaderRenderState.m_MorphFormat;
}
//-----------------------------------------------------------------------------
// Gets the vertex format
//-----------------------------------------------------------------------------
VertexFormat_t CMaterial::GetVertexFormat() const
{
Assert( IsValidRenderState() );
return m_ShaderRenderState.m_VertexFormat;
}
VertexFormat_t CMaterial::GetVertexUsage() const
{
Assert( IsValidRenderState() );
return m_ShaderRenderState.m_VertexUsage;
}
bool CMaterial::PerformDebugTrace() const
{
return IsValidRenderState() && ((GetMaterialVarFlags() & MATERIAL_VAR_DEBUG ) != 0);
}
//-----------------------------------------------------------------------------
// Are we suppressed?
//-----------------------------------------------------------------------------
bool CMaterial::IsSuppressed() const
{
if ( !IsValidRenderState() )
return true;
return ((GetMaterialVarFlags() & MATERIAL_VAR_NO_DRAW) != 0);
}
void CMaterial::ToggleSuppression()
{
if (IsValidRenderState())
{
if ((GetMaterialVarFlags() & MATERIAL_VAR_NO_DEBUG_OVERRIDE) != 0)
return;
SetMaterialVarFlags( MATERIAL_VAR_NO_DRAW,
(GetMaterialVarFlags() & MATERIAL_VAR_NO_DRAW) == 0 );
}
}
void CMaterial::ToggleDebugTrace()
{
if (IsValidRenderState())
{
SetMaterialVarFlags( MATERIAL_VAR_DEBUG,
(GetMaterialVarFlags() & MATERIAL_VAR_DEBUG) == 0 );
}
}
//-----------------------------------------------------------------------------
// Can we override this material in debug?
//-----------------------------------------------------------------------------
bool CMaterial::NoDebugOverride() const
{
return IsValidRenderState() && (GetMaterialVarFlags() & MATERIAL_VAR_NO_DEBUG_OVERRIDE) != 0;
}
//-----------------------------------------------------------------------------
// Material Var flags
//-----------------------------------------------------------------------------
void CMaterial::SetMaterialVarFlag( MaterialVarFlags_t flag, bool on )
{
CMatCallQueue *pCallQueue = MaterialSystem()->GetRenderCallQueue();
if ( pCallQueue )
{
pCallQueue->QueueCall( this, &CMaterial::SetMaterialVarFlag, flag, on );
return;
}
bool oldOn = (GetMaterialVarFlags( ) & flag) != 0;
if (oldOn != on)
{
SetMaterialVarFlags( flag, on );
// This is going to be called from client code; recompute snapshots!
RecomputeStateSnapshots();
}
}
bool CMaterial::GetMaterialVarFlag( MaterialVarFlags_t flag ) const
{
return (GetMaterialVarFlags() & flag) != 0;
}
//-----------------------------------------------------------------------------
// Do we use the env_cubemap entity to get cubemaps from the level?
//-----------------------------------------------------------------------------
bool CMaterial::UsesEnvCubemap( void )
{
Precache();
if( !m_pShader )
{
if ( !HushAsserts() )
{
AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
}
return false;
}
Assert( m_pShaderParams );
return IsFlag2Set( m_pShaderParams, MATERIAL_VAR2_USES_ENV_CUBEMAP );
}
//-----------------------------------------------------------------------------
// Do we need a tangent space at the vertex level?
//-----------------------------------------------------------------------------
bool CMaterial::NeedsTangentSpace( void )
{
Precache();
if( !m_pShader )
{
if ( !HushAsserts() )
{
AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
}
return false;
}
Assert( m_pShaderParams );
return IsFlag2Set( m_pShaderParams, MATERIAL_VAR2_NEEDS_TANGENT_SPACES );
}
bool CMaterial::NeedsPowerOfTwoFrameBufferTexture( bool bCheckSpecificToThisFrame )
{
PrecacheVars();
if( !m_pShader )
{
if ( !HushAsserts() )
{
AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
}
return false;
}
Assert( m_pShaderParams );
return m_pShader->NeedsPowerOfTwoFrameBufferTexture( m_pShaderParams, bCheckSpecificToThisFrame );
}
bool CMaterial::NeedsFullFrameBufferTexture( bool bCheckSpecificToThisFrame )
{
PrecacheVars();
if( !m_pShader )
{
if ( !HushAsserts() )
{
AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
}
return false;
}
Assert( m_pShaderParams );
return m_pShader->NeedsFullFrameBufferTexture( m_pShaderParams, bCheckSpecificToThisFrame );
}
// GR - Is lightmap alpha needed?
bool CMaterial::NeedsLightmapBlendAlpha( void )
{
Precache();
return (GetMaterialVarFlags2() & MATERIAL_VAR2_BLEND_WITH_LIGHTMAP_ALPHA ) != 0;
}
//-----------------------------------------------------------------------------
// Do we need software skinning?
//-----------------------------------------------------------------------------
bool CMaterial::NeedsSoftwareSkinning( void )
{
Precache();
Assert( m_pShader );
if( !m_pShader )
{
return false;
}
Assert( m_pShaderParams );
return IsFlagSet( m_pShaderParams, MATERIAL_VAR_NEEDS_SOFTWARE_SKINNING );
}
//-----------------------------------------------------------------------------
// Do we need software lighting?
//-----------------------------------------------------------------------------
bool CMaterial::NeedsSoftwareLighting( void )
{
Precache();
Assert( m_pShader );
if( !m_pShader )
{
return false;
}
Assert( m_pShaderParams );
return IsFlag2Set( m_pShaderParams, MATERIAL_VAR2_NEEDS_SOFTWARE_LIGHTING );
}
//-----------------------------------------------------------------------------
// Alpha/color modulation
//-----------------------------------------------------------------------------
void CMaterial::AlphaModulate( float alpha )
{
Precache();
if ( m_VarCount > ALPHA )
m_pShaderParams[ALPHA]->SetFloatValue(alpha);
}
void CMaterial::ColorModulate( float r, float g, float b )
{
Precache();
if ( m_VarCount > COLOR )
m_pShaderParams[COLOR]->SetVecValue( r, g, b );
}
float CMaterial::GetAlphaModulation()
{
Precache();
if ( m_VarCount > ALPHA )
return m_pShaderParams[ALPHA]->GetFloatValue();
return 0.0f;
}
void CMaterial::GetColorModulation( float *r, float *g, float *b )
{
Precache();
float pColor[3] = { 0.0f, 0.0f, 0.0f };
if ( m_VarCount > COLOR )
m_pShaderParams[COLOR]->GetVecValue( pColor, 3 );
*r = pColor[0];
*g = pColor[1];
*b = pColor[2];
}
//-----------------------------------------------------------------------------
// Do we use fog?
//-----------------------------------------------------------------------------
bool CMaterial::UseFog() const
{
Assert( m_VarCount > 0 );
return IsValidRenderState() && ((GetMaterialVarFlags() & MATERIAL_VAR_NOFOG) == 0);
}
//-----------------------------------------------------------------------------
// diffuse bump?
//-----------------------------------------------------------------------------
bool CMaterial::IsUsingDiffuseBumpedLighting() const
{
return (GetMaterialVarFlags2() & MATERIAL_VAR2_LIGHTING_BUMPED_LIGHTMAP ) != 0;
}
//-----------------------------------------------------------------------------
// lightmap?
//-----------------------------------------------------------------------------
bool CMaterial::IsUsingLightmap() const
{
return (GetMaterialVarFlags2() & MATERIAL_VAR2_LIGHTING_LIGHTMAP ) != 0;
}
bool CMaterial::IsManuallyCreated() const
{
return (m_Flags & MATERIAL_IS_MANUALLY_CREATED) != 0;
}
bool CMaterial::UsesUNCFileName() const
{
return (m_Flags & MATERIAL_USES_UNC_FILENAME) != 0;
}
void CMaterial::DecideShouldReloadFromWhitelist( IFileList *pFilesToReload )
{
m_bShouldReloadFromWhitelist = false;
if ( IsManuallyCreated() || !IsPrecached() )
return;
// Materials loaded with an absolute pathname are usually debug materials.
if ( V_IsAbsolutePath( GetName() ) )
return;
char vmtFilename[MAX_PATH];
V_ComposeFileName( "materials", GetName(), vmtFilename, sizeof( vmtFilename ) );
V_strncat( vmtFilename, ".vmt", sizeof( vmtFilename ) );
// Check if either this file or any of the files it included need to be reloaded.
bool bShouldReload = pFilesToReload->IsFileInList( vmtFilename );
if ( !bShouldReload )
{
for ( int i=0; i < m_VMTIncludes.Count(); i++ )
{
g_pFullFileSystem->String( m_VMTIncludes[i], vmtFilename, sizeof( vmtFilename ) );
if ( pFilesToReload->IsFileInList( vmtFilename ) )
{
bShouldReload = true;
break;
}
}
}
m_bShouldReloadFromWhitelist = bShouldReload;
}
void CMaterial::ReloadFromWhitelistIfMarked()
{
if ( !m_bShouldReloadFromWhitelist )
return;
#ifdef PURE_SERVER_DEBUG_SPEW
{
char vmtFilename[MAX_PATH];
V_ComposeFileName( "materials", GetName(), vmtFilename, sizeof( vmtFilename ) );
V_strncat( vmtFilename, ".vmt", sizeof( vmtFilename ) );
Msg( "Reloading %s due to pure server whitelist change\n", GetName() );
}
#endif
Uncache();
Precache();
if ( !GetShader() )
{
// We can get in here if we previously loaded this material off disk and now the whitelist
// says to get it out of Steam but it's not in Steam. So just setup a wireframe thingy
// to draw the material with.
m_Flags |= MATERIAL_IS_PRECACHED | MATERIAL_VARS_IS_PRECACHED;
#if DEBUG
if (IsOSX())
{
printf("\n ##### CMaterial::ReloadFromWhitelistIfMarked: GetShader failed on %s, calling SetupErrorShader", m_pDebugName );
}
#endif
SetupErrorShader();
}
}
bool CMaterial::WasReloadedFromWhitelist()
{
return m_bShouldReloadFromWhitelist;
}
//-----------------------------------------------------------------------------
// Loads the material vars
//-----------------------------------------------------------------------------
bool CMaterial::PrecacheVars( KeyValues *pVMTKeyValues, KeyValues *pPatchKeyValues, CUtlVector<FileNameHandle_t> *pIncludes, int nFindContext )
{
// We should get both parameters or neither
Assert( ( pVMTKeyValues == NULL ) ? ( pPatchKeyValues == NULL ) : ( pPatchKeyValues != NULL ) );
// Don't bother if we're already precached
if( IsPrecachedVars() )
return true;
if ( pIncludes )
m_VMTIncludes = *pIncludes;
else
m_VMTIncludes.Purge();
MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
bool bOk = false;
bool bError = false;
KeyValues *vmtKeyValues = NULL;
KeyValues *patchKeyValues = NULL;
if ( m_pVMTKeyValues )
{
// Use the procedural KeyValues
vmtKeyValues = m_pVMTKeyValues;
patchKeyValues = new KeyValues( "vmt_patches" );
// The caller should not be passing in KeyValues if we have procedural ones
Assert( ( pVMTKeyValues == NULL ) && ( pPatchKeyValues == NULL ) );
}
else if ( pVMTKeyValues )
{
// Use the passed-in (already-loaded) KeyValues
vmtKeyValues = pVMTKeyValues;
patchKeyValues = pPatchKeyValues;
}
else
{
m_VMTIncludes.Purge();
// load data from the vmt file
vmtKeyValues = new KeyValues( "vmt" );
patchKeyValues = new KeyValues( "vmt_patches" );
if( !LoadVMTFile( *vmtKeyValues, *patchKeyValues, GetName(), UsesUNCFileName(), &m_VMTIncludes ) )
{
Warning( "CMaterial::PrecacheVars: error loading vmt file for %s\n", GetName() );
bError = true;
}
}
if ( ! bError )
{
// Needed to prevent re-entrancy
m_Flags |= MATERIAL_VARS_IS_PRECACHED;
// Create shader and the material vars...
KeyValues *pFallbackKeyValues = InitializeShader( *vmtKeyValues, *patchKeyValues, nFindContext );
if ( pFallbackKeyValues )
{
// Gotta initialize the proxies too, using the fallback proxies
InitializeMaterialProxy(pFallbackKeyValues);
bOk = true;
}
}
// Clean up
if ( ( vmtKeyValues != m_pVMTKeyValues ) && ( vmtKeyValues != pVMTKeyValues ) )
{
vmtKeyValues->deleteThis();
}
if ( patchKeyValues != pPatchKeyValues )
{
patchKeyValues->deleteThis();
}
MaterialSystem()->Unlock( hMaterialLock );
return bOk;
}
//-----------------------------------------------------------------------------
// Loads the material info from the VMT file
//-----------------------------------------------------------------------------
void CMaterial::Precache()
{
// Don't bother if we're already precached
if ( IsPrecached() )
return;
// load data from the vmt file
if ( !PrecacheVars() )
return;
MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
m_Flags |= MATERIAL_IS_PRECACHED;
// Invokes the SHADER_INIT block in the various shaders,
if ( m_pShader )
{
ShaderSystem()->InitShaderInstance( m_pShader, m_pShaderParams, GetName(), GetTextureGroupName() );
}
// compute the state snapshots
RecomputeStateSnapshots();
FindRepresentativeTexture();
// Reads in the texture width and height from the material var
PrecacheMappingDimensions();
Assert( IsValidRenderState() );
if( m_pShaderParams )
m_QueueFriendlyVersion.UpdateToRealTime();
MaterialSystem()->Unlock( hMaterialLock );
}
//-----------------------------------------------------------------------------
// Unloads the material data from memory
//-----------------------------------------------------------------------------
void CMaterial::Uncache( bool bPreserveVars )
{
MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
// Don't bother if we're not cached
if ( IsPrecached() )
{
// Clean up the state snapshots
CleanUpStateSnapshots();
m_Flags &= ~MATERIAL_VALID_RENDERSTATE;
m_Flags &= ~MATERIAL_IS_PRECACHED;
}
if ( !bPreserveVars )
{
if ( IsPrecachedVars() )
{
// Clean up the shader + params
CleanUpShaderParams();
m_pShader = 0;
// Clean up the material proxy
CleanUpMaterialProxy();
m_Flags &= ~MATERIAL_VARS_IS_PRECACHED;
}
}
MaterialSystem()->Unlock( hMaterialLock );
// Whether we just now did it, or we were already unloaded,
// notify the pure system that the material is unloaded,
// so it doesn't caue us to fail sv_pure checks
if ( ( m_Flags & ( MATERIAL_VARS_IS_PRECACHED | MATERIAL_IS_MANUALLY_CREATED | MATERIAL_USES_UNC_FILENAME ) ) == 0 )
{
char szName[ MAX_PATH ];
V_sprintf_safe( szName, "materials/%s.vmt", GetName() );
g_pFullFileSystem->NotifyFileUnloaded( szName, "GAME" );
}
}
//-----------------------------------------------------------------------------
// reload all textures used by this materals
//-----------------------------------------------------------------------------
void CMaterial::ReloadTextures( void )
{
Precache();
int i;
int nParams = ShaderParamCount();
IMaterialVar **ppVars = GetShaderParams();
for( i = 0; i < nParams; i++ )
{
if( ppVars[i] )
{
if( ppVars[i]->IsTexture() )
{
ITextureInternal *pTexture = ( ITextureInternal * )ppVars[i]->GetTextureValue();
pTexture->Download();
}
}
}
}
//-----------------------------------------------------------------------------
// Meant to be used with materials created using CreateMaterial
// It updates the materials to reflect the current values stored in the material vars
//-----------------------------------------------------------------------------
void CMaterial::Refresh()
{
if ( g_pShaderDevice->IsUsingGraphics() )
{
Uncache();
Precache();
}
}
void CMaterial::RefreshPreservingMaterialVars()
{
if ( g_pShaderDevice->IsUsingGraphics() )
{
Uncache( true );
Precache();
}
}
//-----------------------------------------------------------------------------
// Gets the material name
//-----------------------------------------------------------------------------
char const* CMaterial::GetName() const
{
return m_Name.String();
}
char const* CMaterial::GetTextureGroupName() const
{
return m_TextureGroupName.String();
}
//-----------------------------------------------------------------------------
// Material dimensions
//-----------------------------------------------------------------------------
int CMaterial::GetMappingWidth( )
{
Precache();
return m_MappingWidth;
}
int CMaterial::GetMappingHeight( )
{
Precache();
return m_MappingHeight;
}
//-----------------------------------------------------------------------------
// Animated material info
//-----------------------------------------------------------------------------
int CMaterial::GetNumAnimationFrames( )
{
Precache();
if( m_representativeTexture )
{
return m_representativeTexture->GetNumAnimationFrames();
}
else
{
#ifndef POSIX
Warning( "CMaterial::GetNumAnimationFrames:\nno representative texture for material %s\n", GetName() );
#endif
return 1;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterial::GetMaterialOffset( float *pOffset )
{
// Identity.
pOffset[0] = 0.0f;
pOffset[1] = 0.0f;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterial::GetMaterialScale( float *pScale )
{
// Identity.
pScale[0] = 1.0f;
pScale[1] = 1.0f;
}
//-----------------------------------------------------------------------------
// Reference count
//-----------------------------------------------------------------------------
void CMaterial::IncrementReferenceCount( )
{
++m_RefCount;
}
void CMaterial::DecrementReferenceCount( )
{
--m_RefCount;
}
int CMaterial::GetReferenceCount( ) const
{
return m_RefCount;
}
//-----------------------------------------------------------------------------
// Sets the shader associated with the material
//-----------------------------------------------------------------------------
void CMaterial::SetShader( const char *pShaderName )
{
Assert( pShaderName );
int i;
IShader* pShader;
IMaterialVar* ppVars[256];
int iVarCount = 0;
// Clean up existing state
Uncache();
// Keep going until there's no more fallbacks...
while( true )
{
// Find the shader for this material. Note that this may not be
// the actual shader we use due to fallbacks...
pShader = ShaderSystem()->FindShader( pShaderName );
if (!pShader)
{
// Couldn't find the shader we wanted to use; it's not defined...
Warning( "SetShader: Couldn't find shader %s for material %s!\n", pShaderName, GetName() );
pShaderName = MissingShaderName();
pShader = ShaderSystem()->FindShader( pShaderName );
Assert( pShader );
}
// Create undefined vars for all the actual material vars
iVarCount = pShader->GetNumParams();
for (i = 0; i < iVarCount; ++i)
{
ppVars[i] = IMaterialVar::Create( this, pShader->GetParamName(i) );
}
// Make sure we set default values before the fallback is looked for
ShaderSystem()->InitShaderParameters( pShader, ppVars, pShaderName );
// Now that the material vars are parsed, see if there's a fallback
// But only if we're not in the tools
if (!g_pShaderDevice->IsUsingGraphics())
break;
// Check for a fallback; if not, we're done
pShaderName = pShader->GetFallbackShader( ppVars );
if (!pShaderName)
break;
// Now, blow away all of the material vars + try again...
for (i = 0; i < iVarCount; ++i)
{
Assert( ppVars[i] );
IMaterialVar::Destroy( ppVars[i] );
}
}
// Store off the shader
m_pShader = pShader;
// Store off the material vars + flags
m_VarCount = iVarCount;
m_pShaderParams = (IMaterialVar**)malloc( iVarCount * sizeof(IMaterialVar*) );
memcpy( m_pShaderParams, ppVars, iVarCount * sizeof(IMaterialVar*) );
// Invokes the SHADER_INIT block in the various shaders,
ShaderSystem()->InitShaderInstance( m_pShader, m_pShaderParams, GetName(), GetTextureGroupName() );
// Precache our initial state...
// NOTE: What happens here for textures???
// Pretend that we precached our material vars; we certainly don't have any!
m_Flags |= MATERIAL_VARS_IS_PRECACHED;
// NOTE: The caller has to call 'Refresh' for the shader to be ready...
}
const char *CMaterial::GetShaderName() const
{
const_cast< CMaterial* >( this )->PrecacheVars();
return m_pShader ? m_pShader->GetName() : "shader_error";
}
//-----------------------------------------------------------------------------
// Enumeration ID
//-----------------------------------------------------------------------------
int CMaterial::GetEnumerationID( ) const
{
return m_iEnumerationID;
}
void CMaterial::SetEnumerationID( int id )
{
m_iEnumerationID = id;
}
//-----------------------------------------------------------------------------
// Preview image
//-----------------------------------------------------------------------------
char const* CMaterial::GetPreviewImageName( void )
{
if ( IsConsole() )
{
// not supporting
return NULL;
}
PrecacheVars();
bool found;
IMaterialVar *pRepresentativeTextureVar;
FindVar( "%noToolTexture", &found, false );
if (found)
return NULL;
pRepresentativeTextureVar = FindVar( "%toolTexture", &found, false );
if( found )
{
if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_STRING )
return pRepresentativeTextureVar->GetStringValue();
if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
return pRepresentativeTextureVar->GetTextureValue()->GetName();
}
pRepresentativeTextureVar = FindVar( "$baseTexture", &found, false );
if( found )
{
if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_STRING )
return pRepresentativeTextureVar->GetStringValue();
if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
return pRepresentativeTextureVar->GetTextureValue()->GetName();
}
return GetName();
}
char const* CMaterial::GetPreviewImageFileName( void ) const
{
char const* pName = const_cast<CMaterial*>(this)->GetPreviewImageName();
if( !pName )
return NULL;
static char vtfFilename[MATERIAL_MAX_PATH];
if( Q_strlen( pName ) >= MATERIAL_MAX_PATH - 5 )
{
Warning( "MATERIAL_MAX_PATH to short for %s.vtf\n", pName );
return NULL;
}
if ( !UsesUNCFileName() )
{
Q_snprintf( vtfFilename, sizeof( vtfFilename ), "materials/%s.vtf", pName );
}
else
{
Q_snprintf( vtfFilename, sizeof( vtfFilename ), "%s.vtf", pName );
}
return vtfFilename;
}
PreviewImageRetVal_t CMaterial::GetPreviewImageProperties( int *width, int *height,
ImageFormat *imageFormat, bool* isTranslucent ) const
{
char const* pFileName = GetPreviewImageFileName();
if ( IsX360() || !pFileName )
{
*width = *height = 0;
*imageFormat = IMAGE_FORMAT_RGBA8888;
*isTranslucent = false;
return MATERIAL_NO_PREVIEW_IMAGE;
}
int nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION );
unsigned char *pMem = (unsigned char *)stackalloc( nHeaderSize );
CUtlBuffer buf( pMem, nHeaderSize );
if( !g_pFullFileSystem->ReadFile( pFileName, NULL, buf, nHeaderSize ) )
{
Warning( "\"%s\" - \"%s\": cached version doesn't exist\n", GetName(), pFileName );
return MATERIAL_PREVIEW_IMAGE_BAD;
}
IVTFTexture *pVTFTexture = CreateVTFTexture();
if (!pVTFTexture->Unserialize( buf, true ))
{
Warning( "Error reading material \"%s\"\n", pFileName );
DestroyVTFTexture( pVTFTexture );
return MATERIAL_PREVIEW_IMAGE_BAD;
}
*width = pVTFTexture->Width();
*height = pVTFTexture->Height();
*imageFormat = pVTFTexture->Format();
*isTranslucent = (pVTFTexture->Flags() & (TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA)) != 0;
DestroyVTFTexture( pVTFTexture );
return MATERIAL_PREVIEW_IMAGE_OK;
}
PreviewImageRetVal_t CMaterial::GetPreviewImage( unsigned char *pData, int width, int height,
ImageFormat imageFormat ) const
{
CUtlBuffer buf;
int nHeaderSize;
int nImageOffset, nImageSize;
char const* pFileName = GetPreviewImageFileName();
if ( IsX360() || !pFileName )
{
return MATERIAL_NO_PREVIEW_IMAGE;
}
IVTFTexture *pVTFTexture = CreateVTFTexture();
FileHandle_t fileHandle = g_pFullFileSystem->Open( pFileName, "rb" );
if( !fileHandle )
{
Warning( "\"%s\": cached version doesn't exist\n", pFileName );
goto fail;
}
nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION );
buf.EnsureCapacity( nHeaderSize );
// read the header first.. it's faster!!
int nBytesRead; // GCC won't let this be initialized right away
nBytesRead = g_pFullFileSystem->Read( buf.Base(), nHeaderSize, fileHandle );
buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );
// Unserialize the header
if (!pVTFTexture->Unserialize( buf, true ))
{
Warning( "Error reading material \"%s\"\n", pFileName );
goto fail;
}
// FIXME: Make sure the preview image size requested is the same
// size as mip level 0 of the texture
Assert( (width == pVTFTexture->Width()) && (height == pVTFTexture->Height()) );
// Determine where in the file to start reading (frame 0, face 0, mip 0)
pVTFTexture->ImageFileInfo( 0, 0, 0, &nImageOffset, &nImageSize );
if ( nImageSize == 0 )
{
Warning( "Couldn't determine offset and size of material \"%s\"\n", pFileName );
goto fail;
}
// Prep the utlbuffer for reading
buf.EnsureCapacity( nImageSize );
buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
// Read in the bits at the specified location
g_pFullFileSystem->Seek( fileHandle, nImageOffset, FILESYSTEM_SEEK_HEAD );
g_pFullFileSystem->Read( buf.Base(), nImageSize, fileHandle );
g_pFullFileSystem->Close( fileHandle );
// Convert from the format read in to the requested format
ImageLoader::ConvertImageFormat( (unsigned char*)buf.Base(), pVTFTexture->Format(),
pData, imageFormat, width, height );
DestroyVTFTexture( pVTFTexture );
return MATERIAL_PREVIEW_IMAGE_OK;
fail:
if( fileHandle )
{
g_pFullFileSystem->Close( fileHandle );
}
int nSize = ImageLoader::GetMemRequired( width, height, 1, imageFormat, false );
memset( pData, 0xff, nSize );
DestroyVTFTexture( pVTFTexture );
return MATERIAL_PREVIEW_IMAGE_BAD;
}
//-----------------------------------------------------------------------------
// Material variables
//-----------------------------------------------------------------------------
IMaterialVar *CMaterial::FindVar( char const *pVarName, bool *pFound, bool complain )
{
PrecacheVars();
// FIXME: Could look for flags here too...
MaterialVarSym_t sym = IMaterialVar::FindSymbol(pVarName);
if ( sym != UTL_INVAL_SYMBOL )
{
for (int i = m_VarCount; --i >= 0; )
{
if (m_pShaderParams[i]->GetNameAsSymbol() == sym)
{
if( pFound )
*pFound = true;
return m_pShaderParams[i];
}
}
}
if( pFound )
*pFound = false;
if( complain )
{
static int complainCount = 0;
if( complainCount < 100 )
{
Warning( "No such variable \"%s\" for material \"%s\"\n", pVarName, GetName() );
complainCount++;
}
}
return GetDummyVariable();
}
struct tokencache_t
{
unsigned short symbol;
unsigned char varIndex;
unsigned char cached;
};
IMaterialVar *CMaterial::FindVarFast( char const *pVarName, unsigned int *pCacheData )
{
tokencache_t *pToken = reinterpret_cast<tokencache_t *>(pCacheData);
PrecacheVars();
if ( pToken->cached )
{
if ( pToken->varIndex < m_VarCount && m_pShaderParams[pToken->varIndex]->GetNameAsSymbol() == pToken->symbol )
return m_pShaderParams[pToken->varIndex];
// FIXME: Could look for flags here too...
if ( !IMaterialVar::SymbolMatches(pVarName, pToken->symbol) )
{
pToken->symbol = IMaterialVar::FindSymbol(pVarName);
}
}
else
{
pToken->cached = true;
pToken->symbol = IMaterialVar::FindSymbol(pVarName);
}
if ( pToken->symbol != UTL_INVAL_SYMBOL )
{
for (int i = m_VarCount; --i >= 0; )
{
if (m_pShaderParams[i]->GetNameAsSymbol() == pToken->symbol)
{
pToken->varIndex = i;
return m_pShaderParams[i];
}
}
}
return NULL;
}
//-----------------------------------------------------------------------------
// Lovely material properties
//-----------------------------------------------------------------------------
void CMaterial::GetReflectivity( Vector& reflect )
{
Precache();
reflect = m_Reflectivity;
}
bool CMaterial::GetPropertyFlag( MaterialPropertyTypes_t type )
{
Precache();
if (!IsValidRenderState())
return false;
switch( type )
{
case MATERIAL_PROPERTY_NEEDS_LIGHTMAP:
return IsUsingLightmap();
case MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS:
return IsUsingDiffuseBumpedLighting();
}
return false;
}
//-----------------------------------------------------------------------------
// Is the material visible from both sides?
//-----------------------------------------------------------------------------
bool CMaterial::IsTwoSided()
{
PrecacheVars();
return GetMaterialVarFlag(MATERIAL_VAR_NOCULL);
}
//-----------------------------------------------------------------------------
// Are we translucent?
//-----------------------------------------------------------------------------
bool CMaterial::IsTranslucent()
{
Precache();
if ( m_VarCount > ALPHA )
return IsTranslucentInternal( m_pShaderParams? m_pShaderParams[ALPHA]->GetFloatValue() : 0.0 );
return false;
}
bool CMaterial::IsTranslucentInternal( float fAlphaModulation ) const
{
if (m_pShader && IsValidRenderState())
{
// I have to check for alpha modulation here because it isn't
// factored into the shader's notion of whether or not it's transparent
return ::IsTranslucent(&m_ShaderRenderState) ||
(fAlphaModulation < 1.0f) ||
m_pShader->IsTranslucent( m_pShaderParams );
}
return false;
}
//-----------------------------------------------------------------------------
// Are we alphatested?
//-----------------------------------------------------------------------------
bool CMaterial::IsAlphaTested()
{
Precache();
if (m_pShader && IsValidRenderState())
{
return ::IsAlphaTested(&m_ShaderRenderState) ||
GetMaterialVarFlag( MATERIAL_VAR_ALPHATEST );
}
return false;
}
//-----------------------------------------------------------------------------
// Are we vertex lit?
//-----------------------------------------------------------------------------
bool CMaterial::IsVertexLit()
{
Precache();
if (IsValidRenderState())
{
return ( GetMaterialVarFlags2() & MATERIAL_VAR2_LIGHTING_VERTEX_LIT ) != 0;
}
return false;
}
//-----------------------------------------------------------------------------
// Is the shader a sprite card shader?
//-----------------------------------------------------------------------------
bool CMaterial::IsSpriteCard()
{
Precache();
if (IsValidRenderState())
{
return ( GetMaterialVarFlags2() & MATERIAL_VAR2_IS_SPRITECARD ) != 0;
}
return false;
}
//-----------------------------------------------------------------------------
// Proxies
//-----------------------------------------------------------------------------
void CMaterial::CallBindProxy( void *proxyData )
{
CMatCallQueue *pCallQueue = MaterialSystem()->GetRenderCallQueue();
bool bIsThreaded = ( pCallQueue != NULL );
switch (g_config.proxiesTestMode)
{
case 0:
{
// Make sure we call the proxies in the order in which they show up
// in the .vmt file
if ( m_ProxyInfo.Count() )
{
if ( bIsThreaded )
{
EnableThreadedMaterialVarAccess( true, m_pShaderParams, m_VarCount );
}
for( int i = 0; i < m_ProxyInfo.Count(); ++i )
{
m_ProxyInfo[i]->OnBind( proxyData );
}
if ( bIsThreaded )
{
EnableThreadedMaterialVarAccess( false, m_pShaderParams, m_VarCount );
}
}
}
break;
case 2:
// alpha mod all....
{
float value = ( sin( 2.0f * M_PI * Plat_FloatTime() / 10.0f ) * 0.5f ) + 0.5f;
m_pShaderParams[ALPHA]->SetFloatValue( value );
}
break;
case 3:
// color mod all...
{
float value = ( sin( 2.0f * M_PI * Plat_FloatTime() / 10.0f ) * 0.5f ) + 0.5f;
m_pShaderParams[COLOR]->SetVecValue( value, 1.0f, 1.0f );
}
break;
}
}
IMaterial *CMaterial::CheckProxyReplacement( void *proxyData )
{
if ( m_pReplacementProxy != NULL )
{
IMaterial *pReplaceMaterial = m_pReplacementProxy->GetMaterial();
if ( pReplaceMaterial )
{
return pReplaceMaterial;
}
}
return this;
}
bool CMaterial::HasProxy( ) const
{
const_cast< CMaterial* >( this )->PrecacheVars();
return m_ProxyInfo.Count() > 0;
}
//-----------------------------------------------------------------------------
// Main draw method
//-----------------------------------------------------------------------------
#ifdef _WIN32
#pragma warning (disable: 4189)
#endif
void CMaterial::DrawMesh( VertexCompressionType_t vertexCompression )
{
if ( m_pShader )
{
#ifdef _DEBUG
if ( GetMaterialVarFlags() & MATERIAL_VAR_DEBUG )
{
// Putcher breakpoint here to catch the rendering of a material
// marked for debugging ($debug = 1 in a .vmt file) dynamic state version
int x = 0;
}
#endif
if ((GetMaterialVarFlags() & MATERIAL_VAR_NO_DRAW) == 0)
{
const char *pName = m_pShader->GetName();
ShaderSystem()->DrawElements( m_pShader, m_pShaderParams, &m_ShaderRenderState, vertexCompression, m_ChangeID ^ g_nDebugVarsSignature );
}
}
else
{
Warning( "CMaterial::DrawElements: No bound shader\n" );
}
}
#ifdef _WIN32
#pragma warning (default: 4189)
#endif
IShader *CMaterial::GetShader( ) const
{
return m_pShader;
}
IMaterialVar *CMaterial::GetShaderParam( int id )
{
return m_pShaderParams[id];
}
//-----------------------------------------------------------------------------
// Adds a material variable to the material
//-----------------------------------------------------------------------------
void CMaterial::AddMaterialVar( IMaterialVar *pMaterialVar )
{
++m_VarCount;
m_pShaderParams = (IMaterialVar**)realloc( m_pShaderParams, m_VarCount * sizeof( IMaterialVar*) );
m_pShaderParams[m_VarCount-1] = pMaterialVar;
}
bool CMaterial::IsErrorMaterial() const
{
extern IMaterialInternal *g_pErrorMaterial;
const IMaterialInternal *pThis = this;
return g_pErrorMaterial == pThis;
}
void CMaterial::FindRepresentativeTexture( void )
{
Precache();
// First try to find the base texture...
bool found;
IMaterialVar *textureVar = FindVar( "$baseTexture", &found, false );
if( found && textureVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
{
ITextureInternal *pTexture = ( ITextureInternal * )textureVar->GetTextureValue();
if( pTexture )
{
pTexture->GetReflectivity( m_Reflectivity );
}
}
if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
{
// Try the env map mask if the base texture doesn't work...
// this is needed for specular decals
textureVar = FindVar( "$envmapmask", &found, false );
if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
{
// Try the bumpmap
textureVar = FindVar( "$bumpmap", &found, false );
if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
{
textureVar = FindVar( "$dudvmap", &found, false );
if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
{
textureVar = FindVar( "$normalmap", &found, false );
if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
{
// Warning( "Can't find representative texture for material \"%s\"\n", GetName() );
m_representativeTexture = TextureManager()->ErrorTexture();
return;
}
}
}
}
}
m_representativeTexture = static_cast<ITextureInternal *>( textureVar->GetTextureValue() );
if (m_representativeTexture)
{
m_representativeTexture->Precache();
}
else
{
m_representativeTexture = TextureManager()->ErrorTexture();
Assert( m_representativeTexture );
}
}
void CMaterial::GetLowResColorSample( float s, float t, float *color ) const
{
if( !m_representativeTexture )
{
return;
}
m_representativeTexture->GetLowResColorSample( s, t, color);
}
//-----------------------------------------------------------------------------
// Lightmap-related methods
//-----------------------------------------------------------------------------
void CMaterial::SetMinLightmapPageID( int pageID )
{
m_minLightmapPageID = pageID;
}
void CMaterial::SetMaxLightmapPageID( int pageID )
{
m_maxLightmapPageID = pageID;
}
int CMaterial::GetMinLightmapPageID( ) const
{
return m_minLightmapPageID;
}
int CMaterial::GetMaxLightmapPageID( ) const
{
return m_maxLightmapPageID;
}
void CMaterial::SetNeedsWhiteLightmap( bool val )
{
if ( val )
m_Flags |= MATERIAL_NEEDS_WHITE_LIGHTMAP;
else
m_Flags &= ~MATERIAL_NEEDS_WHITE_LIGHTMAP;
}
bool CMaterial::GetNeedsWhiteLightmap( ) const
{
return (m_Flags & MATERIAL_NEEDS_WHITE_LIGHTMAP) != 0;
}
void CMaterial::MarkAsPreloaded( bool bSet )
{
if ( bSet )
{
m_Flags |= MATERIAL_IS_PRELOADED;
}
else
{
m_Flags &= ~MATERIAL_IS_PRELOADED;
}
}
bool CMaterial::IsPreloaded() const
{
return ( m_Flags & MATERIAL_IS_PRELOADED ) != 0;
}
void CMaterial::ArtificialAddRef( void )
{
if ( m_Flags & MATERIAL_ARTIFICIAL_REFCOUNT )
{
// already done
return;
}
m_Flags |= MATERIAL_ARTIFICIAL_REFCOUNT;
m_RefCount++;
}
void CMaterial::ArtificialRelease( void )
{
if ( !( m_Flags & MATERIAL_ARTIFICIAL_REFCOUNT ) )
{
return;
}
m_Flags &= ~MATERIAL_ARTIFICIAL_REFCOUNT;
m_RefCount--;
}
//-----------------------------------------------------------------------------
// Return the shader params
//-----------------------------------------------------------------------------
IMaterialVar **CMaterial::GetShaderParams( void )
{
return m_pShaderParams;
}
int CMaterial::ShaderParamCount() const
{
return m_VarCount;
}
//-----------------------------------------------------------------------------
// VMT parser
//-----------------------------------------------------------------------------
void InsertKeyValues( KeyValues& dst, KeyValues& src, bool bCheckForExistence, bool bRecursive )
{
KeyValues *pSrcVar = src.GetFirstSubKey();
while( pSrcVar )
{
if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) )
{
switch( pSrcVar->GetDataType() )
{
case KeyValues::TYPE_STRING:
dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() );
break;
case KeyValues::TYPE_INT:
dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() );
break;
case KeyValues::TYPE_FLOAT:
dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() );
break;
case KeyValues::TYPE_PTR:
dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() );
break;
case KeyValues::TYPE_NONE:
{
// Subkey. Recurse.
KeyValues *pNewDest = dst.FindKey( pSrcVar->GetName(), true );
Assert( pNewDest );
InsertKeyValues( *pNewDest, *pSrcVar, bCheckForExistence, true );
}
break;
}
}
pSrcVar = pSrcVar->GetNextKey();
}
if ( bRecursive && !dst.GetFirstSubKey() )
{
// Insert a dummy key to an empty subkey to make sure it doesn't get removed
dst.SetInt( "__vmtpatchdummy", 1 );
}
if( bCheckForExistence )
{
for( KeyValues *pScan = dst.GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() )
{
KeyValues *pTmp = src.FindKey( pScan->GetName() );
if( !pTmp )
continue;
// make sure that this is a subkey.
if( pTmp->GetDataType() != KeyValues::TYPE_NONE )
continue;
InsertKeyValues( *pScan, *pTmp, bCheckForExistence );
}
}
}
void WriteKeyValuesToFile( const char *pFileName, KeyValues& keyValues )
{
keyValues.SaveToFile( g_pFullFileSystem, pFileName );
}
void ApplyPatchKeyValues( KeyValues &keyValues, KeyValues &patchKeyValues )
{
KeyValues *pInsertSection = patchKeyValues.FindKey( "insert" );
KeyValues *pReplaceSection = patchKeyValues.FindKey( "replace" );
if ( pInsertSection )
{
InsertKeyValues( keyValues, *pInsertSection, false );
}
if ( pReplaceSection )
{
InsertKeyValues( keyValues, *pReplaceSection, true );
}
// Could add other commands here, like "delete", "rename", etc.
}
//-----------------------------------------------------------------------------
// Adds keys from srcKeys to destKeys, overwriting any keys that are already
// there.
//-----------------------------------------------------------------------------
void MergeKeyValues( KeyValues &srcKeys, KeyValues &destKeys )
{
for( KeyValues *pKV = srcKeys.GetFirstValue(); pKV; pKV = pKV->GetNextValue() )
{
switch( pKV->GetDataType() )
{
case KeyValues::TYPE_STRING:
destKeys.SetString( pKV->GetName(), pKV->GetString() );
break;
case KeyValues::TYPE_INT:
destKeys.SetInt( pKV->GetName(), pKV->GetInt() );
break;
case KeyValues::TYPE_FLOAT:
destKeys.SetFloat( pKV->GetName(), pKV->GetFloat() );
break;
case KeyValues::TYPE_PTR:
destKeys.SetPtr( pKV->GetName(), pKV->GetPtr() );
break;
}
}
for( KeyValues *pKV = srcKeys.GetFirstTrueSubKey(); pKV; pKV = pKV->GetNextTrueSubKey() )
{
KeyValues *pDestKV = destKeys.FindKey( pKV->GetName(), true );
MergeKeyValues( *pKV, *pDestKV );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void AccumulatePatchKeyValues( KeyValues &srcKeyValues, KeyValues &patchKeyValues )
{
KeyValues *pDestInsertSection = patchKeyValues.FindKey( "insert" );
if ( pDestInsertSection == NULL )
{
pDestInsertSection = new KeyValues( "insert" );
patchKeyValues.AddSubKey( pDestInsertSection );
}
KeyValues *pDestReplaceSection = patchKeyValues.FindKey( "replace" );
if ( pDestReplaceSection == NULL )
{
pDestReplaceSection = new KeyValues( "replace" );
patchKeyValues.AddSubKey( pDestReplaceSection );
}
KeyValues *pSrcInsertSection = srcKeyValues.FindKey( "insert" );
if ( pSrcInsertSection )
{
MergeKeyValues( *pSrcInsertSection, *pDestInsertSection );
}
KeyValues *pSrcReplaceSection = srcKeyValues.FindKey( "replace" );
if ( pSrcReplaceSection )
{
MergeKeyValues( *pSrcReplaceSection, *pDestReplaceSection );
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool AccumulateRecursiveVmtPatches( KeyValues &patchKeyValuesOut, KeyValues **ppBaseKeyValuesOut, const KeyValues& keyValues, const char *pPathID, CUtlVector<FileNameHandle_t> *pIncludes )
{
if ( pIncludes )
{
pIncludes->Purge();
}
patchKeyValuesOut.Clear();
if ( V_stricmp( keyValues.GetName(), "patch" ) != 0 )
{
// Not a patch file, nothing to do
if ( ppBaseKeyValuesOut )
{
// flag to the caller that the passed in keyValues are in fact final non-patch values
*ppBaseKeyValuesOut = NULL;
}
return true;
}
KeyValues *pCurrentKeyValues = keyValues.MakeCopy();
// Recurse down through all patch files:
int nCount = 0;
while( ( nCount < 10 ) && ( V_stricmp( pCurrentKeyValues->GetName(), "patch" ) == 0 ) )
{
// Accumulate the new patch keys from this file
AccumulatePatchKeyValues( *pCurrentKeyValues, patchKeyValuesOut );
// Load the included file
const char *pIncludeFileName = pCurrentKeyValues->GetString( "include" );
if ( pIncludeFileName == NULL )
{
// A patch file without an include key? Not good...
Warning( "VMT patch file has no include key - invalid!\n" );
Assert( pIncludeFileName );
break;
}
CUtlString includeFileName( pIncludeFileName ); // copy off the string before we clear the keyvalues it lives in
pCurrentKeyValues->Clear();
bool bSuccess = pCurrentKeyValues->LoadFromFile( g_pFullFileSystem, includeFileName, pPathID );
if( bSuccess )
{
if ( pIncludes )
{
// Remember that we included this file for the pure server stuff.
pIncludes->AddToTail( g_pFullFileSystem->FindOrAddFileName( includeFileName ) );
}
}
else
{
pCurrentKeyValues->deleteThis();
#ifndef DEDICATED
Warning( "Failed to load $include VMT file (%s)\n", includeFileName.String() );
#endif
if ( !HushAsserts() )
{
AssertMsg( false, "Failed to load $include VMT file (%s)", includeFileName.String() );
}
return false;
}
nCount++;
}
if ( ppBaseKeyValuesOut )
{
*ppBaseKeyValuesOut = pCurrentKeyValues;
}
else
{
pCurrentKeyValues->deleteThis();
}
if( nCount >= 10 )
{
Warning( "Infinite recursion in patch file?\n" );
}
return true;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void ExpandPatchFile( KeyValues& keyValues, KeyValues &patchKeyValues, const char *pPathID, CUtlVector<FileNameHandle_t> *pIncludes )
{
KeyValues *pNonPatchKeyValues = NULL;
if ( !patchKeyValues.IsEmpty() )
{
pNonPatchKeyValues = keyValues.MakeCopy();
}
else
{
bool bSuccess = AccumulateRecursiveVmtPatches( patchKeyValues, &pNonPatchKeyValues, keyValues, pPathID, pIncludes );
if ( !bSuccess )
{
return;
}
}
if ( pNonPatchKeyValues != NULL )
{
// We're dealing with a patch file. Apply accumulated patches to final vmt
ApplyPatchKeyValues( *pNonPatchKeyValues, patchKeyValues );
keyValues = *pNonPatchKeyValues;
pNonPatchKeyValues->deleteThis();
}
}
bool LoadVMTFile( KeyValues &vmtKeyValues, KeyValues &patchKeyValues, const char *pMaterialName, bool bAbsolutePath, CUtlVector<FileNameHandle_t> *pIncludes )
{
char pFileName[MAX_PATH];
const char *pPathID = "GAME";
if ( !bAbsolutePath )
{
Q_snprintf( pFileName, sizeof( pFileName ), "materials/%s.vmt", pMaterialName );
}
else
{
Q_snprintf( pFileName, sizeof( pFileName ), "%s.vmt", pMaterialName );
if ( pMaterialName[0] == '/' && pMaterialName[1] == '/' && pMaterialName[2] != '/' )
{
// UNC, do full search
pPathID = NULL;
}
}
if ( !vmtKeyValues.LoadFromFile( g_pFullFileSystem, pFileName, pPathID ) )
{
return false;
}
ExpandPatchFile( vmtKeyValues, patchKeyValues, pPathID, pIncludes );
return true;
}
int CMaterial::GetNumPasses( void )
{
Precache();
// int mod = m_ShaderRenderState.m_Modulation;
int mod = 0;
return m_ShaderRenderState.m_pSnapshots[mod].m_nPassCount;
}
int CMaterial::GetTextureMemoryBytes( void )
{
Precache();
int bytes = 0;
int i;
for( i = 0; i < m_VarCount; i++ )
{
IMaterialVar *pVar = m_pShaderParams[i];
if( pVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
{
ITexture *pTexture = pVar->GetTextureValue();
if( pTexture && pTexture != ( ITexture * )0xffffffff )
{
bytes += pTexture->GetApproximateVidMemBytes();
}
}
}
return bytes;
}
void CMaterial::SetUseFixedFunctionBakedLighting( bool bEnable )
{
SetMaterialVarFlags2( MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING, bEnable );
}
bool CMaterial::NeedsFixedFunctionFlashlight() const
{
return ( GetMaterialVarFlags2() & MATERIAL_VAR2_NEEDS_FIXED_FUNCTION_FLASHLIGHT ) &&
MaterialSystem()->InFlashlightMode();
}
bool CMaterial::IsUsingVertexID( ) const
{
return ( GetMaterialVarFlags2() & MATERIAL_VAR2_USES_VERTEXID ) != 0;
}
void CMaterial::DeleteIfUnreferenced()
{
if ( m_RefCount > 0 )
return;
IMaterialVar::DeleteUnreferencedTextures( true );
IMaterialInternal::DestroyMaterial( this );
IMaterialVar::DeleteUnreferencedTextures( false );
}