//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implementation of a material
//
//===========================================================================//

#include "imaterialinternal.h"
#include "bitmap/tgaloader.h"
#include "colorspace.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/itexture.h"
#include <string.h>
#include "materialsystem_global.h"
#include "shaderapi/ishaderapi.h"
#include "materialsystem/imaterialproxy.h"							   
#include "shadersystem.h"
#include "materialsystem/imaterialproxyfactory.h"
#include "IHardwareConfigInternal.h"
#include "utlsymbol.h"
#ifdef OSX
#include <malloc/malloc.h>
#else
#include <malloc.h>
#endif
#include "filesystem.h"
#include <KeyValues.h>
#include "mempool.h"
#include "shaderapi/ishaderutil.h"
#include "vtf/vtf.h"
#include "tier1/strtools.h"
#include <ctype.h>
#include "utlbuffer.h"
#include "mathlib/vmatrix.h"
#include "texturemanager.h"
#include "itextureinternal.h"
#include "mempool.h"
#include "tier1/callqueue.h"
#include "cmaterial_queuefriendly.h"
#include "ifilelist.h"
#include "tier0/icommandline.h"
#include "tier0/minidump.h"

// #define PROXY_TRACK_NAMES

//-----------------------------------------------------------------------------
// Material implementation
//-----------------------------------------------------------------------------
class CMaterial : public IMaterialInternal
{
public:
	// Members of the IMaterial interface
	const char  *GetName() const;
	const char  *GetTextureGroupName() const;

	PreviewImageRetVal_t GetPreviewImageProperties( int *width, int *height, 
				 				ImageFormat *imageFormat, bool* isTranslucent ) const;
	PreviewImageRetVal_t GetPreviewImage( unsigned char *data, int width, int height,
								ImageFormat imageFormat ) const;

	int			GetMappingWidth( );
	int			GetMappingHeight( );
	int			GetNumAnimationFrames( );

	bool		InMaterialPage( void )						{ return false; }
	void		GetMaterialOffset( float *pOffset );
	void		GetMaterialScale( float *pOffset );
	IMaterial	*GetMaterialPage( void )					{ return NULL; }

	void		IncrementReferenceCount( );
	void		DecrementReferenceCount( );
	int 		GetEnumerationID( ) const;
	void		GetLowResColorSample( float s, float t, float *color ) const;
	
	IMaterialVar *		FindVar( char const *varName, bool *found, bool complain = true );
	IMaterialVar *		FindVarFast( char const *pVarName, unsigned int *pToken );

	// Sets new VMT shader parameters for the material
	virtual void		SetShaderAndParams( KeyValues *pKeyValues );

	bool				UsesEnvCubemap( void );
	bool				NeedsSoftwareSkinning( void );
	virtual bool		NeedsSoftwareLighting( void );
	bool				NeedsTangentSpace( void );
	bool				NeedsPowerOfTwoFrameBufferTexture( bool bCheckSpecificToThisFrame = true );
	bool				NeedsFullFrameBufferTexture( bool bCheckSpecificToThisFrame = true );
	virtual bool		IsUsingVertexID( ) const;

	// GR - Is lightmap alpha needed?
	bool	NeedsLightmapBlendAlpha( void );
	
	virtual void	AlphaModulate( float alpha );
	virtual void	ColorModulate( float r, float g, float b );
	virtual float	GetAlphaModulation();
	virtual void	GetColorModulation( float *r, float *g, float *b );

	// Gets the morph format
	virtual MorphFormat_t	GetMorphFormat() const;

	void	SetMaterialVarFlag( MaterialVarFlags_t flag, bool on );
	bool	GetMaterialVarFlag( MaterialVarFlags_t flag ) const;

	bool	IsTranslucent();
	bool	IsTranslucentInternal( float fAlphaModulation ) const; //need to centralize the logic without relying on the *current* alpha modulation being that which is stored in m_pShaderParams[ALPHA].
	bool	IsAlphaTested();
	bool	IsVertexLit();
	virtual bool IsSpriteCard();

	void	GetReflectivity( Vector& reflect );
	bool	GetPropertyFlag( MaterialPropertyTypes_t type );

	// Is the material visible from both sides?
	bool	IsTwoSided();

	int		GetNumPasses( void );
	int		GetTextureMemoryBytes( void );

	void	SetUseFixedFunctionBakedLighting( bool bEnable );

	virtual bool IsPrecached( ) const;

public:
	// stuff that is visible only from within the material system

	// constructor, destructor
	CMaterial( char const* materialName, const char *pTextureGroupName, KeyValues *pVMTKeyValues );
	virtual		~CMaterial();

	void		DrawMesh( VertexCompressionType_t vertexCompression );
	int			GetReferenceCount( ) const;
	void		Uncache( bool bPreserveVars = false );
	void		Precache();
	void		ReloadTextures( void );
	// If provided, pKeyValues and pPatchKeyValues should come from LoadVMTFile()
	bool		PrecacheVars( KeyValues *pKeyValues = NULL, KeyValues *pPatchKeyValues = NULL, CUtlVector<FileNameHandle_t> *pIncludes = NULL, int nFindContext = MATERIAL_FINDCONTEXT_NONE );
	void		SetMinLightmapPageID( int pageID );
	void		SetMaxLightmapPageID( int pageID );
	int			GetMinLightmapPageID( ) const;
	int			GetMaxLightmapPageID( ) const;
	void		SetNeedsWhiteLightmap( bool val );
	bool		GetNeedsWhiteLightmap( ) const;
	bool		IsPrecachedVars( ) const;
	IShader *	GetShader() const;
	const char *GetShaderName() const;
	
	virtual void			DeleteIfUnreferenced();

	void					SetEnumerationID( int id );
	void					CallBindProxy( void *proxyData );
	virtual IMaterial		*CheckProxyReplacement( void *proxyData );
	bool					HasProxy( void ) const;

	// Sets the shader associated with the material
	void SetShader( const char *pShaderName );

	// Can we override this material in debug?
	bool NoDebugOverride() const;

	// Gets the vertex format
	VertexFormat_t GetVertexFormat() const;
	
	// diffuse bump lightmap?
	bool IsUsingDiffuseBumpedLighting() const;

	// lightmap?
	bool IsUsingLightmap() const;

	// Gets the vertex usage flags
	VertexFormat_t GetVertexUsage() const;

	// Debugs this material
	bool PerformDebugTrace() const;

	// Are we suppressed?
	bool IsSuppressed() const;

	// Do we use fog?
	bool UseFog( void ) const;
	
	// Should we draw?
	void ToggleSuppression();
	void ToggleDebugTrace();
	
	// Refresh material based on current var values
	void Refresh();
	void RefreshPreservingMaterialVars();

	// This computes the state snapshots for this material
	void RecomputeStateSnapshots();

	// Gets at the shader parameters
	virtual int ShaderParamCount() const;
	virtual IMaterialVar **GetShaderParams( void );

	virtual void AddMaterialVar( IMaterialVar *pMaterialVar );

	virtual bool IsErrorMaterial() const;

	// Was this manually created (not read from a file?)
	virtual bool IsManuallyCreated() const;

	virtual bool NeedsFixedFunctionFlashlight() const;

	virtual void MarkAsPreloaded( bool bSet );
	virtual bool IsPreloaded() const;

	virtual void ArtificialAddRef( void );
	virtual void ArtificialRelease( void );

	virtual void ReportVarChanged( IMaterialVar *pVar )
	{	
		m_ChangeID++;
	}
	virtual void ClearContextData( void );

	virtual uint32 GetChangeID() const { return m_ChangeID; }

	virtual bool IsRealTimeVersion( void ) const { return true; }
	virtual IMaterialInternal *GetRealTimeVersion( void ) { return this; }
	virtual IMaterialInternal *GetQueueFriendlyVersion( void ) { return &m_QueueFriendlyVersion; }

	void DecideShouldReloadFromWhitelist( IFileList *pFilesToReload );
	void ReloadFromWhitelistIfMarked();
	bool WasReloadedFromWhitelist();

private:
	// Initializes, cleans up the shader params
	void CleanUpShaderParams();

	// Sets up an error shader when we run into problems.
	void SetupErrorShader();

	// Does this material have a UNC-file name?
	bool UsesUNCFileName() const;

	// Prints material flags.
	void PrintMaterialFlags( int flags, int flagsDefined );

	// Parses material flags
	bool ParseMaterialFlag( KeyValues* pParseValue, IMaterialVar* pFlagVar,
		IMaterialVar* pFlagDefinedVar, bool parsingOverrides, int& flagMask, int& overrideMask );

	// Computes the material vars for the shader
	int ParseMaterialVars( IShader* pShader, KeyValues& keyValues, 
		KeyValues* pOverride, bool modelDefault, IMaterialVar** ppVars, int nFindContext = MATERIAL_FINDCONTEXT_NONE );

	// Figures out the preview image for worldcraft
	char const*	GetPreviewImageName( );
	char const* GetPreviewImageFileName( void ) const;

	// Hooks up the shader, returns keyvalues of fallback that was used
	KeyValues* InitializeShader( KeyValues &keyValues, KeyValues &patchKeyValues, int nFindContext = MATERIAL_FINDCONTEXT_NONE );

	// Finds the flag associated with a particular flag name
	int FindMaterialVarFlag( char const* pFlagName ) const;

	// Initializes, cleans up the state snapshots
	bool InitializeStateSnapshots();
	void CleanUpStateSnapshots();

	// Initializes, cleans up the material proxy
	void InitializeMaterialProxy( KeyValues* pFallbackKeyValues );
	void CleanUpMaterialProxy();
	void DetermineProxyReplacements( KeyValues *pFallbackKeyValues );

	// Creates, destroys snapshots
	RenderPassList_t *CreateRenderPassList();
	void DestroyRenderPassList( RenderPassList_t *pPassList );

	// Grabs the texture width and height from the var list for faster access
	void PrecacheMappingDimensions( );

	// Gets the renderstate
	virtual ShaderRenderState_t *GetRenderState();

	// Do we have a valid renderstate?
	bool IsValidRenderState() const;

	// Get the material var flags
	int	GetMaterialVarFlags() const;
	void SetMaterialVarFlags( int flags, bool on );
	int	GetMaterialVarFlags2() const;
	void SetMaterialVarFlags2( int flags, bool on );

	// Returns a dummy material variable
	IMaterialVar*	GetDummyVariable();

	IMaterialVar*	GetShaderParam( int id );

	void			FindRepresentativeTexture( void );

	bool ShouldSkipVar( KeyValues *pMaterialVar, bool * pWasConditional );


	// Fixed-size allocator
	DECLARE_FIXEDSIZE_ALLOCATOR( CMaterial );

private:
	enum
	{
		MATERIAL_NEEDS_WHITE_LIGHTMAP = 0x1,
		MATERIAL_IS_PRECACHED = 0x2,
		MATERIAL_VARS_IS_PRECACHED = 0x4,
		MATERIAL_VALID_RENDERSTATE = 0x8,
		MATERIAL_IS_MANUALLY_CREATED = 0x10,
		MATERIAL_USES_UNC_FILENAME = 0x20,
		MATERIAL_IS_PRELOADED = 0x40,
		MATERIAL_ARTIFICIAL_REFCOUNT = 0x80,
	};

	int					m_iEnumerationID;
	
	int					m_minLightmapPageID;
	int					m_maxLightmapPageID;

	unsigned short		m_MappingWidth;
	unsigned short		m_MappingHeight;
	
	IShader				*m_pShader;

	CUtlSymbol			m_Name;
	// Any textures created for this material go under this texture group.
	CUtlSymbol			m_TextureGroupName;

	CInterlockedInt		m_RefCount;
	unsigned short		m_Flags;

	unsigned char		m_VarCount;

	CUtlVector< IMaterialProxy * > m_ProxyInfo;

#ifdef PROXY_TRACK_NAMES
	// Array to track names of above material proxies. Useful for tracking down issues with proxies.
	CUtlVector< CUtlString > m_ProxyInfoNames;
#endif

	IMaterialVar**		m_pShaderParams;
	IMaterialProxy		*m_pReplacementProxy;
	ShaderRenderState_t m_ShaderRenderState;

	// This remembers filenames of VMTs that we included so we can sv_pure/flush ourselves if any of them need to be reloaded.
	CUtlVector<FileNameHandle_t> m_VMTIncludes;
	bool m_bShouldReloadFromWhitelist;	// Tells us if the material decided it should be reloaded due to sv_pure whitelist changes.

	ITextureInternal	*m_representativeTexture;
	Vector				m_Reflectivity;
	uint32				m_ChangeID;

	// Used only by procedural materials; it essentially is an in-memory .VMT file
	KeyValues			*m_pVMTKeyValues;

#if defined( _DEBUG )
	// Makes it easier to see what's going on
	char				*m_pDebugName;
#endif

protected:
	CMaterial_QueueFriendly m_QueueFriendlyVersion;
};


// NOTE: This must be the last file included
// Has to exist *after* fixed size allocator declaration
#include "tier0/memdbgon.h"

// Forward decls of helper functions for dealing with patch vmts.
static void ApplyPatchKeyValues( KeyValues &keyValues, KeyValues &patchKeyValues );
static bool AccumulateRecursiveVmtPatches( KeyValues &patchKeyValuesOut, KeyValues **ppBaseKeyValuesOut,
										   const KeyValues& keyValues, const char *pPathID, CUtlVector<FileNameHandle_t> *pIncludes );

//-----------------------------------------------------------------------------
// Parser utilities
//-----------------------------------------------------------------------------
static inline bool IsWhitespace( char c )
{
	return c == ' ' || c == '\t';
}

static inline bool IsEndline( char c )
{
	return c == '\n' || c == '\0';
}

static inline bool IsVector( char const* v )
{
	while (IsWhitespace(*v))
	{
		++v;
		if (IsEndline(*v))
			return false;
	}
	return *v == '[' || *v == '{';
}


//-----------------------------------------------------------------------------
// Methods to create state snapshots
//-----------------------------------------------------------------------------
#include "tier0/memdbgoff.h"

#ifndef _CONSOLE
struct EditorRenderStateList_t
{
	// Store combo of alpha, color, fixed-function baked lighting, flashlight, editor mode
	RenderPassList_t m_Snapshots[SNAPSHOT_COUNT_EDITOR];

	DECLARE_FIXEDSIZE_ALLOCATOR( EditorRenderStateList_t );
};
#endif

struct StandardRenderStateList_t
{
	// Store combo of alpha, color, fixed-function baked lighting, flashlight
	RenderPassList_t m_Snapshots[SNAPSHOT_COUNT_NORMAL];

	DECLARE_FIXEDSIZE_ALLOCATOR( StandardRenderStateList_t );
};

#include "tier0/memdbgon.h"

#ifndef _CONSOLE
DEFINE_FIXEDSIZE_ALLOCATOR( EditorRenderStateList_t, 256, true );
#endif
DEFINE_FIXEDSIZE_ALLOCATOR( StandardRenderStateList_t, 256, true );


//-----------------------------------------------------------------------------
// class factory methods
//-----------------------------------------------------------------------------
DEFINE_FIXEDSIZE_ALLOCATOR( CMaterial, 256, true );

IMaterialInternal* IMaterialInternal::CreateMaterial( char const* pMaterialName, const char *pTextureGroupName, KeyValues *pVMTKeyValues )
{
	MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
	IMaterialInternal *pResult = new CMaterial( pMaterialName, pTextureGroupName, pVMTKeyValues );
	MaterialSystem()->Unlock( hMaterialLock );
	return pResult;
}

void IMaterialInternal::DestroyMaterial( IMaterialInternal* pMaterial )
{
	MaterialLock_t hMaterialLock = MaterialSystem()->Lock();
	if (pMaterial)
	{
		Assert( pMaterial->IsRealTimeVersion() );
		CMaterial* pMatImp = static_cast<CMaterial*>(pMaterial);
		// Deletion of the error material is deferred until after all other materials have been deleted.
		// See CMaterialSystem::CleanUpErrorMaterial() in cmaterialsystem.cpp.
		if ( !pMatImp->IsErrorMaterial() )
		{
			delete pMatImp;
		}
	}
	MaterialSystem()->Unlock( hMaterialLock );
}

//-----------------------------------------------------------------------------
// constructor, destructor
//-----------------------------------------------------------------------------
CMaterial::CMaterial( char const* materialName, const char *pTextureGroupName, KeyValues *pKeyValues )
{
	m_Reflectivity.Init( 0.2f, 0.2f, 0.2f );
	int len = Q_strlen(materialName);
	char* pTemp = (char*)_alloca( len + 1 );

	// Strip off the extension
	Q_StripExtension( materialName, pTemp, len+1 );
	Q_strlower( pTemp );

#if defined( _X360 )
	// material names are expected to be forward slashed for correct sort and find behavior!
	// assert now to track alternate or regressed path that is source of inconsistency
	Assert( strchr( pTemp, '\\' ) == NULL );
#endif

	// Convert it to a symbol
	m_Name = pTemp;

#if defined( _DEBUG )
	m_pDebugName = new char[strlen(pTemp) + 1];
	Q_strncpy( m_pDebugName, pTemp, strlen(pTemp) + 1 );
#endif

	m_bShouldReloadFromWhitelist = false;
	m_Flags = 0;
	m_pShader = NULL;
	m_pShaderParams = NULL;
	m_RefCount = 0;
	m_representativeTexture = NULL;
	m_pReplacementProxy = NULL;
	m_VarCount = 0;
	m_MappingWidth = m_MappingHeight = 0;
	m_iEnumerationID = 0;
	m_minLightmapPageID = m_maxLightmapPageID = 0;
	m_TextureGroupName = pTextureGroupName;
	m_pVMTKeyValues = pKeyValues;
	if (m_pVMTKeyValues)
	{
		m_Flags |= MATERIAL_IS_MANUALLY_CREATED; 
	}

	if ( pTemp[0] == '/' && pTemp[1] == '/' && pTemp[2] != '/' )
	{
		m_Flags |= MATERIAL_USES_UNC_FILENAME;
	}

	// Initialize the renderstate to something indicating nothing should be drawn
	m_ShaderRenderState.m_Flags = 0;
	m_ShaderRenderState.m_VertexFormat = m_ShaderRenderState.m_VertexUsage = 0;
	m_ShaderRenderState.m_MorphFormat = 0;
	m_ShaderRenderState.m_pSnapshots = CreateRenderPassList(); 
	m_ChangeID = 0;

	m_QueueFriendlyVersion.SetRealTimeVersion( this );
}

CMaterial::~CMaterial()
{
	MaterialSystem()->UnbindMaterial( this );

	Uncache();

	if ( m_RefCount != 0 )
	{
		DevWarning( 2, "Reference Count for Material %s (%d) != 0\n", GetName(), (int) m_RefCount );
	}

	if ( m_pVMTKeyValues )
	{
		m_pVMTKeyValues->deleteThis();
		m_pVMTKeyValues = NULL;
	}

	DestroyRenderPassList( m_ShaderRenderState.m_pSnapshots ); 

	m_representativeTexture = NULL;

#if defined( _DEBUG )
	delete [] m_pDebugName;
#endif

	// Deliberately stomp our VTable so that we can detect cases where code tries to access freed materials.
	int *p = (int *)this;
	*p = 0xc0dedbad;
}


void CMaterial::ClearContextData( void )
{
	int nSnapshotCount = SnapshotTypeCount();
	for( int i = 0 ; i < nSnapshotCount ; i++ )
		for( int j = 0 ; j < m_ShaderRenderState.m_pSnapshots[i].m_nPassCount; j++ )
		{
			if ( m_ShaderRenderState.m_pSnapshots[i].m_pContextData[j] )
			{
				delete m_ShaderRenderState.m_pSnapshots[i].m_pContextData[j];
				m_ShaderRenderState.m_pSnapshots[i].m_pContextData[j] = NULL;
			}
			
		}
}

//-----------------------------------------------------------------------------
// Sets new VMT shader parameters for the material
//-----------------------------------------------------------------------------
void CMaterial::SetShaderAndParams( KeyValues *pKeyValues )
{
	Uncache();

	if ( m_pVMTKeyValues )
	{
		m_pVMTKeyValues->deleteThis();
		m_pVMTKeyValues = NULL;
	}

	m_pVMTKeyValues = pKeyValues ? pKeyValues->MakeCopy() : NULL;
	if (m_pVMTKeyValues)
	{
		m_Flags |= MATERIAL_IS_MANUALLY_CREATED; 
	}

	// Apply patches
	const char *pMaterialName = GetName();
	char pFileName[MAX_PATH];
	const char *pPathID = "GAME";
	if ( !UsesUNCFileName() )
	{
		Q_snprintf( pFileName, sizeof( pFileName ), "materials/%s.vmt", pMaterialName );
	}
	else
	{
		Q_snprintf( pFileName, sizeof( pFileName ), "%s.vmt", pMaterialName );
		if ( pMaterialName[0] == '/' && pMaterialName[1] == '/' && pMaterialName[2] != '/' )
		{
			// UNC, do full search
			pPathID = NULL;
		}
	}

	KeyValues *pLoadedKeyValues = new KeyValues( "vmt" );
	if ( pLoadedKeyValues->LoadFromFile( g_pFullFileSystem, pFileName, pPathID ) )
	{
		// Load succeeded, check if it's a patch file
		if ( V_stricmp( pLoadedKeyValues->GetName(), "patch" ) == 0 )
		{
			// it's a patch file, recursively build up patch keyvalues
			KeyValues *pPatchKeyValues = new KeyValues( "vmt_patch" );
			bool bSuccess = AccumulateRecursiveVmtPatches( *pPatchKeyValues, NULL, *pLoadedKeyValues, pPathID, NULL );
			if ( bSuccess )
			{
				// Apply accumulated patches to final vmt
				ApplyPatchKeyValues( *m_pVMTKeyValues, *pPatchKeyValues );
			}
			pPatchKeyValues->deleteThis();
		}
	}
	pLoadedKeyValues->deleteThis();

	if ( g_pShaderDevice->IsUsingGraphics() )
	{
		Precache();
	}
}


//-----------------------------------------------------------------------------
// Creates, destroys snapshots
//-----------------------------------------------------------------------------
RenderPassList_t *CMaterial::CreateRenderPassList()
{
	RenderPassList_t *pRenderPassList;
	if ( IsConsole() || !MaterialSystem()->CanUseEditorMaterials() )
	{
		StandardRenderStateList_t *pList = new StandardRenderStateList_t;
		pRenderPassList = (RenderPassList_t*)pList->m_Snapshots;
	}
#ifndef _CONSOLE
	else
	{
		EditorRenderStateList_t *pList = new EditorRenderStateList_t;
		pRenderPassList = (RenderPassList_t*)pList->m_Snapshots;
	}
#endif

	int nSnapshotCount = SnapshotTypeCount();
	memset( pRenderPassList, 0, nSnapshotCount * sizeof(RenderPassList_t) );
	return pRenderPassList;
}

void CMaterial::DestroyRenderPassList( RenderPassList_t *pPassList )
{
	if ( !pPassList )
		return;

	int nSnapshotCount = SnapshotTypeCount();
	for( int i = 0 ; i < nSnapshotCount ; i++ )
		for( int j = 0 ; j < pPassList[i].m_nPassCount; j++ )
		{
			if ( pPassList[i].m_pContextData[j] )
			{
				delete pPassList[i].m_pContextData[j];
				pPassList[i].m_pContextData[j] = NULL;
			}
			
		}
	if ( IsConsole() || !MaterialSystem()->CanUseEditorMaterials() )
	{
		StandardRenderStateList_t *pList = (StandardRenderStateList_t*)pPassList;
		delete pList;
	}
#ifndef _CONSOLE
	else
	{
		EditorRenderStateList_t *pList = (EditorRenderStateList_t*)pPassList;
		delete pList;
	}
#endif
}

	
//-----------------------------------------------------------------------------
// Gets the renderstate
//-----------------------------------------------------------------------------
ShaderRenderState_t *CMaterial::GetRenderState()
{
	Precache();
	return &m_ShaderRenderState;
}


//-----------------------------------------------------------------------------
// Returns a dummy material variable
//-----------------------------------------------------------------------------
IMaterialVar* CMaterial::GetDummyVariable()
{
	static IMaterialVar* pDummyVar = 0;
	if (!pDummyVar)
		pDummyVar = IMaterialVar::Create( 0, "$dummyVar", 0 );

	return pDummyVar;
}


//-----------------------------------------------------------------------------
// Are vars precached?
//-----------------------------------------------------------------------------
bool CMaterial::IsPrecachedVars( ) const
{
	return (m_Flags & MATERIAL_VARS_IS_PRECACHED) != 0;
}


//-----------------------------------------------------------------------------
// Are we precached?
//-----------------------------------------------------------------------------
bool CMaterial::IsPrecached( ) const
{
	return (m_Flags & MATERIAL_IS_PRECACHED) != 0;
}


//-----------------------------------------------------------------------------
// Cleans up shader parameters
//-----------------------------------------------------------------------------
void CMaterial::CleanUpShaderParams()
{
	if( m_pShaderParams )
	{
		for (int i = 0; i < m_VarCount; ++i)
		{
			IMaterialVar::Destroy( m_pShaderParams[i] );
		}

		free( m_pShaderParams );
		m_pShaderParams = 0;
	}
	m_VarCount = 0;
}


//-----------------------------------------------------------------------------
// Initializes the material proxy
//-----------------------------------------------------------------------------
void CMaterial::InitializeMaterialProxy( KeyValues* pFallbackKeyValues )
{
	IMaterialProxyFactory *pMaterialProxyFactory;
	pMaterialProxyFactory = MaterialSystem()->GetMaterialProxyFactory();	
	if( !pMaterialProxyFactory )
		return;

	DetermineProxyReplacements( pFallbackKeyValues );

	if ( m_pReplacementProxy )
	{
		m_ProxyInfo.AddToTail( m_pReplacementProxy );
#ifdef PROXY_TRACK_NAMES
		m_ProxyInfoNames.AddToTail( "__replacementproxy" );
#endif
	}

	// See if we've got a proxy section; obey fallbacks
	KeyValues* pProxySection = pFallbackKeyValues->FindKey("Proxies");
	if ( pProxySection )
	{
		// Iterate through the section + create all of the proxies
		KeyValues* pProxyKey = pProxySection->GetFirstSubKey();

		for ( ; pProxyKey; pProxyKey = pProxyKey->GetNextKey() )
		{
			// Each of the proxies should themselves be databases
			IMaterialProxy* pProxy = pMaterialProxyFactory->CreateProxy( pProxyKey->GetName() );
			if (!pProxy)
			{
				Warning( "Error: Material \"%s\" : proxy \"%s\" not found!\n", GetName(), pProxyKey->GetName() );
				continue;
			}

			if (!pProxy->Init( this->GetQueueFriendlyVersion(), pProxyKey ))
			{
				pMaterialProxyFactory->DeleteProxy( pProxy );
				Warning( "Error: Material \"%s\" : proxy \"%s\" unable to initialize!\n", GetName(), pProxyKey->GetName() );
			}
			else
			{
				m_ProxyInfo.AddToTail( pProxy );
#ifdef PROXY_TRACK_NAMES
				m_ProxyInfoNames.AddToTail( pProxyKey->GetName() );
#endif
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Cleans up the material proxy
//-----------------------------------------------------------------------------
void CMaterial::CleanUpMaterialProxy()
{
	if ( !m_ProxyInfo.Count() )
		return;

	IMaterialProxyFactory *pMaterialProxyFactory;
	pMaterialProxyFactory = MaterialSystem()->GetMaterialProxyFactory();
	if ( !pMaterialProxyFactory )
		return;

	// Clean up material proxies
	for ( int i = m_ProxyInfo.Count() - 1; i >= 0; i-- )
	{
		IMaterialProxy *pProxy = m_ProxyInfo[ i ];

		pMaterialProxyFactory->DeleteProxy( pProxy );
	}
	
	m_ProxyInfo.RemoveAll();
#ifdef PROXY_TRACK_NAMES
	m_ProxyInfoNames.RemoveAll();
#endif
}


void CMaterial::DetermineProxyReplacements( KeyValues *pFallbackKeyValues )
{
	m_pReplacementProxy = MaterialSystem()->DetermineProxyReplacements( this, pFallbackKeyValues );
}


static char const *GetVarName( KeyValues *pVar )
{
	char const *pVarName = pVar->GetName();
	char const *pQuestion = strchr( pVarName, '?' );
	if (! pQuestion )
		return pVarName;
	else
		return pQuestion + 1;
}

//-----------------------------------------------------------------------------
// Finds the index of the material var associated with a var
//-----------------------------------------------------------------------------
static int FindMaterialVar( IShader* pShader, char const* pVarName )
{
	if ( !pShader )
		return -1;

	// Strip preceeding spaces
	pVarName += strspn( pVarName, " \t" );

	for (int i = pShader->GetNumParams(); --i >= 0; )
	{
		// Makes the parser a little more lenient.. strips off bogus spaces in the var name.
		const char *pParamName = pShader->GetParamName(i);
		const char *pFound = Q_stristr( pVarName, pParamName );

		// The found string had better start with the first non-whitespace character
		if ( pFound != pVarName )
			continue;

		// Strip spaces at the end
		int nLen = Q_strlen( pParamName );
		pFound += nLen;
		while ( true )
		{
			if ( !pFound[0] )
				return i;

			if ( !IsWhitespace( pFound[0] ) )
				break;

			++pFound;
		}
	}
	return -1;
}


//-----------------------------------------------------------------------------
// Creates a vector material var
//-----------------------------------------------------------------------------
int ParseVectorFromKeyValueString( KeyValues *pKeyValue, const char *pMaterialName, float vecVal[4] )
{
	char const* pScan = pKeyValue->GetString();
	bool divideBy255 = false;

	// skip whitespace
	while( IsWhitespace(*pScan) )
	{
		++pScan;
	}

	if( *pScan == '{' )
	{
		divideBy255 = true;
	}
	else
	{
		Assert( *pScan == '[' );
	}
	
	// skip the '['
	++pScan;
	int i;
	for( i = 0; i < 4; i++ )
	{
		// skip whitespace
		while( IsWhitespace(*pScan) )
		{
			++pScan;
		}

		if( IsEndline(*pScan) || *pScan == ']' || *pScan == '}' )
		{
			if (*pScan != ']' && *pScan != '}')
			{
				Warning( "Warning in .VMT file (%s): no ']' or '}' found in vector key \"%s\".\n"
					"Did you forget to surround the vector with \"s?\n", pMaterialName, pKeyValue->GetName() );
			}

			// allow for vec2's, etc.
			vecVal[i] = 0.0f;
			break;
		}

		char* pEnd;

		vecVal[i] = strtod( pScan, &pEnd );
		if (pScan == pEnd)
		{
			Warning( "Error in .VMT file: error parsing vector element \"%s\" in \"%s\"\n", pKeyValue->GetName(), pMaterialName );
			return 0;
		}

		pScan = pEnd;
	}

	if( divideBy255 )
	{
		vecVal[0] *= ( 1.0f / 255.0f );
		vecVal[1] *= ( 1.0f / 255.0f );
		vecVal[2] *= ( 1.0f / 255.0f );
		vecVal[3] *= ( 1.0f / 255.0f );
	}

	return i;
}

static IMaterialVar* CreateVectorMaterialVarFromKeyValue( IMaterial* pMaterial, KeyValues* pKeyValue )
{
	char const *pszName = GetVarName( pKeyValue );
	float vecVal[4];
	int nDim = ParseVectorFromKeyValueString( pKeyValue, pszName, vecVal );
	if ( nDim == 0 )
		return NULL;

	// Create the variable!
	return IMaterialVar::Create( pMaterial, pszName, vecVal, nDim );
}


//-----------------------------------------------------------------------------
// Creates a vector material var
//-----------------------------------------------------------------------------
static IMaterialVar* CreateMatrixMaterialVarFromKeyValue( IMaterial* pMaterial, KeyValues* pKeyValue )
{
	char const* pScan = pKeyValue->GetString();
	char const *pszName = GetVarName( pKeyValue );

	// Matrices can be specified one of two ways:
	// [ # # # #  # # # #  # # # #  # # # # ]
	// or
	// center # # scale # # rotate # translate # #

	VMatrix mat;
	int count = sscanf( pScan, " [ %f %f %f %f  %f %f %f %f  %f %f %f %f  %f %f %f %f ]",
		&mat.m[0][0], &mat.m[0][1], &mat.m[0][2], &mat.m[0][3],
		&mat.m[1][0], &mat.m[1][1], &mat.m[1][2], &mat.m[1][3],
		&mat.m[2][0], &mat.m[2][1], &mat.m[2][2], &mat.m[2][3],
		&mat.m[3][0], &mat.m[3][1], &mat.m[3][2], &mat.m[3][3] );
	if (count == 16)
	{
		return IMaterialVar::Create( pMaterial, pszName, mat );
	}

	Vector2D scale, center;
	float angle;
	Vector2D translation;
	count = sscanf( pScan, " center %f %f scale %f %f rotate %f translate %f %f",
		&center.x, &center.y, &scale.x, &scale.y, &angle, &translation.x, &translation.y );
	if (count != 7)
		return NULL;

	VMatrix temp;
	MatrixBuildTranslation( mat, -center.x, -center.y, 0.0f );
	MatrixBuildScale( temp, scale.x, scale.y, 1.0f );
	MatrixMultiply( temp, mat, mat );
	MatrixBuildRotateZ( temp, angle );
	MatrixMultiply( temp, mat, mat );
	MatrixBuildTranslation( temp, center.x + translation.x, center.y + translation.y, 0.0f );
	MatrixMultiply( temp, mat, mat );

	// Create the variable!
	return IMaterialVar::Create( pMaterial, pszName, mat );
}


//-----------------------------------------------------------------------------
// Creates a material var from a key value
//-----------------------------------------------------------------------------

static IMaterialVar* CreateMaterialVarFromKeyValue( IMaterial* pMaterial, KeyValues* pKeyValue )
{
	char const *pszName = GetVarName( pKeyValue );
	switch( pKeyValue->GetDataType() )
	{
		case KeyValues::TYPE_INT:
			return IMaterialVar::Create( pMaterial, pszName, pKeyValue->GetInt() );
			
		case KeyValues::TYPE_FLOAT:
			return IMaterialVar::Create( pMaterial, pszName, pKeyValue->GetFloat() );
			
		case KeyValues::TYPE_STRING:
		{
			char const* pString = pKeyValue->GetString();
			if (!pString || !pString[0])
				return 0;
			
			// Look for matrices
			IMaterialVar *pMatrixVar = CreateMatrixMaterialVarFromKeyValue( pMaterial, pKeyValue );
			if (pMatrixVar)
				return pMatrixVar;
			
			// Look for vectors
			if (!IsVector(pString))
				return IMaterialVar::Create( pMaterial, pszName, pString );
			
			// Parse the string as a vector...
			return CreateVectorMaterialVarFromKeyValue( pMaterial, pKeyValue );
		}
	}
	
	return 0;
}


//-----------------------------------------------------------------------------
// Reads out common flags, prevents them from becoming material vars
//-----------------------------------------------------------------------------
int CMaterial::FindMaterialVarFlag( char const* pFlagName ) const
{
	// Strip preceeding spaces
	while ( pFlagName[0] )
	{
		if ( !IsWhitespace( pFlagName[0] ) )
			break;

		++pFlagName;
	}

	for( int i = 0; *ShaderSystem()->ShaderStateString(i); ++i )
	{
		const char *pStateString = ShaderSystem()->ShaderStateString(i);
		const char *pFound = Q_stristr( pFlagName, pStateString );

		// The found string had better start with the first non-whitespace character
		if ( pFound != pFlagName )
			continue;

		// Strip spaces at the end
		int nLen = Q_strlen( pStateString );
		pFound += nLen;
		while ( true )
		{
			if ( !pFound[0] )
				return (1 << i);

			if ( !IsWhitespace( pFound[0] ) )
				break;

			++pFound;
		}
	}
	return 0;
}


//-----------------------------------------------------------------------------
// Print material flags
//-----------------------------------------------------------------------------
void CMaterial::PrintMaterialFlags( int flags, int flagsDefined )
{
	int i;
	for( i = 0; *ShaderSystem()->ShaderStateString(i); i++ )
	{
		if( flags & ( 1<<i ) )
		{
			Warning( "%s|", ShaderSystem()->ShaderStateString(i) );
		}
	}
	Warning( "\n" );
}


//-----------------------------------------------------------------------------
// Parses material flags
//-----------------------------------------------------------------------------
bool CMaterial::ParseMaterialFlag( KeyValues* pParseValue, IMaterialVar* pFlagVar,
	IMaterialVar* pFlagDefinedVar, bool parsingOverrides, int& flagMask, int& overrideMask )
{
	// See if the var is a flag...
	int flagbit = FindMaterialVarFlag( GetVarName( pParseValue ) );
	if (!flagbit)
		return false;

	// Allow for flag override
	int testMask = parsingOverrides ? overrideMask : flagMask;
	if (testMask & flagbit)
	{
		Warning("Error! Flag \"%s\" is multiply defined in material \"%s\"!\n", pParseValue->GetName(), GetName() );
		return true;
	}

	// Make sure overrides win
	if (overrideMask & flagbit)
		return true;

	if (parsingOverrides)
		overrideMask |= flagbit;
	else
		flagMask |= flagbit;

	// If so, then set the flag bit
	if (pParseValue->GetInt())
		pFlagVar->SetIntValue( pFlagVar->GetIntValue() | flagbit );
	else
		pFlagVar->SetIntValue( pFlagVar->GetIntValue() & (~flagbit) );

	// Mark the flag as being defined
	pFlagDefinedVar->SetIntValue( pFlagDefinedVar->GetIntValue() | flagbit );

/*
	if( stristr( m_pDebugName, "glasswindow064a" ) )
	{
		Warning( "flags\n" );
		PrintMaterialFlags( pFlagVar->GetIntValue(), pFlagDefinedVar->GetIntValue() );
	}
*/
	
	return true;
}


ConVar mat_reduceparticles( "mat_reduceparticles",  "0", FCVAR_ALLOWED_IN_COMPETITIVE );

bool CMaterial::ShouldSkipVar( KeyValues *pVar, bool *pWasConditional )
{
	char const *pVarName = pVar->GetName();
	char const *pQuestion = strchr( pVarName, '?' );
	if ( ( ! pQuestion ) || (pQuestion == pVarName ) )
	{
		*pWasConditional = false;							// unconditional var
		return false;
	}
	else
	{
		bool bShouldSkip = true;
		*pWasConditional = true;
		// parse the conditional part
		char pszConditionName[256];
		V_strncpy( pszConditionName, pVarName, 1+pQuestion-pVarName );
		char const *pCond = pszConditionName;
		bool bToggle = false;
		if ( pCond[0] == '!' )
		{
			pCond++;
			bToggle = true;
		}

		if ( ! stricmp( pCond, "lowfill" ) )
		{
			bShouldSkip = !mat_reduceparticles.GetBool();
		}
		else if ( ! stricmp( pCond, "hdr" ) )
		{
			bShouldSkip = false; //( HardwareConfig()->GetHDRType() == HDR_TYPE_NONE );
		}
		else if ( ! stricmp( pCond, "srgb" ) )
		{
			bShouldSkip = ( !HardwareConfig()->UsesSRGBCorrectBlending() );
		}
		else if ( ! stricmp( pCond, "ldr" ) )
		{
			bShouldSkip = ( HardwareConfig()->GetHDRType() != HDR_TYPE_NONE );
		}
		else if ( ! stricmp( pCond, "360" ) )
		{
			bShouldSkip = !IsX360();
		}
		else if ( ! stricmp( pCond, "gameconsole" ) )
		{
			bShouldSkip = !IsGameConsole();
		}
		else
		{
			Warning( "unrecognized conditional test %s in %s\n", pVarName, GetName() );
		}

		return bShouldSkip ^ bToggle;
	}
}


//-----------------------------------------------------------------------------
// Computes the material vars for the shader
//-----------------------------------------------------------------------------
int CMaterial::ParseMaterialVars( IShader* pShader, KeyValues& keyValues, 
			KeyValues* pOverrideKeyValues, bool modelDefault, IMaterialVar** ppVars, int nFindContext )
{
	IMaterialVar* pNewVar;
	bool pOverride[256];
	bool bWasConditional[256];
	int overrideMask = 0;
	int flagMask = 0;

	memset( ppVars, 0, 256 * sizeof(IMaterialVar*) );
	memset( pOverride, 0, sizeof( pOverride ) );
	memset( bWasConditional, 0, sizeof( bWasConditional ) );

	// Create the flag var...
	// Set model mode if we fell back from a model mode shader
	int modelFlag = modelDefault ? MATERIAL_VAR_MODEL : 0;
	ppVars[FLAGS] = IMaterialVar::Create( this, "$flags", modelFlag );
	ppVars[FLAGS_DEFINED] = IMaterialVar::Create( this, "$flags_defined", modelFlag );
	ppVars[FLAGS2] = IMaterialVar::Create( this, "$flags2", 0 );
	ppVars[FLAGS_DEFINED2] = IMaterialVar::Create( this, "$flags_defined2", 0 );

	int numParams = pShader ? pShader->GetNumParams() : 0;
	int varCount = numParams;

	bool parsingOverrides = (pOverrideKeyValues != 0);
	KeyValues* pVar = pOverrideKeyValues ? pOverrideKeyValues->GetFirstSubKey() : keyValues.GetFirstSubKey();

	const char *pszMatName = pVar ? pVar->GetString() : "Unknown";

	while( pVar )
	{
		bool bProcessThisOne = true;

		bool bIsConditionalVar;
		
		const char *pszVarName = GetVarName( pVar );

		if ( (nFindContext == MATERIAL_FINDCONTEXT_ISONAMODEL) && pszVarName && pszVarName[0] )
		{
			// Prevent ignorez models
			// Should we do 'nofog' too? For now, decided not to.
			if ( Q_stristr(pszVarName,"$ignorez") )
			{
				Warning("Ignoring material flag '%s' on material '%s'.\n", pszVarName, pszMatName );
				goto nextVar;
			}
		}

		// See if the var is a flag...
		if ( 
			ShouldSkipVar( pVar, &bIsConditionalVar ) ||	// should skip?
			((pVar->GetName()[0] == '%') && (g_pShaderDevice->IsUsingGraphics()) && (!MaterialSystem()->CanUseEditorMaterials() ) ) || // is an editor var?
			ParseMaterialFlag( pVar, ppVars[FLAGS], ppVars[FLAGS_DEFINED], parsingOverrides, flagMask, overrideMask ) || // is a flag?
			ParseMaterialFlag( pVar, ppVars[FLAGS2], ppVars[FLAGS_DEFINED2], parsingOverrides, flagMask, overrideMask )
			)
			bProcessThisOne = false;

		if ( bProcessThisOne )
		{
			// See if the var is one of the shader params
			int varIdx = FindMaterialVar( pShader, pszVarName );
			
			// Check for multiply defined or overridden
			if (varIdx >= 0)
			{
				if (ppVars[varIdx] && (! bIsConditionalVar ) )
				{
					if ( !pOverride[varIdx] || parsingOverrides )
					{
						Warning("Error! Variable \"%s\" is multiply defined in material \"%s\"!\n", pVar->GetName(), GetName() );
					}
					goto nextVar;
				}
			}
			else
			{
				int i;
				for ( i = numParams; i < varCount; ++i)
				{
					Assert( ppVars[i] );
					if (!stricmp( ppVars[i]->GetName(), pVar->GetName() ))
						break;
				}
				if (i != varCount)
				{
					if ( !pOverride[i] || parsingOverrides )
					{
						Warning("Error! Variable \"%s\" is multiply defined in material \"%s\"!\n", pVar->GetName(), GetName() );
					}
					goto nextVar;
				}
			}

			// Create a material var for this dudely dude; could be zero...
			pNewVar = CreateMaterialVarFromKeyValue( this, pVar );
			if (!pNewVar) 
				goto nextVar;

			if (varIdx < 0)
			{
				varIdx = varCount++;
			}
			if ( ppVars[varIdx] )
			{
				IMaterialVar::Destroy( ppVars[varIdx] );
			}
			ppVars[varIdx] = pNewVar;
			if (parsingOverrides)
				pOverride[varIdx] = true;
			bWasConditional[varIdx] = bIsConditionalVar;

		}

nextVar:
		pVar = pVar->GetNextKey();
		if (!pVar && parsingOverrides)
		{
			pVar = keyValues.GetFirstSubKey();
			parsingOverrides = false;
		}
	}

	// Create undefined vars for all the actual material vars
	for (int i = 0; i < numParams; ++i)
	{
		if (!ppVars[i])
			ppVars[i] = IMaterialVar::Create( this, pShader->GetParamName(i) );
	}

	return varCount;
}


static KeyValues *CheckConditionalFakeShaderName( char const *pShaderName, char const *pSuffixName,
												  KeyValues *pKeyValues )
{
	KeyValues *pFallbackSection = pKeyValues->FindKey( pSuffixName );
	if (pFallbackSection)
		return pFallbackSection;

	char nameBuf[256];
	V_snprintf( nameBuf, sizeof(nameBuf), "%s_%s", pShaderName, pSuffixName );
	pFallbackSection = pKeyValues->FindKey( nameBuf );

	if (pFallbackSection)
		return pFallbackSection;

	return NULL;
}


static KeyValues *FindBuiltinFallbackBlock( char const *pShaderName, KeyValues *pKeyValues )
{
	// handle "fake" shader fallbacks which are conditional upon mode. like _hdr_dx9, etc
	if ( HardwareConfig()->GetDXSupportLevel() < 90 )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<DX90", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() < 95 )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<DX95", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() < 90 || !HardwareConfig()->SupportsPixelShaders_2_b() )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<DX90_20b", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() >= 90 && HardwareConfig()->SupportsPixelShaders_2_b() )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,">=DX90_20b", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() <= 90 )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"<=DX90", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() >= 90 )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,">=DX90", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() > 90 )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,">DX90", pKeyValues );
		if ( pRet )
			return pRet;
	}
//	if ( HardwareConfig()->GetHDRType() != HDR_TYPE_NONE )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"hdr_dx9", pKeyValues );
		if ( pRet )
			return pRet;
		pRet = CheckConditionalFakeShaderName( pShaderName,"hdr", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if( HardwareConfig()->GetHDRType() == HDR_TYPE_NONE )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"ldr", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->UsesSRGBCorrectBlending() )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"srgb", pKeyValues );
		if ( pRet )
			return pRet;
	}
	if ( HardwareConfig()->GetDXSupportLevel() >= 90 )
	{
		KeyValues *pRet = CheckConditionalFakeShaderName( pShaderName,"dx9", pKeyValues );
		if ( pRet )
			return pRet;
	}
	return NULL;
}

inline const char *MissingShaderName()
{
	return (IsWindows() && !IsEmulatingGL()) ? "Wireframe_DX8" : "Wireframe_DX9";
}

//-----------------------------------------------------------------------------
// Hooks up the shader
//-----------------------------------------------------------------------------
KeyValues* CMaterial::InitializeShader( KeyValues &keyValues, KeyValues &patchKeyValues, int nFindContext )
{
	MaterialLock_t hMaterialLock = MaterialSystem()->Lock();

	KeyValues* pCurrentFallback = &keyValues;
	KeyValues* pFallbackSection = 0;

	char szShaderName[MAX_PATH];
	char const* pShaderName = pCurrentFallback->GetName();
	if ( !pShaderName )
	{
		// I'm not quite sure how this can happen, but we'll see... 
		Warning( "Shader not specified in material %s\nUsing wireframe instead...\n", GetName() );
		Assert( 0 );
		pShaderName = MissingShaderName();
	}
	else
	{
		// can't pass a stable reference to the key values name around
		// naive leaf functions can cause KV system to re-alloc
		V_strncpy( szShaderName, pShaderName, sizeof( szShaderName ) );
		pShaderName = szShaderName;
	}

	IShader* pShader;
	IMaterialVar* ppVars[256];
	char pFallbackShaderNameBuf[256];
	char pFallbackMaterialNameBuf[256];
	int varCount = 0;
	bool modelDefault = false;

	// Keep going until there's no more fallbacks...
	while( true )
	{
		// Find the shader for this material. Note that this may not be
		// the actual shader we use due to fallbacks...
		pShader = ShaderSystem()->FindShader( pShaderName );
		if ( !pShader )
		{
			if ( g_pShaderDevice->IsUsingGraphics() )
			{
				Warning( "Error: Material \"%s\" uses unknown shader \"%s\"\n", GetName(), pShaderName );
				//hushed Assert( 0 );
			}

			pShaderName = MissingShaderName();
			pShader = ShaderSystem()->FindShader( pShaderName );

			if ( !HushAsserts() )
			{
				AssertMsg( pShader, "pShader==NULL. Shader: %s", GetName() );
			}

#ifndef DEDICATED
			if ( !pShader ) 
	 		{
#ifdef LINUX
				// Exit out here. We're running into issues where this material is returned in a horribly broken
				//  state and you wind up crashing in LockMesh() because the vertex and index buffer pointers
				//  are NULL. You can repro this by not dying here and showing the intro movie. This happens on
				//  Linux when you run from a symlink'd SteamApps directory.
				Error( "Shader '%s' for material '%s' not found.\n",
					   pCurrentFallback->GetName() ? pCurrentFallback->GetName() : pShaderName, GetName() );
#endif
	 			MaterialSystem()->Unlock( hMaterialLock );
	 			return NULL;
	 		}
#endif

		}

		bool bHasBuiltinFallbackBlock = false;
		if ( !pFallbackSection )
		{
			pFallbackSection = FindBuiltinFallbackBlock( pShaderName, &keyValues );
			if( pFallbackSection )
			{
				bHasBuiltinFallbackBlock = true;
				pFallbackSection->ChainKeyValue( &keyValues );
				pCurrentFallback = pFallbackSection;
			}
		}

		// Here we must set up all flags + material vars that the shader needs
		// because it may look at them when choosing shader fallback.
		varCount = ParseMaterialVars( pShader, keyValues, pFallbackSection, modelDefault, ppVars, nFindContext );

		if ( !pShader )
			break;

		// Make sure we set default values before the fallback is looked for
		ShaderSystem()->InitShaderParameters( pShader, ppVars, GetName() );

		// Now that the material vars are parsed, see if there's a fallback
		// But only if we're not in the tools
/*
		if (!g_pShaderAPI->IsUsingGraphics())
			break;
*/

		// Check for a fallback; if not, we're done
		pShaderName = pShader->GetFallbackShader( ppVars );
		if (!pShaderName)
		{
			break;
		}
		// Copy off the shader name, as it may be in a materialvar in the shader
		// because we're about to delete all materialvars
		Q_strncpy( pFallbackShaderNameBuf, pShaderName, 256 );
		pShaderName = pFallbackShaderNameBuf;

		// Remember the model flag if we're on dx7 or higher...
		if (HardwareConfig()->SupportsVertexAndPixelShaders())
		{
			modelDefault = ( ppVars[FLAGS]->GetIntValue() & MATERIAL_VAR_MODEL ) != 0;
		}

		// Try to get the section associated with the fallback shader
		// Then chain it to the base data so it can override the 
		// values if it wants to
		if( !bHasBuiltinFallbackBlock )
		{
			pFallbackSection = keyValues.FindKey( pShaderName );
			if (pFallbackSection)
			{
				pFallbackSection->ChainKeyValue( &keyValues );
				pCurrentFallback = pFallbackSection;
			}
		}

		// Now, blow away all of the material vars + try again...
		for (int i = 0; i < varCount; ++i)
		{
			Assert( ppVars[i] );
			IMaterialVar::Destroy( ppVars[i] );
		}

		// Check the KeyValues for '$fallbackmaterial'
		// Note we have to do this *after* we chain the keyvalues 
		// based on the fallback shader	since the names of the fallback material
		// must lie within the shader-specific block usually.
		const char *pFallbackMaterial = pCurrentFallback->GetString( "$fallbackmaterial" );
		if ( pFallbackMaterial[0] )
		{
			// Don't fallback to ourselves
			if ( Q_stricmp( GetName(), pFallbackMaterial ) )
			{
				// Gotta copy it off; clearing the keyvalues will blow the string away
				Q_strncpy( pFallbackMaterialNameBuf, pFallbackMaterial, 256 );
				keyValues.Clear();
				if( !LoadVMTFile( keyValues, patchKeyValues, pFallbackMaterialNameBuf, UsesUNCFileName(), NULL ) )
				{
					Warning( "CMaterial::PrecacheVars: error loading vmt file %s for %s\n", pFallbackMaterialNameBuf, GetName() );
					keyValues = *(((CMaterial *)g_pErrorMaterial)->m_pVMTKeyValues);
				}
			}
			else
			{
				Warning( "CMaterial::PrecacheVars: fallback material for vmt file %s is itself!\n", GetName() );
				keyValues = *(((CMaterial *)g_pErrorMaterial)->m_pVMTKeyValues);
			}
			pCurrentFallback = &keyValues;
			pFallbackSection = NULL;

			// I'm not quite sure how this can happen, but we'll see... 
			pShaderName = pCurrentFallback->GetName();
			if (!pShaderName)
			{
				Warning("Shader not specified in material %s (fallback %s)\nUsing wireframe instead...\n", GetName(), pFallbackMaterialNameBuf );
				pShaderName = MissingShaderName();
			}
		}
	}

	// Store off the shader
	m_pShader = pShader;

	// Store off the material vars + flags
	m_VarCount = varCount;
	m_pShaderParams = (IMaterialVar**)malloc( varCount * sizeof(IMaterialVar*) );
	memcpy( m_pShaderParams, ppVars, varCount * sizeof(IMaterialVar*) );

#ifdef _DEBUG
	for (int i = 0; i < varCount; ++i)
	{
		Assert( ppVars[i] );
	}
#endif

	MaterialSystem()->Unlock( hMaterialLock );
	return pCurrentFallback;
}

//-----------------------------------------------------------------------------
// Gets the texturemap size
//-----------------------------------------------------------------------------

void CMaterial::PrecacheMappingDimensions( )
{
	// Cache mapping width and mapping height
	if (!m_representativeTexture)
	{
#ifdef PARANOID
		Warning( "No representative texture on material: \"%s\"\n", GetName() );
#endif
		m_MappingWidth = 64;
		m_MappingHeight = 64;
	}
	else
	{
		m_MappingWidth = m_representativeTexture->GetMappingWidth();
		m_MappingHeight = m_representativeTexture->GetMappingHeight();
	}
}


//-----------------------------------------------------------------------------
// Initialize the state snapshot
//-----------------------------------------------------------------------------
bool CMaterial::InitializeStateSnapshots()
{
	if (IsPrecached())
	{
		if ( MaterialSystem()->GetCurrentMaterial() == this)
		{
			g_pShaderAPI->FlushBufferedPrimitives();
		}

		// Default state
		CleanUpStateSnapshots();

		if ( m_pShader && !ShaderSystem()->InitRenderState( m_pShader, m_VarCount, m_pShaderParams, &m_ShaderRenderState, GetName() ))
		{
			m_Flags &= ~MATERIAL_VALID_RENDERSTATE;
			return false;
		}

		m_Flags |= MATERIAL_VALID_RENDERSTATE;
	}

	return true;
}

void CMaterial::CleanUpStateSnapshots()
{
	if (IsValidRenderState())
	{
		ShaderSystem()->CleanupRenderState(&m_ShaderRenderState);
		// -- THIS CANNOT BE HERE: m_Flags &= ~MATERIAL_VALID_RENDERSTATE;
		// -- because it will cause a crash when main thread asks for material
		// -- sort group it can temporarily see material in invalid render state
		// -- and crash in DecalSurfaceAdd(msurface2_t*, int)
	}
}


//-----------------------------------------------------------------------------
// This sets up a debugging/error shader...
//-----------------------------------------------------------------------------
void CMaterial::SetupErrorShader()
{
	// Preserve the model flags
	int flags = 0;
	if ( m_pShaderParams && m_pShaderParams[FLAGS] )
	{
		flags = (m_pShaderParams[FLAGS]->GetIntValue() & MATERIAL_VAR_MODEL);
	}

	CleanUpShaderParams();
	CleanUpMaterialProxy();

	// We had a failure; replace it with a valid shader...

	m_pShader = ShaderSystem()->FindShader( MissingShaderName() );
	Assert( m_pShader );

	// Create undefined vars for all the actual material vars
	m_VarCount = m_pShader->GetNumParams();
	m_pShaderParams = (IMaterialVar**)malloc( m_VarCount * sizeof(IMaterialVar*) );

	for (int i = 0; i < m_VarCount; ++i)
	{
		m_pShaderParams[i] = IMaterialVar::Create( this, m_pShader->GetParamName(i) );
	}

	// Store the model flags
	SetMaterialVarFlags( flags, true );

	// Set the default values
	ShaderSystem()->InitShaderParameters( m_pShader, m_pShaderParams, "Error" );

	// Invokes the SHADER_INIT block in the various shaders,
	ShaderSystem()->InitShaderInstance( m_pShader, m_pShaderParams, "Error", GetTextureGroupName() );

#ifdef DBGFLAG_ASSERT
	bool ok = 
#endif
		InitializeStateSnapshots();

	m_QueueFriendlyVersion.UpdateToRealTime();

	Assert(ok);
}


//-----------------------------------------------------------------------------
// This computes the state snapshots for this material
//-----------------------------------------------------------------------------
void CMaterial::RecomputeStateSnapshots()
{
	CMatCallQueue *pCallQueue = MaterialSystem()->GetRenderCallQueue();
	if ( pCallQueue )
	{
		pCallQueue->QueueCall( this, &CMaterial::RecomputeStateSnapshots );
		return;
	}

	bool ok = InitializeStateSnapshots();

	// compute the state snapshots
	if (!ok)
	{
		SetupErrorShader();
	}
}


//-----------------------------------------------------------------------------
// Are we valid
//-----------------------------------------------------------------------------
inline bool CMaterial::IsValidRenderState() const
{
	return (m_Flags & MATERIAL_VALID_RENDERSTATE) != 0;
}


//-----------------------------------------------------------------------------
// Gets/sets material var flags
//-----------------------------------------------------------------------------
inline int CMaterial::GetMaterialVarFlags() const
{
	if ( m_pShaderParams && m_pShaderParams[FLAGS] )
	{
		return m_pShaderParams[FLAGS]->GetIntValueFast();
	}
	else
	{
		return 0;
	}
}

inline void CMaterial::SetMaterialVarFlags( int flags, bool on )
{
	if ( !m_pShaderParams )
	{
		Assert( 0 );	// are we hanging onto a material that has been cleaned up or isn't ready?
		return;
	}

	if (on)
	{
		m_pShaderParams[FLAGS]->SetIntValue( GetMaterialVarFlags() | flags );
	}
	else
	{
		m_pShaderParams[FLAGS]->SetIntValue( GetMaterialVarFlags() & (~flags) );
	}

	// Mark it as being defined...
	m_pShaderParams[FLAGS_DEFINED]->SetIntValue( m_pShaderParams[FLAGS_DEFINED]->GetIntValueFast() | flags );
}

inline int CMaterial::GetMaterialVarFlags2() const
{
	if ( m_pShaderParams && m_VarCount > FLAGS2 && m_pShaderParams[FLAGS2] )
	{
		return m_pShaderParams[FLAGS2]->GetIntValueFast();
	}
	else
	{
		return 0;
	}
}

inline void CMaterial::SetMaterialVarFlags2( int flags, bool on )
{
	if ( m_pShaderParams && m_VarCount > FLAGS2 && m_pShaderParams[FLAGS2] )
	{
		if (on)
			m_pShaderParams[FLAGS2]->SetIntValue( GetMaterialVarFlags2() | flags );
		else
			m_pShaderParams[FLAGS2]->SetIntValue( GetMaterialVarFlags2() & (~flags) );
	}

	if ( m_pShaderParams && m_VarCount > FLAGS_DEFINED2 && m_pShaderParams[FLAGS_DEFINED2] )
	{
		// Mark it as being defined...
		m_pShaderParams[FLAGS_DEFINED2]->SetIntValue( 
			m_pShaderParams[FLAGS_DEFINED2]->GetIntValueFast() | flags );
	}
}


//-----------------------------------------------------------------------------
// Gets the morph format
//-----------------------------------------------------------------------------
MorphFormat_t CMaterial::GetMorphFormat() const
{
	const_cast<CMaterial*>(this)->Precache();
	Assert( IsValidRenderState() );
	return m_ShaderRenderState.m_MorphFormat;
}

//-----------------------------------------------------------------------------
// Gets the vertex format
//-----------------------------------------------------------------------------
VertexFormat_t CMaterial::GetVertexFormat() const
{
	Assert( IsValidRenderState() );
	return m_ShaderRenderState.m_VertexFormat;
}

VertexFormat_t CMaterial::GetVertexUsage() const
{
	Assert( IsValidRenderState() );
	return m_ShaderRenderState.m_VertexUsage;
}

bool CMaterial::PerformDebugTrace() const
{
	return IsValidRenderState() && ((GetMaterialVarFlags() & MATERIAL_VAR_DEBUG ) != 0);
}


//-----------------------------------------------------------------------------
// Are we suppressed?
//-----------------------------------------------------------------------------
bool  CMaterial::IsSuppressed() const
{
	if ( !IsValidRenderState() )
		return true;

	return ((GetMaterialVarFlags() & MATERIAL_VAR_NO_DRAW) != 0);
}

void CMaterial::ToggleSuppression()
{
	if (IsValidRenderState())
	{
		if ((GetMaterialVarFlags() & MATERIAL_VAR_NO_DEBUG_OVERRIDE) != 0)
			return;

		SetMaterialVarFlags( MATERIAL_VAR_NO_DRAW, 
			(GetMaterialVarFlags() & MATERIAL_VAR_NO_DRAW) == 0 );
	}
}

void CMaterial::ToggleDebugTrace()
{
	if (IsValidRenderState())
	{
		SetMaterialVarFlags( MATERIAL_VAR_DEBUG, 
			(GetMaterialVarFlags() & MATERIAL_VAR_DEBUG) == 0 );
	}
}

//-----------------------------------------------------------------------------
// Can we override this material in debug?
//-----------------------------------------------------------------------------

bool CMaterial::NoDebugOverride() const
{
	return IsValidRenderState() && (GetMaterialVarFlags() & MATERIAL_VAR_NO_DEBUG_OVERRIDE) != 0;
}


//-----------------------------------------------------------------------------
// Material Var flags
//-----------------------------------------------------------------------------
void CMaterial::SetMaterialVarFlag( MaterialVarFlags_t flag, bool on )
{
	CMatCallQueue *pCallQueue = MaterialSystem()->GetRenderCallQueue();
	if ( pCallQueue )
	{
		pCallQueue->QueueCall( this, &CMaterial::SetMaterialVarFlag, flag, on );
		return;
	}

	bool oldOn = (GetMaterialVarFlags( ) & flag) != 0;
	if (oldOn != on)
	{
		SetMaterialVarFlags( flag, on );

		// This is going to be called from client code; recompute snapshots!
		RecomputeStateSnapshots();
	}
}

bool CMaterial::GetMaterialVarFlag( MaterialVarFlags_t flag ) const
{
	return (GetMaterialVarFlags() & flag) != 0;
}


//-----------------------------------------------------------------------------
// Do we use the env_cubemap entity to get cubemaps from the level?
//-----------------------------------------------------------------------------
bool CMaterial::UsesEnvCubemap( void )
{
	Precache();

	if( !m_pShader )
	{
		if ( !HushAsserts() )
		{
			AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
		}
		return false;
	}

	Assert( m_pShaderParams );
	return IsFlag2Set( m_pShaderParams, MATERIAL_VAR2_USES_ENV_CUBEMAP );
}


//-----------------------------------------------------------------------------
// Do we need a tangent space at the vertex level?
//-----------------------------------------------------------------------------
bool CMaterial::NeedsTangentSpace( void )
{
	Precache();

	if( !m_pShader )
	{
		if ( !HushAsserts() )
		{
			AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
		}
		return false;
	}

	Assert( m_pShaderParams );
	return IsFlag2Set( m_pShaderParams, MATERIAL_VAR2_NEEDS_TANGENT_SPACES );
}

bool CMaterial::NeedsPowerOfTwoFrameBufferTexture( bool bCheckSpecificToThisFrame )
{
	PrecacheVars();
	if( !m_pShader )
	{
		if ( !HushAsserts() )
		{
			AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
		}
		return false;
	}

	Assert( m_pShaderParams );
	return m_pShader->NeedsPowerOfTwoFrameBufferTexture( m_pShaderParams, bCheckSpecificToThisFrame );
}

bool CMaterial::NeedsFullFrameBufferTexture( bool bCheckSpecificToThisFrame )
{
	PrecacheVars();
	if( !m_pShader )
	{
		if ( !HushAsserts() )
		{
			AssertMsg( m_pShader, "m_pShader==NULL. Shader: %s", GetName() );
		}
		return false;
	}

	Assert( m_pShaderParams );
	return m_pShader->NeedsFullFrameBufferTexture( m_pShaderParams, bCheckSpecificToThisFrame );
}

// GR - Is lightmap alpha needed?
bool CMaterial::NeedsLightmapBlendAlpha( void )
{
	Precache();
	return (GetMaterialVarFlags2() & MATERIAL_VAR2_BLEND_WITH_LIGHTMAP_ALPHA ) != 0;
}

//-----------------------------------------------------------------------------
// Do we need software skinning?
//-----------------------------------------------------------------------------
bool CMaterial::NeedsSoftwareSkinning( void )
{
	Precache();
	Assert( m_pShader );
	if( !m_pShader )
	{
		return false;
	}

	Assert( m_pShaderParams );
	return IsFlagSet( m_pShaderParams, MATERIAL_VAR_NEEDS_SOFTWARE_SKINNING );
}


//-----------------------------------------------------------------------------
// Do we need software lighting?
//-----------------------------------------------------------------------------
bool CMaterial::NeedsSoftwareLighting( void )
{
	Precache();
	Assert( m_pShader );
	if( !m_pShader )
	{
		return false;
	}
	Assert( m_pShaderParams );
	return IsFlag2Set( m_pShaderParams, MATERIAL_VAR2_NEEDS_SOFTWARE_LIGHTING );
}

//-----------------------------------------------------------------------------
// Alpha/color modulation
//-----------------------------------------------------------------------------
void CMaterial::AlphaModulate( float alpha )
{
	Precache();
	if ( m_VarCount > ALPHA )
		m_pShaderParams[ALPHA]->SetFloatValue(alpha);
}

void CMaterial::ColorModulate( float r, float g, float b )
{
	Precache();
	if ( m_VarCount > COLOR )
		m_pShaderParams[COLOR]->SetVecValue( r, g, b );
}

float CMaterial::GetAlphaModulation()
{
	Precache();
	if ( m_VarCount > ALPHA )
		return m_pShaderParams[ALPHA]->GetFloatValue();
	return 0.0f;
}

void CMaterial::GetColorModulation( float *r, float *g, float *b )
{
	Precache();

	float pColor[3] = { 0.0f, 0.0f, 0.0f };
	if ( m_VarCount > COLOR )
		m_pShaderParams[COLOR]->GetVecValue( pColor, 3 );
	*r = pColor[0];
	*g = pColor[1];
	*b = pColor[2];
}


//-----------------------------------------------------------------------------
// Do we use fog?
//-----------------------------------------------------------------------------
bool CMaterial::UseFog() const
{
	Assert( m_VarCount > 0 );
	return IsValidRenderState() && ((GetMaterialVarFlags() & MATERIAL_VAR_NOFOG) == 0);
}


//-----------------------------------------------------------------------------
// diffuse bump?
//-----------------------------------------------------------------------------
bool CMaterial::IsUsingDiffuseBumpedLighting() const
{
	return (GetMaterialVarFlags2() & MATERIAL_VAR2_LIGHTING_BUMPED_LIGHTMAP ) != 0;
}


//-----------------------------------------------------------------------------
// lightmap?
//-----------------------------------------------------------------------------
bool CMaterial::IsUsingLightmap() const
{
	return (GetMaterialVarFlags2() & MATERIAL_VAR2_LIGHTING_LIGHTMAP ) != 0;
}

bool CMaterial::IsManuallyCreated() const
{
	return (m_Flags & MATERIAL_IS_MANUALLY_CREATED) != 0;
}

bool CMaterial::UsesUNCFileName() const
{
	return (m_Flags & MATERIAL_USES_UNC_FILENAME) != 0;
}


void CMaterial::DecideShouldReloadFromWhitelist( IFileList *pFilesToReload )
{
	m_bShouldReloadFromWhitelist = false;
	if ( IsManuallyCreated() || !IsPrecached() )
		return;

	// Materials loaded with an absolute pathname are usually debug materials.
	if ( V_IsAbsolutePath( GetName() ) )
		return;

	char vmtFilename[MAX_PATH];
	V_ComposeFileName( "materials", GetName(), vmtFilename, sizeof( vmtFilename ) );
	V_strncat( vmtFilename, ".vmt", sizeof( vmtFilename ) );

	// Check if either this file or any of the files it included need to be reloaded.
	bool bShouldReload = pFilesToReload->IsFileInList( vmtFilename );
	if ( !bShouldReload )
	{
		for ( int i=0; i < m_VMTIncludes.Count(); i++ )
		{
			g_pFullFileSystem->String( m_VMTIncludes[i], vmtFilename, sizeof( vmtFilename ) );
			if ( pFilesToReload->IsFileInList( vmtFilename ) )
			{
				bShouldReload = true;
				break;
			}
		}
	}

	m_bShouldReloadFromWhitelist = bShouldReload;
}

void CMaterial::ReloadFromWhitelistIfMarked()
{
	if ( !m_bShouldReloadFromWhitelist )
		return;

	#ifdef PURE_SERVER_DEBUG_SPEW
	{
		char vmtFilename[MAX_PATH];
		V_ComposeFileName( "materials", GetName(), vmtFilename, sizeof( vmtFilename ) );
		V_strncat( vmtFilename, ".vmt", sizeof( vmtFilename ) );
		Msg( "Reloading %s due to pure server whitelist change\n", GetName() );
	}
	#endif

	Uncache();
	Precache();
	if ( !GetShader() )
	{
		// We can get in here if we previously loaded this material off disk and now the whitelist 
		// says to get it out of Steam but it's not in Steam. So just setup a wireframe thingy
		// to draw the material with.
		m_Flags |= MATERIAL_IS_PRECACHED | MATERIAL_VARS_IS_PRECACHED;
		#if DEBUG
		if (IsOSX())
		{
			printf("\n ##### CMaterial::ReloadFromWhitelistIfMarked: GetShader failed on %s, calling SetupErrorShader", m_pDebugName );
		}
		#endif
		
		SetupErrorShader();
	}
}

bool CMaterial::WasReloadedFromWhitelist()
{
	return m_bShouldReloadFromWhitelist;
}

//-----------------------------------------------------------------------------
// Loads the material vars
//-----------------------------------------------------------------------------
bool CMaterial::PrecacheVars( KeyValues *pVMTKeyValues, KeyValues *pPatchKeyValues, CUtlVector<FileNameHandle_t> *pIncludes, int nFindContext )
{
	// We should get both parameters or neither
	Assert( ( pVMTKeyValues == NULL ) ? ( pPatchKeyValues == NULL ) : ( pPatchKeyValues != NULL ) );

	// Don't bother if we're already precached
	if( IsPrecachedVars() )
		return true;

	if ( pIncludes )
		m_VMTIncludes = *pIncludes;
	else
		m_VMTIncludes.Purge();

	MaterialLock_t hMaterialLock = MaterialSystem()->Lock();

	bool bOk = false;
	bool bError = false;
	KeyValues *vmtKeyValues = NULL;
	KeyValues *patchKeyValues = NULL;
	if ( m_pVMTKeyValues )
	{
		// Use the procedural KeyValues
		vmtKeyValues = m_pVMTKeyValues;
		patchKeyValues = new KeyValues( "vmt_patches" );

		// The caller should not be passing in KeyValues if we have procedural ones
		Assert( ( pVMTKeyValues == NULL ) && ( pPatchKeyValues == NULL ) );
	}
	else if ( pVMTKeyValues )
	{
		// Use the passed-in (already-loaded) KeyValues
		vmtKeyValues = pVMTKeyValues;
		patchKeyValues = pPatchKeyValues;
	}
	else
	{
		m_VMTIncludes.Purge();

		// load data from the vmt file
		vmtKeyValues = new KeyValues( "vmt" );
		patchKeyValues = new KeyValues( "vmt_patches" );
		if( !LoadVMTFile( *vmtKeyValues, *patchKeyValues, GetName(), UsesUNCFileName(), &m_VMTIncludes ) )
		{
			Warning( "CMaterial::PrecacheVars: error loading vmt file for %s\n", GetName() );
			bError = true;
		}
	}

	if ( ! bError )
	{
		// Needed to prevent re-entrancy
		m_Flags |= MATERIAL_VARS_IS_PRECACHED;

		// Create shader and the material vars...
		KeyValues *pFallbackKeyValues = InitializeShader( *vmtKeyValues, *patchKeyValues, nFindContext );
		if ( pFallbackKeyValues )
		{
			// Gotta initialize the proxies too, using the fallback proxies
			InitializeMaterialProxy(pFallbackKeyValues);
			bOk = true;
		}
	}

	// Clean up
	if ( ( vmtKeyValues != m_pVMTKeyValues ) && ( vmtKeyValues != pVMTKeyValues ) )
	{
		vmtKeyValues->deleteThis();
	}
	if ( patchKeyValues != pPatchKeyValues )
	{
		patchKeyValues->deleteThis();
	}

	MaterialSystem()->Unlock( hMaterialLock );

	return bOk;
}


//-----------------------------------------------------------------------------
// Loads the material info from the VMT file
//-----------------------------------------------------------------------------
void CMaterial::Precache()
{
	// Don't bother if we're already precached
	if ( IsPrecached() )
		return;

	// load data from the vmt file
	if ( !PrecacheVars() )
		return;

	MaterialLock_t hMaterialLock = MaterialSystem()->Lock();

	m_Flags |= MATERIAL_IS_PRECACHED;

	// Invokes the SHADER_INIT block in the various shaders,
	if ( m_pShader ) 
	{
		ShaderSystem()->InitShaderInstance( m_pShader, m_pShaderParams, GetName(), GetTextureGroupName() );
	}

	// compute the state snapshots
	RecomputeStateSnapshots();

	FindRepresentativeTexture();

	// Reads in the texture width and height from the material var
	PrecacheMappingDimensions();

	Assert( IsValidRenderState() );

	if( m_pShaderParams )
		m_QueueFriendlyVersion.UpdateToRealTime();

	MaterialSystem()->Unlock( hMaterialLock );
}


//-----------------------------------------------------------------------------
// Unloads the material data from memory
//-----------------------------------------------------------------------------
void CMaterial::Uncache( bool bPreserveVars )
{
	MaterialLock_t hMaterialLock = MaterialSystem()->Lock();

	// Don't bother if we're not cached
	if ( IsPrecached() )
	{
		// Clean up the state snapshots
		CleanUpStateSnapshots();
		m_Flags &= ~MATERIAL_VALID_RENDERSTATE;
		m_Flags &= ~MATERIAL_IS_PRECACHED;
	}

	if ( !bPreserveVars )
	{
		if ( IsPrecachedVars() )
		{
			// Clean up the shader + params
			CleanUpShaderParams();
			m_pShader = 0;

			// Clean up the material proxy
			CleanUpMaterialProxy();

			m_Flags &= ~MATERIAL_VARS_IS_PRECACHED;
		}
	}

	MaterialSystem()->Unlock( hMaterialLock );

	// Whether we just now did it, or we were already unloaded,
	// notify the pure system that the material is unloaded,
	// so it doesn't caue us to fail sv_pure checks
	if ( ( m_Flags & ( MATERIAL_VARS_IS_PRECACHED | MATERIAL_IS_MANUALLY_CREATED | MATERIAL_USES_UNC_FILENAME ) ) == 0 )
	{
		char szName[ MAX_PATH ];
		V_sprintf_safe( szName, "materials/%s.vmt", GetName() );
		g_pFullFileSystem->NotifyFileUnloaded( szName, "GAME" );
	}
}

//-----------------------------------------------------------------------------
// reload all textures used by this materals
//-----------------------------------------------------------------------------
void CMaterial::ReloadTextures( void )
{
	Precache();
	int i;
	int nParams = ShaderParamCount();
	IMaterialVar **ppVars = GetShaderParams();
	for( i = 0; i < nParams; i++ )
	{
		if( ppVars[i] )
		{
			if( ppVars[i]->IsTexture() )
			{
				ITextureInternal *pTexture = ( ITextureInternal * )ppVars[i]->GetTextureValue();
				pTexture->Download();
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Meant to be used with materials created using CreateMaterial
// It updates the materials to reflect the current values stored in the material vars
//-----------------------------------------------------------------------------
void CMaterial::Refresh()
{
	if ( g_pShaderDevice->IsUsingGraphics() )
	{
		Uncache();
		Precache();
	}
}

void CMaterial::RefreshPreservingMaterialVars()
{
	if ( g_pShaderDevice->IsUsingGraphics() )
	{
		Uncache( true );
		Precache();
	}
}

//-----------------------------------------------------------------------------
// Gets the material name
//-----------------------------------------------------------------------------
char const* CMaterial::GetName() const
{
	return m_Name.String();
}


char const* CMaterial::GetTextureGroupName() const
{
	return m_TextureGroupName.String();
}


//-----------------------------------------------------------------------------
// Material dimensions
//-----------------------------------------------------------------------------
int	CMaterial::GetMappingWidth( )
{
	Precache();
	return m_MappingWidth;
}

int	CMaterial::GetMappingHeight( )
{
	Precache();
	return m_MappingHeight;
}


//-----------------------------------------------------------------------------
// Animated material info
//-----------------------------------------------------------------------------

int CMaterial::GetNumAnimationFrames( )
{
	Precache();
	if( m_representativeTexture )
	{
		return m_representativeTexture->GetNumAnimationFrames();
	}
	else
	{
#ifndef POSIX
		Warning( "CMaterial::GetNumAnimationFrames:\nno representative texture for material %s\n", GetName() );
#endif
		return 1;
	}
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterial::GetMaterialOffset( float *pOffset )
{
	// Identity.
	pOffset[0] = 0.0f;
	pOffset[1] = 0.0f;
}

//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CMaterial::GetMaterialScale( float *pScale )
{
	// Identity.
	pScale[0] = 1.0f;
	pScale[1] = 1.0f;
}

//-----------------------------------------------------------------------------
// Reference count
//-----------------------------------------------------------------------------
void CMaterial::IncrementReferenceCount( )
{
	++m_RefCount;
}

void CMaterial::DecrementReferenceCount( )
{
	--m_RefCount;
}

int CMaterial::GetReferenceCount( )	const
{
	return m_RefCount;
}

//-----------------------------------------------------------------------------
// Sets the shader associated with the material
//-----------------------------------------------------------------------------
void CMaterial::SetShader( const char *pShaderName )
{
	Assert( pShaderName );

	int i;
	IShader* pShader;
	IMaterialVar* ppVars[256];
	int iVarCount = 0;

	// Clean up existing state
	Uncache();

	// Keep going until there's no more fallbacks...
	while( true )
	{
		// Find the shader for this material. Note that this may not be
		// the actual shader we use due to fallbacks...
		pShader = ShaderSystem()->FindShader( pShaderName );
		if (!pShader)
		{
			// Couldn't find the shader we wanted to use; it's not defined...
			Warning( "SetShader: Couldn't find shader %s for material %s!\n", pShaderName, GetName() );
			pShaderName = MissingShaderName();
			pShader = ShaderSystem()->FindShader( pShaderName );
			Assert( pShader );
		}

		// Create undefined vars for all the actual material vars
		iVarCount = pShader->GetNumParams();
		for (i = 0; i < iVarCount; ++i)
		{
			ppVars[i] = IMaterialVar::Create( this, pShader->GetParamName(i) );
		}

		// Make sure we set default values before the fallback is looked for
		ShaderSystem()->InitShaderParameters( pShader, ppVars, pShaderName );

		// Now that the material vars are parsed, see if there's a fallback
		// But only if we're not in the tools
		if (!g_pShaderDevice->IsUsingGraphics())
			break;

		// Check for a fallback; if not, we're done
		pShaderName = pShader->GetFallbackShader( ppVars );
		if (!pShaderName)
			break;

		// Now, blow away all of the material vars + try again...
		for (i = 0; i < iVarCount; ++i)
		{
			Assert( ppVars[i] );
			IMaterialVar::Destroy( ppVars[i] );
		}
	}

	// Store off the shader
	m_pShader = pShader;

	// Store off the material vars + flags
	m_VarCount = iVarCount;
	m_pShaderParams = (IMaterialVar**)malloc( iVarCount * sizeof(IMaterialVar*) );
	memcpy( m_pShaderParams, ppVars, iVarCount * sizeof(IMaterialVar*) );

	// Invokes the SHADER_INIT block in the various shaders,
	ShaderSystem()->InitShaderInstance( m_pShader, m_pShaderParams, GetName(), GetTextureGroupName() );

	// Precache our initial state...
	// NOTE: What happens here for textures???

	// Pretend that we precached our material vars; we certainly don't have any!
	m_Flags |= MATERIAL_VARS_IS_PRECACHED;

	// NOTE: The caller has to call 'Refresh' for the shader to be ready...
}

const char *CMaterial::GetShaderName() const
{
	const_cast< CMaterial* >( this )->PrecacheVars();
	return m_pShader ? m_pShader->GetName() : "shader_error";
}


//-----------------------------------------------------------------------------
// Enumeration ID
//-----------------------------------------------------------------------------
int CMaterial::GetEnumerationID( ) const
{
	return m_iEnumerationID;
}

void CMaterial::SetEnumerationID( int id )
{
	m_iEnumerationID = id;
}

//-----------------------------------------------------------------------------
// Preview image
//-----------------------------------------------------------------------------
char const* CMaterial::GetPreviewImageName( void )
{
	if ( IsConsole() )
	{
		// not supporting
		return NULL;
	}

	PrecacheVars();

	bool found;
	IMaterialVar *pRepresentativeTextureVar;
	
	FindVar( "%noToolTexture", &found, false );
	if (found)
		return NULL;

	pRepresentativeTextureVar = FindVar( "%toolTexture", &found, false );
	if( found )
	{
		if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_STRING )
			return pRepresentativeTextureVar->GetStringValue();
		if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
			return pRepresentativeTextureVar->GetTextureValue()->GetName();
	}
	pRepresentativeTextureVar = FindVar( "$baseTexture", &found, false );
	if( found )
	{
		if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_STRING )
			return pRepresentativeTextureVar->GetStringValue();
		if (pRepresentativeTextureVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
			return pRepresentativeTextureVar->GetTextureValue()->GetName();
	}
	return GetName();
}

char const* CMaterial::GetPreviewImageFileName( void ) const
{
	char const* pName = const_cast<CMaterial*>(this)->GetPreviewImageName();
	if( !pName )
		return NULL;

	static char vtfFilename[MATERIAL_MAX_PATH];
	if( Q_strlen( pName ) >= MATERIAL_MAX_PATH - 5 )
	{
		Warning( "MATERIAL_MAX_PATH to short for %s.vtf\n", pName );
		return NULL;
	}

	if ( !UsesUNCFileName() )
	{
		Q_snprintf( vtfFilename, sizeof( vtfFilename ), "materials/%s.vtf", pName );
	}
	else
	{
		Q_snprintf( vtfFilename, sizeof( vtfFilename ), "%s.vtf", pName );
	}

	return vtfFilename;
}

PreviewImageRetVal_t CMaterial::GetPreviewImageProperties( int *width, int *height, 
				 		ImageFormat *imageFormat, bool* isTranslucent ) const
{	
	char const* pFileName = GetPreviewImageFileName();
	if ( IsX360() || !pFileName )
	{
		*width = *height = 0;
		*imageFormat = IMAGE_FORMAT_RGBA8888;
		*isTranslucent = false;
		return MATERIAL_NO_PREVIEW_IMAGE;
	}

	int nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION );
	unsigned char *pMem = (unsigned char *)stackalloc( nHeaderSize );
	CUtlBuffer buf( pMem, nHeaderSize );
	if( !g_pFullFileSystem->ReadFile( pFileName, NULL, buf, nHeaderSize ) )
	{
		Warning( "\"%s\" - \"%s\": cached version doesn't exist\n", GetName(), pFileName );
		return MATERIAL_PREVIEW_IMAGE_BAD;
	}
	
	IVTFTexture *pVTFTexture = CreateVTFTexture();
	if (!pVTFTexture->Unserialize( buf, true ))
	{
		Warning( "Error reading material \"%s\"\n", pFileName );
		DestroyVTFTexture( pVTFTexture );
		return MATERIAL_PREVIEW_IMAGE_BAD;
	}

	*width = pVTFTexture->Width();
	*height = pVTFTexture->Height();
	*imageFormat = pVTFTexture->Format();
	*isTranslucent = (pVTFTexture->Flags() & (TEXTUREFLAGS_ONEBITALPHA | TEXTUREFLAGS_EIGHTBITALPHA)) != 0;
	DestroyVTFTexture( pVTFTexture );
	return MATERIAL_PREVIEW_IMAGE_OK;
}

PreviewImageRetVal_t CMaterial::GetPreviewImage( unsigned char *pData, int width, int height,
					             ImageFormat imageFormat ) const
{
	CUtlBuffer buf;
	int nHeaderSize;
	int nImageOffset, nImageSize;

	char const* pFileName = GetPreviewImageFileName();
	if ( IsX360() || !pFileName )
	{
		return MATERIAL_NO_PREVIEW_IMAGE;
	}

	IVTFTexture *pVTFTexture = CreateVTFTexture();
	FileHandle_t fileHandle = g_pFullFileSystem->Open( pFileName, "rb" );
	if( !fileHandle )
	{
		Warning( "\"%s\": cached version doesn't exist\n", pFileName );
		goto fail;
	}

	nHeaderSize = VTFFileHeaderSize( VTF_MAJOR_VERSION );
	buf.EnsureCapacity( nHeaderSize );
	
	// read the header first.. it's faster!!
	int nBytesRead; // GCC won't let this be initialized right away
	nBytesRead = g_pFullFileSystem->Read( buf.Base(), nHeaderSize, fileHandle );
	buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );
		
	// Unserialize the header
	if (!pVTFTexture->Unserialize( buf, true ))
	{
		Warning( "Error reading material \"%s\"\n", pFileName );
		goto fail;
	}
		
	// FIXME: Make sure the preview image size requested is the same
	// size as mip level 0 of the texture
	Assert( (width == pVTFTexture->Width()) && (height == pVTFTexture->Height()) );
		
	// Determine where in the file to start reading (frame 0, face 0, mip 0)
	pVTFTexture->ImageFileInfo( 0, 0, 0, &nImageOffset, &nImageSize );

	if ( nImageSize == 0 )
	{
		Warning( "Couldn't determine offset and size of material \"%s\"\n", pFileName );
		goto fail;
	}
		
	// Prep the utlbuffer for reading
	buf.EnsureCapacity( nImageSize );
	buf.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
		
	// Read in the bits at the specified location
	g_pFullFileSystem->Seek( fileHandle, nImageOffset, FILESYSTEM_SEEK_HEAD );
	g_pFullFileSystem->Read( buf.Base(), nImageSize, fileHandle );
	g_pFullFileSystem->Close( fileHandle );
		
	// Convert from the format read in to the requested format
	ImageLoader::ConvertImageFormat( (unsigned char*)buf.Base(), pVTFTexture->Format(), 
		pData, imageFormat, width, height );

	DestroyVTFTexture( pVTFTexture );
	return MATERIAL_PREVIEW_IMAGE_OK;

fail:
	if( fileHandle )
	{
		g_pFullFileSystem->Close( fileHandle );
	}
	int nSize = ImageLoader::GetMemRequired( width, height, 1, imageFormat, false );
	memset( pData, 0xff, nSize );
	DestroyVTFTexture( pVTFTexture );
	return MATERIAL_PREVIEW_IMAGE_BAD;
}

//-----------------------------------------------------------------------------
// Material variables
//-----------------------------------------------------------------------------
IMaterialVar *CMaterial::FindVar( char const *pVarName, bool *pFound, bool complain )
{
	PrecacheVars();

	// FIXME: Could look for flags here too...

	MaterialVarSym_t sym = IMaterialVar::FindSymbol(pVarName);
	if ( sym != UTL_INVAL_SYMBOL )
	{
		for (int i = m_VarCount; --i >= 0; )
		{
			if (m_pShaderParams[i]->GetNameAsSymbol() == sym)
			{
				if( pFound )
					*pFound = true;					  
				return m_pShaderParams[i];
			}
		}
	}

	if( pFound )
		*pFound = false;

	if( complain )
	{
		static int complainCount = 0;
		if( complainCount < 100 )
		{
			Warning( "No such variable \"%s\" for material \"%s\"\n", pVarName, GetName() );
			complainCount++;
		}
	}
	return GetDummyVariable();
}

struct tokencache_t
{
	unsigned short symbol;
	unsigned char varIndex;
	unsigned char cached;
};

IMaterialVar *CMaterial::FindVarFast( char const *pVarName, unsigned int *pCacheData )
{
	tokencache_t *pToken = reinterpret_cast<tokencache_t *>(pCacheData);
	PrecacheVars();

	if ( pToken->cached )
	{
		if ( pToken->varIndex < m_VarCount && m_pShaderParams[pToken->varIndex]->GetNameAsSymbol() == pToken->symbol )
			return m_pShaderParams[pToken->varIndex];
		// FIXME: Could look for flags here too...
		if ( !IMaterialVar::SymbolMatches(pVarName, pToken->symbol) )
		{
			pToken->symbol = IMaterialVar::FindSymbol(pVarName);
		}
	}
	else
	{
		pToken->cached = true;
		pToken->symbol = IMaterialVar::FindSymbol(pVarName);
	}

	if ( pToken->symbol != UTL_INVAL_SYMBOL )
	{
		for (int i = m_VarCount; --i >= 0; )
		{
			if (m_pShaderParams[i]->GetNameAsSymbol() == pToken->symbol)
			{
				pToken->varIndex = i;
				return m_pShaderParams[i];
			}
		}
	}
	return NULL;
}

//-----------------------------------------------------------------------------
// Lovely material properties
//-----------------------------------------------------------------------------
void CMaterial::GetReflectivity( Vector& reflect )
{
	Precache();

	reflect = m_Reflectivity;
}


bool CMaterial::GetPropertyFlag( MaterialPropertyTypes_t type )
{
	Precache();

	if (!IsValidRenderState())
		return false;

	switch( type )
	{
	case MATERIAL_PROPERTY_NEEDS_LIGHTMAP:
		return IsUsingLightmap();

	case MATERIAL_PROPERTY_NEEDS_BUMPED_LIGHTMAPS:
		return IsUsingDiffuseBumpedLighting();
	}

	return false;
}


//-----------------------------------------------------------------------------
// Is the material visible from both sides?
//-----------------------------------------------------------------------------
bool CMaterial::IsTwoSided()
{
	PrecacheVars();
	return GetMaterialVarFlag(MATERIAL_VAR_NOCULL);
}


//-----------------------------------------------------------------------------
// Are we translucent?
//-----------------------------------------------------------------------------
bool CMaterial::IsTranslucent()
{
	Precache();
	if ( m_VarCount > ALPHA )
		return IsTranslucentInternal( m_pShaderParams? m_pShaderParams[ALPHA]->GetFloatValue() : 0.0  );
	return false;
}


bool CMaterial::IsTranslucentInternal( float fAlphaModulation ) const
{
	if (m_pShader && IsValidRenderState())
	{
		// I have to check for alpha modulation here because it isn't
		// factored into the shader's notion of whether or not it's transparent
		return ::IsTranslucent(&m_ShaderRenderState) || 
			(fAlphaModulation < 1.0f) ||
			m_pShader->IsTranslucent( m_pShaderParams );
	}
	return false;
}


//-----------------------------------------------------------------------------
// Are we alphatested?
//-----------------------------------------------------------------------------
bool CMaterial::IsAlphaTested()
{
	Precache();
	if (m_pShader && IsValidRenderState())
	{
		return ::IsAlphaTested(&m_ShaderRenderState) || 
				GetMaterialVarFlag( MATERIAL_VAR_ALPHATEST );
	}
	return false;
}


//-----------------------------------------------------------------------------
// Are we vertex lit?
//-----------------------------------------------------------------------------
bool CMaterial::IsVertexLit()
{
	Precache();
	if (IsValidRenderState())
	{
		return ( GetMaterialVarFlags2() & MATERIAL_VAR2_LIGHTING_VERTEX_LIT ) != 0;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Is the shader a sprite card shader?
//-----------------------------------------------------------------------------
bool CMaterial::IsSpriteCard()
{
	Precache();
	if (IsValidRenderState())
	{
		return ( GetMaterialVarFlags2() & MATERIAL_VAR2_IS_SPRITECARD ) != 0;
	}
	return false;
}


//-----------------------------------------------------------------------------
// Proxies
//-----------------------------------------------------------------------------
void CMaterial::CallBindProxy( void *proxyData )
{
	CMatCallQueue *pCallQueue = MaterialSystem()->GetRenderCallQueue();
	bool bIsThreaded = ( pCallQueue != NULL );
	switch (g_config.proxiesTestMode)
	{
	case 0:
		{
			// Make sure we call the proxies in the order in which they show up
			// in the .vmt file
			if ( m_ProxyInfo.Count() )
			{
				if ( bIsThreaded )
				{
					EnableThreadedMaterialVarAccess( true, m_pShaderParams, m_VarCount );
				}
				for( int i = 0; i < m_ProxyInfo.Count(); ++i )
				{
					m_ProxyInfo[i]->OnBind( proxyData );
				}
				if ( bIsThreaded )
				{
					EnableThreadedMaterialVarAccess( false, m_pShaderParams, m_VarCount );
				}
			}
		}
		break;

	case 2:
		// alpha mod all....
		{
			float value = ( sin( 2.0f * M_PI * Plat_FloatTime() / 10.0f ) * 0.5f ) + 0.5f;
			m_pShaderParams[ALPHA]->SetFloatValue( value );
		}
		break;

	case 3:
		// color mod all...
		{
			float value = ( sin( 2.0f * M_PI * Plat_FloatTime() / 10.0f ) * 0.5f ) + 0.5f;
			m_pShaderParams[COLOR]->SetVecValue( value, 1.0f, 1.0f );
		}
		break;
	}
}


IMaterial *CMaterial::CheckProxyReplacement( void *proxyData )
{
	if ( m_pReplacementProxy != NULL )
	{
		IMaterial	*pReplaceMaterial = m_pReplacementProxy->GetMaterial();

		if ( pReplaceMaterial )
		{
			return pReplaceMaterial;
		}
	}

	return this;
}


bool CMaterial::HasProxy( )	const
{
	const_cast< CMaterial* >( this )->PrecacheVars();
	return m_ProxyInfo.Count() > 0;
}


//-----------------------------------------------------------------------------
// Main draw method
//-----------------------------------------------------------------------------

#ifdef _WIN32
#pragma warning (disable: 4189)
#endif

void CMaterial::DrawMesh( VertexCompressionType_t vertexCompression )
{
	if ( m_pShader )
	{
#ifdef _DEBUG
		if ( GetMaterialVarFlags() & MATERIAL_VAR_DEBUG )
		{
			// Putcher breakpoint here to catch the rendering of a material
			// marked for debugging ($debug = 1 in a .vmt file) dynamic state version
			int x = 0;
		}
#endif
		if ((GetMaterialVarFlags() & MATERIAL_VAR_NO_DRAW) == 0)
		{
			const char *pName = m_pShader->GetName();
			ShaderSystem()->DrawElements( m_pShader, m_pShaderParams, &m_ShaderRenderState, vertexCompression, m_ChangeID ^ g_nDebugVarsSignature );
		}
	}
	else
	{
		Warning( "CMaterial::DrawElements: No bound shader\n" );
	}
}

#ifdef _WIN32
#pragma warning (default: 4189)
#endif

IShader *CMaterial::GetShader( ) const
{
	return m_pShader;
}

IMaterialVar *CMaterial::GetShaderParam( int id )
{
	return m_pShaderParams[id];
}


//-----------------------------------------------------------------------------
// Adds a material variable to the material
//-----------------------------------------------------------------------------
void CMaterial::AddMaterialVar( IMaterialVar *pMaterialVar )
{
	++m_VarCount;
	m_pShaderParams = (IMaterialVar**)realloc( m_pShaderParams, m_VarCount * sizeof( IMaterialVar*) );
	m_pShaderParams[m_VarCount-1] = pMaterialVar;
}


bool CMaterial::IsErrorMaterial() const
{
	extern IMaterialInternal *g_pErrorMaterial;
	const IMaterialInternal *pThis = this;
	return g_pErrorMaterial == pThis;
}


void CMaterial::FindRepresentativeTexture( void )
{
	Precache();
	
	// First try to find the base texture...
	bool found;
	IMaterialVar *textureVar = FindVar( "$baseTexture", &found, false );
	if( found && textureVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
	{
		ITextureInternal *pTexture = ( ITextureInternal * )textureVar->GetTextureValue();
		if( pTexture )
		{
			pTexture->GetReflectivity( m_Reflectivity );
		}
	}
	if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
	{
		// Try the env map mask if the base texture doesn't work...
		// this is needed for specular decals
		textureVar = FindVar( "$envmapmask", &found, false );
		if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
		{
			// Try the bumpmap
			textureVar = FindVar( "$bumpmap", &found, false );
			if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
			{
				textureVar = FindVar( "$dudvmap", &found, false );
				if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
				{
					textureVar = FindVar( "$normalmap", &found, false );
					if( !found || textureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE )
					{
						//				Warning( "Can't find representative texture for material \"%s\"\n",	GetName() );
						m_representativeTexture = TextureManager()->ErrorTexture();
						return;
					}
				}
			}
		}
	}

	m_representativeTexture = static_cast<ITextureInternal *>( textureVar->GetTextureValue() );
	if (m_representativeTexture)
	{
		m_representativeTexture->Precache();
	}
	else
	{
		m_representativeTexture = TextureManager()->ErrorTexture();
		Assert( m_representativeTexture );
	}
}


void CMaterial::GetLowResColorSample( float s, float t, float *color ) const
{
	if( !m_representativeTexture )
	{
		return;
	}
	m_representativeTexture->GetLowResColorSample( s, t, color);
}


//-----------------------------------------------------------------------------
// Lightmap-related methods
//-----------------------------------------------------------------------------

void CMaterial::SetMinLightmapPageID( int pageID )
{
	m_minLightmapPageID = pageID;
}

void CMaterial::SetMaxLightmapPageID( int pageID )
{
	m_maxLightmapPageID = pageID;
}

int CMaterial::GetMinLightmapPageID( ) const
{
	return m_minLightmapPageID;
}

int	CMaterial::GetMaxLightmapPageID( ) const
{
	return m_maxLightmapPageID;
}

void CMaterial::SetNeedsWhiteLightmap( bool val )
{
	if ( val )
		m_Flags |= MATERIAL_NEEDS_WHITE_LIGHTMAP;
	else
		m_Flags &= ~MATERIAL_NEEDS_WHITE_LIGHTMAP;
}

bool CMaterial::GetNeedsWhiteLightmap( ) const
{
	return (m_Flags & MATERIAL_NEEDS_WHITE_LIGHTMAP) != 0;
}

void CMaterial::MarkAsPreloaded( bool bSet )
{
	if ( bSet )
	{
		m_Flags |= MATERIAL_IS_PRELOADED;
	}
	else
	{
		m_Flags &= ~MATERIAL_IS_PRELOADED;
	}
}

bool CMaterial::IsPreloaded() const
{
	return ( m_Flags & MATERIAL_IS_PRELOADED ) != 0;
}

void CMaterial::ArtificialAddRef( void )
{
	if ( m_Flags & MATERIAL_ARTIFICIAL_REFCOUNT )
	{
		// already done
		return;
	}

	m_Flags |= MATERIAL_ARTIFICIAL_REFCOUNT;
	m_RefCount++;
}

void CMaterial::ArtificialRelease( void )
{
	if ( !( m_Flags & MATERIAL_ARTIFICIAL_REFCOUNT ) )
	{
		return;
	}

	m_Flags &= ~MATERIAL_ARTIFICIAL_REFCOUNT;
	m_RefCount--;
}

//-----------------------------------------------------------------------------
// Return the shader params
//-----------------------------------------------------------------------------
IMaterialVar **CMaterial::GetShaderParams( void )
{
	return m_pShaderParams;
}

int CMaterial::ShaderParamCount() const
{
	return m_VarCount;
}


//-----------------------------------------------------------------------------
// VMT parser
//-----------------------------------------------------------------------------
void InsertKeyValues( KeyValues& dst, KeyValues& src, bool bCheckForExistence, bool bRecursive )
{
	KeyValues *pSrcVar = src.GetFirstSubKey();
	while( pSrcVar )
	{
		if ( !bCheckForExistence || dst.FindKey( pSrcVar->GetName() ) )
		{
			switch( pSrcVar->GetDataType() )
			{
			case KeyValues::TYPE_STRING:
				dst.SetString( pSrcVar->GetName(), pSrcVar->GetString() );
				break;
			case KeyValues::TYPE_INT:
				dst.SetInt( pSrcVar->GetName(), pSrcVar->GetInt() );
				break;
			case KeyValues::TYPE_FLOAT:
				dst.SetFloat( pSrcVar->GetName(), pSrcVar->GetFloat() );
				break;
			case KeyValues::TYPE_PTR:
				dst.SetPtr( pSrcVar->GetName(), pSrcVar->GetPtr() );
				break;
			case KeyValues::TYPE_NONE:
				{
					// Subkey. Recurse.
					KeyValues *pNewDest = dst.FindKey( pSrcVar->GetName(), true );
					Assert( pNewDest );
					InsertKeyValues( *pNewDest, *pSrcVar, bCheckForExistence, true );
				}
				break;
			}
		}
		pSrcVar = pSrcVar->GetNextKey();
	}
	
	if ( bRecursive && !dst.GetFirstSubKey() )
	{
		// Insert a dummy key to an empty subkey to make sure it doesn't get removed
		dst.SetInt( "__vmtpatchdummy", 1 );
	}

	if( bCheckForExistence )
	{
		for( KeyValues *pScan = dst.GetFirstTrueSubKey(); pScan; pScan = pScan->GetNextTrueSubKey() )
		{
			KeyValues *pTmp = src.FindKey( pScan->GetName() );
			if( !pTmp )
				continue;
			// make sure that this is a subkey.
			if( pTmp->GetDataType() != KeyValues::TYPE_NONE )
				continue;
			InsertKeyValues( *pScan, *pTmp, bCheckForExistence );
		}
	}
}

void WriteKeyValuesToFile( const char *pFileName, KeyValues& keyValues )
{
	keyValues.SaveToFile( g_pFullFileSystem, pFileName );
}

void ApplyPatchKeyValues( KeyValues &keyValues, KeyValues &patchKeyValues )
{
	KeyValues *pInsertSection = patchKeyValues.FindKey( "insert" );
	KeyValues *pReplaceSection = patchKeyValues.FindKey( "replace" );

	if ( pInsertSection )
	{
		InsertKeyValues( keyValues, *pInsertSection, false );
	}

	if ( pReplaceSection )
	{
		InsertKeyValues( keyValues, *pReplaceSection, true );
	}

	// Could add other commands here, like "delete", "rename", etc.
}

//-----------------------------------------------------------------------------
// Adds keys from srcKeys to destKeys, overwriting any keys that are already
// there.
//-----------------------------------------------------------------------------
void MergeKeyValues( KeyValues &srcKeys, KeyValues &destKeys )
{
	for( KeyValues *pKV = srcKeys.GetFirstValue(); pKV; pKV = pKV->GetNextValue() )
	{
		switch( pKV->GetDataType() )
		{
		case KeyValues::TYPE_STRING:
			destKeys.SetString( pKV->GetName(), pKV->GetString() );
			break;
		case KeyValues::TYPE_INT:
			destKeys.SetInt( pKV->GetName(), pKV->GetInt() );
			break;
		case KeyValues::TYPE_FLOAT:
			destKeys.SetFloat( pKV->GetName(), pKV->GetFloat() );
			break;
		case KeyValues::TYPE_PTR:
			destKeys.SetPtr( pKV->GetName(), pKV->GetPtr() );
			break;
		}
	}
	for( KeyValues *pKV = srcKeys.GetFirstTrueSubKey(); pKV; pKV = pKV->GetNextTrueSubKey() )
	{
		KeyValues *pDestKV = destKeys.FindKey( pKV->GetName(), true );
		MergeKeyValues( *pKV, *pDestKV );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void AccumulatePatchKeyValues( KeyValues &srcKeyValues, KeyValues &patchKeyValues )
{
	KeyValues *pDestInsertSection = patchKeyValues.FindKey( "insert" );
	if ( pDestInsertSection == NULL )
	{
		pDestInsertSection = new KeyValues( "insert" );
		patchKeyValues.AddSubKey( pDestInsertSection );
	}

	KeyValues *pDestReplaceSection = patchKeyValues.FindKey( "replace" );
	if ( pDestReplaceSection == NULL )
	{
		pDestReplaceSection = new KeyValues( "replace" );
		patchKeyValues.AddSubKey( pDestReplaceSection );
	}

	KeyValues *pSrcInsertSection = srcKeyValues.FindKey( "insert" );
	if ( pSrcInsertSection )
	{
		MergeKeyValues( *pSrcInsertSection, *pDestInsertSection );
	}

	KeyValues *pSrcReplaceSection = srcKeyValues.FindKey( "replace" );
	if ( pSrcReplaceSection )
	{
		MergeKeyValues( *pSrcReplaceSection, *pDestReplaceSection );
	}
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool AccumulateRecursiveVmtPatches( KeyValues &patchKeyValuesOut, KeyValues **ppBaseKeyValuesOut, const KeyValues& keyValues, const char *pPathID, CUtlVector<FileNameHandle_t> *pIncludes )
{
	if ( pIncludes )
	{
		pIncludes->Purge();
	}

	patchKeyValuesOut.Clear();

	if ( V_stricmp( keyValues.GetName(), "patch" ) != 0 )
	{
		// Not a patch file, nothing to do
		if ( ppBaseKeyValuesOut )
		{
			// flag to the caller that the passed in keyValues are in fact final non-patch values
			*ppBaseKeyValuesOut = NULL;
		}
		return true;
	}

	KeyValues *pCurrentKeyValues = keyValues.MakeCopy();

	// Recurse down through all patch files:
	int nCount = 0;
	while( ( nCount < 10 ) && ( V_stricmp( pCurrentKeyValues->GetName(), "patch" ) == 0 ) )
	{
		// Accumulate the new patch keys from this file
		AccumulatePatchKeyValues( *pCurrentKeyValues, patchKeyValuesOut );
		
		// Load the included file
		const char *pIncludeFileName = pCurrentKeyValues->GetString( "include" );

		if ( pIncludeFileName == NULL )
		{
			// A patch file without an include key? Not good...
			Warning( "VMT patch file has no include key - invalid!\n" );
			Assert( pIncludeFileName );
			break;
		}

		CUtlString includeFileName( pIncludeFileName ); // copy off the string before we clear the keyvalues it lives in
		pCurrentKeyValues->Clear();
		bool bSuccess = pCurrentKeyValues->LoadFromFile( g_pFullFileSystem, includeFileName, pPathID );
		if( bSuccess )
		{
			if ( pIncludes )
			{
				// Remember that we included this file for the pure server stuff.
				pIncludes->AddToTail( g_pFullFileSystem->FindOrAddFileName( includeFileName ) );
			}
		}
		else
		{
			pCurrentKeyValues->deleteThis();
#ifndef DEDICATED
			Warning( "Failed to load $include VMT file (%s)\n", includeFileName.String() );
#endif
			if ( !HushAsserts() )
			{
				AssertMsg( false, "Failed to load $include VMT file (%s)", includeFileName.String() );
			}
			return false;
		}

		nCount++;
	}

	if ( ppBaseKeyValuesOut )
	{
		*ppBaseKeyValuesOut = pCurrentKeyValues;
	}
	else
	{
		pCurrentKeyValues->deleteThis();
	}

	if( nCount >= 10 )
	{
		Warning( "Infinite recursion in patch file?\n" );
	}
	return true;
}

//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void ExpandPatchFile( KeyValues& keyValues, KeyValues &patchKeyValues, const char *pPathID, CUtlVector<FileNameHandle_t> *pIncludes )
{
	KeyValues *pNonPatchKeyValues = NULL;
	if ( !patchKeyValues.IsEmpty() )
	{
		pNonPatchKeyValues = keyValues.MakeCopy();
	}
	else
	{
		bool bSuccess = AccumulateRecursiveVmtPatches( patchKeyValues, &pNonPatchKeyValues, keyValues, pPathID, pIncludes );
		if ( !bSuccess )
		{
			return;
		}
	}

	if ( pNonPatchKeyValues != NULL )
	{
		// We're dealing with a patch file. Apply accumulated patches to final vmt
		ApplyPatchKeyValues( *pNonPatchKeyValues, patchKeyValues );
		keyValues = *pNonPatchKeyValues;
		pNonPatchKeyValues->deleteThis();
	}
}

bool LoadVMTFile( KeyValues &vmtKeyValues, KeyValues &patchKeyValues, const char *pMaterialName, bool bAbsolutePath, CUtlVector<FileNameHandle_t> *pIncludes )
{
	char pFileName[MAX_PATH];
	const char *pPathID = "GAME";
	if ( !bAbsolutePath )
	{
		Q_snprintf( pFileName, sizeof( pFileName ), "materials/%s.vmt", pMaterialName );
	}
	else
	{
		Q_snprintf( pFileName, sizeof( pFileName ), "%s.vmt", pMaterialName );
		if ( pMaterialName[0] == '/' && pMaterialName[1] == '/' && pMaterialName[2] != '/' )
		{
			// UNC, do full search
			pPathID = NULL;
		}
	}

	if ( !vmtKeyValues.LoadFromFile( g_pFullFileSystem, pFileName, pPathID ) )
	{
		return false;
	}
	ExpandPatchFile( vmtKeyValues, patchKeyValues, pPathID, pIncludes );

	return true;
}

int CMaterial::GetNumPasses( void )
{
	Precache();
//	int mod = m_ShaderRenderState.m_Modulation;
	int mod = 0;
	return m_ShaderRenderState.m_pSnapshots[mod].m_nPassCount;
}

int CMaterial::GetTextureMemoryBytes( void )
{
	Precache();
	int bytes = 0;
	int i;
	for( i = 0; i < m_VarCount; i++ )
	{
		IMaterialVar *pVar = m_pShaderParams[i];
		if( pVar->GetType() == MATERIAL_VAR_TYPE_TEXTURE )
		{
			ITexture *pTexture = pVar->GetTextureValue();
			if( pTexture && pTexture != ( ITexture * )0xffffffff )
			{
				bytes += pTexture->GetApproximateVidMemBytes();
			}
		}
	}
	return bytes;
}

void CMaterial::SetUseFixedFunctionBakedLighting( bool bEnable )
{
	SetMaterialVarFlags2( MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING, bEnable );
}

bool CMaterial::NeedsFixedFunctionFlashlight() const
{
	return ( GetMaterialVarFlags2() & MATERIAL_VAR2_NEEDS_FIXED_FUNCTION_FLASHLIGHT ) &&
		MaterialSystem()->InFlashlightMode();
}

bool CMaterial::IsUsingVertexID( ) const
{
	return ( GetMaterialVarFlags2() & MATERIAL_VAR2_USES_VERTEXID ) != 0;
}

void CMaterial::DeleteIfUnreferenced()
{
	if ( m_RefCount > 0 )
		return;
	IMaterialVar::DeleteUnreferencedTextures( true );
	IMaterialInternal::DestroyMaterial( this );
	IMaterialVar::DeleteUnreferencedTextures( false );
}
