diff options
Diffstat (limited to 'materialsystem/cmaterial.cpp')
| -rw-r--r-- | materialsystem/cmaterial.cpp | 3599 |
1 files changed, 3599 insertions, 0 deletions
diff --git a/materialsystem/cmaterial.cpp b/materialsystem/cmaterial.cpp new file mode 100644 index 0000000..90f53d6 --- /dev/null +++ b/materialsystem/cmaterial.cpp @@ -0,0 +1,3599 @@ +//========= 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 = ( 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; + } + else + { + 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(); + if( !IsTextureInternalEnvCubemap( pTexture ) ) + { + 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 ); +} |