You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3596 lines
98 KiB
3596 lines
98 KiB
//========= 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" |
|
#include <malloc.h> |
|
#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", |
|
¢er.x, ¢er.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 |
|
{ |
|
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 ); |
|
}
|
|
|