diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/shadercompile | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/shadercompile')
| -rw-r--r-- | utils/shadercompile/cfgprocessor.cpp | 1399 | ||||
| -rw-r--r-- | utils/shadercompile/cfgprocessor.h | 76 | ||||
| -rw-r--r-- | utils/shadercompile/cmdsink.cpp | 114 | ||||
| -rw-r--r-- | utils/shadercompile/cmdsink.h | 117 | ||||
| -rw-r--r-- | utils/shadercompile/d3dxfxc.cpp | 259 | ||||
| -rw-r--r-- | utils/shadercompile/d3dxfxc.h | 24 | ||||
| -rw-r--r-- | utils/shadercompile/shadercompile.cpp | 2699 | ||||
| -rw-r--r-- | utils/shadercompile/shadercompile.h | 11 | ||||
| -rw-r--r-- | utils/shadercompile/shadercompile_dll.vpc | 60 | ||||
| -rw-r--r-- | utils/shadercompile/subprocess.cpp | 303 | ||||
| -rw-r--r-- | utils/shadercompile/subprocess.h | 104 | ||||
| -rw-r--r-- | utils/shadercompile/utlnodehash.h | 93 |
12 files changed, 5259 insertions, 0 deletions
diff --git a/utils/shadercompile/cfgprocessor.cpp b/utils/shadercompile/cfgprocessor.cpp new file mode 100644 index 0000000..38fc709 --- /dev/null +++ b/utils/shadercompile/cfgprocessor.cpp @@ -0,0 +1,1399 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include <stdio.h> +#include <io.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <time.h> +#include <stdarg.h> + +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier1/utlbuffer.h" + +#include "cfgprocessor.h" + +// Type conversions should be controlled by programmer explicitly - shadercompile makes use of 64-bit integer arithmetics +#pragma warning( error : 4244 ) + +namespace +{ + + int Usage() + { + printf( "Usage: expparser [OPTIONS] <input.txt 2>>output.txt\n" ); + printf( "Options: [none]\n" ); + printf( "Input: Sections in a file:\n" ); + printf( " #BEGIN\n" ); + printf( " #DEFINES:\n" ); + printf( " FASTPATH=0..2\n" ); + printf( " FOGTYPE=0..5\n" ); + printf( " #SKIP:\n" ); + printf( " ($FOGTYPE > 1) && (!$FASTPATH)\n" ); + printf( " #COMMAND:\n" ); + printf( " fxc.exe /DFLAGS=0x00\n" ); + printf( " /Foshader.o myshader.fxc > output.txt\n" ); + printf( " #END\n" ); + printf( "Version: expparser compiled on " __DATE__ " @ " __TIME__ ".\n" ); + + return -1; + } +}; + +namespace +{ + +static bool s_bNoOutput = true; + +void OutputF( FILE *f, char const *szFmt, ... ) +{ + if( s_bNoOutput ) + return; + + va_list args; + va_start( args, szFmt ); + vfprintf( f, szFmt, args ); + va_end( args ); +} + +}; + +////////////////////////////////////////////////////////////////////////// +// +// Utility classes: +// QuickArray<T> +// QuickStrIdx +// QuickString +// QuickStrUnique +// QuickMap +// +////////////////////////////////////////////////////////////////////////// + +#include <unordered_map> +#include <map> +#include <set> +#include <vector> +#include <string> +#include <algorithm> + +template < typename T > +class QuickArray : private std::vector < T > +{ +public: + void Append( T const &e ) { push_back( e ); }; + int Size( void ) const { return ( int ) size(); }; + T const & Get( int idx ) const { return at( idx ); }; + T & GetForEdit( int idx ) { return at( idx ); } + void Clear( void ) { clear(); } + T const * ArrayBase() const { return empty() ? NULL : &at( 0 ); } + T * ArrayBaseForEdit() { return empty() ? NULL : &at( 0 ); } +}; + +template < typename T > +class QuickStack : private std::vector < T > +{ +public: + void Push( T const &e ) { push_back( e ); }; + int Size( void ) const { return ( int ) size(); }; + T const & Top( void ) const { return at( Size() - 1 ); }; + void Pop( void ) { pop_back(); } + void Clear( void ) { clear(); } +}; + +template < typename K, typename V > +class QuickMap : private std::map < K, V > +{ +public: + void Append( K const &k, V const &v ) { insert( value_type( k, v ) ); }; + int Size( void ) const { return ( int ) size(); }; + V const & GetLessOrEq( K &k, V const &v ) const; + V const & Get( K const &k, V const &v ) const { const_iterator it = find( k ); return ( it != end() ? it->second : v ); }; + V & GetForEdit( K const &k, V &v ) { iterator it = find( k ); return ( it != end() ? it->second : v ); }; + void Clear( void ) { clear(); } +}; + +template < typename K, typename V > +V const & QuickMap< K, V >::GetLessOrEq( K &k, V const &v ) const +{ + const_iterator it = lower_bound( k ); + + if ( end() == it ) + { + if ( empty() ) + return v; + -- it; + } + + if ( k < it->first ) + { + if ( begin() == it ) + return v; + -- it; + } + + k = it->first; + return it->second; +} + +class QuickStrIdx : private std::unordered_map < std::string, int > +{ +public: + void Append( char const *szName, int idx ) { insert( value_type( szName, idx ) ); }; + int Size( void ) const { return ( int ) size(); }; + int Get( char const *szName ) const { const_iterator it = find( szName ); if ( end() != it ) return it->second; else return -1; }; + void Clear( void ) { clear(); } +}; + +class QuickStrUnique : private std::set < std::string > +{ +public: + int Size( void ) const { return ( int ) size(); } + bool Add( char const *szString ) { return insert( szString ).second; } + void Remove( char const *szString ) { erase( szString ); } + char const * Lookup( char const *szString ) { const_iterator it = find( szString ); if ( end() != it ) return it->data(); else return NULL; } + char const * AddLookup( char const *szString ) { iterator it = insert( szString ).first; if ( end() != it ) return it->data(); else return NULL; } + void Clear( void ) { clear(); } +}; + +class QuickString : private std::vector< char > +{ +public: + explicit QuickString( char const *szValue, size_t len = -1 ); + int Size() const { return ( int ) ( size() - 1 ); } + char * Get() { return &at( 0 ); } +}; + +QuickString::QuickString( char const *szValue, size_t len ) +{ + if ( size_t( -1 ) == len ) + len = ( size_t ) strlen( szValue ); + + resize( len + 1, 0 ); + memcpy( Get(), szValue, len ); +} + + +////////////////////////////////////////////////////////////////////////// +// +// Define class +// +////////////////////////////////////////////////////////////////////////// + +class Define +{ +public: + explicit Define( char const *szName, int min, int max, bool bStatic ) : m_sName( szName ), m_min( min ), m_max( max ), m_bStatic( bStatic ) {} + +public: + char const * Name() const { return m_sName.data(); }; + int Min() const { return m_min; }; + int Max() const { return m_max; }; + bool IsStatic() const { return m_bStatic; } + +protected: + std::string m_sName; + int m_min, m_max; + bool m_bStatic; +}; + + + +////////////////////////////////////////////////////////////////////////// +// +// Expression parser +// +////////////////////////////////////////////////////////////////////////// + +class IEvaluationContext +{ +public: + virtual int GetVariableValue( int nSlot ) = 0; + virtual char const * GetVariableName( int nSlot ) = 0; + virtual int GetVariableSlot( char const *szVariableName ) = 0; +}; + +class IExpression +{ +public: + virtual int Evaluate( IEvaluationContext *pCtx ) const = 0; + virtual void Print( IEvaluationContext *pCtx ) const = 0; +}; + +#define EVAL virtual int Evaluate( IEvaluationContext *pCtx ) const +#define PRNT virtual void Print( IEvaluationContext *pCtx ) const + +class CExprConstant : public IExpression +{ +public: + CExprConstant( int value ) : m_value( value ) {} + EVAL { pCtx; return m_value; }; + PRNT { pCtx; OutputF( stdout, "%d", m_value ); } +public: + int m_value; +}; + +class CExprVariable : public IExpression +{ +public: + CExprVariable( int nSlot ) : m_nSlot( nSlot ) {} + EVAL { return (m_nSlot >= 0) ? pCtx->GetVariableValue( m_nSlot ) : 0; }; + PRNT { (m_nSlot >= 0) ? OutputF( stdout, "$%s", pCtx->GetVariableName( m_nSlot ) ) : OutputF( stdout, "$**@**" ); } +public: + int m_nSlot; +}; + +class CExprUnary : public IExpression +{ +public: + CExprUnary( IExpression *x ) : m_x( x ) {} +public: + IExpression *m_x; +}; + +#define BEGIN_EXPR_UNARY( className ) class className : public CExprUnary { public: className( IExpression *x ) : CExprUnary( x ) {} +#define END_EXPR_UNARY() }; + +BEGIN_EXPR_UNARY( CExprUnary_Negate ) + EVAL { return ! m_x->Evaluate( pCtx ); }; + PRNT { OutputF( stdout, "!" ); m_x->Print(pCtx); } +END_EXPR_UNARY() + +class CExprBinary : public IExpression +{ +public: + CExprBinary( IExpression *x, IExpression *y ) : m_x( x ), m_y( y ) {} + virtual int Priority() const = 0; +public: + IExpression *m_x; + IExpression *m_y; +}; + +#define BEGIN_EXPR_BINARY( className ) class className : public CExprBinary { public: className( IExpression *x, IExpression *y ) : CExprBinary( x, y ) {} +#define EXPR_BINARY_PRIORITY( nPriority ) virtual int Priority() const { return nPriority; }; +#define END_EXPR_BINARY() }; + +BEGIN_EXPR_BINARY( CExprBinary_And ) + EVAL { return m_x->Evaluate( pCtx ) && m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " && " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 1 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_Or ) + EVAL { return m_x->Evaluate( pCtx ) || m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " || " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 2 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_Eq ) + EVAL { return m_x->Evaluate( pCtx ) == m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " == " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 0 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_Neq ) + EVAL { return m_x->Evaluate( pCtx ) != m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " != " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 0 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_G ) + EVAL { return m_x->Evaluate( pCtx ) > m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " > " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 0 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_Ge ) + EVAL { return m_x->Evaluate( pCtx ) >= m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " >= " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 0 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_L ) + EVAL { return m_x->Evaluate( pCtx ) < m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " < " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 0 ); +END_EXPR_BINARY() + +BEGIN_EXPR_BINARY( CExprBinary_Le ) + EVAL { return m_x->Evaluate( pCtx ) <= m_y->Evaluate( pCtx ); } + PRNT { OutputF( stdout, "( " ); m_x->Print( pCtx ); OutputF( stdout, " <= " ); m_y->Print( pCtx ); OutputF( stdout, " )" ); } + EXPR_BINARY_PRIORITY( 0 ); +END_EXPR_BINARY() + + +class CComplexExpression : public IExpression +{ +public: + CComplexExpression( IEvaluationContext *pCtx ) : m_pRoot( NULL ), m_pContext( pCtx ) { } + ~CComplexExpression() { Clear(); } + + void Parse( char const *szExpression ); + void Clear( void ); + +public: + EVAL { return m_pRoot ? m_pRoot->Evaluate( pCtx ? pCtx : m_pContext ) : 0; }; + PRNT { OutputF( stdout, "[ " ); m_pRoot ? m_pRoot->Print( pCtx ? pCtx : m_pContext ) : OutputF( stdout, "**NEXPR**" ); OutputF( stdout, " ]\n" ); } + +protected: + IExpression * ParseTopLevel( char *&szExpression ); + IExpression * ParseInternal( char *&szExpression ); + IExpression * Allocated( IExpression *pExpression ); + IExpression * AbortedParse( char *&szExpression ) const { *szExpression = 0; return m_pDefFalse; } + +protected: + QuickArray < IExpression * > m_arrAllExpressions; + IExpression *m_pRoot; + IEvaluationContext *m_pContext; + + IExpression *m_pDefTrue, *m_pDefFalse; +}; + +void CComplexExpression::Parse( char const *szExpression ) +{ + Clear(); + + m_pDefTrue = Allocated( new CExprConstant( 1 ) ); + m_pDefFalse = Allocated( new CExprConstant( 0 ) ); + + m_pRoot = m_pDefFalse; + + if (szExpression) + { + QuickString qs( szExpression ); + char *szExpression = qs.Get(), *szExpectEnd = szExpression + qs.Size(), *szParse = szExpression; + m_pRoot = ParseTopLevel( szParse ); + + if ( szParse != szExpectEnd ) + { + m_pRoot = m_pDefFalse; + } + } +} + +IExpression * CComplexExpression::ParseTopLevel( char *&szExpression ) +{ + QuickStack< CExprBinary * > exprStack; + IExpression *pFirstToken = ParseInternal( szExpression ); + + for ( ; ; ) + { + // Skip whitespace + while ( *szExpression && V_isspace( *szExpression ) ) + { + ++ szExpression; + } + + // End of binary expression + if ( !*szExpression || ( *szExpression == ')' ) ) + { + break; + } + + // Determine the binary expression type + CExprBinary *pBinaryExpression = NULL; + + if ( 0 ) + { + NULL; + } + else if ( !strncmp( szExpression, "&&", 2 ) ) + { + pBinaryExpression = new CExprBinary_And( NULL, NULL ); + szExpression += 2; + } + else if ( !strncmp( szExpression, "||", 2 ) ) + { + pBinaryExpression = new CExprBinary_Or( NULL, NULL ); + szExpression += 2; + } + else if ( !strncmp( szExpression, ">=", 2 ) ) + { + pBinaryExpression = new CExprBinary_Ge( NULL, NULL ); + szExpression += 2; + } + else if ( !strncmp( szExpression, "<=", 2 ) ) + { + pBinaryExpression = new CExprBinary_Le( NULL, NULL ); + szExpression += 2; + } + else if ( !strncmp( szExpression, "==", 2 ) ) + { + pBinaryExpression = new CExprBinary_Eq( NULL, NULL ); + szExpression += 2; + } + else if ( !strncmp( szExpression, "!=", 2 ) ) + { + pBinaryExpression = new CExprBinary_Neq( NULL, NULL ); + szExpression += 2; + } + else if ( *szExpression == '>' ) + { + pBinaryExpression = new CExprBinary_G( NULL, NULL ); + ++ szExpression; + } + else if ( *szExpression == '<' ) + { + pBinaryExpression = new CExprBinary_L( NULL, NULL ); + ++ szExpression; + } + else + { + return AbortedParse( szExpression ); + } + + Allocated( pBinaryExpression ); + pBinaryExpression->m_y = ParseInternal( szExpression ); + + // Figure out the expression priority + int nPriority = pBinaryExpression->Priority(); + IExpression *pLastExpr = pFirstToken; + while ( exprStack.Size() ) + { + CExprBinary *pStickTo = exprStack.Top(); + pLastExpr = pStickTo; + + if ( nPriority > pStickTo->Priority() ) + exprStack.Pop(); + else + break; + } + + if ( exprStack.Size() ) + { + CExprBinary *pStickTo = exprStack.Top(); + pBinaryExpression->m_x = pStickTo->m_y; + pStickTo->m_y = pBinaryExpression; + } + else + { + pBinaryExpression->m_x = pLastExpr; + } + + exprStack.Push( pBinaryExpression ); + } + + // Tip-of-the-tree retrieval + { + IExpression *pLastExpr = pFirstToken; + while ( exprStack.Size() ) + { + pLastExpr = exprStack.Top(); + exprStack.Pop(); + } + + return pLastExpr; + } +} + +IExpression * CComplexExpression::ParseInternal( char *&szExpression ) +{ + // Skip whitespace + while ( *szExpression && V_isspace( *szExpression ) ) + { + ++ szExpression; + } + + if ( !*szExpression ) + return AbortedParse( szExpression ); + + if ( 0 ) + { + NULL; + } + else if ( V_isdigit( *szExpression ) ) + { + long lValue = strtol( szExpression, &szExpression, 10 ); + return Allocated( new CExprConstant( lValue ) ); + } + else if ( !strncmp( szExpression, "defined", 7 ) ) + { + szExpression += 7; + IExpression *pNext = ParseInternal( szExpression ); + return Allocated( new CExprConstant( pNext->Evaluate( m_pContext ) ) ); + } + else if ( *szExpression == '(' ) + { + ++ szExpression; + IExpression *pBracketed = ParseTopLevel( szExpression ); + if ( ')' == *szExpression ) + { + ++ szExpression; + return pBracketed; + } + else + { + return AbortedParse( szExpression ); + } + } + else if ( *szExpression == '$' ) + { + size_t lenVariable = 0; + for ( char *szEndVar = szExpression + 1; *szEndVar; ++ szEndVar, ++ lenVariable ) + { + if ( !V_isalnum( *szEndVar ) ) + { + switch ( *szEndVar ) + { + case '_': + break; + default: + goto parsed_variable_name; + } + } + } + +parsed_variable_name: + int nSlot = m_pContext->GetVariableSlot( QuickString( szExpression + 1, lenVariable ).Get() ); + szExpression += lenVariable + 1; + + return Allocated( new CExprVariable( nSlot ) ); + } + else if ( *szExpression == '!' ) + { + ++ szExpression; + IExpression *pNext = ParseInternal( szExpression ); + return Allocated( new CExprUnary_Negate( pNext ) ); + } + else + { + return AbortedParse( szExpression ); + } +} + +IExpression * CComplexExpression::Allocated( IExpression *pExpression ) +{ + m_arrAllExpressions.Append( pExpression ); + return pExpression; +} + +void CComplexExpression::Clear( void ) +{ + for ( int k = 0; k < m_arrAllExpressions.Size() ; ++ k ) + { + delete m_arrAllExpressions.Get( k ); + } + + m_arrAllExpressions.Clear(); + m_pRoot = NULL; +} + + +#undef BEGIN_EXPR_UNARY +#undef BEGIN_EXPR_BINARY + +#undef END_EXPR_UNARY +#undef END_EXPR_BINARY + +#undef EVAL +#undef PRNT + +////////////////////////////////////////////////////////////////////////// +// +// Combo Generator class +// +////////////////////////////////////////////////////////////////////////// + +class ComboGenerator : public IEvaluationContext +{ +public: + void AddDefine( Define const &df ); + Define const * const GetDefinesBase( void ) { return m_arrDefines.ArrayBase(); } + Define const * const GetDefinesEnd( void ) { return m_arrDefines.ArrayBase() + m_arrDefines.Size(); } + + uint64 NumCombos(); + uint64 NumCombos( bool bStaticCombos ); + void RunAllCombos( CComplexExpression const &skipExpr ); + + // IEvaluationContext +public: + virtual int GetVariableValue( int nSlot ) { return m_arrVarSlots.Get( nSlot ); }; + virtual char const * GetVariableName( int nSlot ) { return m_arrDefines.Get( nSlot ).Name(); }; + virtual int GetVariableSlot( char const *szVariableName ) { return m_mapDefines.Get( szVariableName ); }; + +protected: + QuickArray< Define > m_arrDefines; + QuickStrIdx m_mapDefines; + QuickArray < int > m_arrVarSlots; +}; + +void ComboGenerator::AddDefine( Define const &df ) +{ + m_mapDefines.Append( df.Name(), m_arrDefines.Size() ); + m_arrDefines.Append( df ); + m_arrVarSlots.Append( 1 ); +} + +uint64 ComboGenerator::NumCombos() +{ + uint64 numCombos = 1; + + for ( int k = 0, kEnd = m_arrDefines.Size(); k < kEnd; ++ k ) + { + Define const &df = m_arrDefines.Get( k ); + numCombos *= ( df.Max() - df.Min() + 1 ); + } + + return numCombos; +} + +uint64 ComboGenerator::NumCombos( bool bStaticCombos ) +{ + uint64 numCombos = 1; + + for ( int k = 0, kEnd = m_arrDefines.Size(); k < kEnd; ++ k ) + { + Define const &df = m_arrDefines.Get( k ); + ( df.IsStatic() == bStaticCombos ) ? numCombos *= ( df.Max() - df.Min() + 1 ) : 0; + } + + return numCombos; +} + + +struct ComboEmission +{ + std::string m_sPrefix; + std::string m_sSuffix; +} g_comboEmission; + +size_t const g_lenTmpBuffer = 1 * 1024 * 1024; // 1Mb buffer for tmp storage +char g_chTmpBuffer[g_lenTmpBuffer]; + +void ComboGenerator::RunAllCombos( CComplexExpression const &skipExpr ) +{ + // Combo numbers + uint64 const nTotalCombos = NumCombos(); + + // Get the pointers + int * const pnValues = m_arrVarSlots.ArrayBaseForEdit(); + int * const pnValuesEnd = pnValues + m_arrVarSlots.Size(); + int *pSetValues; + + // Defines + Define const * const pDefVars = m_arrDefines.ArrayBase(); + Define const *pSetDef; + + // Set all the variables to max values + for ( pSetValues = pnValues, pSetDef = pDefVars; + pSetValues < pnValuesEnd; + ++ pSetValues, ++ pSetDef ) + { + *pSetValues = pSetDef->Max(); + } + + // Expressions distributed [0] = skips, [1] = evaluated + uint64 nSkipEvalCounters[2] = { 0, 0 }; + + // Go ahead and run the iterations + { + uint64 nCurrentCombo = nTotalCombos; + +next_combo_iteration: + -- nCurrentCombo; + int const valExprSkip = skipExpr.Evaluate( this ); + + ++ nSkipEvalCounters[ !valExprSkip ]; + + if ( valExprSkip ) + { + // TECH NOTE: Giving performance hint to compiler to place a jump here + // since there will be much more skips and actually less than 0.8% cases + // will be "OnCombo" hits. + NULL; + } + else + { + // ------- OnCombo( nCurrentCombo ); ---------- + OutputF( stderr, "%s ", g_comboEmission.m_sPrefix.data() ); + OutputF( stderr, "/DSHADERCOMBO=%d ", nCurrentCombo ); + + for ( pSetValues = pnValues, pSetDef = pDefVars; + pSetValues < pnValuesEnd; + ++ pSetValues, ++ pSetDef ) + { + OutputF( stderr, "/D%s=%d ", pSetDef->Name(), *pSetValues ); + } + + OutputF( stderr, "%s\n", g_comboEmission.m_sSuffix.data() ); + // ------- end of OnCombo --------------------- + } + + // Do a next iteration + for ( pSetValues = pnValues, pSetDef = pDefVars; + pSetValues < pnValuesEnd; + ++ pSetValues, ++ pSetDef ) + { + if ( -- *pSetValues >= pSetDef->Min() ) + goto next_combo_iteration; + + *pSetValues = pSetDef->Max(); + } + } + + OutputF( stdout, "Generated %d combos: %d evaluated, %d skipped.\n", nTotalCombos, nSkipEvalCounters[1], nSkipEvalCounters[0] ); +} + + +namespace ConfigurationProcessing +{ + class CfgEntry + { + public: + CfgEntry() : m_szName( "" ), m_szShaderSrc( "" ), m_pCg( NULL ), m_pExpr( NULL ) { memset( &m_eiInfo, 0, sizeof( m_eiInfo ) ); } + static void Destroy( CfgEntry const &x ) { delete x.m_pCg; delete x.m_pExpr; } + + public: + bool operator < ( CfgEntry const &x ) const { return m_pCg->NumCombos() < x.m_pCg->NumCombos(); } + + public: + char const *m_szName; + char const *m_szShaderSrc; + ComboGenerator *m_pCg; + CComplexExpression *m_pExpr; + std::string m_sPrefix; + std::string m_sSuffix; + + CfgProcessor::CfgEntryInfo m_eiInfo; + }; + + QuickStrUnique s_uniqueSections, s_strPool; + std::multiset< CfgEntry > s_setEntries; + + class ComboHandleImpl : public IEvaluationContext + { + public: + uint64 m_iTotalCommand; + uint64 m_iComboNumber; + uint64 m_numCombos; + CfgEntry const *m_pEntry; + + public: + ComboHandleImpl() : m_iTotalCommand( 0 ), m_iComboNumber( 0 ), m_numCombos( 0 ), m_pEntry( NULL ) {} + + // IEvaluationContext + public: + QuickArray < int > m_arrVarSlots; + public: + virtual int GetVariableValue( int nSlot ) { return m_arrVarSlots.Get( nSlot ); }; + virtual char const * GetVariableName( int nSlot ) { return m_pEntry->m_pCg->GetVariableName( nSlot ); }; + virtual int GetVariableSlot( char const *szVariableName ) { return m_pEntry->m_pCg->GetVariableSlot( szVariableName ); }; + + // External implementation + public: + bool Initialize( uint64 iTotalCommand, const CfgEntry *pEntry ); + bool AdvanceCommands( uint64 &riAdvanceMore ); + bool NextNotSkipped( uint64 iTotalCommand ); + bool IsSkipped( void ) { return ( m_pEntry->m_pExpr->Evaluate( this ) != 0 ); } + void FormatCommand( char *pchBuffer ); + }; + + QuickMap < uint64, ComboHandleImpl > s_mapComboCommands; + + bool ComboHandleImpl::Initialize( uint64 iTotalCommand, const CfgEntry *pEntry ) + { + m_iTotalCommand = iTotalCommand; + m_pEntry = pEntry; + m_numCombos = m_pEntry->m_pCg->NumCombos(); + + // Defines + Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase(); + Define const * const pDefVarsEnd = m_pEntry->m_pCg->GetDefinesEnd(); + Define const *pSetDef; + + // Set all the variables to max values + for ( pSetDef = pDefVars; + pSetDef < pDefVarsEnd; + ++ pSetDef ) + { + m_arrVarSlots.Append( pSetDef->Max() ); + } + + m_iComboNumber = m_numCombos - 1; + return true; + } + + bool ComboHandleImpl::AdvanceCommands( uint64 &riAdvanceMore ) + { + if ( !riAdvanceMore ) + return true; + + // Get the pointers + int * const pnValues = m_arrVarSlots.ArrayBaseForEdit(); + int * const pnValuesEnd = pnValues + m_arrVarSlots.Size(); + int *pSetValues; + + // Defines + Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase(); + Define const *pSetDef; + + if ( m_iComboNumber < riAdvanceMore ) + { + riAdvanceMore -= m_iComboNumber; + return false; + } + + // Do the advance + m_iTotalCommand += riAdvanceMore; + m_iComboNumber -= riAdvanceMore; + for ( pSetValues = pnValues, pSetDef = pDefVars; + ( pSetValues < pnValuesEnd ) && ( riAdvanceMore > 0 ); + ++ pSetValues, ++ pSetDef ) + { + riAdvanceMore += ( pSetDef->Max() - *pSetValues ); + *pSetValues = pSetDef->Max(); + + int iInterval = ( pSetDef->Max() - pSetDef->Min() + 1 ); + *pSetValues -= int( riAdvanceMore % iInterval ); + riAdvanceMore /= iInterval; + } + + return true; + } + + bool ComboHandleImpl::NextNotSkipped( uint64 iTotalCommand ) + { + // Get the pointers + int * const pnValues = m_arrVarSlots.ArrayBaseForEdit(); + int * const pnValuesEnd = pnValues + m_arrVarSlots.Size(); + int *pSetValues; + + // Defines + Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase(); + Define const *pSetDef; + + // Go ahead and run the iterations + { +next_combo_iteration: + if ( m_iTotalCommand + 1 >= iTotalCommand || + !m_iComboNumber ) + return false; + + -- m_iComboNumber; + ++ m_iTotalCommand; + + // Do a next iteration + for ( pSetValues = pnValues, pSetDef = pDefVars; + pSetValues < pnValuesEnd; + ++ pSetValues, ++ pSetDef ) + { + if ( -- *pSetValues >= pSetDef->Min() ) + goto have_combo_iteration; + + *pSetValues = pSetDef->Max(); + } + + return false; + +have_combo_iteration: + if ( m_pEntry->m_pExpr->Evaluate( this ) ) + goto next_combo_iteration; + else + return true; + } + } + + void ComboHandleImpl::FormatCommand( char *pchBuffer ) + { + // Get the pointers + int * const pnValues = m_arrVarSlots.ArrayBaseForEdit(); + int * const pnValuesEnd = pnValues + m_arrVarSlots.Size(); + int *pSetValues; + + // Defines + Define const * const pDefVars = m_pEntry->m_pCg->GetDefinesBase(); + Define const *pSetDef; + + { + // ------- OnCombo( nCurrentCombo ); ---------- + sprintf( pchBuffer, "%s ", m_pEntry->m_sPrefix.data() ); + pchBuffer += strlen( pchBuffer ); + + sprintf( pchBuffer, "/DSHADERCOMBO=%llu ", m_iComboNumber ); + pchBuffer += strlen( pchBuffer ); + + for ( pSetValues = pnValues, pSetDef = pDefVars; + pSetValues < pnValuesEnd; + ++ pSetValues, ++ pSetDef ) + { + sprintf( pchBuffer, "/D%s=%d ", pSetDef->Name(), *pSetValues ); + pchBuffer += strlen( pchBuffer ); + } + + sprintf( pchBuffer, "%s\n", m_pEntry->m_sSuffix.data() ); + pchBuffer += strlen( pchBuffer ); + // ------- end of OnCombo --------------------- + } + } + + struct CAutoDestroyEntries { + ~CAutoDestroyEntries( void ) { + std::for_each( s_setEntries.begin(), s_setEntries.end(), CfgEntry::Destroy ); + } + } s_autoDestroyEntries; + + + FILE *& GetInputStream( FILE * ) + { + static FILE *s_fInput = stdin; + return s_fInput; + } + + CUtlInplaceBuffer *& GetInputStream( CUtlInplaceBuffer * ) + { + static CUtlInplaceBuffer *s_fInput = NULL; + return s_fInput; + } + + char * GetLinePtr_Private( void ) + { + if ( CUtlInplaceBuffer *pUtlBuffer = GetInputStream( ( CUtlInplaceBuffer * ) NULL ) ) + return pUtlBuffer->InplaceGetLinePtr(); + + if ( FILE *fInput = GetInputStream( ( FILE * ) NULL ) ) + return fgets( g_chTmpBuffer, g_lenTmpBuffer, fInput ); + + return NULL; + } + + bool LineEquals( char const *sz1, char const *sz2, int nLen ) + { + return 0 == strncmp( sz1, sz2, nLen ); + } + + char * NextLine( void ) + { + if ( char *szLine = GetLinePtr_Private() ) + { + // Trim trailing whitespace as well + size_t len = ( size_t ) strlen( szLine ); + while ( len -- > 0 && V_isspace( szLine[ len ] ) ) + { + szLine[ len ] = 0; + } + return szLine; + } + return NULL; + } + + char * WaitFor( char const *szWaitString, int nMatchLength ) + { + while ( char *pchResult = NextLine() ) + { + if ( LineEquals( pchResult, szWaitString, nMatchLength ) ) + return pchResult; + } + + return NULL; + } + + bool ProcessSection( CfgEntry &cfge ) + { + bool bStaticDefines; + + // Read the next line for the section src file + if ( char *szLine = NextLine() ) + { + cfge.m_szShaderSrc = s_strPool.AddLookup( szLine ); + } + + if ( char *szLine = WaitFor( "#DEFINES-", 9 ) ) + { + bStaticDefines = ( szLine[9] == 'S' ); + } + else + return false; + + // Combo generator + ComboGenerator &cg = *( cfge.m_pCg = new ComboGenerator ); + CComplexExpression &exprSkip = *( cfge.m_pExpr = new CComplexExpression( &cg ) ); + + // #DEFINES: + while ( char *szLine = NextLine() ) + { + if ( LineEquals( szLine, "#SKIP", 5 ) ) + break; + + // static defines + if ( LineEquals( szLine, "#DEFINES-", 9 ) ) + { + bStaticDefines = ( szLine[9] == 'S' ); + continue; + } + + while ( *szLine && V_isspace(*szLine) ) + { + ++ szLine; + } + + // Find the eq + char *pchEq = strchr( szLine, '=' ); + if ( !pchEq ) + continue; + + char *pchStartRange = pchEq + 1; + *pchEq = 0; + while ( -- pchEq >= szLine && + V_isspace( *pchEq ) ) + { + *pchEq = 0; + } + if ( !*szLine ) + continue; + + // Find the end of range + char *pchEndRange = strstr( pchStartRange, ".." ); + if ( !pchEndRange ) + continue; + pchEndRange += 2; + + // Create the define + Define df( szLine, atoi( pchStartRange ), atoi( pchEndRange ), bStaticDefines ); + if ( df.Max() < df.Min() ) + continue; + + // Add the define + cg.AddDefine( df ); + } + + // #SKIP: + if ( char *szLine = NextLine() ) + { + exprSkip.Parse( szLine ); + } + else + return false; + + // #COMMAND: + if ( !WaitFor( "#COMMAND", 8 ) ) + return false; + if ( char *szLine = NextLine() ) + cfge.m_sPrefix = szLine; + if ( char *szLine = NextLine() ) + cfge.m_sSuffix = szLine; + + // #END + if ( !WaitFor( "#END", 4 ) ) + return false; + + return true; + } + + void UnrollSectionCommands( CfgEntry const &cfge ) + { + // Execute the combo computation + // + // + + g_comboEmission.m_sPrefix = cfge.m_sPrefix; + g_comboEmission.m_sSuffix = cfge.m_sSuffix; + + OutputF( stdout, "Preparing %d combos for %s...\n", cfge.m_pCg->NumCombos(), cfge.m_szName ); + OutputF( stderr, "#%s\n", cfge.m_szName ); + + time_t tt_start = time( NULL ); + cfge.m_pCg->RunAllCombos( *cfge.m_pExpr ); + time_t tt_end = time( NULL ); + + OutputF( stderr, "#%s\n", cfge.m_szName ); + OutputF( stdout, "Prepared %s combos. %d sec.\n", cfge.m_szName, ( int ) difftime( tt_end, tt_start ) ); + + g_comboEmission.m_sPrefix = ""; + g_comboEmission.m_sSuffix = ""; + } + + void RunSection( CfgEntry const &cfge ) + { + // Execute the combo computation + // + // + + g_comboEmission.m_sPrefix = cfge.m_sPrefix; + g_comboEmission.m_sSuffix = cfge.m_sSuffix; + + OutputF( stdout, "Preparing %d combos for %s...\n", cfge.m_pCg->NumCombos(), cfge.m_szName ); + OutputF( stderr, "#%s\n", cfge.m_szName ); + + time_t tt_start = time( NULL ); + cfge.m_pCg->RunAllCombos( *cfge.m_pExpr ); + time_t tt_end = time( NULL ); + + OutputF( stderr, "#%s\n", cfge.m_szName ); + OutputF( stdout, "Prepared %s combos. %d sec.\n", cfge.m_szName, ( int ) difftime( tt_end, tt_start ) ); + + g_comboEmission.m_sPrefix = ""; + g_comboEmission.m_sSuffix = ""; + } + + void ProcessConfiguration() + { + static bool s_bProcessOnce = false; + + while ( char *szLine = WaitFor( "#BEGIN", 6 ) ) + { + if ( ' ' == szLine[6] && !s_uniqueSections.Add(szLine + 7) ) + continue; + + CfgEntry cfge; + cfge.m_szName = s_uniqueSections.Lookup( szLine + 7 ); + ProcessSection( cfge ); + s_setEntries.insert( cfge ); + } + + uint64 nCurrentCommand = 0; + for( std::multiset< CfgEntry >::reverse_iterator it = s_setEntries.rbegin(), + itEnd = s_setEntries.rend(); it != itEnd; ++ it ) + { + // We establish a command mapping for the beginning of the entry + ComboHandleImpl chi; + chi.Initialize( nCurrentCommand, &*it ); + s_mapComboCommands.Append( nCurrentCommand, chi ); + + // We also establish mapping by either splitting the + // combos into 500 intervals or stepping by every 1000 combos. + int iPartStep = ( int ) max( 1000, (int)( chi.m_numCombos / 500 ) ); + for ( uint64 iRecord = nCurrentCommand + iPartStep; + iRecord < nCurrentCommand + chi.m_numCombos; + iRecord += iPartStep ) + { + uint64 iAdvance = iPartStep; + chi.AdvanceCommands( iAdvance ); + s_mapComboCommands.Append( iRecord, chi ); + } + + nCurrentCommand += chi.m_numCombos; + } + + // Establish the last command terminator + { + static CfgEntry s_term; + s_term.m_eiInfo.m_iCommandStart = s_term.m_eiInfo.m_iCommandEnd = nCurrentCommand; + s_term.m_eiInfo.m_numCombos = s_term.m_eiInfo.m_numStaticCombos = s_term.m_eiInfo.m_numDynamicCombos = 1; + s_term.m_eiInfo.m_szName = s_term.m_eiInfo.m_szShaderFileName = ""; + ComboHandleImpl chi; + chi.m_iTotalCommand = nCurrentCommand; + chi.m_pEntry = &s_term; + s_mapComboCommands.Append( nCurrentCommand, chi ); + } + } + +}; // namespace ConfigurationProcessing + +/* +int main( int argc, char **argv ) +{ + if ( _isatty( _fileno( stdin ) ) ) + { + return Usage(); + } + + // Go ahead processing the configuration + ConfigurationProcessing::ProcessConfiguration(); + + return 0; +} +*/ + +namespace CfgProcessor +{ + + typedef ConfigurationProcessing::ComboHandleImpl CPCHI_t; + CPCHI_t * FromHandle( ComboHandle hCombo ) { return reinterpret_cast < CPCHI_t * > ( hCombo ); } + ComboHandle AsHandle( CPCHI_t *pImpl ) { return reinterpret_cast < ComboHandle > ( pImpl ); } + +void ReadConfiguration( FILE *fInputStream ) +{ + CAutoPushPop < FILE * > pushInputStream( ConfigurationProcessing::GetInputStream( fInputStream ), fInputStream ); + ConfigurationProcessing::ProcessConfiguration(); +} + +void ReadConfiguration( CUtlInplaceBuffer *fInputStream ) +{ + CAutoPushPop < CUtlInplaceBuffer * > pushInputStream( ConfigurationProcessing::GetInputStream( fInputStream ), fInputStream ); + ConfigurationProcessing::ProcessConfiguration(); +} + +void DescribeConfiguration( CArrayAutoPtr < CfgEntryInfo > &rarrEntries ) +{ + rarrEntries.Delete(); + rarrEntries.Attach( new CfgEntryInfo [ ConfigurationProcessing::s_setEntries.size() + 1 ] ); + + CfgEntryInfo *pInfo = rarrEntries.Get(); + uint64 nCurrentCommand = 0; + + for( std::multiset< ConfigurationProcessing::CfgEntry >::reverse_iterator it = + ConfigurationProcessing::s_setEntries.rbegin(), + itEnd = ConfigurationProcessing::s_setEntries.rend(); + it != itEnd; ++ it, ++ pInfo ) + { + ConfigurationProcessing::CfgEntry const &e = *it; + + pInfo->m_szName = e.m_szName; + pInfo->m_szShaderFileName = e.m_szShaderSrc; + + pInfo->m_iCommandStart = nCurrentCommand; + pInfo->m_numCombos = e.m_pCg->NumCombos(); + pInfo->m_numDynamicCombos = e.m_pCg->NumCombos( false ); + pInfo->m_numStaticCombos = e.m_pCg->NumCombos( true ); + pInfo->m_iCommandEnd = pInfo->m_iCommandStart + pInfo->m_numCombos; + + const_cast< CfgEntryInfo & > ( e.m_eiInfo ) = *pInfo; + + nCurrentCommand += pInfo->m_numCombos; + } + + // Terminator + memset( pInfo, 0, sizeof( CfgEntryInfo ) ); + pInfo->m_iCommandStart = nCurrentCommand; + pInfo->m_iCommandEnd = nCurrentCommand; +} + +ComboHandle Combo_GetCombo( uint64 iCommandNumber ) +{ + // Find earlier command + uint64 iCommandFound = iCommandNumber; + CPCHI_t emptyCPCHI; + CPCHI_t const &chiFound = ConfigurationProcessing::s_mapComboCommands.GetLessOrEq( iCommandFound, emptyCPCHI ); + + if ( chiFound.m_iTotalCommand < 0 || + chiFound.m_iTotalCommand > iCommandNumber ) + return NULL; + + // Advance the handle as needed + CPCHI_t *pImpl = new CPCHI_t( chiFound ); + + uint64 iCommandFoundAdvance = iCommandNumber - iCommandFound; + pImpl->AdvanceCommands( iCommandFoundAdvance ); + + return AsHandle( pImpl ); +} + +ComboHandle Combo_GetNext( uint64 &riCommandNumber, ComboHandle &rhCombo, uint64 iCommandEnd ) +{ + // Combo handle implementation + CPCHI_t *pImpl = FromHandle( rhCombo ); + + if ( !rhCombo ) + { + // We don't have a combo handle that corresponds to the command + + // Find earlier command + uint64 iCommandFound = riCommandNumber; + CPCHI_t emptyCPCHI; + CPCHI_t const &chiFound = ConfigurationProcessing::s_mapComboCommands.GetLessOrEq( iCommandFound, emptyCPCHI ); + + if ( !chiFound.m_pEntry || + !chiFound.m_pEntry->m_pCg || + !chiFound.m_pEntry->m_pExpr || + chiFound.m_iTotalCommand < 0 || + chiFound.m_iTotalCommand > riCommandNumber ) + return NULL; + + // Advance the handle as needed + pImpl = new CPCHI_t( chiFound ); + rhCombo = AsHandle( pImpl ); + + uint64 iCommandFoundAdvance = riCommandNumber - iCommandFound; + pImpl->AdvanceCommands( iCommandFoundAdvance ); + + if ( !pImpl->IsSkipped() ) + return rhCombo; + } + + for ( ; ; ) + { + // We have the combo handle now + if ( pImpl->NextNotSkipped( iCommandEnd ) ) + { + riCommandNumber = pImpl->m_iTotalCommand; + return rhCombo; + } + + // We failed to get the next combo command (out of range) + if ( pImpl->m_iTotalCommand + 1 >= iCommandEnd ) + { + delete pImpl; + rhCombo = NULL; + riCommandNumber = iCommandEnd; + return NULL; + } + + // Otherwise we just have to obtain the next combo handle + riCommandNumber = pImpl->m_iTotalCommand + 1; + + // Delete the old combo handle + delete pImpl; + rhCombo = NULL; + + // Retrieve the next combo handle data + uint64 iCommandLookup = riCommandNumber; + CPCHI_t emptyCPCHI; + CPCHI_t const &chiNext = ConfigurationProcessing::s_mapComboCommands.GetLessOrEq( iCommandLookup, emptyCPCHI ); + Assert( ( iCommandLookup == riCommandNumber ) && ( chiNext.m_pEntry ) ); + + // Set up the new combo handle + pImpl = new CPCHI_t( chiNext ); + rhCombo = AsHandle( pImpl ); + + if ( !pImpl->IsSkipped() ) + return rhCombo; + } +} + +void Combo_FormatCommand( ComboHandle hCombo, char *pchBuffer ) +{ + CPCHI_t *pImpl = FromHandle( hCombo ); + pImpl->FormatCommand( pchBuffer ); +} + +uint64 Combo_GetCommandNum( ComboHandle hCombo ) +{ + if ( CPCHI_t *pImpl = FromHandle( hCombo ) ) + return pImpl->m_iTotalCommand; + else + return ~uint64(0); +} + +uint64 Combo_GetComboNum( ComboHandle hCombo ) +{ + if ( CPCHI_t *pImpl = FromHandle( hCombo ) ) + return pImpl->m_iComboNumber; + else + return ~uint64(0); +} + +CfgEntryInfo const *Combo_GetEntryInfo( ComboHandle hCombo ) +{ + if ( CPCHI_t *pImpl = FromHandle( hCombo ) ) + return &pImpl->m_pEntry->m_eiInfo; + else + return NULL; +} + +ComboHandle Combo_Alloc( ComboHandle hComboCopyFrom ) +{ + if ( hComboCopyFrom ) + return AsHandle( new CPCHI_t( * FromHandle( hComboCopyFrom ) ) ); + else + return AsHandle( new CPCHI_t ); +} + +void Combo_Assign( ComboHandle hComboDst, ComboHandle hComboSrc ) +{ + Assert( hComboDst ); + * FromHandle( hComboDst ) = * FromHandle( hComboSrc ); +} + +void Combo_Free( ComboHandle &rhComboFree ) +{ + delete FromHandle( rhComboFree ); + rhComboFree = NULL; +} + +}; // namespace CfgProcessor diff --git a/utils/shadercompile/cfgprocessor.h b/utils/shadercompile/cfgprocessor.h new file mode 100644 index 0000000..421fdab --- /dev/null +++ b/utils/shadercompile/cfgprocessor.h @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef CFGPROCESSOR_H +#define CFGPROCESSOR_H +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/smartptr.h" + + +/* + +Layout of the internal structures is as follows: + +|-------- shader1.fxc ---------||--- shader2.fxc ---||--------- shader3.fxc -----||-... +| 0 s s 3 s s s s 8 s 10 s s s || s s 2 3 4 s s s 8 || 0 s s s 4 s s s 8 9 s s s ||-... +| 0 1 2 3 4 5 6 7 8 9 10 * * * 14 * * * * *20 * * 23 * * *27 * * * * * * *35 * * * + +GetSection( 10 ) -> shader1.fxc +GetSection( 27 ) -> shader3.fxc + +GetNextCombo( 3, 3, 14 ) -> shader1.fxc : ( riCommandNumber = 8, rhCombo = "8" ) +GetNextCombo( 10, 10, 14 ) -> NULL : ( riCommandNumber = 14, rhCombo = NULL ) +GetNextCombo( 22, 8, 36 ) -> shader3.fxc : ( riCommandNumber = 23, rhCombo = "0" ) +GetNextCombo( 29, -1, 36 ) -> shader3.fxc : ( riCommandNumber = 31, rhCombo = "8" ) + +*/ + +class CUtlInplaceBuffer; + +namespace CfgProcessor +{ + +// Working with configuration +void ReadConfiguration( FILE *fInputStream ); +void ReadConfiguration( CUtlInplaceBuffer *fInputStream ); + +struct CfgEntryInfo +{ + char const *m_szName; // Name of the shader, e.g. "shader_ps20b" + char const *m_szShaderFileName; // Name of the src file, e.g. "shader_psxx.fxc" + uint64 m_numCombos; // Total possible num of combos, e.g. 1024 + uint64 m_numDynamicCombos; // Num of dynamic combos, e.g. 4 + uint64 m_numStaticCombos; // Num of static combos, e.g. 256 + uint64 m_iCommandStart; // Start command, e.g. 0 + uint64 m_iCommandEnd; // End command, e.g. 1024 +}; + +void DescribeConfiguration( CArrayAutoPtr < CfgEntryInfo > &rarrEntries ); + + +// Working with combos +typedef struct {} * ComboHandle; + +ComboHandle Combo_GetCombo( uint64 iCommandNumber ); +ComboHandle Combo_GetNext( uint64 &riCommandNumber, ComboHandle &rhCombo, uint64 iCommandEnd ); +void Combo_FormatCommand( ComboHandle hCombo, char *pchBuffer ); +uint64 Combo_GetCommandNum( ComboHandle hCombo ); +uint64 Combo_GetComboNum( ComboHandle hCombo ); +CfgEntryInfo const *Combo_GetEntryInfo( ComboHandle hCombo ); + +ComboHandle Combo_Alloc( ComboHandle hComboCopyFrom ); +void Combo_Assign( ComboHandle hComboDst, ComboHandle hComboSrc ); +void Combo_Free( ComboHandle &rhComboFree ); + +}; // namespace CfgProcessor + + +#endif // #ifndef CFGPROCESSOR_H diff --git a/utils/shadercompile/cmdsink.cpp b/utils/shadercompile/cmdsink.cpp new file mode 100644 index 0000000..024673b --- /dev/null +++ b/utils/shadercompile/cmdsink.cpp @@ -0,0 +1,114 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Command sink interface implementation. +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "cmdsink.h" + + +namespace CmdSink +{ + +// ------ implementation of CResponseFiles -------------- + +CResponseFiles::CResponseFiles( char const *szFileResult, char const *szFileListing ) : + m_fResult(NULL), + m_fListing(NULL), + m_lenResult(0), + m_dataResult(NULL), + m_dataListing(NULL) +{ + sprintf( m_szFileResult, szFileResult ); + sprintf( m_szFileListing, szFileListing ); +} + +CResponseFiles::~CResponseFiles( void ) +{ + if ( m_fResult ) + fclose( m_fResult ); + + if ( m_fListing ) + fclose( m_fListing ); +} + +bool CResponseFiles::Succeeded( void ) +{ + OpenResultFile(); + return ( m_fResult != NULL ); +} + +size_t CResponseFiles::GetResultBufferLen( void ) +{ + ReadResultFile(); + return m_lenResult; +} + +const void * CResponseFiles::GetResultBuffer( void ) +{ + ReadResultFile(); + return m_dataResult; +} + +const char * CResponseFiles::GetListing( void ) +{ + ReadListingFile(); + return ( ( m_dataListing && *m_dataListing ) ? m_dataListing : NULL ); +} + +void CResponseFiles::OpenResultFile( void ) +{ + if ( !m_fResult ) + { + m_fResult = fopen( m_szFileResult, "rb" ); + } +} + +void CResponseFiles::ReadResultFile( void ) +{ + if ( !m_dataResult ) + { + OpenResultFile(); + + if ( m_fResult ) + { + fseek( m_fResult, 0, SEEK_END ); + m_lenResult = (size_t) ftell( m_fResult ); + + if ( m_lenResult != size_t(-1) ) + { + m_bufResult.EnsureCapacity( m_lenResult ); + fseek( m_fResult, 0, SEEK_SET ); + fread( m_bufResult.Base(), 1, m_lenResult, m_fResult ); + m_dataResult = m_bufResult.Base(); + } + } + } +} + +void CResponseFiles::ReadListingFile( void ) +{ + if ( !m_dataListing ) + { + if ( !m_fListing ) + m_fListing = fopen( m_szFileListing, "rb" ); + + if ( m_fListing ) + { + fseek( m_fListing, 0, SEEK_END ); + size_t len = (size_t) ftell( m_fListing ); + + if ( len != size_t(-1) ) + { + m_bufListing.EnsureCapacity( len ); + fseek( m_fListing, 0, SEEK_SET ); + fread( m_bufListing.Base(), 1, len, m_fListing ); + m_dataListing = (const char *) m_bufListing.Base(); + } + } + } +} + +}; // namespace CmdSink diff --git a/utils/shadercompile/cmdsink.h b/utils/shadercompile/cmdsink.h new file mode 100644 index 0000000..9731595 --- /dev/null +++ b/utils/shadercompile/cmdsink.h @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Command sink interface implementation. +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef CMDSINK_H +#define CMDSINK_H +#ifdef _WIN32 +#pragma once +#endif + +#include <stdio.h> +#include <tier1/utlbuffer.h> + + +namespace CmdSink +{ + +/* + +struct IResponse + +Interface to give back command execution results. + +*/ +struct IResponse +{ + virtual ~IResponse( void ) {} + virtual void Release( void ) { delete this; } + + // Returns whether the command succeeded + virtual bool Succeeded( void ) = 0; + + // If the command succeeded returns the result buffer length, otherwise zero + virtual size_t GetResultBufferLen( void ) = 0; + // If the command succeeded returns the result buffer base pointer, otherwise NULL + virtual const void * GetResultBuffer( void ) = 0; + + // Returns a zero-terminated string of messages reported during command execution, or NULL if nothing was reported + virtual const char * GetListing( void ) = 0; +}; + + + + +/* + +Response implementation when the result should appear in +one file and the listing should appear in another file. + +*/ +class CResponseFiles : public IResponse +{ +public: + explicit CResponseFiles( char const *szFileResult, char const *szFileListing ); + ~CResponseFiles( void ); + +public: + // Returns whether the command succeeded + virtual bool Succeeded( void ); + + // If the command succeeded returns the result buffer length, otherwise zero + virtual size_t GetResultBufferLen( void ); + // If the command succeeded returns the result buffer base pointer, otherwise NULL + virtual const void * GetResultBuffer( void ); + + // Returns a zero-terminated string of messages reported during command execution + virtual const char * GetListing( void ); + +protected: + void OpenResultFile( void ); //!< Opens the result file if not open yet + void ReadResultFile( void ); //!< Reads the result buffer if not read yet + void ReadListingFile( void ); //!< Reads the listing buffer if not read yet + +protected: + char m_szFileResult[MAX_PATH]; //!< Name of the result file + char m_szFileListing[MAX_PATH]; //!< Name of the listing file + + FILE *m_fResult; //!< Result file (NULL if not open) + FILE *m_fListing; //!< Listing file (NULL if not open) + + CUtlBuffer m_bufResult; //!< Buffer holding the result data + size_t m_lenResult; //!< Result data length (0 if result not read yet) + const void *m_dataResult; //!< Data buffer pointer (NULL if result not read yet) + + CUtlBuffer m_bufListing; //!< Buffer holding the listing + const char *m_dataListing; //!< Listing buffer pointer (NULL if listing not read yet) +}; + +/* + +Response implementation when the result is a generic error. + +*/ +class CResponseError : public IResponse +{ +public: + explicit CResponseError( void ) {} + ~CResponseError( void ) {} + +public: + virtual bool Succeeded( void ) { return false; } + + virtual size_t GetResultBufferLen( void ) { return 0; } + virtual const void * GetResultBuffer( void ) { return NULL; } + + virtual const char * GetListing( void ) { return NULL; } +}; + + +}; // namespace CmdSink + + +#endif // #ifndef CMDSINK_H diff --git a/utils/shadercompile/d3dxfxc.cpp b/utils/shadercompile/d3dxfxc.cpp new file mode 100644 index 0000000..98327e9 --- /dev/null +++ b/utils/shadercompile/d3dxfxc.cpp @@ -0,0 +1,259 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: D3DX command implementation. +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "shadercompile.h" + +#include "d3dxfxc.h" +#include "cmdsink.h" + +// Required to compile using D3DX* routines in the same process +#include <d3dx9shader.h> +#include "dx_proxy/dx_proxy.h" + +#include <tier0/icommandline.h> +#include <tier1/strtools.h> + +#define D3DXSHADER_MICROCODE_BACKEND_OLD_DEPRECATED ( 1 << 25 ) + +namespace InterceptFxc +{ + + // The command that is intercepted by this namespace routines + static const char *s_pszCommand = "fxc.exe "; + static size_t s_uCommandLen = strlen( s_pszCommand ); + + namespace Private + { + // + // Response implementation + // + class CResponse : public CmdSink::IResponse + { + public: + explicit CResponse( LPD3DXBUFFER pShader, LPD3DXBUFFER pListing, HRESULT hr ); + ~CResponse( void ); + + public: + virtual bool Succeeded( void ) { return m_pShader && (m_hr == D3D_OK); } + virtual size_t GetResultBufferLen( void ) { return ( Succeeded() ? m_pShader->GetBufferSize() : 0 ); } + virtual const void * GetResultBuffer( void ) { return ( Succeeded() ? m_pShader->GetBufferPointer() : NULL ); } + virtual const char * GetListing( void ) { return (const char *) ( m_pListing ? m_pListing->GetBufferPointer() : NULL ); } + + protected: + LPD3DXBUFFER m_pShader; + LPD3DXBUFFER m_pListing; + HRESULT m_hr; + }; + + CResponse::CResponse( LPD3DXBUFFER pShader, LPD3DXBUFFER pListing, HRESULT hr ) : + m_pShader(pShader), + m_pListing(pListing), + m_hr(hr) + { + NULL; + } + + CResponse::~CResponse( void ) + { + if ( m_pShader ) + m_pShader->Release(); + + if ( m_pListing ) + m_pListing->Release(); + } + + // + // Perform a fast shader file compilation. + // TODO: avoid writing "shader.o" and "output.txt" files to avoid extra filesystem access. + // + // @param pszFilename the filename to compile (e.g. "debugdrawenvmapmask_vs20.fxc") + // @param pMacros null-terminated array of macro-defines + // @param pszModel shader model for compilation + // + void FastShaderCompile( const char *pszFilename, const D3DXMACRO *pMacros, const char *pszModel, CmdSink::IResponse **ppResponse ) + { + LPD3DXBUFFER pShader = NULL; // NOTE: Must release the COM interface later + LPD3DXBUFFER pErrorMessages = NULL; // NOTE: Must release COM interface later + + // DxProxyModule + static DxProxyModule s_dxModule; + + // X360TEMP: This needs to be moved to an external semantic (or fixed) + bool bIsX360 = false; + for ( int i=0; ;i++ ) + { + if ( !pMacros[i].Name ) + { + break; + } + if ( V_stristr( pMacros[i].Name, "_X360" ) && atoi( pMacros[i].Definition ) ) + { + bIsX360 = true; + break; + } + } + + HRESULT hr = s_dxModule.D3DXCompileShaderFromFile( pszFilename, pMacros, NULL /* LPD3DXINCLUDE */, + "main", pszModel, 0, &pShader, &pErrorMessages, + NULL /* LPD3DXCONSTANTTABLE *ppConstantTable */ ); + + if ( ppResponse ) + { + *ppResponse = new CResponse( pShader, pErrorMessages, hr ); + } + else + { + if ( pShader ) + { + pShader->Release(); + } + + if ( pErrorMessages ) + { + pErrorMessages->Release(); + } + } + } + + }; // namespace Private + + // + // Completely mimic the behaviour of "fxc.exe" in the specific cases related + // to shader compilations. + // + // @param pCommand the command in form + // "fxc.exe /DSHADERCOMBO=1 /DTOTALSHADERCOMBOS=4 /DCENTROIDMASK=0 /DNUMDYNAMICCOMBOS=4 /DFLAGS=0x0 /DNUM_BONES=1 /Dmain=main /Emain /Tvs_2_0 /DSHADER_MODEL_VS_2_0=1 /D_X360=1 /nologo /Foshader.o debugdrawenvmapmask_vs20.fxc>output.txt 2>&1" + // + void ExecuteCommand( const char *pCommand, CmdSink::IResponse **ppResponse ) + { + // Expect that the command passed is exactly "fxc.exe" + Assert( !strncmp( pCommand, s_pszCommand, s_uCommandLen ) ); + pCommand += s_uCommandLen; + + // A duplicate portion of memory for modifications + void *bufEditableCommand = alloca( strlen( pCommand ) + 1 ); + char *pEditableCommand = strcpy( (char *) bufEditableCommand, pCommand ); + + // Macros to be defined for D3DX + CUtlVector<D3DXMACRO> macros; + + // Shader model (determined when parsing "/D" flags) + const char *pszShaderModel = NULL; + + // Iterate over the command line and find all "/D...=..." settings + for ( char *pszFlag = pEditableCommand; + ( pszFlag = strstr( pszFlag, "/D" ) ) != NULL; + /* advance inside */ ) + { + // Make sure this is a command-line flag (basic check for preceding space) + if ( pszFlag > pEditableCommand && + pszFlag[-1] && + ' ' != pszFlag[-1] ) + { + ++ pszFlag; + continue; + } + + // Name is immediately after "/D" + char *pszFlagName = pszFlag + 2; // 2 = length of "/D" + // Value will be determined later + char *pszValue = ""; + + if ( char *pchEq = strchr( pszFlag, '=' ) ) + { + // Value is after '=' sign + *pchEq = 0; + pszValue = pchEq + 1; + pszFlag = pszValue; + } + + if ( char *pchSpace = strchr( pszFlag, ' ' ) ) + { + // Space is designating the end of the flag + *pchSpace = 0; + pszFlag = pchSpace + 1; + } + else + { + // Reached end of command line + pszFlag = ""; + } + + // Shader model extraction + if ( !strncmp(pszFlagName, "SHADER_MODEL_", 13) ) + { + pszShaderModel = pszFlagName + 13; + } + + // Add the macro definition to the macros array + int iMacroIdx = macros.AddToTail(); + D3DXMACRO &m = macros[iMacroIdx]; + + // Fill the macro data + m.Name = pszFlagName; + m.Definition = pszValue; + } + + // Add a NULL-terminator + { + D3DXMACRO nullTerminatorMacro = { NULL, NULL }; + macros.AddToTail( nullTerminatorMacro ); + } + + // Convert shader model to lowercase + char chShaderModel[20] = {0}; + if(pszShaderModel) + { + Q_strncpy( chShaderModel, pszShaderModel, sizeof(chShaderModel) - 1 ); + } + Q_strlower( chShaderModel ); + + // Determine the file name (at the end of the command line before redirection) + char const *pszFilename = ""; + if ( const char *pchCmdRedirect = strstr( pCommand, ">output.txt " ) ) + { + size_t uCmdEndOffset = ( pchCmdRedirect - pCommand ); + + pEditableCommand[uCmdEndOffset] = 0; + pszFilename = &pEditableCommand[uCmdEndOffset]; + + while ( pszFilename > pEditableCommand && + pszFilename[-1] && + ' ' != pszFilename[-1] ) + { + -- pszFilename; + } + } + + // Compile the stuff + Private::FastShaderCompile( pszFilename, macros.Base(), chShaderModel, ppResponse ); + } + + bool TryExecuteCommand( const char *pCommand, CmdSink::IResponse **ppResponse ) + { + { + static bool s_bNoIntercept = ( CommandLine()->FindParm("-nointercept") != 0 ); + static int s_dummy = ( Msg( s_bNoIntercept ? + "[shadercompile] Using old slow technique - runs 'fxc.exe'.\n" : + "[shadercompile] Using new faster Vitaliy's implementation.\n" ), 1 ); + if ( !s_bNoIntercept && !strncmp(pCommand, InterceptFxc::s_pszCommand, InterceptFxc::s_uCommandLen) ) + { + // Trap "fxc.exe" so that we did not spawn extra process every time + InterceptFxc::ExecuteCommand( pCommand, ppResponse ); + return true; + } + } + + return false; + } + +}; // namespace InterceptFxc + + + + diff --git a/utils/shadercompile/d3dxfxc.h b/utils/shadercompile/d3dxfxc.h new file mode 100644 index 0000000..8a4cab8 --- /dev/null +++ b/utils/shadercompile/d3dxfxc.h @@ -0,0 +1,24 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: D3DX command implementation. +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef D3DXFXC_H +#define D3DXFXC_H +#ifdef _WIN32 +#pragma once +#endif + +#include "cmdsink.h" + +namespace InterceptFxc +{ + + bool TryExecuteCommand( const char *pCommand, CmdSink::IResponse **ppResponse ); + +}; // namespace InterceptFxc + +#endif // #ifndef D3DXFXC_H diff --git a/utils/shadercompile/shadercompile.cpp b/utils/shadercompile/shadercompile.cpp new file mode 100644 index 0000000..60eb68a --- /dev/null +++ b/utils/shadercompile/shadercompile.cpp @@ -0,0 +1,2699 @@ +//========= Copyright 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 "utlbinaryblock.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 "cmdsink.h" +#include "d3dxfxc.h" +#include "subprocess.h" +#include "cfgprocessor.h" + + +// 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 ); +} + + +// VMPI packets +#define STARTWORK_PACKETID 5 +#define WORKUNIT_PACKETID 6 +#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_bSuppressWarnings = false; + +FORCEINLINE long AsTargetLong( long x ) { return ( ( g_bIsX360 ) ? ( 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 ) { if ( !m_numTimesReported ) m_sFirstCommand = szCommand; m_numTimesReported += numTimesReported; } + +public: + char const * GetFirstCommand() const { return m_sFirstCommand.String(); } + int GetNumTimesReported() const { return m_numTimesReported; } + +protected: + CUtlString m_sFirstCommand; + 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; + + + +CDispatchReg g_DistributeWorkReg( WORKUNIT_PACKETID, DistributeWorkDispatch ); + +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 ); + +// 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 ( V_isalpha( *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 ); +} + +void ErrMsgDispatchInt( char *szMessage, char const *szShaderName = NULL ) +{ + // First line is the command number "szCommand" + char *szCommand = ConsumeCharacters( szMessage, 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 ); + 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; + + // Set the msg info + g_Master_CompilerMsgInfo[ szMsgLine ].SetMsgReportedCommand( szCommand, atoi( szNumTimesReported ) ); + + 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_OpportunisticCompress( 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" ); + } + + 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 ); // 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 ) + { + 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" ); + + FILE *batFp = fopen( "temp.bat", "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). + "temp.bat", // 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 ); +} + +// 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 ); + + 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 ) + pByteCodeArray->DeleteByKey( nComboOfEntry ); + if( fabs( fCurTime - s_fLastInfoTime ) > 1.f ) + { + Msg( "\rCompiling %s [ %2llu 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(); + pByteCodeArray->DeleteByKey( nComboOfEntry ); + 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_%08llX_%08X", pSp->dwSvcThreadId, (long long)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 ); + + 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 0000: Compiler failed without error description, latest version of fxc.exe might give a description." ); + 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 = 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 ) ); + } + + ////////////////////////////////////////////////////////////////////////// + // + // 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 = strstr( cmd, "/DCENTROIDMASK=" ); + const char *pFlags = strstr( cmd, "/DFLAGS=0x" ); + const char *pShaderModel = strstr( cmd, "/DSHADER_MODEL_" ); + + if( !pCentroidMask || !pFlags || !pShaderModel ) + { + Assert( !"!pCentroidMask || !pFlags || !pShaderModel" ); + return; + } + + 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() ) + { + V_MakeAbsolutePath( filename, sizeof( filename ), pszLineToCopy, g_pShaderPath ); + + 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]; + 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 ) ); +} + +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_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 ) + { + // 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, WORKUNIT_PACKETID, 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 ); + + // 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, WORKUNIT_PACKETID, 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(); + 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), example command:\n", numReported); + + 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 = strstr( str, "/DSHADERCOMBO=" ); + char const *pEnd = strstr( str, "/Dmain" ); + if ( pBegin ) + { + 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 = 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(); + } + } + + 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 ); diff --git a/utils/shadercompile/shadercompile.h b/utils/shadercompile/shadercompile.h new file mode 100644 index 0000000..4829c2c --- /dev/null +++ b/utils/shadercompile/shadercompile.h @@ -0,0 +1,11 @@ + +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Module prototypes. +// +// $NoKeywords: $ +// +//=============================================================================// + +void DebugOut( const char *pMsg, ... ); +void DebugSafeWaitPoint( bool bForceWait = false ); diff --git a/utils/shadercompile/shadercompile_dll.vpc b/utils/shadercompile/shadercompile_dll.vpc new file mode 100644 index 0000000..0fa4f93 --- /dev/null +++ b/utils/shadercompile/shadercompile_dll.vpc @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// SHADERCOMPILE_DLL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common;..\vmpi;$SRCDIR\dx9sdk\include" + $PreprocessorDefinitions "$BASE;SHADERCOMPILE_EXPORTS;MPI" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib odbc32.lib odbccp32.lib" + } +} + +$Project "Shadercompile_dll" +{ + $Folder "Source Files" + { + $File "..\common\cmdlib.cpp" + $File "cmdsink.cpp" + $File "d3dxfxc.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "..\common\pacifier.cpp" + $File "shadercompile.cpp" + $File "subprocess.cpp" + $File "cfgprocessor.cpp" + $File "..\common\threads.cpp" + $File "..\common\vmpi_tools_shared.cpp" + $File "..\common\tools_minidump.cpp" + } + + $Folder "Header Files" + { + $File "cmdsink.h" + $File "d3dxfxc.h" + $File "$SRCDIR\public\ishadercompiledll.h" + $File "shadercompile.h" + $File "utlnodehash.h" + $File "cfgprocessor.h" + $File "$SRCDIR\public\tier1\UtlStringMap.h" + } + + $Folder "Link Libraries" + { + $Lib tier2 + $Lib vmpi + $Lib $LIBCOMMON\lzma + } +} diff --git a/utils/shadercompile/subprocess.cpp b/utils/shadercompile/subprocess.cpp new file mode 100644 index 0000000..2fb8b63 --- /dev/null +++ b/utils/shadercompile/subprocess.cpp @@ -0,0 +1,303 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include <windows.h> + +#include "cmdsink.h" + +#include "subprocess.h" + +#include "d3dxfxc.h" + +#include "tools_minidump.h" + +////////////////////////////////////////////////////////////////////////// +// +// Base implementation of the shaderd kernel objects +// +////////////////////////////////////////////////////////////////////////// + +SubProcessKernelObjects::SubProcessKernelObjects( void ) : + m_hMemorySection( NULL ), + m_hMutex( NULL ) +{ + ZeroMemory( m_hEvent, sizeof( m_hEvent ) ); +} + +SubProcessKernelObjects::~SubProcessKernelObjects( void ) +{ + Close(); +} + +BOOL SubProcessKernelObjects::Create( char const *szBaseName ) +{ + char chBufferName[0x100] = { 0 }; + + sprintf( chBufferName, "%s_msec", szBaseName ); + m_hMemorySection = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, + PAGE_READWRITE, 0, 4 * 1024 * 1024, chBufferName ); // 4Mb for a piece + if ( NULL != m_hMemorySection ) + { + if ( ERROR_ALREADY_EXISTS == GetLastError() ) + { + CloseHandle( m_hMemorySection ); + m_hMemorySection = NULL; + + Assert( 0 && "CreateFileMapping - already exists!\n" ); + } + } + + sprintf( chBufferName, "%s_mtx", szBaseName ); + m_hMutex = CreateMutex( NULL, FALSE, chBufferName ); + + for ( int k = 0; k < 2; ++ k ) + { + sprintf( chBufferName, "%s_evt%d", szBaseName, k ); + m_hEvent[k] = CreateEvent( NULL, FALSE, ( k ? TRUE /* = master */ : FALSE ), chBufferName ); + } + + return IsValid(); +} + +BOOL SubProcessKernelObjects::Open( char const *szBaseName ) +{ + char chBufferName[0x100] = { 0 }; + + sprintf( chBufferName, "%s_msec", szBaseName ); + m_hMemorySection = OpenFileMapping( FILE_MAP_ALL_ACCESS, FALSE, chBufferName ); + + sprintf( chBufferName, "%s_mtx", szBaseName ); + m_hMutex = OpenMutex( MUTEX_ALL_ACCESS, FALSE, chBufferName ); + + for ( int k = 0; k < 2; ++ k ) + { + sprintf( chBufferName, "%s_evt%d", szBaseName, k ); + m_hEvent[k] = OpenEvent( EVENT_ALL_ACCESS, FALSE, chBufferName ); + } + + return IsValid(); +} + +BOOL SubProcessKernelObjects::IsValid( void ) const +{ + return m_hMemorySection && m_hMutex && m_hEvent; +} + +void SubProcessKernelObjects::Close( void ) +{ + if ( m_hMemorySection ) + CloseHandle( m_hMemorySection ); + + if ( m_hMutex ) + CloseHandle( m_hMutex ); + + for ( int k = 0; k < 2; ++ k ) + if ( m_hEvent[k] ) + CloseHandle( m_hEvent[k] ); +} + +////////////////////////////////////////////////////////////////////////// +// +// Helper class to send data back and forth +// +////////////////////////////////////////////////////////////////////////// + +void * SubProcessKernelObjects_Memory::Lock( void ) +{ + // Wait for our turn to act + for ( unsigned iWaitAttempt = 0; iWaitAttempt < 13u; ++ iWaitAttempt ) + { + DWORD dwWait = ::WaitForSingleObject( m_pObjs->m_hEvent[ m_pObjs->m_dwCookie ], 10000 ); + switch ( dwWait ) + { + case WAIT_OBJECT_0: + { + m_pLockData = MapViewOfFile( m_pObjs->m_hMemorySection, FILE_MAP_ALL_ACCESS, 0, 0, 0 ); + + if ( * ( const DWORD * ) m_pLockData != m_pObjs->m_dwCookie ) + { + // Yes, this is our turn, set our cookie in that memory segment + * ( DWORD * ) m_pLockData = m_pObjs->m_dwCookie; + m_pMemory = ( ( byte * ) m_pLockData ) + 2 * sizeof( DWORD ); + + return m_pMemory; + } + else + { + // We just acted, still waiting for result + UnmapViewOfFile( m_pLockData ); + m_pLockData = NULL; + + SetEvent( m_pObjs->m_hEvent[ !m_pObjs->m_dwCookie ] ); + Sleep( 1 ); + + continue; + } + } + break; + + case WAIT_TIMEOUT: + { + char chMsg[0x100]; + sprintf( chMsg, "th%08X> WAIT_TIMEOUT in Memory::Lock (attempt %d).\n", GetCurrentThreadId(), iWaitAttempt ); + OutputDebugString( chMsg ); + } + continue; // retry + + default: + OutputDebugString( "WAIT failure in Memory::Lock\n" ); + SetLastError( ERROR_BAD_UNIT ); + return NULL; + } + } + + OutputDebugString( "Ran out of wait attempts in Memory::Lock\n" ); + SetLastError( ERROR_NOT_READY ); + return NULL; +} + +BOOL SubProcessKernelObjects_Memory::Unlock( void ) +{ + if ( m_pLockData ) + { + // Assert that the memory hasn't been spoiled + Assert( m_pObjs->m_dwCookie == * ( const DWORD * ) m_pLockData ); + + UnmapViewOfFile( m_pLockData ); + m_pMemory = NULL; + m_pLockData = NULL; + + SetEvent( m_pObjs->m_hEvent[ !m_pObjs->m_dwCookie ] ); + Sleep( 1 ); + + return TRUE; + } + + return FALSE; +} + + +////////////////////////////////////////////////////////////////////////// +// +// Implementation of the command subprocess: +// +// MASTER ---- command -------> SUB +// string - zero terminated command string. +// +// +// MASTER <---- result -------- SUB +// dword - 1 if succeeded, 0 if failed +// dword - result buffer length, 0 if failed +// <bytes> - result buffer data, none if result buffer length is 0 +// string - zero-terminated listing string +// +////////////////////////////////////////////////////////////////////////// + + +CSubProcessResponse::CSubProcessResponse( void const *pvMemory ) : + m_pvMemory( pvMemory ) +{ + byte const *pBytes = ( byte const * ) pvMemory; + + m_dwResult = * ( DWORD const * ) pBytes; + pBytes += sizeof( DWORD ); + + m_dwResultBufferLength = * ( DWORD const * ) pBytes; + pBytes += sizeof( DWORD ); + + m_pvResultBuffer = pBytes; + pBytes += m_dwResultBufferLength; + + m_szListing = ( char const * ) ( *pBytes ? pBytes : NULL ); +} + + +void ShaderCompile_Subprocess_ExceptionHandler( unsigned long exceptionCode, void *pvExceptionInfo ) +{ + // Subprocesses just silently die in our case, then this case will be detected by the worker process and an error code will be passed to the master + Assert( !"ShaderCompile_Subprocess_ExceptionHandler" ); + ::TerminateProcess( ::GetCurrentProcess(), exceptionCode ); +} + + +int ShaderCompile_Subprocess_Main( char const *szSubProcessData ) +{ + // Set our crash handler + SetupToolsMinidumpHandler( ShaderCompile_Subprocess_ExceptionHandler ); + + // Get our kernel objects + SubProcessKernelObjects_Open objs( szSubProcessData ); + + if ( !objs.IsValid() ) + return -1; + + // Enter the command pumping loop + SubProcessKernelObjects_Memory shrmem( &objs ); + for ( + void *pvMemory = NULL; + NULL != ( pvMemory = shrmem.Lock() ); + shrmem.Unlock() + ) + { + // The memory is actually a command + char const *szCommand = ( char const * ) pvMemory; + + if ( !stricmp( "keepalive", szCommand ) ) + { + ZeroMemory( pvMemory, 4 * sizeof( DWORD ) ); + continue; + } + + if ( !stricmp( "quit", szCommand ) ) + { + ZeroMemory( pvMemory, 4 * sizeof( DWORD ) ); + return 0; + } + + CmdSink::IResponse *pResponse = NULL; + if ( InterceptFxc::TryExecuteCommand( szCommand, &pResponse ) ) + { + byte *pBytes = ( byte * ) pvMemory; + + // Result + DWORD dwSucceededResult = pResponse->Succeeded() ? 1 : 0; + * ( DWORD * ) pBytes = dwSucceededResult; + pBytes += sizeof( DWORD ); + + // Result buffer len + DWORD dwBufferLength = pResponse->GetResultBufferLen(); + * ( DWORD * ) pBytes = dwBufferLength; + pBytes += sizeof( DWORD ); + + // Result buffer + const void *pvResultBuffer = pResponse->GetResultBuffer(); + memcpy( pBytes, pvResultBuffer, dwBufferLength ); + pBytes += dwBufferLength; + + // Listing - copy string + const char *szListing = pResponse->GetListing(); + if ( szListing ) + { + while ( 0 != ( * ( pBytes ++ ) = * ( szListing ++ ) ) ) + { + NULL; + } + } + else + { + * ( pBytes ++ ) = 0; + } + } + else + { + ZeroMemory( pvMemory, 4 * sizeof( DWORD ) ); + } + } + + return -2; +} diff --git a/utils/shadercompile/subprocess.h b/utils/shadercompile/subprocess.h new file mode 100644 index 0000000..703fccc --- /dev/null +++ b/utils/shadercompile/subprocess.h @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#ifndef SUBPROCESS_H +#define SUBPROCESS_H +#ifdef _WIN32 +#pragma once +#endif + +class SubProcessKernelObjects +{ + friend class SubProcessKernelObjects_Memory; + +public: + SubProcessKernelObjects( void ); + ~SubProcessKernelObjects( void ); + +private: + SubProcessKernelObjects( SubProcessKernelObjects const & ); + SubProcessKernelObjects & operator =( SubProcessKernelObjects const & ); + +protected: + BOOL Create( char const *szBaseName ); + BOOL Open( char const *szBaseName ); + +public: + BOOL IsValid( void ) const; + void Close( void ); + +protected: + HANDLE m_hMemorySection; + HANDLE m_hMutex; + HANDLE m_hEvent[2]; + DWORD m_dwCookie; +}; + +class SubProcessKernelObjects_Create : public SubProcessKernelObjects +{ +public: + SubProcessKernelObjects_Create( char const *szBaseName ) { Create( szBaseName ), m_dwCookie = 1; } +}; + +class SubProcessKernelObjects_Open : public SubProcessKernelObjects +{ +public: + SubProcessKernelObjects_Open( char const *szBaseName ) { Open( szBaseName ), m_dwCookie = 0; } +}; + +class SubProcessKernelObjects_Memory +{ +public: + SubProcessKernelObjects_Memory( SubProcessKernelObjects *p ) : m_pObjs( p ), m_pLockData( NULL ), m_pMemory( NULL ) { } + ~SubProcessKernelObjects_Memory() { Unlock(); } + +public: + void * Lock( void ); + BOOL Unlock( void ); + +public: + BOOL IsValid( void ) const { return m_pLockData != NULL; } + void * GetMemory( void ) const { return m_pMemory; } + +protected: + void *m_pMemory; + +private: + SubProcessKernelObjects *m_pObjs; + void *m_pLockData; +}; + + +// +// Response implementation +// +class CSubProcessResponse : public CmdSink::IResponse +{ +public: + explicit CSubProcessResponse( void const *pvMemory ); + ~CSubProcessResponse( void ) { } + +public: + virtual bool Succeeded( void ) { return ( 1 == m_dwResult ); } + virtual size_t GetResultBufferLen( void ) { return ( Succeeded() ? m_dwResultBufferLength : 0 ); } + virtual const void * GetResultBuffer( void ) { return ( Succeeded() ? m_pvResultBuffer : NULL ); } + virtual const char * GetListing( void ) { return (const char *) ( ( m_szListing && * m_szListing ) ? m_szListing : NULL ); } + +protected: + void const *m_pvMemory; + DWORD m_dwResult; + DWORD m_dwResultBufferLength; + void const *m_pvResultBuffer; + char const *m_szListing; +}; + + +int ShaderCompile_Subprocess_Main( char const *szSubProcessData ); + + +#endif // #ifndef SUBPROCESS_H diff --git a/utils/shadercompile/utlnodehash.h b/utils/shadercompile/utlnodehash.h new file mode 100644 index 0000000..0e59a0b --- /dev/null +++ b/utils/shadercompile/utlnodehash.h @@ -0,0 +1,93 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: hashed intrusive linked list. +// +// $NoKeywords: $ +// +// Serialization/unserialization buffer +//=============================================================================// + +#ifndef UTLNODEHASH_H +#define UTLNODEHASH_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "tier1/utlmemory.h" +#include "tier1/byteswap.h" +#include "tier1/utlintrusivelist.h" + +#include <stdarg.h> + +// to use this class, your list node class must have a Key() function defined which returns an +// integer type. May add this class to main utl tier when i'm happy w/ it. +template<class T, int HASHSIZE = 7907, class K = int > class CUtlNodeHash +{ + + int m_nNumNodes; + +public: + + CUtlIntrusiveDList<T> m_HashChains[HASHSIZE]; + + CUtlNodeHash( void ) + { + m_nNumNodes = 0; + } + + + T *FindByKey(K nMatchKey, int *pChainNumber = NULL) + { + unsigned int nChain=(unsigned int) nMatchKey ; + nChain %= HASHSIZE; + if ( pChainNumber ) + *( pChainNumber ) = nChain; + for( T * pNode = m_HashChains[ nChain ].m_pHead; pNode; pNode = pNode->m_pNext ) + if ( pNode->Key() == nMatchKey ) + return pNode; + return NULL; + } + + void Add( T * pNode ) + { + unsigned int nChain=(unsigned int) pNode->Key(); + nChain %= HASHSIZE; + m_HashChains[ nChain ].AddToHead( pNode ); + m_nNumNodes++; + } + + + void Purge( void ) + { + m_nNumNodes = 0; + // delete all nodes + for( int i=0; i < HASHSIZE; i++) + m_HashChains[i].Purge(); + } + + int Count( void ) const + { + return m_nNumNodes; + } + + void DeleteByKey( K nMatchKey ) + { + int nChain; + T *pSearch = FindByKey( nMatchKey, &nChain ); + if ( pSearch ) + { + m_HashChains[ nChain ].RemoveNode( pSearch ); + m_nNumNodes--; + } + } + + ~CUtlNodeHash( void ) + { + // delete all lists + Purge(); + } +}; + + +#endif |