summaryrefslogtreecommitdiff
path: root/utils/shadercompile
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/shadercompile
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'utils/shadercompile')
-rw-r--r--utils/shadercompile/cfgprocessor.cpp1399
-rw-r--r--utils/shadercompile/cfgprocessor.h76
-rw-r--r--utils/shadercompile/cmdsink.cpp114
-rw-r--r--utils/shadercompile/cmdsink.h117
-rw-r--r--utils/shadercompile/d3dxfxc.cpp259
-rw-r--r--utils/shadercompile/d3dxfxc.h24
-rw-r--r--utils/shadercompile/shadercompile.cpp2699
-rw-r--r--utils/shadercompile/shadercompile.h11
-rw-r--r--utils/shadercompile/shadercompile_dll.vpc60
-rw-r--r--utils/shadercompile/subprocess.cpp303
-rw-r--r--utils/shadercompile/subprocess.h104
-rw-r--r--utils/shadercompile/utlnodehash.h93
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