Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2839 lines
79 KiB

//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
// vmpi_bareshell.cpp : Defines the entry point for the console application.
//
#include <windows.h>
#include <conio.h>
#include <process.h>
#include "vmpi.h"
#include "filesystem.h"
#include "vmpi_filesystem.h"
#include "vmpi_distribute_work.h"
#include "vmpi_tools_shared.h"
#include "cmdlib.h"
#include "UtlVector.h"
#include "Utlhash.h"
#include "UtlBuffer.h"
#include "utlstring.h"
#include "tier2/utlstreambuffer.h"
#include "UtlLinkedList.h"
#include "UtlStringMap.h"
#include "tier0/icommandline.h"
#include "tier1/strtools.h"
#include "vstdlib/jobthread.h"
#include "threads.h"
#include "tier0/dbg.h"
#include "tier1/smartptr.h"
#include "interface.h"
#include "ishadercompiledll.h"
#include <direct.h>
#include "io.h"
#include <sys/types.h>
#include <sys/stat.h>
#include "materialsystem/shader_vcs_version.h"
#include "ilaunchabledll.h"
#include <tier1/diff.h>
#include "utlnodehash.h"
#include "lzma/lzma.h"
#include "mathlib/mathlib.h"
#include "tier1/checksum_crc.h"
#include "tier0/tslist.h"
#include "tools_minidump.h"
#include "shadercompile_ps3_helpers.h"
#include "cmdsink.h"
#include "d3dxfxc.h"
#include "subprocess.h"
#include "cfgprocessor.h"
// Set this to one when working on shaders to get immediate errors rather than waiting for the end of the compile.
#define IMMEDIATEERRORS 0
// Type conversions should be controlled by programmer explicitly - shadercompile makes use of 64-bit integer arithmetics
#pragma warning( error : 4244 )
static inline uint32 uint64_as_uint32( uint64 x )
{
Assert( x < uint64( uint32( ~0 ) ) );
return uint32( x );
}
static inline UtlSymId_t int_as_symid( int x )
{
Assert( ( sizeof( UtlSymId_t ) >= sizeof( int ) ) || ( x >= 0 && x < ( int )( unsigned int )( UtlSymId_t(~0) ) ) );
return UtlSymId_t( x );
}
static bool isspace_force_valid_characters( char c )
{
return !!V_isspace( ( unsigned char )c );
}
static bool isalpha_force_valid_characters( char c )
{
return !!V_isalpha( ( unsigned char )c );
}
// VMPI packets
#define STARTWORK_PACKETID 5
#define ERRMSG_PACKETID 7
#define SHADERHADERROR_PACKETID 8
#define MACHINE_NAME 9
#ifdef _DEBUG
//#define DEBUGFP
#endif
// Dealing with job list
namespace
{
CArrayAutoPtr< CfgProcessor::CfgEntryInfo > g_arrCompileEntries;
uint64 g_numShaders = 0, g_numCompileCommands = 0, g_numStaticCombos = 0;
uint64 g_nStaticCombosPerWorkUnit = 0, g_numCompletedStaticCombos = 0, g_numCommandsCompleted = 0;
uint64 g_numSkippedStaticCombos = 0;
CfgProcessor::CfgEntryInfo const * GetEntryByStaticComboNum( uint64 nStaticCombo, uint64 *pnStaticCombo )
{
CfgProcessor::CfgEntryInfo const *pInfo;
uint64 nRemainStaticCombos = nStaticCombo;
for ( pInfo = g_arrCompileEntries.Get(); pInfo && pInfo->m_szName; ++ pInfo )
{
if ( nRemainStaticCombos >= pInfo->m_numStaticCombos )
nRemainStaticCombos -= pInfo->m_numStaticCombos;
else
break;
}
if ( pnStaticCombo )
*pnStaticCombo = nRemainStaticCombos;
return pInfo;
}
}; // `anonymous` namespace
char * PrettyPrintNumber( uint64 k )
{
static char chCompileString[50] = {0};
char *pchPrint = chCompileString + sizeof( chCompileString ) - 3;
for ( uint64 j = 0; k > 0; k /= 10, ++ j )
{
( j && !( j % 3 ) ) ? ( * pchPrint -- = ',' ) : 0;
* pchPrint -- = '0' + char( k % 10 );
}
( * ++ pchPrint ) ? 0 : ( * pchPrint = 0 );
return pchPrint;
}
const char *g_pShaderPath = NULL;
char g_WorkerTempPath[MAX_PATH];
char g_ExeDir[MAX_PATH];
#ifdef DEBUGFP
FILE *g_WorkerDebugFp = NULL;
#endif
bool g_bGotStartWorkPacket = false;
double g_flStartTime;
bool g_bVerbose = false;
bool g_bIsX360 = false;
bool g_bIsPS3 = false;
bool g_bGeneratePS3DebugInfo = false;
bool g_bOptimizePS3ShaderScheduling = false;
bool g_bSuppressWarnings = false;
FORCEINLINE long AsTargetLong( long x ) { return ( ( g_bIsX360 || g_bIsPS3 ) ? ( BigLong( x ) ) : ( x ) ); }
struct ShaderInfo_t
{
ShaderInfo_t() { memset( this, 0, sizeof( *this ) ); }
uint64 m_nShaderCombo;
uint64 m_nTotalShaderCombos;
const char *m_pShaderName;
const char *m_pShaderSrc;
unsigned m_CentroidMask;
uint64 m_nDynamicCombos;
uint64 m_nStaticCombo;
unsigned m_Flags; // from IShader.h
char m_szShaderModel[ 12 ];
};
void Shader_ParseShaderInfoFromCompileCommands( CfgProcessor::CfgEntryInfo const *pEntry, ShaderInfo_t &shaderInfo );
struct CByteCodeBlock
{
CByteCodeBlock *m_pNext, *m_pPrev;
int m_nCRC32;
uint64 m_nComboID;
size_t m_nCodeSize;
uint8 *m_ByteCode;
CByteCodeBlock( void )
{
m_ByteCode = NULL;
}
CByteCodeBlock( void const *pByteCode, size_t nCodeSize, uint64 nComboID )
{
m_ByteCode = new uint8[nCodeSize];
m_nComboID = nComboID;
m_nCodeSize = nCodeSize;
memcpy( m_ByteCode, pByteCode, nCodeSize );
m_nCRC32 = CRC32_ProcessSingleBuffer( pByteCode, nCodeSize );
}
~CByteCodeBlock( void )
{
if ( m_ByteCode )
delete[] m_ByteCode;
}
};
static int __cdecl CompareDynamicComboIDs( CByteCodeBlock * const *pA, CByteCodeBlock * const *pB )
{
if ( (*pA)->m_nComboID < (*pB)->m_nComboID )
return -1;
if ( (*pA)->m_nComboID > (*pB)->m_nComboID )
return 1;
return 0;
}
struct CStaticCombo // all the data for one static combo
{
CStaticCombo *m_pNext, *m_pPrev;
uint64 m_nStaticComboID;
CUtlVector< CByteCodeBlock* > m_DynamicCombos;
struct PackedCode : protected CArrayAutoPtr<uint8> {
size_t GetLength() const { if( uint8 *pb = Get() ) return *reinterpret_cast<size_t *>( pb ); else return 0; }
uint8 *GetData() const { if( uint8 *pb = Get() ) return pb + sizeof( size_t ); else return NULL; }
uint8 *AllocData( size_t len ) { Delete(); if ( len ) { Attach( new uint8[ len + sizeof( size_t ) ] ); *reinterpret_cast<size_t *>( Get() ) = len; } return GetData(); }
} m_abPackedCode; // Packed code for entire static combo
uint64 Key( void ) const
{
return m_nStaticComboID;
}
CStaticCombo( uint64 nComboID )
{
m_nStaticComboID = nComboID;
}
~CStaticCombo( void )
{
m_DynamicCombos.PurgeAndDeleteElements();
}
void AddDynamicCombo( uint64 nComboID, void const *pComboData, size_t nCodeSize )
{
CByteCodeBlock *pNewBlock = new CByteCodeBlock( pComboData, nCodeSize, nComboID );
m_DynamicCombos.AddToTail( pNewBlock );
}
void SortDynamicCombos( void )
{
m_DynamicCombos.Sort( CompareDynamicComboIDs );
}
uint8 *AllocPackedCodeBlock( size_t nPackedCodeSize )
{
return m_abPackedCode.AllocData( nPackedCodeSize );
}
};
typedef CUtlNodeHash<CStaticCombo, 7097, uint64> StaticComboNodeHash_t;
template <>
inline StaticComboNodeHash_t ** Construct( StaticComboNodeHash_t ** pMemory )
{
return ::new( pMemory ) StaticComboNodeHash_t *( NULL ); // Explicitly new with NULL
}
struct CShaderMap : public CUtlStringMap<StaticComboNodeHash_t *> {
;
} g_ShaderByteCode;
CStaticCombo * StaticComboFromDictAdd( char const *pszShaderName, uint64 nStaticComboId )
{
StaticComboNodeHash_t *& rpNodeHash = g_ShaderByteCode[ pszShaderName ];
if ( !rpNodeHash )
{
rpNodeHash = new StaticComboNodeHash_t;
}
// search for this static combo. make it if not found
CStaticCombo *pStaticCombo = rpNodeHash->FindByKey( nStaticComboId );
if ( !pStaticCombo )
{
pStaticCombo = new CStaticCombo( nStaticComboId );
rpNodeHash->Add( pStaticCombo );
}
return pStaticCombo;
}
CStaticCombo * StaticComboFromDict( char const *pszShaderName, uint64 nStaticComboId )
{
if ( StaticComboNodeHash_t *pNodeHash = g_ShaderByteCode[ pszShaderName ] )
return pNodeHash->FindByKey( nStaticComboId );
else
return NULL;
}
CUtlStringMap<ShaderInfo_t> g_ShaderToShaderInfo;
class CompilerMsgInfo
{
public:
CompilerMsgInfo() : m_numTimesReported( 0 ) {}
public:
void SetMsgReportedCommand( char const *szCommand, int numTimesReported = 1, const char *szMachineName = "" ) { if ( !m_numTimesReported ) { m_sFirstCommand = szCommand; if ( szMachineName ) m_sFirstMachineName = szMachineName; } m_numTimesReported += numTimesReported; }
public:
char const * GetFirstCommand() const { return m_sFirstCommand.String(); }
char const * GetFirstMachineName() const { return m_sFirstMachineName.String(); }
int GetNumTimesReported() const { return m_numTimesReported; }
protected:
CUtlString m_sFirstCommand;
CUtlString m_sFirstMachineName;
int m_numTimesReported;
};
CUtlStringMap<bool> g_Master_ShaderHadError;
CUtlStringMap<bool> g_Master_ShaderWrittenToDisk;
CUtlStringMap<CompilerMsgInfo> g_Master_CompilerMsgInfo;
namespace Threading
{
enum Mode { eSingleThreaded = 0, eMultiThreaded = 1 };
// A special object that makes single-threaded code incur no penalties
// and multithreaded code to be synchronized properly.
template < class MT_MUTEX_TYPE = CThreadFastMutex >
class CSwitchableMutex
{
public:
public:
FORCEINLINE explicit CSwitchableMutex( Mode eMode, MT_MUTEX_TYPE *pMtMutex = NULL ) : m_pMtx( pMtMutex ), m_pUseMtx( eMode ? pMtMutex : NULL ) {}
public:
FORCEINLINE void SetMtMutex( MT_MUTEX_TYPE *pMtMutex ) { m_pMtx = pMtMutex; m_pUseMtx = ( m_pUseMtx ? pMtMutex : NULL ); }
FORCEINLINE void SetThreadedMode( Mode eMode ) { m_pUseMtx = ( eMode ? m_pMtx : NULL ); }
public:
FORCEINLINE void Lock() { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) pUseMtx->Lock(); }
FORCEINLINE void Unlock() { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) pUseMtx->Unlock(); }
FORCEINLINE bool TryLock() { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) return pUseMtx->TryLock(); else return true; }
FORCEINLINE bool AssertOwnedByCurrentThread() { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) return pUseMtx->AssertOwnedByCurrentThread(); else return true; }
FORCEINLINE void SetTrace( bool b ) { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) pUseMtx->SetTrace( b ); }
FORCEINLINE uint32 GetOwnerId() { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) return pUseMtx->GetOwnerId(); else return 0; }
FORCEINLINE int GetDepth() { if ( MT_MUTEX_TYPE *pUseMtx = m_pUseMtx ) return pUseMtx->GetDepth(); else return 0; }
private:
MT_MUTEX_TYPE *m_pMtx;
CInterlockedPtr< MT_MUTEX_TYPE > m_pUseMtx;
};
namespace Private
{
typedef CThreadMutex MtMutexType_t;
MtMutexType_t g_mtxSyncObjMT;
}; // namespace Private
CSwitchableMutex< Private::MtMutexType_t > g_mtxGlobal( eSingleThreaded, &Private::g_mtxSyncObjMT );
class CGlobalMutexAutoLock
{
public:
CGlobalMutexAutoLock() { g_mtxGlobal.Lock(); }
~CGlobalMutexAutoLock() { g_mtxGlobal.Unlock(); }
};
}; // namespace Threading
// Access to global data should be synchronized by these global locks
#define GLOBAL_DATA_MTX_LOCK() Threading::g_mtxGlobal.Lock()
#define GLOBAL_DATA_MTX_UNLOCK() Threading::g_mtxGlobal.Unlock()
#define GLOBAL_DATA_MTX_LOCK_AUTO Threading::CGlobalMutexAutoLock UNIQUE_ID;
unsigned long VMPI_Stats_GetJobWorkerID( void )
{
return 0;
}
bool StartWorkDispatch( MessageBuffer *pBuf, int iSource, int iPacketID )
{
g_bGotStartWorkPacket = true;
return true;
}
CDispatchReg g_StartWorkReg( STARTWORK_PACKETID, StartWorkDispatch );
CDispatchReg g_PS3ShaderDebugInfoReg( PS3_SHADER_DEBUG_INFO_PACKETID, PS3ShaderDebugInfoDispatch );
CDispatchReg g_PS3ShaderCompileLogReg( PS3_SHADER_COMPILE_LOG_PACKETID, PS3ShaderCompileLogDispatch );
// Consume all characters for which (isspace) is true
template < typename T >
char * ConsumeCharacters( char *szString, T pred )
{
if ( szString )
{
while ( *szString && pred( *szString ) )
{
++ szString;
}
}
return szString;
}
char * FindNext( char *szString, char *szSearchSet )
{
bool bFound = (szString == NULL);
char *szNext = NULL;
if ( szString && szSearchSet )
{
for ( ; *szSearchSet; ++ szSearchSet )
{
if ( char *szTmp = strchr( szString, *szSearchSet ) )
{
szNext = bFound ? ( min( szNext, szTmp ) ) : szTmp;
bFound = true;
}
}
}
return bFound ? szNext : ( szString + strlen( szString ) );
}
char * FindLast( char *szString, char *szSearchSet )
{
bool bFound = (szString != NULL);
char *szNext = NULL;
if ( szString && szSearchSet )
{
for ( ; *szSearchSet; ++ szSearchSet )
{
if ( char *szTmp = strrchr( szString, *szSearchSet ) )
{
szNext = bFound ? ( max( szNext, szTmp ) ) : szTmp;
bFound = true;
}
}
}
return bFound ? szNext : ( szString + strlen( szString ) );
}
void ErrMsgDispatchMsgLine( char const *szCommand, char *szMsgLine, char const *szShaderName = NULL )
{
// When the filename is specified in front of the message, make sure it is truncated to the bare name only
if ( isalpha_force_valid_characters( *szMsgLine ) && szMsgLine[1] == ':' )
{
// Preceded by drive letter
szMsgLine += 2;
}
// Trim the path from the msg
// e.g. make string
// c:\temp\shadercompiletemp\1234\myfile.fxc(435): warning X3083: Truncating ...
// look like
// myfile.fxc(435): warning X3083: Truncating ...
// which will be both readable and same coming from different worker machines
char *szEndFileLinePlant = FindNext( szMsgLine, ":" );
if ( ':' == *szEndFileLinePlant )
{
*szEndFileLinePlant = 0;
if ( char *szLastSlash = FindLast( szMsgLine, "\\/" ) )
{
if ( *szLastSlash )
{
*szLastSlash = 0;
szMsgLine = szLastSlash + 1;
}
}
*szEndFileLinePlant = ':';
}
// If the shader file name is not given in the message add it
if ( szShaderName )
{
static char chFitLongMsgLine[4096];
if ( *szMsgLine == '(' )
{
sprintf( chFitLongMsgLine, "%s%s", szShaderName, szMsgLine );
szMsgLine = chFitLongMsgLine;
}
else if ( !strncmp( szMsgLine, "memory(", 7 ) )
{
sprintf( chFitLongMsgLine, "%s%s", szShaderName, szMsgLine+6 );
szMsgLine = chFitLongMsgLine;
}
}
// Now store the message with the command it was generated from
g_Master_CompilerMsgInfo[ szMsgLine ].SetMsgReportedCommand( szCommand, 1, VMPI_GetLocalMachineName() );
}
void ErrMsgDispatchInt( char *szMessage, char const *szShaderName = NULL )
{
// First line is the command number "szCommand"
char *szCommand = ConsumeCharacters( szMessage, V_isspace );
char *szMessageListing = FindNext(szCommand, "\r\n");
char chTerminator = *szMessageListing;
*( szMessageListing ++ ) = 0;
// Now come the command lines actually
while ( chTerminator )
{
char *szMsgText = ConsumeCharacters( szMessageListing, isspace_force_valid_characters );
szMessageListing = FindNext( szMsgText, "\r\n" );
chTerminator = *szMessageListing;
*( szMessageListing ++ ) = 0;
if( *szMsgText )
{
// Trim command at redirection character if present
* FindNext( szCommand, ">" ) = 0;
ErrMsgDispatchMsgLine( szCommand, szMsgText, szShaderName );
}
}
}
//
// BUFFER:
// 1 byte = * = buffer type
//
// string = message
// 1 byte = \n = newline delimiting the message
//
// string = command that first encountered the message
// 1 byte = \n = newline delimiting the command
//
// string = printed number of times the message was encountered
// 1 byte = \n = newline delimiting the number
//
// 1 byte = 0 = null-terminator for the buffer
//
bool ErrMsgDispatch( MessageBuffer *pBuf, int iSource, int iPacketID )
{
GLOBAL_DATA_MTX_LOCK_AUTO;
bool bInvalidPkgRetCode = true;
// Parse the err msg packet
char *szMsgLine = pBuf->data + 1;
char *szCommand = FindNext( szMsgLine, "\n" );
if ( !*szCommand )
return bInvalidPkgRetCode;
*( szCommand ++ ) = 0;
char *szNumTimesReported = FindNext( szCommand, "\n" );
if ( !*szNumTimesReported )
return bInvalidPkgRetCode;
*( szNumTimesReported ++ ) = 0;
char *szTerminator = FindNext( szNumTimesReported, "\n" );
if ( !*szTerminator )
return bInvalidPkgRetCode;
*( szTerminator ++ ) = 0;
#if IMMEDIATEERRORS
char str[ 4096 ];
uint64 iFirstCommand = _strtoui64( szCommand, NULL, 10 );
CfgProcessor::ComboHandle hCombo = NULL;
CfgProcessor::CfgEntryInfo const *pComboEntryInfo = NULL;
if ( CfgProcessor::Combo_GetNext( iFirstCommand, hCombo, g_numCompileCommands ) )
{
Combo_FormatCommand( hCombo, str );
pComboEntryInfo = Combo_GetEntryInfo( hCombo );
Combo_Free( hCombo );
}
else
{
sprintf( str, "cmd # %s", szCommand );
}
fprintf( stderr, "%s\n%s\nMachine: %s\n", szMsgLine, str, VMPI_GetMachineName( iSource ) );
#endif
// Set the msg info
g_Master_CompilerMsgInfo[ szMsgLine ].SetMsgReportedCommand( szCommand, atoi( szNumTimesReported ), VMPI_GetMachineName( iSource ) );
return true;
}
CDispatchReg g_ErrMsgReg( ERRMSG_PACKETID, ErrMsgDispatch );
void ShaderHadErrorDispatchInt( char const *szShader )
{
g_Master_ShaderHadError[ szShader ] = true;
}
//
// BUFFER:
// 1 byte = * = buffer type
//
// string = shader name
// 1 byte = 0 = null-terminator for the name
//
bool ShaderHadErrorDispatch( MessageBuffer *pBuf, int iSource, int iPacketID )
{
GLOBAL_DATA_MTX_LOCK_AUTO;
ShaderHadErrorDispatchInt( pBuf->data + 1 );
return true;
}
CDispatchReg g_ShaderHadErrorReg( SHADERHADERROR_PACKETID, ShaderHadErrorDispatch );
void DebugOut( const char *pMsg, ... )
{
if (g_bVerbose)
{
char msg[2048];
va_list marker;
va_start( marker, pMsg );
_vsnprintf( msg, sizeof( msg ), pMsg, marker );
va_end( marker );
Msg( "%s", msg );
#ifdef DEBUGFP
fprintf( g_WorkerDebugFp, "%s", msg );
fflush( g_WorkerDebugFp );
#endif
}
}
void Vmpi_Worker_DefaultDisconnectHandler( int procID, const char *pReason )
{
Msg( "Master disconnected.\n ");
DebugOut( "Master disconnected.\n" );
TerminateProcess( GetCurrentProcess(), 1 );
}
typedef void ( * DisconnectHandlerFn_t )( int procID, const char *pReason );
DisconnectHandlerFn_t g_fnDisconnectHandler = Vmpi_Worker_DefaultDisconnectHandler;
// Worker should implement this so it will quit nicely when the master disconnects.
void MyDisconnectHandler( int procID, const char *pReason )
{
// If we're a worker, then it's a fatal error if we lose the connection to the master.
if ( !g_bMPIMaster && g_fnDisconnectHandler )
{
(* g_fnDisconnectHandler)( procID, pReason );
}
}
// new format:
// ver#
// total shader combos
// total dynamic combos
// flags
// centroid mask
// total non-skipped static combos
// [ (sorted by static combo id)
// static combo id
// file offset of packed dynamic combo
// ]
// 0xffffffff (sentinel key)
// end of file offset (so can tell compressed size of last combo)
//
// # of duplicate static combos (if version >= 6 )
// [ (sorted by static combo id)
// static combo id
// id of static bombo which is identical
// ]
//
// each packed dynamic combo for a given static combo is stored as a series of compressed blocks.
// block 1:
// ulong blocksize (high bit set means uncompressed)
// block data
// block2..
// 0xffffffff indicates no more blocks for this combo
//
// each block, when uncompressed, holds one or more dynamic combos:
// dynamic combo id (full id if v<6, dynamic combo id only id >=6)
// size of shader
// ..
// there is no terminator - the size of the uncompressed shader tells you when to stop
// this record is then bzip2'd.
// qsort driver function
// returns negative number if idA is less than idB, positive when idA is greater than idB
// and zero if the ids are equal
static int __cdecl CompareDupComboIndices( const StaticComboAliasRecord_t *pA, const StaticComboAliasRecord_t *pB )
{
if ( pA->m_nStaticComboID < pB->m_nStaticComboID )
return -1;
if ( pA->m_nStaticComboID > pB->m_nStaticComboID )
return 1;
return 0;
}
static void FlushCombos( size_t *pnTotalFlushedSize, CUtlBuffer *pDynamicComboBuffer, MessageBuffer *pBuf )
{
if ( !pDynamicComboBuffer->TellPut() )
// Nothing to do here
return;
size_t nCompressedSize;
uint8 *pCompressedShader = LZMA_Compress( reinterpret_cast<uint8 *> ( pDynamicComboBuffer->Base() ),
pDynamicComboBuffer->TellPut(),
&nCompressedSize );
// high 2 bits of length =
// 00 = bzip2 compressed
// 10 = uncompressed
// 01 = lzma compressed
// 11 = unused
if ( ! pCompressedShader )
{
// it grew
long lFlagSize = AsTargetLong( 0x80000000 | pDynamicComboBuffer->TellPut() );
pBuf->write( &lFlagSize, sizeof( lFlagSize ) );
pBuf->write( pDynamicComboBuffer->Base(), pDynamicComboBuffer->TellPut() );
*pnTotalFlushedSize += sizeof( lFlagSize ) + pDynamicComboBuffer->TellPut();
}
else
{
long lFlagSize = AsTargetLong( 0x40000000 | nCompressedSize );
pBuf->write( &lFlagSize, sizeof( lFlagSize ) );
pBuf->write( pCompressedShader, nCompressedSize );
delete[] pCompressedShader;
*pnTotalFlushedSize += sizeof( lFlagSize ) + nCompressedSize;
}
pDynamicComboBuffer->Clear(); // start over
}
static void OutputDynamicCombo( size_t *pnTotalFlushedSize, CUtlBuffer *pDynamicComboBuffer,
MessageBuffer *pBuf, uint64 nComboID, int nComboSize,
uint8 *pComboCode )
{
if ( pDynamicComboBuffer->TellPut() + nComboSize+16 >= MAX_SHADER_UNPACKED_BLOCK_SIZE )
{
FlushCombos( pnTotalFlushedSize, pDynamicComboBuffer, pBuf );
}
pDynamicComboBuffer->PutInt( uint64_as_uint32( nComboID ) );
pDynamicComboBuffer->PutInt( nComboSize );
// pDynamicComboBuffer->PutInt( CRC32_ProcessSingleBuffer( pComboCode, nComboSize ) );
pDynamicComboBuffer->Put( pComboCode, nComboSize );
}
static void OutputDynamicComboDup( size_t *pnTotalFlushedSize, CUtlBuffer *pDynamicComboBuffer,
MessageBuffer *pBuf, uint64 nComboID, uint64 nBaseCombo )
{
if ( pDynamicComboBuffer->TellPut() + 8 >= MAX_SHADER_UNPACKED_BLOCK_SIZE )
{
FlushCombos( pnTotalFlushedSize, pDynamicComboBuffer, pBuf );
}
pDynamicComboBuffer->PutInt( uint64_as_uint32( nComboID ) | 0x80000000 );
pDynamicComboBuffer->PutInt( uint64_as_uint32( nBaseCombo ) );
}
void GetVCSFilenames( char *pszMainOutFileName, ShaderInfo_t const &si )
{
sprintf( pszMainOutFileName, "%s\\shaders\\fxc", g_pShaderPath );
struct _stat buf;
if( _stat( pszMainOutFileName, &buf ) == -1 )
{
printf( "mkdir %s\n", pszMainOutFileName );
// doh. . need to make the directory that the vcs file is going to go into.
_mkdir( pszMainOutFileName );
}
strcat( pszMainOutFileName, "\\" );
strcat( pszMainOutFileName, si.m_pShaderName );
if ( g_bIsX360 )
{
strcat( pszMainOutFileName, ".360" );
}
else if ( g_bIsPS3 )
{
strcat( pszMainOutFileName, ".ps3" );
}
strcat( pszMainOutFileName, ".vcs" ); // Different extensions for main output file
// Check status of vcs file...
if( _stat( pszMainOutFileName, &buf ) != -1 )
{
// The file exists, let's see if it's writable.
if( !( buf.st_mode & _S_IWRITE ) )
{
// It isn't writable. . we'd better change its permissions (or check it out possibly)
printf( "Warning: making %s writable!\n", pszMainOutFileName );
_chmod( pszMainOutFileName, _S_IREAD | _S_IWRITE );
}
}
}
// WriteShaderFiles
//
// should be called either on the main thread or
// on the async writing thread.
//
// So the function WriteShaderFiles should not be reentrant, however the
// data that it uses might be updated by the main thread when built pieces
// are received from the workers.
//
#define STATIC_COMBO_HASH_SIZE 73
struct StaticComboAuxInfo_t : StaticComboRecord_t
{
uint32 m_nCRC32; // CRC32 of packed data
struct CStaticCombo *m_pByteCode;
};
static int __cdecl CompareComboIds( const StaticComboAuxInfo_t *pA, const StaticComboAuxInfo_t *pB )
{
if ( pA->m_nStaticComboID < pB->m_nStaticComboID )
return -1;
if ( pA->m_nStaticComboID > pB->m_nStaticComboID )
return 1;
return 0;
}
static void WriteShaderFiles( const char *pShaderName )
{
if ( !g_Master_ShaderWrittenToDisk.Defined( pShaderName ) )
g_Master_ShaderWrittenToDisk[ pShaderName ] = true;
else
return;
bool bShaderFailed = g_Master_ShaderHadError.Defined( pShaderName );
char const *szShaderFileOperation = bShaderFailed ? "Removing failed" : "Writing";
//
// Progress indication
//
if ( g_numCommandsCompleted < g_numCompileCommands )
{
static char chProgress[] = { '/', '-', '\\', '|' };
static int iProgressSymbol = 0;
Msg( "\b%c", chProgress[ ( ++ iProgressSymbol ) % 4 ] );
}
else
{
char chShaderName[33];
Q_snprintf( chShaderName, 29, "%s...", pShaderName );
sprintf( chShaderName + sizeof( chShaderName ) - 5, "..." );
Msg( "\r%s %s \r", szShaderFileOperation, chShaderName );
}
//
// Retrieve the data we are going to operate on
// from global variables under lock.
//
GLOBAL_DATA_MTX_LOCK();
StaticComboNodeHash_t *pByteCodeArray;
{
StaticComboNodeHash_t *&rp = g_ShaderByteCode[pShaderName]; // Get a static combo pointer, reset it as well
pByteCodeArray = rp;
rp = NULL;
/*
Assert( pByteCodeArray );
if ( !pByteCodeArray )
ShaderHadErrorDispatchInt( pShaderName );
*/
}
ShaderInfo_t shaderInfo = g_ShaderToShaderInfo[pShaderName];
if ( !shaderInfo.m_pShaderName )
{
for ( CfgProcessor::CfgEntryInfo const *pAnalyze = g_arrCompileEntries.Get() ;
pAnalyze->m_szName ;
++ pAnalyze )
{
if ( !strcmp( pAnalyze->m_szName, pShaderName ) )
{
Shader_ParseShaderInfoFromCompileCommands( pAnalyze, shaderInfo );
g_ShaderToShaderInfo[ pShaderName ] = shaderInfo;
break;
}
}
}
GLOBAL_DATA_MTX_UNLOCK();
if ( !shaderInfo.m_pShaderName )
return;
//
// Shader vcs file name
//
char szVCSfilename[MAX_PATH];
GetVCSFilenames( szVCSfilename, shaderInfo );
if ( bShaderFailed )
{
DebugOut( "Removing failed shader file \"%s\".\n", szVCSfilename );
unlink( szVCSfilename );
return;
}
if ( !pByteCodeArray )
return;
DebugOut( "%s : %I64u combos centroid mask: 0x%x numDynamicCombos: %I64u flags: 0x%x\n",
pShaderName, shaderInfo.m_nTotalShaderCombos,
shaderInfo.m_CentroidMask, shaderInfo.m_nDynamicCombos, shaderInfo.m_Flags );
//
// Static combo headers
//
CUtlVector< StaticComboAuxInfo_t > StaticComboHeaders;
StaticComboHeaders.EnsureCapacity( 1 + pByteCodeArray->Count() ); // we know how much ram we need
CUtlVector< int > comboIndicesHashedByCRC32[STATIC_COMBO_HASH_SIZE];
CUtlVector< StaticComboAliasRecord_t > duplicateCombos;
// now, lets fill in our combo headers, sort, and write
for( int nChain = 0 ; nChain < NELEMS( pByteCodeArray->m_HashChains) ; nChain++ )
{
for( CStaticCombo *pStatic = pByteCodeArray->m_HashChains[ nChain ].m_pHead;
pStatic;
pStatic = pStatic->m_pNext )
{
if ( pStatic->m_abPackedCode.GetLength() )
{
StaticComboAuxInfo_t Hdr;
Hdr.m_nStaticComboID = uint64_as_uint32( pStatic->m_nStaticComboID );
Hdr.m_nFileOffset = 0; // fill in later
Hdr.m_nCRC32 = CRC32_ProcessSingleBuffer( pStatic->m_abPackedCode.GetData(), pStatic->m_abPackedCode.GetLength() );
int nHashIdx = Hdr.m_nCRC32 % STATIC_COMBO_HASH_SIZE;
Hdr.m_pByteCode = pStatic;
// now, see if we have an identical static combo
bool bIsDuplicate = false;
for( int i = 0; i < comboIndicesHashedByCRC32[nHashIdx].Count() ; i++ )
{
StaticComboAuxInfo_t const &check = StaticComboHeaders[comboIndicesHashedByCRC32[nHashIdx][i]];
if (
( check.m_nCRC32 == Hdr.m_nCRC32 ) &&
( check.m_pByteCode->m_abPackedCode.GetLength() == pStatic->m_abPackedCode.GetLength() ) &&
( memcmp( check.m_pByteCode->m_abPackedCode.GetData(), pStatic->m_abPackedCode.GetData(), check.m_pByteCode->m_abPackedCode.GetLength() ) == 0 )
)
{
// this static combo is the same as another one!!
StaticComboAliasRecord_t aliasHdr;
aliasHdr.m_nStaticComboID = Hdr.m_nStaticComboID;
aliasHdr.m_nSourceStaticCombo = check.m_nStaticComboID;
duplicateCombos.AddToTail( aliasHdr );
bIsDuplicate = true;
break;
}
}
if ( ! bIsDuplicate )
{
StaticComboHeaders.AddToTail( Hdr );
comboIndicesHashedByCRC32[nHashIdx].AddToTail( StaticComboHeaders.Count() - 1 );
}
}
}
}
// add sentinel key
StaticComboAuxInfo_t Hdr;
Hdr.m_nStaticComboID = 0xffffffff;
Hdr.m_nFileOffset = 0;
StaticComboHeaders.AddToTail( Hdr );
// now, sort. sentinel key will end up at end
StaticComboHeaders.Sort( CompareComboIds );
// Set the CRC to zero for now. . will patch in copyshaders.pl with the correct CRC.
unsigned int crc32 = 0;
//
// Shader file stream buffer
//
CUtlStreamBuffer ShaderFile( szVCSfilename, NULL ); // Streaming buffer for vcs file (since this can blow memory)
ShaderFile.SetBigEndian( g_bIsX360 || g_bIsPS3 ); // Swap the header bytes to X360 format
// ------ Header --------------
ShaderFile.PutInt( SHADER_VCS_VERSION_NUMBER ); // Version
ShaderFile.PutInt( uint64_as_uint32( shaderInfo.m_nTotalShaderCombos ) );
ShaderFile.PutInt( uint64_as_uint32( shaderInfo.m_nDynamicCombos ) );
ShaderFile.PutUnsignedInt( shaderInfo.m_Flags );
ShaderFile.PutUnsignedInt( shaderInfo.m_CentroidMask );
ShaderFile.PutUnsignedInt( StaticComboHeaders.Count() );
ShaderFile.PutUnsignedInt( crc32 );
// static combo dictionary
int nDictionaryOffset= ShaderFile.TellPut();
// we will re write this one we know the offsets
ShaderFile.Put( StaticComboHeaders.Base(), sizeof( StaticComboRecord_t ) * StaticComboHeaders.Count() ); // dummy write, 8 bytes per static combo
ShaderFile.PutUnsignedInt( duplicateCombos.Count() );
// now, write out all duplicate header records
// sort duplicate combo records for binary search
duplicateCombos.Sort( CompareDupComboIndices );
for( int i = 0; i < duplicateCombos.Count(); i++ )
{
ShaderFile.PutUnsignedInt( duplicateCombos[i].m_nStaticComboID );
ShaderFile.PutUnsignedInt( duplicateCombos[i].m_nSourceStaticCombo );
}
// now, write out all static combos
for( int i=0 ; i<StaticComboHeaders.Count(); i++ )
{
StaticComboRecord_t &SRec = StaticComboHeaders[i];
SRec.m_nFileOffset = ShaderFile.TellPut();
if ( SRec.m_nStaticComboID != 0xffffffff ) // sentinel key?
{
CStaticCombo *pStatic=pByteCodeArray->FindByKey( SRec.m_nStaticComboID );
Assert( pStatic );
// Put the packed chunk of code for this static combo
if ( size_t nPackedLen = pStatic->m_abPackedCode.GetLength() )
ShaderFile.Put( pStatic->m_abPackedCode.GetData(), nPackedLen );
ShaderFile.PutInt( 0xffffffff ); // end of dynamic combos
}
if ( g_bIsX360 || g_bIsPS3 )
{
SRec.m_nFileOffset = BigLong( SRec.m_nFileOffset );
SRec.m_nStaticComboID = BigLong( SRec.m_nStaticComboID );
}
}
ShaderFile.Close();
//
// Re-writing the combo header
//
{
FILE *Handle=fopen( szVCSfilename, "rb+" );
if (! Handle )
printf(" failed to re-open %s\n",szVCSfilename );
fseek( Handle, nDictionaryOffset, SEEK_SET );
// now, rewrite header. data is already byte-swapped appropriately
for( int i = 0; i < StaticComboHeaders.Count(); i++ )
{
fwrite( &( StaticComboHeaders[i].m_nStaticComboID ), 4, 1, Handle );
fwrite( &( StaticComboHeaders[i].m_nFileOffset ), 4, 1, Handle );
}
fclose( Handle );
}
// Finalize, free memory
delete pByteCodeArray;
if ( g_numCommandsCompleted >= g_numCompileCommands )
{
Msg( "\r \r" );
}
}
// pBuf is ready to read the results written to the buffer in ProcessWorkUnitFn.
// work is done. .master gets it back this way.
// compiled code in pBuf
void Master_ReceiveWorkUnitFn( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker )
{
GLOBAL_DATA_MTX_LOCK_AUTO;
uint64 comboStart = iWorkUnit * g_nStaticCombosPerWorkUnit;
uint64 comboEnd = comboStart + g_nStaticCombosPerWorkUnit;
comboEnd = min( g_numStaticCombos, comboEnd );
char const *chLastShaderName = "";
ShaderInfo_t siLastShaderInfo;
memset( &siLastShaderInfo, 0, sizeof( siLastShaderInfo ) );
siLastShaderInfo.m_pShaderName = chLastShaderName;
uint64 nComboOfTheEntry = 0;
CfgProcessor::CfgEntryInfo const *pEntry = GetEntryByStaticComboNum( comboStart, &nComboOfTheEntry );
nComboOfTheEntry = pEntry->m_numStaticCombos - 1 - nComboOfTheEntry;
for( uint64 iCombo = comboStart; iCombo ++ < comboEnd;
( ( ! nComboOfTheEntry -- ) ? ( ++ pEntry, nComboOfTheEntry = pEntry->m_numStaticCombos - 1 ) : 0 ) )
{
Assert( nComboOfTheEntry < pEntry->m_numStaticCombos );
// Read length
int len;
pBuf->read( &len, sizeof( len ) );
// Length can indicate the number of skips to make
if ( len <= 0 )
{
// remember how many static combos get skipped
g_numSkippedStaticCombos += -len;
// then we skip as instructed
for ( int64 numSkips = - len - 1;
numSkips > 0; )
{
if ( numSkips <= nComboOfTheEntry )
{
nComboOfTheEntry -= numSkips;
iCombo += numSkips;
numSkips = 0;
}
else
{
numSkips -= nComboOfTheEntry + 1;
iCombo += nComboOfTheEntry + 1;
++ pEntry;
nComboOfTheEntry = pEntry->m_numStaticCombos - 1;
}
}
if ( iCombo < comboEnd )
continue;
else
break;
}
// Shader code arrived
char const *chShaderName = pEntry->m_szName;
// If starting new shader remember shader info
if ( chLastShaderName != chShaderName )
{
Shader_ParseShaderInfoFromCompileCommands( pEntry, siLastShaderInfo );
chLastShaderName = chShaderName;
g_ShaderToShaderInfo[ chLastShaderName ] = siLastShaderInfo;
}
// Read buffer
uint8 *pCodeBuffer = StaticComboFromDictAdd( chShaderName, nComboOfTheEntry )->AllocPackedCodeBlock( len );
if ( pCodeBuffer )
pBuf->read( pCodeBuffer, len );
}
}
//
// A function that will wait for right Ctrl+Alt+Shift to be held down simultaneously.
// This is useful for debugging short-lived processes and gives time for debugger to
// get attached.
//
void DebugSafeWaitPoint( bool bForceWait = false )
{
static bool s_bDebuggerAttached = ( CommandLine()->FindParm( "-debugwait" ) == 0 );
if ( bForceWait )
{
s_bDebuggerAttached = false;
}
if ( !s_bDebuggerAttached )
{
Msg( "Waiting for right Ctrl+Alt+Shift to continue..." );
while ( !s_bDebuggerAttached )
{
Msg( "." );
Sleep(1000);
if ( short( GetAsyncKeyState( VK_RCONTROL ) ) < 0 &&
short( GetAsyncKeyState( VK_RSHIFT ) ) < 0 &&
short( GetAsyncKeyState( VK_RMENU ) ) < 0 )
{
s_bDebuggerAttached = true;
}
}
Msg( " ok.\n" );
}
}
// same as "system", but doesn't pop up a window
void MySystem( char const * const pCommand, CmdSink::IResponse **ppResponse )
{
// Trap the command in InterceptFxc
if ( InterceptFxc::TryExecuteCommand( pCommand, ppResponse ) )
{
Sleep( 0 );
return;
}
unlink( "shader.o" );
char szTempFileName[100];
sprintf( szTempFileName, "sc%d_%d.bat", GetCurrentProcessId(), GetCurrentThreadId() );
FILE *batFp = fopen( szTempFileName, "w" );
fprintf( batFp, "%s\n", pCommand );
fclose( batFp );
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line).
szTempFileName, // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
FALSE, // Set handle inheritance to FALSE.
IDLE_PRIORITY_CLASS | CREATE_NO_WINDOW, // No creation flags.
NULL, // Use parent's environment block.
g_WorkerTempPath, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi ) // Pointer to PROCESS_INFORMATION structure.
)
{
Error( "CreateProcess failed." );
Assert( 0 );
}
// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
unlink( szTempFileName );
}
// Assemble a reply package to the master from the compiled bytecode
// return the length of the package.
size_t AssembleWorkerReplyPackage( CfgProcessor::CfgEntryInfo const *pEntry, uint64 nComboOfEntry,
MessageBuffer *pBuf )
{
GLOBAL_DATA_MTX_LOCK();
CStaticCombo *pStComboRec = StaticComboFromDict( pEntry->m_szName, nComboOfEntry );
StaticComboNodeHash_t *pByteCodeArray = g_ShaderByteCode[ pEntry->m_szName ];
GLOBAL_DATA_MTX_UNLOCK();
size_t nBytesWritten = 0;
if ( pStComboRec && pStComboRec->m_DynamicCombos.Count() )
{
CUtlBuffer ubDynamicComboBuffer;
ubDynamicComboBuffer.SetBigEndian( g_bIsX360 || g_bIsPS3 );
pStComboRec->SortDynamicCombos();
// iterate over all dynamic combos.
for(int i = 0 ; i < pStComboRec->m_DynamicCombos.Count(); i++ )
{
CByteCodeBlock *pCode = pStComboRec->m_DynamicCombos[i];
// check if we have already output an identical combo
bool bDup = false;
#if 0
// check for duplicate bytecode. actually doesn't save much because bzip does a good
// job compressing dupes.
for( int j = 0; j < i; j++ )
{
if (
( pCode->m_nCRC32 == pStComboRec->m_DynamicCombos[j]->m_nCRC32 ) &&
( pCode->m_nCodeSize == pStComboRec->m_DynamicCombos[j]->m_nCodeSize ) &&
( memcmp( pCode->m_ByteCode, pStComboRec->m_DynamicCombos[i]->m_ByteCode, pCode->m_nCodeSize ) == 0 )
) // identical bytecode?
{
bDup = true;
OutputDynamicComboDup( &nBytesWritten, &ubDynamicComboBuffer,
pBuf, pCode->m_nComboID,
pStComboRec->m_DynamicCombos[j]->m_nComboID );
}
}
#endif
if ( ! bDup )
OutputDynamicCombo( &nBytesWritten, &ubDynamicComboBuffer,
pBuf, pCode->m_nComboID,
pCode->m_nCodeSize, pCode->m_ByteCode );
}
FlushCombos( &nBytesWritten, &ubDynamicComboBuffer, pBuf );
}
// Time to limit amount of prints
static float s_fLastInfoTime = 0;
float fCurTime = ( float ) Plat_FloatTime();
GLOBAL_DATA_MTX_LOCK();
if ( pStComboRec )
{
CStaticCombo *pCombo = pByteCodeArray->FindByKey( nComboOfEntry );
pByteCodeArray->DeleteByKey( nComboOfEntry );
delete pCombo;
}
if( fabs( fCurTime - s_fLastInfoTime ) > 1.f )
{
Msg( "\rCompiling %s [ %2d remaining ] ... \r",
pEntry->m_szName, nComboOfEntry );
s_fLastInfoTime = fCurTime;
}
GLOBAL_DATA_MTX_UNLOCK();
return nBytesWritten;
}
// Copy a reply package to the master from the compiled bytecode
// return the length of the data copied.
size_t CopyWorkerReplyPackage( CfgProcessor::CfgEntryInfo const *pEntry, uint64 nComboOfEntry,
MessageBuffer *pBuf, int nSkipsSoFar )
{
GLOBAL_DATA_MTX_LOCK();
CStaticCombo *pStComboRec = StaticComboFromDict( pEntry->m_szName, nComboOfEntry );
StaticComboNodeHash_t *pByteCodeArray = g_ShaderByteCode[ pEntry->m_szName ]; // Get a static combo pointer
GLOBAL_DATA_MTX_UNLOCK();
int len = pStComboRec ? pStComboRec->m_abPackedCode.GetLength() : NULL;
if ( len )
{
if ( nSkipsSoFar )
{
pBuf->write( &nSkipsSoFar, sizeof( nSkipsSoFar ) );
}
pBuf->write( &len, sizeof( len ) );
if ( len )
pBuf->write( pStComboRec->m_abPackedCode.GetData(), len );
}
if ( pStComboRec )
{
GLOBAL_DATA_MTX_LOCK();
CStaticCombo *pCombo = pByteCodeArray->FindByKey( nComboOfEntry );
pByteCodeArray->DeleteByKey( nComboOfEntry );
delete pCombo;
GLOBAL_DATA_MTX_UNLOCK();
}
return size_t( len );
}
template < typename TMutexType >
class CWorkerAccumState : public CParallelProcessorBase < CWorkerAccumState < TMutexType > >
{
friend ThisParallelProcessorBase_t;
private:
static bool & DisconnectState() { static bool sb = false; return sb; }
static void Special_DisconnectHandler( int procID, const char *pReason ) { DisconnectState() = true; }
public:
explicit CWorkerAccumState( TMutexType *pMutex ) :
m_pMutex( pMutex ), m_iFirstCommand( 0 ), m_iNextCommand( 0 ),
m_iEndCommand( 0 ), m_iLastFinished( 0 ),
m_hCombo( NULL ),
m_fnOldDisconnectHandler( g_fnDisconnectHandler ),
m_autoRestoreDisconnectHandler( g_fnDisconnectHandler )
{
DisconnectState() = false;
}
~CWorkerAccumState() { QuitSubs(); }
void RangeBegin( uint64 iFirstCommand, uint64 iEndCommand );
void RangeFinished( void );
void ExecuteCompileCommand( CfgProcessor::ComboHandle hCombo );
void ExecuteCompileCommandThreaded( CfgProcessor::ComboHandle hCombo );
void HandleCommandResponse( CfgProcessor::ComboHandle hCombo, CmdSink::IResponse *pResponse );
public:
using ThisParallelProcessorBase_t::Run;
public:
bool OnProcess();
bool OnProcessST();
protected:
TMutexType *m_pMutex;
protected:
struct SubProcess
{
DWORD dwIndex;
DWORD dwSvcThreadId;
uint64 iRunningCommand;
PROCESS_INFORMATION pi;
SubProcessKernelObjects *pCommObjs;
};
CTHREADLOCAL( SubProcess * ) m_lpSubProcessInfo;
CUtlVector < SubProcess * > m_arrSubProcessInfos;
uint64 m_iFirstCommand;
uint64 m_iNextCommand;
uint64 m_iEndCommand;
uint64 m_iLastFinished;
CfgProcessor::ComboHandle m_hCombo;
DisconnectHandlerFn_t m_fnOldDisconnectHandler;
CAutoPushPop< DisconnectHandlerFn_t > m_autoRestoreDisconnectHandler;
void QuitSubs( void );
void TryToPackageData( uint64 iCommandNumber );
void PrepareSubProcess( SubProcess **ppSp, SubProcessKernelObjects **ppCommObjs );
};
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::RangeBegin( uint64 iFirstCommand, uint64 iEndCommand )
{
m_iFirstCommand = iFirstCommand;
m_iNextCommand = iFirstCommand;
m_iEndCommand = iEndCommand;
m_iLastFinished = iFirstCommand;
m_hCombo = NULL;
CfgProcessor::Combo_GetNext( m_iNextCommand, m_hCombo, m_iEndCommand );
g_fnDisconnectHandler = Special_DisconnectHandler;
// Notify all connected sub-processes that the master is still alive
for ( int k = 0; k < m_arrSubProcessInfos.Count(); ++ k )
{
if ( SubProcess *pSp = m_arrSubProcessInfos[ k ] )
{
SubProcessKernelObjects_Memory shrmem( pSp->pCommObjs );
if ( void *pvMemory = shrmem.Lock() )
{
strcpy( ( char * ) pvMemory, "keepalive" );
shrmem.Unlock();
}
}
}
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::RangeFinished( void )
{
if( !DisconnectState() )
{
// Finish packaging data
TryToPackageData( m_iEndCommand - 1 );
}
else
{
// Master disconnected
QuitSubs();
}
g_fnDisconnectHandler = m_fnOldDisconnectHandler;
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::QuitSubs( void )
{
CUtlVector < HANDLE > m_arrWait;
m_arrWait.EnsureCapacity( m_arrSubProcessInfos.Count() );
for ( int k = 0; k < m_arrSubProcessInfos.Count(); ++ k )
{
if ( SubProcess *pSp = m_arrSubProcessInfos[ k ] )
{
SubProcessKernelObjects_Memory shrmem( pSp->pCommObjs );
if ( void *pvMemory = shrmem.Lock() )
{
strcpy( ( char * ) pvMemory, "quit" );
shrmem.Unlock();
}
m_arrWait.AddToTail( pSp->pi.hProcess );
}
}
if ( m_arrWait.Count() )
{
DWORD dwWait = WaitForMultipleObjects( m_arrWait.Count(), m_arrWait.Base(), TRUE, 2 * 1000 );
if ( WAIT_TIMEOUT == dwWait )
{
Warning( "Timed out while waiting for sub-processes to shut down!\n" );
}
}
for ( int k = 0; k < m_arrSubProcessInfos.Count(); ++ k )
{
if ( SubProcess *pSp = m_arrSubProcessInfos[ k ] )
{
CloseHandle( pSp->pi.hThread );
CloseHandle( pSp->pi.hProcess );
delete pSp->pCommObjs;
delete pSp;
}
}
if ( DisconnectState() )
Vmpi_Worker_DefaultDisconnectHandler( 0, "Master disconnected during compilation." );
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::PrepareSubProcess( SubProcess **ppSp, SubProcessKernelObjects **ppCommObjs )
{
SubProcess *pSp = m_lpSubProcessInfo.Get();
SubProcessKernelObjects *pCommObjs = NULL;
if ( pSp )
{
pCommObjs = pSp->pCommObjs;
}
else
{
pSp = new SubProcess;
m_lpSubProcessInfo.Set( pSp );
pSp->dwSvcThreadId = ThreadGetCurrentId();
char chBaseNameBuffer[0x30];
sprintf( chBaseNameBuffer, "SHCMPL_SUB_%08X_%I64X_%08X", pSp->dwSvcThreadId, time( NULL ), GetCurrentProcessId() );
pCommObjs = pSp->pCommObjs = new SubProcessKernelObjects_Create( chBaseNameBuffer );
ZeroMemory( &pSp->pi, sizeof( pSp->pi ) );
STARTUPINFO si;
ZeroMemory( &si, sizeof( si ) );
si.cb = sizeof( si );
char chCommandLine[0x100];
sprintf( chCommandLine, "\"%s\\shadercompile.exe\" -subprocess %s", g_WorkerTempPath, chBaseNameBuffer );
#ifdef _DEBUG
V_strncat( chCommandLine, " -allowdebug", sizeof( chCommandLine ) );
#endif
BOOL bCreateResult = CreateProcess( NULL, chCommandLine, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, g_WorkerTempPath, &si, &pSp->pi );
( void ) bCreateResult;
Assert( bCreateResult && "CreateProcess failed?" );
m_pMutex->Lock();
pSp->dwIndex = m_arrSubProcessInfos.AddToTail( pSp );
m_pMutex->Unlock();
}
if ( ppSp ) *ppSp = pSp;
if ( ppCommObjs ) *ppCommObjs = pCommObjs;
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::ExecuteCompileCommandThreaded( CfgProcessor::ComboHandle hCombo )
{
// DebugOut( "threaded: running: \"%s\"\n", szCommand );
SubProcessKernelObjects *pCommObjs = NULL;
PrepareSubProcess( NULL, &pCommObjs );
// Execute the command
SubProcessKernelObjects_Memory shrmem( pCommObjs );
{
void *pvMemory = shrmem.Lock();
Assert( pvMemory );
Combo_FormatCommand( hCombo, ( char * ) pvMemory );
shrmem.Unlock();
}
// Obtain the command response
{
void const *pvMemory = shrmem.Lock();
Assert( pvMemory );
// TODO: Vitaliy :: TEMP fix:
// Usually what happens if we fail to lock here is
// when our subprocess dies and to recover we will
// attempt to restart on another worker.
if ( !pvMemory )
// ::RaiseException( GetLastError(), EXCEPTION_NONCONTINUABLE, 0, NULL );
TerminateProcess( GetCurrentProcess(), 1 );
CmdSink::IResponse *pResponse;
if ( pvMemory )
pResponse = new CSubProcessResponse( pvMemory );
else
pResponse = new CmdSink::CResponseError;
HandleCommandResponse( hCombo, pResponse );
delete pResponse;
shrmem.Unlock();
}
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::ExecuteCompileCommand( CfgProcessor::ComboHandle hCombo )
{
CmdSink::IResponse *pResponse = NULL;
{
char chBuffer[ 4096 ];
Combo_FormatCommand( hCombo, chBuffer );
DebugOut( "running: \"%s\"\n", chBuffer );
MySystem( chBuffer, &pResponse );
}
HandleCommandResponse( hCombo, pResponse );
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::HandleCommandResponse( CfgProcessor::ComboHandle hCombo, CmdSink::IResponse *pResponse )
{
VMPI_HandleSocketErrors();
if ( !pResponse )
pResponse = new CmdSink::CResponseFiles( "shader.o", "output.txt" );
// Command info
CfgProcessor::CfgEntryInfo const *pEntryInfo = Combo_GetEntryInfo( hCombo );
uint64 iComboIndex = Combo_GetComboNum( hCombo );
uint64 iCommandNumber = Combo_GetCommandNum( hCombo );
if ( pResponse->Succeeded() )
{
GLOBAL_DATA_MTX_LOCK();
uint64 nStComboIdx = iComboIndex / pEntryInfo->m_numDynamicCombos;
uint64 nDyComboIdx = iComboIndex - ( nStComboIdx * pEntryInfo->m_numDynamicCombos );
StaticComboFromDictAdd( pEntryInfo->m_szName, nStComboIdx )->AddDynamicCombo( nDyComboIdx , pResponse->GetResultBuffer(), pResponse->GetResultBufferLen() );
GLOBAL_DATA_MTX_UNLOCK();
}
// Tell the master that this shader failed
if ( !pResponse->Succeeded() )
{
GLOBAL_DATA_MTX_LOCK();
ShaderHadErrorDispatchInt( pEntryInfo->m_szName );
GLOBAL_DATA_MTX_UNLOCK();
}
// Process listing even if the shader succeeds for warnings
char const *szListing = pResponse->GetListing();
if ( ( !g_bSuppressWarnings && szListing ) || !pResponse->Succeeded() )
{
char chCommandNumber[50];
sprintf( chCommandNumber, "%I64u", iCommandNumber );
char chUnreportedListing[0xFF];
if ( !szListing )
{
sprintf( chUnreportedListing, "(0): error %s: shadercompile.cpp: Compiler failed without error description - possible DX_PROXY DLL, D3DX DLL, D3DCOMPILER DLL, or other .DLL dependency problem?", chCommandNumber );
szListing = chUnreportedListing;
}
// Send the listing for dispatch
CUtlBinaryBlock errMsg;
errMsg.SetLength(
strlen( chCommandNumber ) + 1 + // command + newline
strlen( szListing ) + 1 + // listing + newline
1 // null-terminator
);
sprintf( ( char * ) errMsg.Get(), "%s\n%s\n", chCommandNumber, szListing );
GLOBAL_DATA_MTX_LOCK();
ErrMsgDispatchInt( ( char * ) errMsg.Get(), pEntryInfo->m_szShaderFileName );
GLOBAL_DATA_MTX_UNLOCK();
}
// Maybe zip things up
TryToPackageData( iCommandNumber );
}
template < typename TMutexType >
void CWorkerAccumState < TMutexType > ::TryToPackageData( uint64 iCommandNumber )
{
m_pMutex->Lock();
uint64 iFinishedByNow = iCommandNumber + 1;
// Check if somebody is running an earlier command
for ( int k = 0; k < m_arrSubProcessInfos.Count(); ++ k )
{
if ( SubProcess *pSp = m_arrSubProcessInfos[ k ] )
{
if ( pSp->iRunningCommand < iCommandNumber )
{
iFinishedByNow = 0;
break;
}
}
}
uint64 iLastFinished = m_iLastFinished;
if ( iFinishedByNow > m_iLastFinished )
{
m_iLastFinished = iFinishedByNow;
m_pMutex->Unlock();
}
else
{
m_pMutex->Unlock();
return;
}
CfgProcessor::ComboHandle hChBegin = CfgProcessor::Combo_GetCombo( iLastFinished );
CfgProcessor::ComboHandle hChEnd = CfgProcessor::Combo_GetCombo( iFinishedByNow );
Assert( hChBegin && hChEnd );
CfgProcessor::CfgEntryInfo const *pInfoBegin = Combo_GetEntryInfo( hChBegin );
CfgProcessor::CfgEntryInfo const *pInfoEnd = Combo_GetEntryInfo( hChEnd );
uint64 nComboBegin = Combo_GetComboNum( hChBegin ) / pInfoBegin->m_numDynamicCombos;
uint64 nComboEnd = Combo_GetComboNum( hChEnd ) / pInfoEnd->m_numDynamicCombos;
for ( ; pInfoBegin && (
( pInfoBegin->m_iCommandStart < pInfoEnd->m_iCommandStart ) ||
( nComboBegin > nComboEnd ) ); )
{
// Zip this combo
MessageBuffer mbPacked;
size_t nPackedLength = AssembleWorkerReplyPackage( pInfoBegin, nComboBegin, &mbPacked );
if ( nPackedLength )
{
// Packed buffer
GLOBAL_DATA_MTX_LOCK();
uint8 *pCodeBuffer = StaticComboFromDictAdd( pInfoBegin->m_szName,
nComboBegin )->AllocPackedCodeBlock( nPackedLength );
GLOBAL_DATA_MTX_UNLOCK();
if ( pCodeBuffer )
mbPacked.read( pCodeBuffer, nPackedLength );
}
// Next iteration
if ( ! nComboBegin -- )
{
Combo_Free( hChBegin );
if ( ( hChBegin = CfgProcessor::Combo_GetCombo( pInfoBegin->m_iCommandEnd ) ) != NULL )
{
pInfoBegin = Combo_GetEntryInfo( hChBegin );
nComboBegin = pInfoBegin->m_numStaticCombos - 1;
}
}
}
Combo_Free( hChBegin );
Combo_Free( hChEnd );
}
template < typename TMutexType >
bool CWorkerAccumState < TMutexType > ::OnProcess()
{
m_pMutex->Lock();
CfgProcessor::ComboHandle hThreadCombo = m_hCombo ? Combo_Alloc( m_hCombo ) : NULL;
m_pMutex->Unlock();
uint64 iThreadCommand = ~uint64(0);
SubProcess *pSp = NULL;
PrepareSubProcess( &pSp, NULL );
for ( ; ; )
{
m_pMutex->Lock();
if ( DisconnectState() )
Combo_Free( m_hCombo );
if ( m_hCombo )
{
Combo_Assign( hThreadCombo, m_hCombo );
pSp->iRunningCommand = Combo_GetCommandNum( hThreadCombo );
Combo_GetNext( iThreadCommand, m_hCombo, m_iEndCommand );
}
else
{
Combo_Free( hThreadCombo );
iThreadCommand = ~uint64(0);
pSp->iRunningCommand = ~uint64(0);
}
m_pMutex->Unlock();
if ( hThreadCombo )
{
ExecuteCompileCommandThreaded( hThreadCombo );
}
else
break;
}
Combo_Free( hThreadCombo );
return false;
}
template < typename TMutexType >
bool CWorkerAccumState < TMutexType > ::OnProcessST()
{
while ( m_hCombo )
{
ExecuteCompileCommand( m_hCombo );
Combo_GetNext( m_iNextCommand, m_hCombo, m_iEndCommand );
}
return false;
}
//
// Worker_ProcessCommandRange_Singleton
//
class Worker_ProcessCommandRange_Singleton
{
public:
static Worker_ProcessCommandRange_Singleton *& Instance() { static Worker_ProcessCommandRange_Singleton *s_ptr = NULL; return s_ptr; }
static Worker_ProcessCommandRange_Singleton * GetInstance() { Worker_ProcessCommandRange_Singleton *p = Instance(); Assert( p ); return p; }
public:
Worker_ProcessCommandRange_Singleton() { Assert( !Instance() ); Instance() = this; Startup(); }
~Worker_ProcessCommandRange_Singleton() { Assert( Instance() == this ); Instance() = NULL; Shutdown(); }
public:
void ProcessCommandRange( uint64 shaderStart, uint64 shaderEnd );
protected:
void Startup( void );
void Shutdown( void );
//
// Multi-threaded section
protected:
struct MT {
MT() : pWorkerObj( NULL ), pThreadPool( NULL ) {}
typedef CThreadFastMutex MultiThreadMutex_t;
MultiThreadMutex_t mtx;
typedef CWorkerAccumState < MultiThreadMutex_t > WorkerClass_t;
WorkerClass_t *pWorkerObj;
IThreadPool *pThreadPool;
ThreadPoolStartParams_t tpsp;
} m_MT;
//
// Single-threaded section
protected:
struct ST {
ST() : pWorkerObj( NULL ) {}
typedef CThreadNullMutex MultiThreadMutex_t;
MultiThreadMutex_t mtx;
typedef CWorkerAccumState < MultiThreadMutex_t > WorkerClass_t;
WorkerClass_t *pWorkerObj;
} m_ST;
};
void Worker_ProcessCommandRange_Singleton::Startup( void )
{
bool bInitializedThreadPool = false;
CPUInformation const &cpu = GetCPUInformation();
if ( cpu.m_nLogicalProcessors > 1 )
{
// Attempt to initialize thread pool
m_MT.pThreadPool = CommandLine()->FindParm("-singlethreaded") ? NULL : g_pThreadPool;
if ( m_MT.pThreadPool )
{
m_MT.tpsp.bIOThreads = false;
m_MT.tpsp.nThreads = cpu.m_nLogicalProcessors - 1;
if ( m_MT.pThreadPool->Start( m_MT.tpsp ) )
{
if ( m_MT.pThreadPool->NumThreads() >= 1 )
{
// Make sure that our mutex is in multi-threaded mode
Threading::g_mtxGlobal.SetThreadedMode( Threading::eMultiThreaded );
m_MT.pWorkerObj = new MT::WorkerClass_t( &m_MT.mtx );
bInitializedThreadPool = true;
}
else
{
m_MT.pThreadPool->Stop();
}
}
if ( !bInitializedThreadPool )
m_MT.pThreadPool = NULL;
}
}
// Otherwise initialize single-threaded mode
if ( !bInitializedThreadPool )
{
m_ST.pWorkerObj = new ST::WorkerClass_t( &m_ST.mtx );
}
}
void Worker_ProcessCommandRange_Singleton::Shutdown( void )
{
if ( m_MT.pThreadPool )
{
if( m_MT.pWorkerObj )
delete m_MT.pWorkerObj;
m_MT.pThreadPool->Stop();
m_MT.pThreadPool = NULL;
}
else
{
if ( m_ST.pWorkerObj )
delete m_ST.pWorkerObj;
}
}
void Worker_ProcessCommandRange_Singleton::ProcessCommandRange( uint64 shaderStart, uint64 shaderEnd )
{
if ( m_MT.pThreadPool )
{
MT::WorkerClass_t *pWorkerObj = m_MT.pWorkerObj;
pWorkerObj->RangeBegin( shaderStart, shaderEnd );
pWorkerObj->Run();
pWorkerObj->RangeFinished();
}
else
{
ST::WorkerClass_t *pWorkerObj = m_ST.pWorkerObj;
pWorkerObj->RangeBegin( shaderStart, shaderEnd );
pWorkerObj->OnProcessST();
pWorkerObj->RangeFinished();
}
}
// You must process the work unit range.
void Worker_ProcessCommandRange( uint64 shaderStart, uint64 shaderEnd )
{
Worker_ProcessCommandRange_Singleton::GetInstance()->ProcessCommandRange( shaderStart, shaderEnd );
}
// You must append data to pBuf with the work unit results.
void Worker_ProcessWorkUnitFn( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf )
{
uint64 comboStart = iWorkUnit * g_nStaticCombosPerWorkUnit;
uint64 comboEnd = comboStart + g_nStaticCombosPerWorkUnit;
comboEnd = min( g_numStaticCombos, comboEnd );
// Determine the commands required to be executed:
uint64 nComboOfTheEntry = 0;
CfgProcessor::CfgEntryInfo const *pEntry = NULL;
pEntry = GetEntryByStaticComboNum( comboEnd, &nComboOfTheEntry );
uint64 commandEnd = pEntry->m_iCommandStart + nComboOfTheEntry * pEntry->m_numDynamicCombos;
Assert( commandEnd <= g_numCompileCommands );
pEntry = GetEntryByStaticComboNum( comboStart, &nComboOfTheEntry );
uint64 commandStart = pEntry->m_iCommandStart + nComboOfTheEntry * pEntry->m_numDynamicCombos;
// Compile all the shader combos
Worker_ProcessCommandRange( commandStart, commandEnd );
nComboOfTheEntry = pEntry->m_numStaticCombos - 1 - nComboOfTheEntry;
// Copy off the reply packages
int nSkipsSoFar = 0;
for ( uint64 kCombo = comboStart; kCombo < comboEnd; ++ kCombo )
{
size_t nCpBytes = CopyWorkerReplyPackage( pEntry, nComboOfTheEntry, pBuf, nSkipsSoFar );
if ( nCpBytes )
nSkipsSoFar = 0;
else
-- nSkipsSoFar;
if ( nComboOfTheEntry == 0 )
{
++pEntry;
nComboOfTheEntry = pEntry->m_numStaticCombos;
}
nComboOfTheEntry--;
}
if ( nSkipsSoFar )
{
pBuf->write( &nSkipsSoFar, sizeof( nSkipsSoFar ) );
}
// Copy off SCE-CGC compiler generated metadata, used for shader debugging
if ( g_bIsPS3 )
{
PS3SendShaderCompileLogContentsToMaster();
if ( g_bGeneratePS3DebugInfo )
{
SendSubDirectoryToMaster( "cgc-capture" );
}
}
//////////////////////////////////////////////////////////////////////////
//
// Now deliver all our accumulated spew to the master
//
//////////////////////////////////////////////////////////////////////////
// Failed shaders
for ( int k = 0, kEnd = g_Master_ShaderHadError.GetNumStrings(); k < kEnd; ++ k )
{
char const *szShaderName = g_Master_ShaderHadError.String( k );
if ( !g_Master_ShaderHadError[ int_as_symid( k ) ] )
continue;
int const len = strlen( szShaderName );
CUtlBinaryBlock bb;
bb.SetLength( 1 + len + 1 );
sprintf( ( char * ) bb.Get(), "%c%s", SHADERHADERROR_PACKETID, szShaderName );
VMPI_SendData( bb.Get(), bb.Length(), VMPI_MASTER_ID );
VMPI_HandleSocketErrors();
}
// Compiler spew
for ( int k = 0, kEnd = g_Master_CompilerMsgInfo.GetNumStrings(); k < kEnd; ++ k )
{
char const * const szMsg = g_Master_CompilerMsgInfo.String( k );
CompilerMsgInfo const &cmi = g_Master_CompilerMsgInfo[ int_as_symid( k ) ];
char const * const szFirstCmd = cmi.GetFirstCommand();
int const numReported = cmi.GetNumTimesReported();
char chNumReported[0x40];
sprintf( chNumReported, "%d", numReported );
CUtlBinaryBlock bb;
bb.SetLength( 1 + strlen(szMsg) + 1 + strlen( szFirstCmd ) + 1 + strlen( chNumReported ) + 1 + 1 );
sprintf( ( char * ) bb.Get(), "%c%s\n%s\n%s\n", ERRMSG_PACKETID, szMsg, szFirstCmd, chNumReported );
VMPI_SendData( bb.Get(), bb.Length(), VMPI_MASTER_ID );
VMPI_HandleSocketErrors();
}
// Clean all reported msgs
g_Master_CompilerMsgInfo.Purge();
}
void Shader_ParseShaderInfoFromCompileCommands( CfgProcessor::CfgEntryInfo const *pEntry, ShaderInfo_t &shaderInfo )
{
if ( CfgProcessor::ComboHandle hCombo = CfgProcessor::Combo_GetCombo( pEntry->m_iCommandStart ) )
{
char cmd[ 4096 ];
Combo_FormatCommand( hCombo, cmd );
{
memset( &shaderInfo, 0, sizeof( ShaderInfo_t ) );
const char *pCentroidMask;
const char *pFlags;
const char *pShaderModel;
if ( g_bIsPS3 )
{
pCentroidMask = strstr( cmd, "-DCENTROIDMASK=" );
pFlags = strstr( cmd, "-DFLAGS=0x" );
pShaderModel = strstr( cmd, "-DSHADER_MODEL_" );
}
else
{
pCentroidMask = strstr( cmd, "/DCENTROIDMASK=" );
pFlags = strstr( cmd, "/DFLAGS=0x" );
pShaderModel = strstr( cmd, "/DSHADER_MODEL_" );
}
if( !pCentroidMask || !pFlags || !pShaderModel )
{
Assert( !"!pCentroidMask || !pFlags || !pShaderModel" );
return;
}
// Don't need to adjust the string for PS3 because it's the same length
sscanf( pCentroidMask + strlen( "/DCENTROIDMASK=" ), "%u", &shaderInfo.m_CentroidMask );
sscanf( pFlags + strlen( "/DFLAGS=0x" ), "%x", &shaderInfo.m_Flags );
// Copy shader model
pShaderModel += strlen( "-DSHADER_MODEL_" );
for ( char *pszSm = shaderInfo.m_szShaderModel, * const pszEnd = pszSm + sizeof( shaderInfo.m_szShaderModel ) - 1;
pszSm < pszEnd ; ++ pszSm )
{
char &rchLastChar = (*pszSm = *pShaderModel ++);
if ( !rchLastChar ||
V_isspace( rchLastChar ) ||
'=' == rchLastChar )
{
rchLastChar = 0;
break;
}
}
shaderInfo.m_nShaderCombo = 0;
shaderInfo.m_nTotalShaderCombos = pEntry->m_numCombos;
shaderInfo.m_nDynamicCombos = pEntry->m_numDynamicCombos;
shaderInfo.m_nStaticCombo = 0;
shaderInfo.m_pShaderName = pEntry->m_szName;
shaderInfo.m_pShaderSrc = pEntry->m_szShaderFileName;
}
Combo_Free( hCombo );
}
}
void Worker_GetLocalCopyOfShaders( void )
{
// Create virtual files for all of the stuff that we need to compile the shader
// make sure and prefix the file name so that it doesn't find it locally.
char filename[1024];
sprintf( filename, "%s\\uniquefilestocopy.txt", g_pShaderPath );
CUtlInplaceBuffer bffr( 0, 0, CUtlBuffer::TEXT_BUFFER );
if( !g_pFileSystem->ReadFile( filename, NULL, bffr ) )
{
fprintf( stderr, "Can't open uniquefilestocopy.txt!\n" );
exit( -1 );
}
while( char *pszLineToCopy = bffr.InplaceGetLinePtr() )
{
sprintf( filename, "%s\\%s", g_pShaderPath, pszLineToCopy );
if ( g_bVerbose )
printf( "getting local copy of shader: \"%s\" (\"%s\")\n", pszLineToCopy, filename );
CUtlBuffer fileBuf;
if ( !g_pFileSystem->ReadFile( filename, NULL, fileBuf ) )
{
Warning( "Can't find \"%s\"\n", filename );
continue;
}
// Grab just the filename.
char justFilename[MAX_PATH];
char *pLastSlash = max( strrchr( pszLineToCopy, '/' ), strrchr( pszLineToCopy, '\\' ) );
if ( pLastSlash )
Q_strncpy( justFilename, pLastSlash + 1, sizeof( justFilename ) );
else
Q_strncpy( justFilename, pszLineToCopy, sizeof( justFilename ) );
sprintf( filename, "%s%s", g_WorkerTempPath, justFilename );
if ( g_bVerbose )
printf( "creating \"%s\"\n", filename );
FILE *fp3 = fopen( filename, "wb" );
if ( !fp3 )
{
Error( "Can't open '%s' for writing.", pszLineToCopy );
continue;
}
fwrite( fileBuf.Base(), 1, fileBuf.GetBytesRemaining(), fp3 );
fclose( fp3 );
// SUPER EVIL, but if we don't do this, Windows will randomly nuke files of ours
// while we're running since they're in the temp path.
static CUtlVector< FILE * > s_arrHackedFiles;
static struct X_s_arrHackedFiles { ~X_s_arrHackedFiles() {
for ( int k = 0; k < s_arrHackedFiles.Count(); ++ k )
fclose( s_arrHackedFiles[k] );
} } s_autoCloseHackedFiles;
/* THIS IS THE EVIL LINE ----> */ FILE *fHack = fopen( filename, "r" );
s_arrHackedFiles.AddToTail( fHack );
// -- END of EVIL
}
}
void Worker_GetLocalCopyOfBinary( const char *pFilename )
{
CUtlBuffer fileBuf;
char tmpFilename[MAX_PATH];
sprintf( tmpFilename, "%s\\%s", g_ExeDir, pFilename );
if ( g_bVerbose )
printf( "trying to open: %s\n", tmpFilename );
FILE *fp = fopen( tmpFilename, "rb" );
if( !fp )
{
Assert( 0 );
fprintf( stderr, "Can't open %s!\n", pFilename );
exit( -1 );
}
fseek( fp, 0, SEEK_END );
int fileLen = ftell( fp );
fseek( fp, 0, SEEK_SET );
fileBuf.EnsureCapacity( fileLen );
int nBytesRead = fread( fileBuf.Base(), 1, fileLen, fp );
fclose( fp );
fileBuf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead );
char newFilename[MAX_PATH];
sprintf( newFilename, "%s%s", g_WorkerTempPath, pFilename );
FILE *fp2 = fopen( newFilename, "wb" );
if( !fp2 )
{
Assert( 0 );
fprintf( stderr, "Can't open %s!\n", newFilename );
exit( -1 );
}
fwrite( fileBuf.Base(), 1, fileLen, fp2 );
fclose( fp2 );
// SUPER EVIL, but if we don't do this, Windows will randomly nuke files of ours
// while we're running since they're in the temp path.
fopen( newFilename, "r" );
}
void Worker_GetLocalCopyOfBinaries( void )
{
Worker_GetLocalCopyOfBinary( "mysql_wrapper.dll" ); // This is necessary so VMPI doesn't run in SDK mode.
Worker_GetLocalCopyOfBinary( "vstdlib.dll" );
Worker_GetLocalCopyOfBinary( "tier0.dll" );
}
void Shared_ParseListOfCompileCommands( void )
{
double tt_start = Plat_FloatTime();
char fileListFileName[1024] = {0};
sprintf( fileListFileName, "%s\\filelist.txt", g_pShaderPath );
CUtlInplaceBuffer bffr( 0, 0, CUtlInplaceBuffer::TEXT_BUFFER );
if( !g_pFileSystem->ReadFile( fileListFileName, NULL, bffr) )
{
DebugOut( "Can't open %s!\n", fileListFileName );
fprintf( stderr, "Can't open %s!\n", fileListFileName );
exit( -1 );
}
CfgProcessor::ReadConfiguration( &bffr );
CfgProcessor::DescribeConfiguration( g_arrCompileEntries );
for ( CfgProcessor::CfgEntryInfo const *pInfo = g_arrCompileEntries.Get();
pInfo && pInfo->m_szName; ++ pInfo )
{
++ g_numShaders;
g_numStaticCombos += pInfo->m_numStaticCombos;
g_numCompileCommands = pInfo->m_iCommandEnd;
}
double tt_end = Plat_FloatTime();
Msg( "\rCompiling %s commands. \r", PrettyPrintNumber( g_numCompileCommands ), (tt_end - tt_start) );
}
void SetupExeDir( int argc, char **argv )
{
strcpy( g_ExeDir, argv[0] );
Q_StripFilename( g_ExeDir );
if ( g_ExeDir[0] == 0 )
{
Q_strncpy( g_ExeDir, ".\\", sizeof( g_ExeDir ) );
}
Q_FixSlashes( g_ExeDir );
}
void SetupPaths( int argc, char **argv )
{
GetTempPath( sizeof( g_WorkerTempPath ), g_WorkerTempPath );
strcat( g_WorkerTempPath, "shadercompiletemp\\" );
char tmp[MAX_PATH];
sprintf( tmp, "rd /s /q \"%s\"", g_WorkerTempPath );
system( tmp );
_mkdir( g_WorkerTempPath );
// printf( "g_WorkerTempPath: \"%s\"\n", g_WorkerTempPath );
CommandLine()->CreateCmdLine( argc, argv );
g_pShaderPath = CommandLine()->ParmValue( "-shaderpath", "" );
g_bVerbose = CommandLine()->FindParm("-verbose") != 0;
}
void SetupDebugFile( void )
{
#ifdef DEBUGFP
const char *pComputerName = getenv( "COMPUTERNAME" );
char filename[MAX_PATH];
sprintf( filename, "\\\\fileserver\\user\\gary\\debug\\%s.txt", pComputerName );
g_WorkerDebugFp = fopen( filename, "w" );
Assert( g_WorkerDebugFp );
DebugOut( "opened debug file\n" );
#endif
}
void CompileShaders_NoVMPI()
{
Worker_ProcessCommandRange_Singleton pcr;
//
// We will iterate on the cfg entries and process them
//
for ( CfgProcessor::CfgEntryInfo const *pEntry = g_arrCompileEntries.Get();
pEntry && pEntry->m_szName; ++ pEntry )
{
//
// Stick the shader info
//
ShaderInfo_t siLastShaderInfo;
memset( &siLastShaderInfo, 0, sizeof( siLastShaderInfo ) );
Shader_ParseShaderInfoFromCompileCommands( pEntry, siLastShaderInfo );
g_ShaderToShaderInfo[ pEntry->m_szName ] = siLastShaderInfo;
//
// Compile stuff
//
Worker_ProcessCommandRange( pEntry->m_iCommandStart, pEntry->m_iCommandEnd );
//
// Now when the whole shader is finished we can write it
//
char const *szShaderToWrite = pEntry->m_szName;
g_numCommandsCompleted = g_numCompileCommands;
WriteShaderFiles( szShaderToWrite );
g_numCommandsCompleted = pEntry->m_iCommandEnd;
}
Msg( "\r \r" );
}
class CDistributeShaderCompileMaster : public IWorkUnitDistributorCallbacks
{
public:
CDistributeShaderCompileMaster( void );
~CDistributeShaderCompileMaster( void );
public:
virtual void OnWorkUnitsCompleted( uint64 numWorkUnits );
private:
void ThreadProc( void );
friend DWORD WINAPI CDistributeShaderCompileMaster::ThreadProcAdapter( LPVOID pvArg );
static DWORD WINAPI ThreadProcAdapter( LPVOID pvArg ) { reinterpret_cast< CDistributeShaderCompileMaster * >( pvArg )->ThreadProc(); return 0; }
private:
HANDLE m_hThread;
HANDLE m_hEvent;
CThreadFastMutex m_mtx;
BOOL m_bRunning;
private:
CfgProcessor::CfgEntryInfo const *m_pAnalyzeShaders;
CUtlVector< char const * > m_arrShaderNamesToWrite;
};
CDistributeShaderCompileMaster::CDistributeShaderCompileMaster( void ) :
m_hThread( NULL ),
m_hEvent( NULL ),
m_bRunning( TRUE )
{
m_hEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
m_hThread = CreateThread( NULL, 0, ThreadProcAdapter, reinterpret_cast< LPVOID >(this), 0, NULL );
m_pAnalyzeShaders = g_arrCompileEntries.Get();
}
CDistributeShaderCompileMaster::~CDistributeShaderCompileMaster( void )
{
m_bRunning = FALSE;
SetEvent( m_hEvent );
WaitForSingleObject( m_hThread, INFINITE );
CloseHandle( m_hThread );
CloseHandle( m_hEvent );
}
void CDistributeShaderCompileMaster::OnWorkUnitsCompleted( uint64 numWorkUnits )
{
// Make sure that our mutex is in multi-threaded mode
Threading::g_mtxGlobal.SetThreadedMode( Threading::eMultiThreaded );
// Figure out how many commands have completed based on work units
g_numCompletedStaticCombos = numWorkUnits * g_nStaticCombosPerWorkUnit;
uint64 numStaticCombosOfTheEntry = 0;
CfgProcessor::CfgEntryInfo const *pEntry = GetEntryByStaticComboNum( g_numCompletedStaticCombos, &numStaticCombosOfTheEntry );
g_numCommandsCompleted = pEntry->m_iCommandStart + numStaticCombosOfTheEntry * pEntry->m_numDynamicCombos;
// Iterate over the shaders yet to be written and see if we can queue them
for ( ; m_pAnalyzeShaders->m_szName &&
m_pAnalyzeShaders->m_iCommandEnd <= g_numCommandsCompleted;
++ m_pAnalyzeShaders
)
{
m_mtx.Lock();
m_arrShaderNamesToWrite.AddToTail( m_pAnalyzeShaders->m_szName );
SetEvent( m_hEvent );
m_mtx.Unlock();
}
}
void CDistributeShaderCompileMaster::ThreadProc( void )
{
for ( ; m_bRunning; )
{
WaitForSingleObject( m_hEvent, INFINITE );
// Do a pump of shaders to write
for ( int numShadersWritten = 0; /* forever */ ; ++ numShadersWritten )
{
m_mtx.Lock();
char const * szShaderToWrite = NULL;
if ( m_arrShaderNamesToWrite.Count() > numShadersWritten )
szShaderToWrite = m_arrShaderNamesToWrite[ numShadersWritten ];
else
m_arrShaderNamesToWrite.RemoveAll();
m_mtx.Unlock();
if ( !szShaderToWrite )
break;
// We have the shader to write asynchronously
WriteShaderFiles( szShaderToWrite );
}
}
}
int ShaderCompile_Main( int argc, char* argv[] )
{
InstallSpewFunction();
g_bSuppressPrintfOutput = false;
g_flStartTime = Plat_FloatTime();
SetupDebugFile();
numthreads = 1; // managed specifically in Worker_ProcessCommandRange_Singleton::Startup
/*
Special section of code implementing "-subprocess" flag
*/
if ( int iSubprocess = CommandLine()->FindParm( "-subprocess" ) )
{
char const *szSubProcessData = CommandLine()->GetParm( 1 + iSubprocess );
return ShaderCompile_Subprocess_Main( szSubProcessData );
}
// This needs to get called before VMPI is setup because in SDK mode, VMPI will change the args around.
SetupExeDir( argc, argv );
g_bIsX360 = CommandLine()->FindParm( "-x360" ) != 0;
g_bIsPS3 = CommandLine()->FindParm( "-ps3" ) != 0;
g_bGeneratePS3DebugInfo = CommandLine()->FindParm( "-ps3debug" ) != 0;
g_bOptimizePS3ShaderScheduling = CommandLine()->FindParm( "-ps3optimizeschedules" ) != 0;
// g_bSuppressWarnings = g_bIsX360;
bool bShouldUseVMPI = ( CommandLine()->FindParm( "-nompi" ) == 0 );
if ( bShouldUseVMPI )
{
// Master, start accepting connections.
// Worker, make a connection.
DebugOut( "Before VMPI_Init\n" );
g_bSuppressPrintfOutput = true;
VMPIRunMode mode = VMPI_RUN_NETWORKED;
if ( !VMPI_Init( argc, argv, "dependency_info_shadercompile.txt", MyDisconnectHandler, mode ) )
{
g_bSuppressPrintfOutput = false;
DebugOut( "MPI_Init failed.\n" );
Error( "MPI_Init failed." );
}
extern void VMPI_SetWorkUnitsPartitionSize( int numWusToDeal );
VMPI_SetWorkUnitsPartitionSize( 32 );
}
SetupPaths( argc, argv );
g_bSuppressPrintfOutput = false;
DebugOut( "After VMPI_Init\n" );
// Setting up the minidump handlers
if ( bShouldUseVMPI && !g_bMPIMaster )
SetupToolsMinidumpHandler( VMPI_ExceptionFilter );
else
SetupDefaultToolsMinidumpHandler();
if ( CommandLine()->FindParm( "-game" ) == 0 )
{
// Used with filesystem_stdio.dll
FileSystem_Init( NULL, 0, FS_INIT_COMPATIBILITY_MODE );
}
else
{
// SDK uses this since it only has filesystem_steam.dll.
FileSystem_Init( NULL, 0, FS_INIT_FULL );
}
DebugOut( "After VMPI_FileSystem_Init\n" );
Shared_ParseListOfCompileCommands();
DebugOut( "After Shared_ParseListOfCompileCommands\n" );
if ( bShouldUseVMPI )
{
// Partition combos
g_nStaticCombosPerWorkUnit = 0;
if ( g_numStaticCombos )
{
if ( g_numStaticCombos <= 1024 )
g_nStaticCombosPerWorkUnit = 1;
else if ( g_numStaticCombos > 1024 * 10 )
g_nStaticCombosPerWorkUnit = 10;
else
g_nStaticCombosPerWorkUnit = g_numStaticCombos / 1024;
}
uint64 nWorkUnits;
if( g_nStaticCombosPerWorkUnit == 0 )
{
nWorkUnits = 1;
g_nStaticCombosPerWorkUnit = g_numStaticCombos;
}
else
{
nWorkUnits = g_numStaticCombos / g_nStaticCombosPerWorkUnit + 1;
}
DebugOut( "Before conditional\n" );
if ( g_bMPIMaster )
{
if ( g_bIsPS3 && g_bGeneratePS3DebugInfo )
{
// Prepare the files on the master which we will use to store the large amount of
// debug metadata generated by the Sony Cg compiler on each worker machine.
InitializePS3ShaderDebugPackFiles();
}
// Send all of the workers the complete list of work to do.
DebugOut( "Before STARTWORK_PACKETID\n" );
char packetID = STARTWORK_PACKETID;
VMPI_SendData( &packetID, sizeof( packetID ), VMPI_PERSISTENT );
// Compile master distribution tracker
CDistributeShaderCompileMaster dscm;
g_pDistributeWorkCallbacks = &dscm;
{
char chCommands[50], chStaticCombos[50], chNumWorkUnits[50];
sprintf( chCommands, "%s", PrettyPrintNumber( g_numCompileCommands ) );
sprintf( chStaticCombos, "%s", PrettyPrintNumber( g_numStaticCombos ) );
sprintf( chNumWorkUnits, "%s", PrettyPrintNumber( nWorkUnits ) );
Msg( "\rCompiling %s commands in %s work units.\n", chCommands, chNumWorkUnits );
}
// nWorkUnits is how many work units. . .1000 is good.
// The work unit number impies which combo to do.
DebugOut( "Before DistributeWork\n" );
DistributeWork( nWorkUnits, NULL, Master_ReceiveWorkUnitFn );
g_pDistributeWorkCallbacks = NULL;
}
else
{
// wait until we get a packet from the master to start doing stuff.
MessageBuffer buf;
DebugOut( "Before VMPI_DispatchUntil\n" );
while ( !g_bGotStartWorkPacket )
{
VMPI_DispatchNextMessage();
}
DebugOut( "after VMPI_DispatchUntil\n" );
DebugOut( "Before Worker_GetLocalCopyOfShaders\n" );
Worker_GetLocalCopyOfShaders();
DebugOut( "Before Worker_GetLocalCopyOfBinaries\n" );
Worker_GetLocalCopyOfBinaries();
DebugOut( "Before _chdir\n" );
_chdir( g_WorkerTempPath );
if ( g_bIsPS3 )
{
char szLogFilename[MAX_PATH];
if ( GetEnvironmentVariableA( "PS3COMPILELOG", szLogFilename, sizeof( szLogFilename ) ) == 0 )
{
uint nUniqueIndex = ( (DWORD)GetCurrentProcessId() ^ (DWORD)GetCurrentThreadId() ) + (DWORD)GetTickCount() + (DWORD)&nWorkUnits;
sprintf_s( szLogFilename, sizeof( szLogFilename ), "%s__ps3compilelog%08X__.tmp", g_WorkerTempPath, nUniqueIndex );
_unlink( szLogFilename );
SetEnvironmentVariableA( "PS3COMPILELOG", szLogFilename );
}
SetEnvironmentVariableA( "PS3FINDOPTIMALSCHEDULES", g_bOptimizePS3ShaderScheduling ? "1" : "0" );
SetEnvironmentVariableA( "PS3OPTIMALSCHEDULESFILE", g_bOptimizePS3ShaderScheduling ? "" : "ps3optimalschedules.bin" );
if ( g_bGeneratePS3DebugInfo )
{
// Required by the Sony Cg compiler to emit debug metadata. Files are emitted on worker machine then copied back to master.
SetEnvironmentVariable( "SCECGC_CAPTUREDIR", g_WorkerTempPath );
}
}
// nWorkUnits is how many work units. . .1000 is good.
// The work unit number impies which combo to do.
DebugOut( "Before DistributeWork\n" );
// Allows calling into ProcessCommandRange inside the worker function
{
Worker_ProcessCommandRange_Singleton pcr;
DistributeWork( nWorkUnits, Worker_ProcessWorkUnitFn, NULL );
}
}
g_bSuppressPrintfOutput = true;
g_bSuppressPrintfOutput = false;
}
else // no VMPI
{
Worker_GetLocalCopyOfShaders();
Worker_GetLocalCopyOfBinaries();
_chdir( g_WorkerTempPath );
{
char chCommands[50], chStaticCombos[50];
sprintf( chCommands, "%s", PrettyPrintNumber( g_numCompileCommands ) );
sprintf( chStaticCombos, "%s", PrettyPrintNumber( g_numStaticCombos ) );
Msg( "\rCompiling %s commands in %s static combos.\n", chCommands, chStaticCombos );
}
CompileShaders_NoVMPI();
}
Msg( "\r \r" );
if ( g_bMPIMaster || !bShouldUseVMPI )
{
char str[ 4096 ];
// Write everything that succeeded
int nStrings = g_ShaderByteCode.GetNumStrings();
for( int i = 0; i < nStrings; i++ )
{
WriteShaderFiles( g_ShaderByteCode.String(i) );
}
// Write all the errors
//////////////////////////////////////////////////////////////////////////
//
// Now deliver all our accumulated spew to the output
//
//////////////////////////////////////////////////////////////////////////
bool bValveVerboseComboErrors = ( getenv( "VALVE_VERBOSE_COMBO_ERRORS" ) &&
atoi( getenv( "VALVE_VERBOSE_COMBO_ERRORS" ) ) ) ? true : false;
// Compiler spew
for ( int k = 0, kEnd = g_Master_CompilerMsgInfo.GetNumStrings(); k < kEnd; ++ k )
{
char const * const szMsg = g_Master_CompilerMsgInfo.String( k );
CompilerMsgInfo const &cmi = g_Master_CompilerMsgInfo[ int_as_symid( k ) ];
char const * const szFirstCmd = cmi.GetFirstCommand();
char const * const szFirstMachineName = cmi.GetFirstMachineName();
int const numReported = cmi.GetNumTimesReported();
uint64 iFirstCommand = _strtoui64( szFirstCmd, NULL, 10 );
CfgProcessor::ComboHandle hCombo = NULL;
CfgProcessor::CfgEntryInfo const *pComboEntryInfo = NULL;
if ( CfgProcessor::Combo_GetNext( iFirstCommand, hCombo, g_numCompileCommands ) )
{
Combo_FormatCommand( hCombo, str );
pComboEntryInfo = Combo_GetEntryInfo( hCombo );
Combo_Free( hCombo );
}
else
{
sprintf( str, "cmd # %s", szFirstCmd );
}
Msg( "\n%s\n", szMsg );
Msg( " Reported %d time(s), first machine \"%s\", example command:\n", numReported, szFirstMachineName );
if ( bValveVerboseComboErrors )
{
Msg( " Verbose Description:\n" );
if ( pComboEntryInfo )
{
Msg( " Src File: %s\n", pComboEntryInfo->m_szShaderFileName );
Msg( " Tgt File: %s\n", pComboEntryInfo->m_szName );
}
// Between /DSHADERCOMBO= and /Dmain
char const *pBegin;
char const *pEnd;
if ( g_bIsPS3 )
{
pBegin = strstr( str, "-DSHADERCOMBO=" );
pEnd = strstr( str, "-Dmain" );
}
else
{
pBegin = strstr( str, "/DSHADERCOMBO=" );
pEnd = strstr( str, "/Dmain" );
}
if ( pBegin )
{
// Don't need to adjust the string for PS3 because it's the same length
pBegin += strlen( "/DSHADERCOMBO=" ) ;
char const *pSpace = strchr( pBegin, ' ' );
if ( pSpace )
Msg( " Combo # : %.*s\n", ( pSpace - pBegin ), pBegin );
}
if ( !pEnd )
pEnd = str + strlen( str );
while ( pBegin && *pBegin && !V_isspace( *pBegin ) )
++ pBegin;
while ( pBegin && *pBegin && V_isspace( *pBegin ) )
++ pBegin;
// Now parse all combo defines in [pBegin, pEnd]
while ( pBegin && *pBegin && ( pBegin < pEnd ) )
{
char const *pDefine;
if ( g_bIsPS3 )
{
pDefine = strstr( pBegin, "-D" );
}
else
{
pDefine = strstr( pBegin, "/D" );
}
if ( !pDefine || pDefine >= pEnd )
break;
char const *pEqSign = strchr( pDefine, '=' );
if ( !pEqSign || pEqSign >= pEnd )
break;
char const *pSpace = strchr( pEqSign, ' ' );
if ( !pSpace || pSpace >= pEnd )
pSpace = pEnd;
pBegin = pSpace;
Msg( " %.*s %.*s\n",
( pSpace - pEqSign - 1 ), pEqSign + 1,
( pEqSign - pDefine - 2 ), pDefine + 2 );
}
}
Msg( " %s\n", str );
}
// Failed shaders summary
for ( int k = 0, kEnd = g_Master_ShaderHadError.GetNumStrings(); k < kEnd; ++ k )
{
char const *szShaderName = g_Master_ShaderHadError.String( k );
if ( !g_Master_ShaderHadError[ int_as_symid( k ) ] )
continue;
Msg( "FAILED: %s\n", szShaderName );
}
//
// End
//
double end = Plat_FloatTime();
GetHourMinuteSecondsString( (int)( end - g_flStartTime ), str, sizeof( str ) );
DebugOut( "%s elapsed\n", str );
DebugOut( "Precise timing = %.5f\n", ( end - g_flStartTime ) );
if ( bShouldUseVMPI )
{
VMPI_FileSystem_Term();
DebugOut( "Before VMPI_Finalize\n" );
VMPI_Finalize();
}
if ( g_bIsPS3 && g_bGeneratePS3DebugInfo )
{
// On the master, expand the giant TOC/Pack files we've built into many tiny files needed by the shader debug process.
ExpandPS3DebugInfo();
}
}
return g_Master_ShaderHadError.GetNumStrings();
}
class CShaderCompileDLL : public IShaderCompileDLL
{
int main( int argc, char **argv );
};
int CShaderCompileDLL::main( int argc, char **argv )
{
return ShaderCompile_Main( argc, argv );
}
EXPOSE_SINGLE_INTERFACE( CShaderCompileDLL, IShaderCompileDLL, SHADER_COMPILE_INTERFACE_VERSION );
class CLaunchableDLL : public ILaunchableDLL
{
int main( int argc, char **argv )
{
return ShaderCompile_Main( argc, argv );
}
};
EXPOSE_SINGLE_INTERFACE( CLaunchableDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION );