//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //===========================================================================// #include "shadersystem.h" #include #include "materialsystem_global.h" #include "filesystem.h" #include "tier1/utldict.h" #include "shaderlib/ShaderDLL.h" #include "texturemanager.h" #include "itextureinternal.h" #include "IHardwareConfigInternal.h" #include "tier1/utlstack.h" #include "tier1/utlbuffer.h" #include "mathlib/vmatrix.h" #include "imaterialinternal.h" #include "tier1/strtools.h" #include "tier0/icommandline.h" #include "shaderlib/cshader.h" #include "tier1/convar.h" #include "tier1/KeyValues.h" #include "shader_dll_verify.h" #include "tier0/vprof.h" // NOTE: This must be the last file included! #include "tier0/memdbgon.h" //#define DEBUG_DEPTH 1 //----------------------------------------------------------------------------- // Lovely convars //----------------------------------------------------------------------------- static ConVar mat_showenvmapmask( "mat_showenvmapmask", "0" ); static ConVar mat_debugdepth( "mat_debugdepth", "0" ); extern ConVar mat_supportflashlight; //----------------------------------------------------------------------------- // Implementation of the shader system //----------------------------------------------------------------------------- class CShaderSystem : public IShaderSystemInternal { public: CShaderSystem(); // Methods of IShaderSystem virtual ShaderAPITextureHandle_t GetShaderAPITextureBindHandle( ITexture *pTexture, int nFrameVar, int nTextureChannel = 0 ); virtual void BindTexture( Sampler_t sampler1, ITexture *pTexture, int nFrame = 0 ); virtual void BindTexture( Sampler_t sampler1, Sampler_t sampler2, ITexture *pTexture, int nFrame = 0 ); virtual void TakeSnapshot( ); virtual void DrawSnapshot( bool bMakeActualDrawCall = true ); virtual bool IsUsingGraphics() const; virtual bool CanUseEditorMaterials() const; // Methods of IShaderSystemInternal virtual void Init(); virtual void Shutdown(); virtual void ModInit(); virtual void ModShutdown(); virtual bool LoadShaderDLL( const char *pFullPath ); virtual bool LoadShaderDLL( const char *pFullPath, const char *pPathID, bool bModShaderDLL ); virtual void UnloadShaderDLL( const char *pFullPath ); virtual IShader* FindShader( char const* pShaderName ); virtual void CreateDebugMaterials(); virtual void CleanUpDebugMaterials(); virtual char const* ShaderStateString( int i ) const; virtual int ShaderStateCount( ) const; virtual void InitShaderParameters( IShader *pShader, IMaterialVar **params, const char *pMaterialName ); virtual void InitShaderInstance( IShader *pShader, IMaterialVar **params, const char *pMaterialName, const char *pTextureGroupName ); virtual bool InitRenderState( IShader *pShader, int numParams, IMaterialVar **params, ShaderRenderState_t* pRenderState, char const* pMaterialName ); virtual void CleanupRenderState( ShaderRenderState_t* pRenderState ); virtual void DrawElements( IShader *pShader, IMaterialVar **params, ShaderRenderState_t* pShaderState, VertexCompressionType_t vertexCompression, uint32 nVarChangeID ); // Used to iterate over all shaders for editing purposes virtual int ShaderCount() const; virtual int GetShaders( int nFirstShader, int nMaxCount, IShader **ppShaderList ) const; // Methods of IShaderInit virtual void LoadTexture( IMaterialVar *pTextureVar, const char *pTextureGroupName, int nAdditionalCreationFlags = 0 ); virtual void LoadBumpMap( IMaterialVar *pTextureVar, const char *pTextureGroupName ); virtual void LoadCubeMap( IMaterialVar **ppParams, IMaterialVar *pTextureVar, int nAdditionalCreationFlags = 0 ); // Used to prevent re-entrant rendering from warning messages void BufferSpew( SpewType_t spewType, const Color &c, const char *pMsg ); private: struct ShaderDLLInfo_t { char *m_pFileName; CSysModule *m_hInstance; IShaderDLLInternal *m_pShaderDLL; ShaderDLL_t m_hShaderDLL; // True if this is a mod's shader DLL, in which case it's not allowed to // override any existing shader names. bool m_bModShaderDLL; CUtlDict< IShader *, unsigned short > m_ShaderDict; }; private: // hackhack: remove this when VAC2 is online. void VerifyBaseShaderDLL( CSysModule *pModule ); // Load up the shader DLLs... void LoadAllShaderDLLs(); // Load the "mshader_" DLLs. void LoadModShaderDLLs( int dxSupportLevel ); // Unload all the shader DLLs... void UnloadAllShaderDLLs(); // Sets up the shader dictionary. void SetupShaderDictionary( int nShaderDLLIndex ); // Cleans up the shader dictionary. void CleanupShaderDictionary( int nShaderDLLIndex ); // Finds an already loaded shader DLL int FindShaderDLL( const char *pFullPath ); // Unloads a particular shader DLL void UnloadShaderDLL( int nShaderDLLIndex ); // Sets up the current ShaderState_t for rendering void PrepForShaderDraw( IShader *pShader, IMaterialVar** ppParams, ShaderRenderState_t* pRenderState, int modulation ); void DoneWithShaderDraw(); // Initializes state snapshots void InitStateSnapshots( IShader *pShader, IMaterialVar **params, ShaderRenderState_t* pRenderState ); // Compute snapshots for all combinations of alpha + color modulation void InitRenderStateFlags( ShaderRenderState_t* pRenderState, int numParams, IMaterialVar **params ); // Computes flags from a particular snapshot void ComputeRenderStateFlagsFromSnapshot( ShaderRenderState_t* pRenderState ); // Computes vertex format + usage from a particular snapshot bool ComputeVertexFormatFromSnapshot( IMaterialVar **params, ShaderRenderState_t* pRenderState ); // Used to prevent re-entrant rendering from warning messages void PrintBufferedSpew( void ); // Gets at the current snapshot StateSnapshot_t CurrentStateSnapshot(); // Draws using a particular material.. void DrawUsingMaterial( IMaterialInternal *pMaterial, VertexCompressionType_t vertexCompression ); // Copies material vars void CopyMaterialVarToDebugShader( IMaterialInternal *pDebugMaterial, IShader *pShader, IMaterialVar **ppParams, const char *pSrcVarName, const char *pDstVarName = NULL ); // Debugging draw methods... void DrawMeasureFillRate( ShaderRenderState_t* pRenderState, int mod, VertexCompressionType_t vertexCompression ); void DrawNormalMap( IShader *pShader, IMaterialVar **ppParams, VertexCompressionType_t vertexCompression ); bool DrawEnvmapMask( IShader *pShader, IMaterialVar **ppParams, ShaderRenderState_t* pRenderState, VertexCompressionType_t vertexCompression ); int GetModulationSnapshotCount( IMaterialVar **params ); private: // List of all DLLs containing shaders CUtlVector< ShaderDLLInfo_t > m_ShaderDLLs; // Used to prevent re-entrant rendering from warning messages SpewOutputFunc_t m_SaveSpewOutput; CUtlBuffer m_StoredSpew; // Render state we're drawing with ShaderRenderState_t* m_pRenderState; unsigned short m_hShaderDLL; unsigned char m_nModulation; unsigned char m_nRenderPass; // Debugging materials // If you add to this, add to the list of debug shader names (s_pDebugShaderName) below enum { MATERIAL_FILL_RATE = 0, MATERIAL_DEBUG_NORMALMAP, MATERIAL_DEBUG_ENVMAPMASK, MATERIAL_DEBUG_DEPTH, MATERIAL_DEBUG_DEPTH_DECAL, MATERIAL_DEBUG_WIREFRAME, MATERIAL_DEBUG_COUNT, }; IMaterialInternal* m_pDebugMaterials[MATERIAL_DEBUG_COUNT]; static const char *s_pDebugShaderName[MATERIAL_DEBUG_COUNT]; bool m_bForceUsingGraphicsReturnTrue; }; //----------------------------------------------------------------------------- // Singleton //----------------------------------------------------------------------------- static CShaderSystem s_ShaderSystem; IShaderSystemInternal *g_pShaderSystem = &s_ShaderSystem; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CShaderSystem, IShaderSystem, SHADERSYSTEM_INTERFACE_VERSION, s_ShaderSystem ); //----------------------------------------------------------------------------- // Debugging shader names //----------------------------------------------------------------------------- const char *CShaderSystem::s_pDebugShaderName[MATERIAL_DEBUG_COUNT] = { "FillRate", "DebugNormalMap", "DebugDrawEnvmapMask", "DebugDepth", "DebugDepth", "Wireframe_DX9" }; //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CShaderSystem::CShaderSystem() : m_StoredSpew( 0, 512, 0 ), m_bForceUsingGraphicsReturnTrue( false ) { } //----------------------------------------------------------------------------- // Initialization, shutdown //----------------------------------------------------------------------------- void CShaderSystem::Init() { m_SaveSpewOutput = NULL; m_bForceUsingGraphicsReturnTrue = false; if ( CommandLine()->FindParm( "-noshaderapi" ) || CommandLine()->FindParm( "-makereslists" ) ) { m_bForceUsingGraphicsReturnTrue = true; } for ( int i = 0; i < MATERIAL_DEBUG_COUNT; ++i ) { m_pDebugMaterials[i] = NULL; } LoadAllShaderDLLs(); } void CShaderSystem::Shutdown() { UnloadAllShaderDLLs(); } //----------------------------------------------------------------------------- // Load/unload mod-specific shader DLLs //----------------------------------------------------------------------------- void CShaderSystem::ModInit() { // Load up standard shader DLLs... int dxSupportLevel = HardwareConfig()->GetMaxDXSupportLevel(); Assert( dxSupportLevel >= 60 ); dxSupportLevel /= 10; LoadModShaderDLLs( dxSupportLevel ); } void CShaderSystem::ModShutdown() { // Unload only MOD dlls for ( int i = m_ShaderDLLs.Count(); --i >= 0; ) { if ( m_ShaderDLLs[i].m_bModShaderDLL ) { UnloadShaderDLL(i); delete[] m_ShaderDLLs[i].m_pFileName; m_ShaderDLLs.Remove( i ); } } } //----------------------------------------------------------------------------- // Load up the shader DLLs... //----------------------------------------------------------------------------- void CShaderSystem::LoadAllShaderDLLs( ) { UnloadAllShaderDLLs(); GetShaderDLLInternal()->Connect( Sys_GetFactoryThis(), true ); // Loads local defined or statically linked shaders int i = m_ShaderDLLs.AddToHead(); m_ShaderDLLs[i].m_pFileName = new char[1]; m_ShaderDLLs[i].m_pFileName[0] = 0; m_ShaderDLLs[i].m_hInstance = NULL; m_ShaderDLLs[i].m_pShaderDLL = GetShaderDLLInternal(); m_ShaderDLLs[i].m_bModShaderDLL = false; // Add the shaders to the dictionary of shaders... SetupShaderDictionary( i ); // 360 has the the debug shaders in its dx9 dll if ( IsPC() || !IsX360() ) { // Always need the debug shaders LoadShaderDLL( "stdshader_dbg" DLL_EXT_STRING ); } // Load up standard shader DLLs... int dxSupportLevel = HardwareConfig()->GetMaxDXSupportLevel(); Assert( dxSupportLevel >= 60 ); dxSupportLevel /= 10; // 360 only supports its dx9 dll int dxStart = IsX360() ? 9 : 6; char buf[32]; for ( i = dxStart; i <= dxSupportLevel; ++i ) { Q_snprintf( buf, sizeof( buf ), "stdshader_dx%d%s", i, DLL_EXT_STRING ); LoadShaderDLL( buf ); } const char *pShaderName = NULL; #ifdef _DEBUG pShaderName = CommandLine()->ParmValue( "-shader" ); #endif if ( !pShaderName ) { pShaderName = HardwareConfig()->GetHWSpecificShaderDLLName(); } if ( pShaderName ) { LoadShaderDLL( pShaderName ); } #ifdef _DEBUG // For fast-iteration debugging if ( CommandLine()->FindParm( "-testshaders" ) ) { LoadShaderDLL( "shader_test" DLL_EXT_STRING ); } #endif } const char *COM_GetModDirectory() { static char modDir[MAX_PATH]; if ( Q_strlen( modDir ) == 0 ) { const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); Q_strncpy( modDir, gamedir, sizeof(modDir) ); if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) ) { Q_StripLastDir( modDir, sizeof(modDir) ); int dirlen = Q_strlen( modDir ); Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen ); } } return modDir; } void CShaderSystem::LoadModShaderDLLs( int dxSupportLevel ) { if ( IsX360() ) return; // Don't do this for Valve mods. They don't need them, and attempting to load them is an opportunity for cheaters to get their code into the process const char *pGameDir = COM_GetModDirectory(); if ( !Q_stricmp( pGameDir, "hl2" ) || !Q_stricmp( pGameDir, "cstrike" ) || !Q_stricmp( pGameDir, "cstrike_beta" ) || !Q_stricmp( pGameDir, "hl2mp" ) || !Q_stricmp( pGameDir, "lostcoast" ) || !Q_stricmp( pGameDir, "episodic" ) || !Q_stricmp( pGameDir, "portal" ) || !Q_stricmp( pGameDir, "ep2" ) || !Q_stricmp( pGameDir, "dod" ) || !Q_stricmp( pGameDir, "tf" ) || !Q_stricmp( pGameDir, "tf_beta" ) || !Q_stricmp( pGameDir, "hl1" ) ) { return; } const char *pModShaderPathID = "GAMEBIN"; // First load the ones with dx_ prefix. char buf[256]; int dxStart = 6; for ( int i = dxStart; i <= dxSupportLevel; ++i ) { Q_snprintf( buf, sizeof( buf ), "game_shader_dx%d%s", i, DLL_EXT_STRING ); LoadShaderDLL( buf, pModShaderPathID, true ); } // Now load the ones with any dx_ prefix. FileFindHandle_t findHandle; const char *pFilename = g_pFullFileSystem->FindFirstEx( "game_shader_generic*", pModShaderPathID, &findHandle ); while ( pFilename ) { Q_snprintf( buf, sizeof( buf ), "%s%s", pFilename, DLL_EXT_STRING ); LoadShaderDLL( buf, pModShaderPathID, true ); pFilename = g_pFullFileSystem->FindNext( findHandle ); } } //----------------------------------------------------------------------------- // Unload all the shader DLLs... //----------------------------------------------------------------------------- void CShaderSystem::UnloadAllShaderDLLs() { if ( m_ShaderDLLs.Count() == 0 ) return; for ( int i = m_ShaderDLLs.Count(); --i >= 0; ) { UnloadShaderDLL(i); delete[] m_ShaderDLLs[i].m_pFileName; } m_ShaderDLLs.RemoveAll(); } bool CShaderSystem::LoadShaderDLL( const char *pFullPath ) { return LoadShaderDLL( pFullPath, NULL, false ); } // HACKHACK: remove me when VAC2 is online. #if defined( _WIN32 ) && !defined( _X360 ) // Instead of including windows.h extern "C" { extern void * __stdcall GetProcAddress( void *hModule, const char *pszProcName ); }; #endif void CShaderSystem::VerifyBaseShaderDLL( CSysModule *pModule ) { #if defined( _WIN32 ) && !defined( _X360 ) const char *pErrorStr = "Corrupt save data settings."; unsigned char *testData1 = new unsigned char[SHADER_DLL_VERIFY_DATA_LEN1]; ShaderDLLVerifyFn fn = (ShaderDLLVerifyFn)GetProcAddress( (void *)pModule, SHADER_DLL_FNNAME_1 ); if ( !fn ) Error( pErrorStr ); IShaderDLLVerification *pVerify; char *pPtr = (char*)(void*)&pVerify; pPtr -= SHADER_DLL_VERIFY_DATA_PTR_OFFSET; fn( pPtr ); // Test the first CRC. CRC32_t testCRC; CRC32_Init( &testCRC ); CRC32_ProcessBuffer( &testCRC, testData1, SHADER_DLL_VERIFY_DATA_LEN1 ); CRC32_ProcessBuffer( &testCRC, &pModule, 4 ); CRC32_ProcessBuffer( &testCRC, &pVerify, 4 ); CRC32_Final( &testCRC ); if ( testCRC != pVerify->Function1( testData1 - SHADER_DLL_VERIFY_DATA_PTR_OFFSET ) ) Error( pErrorStr ); // Test the next one. unsigned char digest[MD5_DIGEST_LENGTH]; MD5Context_t md5Context; MD5Init( &md5Context ); MD5Update( &md5Context, testData1 + SHADER_DLL_VERIFY_DATA_PTR_OFFSET, SHADER_DLL_VERIFY_DATA_LEN1 - SHADER_DLL_VERIFY_DATA_PTR_OFFSET ); MD5Final( digest, &md5Context ); pVerify->Function2( 2, 3, 3 ); // fn2 is supposed to place the result in testData1. if ( memcmp( digest, testData1, MD5_DIGEST_LENGTH ) != 0 ) Error( pErrorStr ); pVerify->Function5(); delete [] testData1; #endif } //----------------------------------------------------------------------------- // Methods related to reading in shader DLLs //----------------------------------------------------------------------------- bool CShaderSystem::LoadShaderDLL( const char *pFullPath, const char *pPathID, bool bModShaderDLL ) { if ( !pFullPath && !pFullPath[0] ) return true; // Load the new shader bool bValidatedDllOnly = true; if ( bModShaderDLL ) bValidatedDllOnly = false; CSysModule *hInstance = g_pFullFileSystem->LoadModule( pFullPath, pPathID, bValidatedDllOnly ); if ( !hInstance ) return false; // Get at the shader DLL interface CreateInterfaceFn factory = Sys_GetFactory( hInstance ); if (!factory) { g_pFullFileSystem->UnloadModule( hInstance ); return false; } IShaderDLLInternal *pShaderDLL = (IShaderDLLInternal*)factory( SHADER_DLL_INTERFACE_VERSION, NULL ); if ( !pShaderDLL ) { g_pFullFileSystem->UnloadModule( hInstance ); return false; } // Make sure it's a valid base shader DLL if necessary. //HACKHACK get rid of this when VAC2 comes online. if ( !bModShaderDLL ) { VerifyBaseShaderDLL( hInstance ); } // Allow the DLL to try to connect to interfaces it needs if ( !pShaderDLL->Connect( Sys_GetFactoryThis(), false ) ) { g_pFullFileSystem->UnloadModule( hInstance ); return false; } // FIXME: We need to do some sort of shader validation here for anticheat. // Now replace any existing shader int nShaderDLLIndex = FindShaderDLL( pFullPath ); if ( nShaderDLLIndex >= 0 ) { UnloadShaderDLL( nShaderDLLIndex ); } else { nShaderDLLIndex = m_ShaderDLLs.AddToTail(); int nLen = Q_strlen(pFullPath) + 1; m_ShaderDLLs[nShaderDLLIndex].m_pFileName = new char[ nLen ]; Q_strncpy( m_ShaderDLLs[nShaderDLLIndex].m_pFileName, pFullPath, nLen ); } // Ok, the shader DLL's good! m_ShaderDLLs[nShaderDLLIndex].m_hInstance = hInstance; m_ShaderDLLs[nShaderDLLIndex].m_pShaderDLL = pShaderDLL; m_ShaderDLLs[nShaderDLLIndex].m_bModShaderDLL = bModShaderDLL; // Add the shaders to the dictionary of shaders... SetupShaderDictionary( nShaderDLLIndex ); // FIXME: Fix up existing materials that were using shaders that have // been reloaded? return true; } //----------------------------------------------------------------------------- // Finds an already loaded shader DLL //----------------------------------------------------------------------------- int CShaderSystem::FindShaderDLL( const char *pFullPath ) { for ( int i = m_ShaderDLLs.Count(); --i >= 0; ) { if ( !Q_stricmp( pFullPath, m_ShaderDLLs[i].m_pFileName ) ) return i; } return -1; } //----------------------------------------------------------------------------- // Unloads a particular shader DLL //----------------------------------------------------------------------------- void CShaderSystem::UnloadShaderDLL( int nShaderDLLIndex ) { if ( nShaderDLLIndex < 0 ) return; // FIXME: Do some sort of fixup of materials to determine which // materials are referencing shaders in this DLL? CleanupShaderDictionary( nShaderDLLIndex ); IShaderDLLInternal *pShaderDLL = m_ShaderDLLs[nShaderDLLIndex].m_pShaderDLL; pShaderDLL->Disconnect( pShaderDLL == GetShaderDLLInternal() ); if ( m_ShaderDLLs[nShaderDLLIndex].m_hInstance ) { g_pFullFileSystem->UnloadModule( m_ShaderDLLs[nShaderDLLIndex].m_hInstance ); } } //----------------------------------------------------------------------------- // Unloads a particular shader DLL //----------------------------------------------------------------------------- void CShaderSystem::UnloadShaderDLL( const char *pFullPath ) { int nShaderDLLIndex = FindShaderDLL( pFullPath ); if ( nShaderDLLIndex >= 0 ) { UnloadShaderDLL( nShaderDLLIndex ); delete[] m_ShaderDLLs[nShaderDLLIndex].m_pFileName; m_ShaderDLLs.Remove( nShaderDLLIndex ); } } //----------------------------------------------------------------------------- // Make sure these match the bits in imaterial.h //----------------------------------------------------------------------------- static const char* s_pShaderStateString[] = { "$debug", "$no_fullbright", "$no_draw", "$use_in_fillrate_mode", "$vertexcolor", "$vertexalpha", "$selfillum", "$additive", "$alphatest", "$multipass", "$znearer", "$model", "$flat", "$nocull", "$nofog", "$ignorez", "$decal", "$envmapsphere", "$noalphamod", "$envmapcameraspace", "$basealphaenvmapmask", "$translucent", "$normalmapalphaenvmapmask", "$softwareskin", "$opaquetexture", "$envmapmode", "$nodecal", "$halflambert", "$wireframe", "$allowalphatocoverage", "" // last one must be null }; //----------------------------------------------------------------------------- // returns strings associated with the shader state flags... // If you modify this, make sure and modify MaterialVarFlags_t in imaterial.h //----------------------------------------------------------------------------- int CShaderSystem::ShaderStateCount( ) const { return sizeof( s_pShaderStateString ) / sizeof( char* ) - 1; } //----------------------------------------------------------------------------- // returns strings associated with the shader state flags... // If you modify this, make sure and modify MaterialVarFlags_t in imaterial.h //----------------------------------------------------------------------------- char const* CShaderSystem::ShaderStateString( int i ) const { return s_pShaderStateString[i]; } //----------------------------------------------------------------------------- // Sets up the shader dictionary. //----------------------------------------------------------------------------- void CShaderSystem::SetupShaderDictionary( int nShaderDLLIndex ) { // We could have put the shader dictionary into each shader DLL // I'm not sure if that makes this system any less secure than it already is int i; ShaderDLLInfo_t &info = m_ShaderDLLs[nShaderDLLIndex]; int nCount = info.m_pShaderDLL->ShaderCount(); for ( i = 0; i < nCount; ++i ) { IShader *pShader = info.m_pShaderDLL->GetShader( i ); const char *pShaderName = pShader->GetName(); #ifdef POSIX if (CommandLine()->FindParm("-glmspew")) printf("CShaderSystem::SetupShaderDictionary: %s", pShaderName ); #endif // Make sure it doesn't try to override another shader DLL's names. if ( info.m_bModShaderDLL ) { for ( int iTestDLL=0; iTestDLL < m_ShaderDLLs.Count(); iTestDLL++ ) { ShaderDLLInfo_t *pTestDLL = &m_ShaderDLLs[iTestDLL]; if ( !pTestDLL->m_bModShaderDLL ) { if ( pTestDLL->m_ShaderDict.Find( pShaderName ) != pTestDLL->m_ShaderDict.InvalidIndex() ) { Error( "Game shader '%s' trying to override a base shader '%s'.", info.m_pFileName, pShaderName ); } } } } info.m_ShaderDict.Insert( pShaderName, pShader ); } } //----------------------------------------------------------------------------- // Cleans up the shader dictionary. //----------------------------------------------------------------------------- void CShaderSystem::CleanupShaderDictionary( int nShaderDLLIndex ) { } //----------------------------------------------------------------------------- // Finds a shader in the shader dictionary //----------------------------------------------------------------------------- IShader* CShaderSystem::FindShader( char const* pShaderName ) { // FIXME: What kind of search order should we use here? // I'm currently assuming last added, first searched. for (int i = m_ShaderDLLs.Count(); --i >= 0; ) { ShaderDLLInfo_t &info = m_ShaderDLLs[i]; unsigned short idx = info.m_ShaderDict.Find( pShaderName ); if ( idx != info.m_ShaderDict.InvalidIndex() ) { return info.m_ShaderDict[idx]; } } return NULL; } //----------------------------------------------------------------------------- // Used to iterate over all shaders for editing purposes //----------------------------------------------------------------------------- int CShaderSystem::ShaderCount() const { return GetShaders( 0, 65536, NULL ); } int CShaderSystem::GetShaders( int nFirstShader, int nMaxCount, IShader **ppShaderList ) const { CUtlSymbolTable uniqueNames( 0, 512, true ); int nCount = 0; int nActualCount = 0; for ( int i = m_ShaderDLLs.Count(); --i >= 0; ) { const ShaderDLLInfo_t &info = m_ShaderDLLs[i]; for ( unsigned short j = info.m_ShaderDict.First(); j != info.m_ShaderDict.InvalidIndex(); j = info.m_ShaderDict.Next( j ) ) { // Don't add shaders twice const char *pShaderName = info.m_ShaderDict.GetElementName( j ); if ( uniqueNames.Find( pShaderName ) != UTL_INVAL_SYMBOL ) continue; // Indicate we've seen this shader uniqueNames.AddString( pShaderName ); ++nActualCount; if ( nActualCount > nFirstShader ) { if ( ppShaderList ) { ppShaderList[ nCount ] = info.m_ShaderDict[j]; } ++nCount; if ( nCount >= nMaxCount ) return nCount; } } } return nCount; } //----------------------------------------------------------------------------- // // Methods of IShaderInit lie below // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Gets at the render pass info for this pass... //----------------------------------------------------------------------------- inline StateSnapshot_t CShaderSystem::CurrentStateSnapshot() { Assert( m_pRenderState ); Assert( m_nRenderPass < MAX_RENDER_PASSES ); Assert( m_nRenderPass < m_pRenderState->m_pSnapshots[m_nModulation].m_nPassCount ); return m_pRenderState->m_pSnapshots[m_nModulation].m_Snapshot[m_nRenderPass]; } //----------------------------------------------------------------------------- // Create debugging materials //----------------------------------------------------------------------------- void CShaderSystem::CreateDebugMaterials() { if (m_pDebugMaterials[0]) return; KeyValues *pVMTKeyValues[MATERIAL_DEBUG_COUNT]; int i; for ( i = 0; i < MATERIAL_DEBUG_COUNT; ++i ) { pVMTKeyValues[i] = new KeyValues( s_pDebugShaderName[i] ); } pVMTKeyValues[MATERIAL_DEBUG_DEPTH_DECAL]->SetInt( "$decal", 1 ); for ( i = 0; i < MATERIAL_DEBUG_COUNT; ++i ) { char shaderName[64]; Q_snprintf( shaderName, sizeof( shaderName ), "___%s_%d.vmt", s_pDebugShaderName[i], i ); m_pDebugMaterials[i] = static_cast(MaterialSystem()->CreateMaterial( shaderName, pVMTKeyValues[i] )); if( m_pDebugMaterials[i] ) m_pDebugMaterials[i] = m_pDebugMaterials[i]->GetRealTimeVersion(); } } //----------------------------------------------------------------------------- // Cleans up the debugging materials //----------------------------------------------------------------------------- void CShaderSystem::CleanUpDebugMaterials() { if (m_pDebugMaterials[0]) { for ( int i = 0; i < MATERIAL_DEBUG_COUNT; ++i ) { m_pDebugMaterials[i]->DecrementReferenceCount(); if ( m_pDebugMaterials[i]->InMaterialPage() ) { MaterialSystem()->RemoveMaterialSubRect( m_pDebugMaterials[i] ); } else { MaterialSystem()->RemoveMaterial( m_pDebugMaterials[i] ); } m_pDebugMaterials[i] = NULL; } } } //----------------------------------------------------------------------------- // Deal with buffering of spew while doing shader draw so that we don't get // recursive spew during precache due to fonts not being loaded, etc. //----------------------------------------------------------------------------- CThreadFastMutex g_StgoredSpewMutex; void CShaderSystem::BufferSpew( SpewType_t spewType, const Color &c, const char *pMsg ) { AUTO_LOCK( g_StgoredSpewMutex ); m_StoredSpew.PutInt( spewType ); m_StoredSpew.PutChar( c.r() ); m_StoredSpew.PutChar( c.g() ); m_StoredSpew.PutChar( c.b() ); m_StoredSpew.PutChar( c.a() ); m_StoredSpew.PutString( pMsg ); } void CShaderSystem::PrintBufferedSpew( void ) { AUTO_LOCK( g_StgoredSpewMutex ); while ( m_StoredSpew.GetBytesRemaining() > 0 ) { SpewType_t spewType = (SpewType_t)m_StoredSpew.GetInt(); unsigned char r, g, b, a; r = m_StoredSpew.GetChar(); g = m_StoredSpew.GetChar(); b = m_StoredSpew.GetChar(); a = m_StoredSpew.GetChar(); Color c( r, g, b, a ); int nLen = m_StoredSpew.PeekStringLength(); if ( nLen ) { char *pBuf = (char*)_alloca( nLen ); m_StoredSpew.GetStringManualCharCount( pBuf, nLen ); ColorSpewMessage( spewType, &c, "%s", pBuf ); } else { break; } } m_StoredSpew.Clear(); } static SpewRetval_t MySpewOutputFunc( SpewType_t spewType, char const *pMsg ) { AUTO_LOCK( g_StgoredSpewMutex ); Color c = *GetSpewOutputColor(); s_ShaderSystem.BufferSpew( spewType, c, pMsg ); switch( spewType ) { case SPEW_MESSAGE: case SPEW_WARNING: case SPEW_LOG: return SPEW_CONTINUE; case SPEW_ASSERT: case SPEW_ERROR: default: return SPEW_DEBUGGER; } } //----------------------------------------------------------------------------- // Deals with shader draw //----------------------------------------------------------------------------- void CShaderSystem::PrepForShaderDraw( IShader *pShader, IMaterialVar** ppParams, ShaderRenderState_t* pRenderState, int nModulation ) { Assert( !m_pRenderState ); // 360 runs the console remotely, spew cannot cause the matsys to be reentrant // 360 sidesteps the other negative affect that *all* buffered spew redirects as warning text if ( IsPC() || !IsX360() ) { Assert( !m_SaveSpewOutput ); m_SaveSpewOutput = GetSpewOutputFunc(); SpewOutputFunc( MySpewOutputFunc ); } m_pRenderState = pRenderState; m_nModulation = nModulation; m_nRenderPass = 0; } void CShaderSystem::DoneWithShaderDraw() { if ( IsPC() || !IsX360() ) { SpewOutputFunc( m_SaveSpewOutput ); PrintBufferedSpew(); m_SaveSpewOutput = NULL; } m_pRenderState = NULL; } //----------------------------------------------------------------------------- // Call the SHADER_PARAM_INIT block of the shaders //----------------------------------------------------------------------------- void CShaderSystem::InitShaderParameters( IShader *pShader, IMaterialVar **params, const char *pMaterialName ) { // Let the derived class do its thing PrepForShaderDraw( pShader, params, 0, 0 ); pShader->InitShaderParams( params, pMaterialName ); DoneWithShaderDraw(); // Set up color + alpha defaults if (!params[COLOR]->IsDefined()) { params[COLOR]->SetVecValue( 1.0f, 1.0f, 1.0f ); } if (!params[ALPHA]->IsDefined()) { params[ALPHA]->SetFloatValue( 1.0f ); } // Initialize all shader params based on their type... int i; for ( i = pShader->GetNumParams(); --i >= 0; ) { // Don't initialize parameters that are already set up if (params[i]->IsDefined()) continue; int type = pShader->GetParamType( i ); switch( type ) { case SHADER_PARAM_TYPE_TEXTURE: // Do nothing; we'll be loading in a string later break; case SHADER_PARAM_TYPE_STRING: // Do nothing; we'll be loading in a string later break; case SHADER_PARAM_TYPE_MATERIAL: params[i]->SetMaterialValue( NULL ); break; case SHADER_PARAM_TYPE_BOOL: case SHADER_PARAM_TYPE_INTEGER: params[i]->SetIntValue( 0 ); break; case SHADER_PARAM_TYPE_COLOR: params[i]->SetVecValue( 1.0f, 1.0f, 1.0f ); break; case SHADER_PARAM_TYPE_VEC2: params[i]->SetVecValue( 0.0f, 0.0f ); break; case SHADER_PARAM_TYPE_VEC3: params[i]->SetVecValue( 0.0f, 0.0f, 0.0f ); break; case SHADER_PARAM_TYPE_VEC4: params[i]->SetVecValue( 0.0f, 0.0f, 0.0f, 0.0f ); break; case SHADER_PARAM_TYPE_FLOAT: params[i]->SetFloatValue( 0 ); break; case SHADER_PARAM_TYPE_FOURCC: params[i]->SetFourCCValue( 0, 0 ); break; case SHADER_PARAM_TYPE_MATRIX: { VMatrix identity; MatrixSetIdentity( identity ); params[i]->SetMatrixValue( identity ); } break; case SHADER_PARAM_TYPE_MATRIX4X2: { VMatrix identity; MatrixSetIdentity( identity ); params[i]->SetMatrixValue( identity ); } break; default: Assert(0); } } } //----------------------------------------------------------------------------- // Call the SHADER_INIT block of the shaders //----------------------------------------------------------------------------- void CShaderSystem::InitShaderInstance( IShader *pShader, IMaterialVar **params, const char *pMaterialName, const char *pTextureGroupName ) { // Let the derived class do its thing PrepForShaderDraw( pShader, params, 0, 0 ); pShader->InitShaderInstance( params, ShaderSystem(), pMaterialName, pTextureGroupName ); DoneWithShaderDraw(); } //----------------------------------------------------------------------------- // Compute snapshots for all combinations of alpha + color modulation //----------------------------------------------------------------------------- void CShaderSystem::InitRenderStateFlags( ShaderRenderState_t* pRenderState, int numParams, IMaterialVar **params ) { // Compute vertex format and flags pRenderState->m_Flags = 0; // Make sure the shader don't force these flags. . they are automatically computed. Assert( !( pRenderState->m_Flags & SHADER_OPACITY_TRANSLUCENT ) ); Assert( !( pRenderState->m_Flags & SHADER_OPACITY_ALPHATEST ) ); Assert( !( pRenderState->m_Flags & SHADER_OPACITY_OPAQUE ) ); // If we are in release mode, just go ahead and clear in case the above is screwed up. pRenderState->m_Flags &= ~SHADER_OPACITY_MASK; /* // HACK: Also kind of gross; turn off bump lightmapping for low-end if (g_config.bUseGraphics && !HardwareConfig()->SupportsVertexAndPixelShaders()) { pRenderState->m_Flags &= ~SHADER_NEEDS_BUMPED_LIGHTMAPS; } */ /* // HACK: more grossness!!! turn off bump lightmapping if we don't have a bumpmap // Shaders should specify SHADER_NEEDS_BUMPED_LIGHTMAPS if they might need a bumpmap, // and this'll take care of getting rid of it if it isn't there. if( pRenderState->m_Flags & SHADER_NEEDS_BUMPED_LIGHTMAPS ) { pRenderState->m_Flags &= ~SHADER_NEEDS_BUMPED_LIGHTMAPS; for( int i = 0; i < numParams; i++ ) { if( stricmp( params[i]->GetName(), "$bumpmap" ) == 0 ) { if( params[i]->IsDefined() ) { const char *blah = params[i]->GetStringValue(); pRenderState->m_Flags |= SHADER_NEEDS_BUMPED_LIGHTMAPS; break; } } } } */ } //----------------------------------------------------------------------------- // Computes flags from a particular snapshot //----------------------------------------------------------------------------- void CShaderSystem::ComputeRenderStateFlagsFromSnapshot( ShaderRenderState_t* pRenderState ) { // When computing the flags, use the snapshot that has no alpha or color // modulation. When asking for translucency, we'll have to check for // alpha modulation in addition to checking the TRANSLUCENT flag. // I have to do it this way because I'm really wanting to treat alpha // modulation as a dynamic state, even though it's being used to compute // shadow state. I still want to use it to compute shadow state though // because it's somewhat complicated code that I'd rather precache. StateSnapshot_t snapshot = pRenderState->m_pSnapshots[0].m_Snapshot[0]; // Automatically compute if the snapshot is transparent or not if ( g_pShaderAPI->IsTranslucent( snapshot ) ) { pRenderState->m_Flags |= SHADER_OPACITY_TRANSLUCENT; } else { if ( g_pShaderAPI->IsAlphaTested( snapshot ) ) { pRenderState->m_Flags |= SHADER_OPACITY_ALPHATEST; } else { pRenderState->m_Flags |= SHADER_OPACITY_OPAQUE; } } #ifdef _DEBUG if( pRenderState->m_Flags & SHADER_OPACITY_TRANSLUCENT ) { Assert( !( pRenderState->m_Flags & SHADER_OPACITY_ALPHATEST ) ); Assert( !( pRenderState->m_Flags & SHADER_OPACITY_OPAQUE ) ); } if( pRenderState->m_Flags & SHADER_OPACITY_ALPHATEST ) { Assert( !( pRenderState->m_Flags & SHADER_OPACITY_TRANSLUCENT ) ); Assert( !( pRenderState->m_Flags & SHADER_OPACITY_OPAQUE ) ); } if( pRenderState->m_Flags & SHADER_OPACITY_OPAQUE ) { Assert( !( pRenderState->m_Flags & SHADER_OPACITY_ALPHATEST ) ); Assert( !( pRenderState->m_Flags & SHADER_OPACITY_TRANSLUCENT ) ); } #endif } //----------------------------------------------------------------------------- // Initializes state snapshots //----------------------------------------------------------------------------- #ifdef _DEBUG #pragma warning (disable:4189) #endif int CShaderSystem::GetModulationSnapshotCount( IMaterialVar **params ) { int nSnapshotCount = SnapshotTypeCount(); if ( !MaterialSystem()->CanUseEditorMaterials() ) { if( !IsFlag2Set( params, MATERIAL_VAR2_NEEDS_BAKED_LIGHTING_SNAPSHOTS ) ) { nSnapshotCount /= 2; } } return nSnapshotCount; } void CShaderSystem::InitStateSnapshots( IShader *pShader, IMaterialVar **params, ShaderRenderState_t* pRenderState ) { #ifdef _DEBUG if ( IsFlagSet( params, MATERIAL_VAR_DEBUG ) ) { // Putcher breakpoint here to catch the rendering of a material // marked for debugging ($debug = 1 in a .vmt file) shadow state version int x = 0; } #endif // Store off the current alpha + color modulations float alpha; float color[3]; params[COLOR]->GetVecValue( color, 3 ); alpha = params[ALPHA]->GetFloatValue( ); bool bBakedLighting = IsFlag2Set( params, MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING ); bool bFlashlight = IsFlag2Set( params, MATERIAL_VAR2_USE_FLASHLIGHT ); bool bEditor = IsFlag2Set( params, MATERIAL_VAR2_USE_EDITOR ); // bool bSupportsFlashlight = IsFlag2Set( params, MATERIAL_VAR2_SUPPORTS_FLASHLIGHT ); float white[3] = { 1, 1, 1 }; float grey[3] = { .5, .5, .5 }; int nSnapshotCount = GetModulationSnapshotCount( params ); // If the current mod does not use the flashlight, skip all flashlight snapshots (saves a ton of memory) bool bModUsesFlashlight = ( mat_supportflashlight.GetInt() != 0 ); for (int i = 0; i < nSnapshotCount; ++i) { if ( ( i & SHADER_USING_FLASHLIGHT ) && !bModUsesFlashlight ) { pRenderState->m_pSnapshots[i].m_nPassCount = 0; continue; } // Set modulation to force particular code paths if (i & SHADER_USING_COLOR_MODULATION) { params[COLOR]->SetVecValue( grey, 3 ); } else { params[COLOR]->SetVecValue( white, 3 ); } if (i & SHADER_USING_ALPHA_MODULATION) { params[ALPHA]->SetFloatValue( grey[0] ); } else { params[ALPHA]->SetFloatValue( white[0] ); } if ( i & SHADER_USING_FLASHLIGHT ) { // if ( !bSupportsFlashlight ) // { // pRenderState->m_pSnapshots[i].m_nPassCount = 0; // continue; // } SET_FLAGS2( MATERIAL_VAR2_USE_FLASHLIGHT ); } else { CLEAR_FLAGS2( MATERIAL_VAR2_USE_FLASHLIGHT ); } if ( i & SHADER_USING_EDITOR ) { SET_FLAGS2( MATERIAL_VAR2_USE_EDITOR ); } else { CLEAR_FLAGS2( MATERIAL_VAR2_USE_EDITOR ); } if ( i & SHADER_USING_FIXED_FUNCTION_BAKED_LIGHTING ) { SET_FLAGS2( MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING ); } else { CLEAR_FLAGS2( MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING ); } PrepForShaderDraw( pShader, params, pRenderState, i ); // Now snapshot how we're going to draw pRenderState->m_pSnapshots[i].m_nPassCount = 0; pShader->DrawElements( params, i, g_pShaderShadow, 0, VERTEX_COMPRESSION_NONE, &(pRenderState->m_pSnapshots[i].m_pContextData[0] ) ); DoneWithShaderDraw(); } // Restore alpha + color modulation params[COLOR]->SetVecValue( color, 3 ); params[ALPHA]->SetFloatValue( alpha ); if( bBakedLighting ) { SET_FLAGS2( MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING ); } else { CLEAR_FLAGS2( MATERIAL_VAR2_USE_FIXED_FUNCTION_BAKED_LIGHTING ); } if( bEditor ) { SET_FLAGS2( MATERIAL_VAR2_USE_EDITOR ); } else { CLEAR_FLAGS2( MATERIAL_VAR2_USE_EDITOR ); } if( bFlashlight ) { SET_FLAGS2( MATERIAL_VAR2_USE_FLASHLIGHT ); } else { CLEAR_FLAGS2( MATERIAL_VAR2_USE_FLASHLIGHT ); } } #ifdef _DEBUG #pragma warning (default:4189) #endif //----------------------------------------------------------------------------- // Helper to count texture coordinates //----------------------------------------------------------------------------- static int NumTextureCoordinates( VertexFormat_t vertexFormat ) { // FIXME: this is a duplicate of the function in meshdx8.cpp int nTexCoordCount = 0; for ( int i = 0; i < VERTEX_MAX_TEXTURE_COORDINATES; ++i ) { if ( TexCoordSize( i, vertexFormat ) == 0 ) continue; ++nTexCoordCount; } return nTexCoordCount; } //----------------------------------------------------------------------------- // Displays the vertex format //----------------------------------------------------------------------------- static void OutputVertexFormat( VertexFormat_t format ) { // FIXME: this is a duplicate of the function in meshdx8.cpp VertexCompressionType_t compressionType = CompressionType( format ); if( format & VERTEX_POSITION ) { Warning( "VERTEX_POSITION|" ); } if( format & VERTEX_NORMAL ) { if ( compressionType == VERTEX_COMPRESSION_ON ) Warning( "VERTEX_NORMAL[COMPRESSED]|" ); else Warning( "VERTEX_NORMAL|" ); } if( format & VERTEX_COLOR ) { Warning( "VERTEX_COLOR|" ); } if( format & VERTEX_SPECULAR ) { Warning( "VERTEX_SPECULAR|" ); } if( format & VERTEX_TANGENT_S ) { Warning( "VERTEX_TANGENT_S|" ); } if( format & VERTEX_TANGENT_T ) { Warning( "VERTEX_TANGENT_T|" ); } if( format & VERTEX_BONE_INDEX ) { Warning( "VERTEX_BONE_INDEX|" ); } if( format & VERTEX_FORMAT_VERTEX_SHADER ) { Warning( "VERTEX_FORMAT_VERTEX_SHADER|" ); } Warning( "\nBone weights: %d\n", NumBoneWeights( format ) ); Warning( "user data size: %d (%s)\n", UserDataSize( format ), ( CompressionType( format ) == VERTEX_COMPRESSION_ON ? "compressed" : "uncompressed" ) ); Warning( "num tex coords: %d\n", NumTextureCoordinates( format ) ); // NOTE: This doesn't print texcoord sizes. } #ifdef _DEBUG static bool IsVertexFormatSubsetOfVertexformat( VertexFormat_t subset, VertexFormat_t superset ) { subset &= ~VERTEX_FORMAT_USE_EXACT_FORMAT; superset &= ~VERTEX_FORMAT_USE_EXACT_FORMAT; // Test the flags if( VertexFlags( subset ) & VertexFlags( ~superset ) ) return false; // Test bone weights if( NumBoneWeights( subset ) > NumBoneWeights( superset ) ) return false; // Test user data size if( UserDataSize( subset ) > UserDataSize( superset ) ) return false; // Test the texcoord dimensions for( int i = 0; i < VERTEX_MAX_TEXTURE_COORDINATES; i++ ) { if( TexCoordSize( i, subset ) > TexCoordSize( i, superset ) ) return false; } return true; } #endif //----------------------------------------------------------------------------- // Adds state snapshots to the render list //----------------------------------------------------------------------------- static void AddSnapshotsToList( RenderPassList_t *pPassList, int &nSnapshotID, StateSnapshot_t *pSnapshots ) { int nNumPassSnapshots = pPassList->m_nPassCount; for( int i = 0; i < nNumPassSnapshots; ++i ) { pSnapshots[nSnapshotID] = pPassList->m_Snapshot[i]; nSnapshotID++; } } //----------------------------------------------------------------------------- // Computes vertex format + usage from a particular snapshot //----------------------------------------------------------------------------- bool CShaderSystem::ComputeVertexFormatFromSnapshot( IMaterialVar **params, ShaderRenderState_t* pRenderState ) { // When computing the usage, use the snapshot that has no alpha or color // modulation. We need the usage + format to be the same for all // combinations of alpha + color modulation, though, or we are asking for // trouble. int nModulationSnapshotCount = GetModulationSnapshotCount( params ); int numSnapshots = pRenderState->m_pSnapshots[0].m_nPassCount; if (nModulationSnapshotCount >= SHADER_USING_FLASHLIGHT) { numSnapshots += pRenderState->m_pSnapshots[SHADER_USING_FLASHLIGHT].m_nPassCount; } if ( MaterialSystem()->CanUseEditorMaterials() ) { numSnapshots += pRenderState->m_pSnapshots[SHADER_USING_EDITOR].m_nPassCount; } StateSnapshot_t* pSnapshots = (StateSnapshot_t*)stackalloc( numSnapshots * sizeof(StateSnapshot_t) ); int snapshotID = 0; AddSnapshotsToList( &pRenderState->m_pSnapshots[0], snapshotID, pSnapshots ); if (nModulationSnapshotCount >= SHADER_USING_FLASHLIGHT) { AddSnapshotsToList( &pRenderState->m_pSnapshots[SHADER_USING_FLASHLIGHT], snapshotID, pSnapshots ); } if ( MaterialSystem()->CanUseEditorMaterials() ) { AddSnapshotsToList( &pRenderState->m_pSnapshots[SHADER_USING_EDITOR], snapshotID, pSnapshots ); } Assert( snapshotID == numSnapshots ); pRenderState->m_VertexUsage = g_pShaderAPI->ComputeVertexUsage( numSnapshots, pSnapshots ); pRenderState->m_MorphFormat = g_pShaderAPI->ComputeMorphFormat( numSnapshots, pSnapshots ); #ifdef _DEBUG // Make sure all modulation combinations match vertex usage for ( int mod = 1; mod < nModulationSnapshotCount; ++mod ) { int numSnapshotsTest = pRenderState->m_pSnapshots[mod].m_nPassCount; StateSnapshot_t* pSnapshotsTest = (StateSnapshot_t*)_alloca( numSnapshotsTest * sizeof(StateSnapshot_t) ); for (int i = 0; i < numSnapshotsTest; ++i) { pSnapshotsTest[i] = pRenderState->m_pSnapshots[mod].m_Snapshot[i]; } VertexFormat_t usageTest = g_pShaderAPI->ComputeVertexUsage( numSnapshotsTest, pSnapshotsTest ); Assert( IsVertexFormatSubsetOfVertexformat( usageTest, pRenderState->m_VertexUsage ) ); } #endif if ( IsPC() ) { pRenderState->m_VertexFormat = g_pShaderAPI->ComputeVertexFormat( numSnapshots, pSnapshots ); } else { pRenderState->m_VertexFormat = pRenderState->m_VertexUsage; } return true; } //----------------------------------------------------------------------------- // go through each param and make sure it is the right type, load textures, // compute state snapshots and vertex types, etc. //----------------------------------------------------------------------------- bool CShaderSystem::InitRenderState( IShader *pShader, int numParams, IMaterialVar **params, ShaderRenderState_t* pRenderState, char const* pMaterialName ) { Assert( !m_pRenderState ); // Initialize render state flags InitRenderStateFlags( pRenderState, numParams, params ); // Compute state snapshots for each combination of alpha + color InitStateSnapshots( pShader, params, pRenderState ); // Compute other infomation for the render state based on snapshots if (pRenderState->m_pSnapshots[0].m_nPassCount == 0) { Warning( "Material \"%s\":\n No render states in shader \"%s\"\n", pMaterialName, pShader->GetName() ); return false; } // Set a couple additional flags based on the render state ComputeRenderStateFlagsFromSnapshot( pRenderState ); // Compute the vertex format + usage from the snapshot if ( !ComputeVertexFormatFromSnapshot( params, pRenderState ) ) { // warn.. return a null render state... Warning("Material \"%s\":\n Shader \"%s\" can't be used with models!\n", pMaterialName, pShader->GetName() ); CleanupRenderState( pRenderState ); return false; } return true; } // When you're done with the shader, be sure to call this to clean up void CShaderSystem::CleanupRenderState( ShaderRenderState_t* pRenderState ) { if (pRenderState) { int nSnapshotCount = SnapshotTypeCount(); // kill context data // Indicate no passes for any of the snapshot lists RenderPassList_t *pTemp = pRenderState->m_pSnapshots; for(int i = 0; i < nSnapshotCount; i++ ) { for(int j = 0 ; j < pRenderState->m_pSnapshots[i].m_nPassCount; j++ ) if ( pTemp[i].m_pContextData[j] ) { delete pTemp[i].m_pContextData[j]; pTemp[i].m_pContextData[j] = NULL; } pRenderState->m_pSnapshots[i].m_nPassCount = 0; } } } //----------------------------------------------------------------------------- // Does the rendering! //----------------------------------------------------------------------------- void CShaderSystem::DrawElements( IShader *pShader, IMaterialVar **params, ShaderRenderState_t* pRenderState, VertexCompressionType_t vertexCompression, uint32 nMaterialVarChangeTimeStamp ) { VPROF("CShaderSystem::DrawElements"); g_pShaderAPI->InvalidateDelayedShaderConstants(); // Compute modulation... int mod = pShader->ComputeModulationFlags( params, g_pShaderAPI ); // No snapshots? do nothing. if ( pRenderState->m_pSnapshots[mod].m_nPassCount == 0 ) return; // If we're rendering a model, gotta have skinning matrices int materialVarFlags = params[FLAGS]->GetIntValue(); if ( (( materialVarFlags & MATERIAL_VAR_MODEL ) != 0) || ( IsFlag2Set( params, MATERIAL_VAR2_SUPPORTS_HW_SKINNING ) && ( g_pShaderAPI->GetCurrentNumBones() > 0 )) ) { g_pShaderAPI->SetSkinningMatrices( ); } // FIXME: need one conditional that we calculate once a frame for debug or not with everything debug under that. #ifndef DX_TO_GL_ABSTRACTION if ( ( ( g_config.bMeasureFillRate || g_config.bVisualizeFillRate ) && ( ( materialVarFlags & MATERIAL_VAR_USE_IN_FILLRATE_MODE ) == 0 ) ) ) { DrawMeasureFillRate( pRenderState, mod, vertexCompression ); } else #endif if( ( g_config.bShowNormalMap || g_config.nShowMipLevels == 2 ) && ( IsFlag2Set( params, MATERIAL_VAR2_LIGHTING_BUMPED_LIGHTMAP ) || IsFlag2Set( params, MATERIAL_VAR2_DIFFUSE_BUMPMAPPED_MODEL ) ) ) { DrawNormalMap( pShader, params, vertexCompression ); } #if defined(DEBUG_DEPTH) else if ( mat_debugdepth.GetInt() && ((materialVarFlags & MATERIAL_VAR_NO_DEBUG_OVERRIDE) == 0) ) { int nIndex = 0; if ( IsFlagSet( params, MATERIAL_VAR_DECAL ) ) { nIndex |= 0x1; } IMaterialInternal *pDebugMaterial = m_pDebugMaterials[ MATERIAL_DEBUG_DEPTH + nIndex ]; if ( !g_pShaderAPI->IsDepthWriteEnabled( pRenderState->m_Snapshots[mod].m_Snapshot[0] ) ) { pDebugMaterial = m_pDebugMaterials[MATERIAL_DEBUG_WIREFRAME]; } DrawUsingMaterial( pDebugMaterial, vertexCompression ); } #endif else { g_pShaderAPI->SetDefaultState(); // If we're rendering flat, turn on flat mode... if (materialVarFlags & MATERIAL_VAR_FLAT) { g_pShaderAPI->ShadeMode( SHADER_FLAT ); } PrepForShaderDraw( pShader, params, pRenderState, mod ); g_pShaderAPI->BeginPass( CurrentStateSnapshot() ); CBasePerMaterialContextData ** pContextDataPtr = &( m_pRenderState->m_pSnapshots[m_nModulation].m_pContextData[m_nRenderPass] ); if ( *pContextDataPtr && ( (*pContextDataPtr)->m_nVarChangeID != nMaterialVarChangeTimeStamp ) ) { (*pContextDataPtr)->m_bMaterialVarsChanged = true; (*pContextDataPtr)->m_nVarChangeID = nMaterialVarChangeTimeStamp; } pShader->DrawElements( params, mod, 0, g_pShaderAPI, vertexCompression, &( m_pRenderState->m_pSnapshots[m_nModulation].m_pContextData[m_nRenderPass] ) ); DoneWithShaderDraw(); } MaterialSystem()->ForceDepthFuncEquals( false ); } //----------------------------------------------------------------------------- // Are we using graphics? //----------------------------------------------------------------------------- bool CShaderSystem::IsUsingGraphics() const { // YWB Hack if running with -noshaderapi/-makereslists this forces materials to "precache" which means they will resolve their .vtf files for // things like normal/height/dudv maps... if ( m_bForceUsingGraphicsReturnTrue ) return true; return g_pShaderDevice->IsUsingGraphics(); } //----------------------------------------------------------------------------- // Are we using the editor materials? //----------------------------------------------------------------------------- bool CShaderSystem::CanUseEditorMaterials() const { return MaterialSystem()->CanUseEditorMaterials(); } //----------------------------------------------------------------------------- // Takes a snapshot //----------------------------------------------------------------------------- void CShaderSystem::TakeSnapshot( ) { Assert( m_pRenderState ); Assert( m_nModulation < SnapshotTypeCount() ); if( g_pHardwareConfig->SupportsPixelShaders_2_b() ) { //enable linear->gamma srgb conversion lookup texture g_pShaderShadow->EnableTexture( SHADER_SAMPLER15, true ); g_pShaderShadow->EnableSRGBRead( SHADER_SAMPLER15, true ); } RenderPassList_t& snapshotList = m_pRenderState->m_pSnapshots[m_nModulation]; // Take a snapshot... snapshotList.m_Snapshot[snapshotList.m_nPassCount] = g_pShaderAPI->TakeSnapshot(); ++snapshotList.m_nPassCount; } //----------------------------------------------------------------------------- // Draws a snapshot //----------------------------------------------------------------------------- void CShaderSystem::DrawSnapshot( bool bMakeActualDrawCall ) { Assert( m_pRenderState ); RenderPassList_t& snapshotList = m_pRenderState->m_pSnapshots[m_nModulation]; int nPassCount = snapshotList.m_nPassCount; Assert( m_nRenderPass < nPassCount ); if ( bMakeActualDrawCall ) { g_pShaderAPI->RenderPass( m_nRenderPass, nPassCount ); } g_pShaderAPI->InvalidateDelayedShaderConstants(); if (++m_nRenderPass < nPassCount) { g_pShaderAPI->BeginPass( CurrentStateSnapshot() ); } } //----------------------------------------------------------------------------- // // Debugging material methods below // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Draws a using a particular material.. //----------------------------------------------------------------------------- void CShaderSystem::DrawUsingMaterial( IMaterialInternal *pMaterial, VertexCompressionType_t vertexCompression ) { ShaderRenderState_t *pRenderState = pMaterial->GetRenderState(); g_pShaderAPI->SetDefaultState( ); IShader *pShader = pMaterial->GetShader(); int nMod = pShader->ComputeModulationFlags( pMaterial->GetShaderParams(), g_pShaderAPI ); PrepForShaderDraw( pShader, pMaterial->GetShaderParams(), pRenderState, nMod ); g_pShaderAPI->BeginPass( pRenderState->m_pSnapshots[nMod].m_Snapshot[0] ); pShader->DrawElements( pMaterial->GetShaderParams(), nMod, 0, g_pShaderAPI, vertexCompression, &( pRenderState->m_pSnapshots[nMod].m_pContextData[0] ) ); DoneWithShaderDraw( ); } //----------------------------------------------------------------------------- // Copies material vars //----------------------------------------------------------------------------- void CShaderSystem::CopyMaterialVarToDebugShader( IMaterialInternal *pDebugMaterial, IShader *pShader, IMaterialVar **ppParams, const char *pSrcVarName, const char *pDstVarName ) { bool bFound; IMaterialVar *pMaterialVar = pDebugMaterial->FindVar( pDstVarName ? pDstVarName : pSrcVarName, &bFound ); Assert( bFound ); for( int i = pShader->GetNumParams(); --i >= 0; ) { if( !Q_stricmp( ppParams[i]->GetName( ), pSrcVarName ) ) { pMaterialVar->CopyFrom( ppParams[i] ); return; } } pMaterialVar->SetUndefined(); } //----------------------------------------------------------------------------- // Draws the puppy in fill rate mode... //----------------------------------------------------------------------------- void CShaderSystem::DrawMeasureFillRate( ShaderRenderState_t* pRenderState, int mod, VertexCompressionType_t vertexCompression ) { int nPassCount = pRenderState->m_pSnapshots[mod].m_nPassCount; // We require the use of a vertex shader rather than fixed function transforms Assert( (VertexFlags(pRenderState->m_VertexFormat) & VERTEX_FORMAT_VERTEX_SHADER) != 0 ); IMaterialInternal *pMaterial = m_pDebugMaterials[ MATERIAL_FILL_RATE ]; bool bFound; IMaterialVar *pMaterialVar = pMaterial->FindVar( "$passcount", &bFound ); pMaterialVar->SetIntValue( nPassCount ); DrawUsingMaterial( pMaterial, vertexCompression ); } //----------------------------------------------------------------------------- // Draws normalmaps //----------------------------------------------------------------------------- void CShaderSystem::DrawNormalMap( IShader *pShader, IMaterialVar **ppParams, VertexCompressionType_t vertexCompression ) { IMaterialInternal *pDebugMaterial = m_pDebugMaterials[MATERIAL_DEBUG_NORMALMAP]; if( !g_config.m_bFastNoBump ) { CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$bumpmap" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$bumpframe" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$bumptransform" ); } else { bool bFound; IMaterialVar *pMaterialVar = pDebugMaterial->FindVar( "$bumpmap", &bFound ); Assert( bFound ); pMaterialVar->SetUndefined(); } DrawUsingMaterial( pDebugMaterial, vertexCompression ); } //----------------------------------------------------------------------------- // Draws envmapmask //----------------------------------------------------------------------------- bool CShaderSystem::DrawEnvmapMask( IShader *pShader, IMaterialVar **ppParams, ShaderRenderState_t* pRenderState, VertexCompressionType_t vertexCompression ) { // FIXME! Make this work with fixed function. int vertexFormat = pRenderState->m_VertexFormat; bool bUsesVertexShader = (VertexFlags(vertexFormat) & VERTEX_FORMAT_VERTEX_SHADER) != 0; if( !bUsesVertexShader ) { Assert( 0 ); return false; } IMaterialInternal *pDebugMaterial = m_pDebugMaterials[ MATERIAL_DEBUG_ENVMAPMASK ]; bool bFound; IMaterialVar *pMaterialVar = pDebugMaterial->FindVar( "$showalpha", &bFound ); Assert( bFound ); if( IsFlagSet( ppParams, MATERIAL_VAR_NORMALMAPALPHAENVMAPMASK ) ) { // $bumpmap CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$bumpmap", "$basetexture" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$bumpframe", "$frame" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$bumptransform", "$basetexturetransform" ); pMaterialVar->SetIntValue( 1 ); } else if( IsFlagSet( ppParams, MATERIAL_VAR_BASEALPHAENVMAPMASK ) ) { // $basealphaenvmapmask CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$basetexture" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$frame" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$basetexturetransform" ); pMaterialVar->SetIntValue( 1 ); } else { // $envmapmask CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$envmapmask", "$basetexture" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$envmapmaskframe", "$frame" ); CopyMaterialVarToDebugShader( pDebugMaterial, pShader, ppParams, "$envmapmasktransform", "$basetexturetransform" ); pMaterialVar->SetIntValue( 0 ); } if( pDebugMaterial->FindVar( "$basetexture", NULL )->IsTexture() ) { DrawUsingMaterial( pDebugMaterial, vertexCompression ); return true; } else { return false; } } //----------------------------------------------------------------------------- // // Methods of IShaderSystem lie below // //----------------------------------------------------------------------------- ShaderAPITextureHandle_t CShaderSystem::GetShaderAPITextureBindHandle( ITexture *pTexture, int nFrame, int nTextureChannel ) { Assert( !IsTextureInternalEnvCubemap( static_cast(pTexture) ) ); // Bind away baby if( pTexture ) { // This is ugly. Basically, this is yet another way that textures can be bound. They don't get bound here, // but the return is only used to bind them for semistatic command buffer building, which doesn't go through // CTexture::Bind for whatever reason. So let's request the mipmaps here. If you run into this, in a situation // where we shouldn't be doing the request, we could relocate this code to the appropriate callsites instead. ITextureInternal* pTex = assert_cast< ITextureInternal* >( pTexture ); TextureManager()->RequestAllMipmaps( pTex ); return pTex->GetTextureHandle( nFrame, nTextureChannel ); } else return INVALID_SHADERAPI_TEXTURE_HANDLE; } //----------------------------------------------------------------------------- // Binds a texture //----------------------------------------------------------------------------- void CShaderSystem::BindTexture( Sampler_t sampler1, ITexture *pTexture, int nFrame /* = 0 */ ) { // The call to IMaterialVar::GetTextureValue should have converted this to a real thing Assert( !IsTextureInternalEnvCubemap( static_cast(pTexture) ) ); // Bind away baby if( pTexture ) { static_cast(pTexture)->Bind( sampler1, nFrame ); } } void CShaderSystem::BindTexture( Sampler_t sampler1, Sampler_t sampler2, ITexture *pTexture, int nFrame /* = 0 */ ) { // The call to IMaterialVar::GetTextureValue should have converted this to a real thing Assert( !IsTextureInternalEnvCubemap( static_cast(pTexture) ) ); // Bind away baby if( pTexture ) { if ( sampler2 == Sampler_t(-1) ) { static_cast(pTexture)->Bind( sampler1, nFrame ); } else { static_cast(pTexture)->Bind( sampler1, nFrame, sampler2 ); } } } //----------------------------------------------------------------------------- // // Methods of IShaderInit lie below // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Loads a texture //----------------------------------------------------------------------------- void CShaderSystem::LoadTexture( IMaterialVar *pTextureVar, const char *pTextureGroupName, int nAdditionalCreationFlags /* = 0 */ ) { if (pTextureVar->GetType() != MATERIAL_VAR_TYPE_STRING) { // This here will cause 'UNDEFINED' material vars if (pTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE) { pTextureVar->SetTextureValue( TextureManager()->ErrorTexture() ); } return; } // In this case, we have to convert the string into a texture value const char *pName = pTextureVar->GetStringValue(); // Fix cases where people stupidly put a slash at the front of the vtf filename in a vmt. Causes trouble elsewhere. if ( pName[0] == CORRECT_PATH_SEPARATOR || pName[1] == CORRECT_PATH_SEPARATOR ) ++pName; ITextureInternal *pTexture; // Force local cubemaps when using the editor if ( MaterialSystem()->CanUseEditorMaterials() && ( stricmp( pName, "env_cubemap" ) == 0 ) ) { pTexture = (ITextureInternal*)-1; } else { pTexture = static_cast< ITextureInternal * >( MaterialSystem()->FindTexture( pName, pTextureGroupName, false, nAdditionalCreationFlags ) ); } if( !pTexture ) { if( !g_pShaderDevice->IsUsingGraphics() && ( stricmp( pName, "env_cubemap" ) != 0 ) ) { Warning( "Shader_t::LoadTexture: texture \"%s.vtf\" doesn't exist\n", pName ); } pTexture = TextureManager()->ErrorTexture(); } pTextureVar->SetTextureValue( pTexture ); } //----------------------------------------------------------------------------- // Loads a bumpmap //----------------------------------------------------------------------------- void CShaderSystem::LoadBumpMap( IMaterialVar *pTextureVar, const char *pTextureGroupName ) { Assert( pTextureVar ); if (pTextureVar->GetType() != MATERIAL_VAR_TYPE_STRING) { // This here will cause 'UNDEFINED' material vars if (pTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE) { pTextureVar->SetTextureValue( TextureManager()->ErrorTexture() ); } return; } // Convert a string to the actual texture ITexture *pTexture; pTexture = MaterialSystem()->FindTexture( pTextureVar->GetStringValue(), pTextureGroupName, false, 0 ); // FIXME: Make a bumpmap error texture if (!pTexture) { pTexture = TextureManager()->ErrorTexture(); } pTextureVar->SetTextureValue( pTexture ); } //----------------------------------------------------------------------------- // Loads a cubemap //----------------------------------------------------------------------------- void CShaderSystem::LoadCubeMap( IMaterialVar **ppParams, IMaterialVar *pTextureVar, int nAdditionalCreationFlags /* = 0 */ ) { if ( !HardwareConfig()->SupportsCubeMaps() ) return; if ( pTextureVar->GetType() != MATERIAL_VAR_TYPE_STRING ) { // This here will cause 'UNDEFINED' material vars if (pTextureVar->GetType() != MATERIAL_VAR_TYPE_TEXTURE) { pTextureVar->SetTextureValue( TextureManager()->ErrorTexture() ); } return; } if ( stricmp( pTextureVar->GetStringValue(), "env_cubemap" ) == 0 ) { // garymcthack // don't have to load anything here. . just set the texture value to something // special that says to use the cubemap entity. pTextureVar->SetTextureValue( ( ITexture * )-1 ); SetFlags2( ppParams, MATERIAL_VAR2_USES_ENV_CUBEMAP ); } else { ITexture *pTexture; char textureName[MAX_PATH]; Q_strncpy( textureName, pTextureVar->GetStringValue(), MAX_PATH ); if ( HardwareConfig()->GetHDRType() != HDR_TYPE_NONE ) { // Overload the texture name to ".hdr.vtf" (instead of .vtf) if we are running with // HDR enabled. Q_strncat( textureName, ".hdr", MAX_PATH, COPY_ALL_CHARACTERS ); } pTexture = MaterialSystem()->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, false, nAdditionalCreationFlags ); // FIXME: Make a cubemap error texture if ( !pTexture ) { pTexture = TextureManager()->ErrorTexture(); } pTextureVar->SetTextureValue( pTexture ); } }