summaryrefslogtreecommitdiff
path: root/materialsystem/ctexturecompositor.cpp
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 /materialsystem/ctexturecompositor.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'materialsystem/ctexturecompositor.cpp')
-rw-r--r--materialsystem/ctexturecompositor.cpp2842
1 files changed, 2842 insertions, 0 deletions
diff --git a/materialsystem/ctexturecompositor.cpp b/materialsystem/ctexturecompositor.cpp
new file mode 100644
index 0000000..88683b1
--- /dev/null
+++ b/materialsystem/ctexturecompositor.cpp
@@ -0,0 +1,2842 @@
+//========= Copyright Valve Corporation, All rights reserved. ================================== //
+//
+// Purpose:
+//
+//============================================================================================== //
+
+#include "pch_materialsystem.h"
+#include "ctexturecompositor.h"
+
+#include "materialsystem/itexture.h"
+#include "materialsystem/imaterialsystem.h"
+#include "materialsystem/combineoperations.h"
+#include "texturemanager.h"
+
+#define MATSYS_INTERNAL // Naughty!
+#include "cmaterialsystem.h"
+
+#include "tier0/memdbgon.h"
+
+#ifndef _WINDOWS
+#define sscanf_s sscanf
+#endif
+
+// If this is 0 or unset, we won't use the caching functionality.
+#define WITH_TEX_COMPOSITE_CACHE 1
+
+#ifdef STAGING_ONLY // Always should remain staging only.
+ ConVar r_texcomp_dump( "r_texcomp_dump", "0", FCVAR_NONE, "Whether we should dump the textures to disk or not. 1: Save all; 2: Save Final; 3: Save Final with name suitable for scripting; 4: Save Final and skip saving workshop icons." );
+#endif
+
+const int cMaxSelectors = 16;
+
+// Ugh, this is annoying and matches TF's enums. That's lame. We should workaround this.
+enum { Neutral = 0, Red = 2, Blue = 3 };
+
+static int s_nDumpCount = 0;
+static CInterlockedInt s_nCompositeCount = 0;
+
+void ComputeTextureMatrixFromRectangle( VMatrix* pOutMat, const Vector2D& bl, const Vector2D& tl, const Vector2D& tr );
+bool HasCycle( CTextureCompositorTemplate* pStartTempl );
+CTextureCompositorTemplate* Advance( CTextureCompositorTemplate* pTmpl, int nSteps );
+void PrintMinimumCycle( CTextureCompositorTemplate* pStartTempl );
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+struct CTCStageResult_t
+{
+ ITexture* m_pTexture;
+ ITexture* m_pRenderTarget;
+
+ float m_fAdjustBlackPoint;
+ float m_fAdjustWhitePoint;
+ float m_fAdjustGamma;
+
+ matrix3x4_t m_mUvAdjust;
+
+ inline CTCStageResult_t()
+ : m_pTexture(NULL)
+ , m_pRenderTarget(NULL)
+ , m_fAdjustBlackPoint(0.0f)
+ , m_fAdjustWhitePoint(1.0f)
+ , m_fAdjustGamma(1.0f)
+ {
+ SetIdentityMatrix( m_mUvAdjust );
+ }
+
+ inline void Cleanup( CTextureCompositor* _comp )
+ {
+ if ( m_pRenderTarget )
+ _comp->ReleaseCompositorRenderTarget( m_pRenderTarget );
+
+ m_pTexture = NULL;
+ m_pRenderTarget = NULL;
+ }
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class CTCStage : public IAsyncTextureOperationReceiver
+{
+public:
+ CTCStage();
+
+protected:
+ // Called by Release()
+ virtual ~CTCStage();
+
+public:
+ // IAsyncTextureOperationReceiver
+ virtual int AddRef() OVERRIDE;
+ virtual int Release() OVERRIDE;
+ virtual int GetRefCount() const OVERRIDE { return m_nReferenceCount; }
+ virtual void OnAsyncCreateComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { }
+ virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs ) OVERRIDE { }
+ virtual void OnAsyncMapComplete( ITexture* pTex, void* pExtraArgs, void* pMemory, int pPitch ) OVERRIDE { }
+ virtual void OnAsyncReadbackBegin( ITexture* pDst, ITexture* pSrc, void* pExtraArgs ) OVERRIDE { }
+
+
+ // Our stuff.
+ void Resolve( bool bFirstTime, CTextureCompositor* _comp );
+ inline ECompositeResolveStatus GetResolveStatus() const { return m_ResolveStatus; }
+ inline const CTCStageResult_t& GetResult() const { Assert( GetResolveStatus() == ECRS_Complete ); return m_Result; }
+
+ bool HasTeamSpecifics() const;
+ void ComputeRandomValues( int* pCurIndex, CUniformRandomStream* pRNGs, int nRNGCount );
+
+ inline void SetFirstChild( CTCStage* _stage ) { m_pFirstChild = _stage; }
+ inline void SetNextSibling( CTCStage* _stage ) { m_pNextSibling = _stage; }
+
+ inline CTCStage* GetFirstChild() { return m_pFirstChild; }
+ inline CTCStage* GetNextSibling() { return m_pNextSibling; }
+
+ inline const CTCStage* GetFirstChild() const { return m_pFirstChild; }
+ inline const CTCStage* GetNextSibling() const { return m_pNextSibling; }
+
+ void AppendChildren( const CUtlVector< CTCStage* >& _children )
+ {
+ // Do these in reverse order, they will wind up in the right order
+ FOR_EACH_VEC_BACK( _children, i )
+ {
+ CTCStage* childStage = _children[i];
+ childStage->SetNextSibling( GetFirstChild() );
+ SetFirstChild( childStage );
+ }
+ }
+
+ void CleanupChildResults( CTextureCompositor* _comp );
+
+ // Render a quad with _mat using _inputs to _destRT
+ void Render( ITexture* _destRT, IMaterial* _mat, const CUtlVector<CTCStageResult_t>& _inputs, CTextureCompositor* _comp, bool bClear );
+
+ void Cleanup( CTextureCompositor* _comp );
+
+ // Does this stage target a render target or a texture?
+ virtual bool DoesTargetRenderTarget() const = 0;
+
+ inline void SetResult( const CTCStageResult_t& _result )
+ {
+ Assert( m_ResolveStatus != ECRS_Complete );
+ m_Result = _result;
+ m_ResolveStatus = ECRS_Complete;
+ }
+
+protected:
+
+ inline void SetResolveStatus( ECompositeResolveStatus _status )
+ {
+ m_ResolveStatus = _status;
+ }
+
+ // This function is called only once during the first ResolveTraversal, and is
+ // for the compositor to request its textures. Textures should not be requested
+ // before this or they can be held waaaay too long.
+ virtual void RequestTextures() = 0;
+
+ // This function will be called during Resolve traversal. At the point when this is called,
+ // all of this node's children will have had their resolve completed. Our siblings will
+ // not have resolved yet.
+ virtual void ResolveThis( CTextureCompositor* _comp ) = 0;
+
+ // This function is called during HasTeamSpecifics traversal.
+ virtual bool HasTeamSpecificsThis() const = 0;
+
+ virtual bool ComputeRandomValuesThis( CUniformRandomStream* pRNG ) = 0;
+
+private:
+ CInterlockedInt m_nReferenceCount;
+
+ CTCStage* m_pFirstChild;
+ CTCStage* m_pNextSibling;
+
+ CTCStageResult_t m_Result;
+ ECompositeResolveStatus m_ResolveStatus;
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+typedef void ( *ParseSingleKV )( KeyValues* _kv, void* _dest );
+struct ParseTableEntry
+{
+ const char* keyName;
+ ParseSingleKV parseFunc;
+ size_t structOffset;
+};
+
+// ------------------------------------------------------------------------------------------------
+struct Range
+{
+ float low;
+ float high;
+
+ Range( )
+ : low( 0 )
+ , high( 0 )
+ { }
+
+ Range( float _l, float _h )
+ : low( _l )
+ , high( _h )
+ { }
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+void ParseBoolFromKV( KeyValues* _kv, void* _pDest )
+{
+ bool* realDest = ( bool* ) _pDest;
+ ( *realDest ) = _kv->GetBool();
+}
+
+// ------------------------------------------------------------------------------------------------
+template<int N>
+void ParseIntVectorFromKV( KeyValues* _kv, void* _pDest )
+{
+ CCopyableUtlVector<int>* realDest = ( CCopyableUtlVector<int>* ) _pDest;
+ const int parsedValue = _kv->GetInt();
+ if ( realDest->Size() < N )
+ {
+ realDest->AddToTail( parsedValue );
+ }
+ else
+ {
+ DevWarning( "Too many numbers (>%d), ignoring the value '%d'.\n", N, parsedValue );
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+template< class T >
+CUtlString AsStringT( const T& _val )
+{
+#ifdef _WIN32
+ // Not sure why linux is unhappy here. Error messages unhelpful. Thanks, GCC.
+ static_assert( false, "Must add specialization for typename T" );
+#endif
+ return CUtlString( "" );
+}
+
+// ------------------------------------------------------------------------------------------------
+template<>
+CUtlString AsStringT< int >( const int& _val )
+{
+ char buffer[ 12 ];
+ V_sprintf_safe( buffer, "%d", _val );
+ return CUtlString( buffer );
+}
+
+// ------------------------------------------------------------------------------------------------
+template< class T >
+void ParseTFromKV( KeyValues* _kv, void* _pDest )
+{
+#ifdef _WIN32
+ // Not sure why linux is unhappy here. Error messages unhelpful. Thanks, GCC.
+ static_assert( false, "Must add specialization for typename T" );
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+template<>
+void ParseTFromKV< int >( KeyValues* _kv, void* _pDest )
+{
+ int* realDest = ( int* ) _pDest;
+ ( *realDest ) = _kv->GetInt();
+}
+
+// ------------------------------------------------------------------------------------------------
+template<>
+void ParseTFromKV< Vector2D >( KeyValues* _kv, void* _pDest )
+{
+ Vector2D* realDest = ( Vector2D* ) _pDest;
+ Vector2D tmpDest;
+ int count = sscanf_s( _kv->GetString(), "%f %f", &tmpDest.x, &tmpDest.y );
+ if ( count != 2 )
+ {
+ Error( "Expected exactly two values, %d were provided.\n", count );
+ return;
+ }
+
+ *realDest = tmpDest;
+}
+
+// ------------------------------------------------------------------------------------------------
+template< class T, int N = INT_MAX >
+void ParseVectorFromKV( KeyValues* _kv, void* _pDest )
+{
+ CCopyableUtlVector< T >* realDest = ( CCopyableUtlVector< T >* ) _pDest;
+
+ T parsedValue = T();
+ ParseTFromKV<T>( _kv, &parsedValue );
+
+ if ( realDest->Size() < N )
+ {
+ realDest->AddToTail( parsedValue );
+ }
+ else
+ {
+ DevWarning( "Too many entries (>%d), ignoring the value '%s'.\n", N, AsStringT( parsedValue ).Get() );
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ParseRangeFromKV( KeyValues* _kv, void* _pDest )
+{
+ Range* realDest = ( Range* ) _pDest;
+ Range tmpDest;
+
+ int count = sscanf_s( _kv->GetString(), "%f %f", &tmpDest.low, &tmpDest.high );
+ switch (count)
+ {
+ case 1:
+ // If we parse one, use the same value for low and high.
+ ( *realDest ).low = tmpDest.low;
+ ( *realDest ).high = tmpDest.low;
+ break;
+ case 2:
+ // If we parse two, they're both correct.
+ ( *realDest ).low = tmpDest.low;
+ ( *realDest ).high = tmpDest.high;
+ break;
+
+ // error cases
+ case EOF:
+ case 0:
+ default:
+ Error( "Incorrect number of numbers while parsing, using defaults. This error message should be improved\n" );
+ };
+}
+
+// ------------------------------------------------------------------------------------------------
+void ParseInverseRangeFromKV( KeyValues* _kv, void* _pDest )
+{
+ const float kSubstValue = 0.00001;
+ ParseRangeFromKV( _kv, _pDest );
+ Range* realDest = ( Range* ) _pDest;
+
+ if ( realDest->low != 0.0f )
+ {
+ ( *realDest ).low = 1.0f / realDest->low;
+ }
+ else
+ {
+ Error( "Specified 0.0 for low value, that is illegal in this field. Substituting %.5f\n", kSubstValue );
+ ( *realDest ).low = kSubstValue;
+ }
+
+ if ( realDest->high != 0.0f )
+ {
+ ( *realDest ).high = 1.0f / realDest->high;
+ }
+ else
+ {
+ Error( "Specified 0.0 for high value, that is illegal in this field. Substituting %.5f\n", kSubstValue );
+ ( *realDest ).high = kSubstValue;
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+template < int Div >
+void ParseRangeThenDivideBy( KeyValues *_kv, void* _pDest )
+{
+ static_assert( Div != 0, "Cannot specify a divisor of 0." );
+ float fDiv = (float) Div;
+
+ ParseRangeFromKV( _kv, _pDest );
+ Range* realDest = ( Range* ) _pDest;
+
+ ( *realDest ).low = ( *realDest ).low / fDiv;
+ ( *realDest ).high = ( *realDest ).high / fDiv;
+}
+
+// ------------------------------------------------------------------------------------------------
+void ParseStringFromKV( KeyValues* _kv, void* _pDest )
+{
+ CUtlString* realDest = ( CUtlString* ) _pDest;
+ (*realDest) = _kv->GetString();
+}
+
+// ------------------------------------------------------------------------------------------------
+struct TextureStageParameters
+{
+ CUtlString m_pTexFilename;
+ CUtlString m_pTexRedFilename;
+ CUtlString m_pTexBlueFilename;
+ Range m_AdjustBlack;
+ Range m_AdjustOffset;
+ Range m_AdjustGamma;
+
+ Range m_Rotation;
+ Range m_TranslateU;
+ Range m_TranslateV;
+ Range m_ScaleUV;
+ bool m_AllowFlipU;
+ bool m_AllowFlipV;
+ bool m_Evaluate;
+
+ TextureStageParameters()
+ : m_AdjustBlack( 0, 0 )
+ , m_AdjustOffset( 1, 1 )
+ , m_AdjustGamma( 1, 1 )
+ , m_Rotation( 0 , 0 )
+ , m_TranslateU( 0, 0 )
+ , m_TranslateV( 0, 0 )
+ , m_ScaleUV( 1, 1 )
+ , m_AllowFlipU( false )
+ , m_AllowFlipV( false )
+ , m_Evaluate( true )
+ { }
+};
+
+// ------------------------------------------------------------------------------------------------
+const ParseTableEntry cTextureStageParametersParseTable[] =
+{
+ { "texture", ParseStringFromKV, offsetof( TextureStageParameters, m_pTexFilename ) },
+ { "texture_red", ParseStringFromKV, offsetof( TextureStageParameters, m_pTexRedFilename ) },
+ { "texture_blue", ParseStringFromKV, offsetof( TextureStageParameters, m_pTexBlueFilename ) },
+ { "adjust_black", ParseRangeThenDivideBy<255>, offsetof( TextureStageParameters, m_AdjustBlack ) },
+ { "adjust_offset", ParseRangeThenDivideBy<255>, offsetof( TextureStageParameters, m_AdjustOffset ) },
+ { "adjust_gamma", ParseInverseRangeFromKV, offsetof( TextureStageParameters, m_AdjustGamma ) },
+ { "rotation", ParseRangeFromKV, offsetof( TextureStageParameters, m_Rotation ) },
+ { "translate_u", ParseRangeFromKV, offsetof( TextureStageParameters, m_TranslateU ) },
+ { "translate_v", ParseRangeFromKV, offsetof( TextureStageParameters, m_TranslateV ) },
+ { "scale_uv", ParseRangeFromKV, offsetof( TextureStageParameters, m_ScaleUV ) },
+ { "flip_u", ParseBoolFromKV, offsetof( TextureStageParameters, m_AllowFlipU ) },
+ { "flip_v", ParseBoolFromKV, offsetof( TextureStageParameters, m_AllowFlipV ) },
+ { "evaluate?", ParseBoolFromKV, offsetof( TextureStageParameters, m_Evaluate ) },
+
+ { 0, 0 }
+};
+
+ // ------------------------------------------------------------------------------------------------
+ // ------------------------------------------------------------------------------------------------
+ // ------------------------------------------------------------------------------------------------
+class CTCTextureStage : public CTCStage
+{
+public:
+ CTCTextureStage( const TextureStageParameters& _tsp, uint32 nTexCompositeCreateFlags )
+ : m_Parameters( _tsp )
+ , m_pTex( NULL )
+ , m_pTexRed( NULL )
+ , m_pTexBlue( NULL )
+ {
+ }
+
+ virtual ~CTCTextureStage()
+ {
+ SafeRelease( &m_pTex );
+ SafeRelease( &m_pTexBlue );
+ SafeRelease( &m_pTexRed );
+ }
+
+ virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs )
+ {
+ switch ( ( int ) pExtraArgs )
+ {
+ case Neutral:
+ SafeAssign( &m_pTex, pTex );
+ break;
+ case Red:
+ SafeAssign( &m_pTexRed, pTex );
+ break;
+ case Blue:
+ SafeAssign( &m_pTexBlue, pTex );
+ break;
+ default:
+ Assert( !"Unexpected value passed to OnAsyncFindComplete" );
+ break;
+ };
+ }
+
+ virtual bool DoesTargetRenderTarget() const { return false; }
+
+protected:
+ bool AreTexturesLoaded() const
+ {
+ if ( !m_Parameters.m_pTexFilename.IsEmpty() && !m_pTex )
+ return false;
+
+ if ( !m_Parameters.m_pTexRedFilename.IsEmpty() && !m_pTexRed )
+ return false;
+
+ if ( !m_Parameters.m_pTexBlueFilename.IsEmpty() && !m_pTexBlue )
+ return false;
+
+ return true;
+ }
+
+ ITexture* GetTeamSpecificTexture( int nTeam )
+ {
+ if ( nTeam == Red && m_pTexRed )
+ return m_pTexRed;
+
+ if ( nTeam == Blue && m_pTexBlue )
+ return m_pTexBlue;
+
+ return m_pTex;
+ }
+
+ virtual void RequestTextures()
+ {
+ if ( !m_Parameters.m_pTexFilename.IsEmpty() )
+ materials->AsyncFindTexture( m_Parameters.m_pTexFilename.Get(), TEXTURE_GROUP_RUNTIME_COMPOSITE, this, ( void* ) Neutral, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+ if ( !m_Parameters.m_pTexRedFilename.IsEmpty() )
+ materials->AsyncFindTexture( m_Parameters.m_pTexRedFilename.Get(), TEXTURE_GROUP_RUNTIME_COMPOSITE, this, ( void* ) Red, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+ if ( !m_Parameters.m_pTexBlueFilename.IsEmpty() )
+ materials->AsyncFindTexture( m_Parameters.m_pTexBlueFilename.Get(), TEXTURE_GROUP_RUNTIME_COMPOSITE, this, ( void* ) Blue, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+ }
+
+ virtual void ResolveThis( CTextureCompositor* _comp )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // We shouldn't have any children, we're going to ignore them anyways.
+ Assert( GetFirstChild() == NULL );
+
+ ECompositeResolveStatus resolveStatus = GetResolveStatus();
+ // If we're done, we're done.
+ if ( resolveStatus == ECRS_Complete || resolveStatus == ECRS_Error )
+ return;
+
+ if ( resolveStatus == ECRS_Scheduled )
+ SetResolveStatus( ECRS_PendingTextureLoads );
+
+ // Someone is misusing this node if this assert fires.
+ Assert( GetResolveStatus() == ECRS_PendingTextureLoads );
+
+ // When the texture has finished loading, this will be set to the texture we should use.
+ if ( !AreTexturesLoaded() )
+ return;
+
+ if ( !m_pTex && !m_pTexRed && !m_pTexBlue )
+ {
+ _comp->Error( false, "Invalid texture_lookup node, must specify at least texture (or texture_red and texture_blue) or all of them.\n" );
+ return;
+ }
+
+ if ( m_pTex && m_pTex->IsError() )
+ {
+ _comp->Error( false, "Failed to load texture '%s', this is non-recoverable.\n", m_Parameters.m_pTexFilename.Get() );
+ return;
+ }
+
+ if ( m_pTexRed && m_pTexRed->IsError() )
+ {
+ _comp->Error( false, "Failed to load texture_red '%s', this is non-recoverable.\n", m_Parameters.m_pTexRedFilename.Get() );
+ return;
+ }
+
+ if ( m_pTexBlue && m_pTexBlue->IsError() )
+ {
+ _comp->Error( false, "Failed to load texture_blue '%s', this is non-recoverable.\n", m_Parameters.m_pTexBlueFilename.Get() );
+ return;
+ }
+
+ CTCStageResult_t res;
+ res.m_pTexture = GetTeamSpecificTexture( _comp->GetTeamNumber() );
+ res.m_fAdjustBlackPoint = m_fAdjustBlack;
+ res.m_fAdjustWhitePoint = m_fAdjustWhite;
+ res.m_fAdjustGamma = m_fAdjustGamma;
+ // Store the matrix into the uv adjustment matrix
+ m_mTextureAdjust.Set3x4( res.m_mUvAdjust );
+
+ SetResult( res );
+
+ CleanupChildResults( _comp );
+ tmMessage( TELEMETRY_LEVEL0, TMMF_ICON_NOTE, "Completed: %s", __FUNCTION__ );
+ }
+
+ virtual bool HasTeamSpecificsThis() const OVERRIDE
+ {
+ return !m_Parameters.m_pTexBlueFilename.IsEmpty();
+ }
+
+ virtual bool ComputeRandomValuesThis( CUniformRandomStream* pRNG ) OVERRIDE
+ {
+ // If you change the order of these random numbers being generated, or add new ones, you will
+ // change the look of existing players' weapons! Don't do that.
+ const bool shouldFlipU = m_Parameters.m_AllowFlipU ? pRNG->RandomInt( 0, 1 ) != 0 : false;
+ const bool shouldFlipV = m_Parameters.m_AllowFlipV ? pRNG->RandomInt( 0, 1 ) != 0 : false;
+ const float translateU = pRNG->RandomFloat( m_Parameters.m_TranslateU.low, m_Parameters.m_TranslateU.high );
+ const float translateV = pRNG->RandomFloat( m_Parameters.m_TranslateV.low, m_Parameters.m_TranslateV.high );
+ const float rotation = pRNG->RandomFloat( m_Parameters.m_Rotation.low, m_Parameters.m_Rotation.high );
+ const float scaleUV = pRNG->RandomFloat( m_Parameters.m_ScaleUV.low, m_Parameters.m_ScaleUV.high );
+
+ const float adjustBlack = pRNG->RandomFloat( m_Parameters.m_AdjustBlack.low, m_Parameters.m_AdjustBlack.high );
+ const float adjustOffset = pRNG->RandomFloat( m_Parameters.m_AdjustOffset.low, m_Parameters.m_AdjustOffset.high );
+ const float adjustGamma = pRNG->RandomFloat( m_Parameters.m_AdjustGamma.low, m_Parameters.m_AdjustGamma.high );
+ const float adjustWhite = adjustBlack + adjustOffset;
+
+ m_fAdjustBlack = adjustBlack;
+ m_fAdjustWhite = adjustWhite;
+ m_fAdjustGamma = adjustGamma;
+
+ const float finalScaleU = scaleUV * ( shouldFlipU ? -1.0f : 1.0f );
+ const float finalScaleV = scaleUV * ( shouldFlipV ? -1.0f : 1.0f );
+
+ MatrixBuildRotateZ( m_mTextureAdjust, rotation );
+ m_mTextureAdjust = m_mTextureAdjust.Scale( Vector( finalScaleU, finalScaleV, 1.0f ) );
+ MatrixTranslate( m_mTextureAdjust, Vector( translateU, translateV, 0 ) );
+ // Copy W into Z because we're doing a texture matrix.
+ m_mTextureAdjust[ 0 ][ 2 ] = m_mTextureAdjust[ 0 ][ 3 ];
+ m_mTextureAdjust[ 1 ][ 2 ] = m_mTextureAdjust[ 1 ][ 3 ];
+ m_mTextureAdjust[ 2 ][ 2 ] = 1.0f;
+
+ return true;
+ }
+
+
+private:
+ TextureStageParameters m_Parameters;
+ ITexture* m_pTex;
+ ITexture* m_pTexRed;
+ ITexture* m_pTexBlue;
+
+ // Random values here
+ float m_fAdjustBlack;
+ float m_fAdjustWhite;
+ float m_fAdjustGamma;
+ VMatrix m_mTextureAdjust;
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+
+// Keep in sync with CombineOperation
+const char* cCombineMaterialName[] =
+{
+ "dev/CompositorMultiply",
+ "dev/CompositorAdd",
+ "dev/CompositorLerp",
+
+ "dev/CompositorSelect",
+
+ "\0 ECO_Legacy_Lerp_FirstPass", // Procedural; starting with \0 will skip precaching
+ "\0 ECO_Legacy_Lerp_SecondPass", // Procedural; starting with \0 will skip precaching
+
+ "dev/CompositorBlend",
+
+ "\0 ECO_LastPrecacheMaterial", //
+
+ "CompositorError",
+
+ NULL
+};
+
+static_assert( ARRAYSIZE( cCombineMaterialName ) == ECO_COUNT + 1, "cCombineMaterialName and ECombineOperation are out of sync." );
+
+// ------------------------------------------------------------------------------------------------
+struct CombineStageParameters
+{
+ ECombineOperation m_CombineOp;
+ Range m_AdjustBlack;
+ Range m_AdjustOffset;
+ Range m_AdjustGamma;
+
+ Range m_Rotation;
+ Range m_TranslateU;
+ Range m_TranslateV;
+ Range m_ScaleUV;
+
+ bool m_AllowFlipU;
+ bool m_AllowFlipV;
+ bool m_Evaluate;
+
+ CombineStageParameters()
+ : m_CombineOp( ECO_Error )
+ , m_AdjustBlack( 0, 0 )
+ , m_AdjustOffset( 1, 1 )
+ , m_AdjustGamma( 1, 1 )
+ , m_Rotation( 0 , 0 )
+ , m_TranslateU( 0, 0 )
+ , m_TranslateV( 0, 0 )
+ , m_ScaleUV( 1, 1 )
+ , m_AllowFlipU( false )
+ , m_AllowFlipV( false )
+ , m_Evaluate( true )
+ { }
+
+};
+
+// ------------------------------------------------------------------------------------------------
+void ParseOperationFromKV( KeyValues* _kv, void* _pDest )
+{
+ ECombineOperation* realDest = ( ECombineOperation* ) _pDest;
+ const char* opStr = _kv->GetString();
+
+ if ( V_stricmp( "multiply", opStr ) == 0 )
+ (*realDest) = ECO_Multiply;
+ else if ( V_stricmp( "add", opStr ) == 0 )
+ (*realDest) = ECO_Add;
+ else if ( V_stricmp( "lerp", opStr) == 0 )
+ (*realDest) = ECO_Lerp;
+ else
+ (*realDest) = ECO_Error;
+}
+
+// ------------------------------------------------------------------------------------------------
+const ParseTableEntry cCombineStageParametersParseTable[] =
+{
+ { "adjust_black", ParseRangeThenDivideBy<255>, offsetof( CombineStageParameters, m_AdjustBlack ) },
+ { "adjust_offset", ParseRangeThenDivideBy<255>, offsetof( CombineStageParameters, m_AdjustOffset ) },
+ { "adjust_gamma", ParseInverseRangeFromKV, offsetof( CombineStageParameters, m_AdjustGamma ) },
+ { "rotation", ParseRangeFromKV, offsetof( CombineStageParameters, m_Rotation ) },
+ { "translate_u", ParseRangeFromKV, offsetof( CombineStageParameters, m_TranslateU ) },
+ { "translate_v", ParseRangeFromKV, offsetof( CombineStageParameters, m_TranslateV ) },
+ { "scale_uv", ParseRangeFromKV, offsetof( CombineStageParameters, m_ScaleUV ) },
+ { "flip_u", ParseBoolFromKV, offsetof( CombineStageParameters, m_AllowFlipU ) },
+ { "flip_v", ParseBoolFromKV, offsetof( CombineStageParameters, m_AllowFlipV ) },
+ { "evaluate?", ParseBoolFromKV, offsetof( CombineStageParameters, m_Evaluate ) },
+
+ { 0, 0 }
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class CTCCombineStage : public CTCStage
+{
+public:
+ CTCCombineStage( const CombineStageParameters& _csp, uint32 nTexCompositeCreateFlags )
+ : m_Parameters( _csp )
+ , m_pMaterial( NULL )
+ {
+ Assert( m_Parameters.m_CombineOp >= 0 && m_Parameters.m_CombineOp < ECO_COUNT );
+
+ SafeAssign( &m_pMaterial, materials->FindMaterial( cCombineMaterialName[ m_Parameters.m_CombineOp ], TEXTURE_GROUP_RUNTIME_COMPOSITE ) );
+ }
+
+ virtual ~CTCCombineStage()
+ {
+ SafeRelease( &m_pMaterial );
+ }
+
+ virtual bool DoesTargetRenderTarget() const { return true; }
+
+
+protected:
+ virtual void RequestTextures() { /* No textures here */ }
+
+ virtual void ResolveThis( CTextureCompositor* _comp )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ ECompositeResolveStatus resolveStatus = GetResolveStatus();
+ // If we're done, we're done.
+ if ( resolveStatus == ECRS_Complete || resolveStatus == ECRS_Error )
+ return;
+
+ if ( resolveStatus == ECRS_Scheduled )
+ SetResolveStatus( ECRS_PendingTextureLoads );
+
+ // Someone is misusing this node if this assert fires.
+ Assert( GetResolveStatus() == ECRS_PendingTextureLoads );
+
+ for ( CTCStage* child = GetFirstChild(); child; child = child->GetNextSibling() )
+ {
+ // If any child isn't ready to go, we're not ready to go.
+ if ( child->GetResolveStatus() != ECRS_Complete )
+ return;
+ }
+
+ ITexture* pRenderTarget = _comp->AllocateCompositorRenderTarget();
+
+ CUtlVector<CTCStageResult_t> results;
+ uint childCount = 0;
+ for ( CTCStage* child = GetFirstChild(); child; child = child->GetNextSibling() )
+ {
+ results.AddToTail( child->GetResult() );
+ ++childCount;
+ }
+
+ // TODO: If there are more than 8 children, need to split them into multiple groups here. Skip it for now.
+
+ Render( pRenderTarget, m_pMaterial, results, _comp, true );
+
+ CTCStageResult_t res;
+ res.m_pRenderTarget = pRenderTarget;
+ res.m_fAdjustBlackPoint = m_fAdjustBlack;
+ res.m_fAdjustWhitePoint = m_fAdjustWhite;
+ res.m_fAdjustGamma = m_fAdjustGamma;
+
+ SetResult( res );
+
+ // As soon as we have scheduled the read of a child render target, we can release that
+ // texture back to the pool for use by another stage. Everything is pipelined, so this just
+ // works.
+ CleanupChildResults( _comp );
+ tmMessage( TELEMETRY_LEVEL0, TMMF_ICON_NOTE, "Completed: %s", __FUNCTION__ );
+ }
+
+ virtual bool HasTeamSpecificsThis() const OVERRIDE{ return false; }
+
+ virtual bool ComputeRandomValuesThis( CUniformRandomStream* pRNG ) OVERRIDE
+ {
+ const float adjustBlack = pRNG->RandomFloat( m_Parameters.m_AdjustBlack.low, m_Parameters.m_AdjustBlack.high );
+ const float adjustOffset = pRNG->RandomFloat( m_Parameters.m_AdjustOffset.low, m_Parameters.m_AdjustOffset.high );
+ const float adjustGamma = pRNG->RandomFloat( m_Parameters.m_AdjustGamma.low, m_Parameters.m_AdjustGamma.high );
+ const float adjustWhite = adjustBlack + adjustOffset;
+
+ m_fAdjustBlack = adjustBlack;
+ m_fAdjustWhite = adjustWhite;
+ m_fAdjustGamma = adjustGamma;
+
+ return true;
+ }
+
+private:
+ CombineStageParameters m_Parameters;
+ IMaterial* m_pMaterial;
+
+ float m_fAdjustBlack;
+ float m_fAdjustWhite;
+ float m_fAdjustGamma;
+};
+
+// ------------------------------------------------------------------------------------------------
+struct SelectStageParameters
+{
+ CUtlString m_pTexFilename;
+ CCopyableUtlVector<int> m_Select;
+ bool m_Evaluate;
+
+ SelectStageParameters()
+ : m_Evaluate( true )
+ {
+ }
+};
+
+// ------------------------------------------------------------------------------------------------
+const ParseTableEntry cSelectStageParametersParseTable[] =
+{
+ { "groups", ParseStringFromKV, offsetof( SelectStageParameters, m_pTexFilename ) },
+ { "select", ParseVectorFromKV< int, cMaxSelectors >, offsetof( SelectStageParameters, m_Select ) },
+ { "evaluate?", ParseBoolFromKV, offsetof( SelectStageParameters, m_Evaluate ) },
+
+ { 0, 0 }
+};
+
+ // ------------------------------------------------------------------------------------------------
+ // ------------------------------------------------------------------------------------------------
+ // ------------------------------------------------------------------------------------------------
+class CTCSelectStage : public CTCStage
+{
+public:
+ CTCSelectStage( const SelectStageParameters& _ssp, uint32 nTexCompositeCreateFlags )
+ : m_Parameters( _ssp )
+ , m_pMaterial( NULL )
+ , m_pTex( NULL )
+ {
+ SafeAssign( &m_pMaterial, materials->FindMaterial( cCombineMaterialName[ ECO_Select ], TEXTURE_GROUP_RUNTIME_COMPOSITE ) );
+ }
+ virtual ~CTCSelectStage()
+ {
+ SafeRelease( &m_pMaterial );
+ SafeRelease( &m_pTex );
+ }
+
+ virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs ) { SafeAssign( &m_pTex, pTex ); }
+
+ virtual bool DoesTargetRenderTarget() const { return true; }
+
+
+protected:
+ virtual void RequestTextures()
+ {
+ materials->AsyncFindTexture( m_Parameters.m_pTexFilename.Get(), TEXTURE_GROUP_RUNTIME_COMPOSITE, this, NULL, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+ }
+
+ virtual void ResolveThis( CTextureCompositor* _comp )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // We shouldn't have any children, we're going to ignore them anyways.
+ Assert( GetFirstChild() == NULL );
+
+ ECompositeResolveStatus resolveStatus = GetResolveStatus();
+ // If we're done, we're done.
+ if ( resolveStatus == ECRS_Complete || resolveStatus == ECRS_Error )
+ return;
+
+ if ( resolveStatus == ECRS_Scheduled )
+ SetResolveStatus( ECRS_PendingTextureLoads );
+
+ // Someone is misusing this node if this assert fires.
+ Assert( GetResolveStatus() == ECRS_PendingTextureLoads );
+
+ // When the texture has finished loading, this will be set to the texture we should use.
+ if ( m_pTex == NULL )
+ return;
+
+ if ( m_pTex->IsError() )
+ {
+ _comp->Error( false, "Failed to load texture %s, this is non-recoverable.\n", m_Parameters.m_pTexFilename.Get() );
+ return;
+ }
+
+ ITexture* pRenderTarget = _comp->AllocateCompositorRenderTarget();
+
+ char buffer[128];
+ for ( int i = 0; i < cMaxSelectors; ++i )
+ {
+ bool bFound = false;
+
+ V_snprintf( buffer, ARRAYSIZE( buffer ), "$selector%d", i );
+ IMaterialVar* pVar = m_pMaterial->FindVar( buffer, &bFound );
+ Assert(bFound);
+ if ( i < m_Parameters.m_Select.Size() )
+ pVar->SetIntValue( m_Parameters.m_Select[i] );
+ else
+ pVar->SetIntValue( 0 );
+ }
+
+ CTCStageResult_t inRes;
+ inRes.m_pTexture = m_pTex;
+ CUtlVector<CTCStageResult_t> fakeResults;
+ fakeResults.AddToTail( inRes );
+ Render( pRenderTarget, m_pMaterial, fakeResults, _comp, true );
+
+ CTCStageResult_t outRes;
+ outRes.m_pRenderTarget = pRenderTarget;
+ SetResult( outRes );
+
+ CleanupChildResults( _comp );
+ tmMessage( TELEMETRY_LEVEL0, TMMF_ICON_NOTE, "Completed: %s", __FUNCTION__ );
+ }
+
+ virtual bool HasTeamSpecificsThis() const OVERRIDE { return false; }
+
+ virtual bool ComputeRandomValuesThis( CUniformRandomStream* pRNG ) OVERRIDE
+ {
+ // No RNG here.
+ return false;
+ }
+
+private:
+ SelectStageParameters m_Parameters;
+ IMaterial* m_pMaterial;
+ ITexture* m_pTex;
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+struct Sticker_t
+{
+ float m_fWeight; // Random likelihood this one is to be selected
+ CUtlString m_baseFilename; // Name of the base file for the sticker (the albedo).
+ CUtlString m_specFilename; // Name of the specular file for the sticker, or if blank we will assume it is baseFilename + _spec + baseExtension
+
+ Sticker_t()
+ : m_fWeight( 1.0 )
+ { }
+};
+
+// ------------------------------------------------------------------------------------------------
+template<>
+void ParseTFromKV< Sticker_t >( KeyValues* _kv, void* _pDest )
+{
+ Sticker_t* realDest = ( Sticker_t* ) _pDest;
+ Sticker_t tmpDest;
+
+ tmpDest.m_fWeight = _kv->GetFloat( "weight", 1.0 );
+ tmpDest.m_baseFilename = _kv->GetString( "base" );
+ KeyValues* pSpec = _kv->FindKey( "spec" );
+ if ( pSpec )
+ tmpDest.m_specFilename = pSpec->GetString();
+ else
+ {
+ CUtlString specPath = tmpDest.m_baseFilename.StripExtension()
+ + "_s"
+ + tmpDest.m_baseFilename.GetExtension();
+
+ tmpDest.m_specFilename = specPath;
+ }
+
+ *realDest = tmpDest;
+}
+
+// ------------------------------------------------------------------------------------------------
+template <>
+CUtlString AsStringT< Sticker_t >( const Sticker_t& _val )
+{
+ char buffer[ 80 ];
+ V_sprintf_safe( buffer, "[ weight %.2f; base \"%s\"; spec \"%s\" ]", _val.m_fWeight, _val.m_baseFilename.Get(), _val.m_specFilename.Get() );
+ return CUtlString( buffer );
+}
+
+// ------------------------------------------------------------------------------------------------
+template< class T >
+struct Settable_t
+{
+ T m_val;
+ bool m_bSet;
+
+ Settable_t()
+ : m_val( T() )
+ , m_bSet( false )
+ { }
+};
+
+// ------------------------------------------------------------------------------------------------
+template < class T >
+void ParseSettable( KeyValues *_kv, void* _pDest )
+{
+ Settable_t<T> *pSettable = ( Settable_t<T>* )_pDest;
+
+ ParseTFromKV<T>( _kv, &pSettable->m_val );
+ ( *pSettable ).m_bSet = true;
+}
+
+// ------------------------------------------------------------------------------------------------
+struct ApplyStickerStageParameters
+{
+ CCopyableUtlVector< Sticker_t > m_possibleStickers;
+
+ Settable_t< Vector2D > m_vDestBL;
+ Settable_t< Vector2D > m_vDestTL;
+ Settable_t< Vector2D > m_vDestTR;
+
+ Range m_AdjustBlack;
+ Range m_AdjustOffset;
+ Range m_AdjustGamma;
+ bool m_Evaluate;
+
+ ApplyStickerStageParameters()
+ : m_AdjustBlack( 0, 0 )
+ , m_AdjustOffset( 1, 1 )
+ , m_AdjustGamma( 1, 1 )
+ , m_Evaluate( true )
+ { }
+};
+
+// ------------------------------------------------------------------------------------------------
+const ParseTableEntry cApplyStickerStageParametersParseTable[] =
+{
+ { "sticker", ParseVectorFromKV< Sticker_t >, offsetof( ApplyStickerStageParameters, m_possibleStickers ) },
+ { "dest_bl", ParseSettable< Vector2D >, offsetof( ApplyStickerStageParameters, m_vDestBL ) },
+ { "dest_tl", ParseSettable< Vector2D >, offsetof( ApplyStickerStageParameters, m_vDestTL ) },
+ { "dest_tr", ParseSettable< Vector2D >, offsetof( ApplyStickerStageParameters, m_vDestTR ) },
+ { "adjust_black", ParseRangeThenDivideBy< 255 >, offsetof( ApplyStickerStageParameters, m_AdjustBlack ) },
+ { "adjust_offset", ParseRangeThenDivideBy< 255 >, offsetof( ApplyStickerStageParameters, m_AdjustOffset ) },
+ { "adjust_gamma", ParseInverseRangeFromKV, offsetof( ApplyStickerStageParameters, m_AdjustGamma ) },
+ { "evaluate?", ParseBoolFromKV, offsetof( ApplyStickerStageParameters, m_Evaluate ) },
+
+ { 0, 0 }
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+class CTCApplyStickerStage : public CTCStage
+{
+ enum { Albedo = 0, Specular = 1 };
+
+public:
+ CTCApplyStickerStage( const ApplyStickerStageParameters& _assp, uint32 nTexCompositeCreateFlags )
+ : m_Parameters( _assp )
+ , m_pMaterial( NULL )
+ , m_pTex( NULL )
+ , m_pTexSpecular( NULL )
+ , m_nChoice( 0 )
+ {
+ SafeAssign( &m_pMaterial, materials->FindMaterial( cCombineMaterialName[ ECO_Blend ], TEXTURE_GROUP_RUNTIME_COMPOSITE ) );
+ }
+
+ virtual ~CTCApplyStickerStage()
+ {
+ SafeRelease( &m_pTex );
+ SafeRelease( &m_pTexSpecular );
+ SafeRelease( &m_pMaterial );
+ }
+
+ virtual bool DoesTargetRenderTarget() const { return true; }
+
+protected:
+ bool AreTexturesLoaded() const
+ {
+ if ( !m_Parameters.m_possibleStickers[ m_nChoice ].m_baseFilename.IsEmpty() && !m_pTex )
+ return false;
+
+ if ( !m_Parameters.m_possibleStickers[ m_nChoice ].m_specFilename.IsEmpty() && !m_pTexSpecular )
+ return false;
+
+ return true;
+ }
+
+ virtual void RequestTextures()
+ {
+ if ( !m_Parameters.m_possibleStickers[ m_nChoice ].m_baseFilename.IsEmpty() )
+ materials->AsyncFindTexture( m_Parameters.m_possibleStickers[ m_nChoice ].m_baseFilename.Get(), TEXTURE_GROUP_RUNTIME_COMPOSITE, this, ( void* ) Albedo, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+
+ if ( !m_Parameters.m_possibleStickers[ m_nChoice ].m_specFilename.IsEmpty() )
+ materials->AsyncFindTexture( m_Parameters.m_possibleStickers[ m_nChoice ].m_specFilename.Get(), TEXTURE_GROUP_RUNTIME_COMPOSITE, this, ( void* ) Specular, false, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+ }
+
+ virtual void ResolveThis( CTextureCompositor* _comp )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ ECompositeResolveStatus resolveStatus = GetResolveStatus();
+ // If we're done, we're done.
+ if ( resolveStatus == ECRS_Complete || resolveStatus == ECRS_Error )
+ return;
+
+ if ( resolveStatus == ECRS_Scheduled )
+ SetResolveStatus( ECRS_PendingTextureLoads );
+
+ // Someone is misusing this node if this assert fires.
+ Assert( GetResolveStatus() == ECRS_PendingTextureLoads );
+
+ CTCStage* pChild = GetFirstChild();
+ if ( pChild != NULL && pChild->GetResolveStatus() != ECRS_Complete )
+ return;
+
+ if ( !AreTexturesLoaded() )
+ return;
+
+ // Ensure we only have zero or one direct children.
+ Assert( !pChild || pChild->GetNextSibling() == NULL );
+
+ // We expect exactly one or zero children. If we have a child, use its render target to render to, otherwise
+ // Get one and use that.
+ ITexture* pRenderTarget = _comp->AllocateCompositorRenderTarget();
+
+ CUtlVector<CTCStageResult_t> results;
+
+ // If we have a child, great! Use it. If not,
+ if ( pChild )
+ results.AddToTail( pChild->GetResult() );
+ else
+ {
+ CTCStageResult_t fakeRes;
+ fakeRes.m_pTexture = materials->FindTexture( "black", TEXTURE_GROUP_RUNTIME_COMPOSITE );
+ }
+
+ CTCStageResult_t baseTex, specTex;
+ baseTex.m_pTexture = m_pTex;
+ m_mTextureAdjust.Set3x4( baseTex.m_mUvAdjust );
+ results.AddToTail( baseTex );
+
+ specTex.m_pTexture = m_pTexSpecular;
+ m_mTextureAdjust.Set3x4( specTex.m_mUvAdjust );
+ results.AddToTail( specTex );
+
+ Render( pRenderTarget, m_pMaterial, results, _comp, pChild == NULL );
+
+ CTCStageResult_t res;
+ res.m_pRenderTarget = pRenderTarget;
+ res.m_fAdjustBlackPoint = m_fAdjustBlack;
+ res.m_fAdjustWhitePoint = m_fAdjustWhite;
+ res.m_fAdjustGamma = m_fAdjustGamma;
+
+ SetResult( res );
+
+ // As soon as we have scheduled the read of a child render target, we can release that
+ // texture back to the pool for use by another stage. Everything is pipelined, so this just
+ // works.
+ CleanupChildResults( _comp );
+ tmMessage( TELEMETRY_LEVEL0, TMMF_ICON_NOTE, "Completed: %s", __FUNCTION__ );
+ }
+
+ virtual bool HasTeamSpecificsThis() const OVERRIDE{ return false; }
+
+ virtual bool ComputeRandomValuesThis( CUniformRandomStream* pRNG ) OVERRIDE
+ {
+ float m_fTotalWeight = 0;
+ FOR_EACH_VEC( m_Parameters.m_possibleStickers, i )
+ {
+ m_fTotalWeight += m_Parameters.m_possibleStickers[ i ].m_fWeight;
+ }
+
+ float fWeight = pRNG->RandomFloat( 0.0f, m_fTotalWeight );
+ FOR_EACH_VEC( m_Parameters.m_possibleStickers, i )
+ {
+ const float thisWeight = m_Parameters.m_possibleStickers[ i ].m_fWeight;
+ if ( fWeight < thisWeight )
+ {
+ m_nChoice = i;
+ break;
+ }
+ else
+ {
+ fWeight -= thisWeight;
+ }
+ }
+
+ const float adjustBlack = pRNG->RandomFloat( m_Parameters.m_AdjustBlack.low, m_Parameters.m_AdjustBlack.high );
+ const float adjustOffset = pRNG->RandomFloat( m_Parameters.m_AdjustOffset.low, m_Parameters.m_AdjustOffset.high );
+ const float adjustGamma = pRNG->RandomFloat( m_Parameters.m_AdjustGamma.low, m_Parameters.m_AdjustGamma.high );
+ const float adjustWhite = adjustBlack + adjustOffset;
+
+ m_fAdjustBlack = adjustBlack;
+ m_fAdjustWhite = adjustWhite;
+ m_fAdjustGamma = adjustGamma;
+
+ ComputeTextureMatrixFromRectangle( &m_mTextureAdjust, m_Parameters.m_vDestBL.m_val, m_Parameters.m_vDestTL.m_val, m_Parameters.m_vDestTR.m_val );
+ return true;
+ }
+
+ virtual void OnAsyncFindComplete( ITexture* pTex, void* pExtraArgs )
+ {
+ switch ( ( int ) pExtraArgs )
+ {
+ case Albedo:
+ SafeAssign( &m_pTex, pTex );
+ break;
+ case Specular:
+ // It's okay if this is the case, we just need to substitute with the black texture.
+ if ( pTex->IsError() )
+ {
+ pTex = materials->FindTexture( "black", TEXTURE_GROUP_RUNTIME_COMPOSITE );
+ }
+ SafeAssign( &m_pTexSpecular, pTex );
+ break;
+ default:
+ Assert( !"Unexpected value passed to OnAsyncFindComplete" );
+ break;
+ };
+ }
+
+private:
+ ApplyStickerStageParameters m_Parameters;
+ IMaterial* m_pMaterial;
+ ITexture* m_pTex;
+ ITexture* m_pTexSpecular;
+ int m_nChoice;
+
+ float m_fAdjustBlack;
+ float m_fAdjustWhite;
+ float m_fAdjustGamma;
+ VMatrix m_mTextureAdjust;
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// This is a procedural stage we use to copy the results of a composite into a texture so we can
+// release the render targets back to a pool to be used later.
+class CTCCopyStage : public CTCStage
+{
+public:
+ CTCCopyStage()
+ : m_pTex( NULL )
+ {
+
+ }
+
+ ~CTCCopyStage()
+ {
+ SafeRelease( &m_pTex );
+ }
+
+ virtual void OnAsyncCreateComplete( ITexture* pTex, void* pExtraArgs )
+ {
+ SafeAssign( &m_pTex, pTex );
+ tmMessage( TELEMETRY_LEVEL0, TMMF_ICON_NOTE, "Completed: %s", __FUNCTION__ );
+ }
+
+ virtual bool DoesTargetRenderTarget() const { return false; }
+
+private:
+ virtual void RequestTextures() { /* No input textures */ }
+
+ virtual void ResolveThis( CTextureCompositor* _comp )
+ {
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ ECompositeResolveStatus resolveStatus = GetResolveStatus();
+
+ // If we're done, we're done.
+ if ( resolveStatus == ECRS_Complete || resolveStatus == ECRS_Error )
+ return;
+
+ if ( resolveStatus == ECRS_Scheduled )
+ SetResolveStatus( ECRS_PendingTextureLoads );
+
+ Assert( GetFirstChild() != NULL );
+
+ // Can't move forward until the child is done.
+ if ( GetFirstChild()->GetResolveStatus() != ECRS_Complete )
+ return;
+
+ // Compositing has completed!
+ if ( m_pTex )
+ {
+ if ( m_pTex->IsError() )
+ {
+ _comp->Error( false, "Error occurred copying render target to texture. This is fatal." );
+ return;
+ }
+
+ CTCStageResult_t res;
+ res.m_pTexture = m_pTex;
+
+#ifdef STAGING_ONLY
+ if ( r_texcomp_dump.GetInt() == 2 )
+ {
+ char buffer[128];
+ V_snprintf( buffer, ARRAYSIZE(buffer), "composite_%s_result_%02d.tga", _comp->GetName().Get(), s_nDumpCount++ );
+ GetFirstChild()->GetResult().m_pRenderTarget->SaveToFile( buffer );
+ }
+#endif
+
+ SetResult( res );
+ return;
+ }
+
+ if ( resolveStatus == ECRS_PendingComposites )
+ return;
+
+ ImageFormat fmt = IMAGE_FORMAT_DXT5_RUNTIME;
+
+ if ( _comp->GetCreateFlags() & TEX_COMPOSITE_CREATE_FLAGS_NO_COMPRESSION )
+ fmt = IMAGE_FORMAT_RGBA8888;
+
+ bool bGenMipmaps = !( _comp->GetCreateFlags() & TEX_COMPOSITE_CREATE_FLAGS_NO_MIPMAPS );
+
+ // We want to do this once only.
+ char buffer[_MAX_PATH];
+ _comp->GetTextureName( buffer, ARRAYSIZE( buffer ) );
+
+ int nCreateFlags = TEXTUREFLAGS_IMMEDIATE_CLEANUP
+ | TEXTUREFLAGS_TRILINEAR
+ | TEXTUREFLAGS_ANISOTROPIC;
+
+#if defined( STAGING_ONLY )
+ #if WITH_TEX_COMPOSITE_CACHE
+ if ( r_texcomp_dump.GetInt() == 0 && ( _comp->GetCreateFlags() & TEX_COMPOSITE_CREATE_FLAGS_FORCE ) == 0 )
+ nCreateFlags = 0;
+ #endif
+#endif
+
+ CMatRenderContextPtr pRenderContext( materials );
+ pRenderContext->AsyncCreateTextureFromRenderTarget( GetFirstChild()->GetResult().m_pRenderTarget, buffer, fmt, bGenMipmaps, nCreateFlags, this, NULL );
+
+ SetResolveStatus( ECRS_PendingComposites );
+ // Don't clean up here just yet, we'll get cleaned up when the composite is totally complete.
+ tmMessage( TELEMETRY_LEVEL0, TMMF_ICON_NOTE, "Begun: %s", __FUNCTION__ );
+ }
+
+ virtual bool HasTeamSpecificsThis() const OVERRIDE { return false; }
+
+ virtual bool ComputeRandomValuesThis( CUniformRandomStream* pRNG ) OVERRIDE
+ {
+ // No RNG here.
+ return false;
+ }
+
+ ITexture* m_pTex;
+ CUtlString m_FinalTextureName;
+ uint32 m_nTexCompositeCreateFlags;
+};
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+CTextureCompositor::CTextureCompositor( int _width, int _height, int nTeam, const char* pCompositeName, uint64 nRandomSeed, uint32 nTexCompositeCreateFlags )
+: m_nReferenceCount( 0 )
+, m_nWidth( _width )
+, m_nHeight( _height )
+, m_nTeam( nTeam )
+, m_nRandomSeed( nRandomSeed )
+, m_pRootStage( NULL )
+, m_ResolveStatus( ECRS_Idle )
+, m_bError( false )
+, m_bFatal( false )
+, m_nRenderTargetsAllocated( 0 )
+, m_CompositeName( pCompositeName )
+, m_nTexCompositeCreateFlags( nTexCompositeCreateFlags )
+, m_bHasTeamSpecifics( false )
+, m_nCompositePaintKitId( 0 )
+{
+
+}
+
+// ------------------------------------------------------------------------------------------------
+CTextureCompositor::~CTextureCompositor()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+ Assert ( m_nReferenceCount == 0 );
+
+ // Have to clean up the stages before cleaning up the render target pool, because cleanup up
+ // stages will throw things back to the render target pool.
+ SafeRelease( &m_pRootStage );
+
+ FOR_EACH_VEC( m_RenderTargetPool, i )
+ {
+ RenderTarget_t& rt = m_RenderTargetPool[ i ];
+ SafeRelease( &rt.m_pRT );
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::Restart()
+{
+ Assert(!"TODO! Need to clone the root node, then cleanup the old root and start the new work.");
+
+ // CTCStage* clone = m_pRootStage->Clone();
+ SafeRelease( &m_pRootStage );
+ // m_pRootStage = clone;
+
+ m_ResolveStatus = ECRS_Scheduled;
+
+ // Kick it off again
+ m_pRootStage->Resolve( true, this );
+ m_ResolveStatus = ECRS_PendingTextureLoads;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::Shutdown()
+{
+ // If this thing is a template, then it's a faker and doesn't have an m_pRootStage. This is
+ // only true during startup when we're just verifying that the templates look sane--later
+ // they should have real data.
+ if ( m_pRootStage )
+ m_pRootStage->Cleanup( this );
+
+ // These should match now.
+ Assert( m_nRenderTargetsAllocated == m_RenderTargetPool.Count() );
+}
+
+// ------------------------------------------------------------------------------------------------
+int CTextureCompositor::AddRef()
+{
+ return ++m_nReferenceCount;
+}
+
+// ------------------------------------------------------------------------------------------------
+int CTextureCompositor::Release()
+{
+ int retVal = --m_nReferenceCount;
+ Assert( retVal >= 0 );
+ if ( retVal == 0 )
+ {
+ Shutdown();
+ delete this;
+ }
+
+ return retVal;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::Update()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ Assert( m_pRootStage );
+
+ if ( m_bError )
+ {
+ if ( !m_bFatal )
+ {
+ m_bError = false;
+ Restart();
+ }
+ else
+ m_ResolveStatus = ECRS_Error;
+ return;
+ }
+
+ if ( m_pRootStage->GetResolveStatus() != ECRS_Complete )
+ m_pRootStage->Resolve( false, this );
+
+ if ( m_pRootStage->GetResolveStatus() == ECRS_Complete )
+ {
+ #ifdef STAGING_ONLY
+ // One time, go ahead and dump out the texture if we're supposed to right here, at completion time.
+ if ( ( r_texcomp_dump.GetInt() == 3 || r_texcomp_dump.GetInt() == 4 ) && m_ResolveStatus != ECRS_Complete )
+ {
+ char filename[_MAX_PATH];
+ V_sprintf_safe( filename, "%s.tga", m_CompositeName.Get() );
+ m_pRootStage->GetResult().m_pTexture->SaveToFile( filename );
+ }
+ #endif
+
+ m_ResolveStatus = ECRS_Complete;
+
+#ifdef RAD_TELEMETRY_ENABLED
+ char buffer[ 256 ];
+ GetTextureName( buffer, ARRAYSIZE( buffer ) );
+ tmEndTimeSpan( TELEMETRY_LEVEL0, m_nCompositePaintKitId, 0, "Composite: %s", tmDynamicString( TELEMETRY_LEVEL0, buffer ) );
+#endif
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+ITexture* CTextureCompositor::GetResultTexture() const
+{
+ Assert( m_pRootStage && m_pRootStage->GetResolveStatus() == ECRS_Complete );
+ Assert( m_pRootStage->GetResult().m_pTexture );
+ return m_pRootStage->GetResult().m_pTexture;
+}
+
+// ------------------------------------------------------------------------------------------------
+ECompositeResolveStatus CTextureCompositor::GetResolveStatus() const
+{
+ return m_ResolveStatus;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::ScheduleResolve( )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ Assert( m_pRootStage );
+ Assert( m_ResolveStatus == ECRS_Idle );
+
+ #if WITH_TEX_COMPOSITE_CACHE
+ if ( ( GetCreateFlags() & TEX_COMPOSITE_CREATE_FLAGS_FORCE ) == 0)
+ {
+ char buffer[ _MAX_PATH ];
+ GetTextureName( buffer, ARRAYSIZE( buffer ) );
+
+ // I think there's a race condition here, add a flag to FindTexture that says only if loaded, and bumps ref?
+ if ( materials->IsTextureLoaded( buffer ) )
+ {
+ ITexture* resTexture = materials->FindTexture( buffer, TEXTURE_GROUP_RUNTIME_COMPOSITE, false, 0 );
+ if ( resTexture && resTexture->IsError() == false )
+ {
+ m_pRootStage->OnAsyncCreateComplete( resTexture, NULL );
+ CTCStageResult_t res;
+ res.m_pTexture = resTexture;
+ m_pRootStage->SetResult( res );
+
+ m_ResolveStatus = ECRS_Complete;
+ return;
+ }
+ }
+ }
+ #endif
+
+ #ifdef RAD_TELEMETRY_ENABLED
+ m_nCompositePaintKitId = ++s_nCompositeCount;
+ char buffer[256];
+ GetTextureName( buffer, ARRAYSIZE( buffer ) );
+ tmBeginTimeSpan( TELEMETRY_LEVEL0, m_nCompositePaintKitId, 0, "Composite: %s", tmDynamicString( TELEMETRY_LEVEL0, buffer ) );
+ #endif
+
+ m_ResolveStatus = ECRS_Scheduled;
+
+ // Naughty.
+ extern CMaterialSystem g_MaterialSystem;
+ g_MaterialSystem.ScheduleTextureComposite( this );
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::Resolve()
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ // We can actually get in multiply times for the same one because of the way EconItemView works.
+ // So if that's the case, bail.
+ if ( m_ResolveStatus != ECRS_Scheduled )
+ return;
+
+ m_pRootStage->Resolve( true, this );
+
+ // Update our resolve status
+ m_ResolveStatus = ECRS_PendingTextureLoads;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::Error( bool _retry, const char* _debugDevMsg, ... )
+{
+ m_bError = true;
+ m_bFatal = !_retry;
+
+ va_list args;
+ va_start( args, _debugDevMsg );
+ WarningV( _debugDevMsg, args );
+ va_end( args );
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::SetRootStage( CTCStage* rootStage )
+{
+ SafeAssign( &m_pRootStage, rootStage );
+
+ // After we set a root, compute everyone's RNG values. Do this once, early, to ensure the values are stable.
+ uint32 seedhi = 0;
+ uint32 seedlo = 0;
+ GetSeed( &seedhi, &seedlo );
+
+ CUniformRandomStream streams[2];
+ streams[0].SetSeed( seedhi );
+ streams[1].SetSeed( seedlo );
+
+ int currentIndex = 0;
+
+ m_pRootStage->ComputeRandomValues( &currentIndex, streams, ARRAYSIZE( streams ) );
+}
+
+// ------------------------------------------------------------------------------------------------
+// TODO: Need to accept format and depth status
+ITexture* CTextureCompositor::AllocateCompositorRenderTarget( )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ FOR_EACH_VEC( m_RenderTargetPool, i )
+ {
+ const RenderTarget_t& rt = m_RenderTargetPool[ i ];
+ if ( rt.m_nWidth == m_nWidth && rt.m_nHeight == m_nHeight )
+ {
+ ITexture* retVal = rt.m_pRT;
+ m_RenderTargetPool.Remove( i );
+ return retVal;
+ }
+ }
+
+ // Lie to the material system that we are asking for this allocation way back at the beginning of time.
+ // This used to matter to GPUs for perf, but hasn't in a long time.
+ materials->OverrideRenderTargetAllocation( true );
+ ITexture* retVal = materials->CreateNamedRenderTargetTextureEx( "", m_nWidth, m_nHeight, RT_SIZE_LITERAL_PICMIP, IMAGE_FORMAT_RGBA8888, MATERIAL_RT_DEPTH_NONE, TEXTUREFLAGS_IMMEDIATE_CLEANUP );
+ Assert( retVal );
+ materials->OverrideRenderTargetAllocation( false );
+
+ // Used to count how many we actually allocated so we can verify we cleaned them all up at
+ // shutdown
+ ++m_nRenderTargetsAllocated;
+ return retVal;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::ReleaseCompositorRenderTarget( ITexture* _tex )
+{
+ Assert( _tex );
+ int w = _tex->GetMappingWidth();
+ int h = _tex->GetMappingHeight();
+
+ RenderTarget_t rt = { w, h, _tex };
+ m_RenderTargetPool.AddToTail( rt );
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::GetTextureName( char* pOutBuffer, int nBufferLen ) const
+{
+ uint32 seedhi = 0;
+ uint32 seedlo = 0;
+ GetSeed( &seedhi, &seedlo );
+
+ Assert( m_pRootStage != NULL );
+ if ( m_pRootStage->HasTeamSpecifics() )
+ V_snprintf( pOutBuffer, nBufferLen, "proc/texcomp/%s_flags%08x_seedhi%08x_seedlo%08x_team%d_w%d_h%d", GetName().Get(), GetCreateFlags(), seedhi, seedlo, m_nTeam, m_nWidth, m_nHeight );
+ else
+ V_snprintf( pOutBuffer, nBufferLen, "proc/texcomp/%s_flags%08x_seedhi%08x_seedlo%08x_w%d_h%d", GetName().Get(), GetCreateFlags(), seedhi, seedlo, m_nWidth, m_nHeight );
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTextureCompositor::GetSeed( uint32* pOutHi, uint32* pOutLo ) const
+{
+ tmZone( TELEMETRY_LEVEL2, TMZF_NONE, "%s", __FUNCTION__ );
+
+ Assert( pOutHi && pOutLo );
+ ( *pOutHi ) = 0;
+ ( *pOutLo ) = 0;
+
+ // This is most definitely not the most efficient way to do this.
+ for ( int i = 0; i < 32; ++i )
+ {
+ ( *pOutHi ) |= (uint32)( ( m_nRandomSeed & ( uint64( 1 ) << ( ( 2 * i ) + 0 ) ) ) >> i );
+ ( *pOutLo ) |= (uint32)( ( m_nRandomSeed & ( uint64( 1 ) << ( ( 2 * i ) + 1 ) ) ) >> ( i + 1 ) );
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+CTCStage::CTCStage()
+: m_nReferenceCount( 1 ) // This is 1 because the common case is to assign these as children, and we don't want to play with refs there.
+, m_pFirstChild( NULL )
+, m_pNextSibling( NULL )
+, m_ResolveStatus( ECRS_Idle )
+{ }
+
+// ------------------------------------------------------------------------------------------------
+CTCStage::~CTCStage()
+{
+ Assert ( m_nReferenceCount == 0 );
+ SafeRelease( &m_pFirstChild );
+ SafeRelease( &m_pNextSibling );
+}
+
+// ------------------------------------------------------------------------------------------------
+int CTCStage::AddRef()
+{
+ return ++m_nReferenceCount;
+}
+
+// ------------------------------------------------------------------------------------------------
+int CTCStage::Release()
+{
+ int retVal = --m_nReferenceCount;
+ if ( retVal == 0 )
+ delete this;
+ return retVal;
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTCStage::Resolve( bool bFirstTime, CTextureCompositor* _comp )
+{
+
+ if ( m_pFirstChild )
+ m_pFirstChild->Resolve( bFirstTime, _comp );
+
+ // Update our status, which may be updated below. Only do this the first time through.
+ if ( bFirstTime )
+ {
+ m_ResolveStatus = ECRS_Scheduled;
+ // Request textures here. We used to request in the constructor, but it caused us
+ // to potentially hold all paintkitted textures for all time. That's bad for Mac,
+ // where we are super memory constrained.
+ RequestTextures();
+ }
+
+ ResolveThis( _comp );
+
+ if ( m_pNextSibling )
+ m_pNextSibling->Resolve( bFirstTime, _comp );
+}
+
+// ------------------------------------------------------------------------------------------------
+bool CTCStage::HasTeamSpecifics( ) const
+{
+ if ( m_pFirstChild && m_pFirstChild->HasTeamSpecifics() )
+ return true;
+
+ if ( HasTeamSpecificsThis() )
+ return true;
+
+ return m_pNextSibling && m_pNextSibling->HasTeamSpecifics();
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTCStage::ComputeRandomValues( int* pCurIndex, CUniformRandomStream* pRNGs, int nRNGCount )
+{
+ Assert( pCurIndex != NULL );
+ Assert( pRNGs != NULL );
+ Assert( nRNGCount != 0 );
+
+ // We do a depth-first traversal here, but we hit ourselves first.
+ if ( ComputeRandomValuesThis( &pRNGs[*pCurIndex] ) )
+ {
+ // Switch which RNG the next person will use.
+ ( *pCurIndex ) = ( ( *pCurIndex ) + 1 ) % nRNGCount;
+ }
+
+ if ( m_pFirstChild )
+ m_pFirstChild->ComputeRandomValues( pCurIndex, pRNGs, nRNGCount );
+
+ if ( m_pNextSibling )
+ m_pNextSibling->ComputeRandomValues( pCurIndex, pRNGs, nRNGCount );
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTCStage::CleanupChildResults( CTextureCompositor* _comp )
+{
+ // This does not recurse. We call it as we move through the tree to clean up our
+ // first-generation children.
+ for ( CTCStage* child = GetFirstChild(); child; child = child->GetNextSibling() )
+ {
+ child->m_Result.Cleanup( _comp );
+ child->m_Result = CTCStageResult_t();
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTCStage::Render( ITexture* _destRT, IMaterial* _mat, const CUtlVector<CTCStageResult_t>& _inputs, CTextureCompositor* _comp, bool bClear )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ CUtlVector< IMaterialVar* > varsToClean;
+ bool bFound = false;
+ char buffer[128];
+ FOR_EACH_VEC( _inputs, i )
+ {
+ const CTCStageResult_t& stageParams = _inputs[ i ];
+
+ Assert( stageParams.m_pTexture || stageParams.m_pRenderTarget );
+ ITexture* inTex = stageParams.m_pTexture
+ ? stageParams.m_pTexture
+ : stageParams.m_pRenderTarget;
+
+ V_snprintf( buffer, ARRAYSIZE( buffer ), "$srctexture%d", i );
+
+ // Set the texture
+ IMaterialVar* var = _mat->FindVar( buffer, &bFound );
+ Assert( bFound );
+ var->SetTextureValue( inTex );
+ varsToClean.AddToTail( var );
+
+ // And the levels parameters
+ V_snprintf( buffer, ARRAYSIZE(buffer), "$texadjustlevels%d", i );
+ var = _mat->FindVar( buffer, &bFound );
+ Assert(bFound);
+ var->SetVecValue( stageParams.m_fAdjustBlackPoint, stageParams.m_fAdjustWhitePoint, stageParams.m_fAdjustGamma );
+
+ // And the expected transform
+ V_snprintf( buffer, ARRAYSIZE(buffer), "$textransform%d", i );
+ var = _mat->FindVar( buffer, &bFound );
+ Assert(bFound);
+ var->SetMatrixValue( stageParams.m_mUvAdjust );
+ }
+
+ IMaterialVar* var = _mat->FindVar( "$textureinputcount", &bFound );
+ Assert( bFound );
+ var->SetIntValue( _inputs.Count() );
+
+ CMatRenderContextPtr pRenderContext( materials );
+
+ int w = _destRT->GetActualWidth();
+ int h = _destRT->GetActualHeight();
+
+ pRenderContext->PushRenderTargetAndViewport( _destRT, 0, 0, w, h );
+
+ if ( bClear )
+ {
+ pRenderContext->ClearColor4ub( 0, 0, 0, 255 );
+ pRenderContext->ClearBuffers( true, false, false );
+ }
+
+ // Perform the render!
+ pRenderContext->DrawScreenSpaceQuad( _mat );
+
+#ifdef STAGING_ONLY
+ if (r_texcomp_dump.GetInt() == 1)
+ {
+ FOR_EACH_VEC(_inputs, i)
+ {
+ if (_inputs[i].m_pTexture)
+ {
+ V_snprintf(buffer, ARRAYSIZE(buffer), "composite_%s_input_%02d_in%01d_%08x.tga", _comp->GetName().Get(), s_nDumpCount, i, (int) this);
+ _inputs[i].m_pTexture->SaveToFile(buffer);
+ }
+ }
+
+ V_snprintf(buffer, ARRAYSIZE(buffer), "composite_%s_result_%02d_%08x.tga", _comp->GetName().Get(), s_nDumpCount++, (int) this);
+ _destRT->SaveToFile(buffer);
+ }
+#endif
+
+ // Restore previous state
+ pRenderContext->PopRenderTargetAndViewport();
+
+ // After rendering, clean up the leftover texture references or they will be there for a long
+ // time.
+ FOR_EACH_VEC( varsToClean, i )
+ {
+ varsToClean[ i ]->SetUndefined();
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+void CTCStage::Cleanup( CTextureCompositor* _comp )
+{
+ if ( m_pFirstChild )
+ m_pFirstChild->Cleanup( _comp );
+
+ m_Result.Cleanup( _comp );
+
+ if ( m_pNextSibling )
+ m_pNextSibling->Cleanup( _comp );
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+typedef bool ( *TBuildNodeFromKVFunc )( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags );
+bool TexStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags );
+template<int Type> bool CombineStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags );
+bool SelectStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags );
+bool ApplyStickerStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags );
+
+struct NodeDefinitionEntry
+{
+ const char* keyName;
+ TBuildNodeFromKVFunc buildFunc;
+};
+
+NodeDefinitionEntry cNodeParseTable[] =
+{
+ { "texture_lookup", TexStageFromKV },
+
+ { "combine_add", CombineStageFromKV<ECO_Add> },
+ { "combine_lerp", CombineStageFromKV<ECO_Lerp> },
+ { "combine_multiply", CombineStageFromKV<ECO_Multiply> },
+
+ { "select", SelectStageFromKV },
+
+ { "apply_sticker", ApplyStickerStageFromKV },
+
+ { 0, 0 }
+};
+
+// ------------------------------------------------------------------------------------------------
+template<typename S>
+void ParseIntoStruct( S* _outStruct, CUtlVector< KeyValues *>* _leftovers, KeyValues* _kv, uint32 nTexCompositeCreateFlags, const ParseTableEntry* _entries )
+{
+ Assert( _leftovers );
+
+ const char* keyName = _kv->GetName();
+ keyName;
+
+ FOR_EACH_SUBKEY( _kv, thisKey )
+ {
+ bool parsed = false;
+ for ( int e = 0; _entries[e].keyName; ++e )
+ {
+ if ( V_stricmp( _entries[e].keyName, thisKey->GetName() ) == 0 )
+ {
+ // If we're instancing, go ahead and run the parse function. If we're just doing template verification
+ // then the right hand side may still have variables that need to be expanded, so just verify that the
+ // left hand side is sane.
+ if ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) == 0 )
+ {
+ void* pDest = ((unsigned char*)_outStruct) + _entries[e].structOffset;
+ _entries[e].parseFunc( thisKey, pDest );
+ }
+ parsed = true;
+ break;
+ }
+ }
+
+ if ( !parsed )
+ {
+ ( *_leftovers ).AddToTail( thisKey );
+ }
+ }
+}
+
+// ------------------------------------------------------------------------------------------------
+bool ParseNodes( CUtlVector< CTCStage* >* _outStages, const CUtlVector< KeyValues *>& _kvs, uint32 nTexCompositeCreateFlags )
+{
+ tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
+
+ bool anyFails = false;
+
+ FOR_EACH_VEC( _kvs, thisKey )
+ {
+ KeyValues *thisKV = _kvs[ thisKey ];
+
+ bool parsed = false;
+ for ( int e = 0; cNodeParseTable[ e ].keyName; ++e )
+ {
+ if ( V_stricmp( cNodeParseTable[ e ].keyName, thisKV->GetName() ) == 0 )
+ {
+ CTCStage* pNewStage = NULL;
+ if ( !cNodeParseTable[ e ].buildFunc( &pNewStage, thisKV->GetName(), thisKV, nTexCompositeCreateFlags ) )
+ anyFails = true;
+
+ (*_outStages).AddToTail( pNewStage );
+ parsed = true;
+ break;
+ }
+ }
+
+ if (!parsed)
+ {
+ DevWarning( "Compositor Error: Unexpected key '%s' while parsing definition.\n", thisKV->GetName() );
+ anyFails = true;
+ }
+ }
+
+ return !anyFails;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool TexStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags )
+{
+ Assert( ppOutStage != NULL );
+
+ TextureStageParameters tsp;
+ CUtlVector< KeyValues* > leftovers;
+ CUtlVector< CTCStage* > childNodes;
+ ParseIntoStruct( &tsp, &leftovers, _kv, nTexCompositeCreateFlags, cTextureStageParametersParseTable );
+ if ( !ParseNodes( &childNodes, leftovers, nTexCompositeCreateFlags ) )
+ return false;
+
+ if ( !( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) )
+ {
+ ( *ppOutStage ) = new CTCTextureStage( tsp, nTexCompositeCreateFlags );
+ ( *ppOutStage )->AppendChildren( childNodes );
+ }
+
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+template <int Type>
+bool CombineStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags )
+{
+ Assert( ppOutStage != NULL );
+
+ static_assert( Type >= 0 && Type < ECO_Error, "Invalid type, you need to update the enum." );
+ CombineStageParameters csp;
+ csp.m_CombineOp = (ECombineOperation) Type;
+
+ CUtlVector< KeyValues* > leftovers;
+ CUtlVector< CTCStage* > childNodes;
+ ParseIntoStruct( &csp, &leftovers, _kv, nTexCompositeCreateFlags, cCombineStageParametersParseTable );
+ if ( !ParseNodes( &childNodes, leftovers, nTexCompositeCreateFlags ) )
+ return false;
+
+ if ( !( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) )
+ {
+ ( *ppOutStage ) = new CTCCombineStage( csp, nTexCompositeCreateFlags );
+ ( *ppOutStage )->AppendChildren( childNodes );
+ }
+
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool SelectStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags )
+{
+ Assert( ppOutStage != NULL );
+
+ SelectStageParameters ssp;
+ CUtlVector< KeyValues* > leftovers;
+ CUtlVector< CTCStage* > childNodes;
+ ParseIntoStruct( &ssp, &leftovers, _kv, nTexCompositeCreateFlags, cSelectStageParametersParseTable );
+ if ( !ParseNodes( &childNodes, leftovers, nTexCompositeCreateFlags ) )
+ return false;
+
+ if ( !( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) )
+ {
+ ( *ppOutStage ) = new CTCSelectStage( ssp, nTexCompositeCreateFlags );
+ ( *ppOutStage )->AppendChildren( childNodes );
+ }
+
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool ApplyStickerStageFromKV( CTCStage** ppOutStage, const char* _key, KeyValues* _kv, uint32 nTexCompositeCreateFlags )
+{
+ Assert( ppOutStage != NULL );
+
+ ApplyStickerStageParameters assp;
+ CUtlVector< KeyValues* > leftovers;
+ CUtlVector< CTCStage* > childNodes;
+ ParseIntoStruct( &assp, &leftovers, _kv, nTexCompositeCreateFlags, cApplyStickerStageParametersParseTable );
+ if ( !ParseNodes( &childNodes, leftovers, nTexCompositeCreateFlags ) )
+ return false;
+
+ // These stages can have exactly one child.
+ if ( childNodes.Count() > 1 )
+ return false;
+
+ int setCount = 0;
+ if ( assp.m_vDestBL.m_bSet ) ++setCount;
+ if ( assp.m_vDestTL.m_bSet ) ++setCount;
+ if ( assp.m_vDestTR.m_bSet ) ++setCount;
+ if ( setCount != 3 )
+ return false;
+
+ if ( !( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY ) )
+ {
+ ( *ppOutStage ) = new CTCApplyStickerStage( assp, nTexCompositeCreateFlags );
+ ( *ppOutStage )->AppendChildren( childNodes );
+ }
+
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+const char *GetCombinedMaterialName( ECombineOperation eMaterial )
+{
+ Assert( eMaterial >= ECO_FirstPrecacheMaterial && eMaterial < ECO_COUNT );
+ return cCombineMaterialName[eMaterial];
+}
+
+// ------------------------------------------------------------------------------------------------
+KeyValues* ResolveTemplate( const char* pRootName, KeyValues* pValues, uint32 nTexCompositeCreateFlags, bool *pInOutAllocdNew )
+{
+ Assert( pRootName != NULL && pValues != NULL && pInOutAllocdNew != NULL );
+
+ const char* pTemplateName = NULL;
+ bool bImplementsTemplate = false;
+ bool bHasOtherNodes = false;
+
+ // First, figure out if the tree is sensible.
+ FOR_EACH_SUBKEY( pValues, pChild )
+ {
+ const char* pChildName = pChild->GetName();
+ if ( V_stricmp( pChildName, "implements" ) == 0 )
+ {
+ if ( bImplementsTemplate )
+ {
+ Warning( "ERROR[%s]: implements field can only appear once, seen a second time as 'implements \"%s\"\n", pRootName, pChild->GetString() );
+ return NULL;
+ }
+
+ bImplementsTemplate = true;
+ pTemplateName = pChild->GetString();
+ }
+ else if ( pChildName && pChildName[0] != '$' )
+ {
+ bHasOtherNodes = true;
+ }
+ }
+
+ if ( bImplementsTemplate && bHasOtherNodes )
+ {
+ Warning( "ERROR[%s]: if using 'implements', can only have variable definitions--other fields not allowed.\n", pRootName );
+ return NULL;
+ }
+
+ // If we're not doing templates, we're all finished.
+ if ( !bImplementsTemplate )
+ return pValues;
+
+ KeyValues* pNewKV = NULL;
+
+ if ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) == 0 )
+ {
+ CTextureCompositorTemplate* pTmpl = TextureManager()->FindTextureCompositorTemplate( pTemplateName );
+ if ( !pTmpl )
+ {
+ Warning( "ERROR[%s]: Couldn't find template named '%s'.\n", pRootName, pTemplateName );
+ return NULL;
+ }
+
+ Assert( pTmpl->GetKV() );
+
+ // If the verify flag isn't set, we're instancing the template so do all the logic.
+ if ( pTmpl->ImplementsTemplate() )
+ {
+ pNewKV = ResolveTemplate( pRootName, pTmpl->GetKV(), nTexCompositeCreateFlags, pInOutAllocdNew );
+ }
+ else
+ {
+ // The root-most template will allocate the memory for all of us.
+ pNewKV = pTmpl->GetKV()->MakeCopy();
+ pNewKV->SetName( pRootName );
+ ( *pInOutAllocdNew ) = true;
+ }
+ }
+ else
+ {
+ // Just return the original KV back to the caller, who just wants a success code here.
+ return pValues;
+ }
+
+ // Now, copy any child var definitions from pValues into pNewKV. Because of the recursive call stack,
+ // this has the net effect that more concrete templates will write their values later than more remote templates.
+ FOR_EACH_SUBKEY( pValues, pChild )
+ {
+ const char* pChildName = pChild->GetName();
+ if ( pChildName && pChildName[0] == '$' )
+ {
+ pNewKV->AddSubKey( pChild->MakeCopy() );
+ }
+ }
+
+ // Success!
+ return pNewKV;
+}
+
+// ------------------------------------------------------------------------------------------------
+typedef CUtlDict< const char* > VariableDefs_t;
+KeyValues* ExtractVariableDefinitions( VariableDefs_t* pOutVarDefs, const char* pRootName, KeyValues* pKeyValues )
+{
+ Assert( pOutVarDefs );
+
+ FOR_EACH_SUBKEY( pKeyValues, pChild )
+ {
+ const char* pChildName = pChild->GetName();
+ if ( pChildName[0] == '$' )
+ {
+ if ( pChild->GetFirstTrueSubKey() )
+ {
+ Warning( "ERROR[%s]: All variable definitions must be simple strings, '%s' was a full subtree.\n", pRootName, pChildName );
+ return NULL;
+ }
+
+ int ndx = ( *pOutVarDefs ).Find( pChildName + 1 );
+ if ( pOutVarDefs->IsValidIndex( ndx ) )
+ ( *pOutVarDefs )[ ndx ] = pChild->GetString();
+ else
+ ( *pOutVarDefs ).Insert( pChildName + 1, pChild->GetString() );
+ }
+ }
+
+ return pKeyValues;
+}
+
+// ------------------------------------------------------------------------------------------------
+CUtlString GetErrorTrail( CUtlVector< const char* >& errorStack )
+{
+ if ( errorStack.Count() == 0 )
+ return CUtlString( "" );
+
+ const int stackLength = errorStack.Count();
+ const int stackLengthMinusOne = stackLength - 1;
+
+ const char* cStageSep = " -> ";
+ const int cStageSepStrLen = V_strlen( cStageSep );
+
+ int totalStrLength = 0;
+
+ for ( int i = 0; i < stackLength; ++i )
+ {
+ totalStrLength += V_strlen( errorStack[ i ] );
+ }
+
+ totalStrLength += stackLengthMinusOne * cStageSepStrLen;
+
+ CUtlString retStr;
+ retStr.SetLength( totalStrLength );
+
+ char* pDstOrig = retStr.GetForModify(); pDstOrig;
+ char* pDst = retStr.GetForModify();
+
+ int destPos = 0;
+ for ( int i = 0; i < stackLength; ++i )
+ {
+ // Copy the string
+ const char* pSrc = errorStack[ i ];
+ while ( ( *pDst++ = *pSrc++ ) != 0 )
+ ++destPos;
+ --pDst;
+
+ if ( i < stackLengthMinusOne )
+ {
+ // Now copy our separator
+ pSrc = cStageSep;
+ while ( ( *pDst++ = *pSrc++ ) != 0 )
+ ++destPos;
+ --pDst;
+ }
+ }
+
+ Assert( destPos == totalStrLength );
+ Assert( pDst - retStr.Get() == totalStrLength );
+ // SetLength above already included the +1 to length for the null terminator.
+ *pDst = '\0';
+
+ return retStr;
+}
+
+// ------------------------------------------------------------------------------------------------
+enum ParseMode
+{
+ Copy,
+ DetermineStringForReplace,
+};
+
+// ------------------------------------------------------------------------------------------------
+// Returns the number of characters written into pOutBuffer or -1 if there was an error.
+int SubstituteVarsRecursive( char* pOutBuffer, int* pOutSubsts, CUtlVector< const char* >& errorStack, const char* pStr, uint32 nTexCompositeCreateFlags, const VariableDefs_t& varDefs )
+{
+ ParseMode mode = Copy;
+ char* pCurVariable = NULL;
+
+ char* pDst = pOutBuffer;
+
+ int srcPos = 0;
+ int dstPos = 0;
+ while ( pStr[ srcPos ] != 0 )
+ {
+ const char* srcC = pStr + srcPos;
+
+ switch ( mode )
+ {
+ case Copy:
+ if ( srcC[ 0 ] == '$' && srcC[ 1 ] == '[' )
+ {
+ mode = DetermineStringForReplace;
+ srcPos += 2;
+ pCurVariable = const_cast< char* >( pStr + srcPos );
+ continue;
+ }
+ else if ( pOutBuffer )
+ {
+ pDst[ dstPos++ ] = pStr[ srcPos++ ];
+ }
+ else
+ {
+ ++dstPos;
+ ++srcPos;
+ }
+
+ break;
+
+ case DetermineStringForReplace:
+ if ( srcC[ 0 ] == ']' )
+ {
+ // Make a modification so we can just do the lookup from this buffer.
+ pCurVariable[ srcC - pCurVariable ] = 0;
+
+ // Lookup our substitution value.
+ int ndx = varDefs.Find( pCurVariable );
+ const char* pSubstText = NULL;
+
+ if ( ndx != varDefs.InvalidIndex() )
+ {
+ pSubstText = varDefs[ ndx ];
+ }
+ else if ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) != 0 )
+ {
+ pSubstText = ""; // It's fine to run into these when verifying the template only.
+ }
+ else
+ {
+ Warning( "ERROR[%s]: Couldn't find variable named $%s that was requested to be substituted.\n", ( const char* ) GetErrorTrail( errorStack ), pCurVariable );
+
+ // Restore the string first.
+ pCurVariable[ srcC - pCurVariable ] = ']';
+
+ return -1;
+ }
+
+ // Put it back.
+ pCurVariable[ srcC - pCurVariable ] = ']';
+
+ int charsWritten = SubstituteVarsRecursive( pOutBuffer ? &pDst[ dstPos ] : NULL, pOutSubsts, errorStack, pSubstText, nTexCompositeCreateFlags, varDefs );
+ if ( charsWritten < 0 )
+ return -1;
+
+ ++( *pOutSubsts );
+ dstPos += charsWritten;
+ ++srcPos;
+
+ mode = Copy;
+ }
+ else
+ {
+ ++srcPos;
+ }
+
+ break;
+ }
+ }
+
+ if ( mode == DetermineStringForReplace )
+ {
+ Warning( "ERROR[%s]: Variable $[%s missing closing bracket ].\n", ( const char* ) GetErrorTrail( errorStack ), pCurVariable );
+ return -1;
+ }
+
+ return dstPos;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns true if successful, false otherwise.
+bool SubstituteVars( CUtlString* pOutStr, int* pOutSubsts, CUtlVector< const char* >& errorStack, const char* pStr, uint32 nTexCompositeCreateFlags, const VariableDefs_t& varDefs )
+{
+ Assert( pOutStr != NULL && pOutSubsts != NULL && pStr != NULL );
+
+ ( *pOutSubsts ) = 0;
+
+ // Even though this involves a traversal, we're saving a malloc by walking this thing once looking for the start token.
+ const char* pFirstRepl = V_strstr( pStr, "$[" );
+
+ // No substitutions, so bail out now.
+ if ( pFirstRepl == NULL )
+ {
+ ( *pOutStr ) = pStr;
+ return true;
+ }
+
+ // We could do this as we go, but we're trying to avoid re-mallocing memory repeatedly in here so process once
+ // to find out what the size is.
+ int expectedLen = SubstituteVarsRecursive( NULL, pOutSubsts, errorStack, pStr, nTexCompositeCreateFlags, varDefs );
+ if ( expectedLen < 0 )
+ return false;
+
+ // We don't need to actually write the string, and we shouldn't. If we're just verifying, exit now with success.
+ if ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) != 0 )
+ return true;
+
+ CUtlString& outStr = ( *pOutStr );
+ outStr.SetLength( expectedLen ); // SetLength does +1 to the length for us.
+
+ int finalLen = SubstituteVarsRecursive( outStr.GetForModify(), pOutSubsts, errorStack, pStr, nTexCompositeCreateFlags, varDefs );
+
+ if ( finalLen < 0 )
+ return false;
+
+ // Otherwise things have gone horribly wrong.
+ Assert( outStr.Length() == expectedLen );
+ Assert( expectedLen == finalLen );
+
+ // Success!
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool ResolveAllVariablesRecursive( CUtlVector< const char* >& errorStack, const VariableDefs_t& varDefs, KeyValues* pKeyValues, uint32 nTexCompositeCreateFlags, CUtlString& tmpStr )
+{
+ // hope for the best
+ bool success = true;
+
+ FOR_EACH_SUBKEY( pKeyValues, pChild )
+ {
+ if ( pChild->GetName()[ 0 ] == '$' )
+ continue;
+
+ errorStack.AddToTail( pChild->GetName() );
+
+ if ( pChild->GetFirstSubKey() )
+ {
+ if ( !ResolveAllVariablesRecursive( errorStack, varDefs, pChild, nTexCompositeCreateFlags, tmpStr ) )
+ success = false;
+ }
+ else
+ {
+ int nSubsts = 0;
+ if ( !SubstituteVars( &tmpStr, &nSubsts, errorStack, pChild->GetString(), nTexCompositeCreateFlags, varDefs ) )
+ success = false;
+
+ // Did we do any substitutions?
+ if ( nSubsts > 0 && ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) == 0 ) )
+ pChild->SetStringValue( tmpStr );
+ }
+
+ errorStack.RemoveMultipleFromTail( 1 );
+ }
+
+ return success;
+}
+
+// ------------------------------------------------------------------------------------------------
+KeyValues* ResolveAllVariables( const char* pRootName, const VariableDefs_t& varDefs, KeyValues* pKeyValues, uint32 nTexCompositeCreateFlags, bool *pInOutAllocdNew )
+{
+ KeyValuesAD kvad_onError( ( KeyValues* ) nullptr );
+
+ // Let's just assume first that if we have any vars, we will need to substitute them.
+ // But if we're just verifying the template, no need.
+ if ( !( *pInOutAllocdNew ) && varDefs.Count() > 0 && ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) == 0 ) )
+ {
+ pKeyValues = pKeyValues->MakeCopy();
+ kvad_onError.Assign( pKeyValues );
+ ( *pInOutAllocdNew ) = true;
+ }
+
+ CUtlString str;
+
+ CUtlVector< const char* > errorStack;
+ errorStack.AddToHead( pRootName );
+
+ if ( !ResolveAllVariablesRecursive( errorStack, varDefs, pKeyValues, nTexCompositeCreateFlags, str ) )
+ return NULL;
+
+ kvad_onError.Assign( NULL );
+ return pKeyValues;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Perform all template expansion and variable substitution here. What should be output
+// should look like v1.0 paintkits without templates or variables. Return NULL
+// if var substitution fails or if we can't resolve a template or something
+// (after outputting a meaningful error message, of course).
+KeyValues* ParseTopLevelIntoKV( const char* pRootName, KeyValues* pValues, uint32 nTexCompositeCreateFlags, bool *pOutAllocdNew )
+{
+ Assert( pRootName != NULL );
+ Assert( pOutAllocdNew != NULL );
+ if ( !pValues )
+ return NULL;
+
+ bool bRequiresCleanup = false;
+ KeyValues* pExpandedKV = NULL;
+ KeyValuesAD autoCleanup_pExpandedKV( pExpandedKV );
+ VariableDefs_t varDefs;
+
+ pExpandedKV = ResolveTemplate( pRootName, pValues, nTexCompositeCreateFlags, &bRequiresCleanup );
+ if ( pExpandedKV == NULL )
+ return NULL;
+
+ if ( bRequiresCleanup )
+ {
+ Assert( autoCleanup_pExpandedKV == nullptr || autoCleanup_pExpandedKV == pExpandedKV );
+ autoCleanup_pExpandedKV.Assign( pExpandedKV );
+ }
+
+ pExpandedKV = ExtractVariableDefinitions( &varDefs, pRootName, pExpandedKV );
+ if ( pExpandedKV == NULL )
+ return NULL;
+
+ // Only resolve the variables if we're instantiating. During verification time, we'll
+ // just check that the keys are sensible and we can skip this.
+ pExpandedKV = ResolveAllVariables( pRootName, varDefs, pExpandedKV, nTexCompositeCreateFlags, &bRequiresCleanup);
+ if ( pExpandedKV == NULL )
+ return NULL;
+
+ Assert( bRequiresCleanup || varDefs.Count() == 0 || ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) != 0 ) );
+ varDefs.RemoveAll(); // These won't be valid after we cleanup the tree to remove variable definitions.
+
+ if ( bRequiresCleanup )
+ {
+ KeyValues* pChild = pExpandedKV->GetFirstSubKey();
+
+ while ( pChild )
+ {
+ const char* pChildName = pChild->GetName();
+ if ( pChildName[ 0 ] == '$' )
+ {
+ KeyValues* pNext = pChild->GetNextKey();
+
+ pExpandedKV->RemoveSubKey( pChild );
+ pChild->deleteThis();
+ pChild = pNext;
+ }
+ else
+ pChild = pChild->GetNextKey();
+ }
+ }
+
+
+ // We don't need to clean up the KeyValues we created, so clear the AD.
+ autoCleanup_pExpandedKV.Assign( NULL );
+
+ ( *pOutAllocdNew ) = bRequiresCleanup;
+ return pExpandedKV;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool HasTemplateOrVariables( const char** ppOutTemplateName, KeyValues* pKV)
+{
+ Assert( ppOutTemplateName );
+
+ bool retVal = false;
+ ( *ppOutTemplateName ) = NULL;
+
+ FOR_EACH_SUBKEY( pKV, pChild )
+ {
+ const char* pName = pChild->GetName();
+ if ( V_stricmp( pName, "implements" ) == 0 )
+ {
+ ( *ppOutTemplateName ) = pChild->GetString();
+ retVal = true;
+ }
+
+ if ( pName[ 0 ] == '$' )
+ retVal = true;
+ }
+
+ return retVal;
+}
+
+// ------------------------------------------------------------------------------------------------
+CTextureCompositor* CreateTextureCompositor( int _w, int _h, const char* pCompositeName, int nTeamNum, uint64 nRandomSeed, KeyValues* _stageDesc, uint32 nTexCompositeCreateFlags )
+{
+ TM_ZONE_DEFAULT( TELEMETRY_LEVEL0 );
+
+ #ifdef STAGING_ONLY
+ if ( r_texcomp_dump.GetInt() == 3 || r_texcomp_dump.GetInt() == 4 )
+ {
+ // Skip compression because it breaks saving render targets out
+ // Also don't pollute the cache (or use it)
+ nTexCompositeCreateFlags |= ( TEX_COMPOSITE_CREATE_FLAGS_NO_COMPRESSION | TEX_COMPOSITE_CREATE_FLAGS_FORCE );
+ }
+ #endif
+
+ CUtlVector< CTCStage* > vecStage;
+ CUtlVector< KeyValues* > kvs;
+
+ KeyValuesAD kvAutoCleanup( (KeyValues*) nullptr );
+
+ bool bRequiresCleanup = false;
+
+ if ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_LOG_NODES_ONLY ) != 0 )
+ {
+ DevMsg( 0, "%s\n{\n", pCompositeName );
+ KeyValuesDumpAsDevMsg( _stageDesc, 1, 0 );
+ DevMsg( 0, "}\n" );
+ }
+
+ _stageDesc = ParseTopLevelIntoKV( pCompositeName, _stageDesc, nTexCompositeCreateFlags, &bRequiresCleanup );
+ if ( !_stageDesc )
+ {
+ if ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_LOG_NODES_ONLY ) != 0 )
+ Msg( "ERROR[%s]: Failed to create compositor, errors above.\n", pCompositeName );
+
+ return NULL;
+ }
+
+ // Set ourselves up for future cleanup.
+ if ( bRequiresCleanup )
+ kvAutoCleanup.Assign( _stageDesc );
+
+ if ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_LOG_NODES_ONLY )
+ {
+ if ( bRequiresCleanup )
+ {
+ DevMsg( 0, "With expansion:\n%s\n{\n", pCompositeName );
+ KeyValuesDumpAsDevMsg( _stageDesc, 1, 0 );
+ DevMsg( 0, "}\n" );
+ }
+ return NULL;
+ }
+
+ const char* pTemplateName = NULL;
+ // If we're just doing a template verification, and we still have keys or values that look like template stuff, bail out now.
+ if ( HasTemplateOrVariables( &pTemplateName, _stageDesc ) && ( ( nTexCompositeCreateFlags & TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY ) != 0 ) )
+ {
+ CTextureCompositor* pComp = new CTextureCompositor( _w, _h, nTeamNum, pCompositeName, nRandomSeed, nTexCompositeCreateFlags );
+ if ( pTemplateName )
+ pComp->SetTemplate( pTemplateName );
+ return pComp;
+ }
+
+ KeyValues* kv = _stageDesc->GetFirstTrueSubKey();
+ if ( !kv )
+ return NULL;
+
+ kvs.AddToTail( kv );
+
+ if ( !ParseNodes( &vecStage, kvs, nTexCompositeCreateFlags ) )
+ {
+ FOR_EACH_VEC( vecStage, i )
+ {
+ SafeRelease( &vecStage[ i ] );
+ }
+
+ return NULL;
+ }
+
+ // Should only get 1 here.
+ Assert( vecStage.Count() == 1 );
+
+ CTCStage* rootStage = vecStage[ 0 ];
+
+ // Need to add a copy as the new root.
+ CTCStage* copyStage = new CTCCopyStage;
+ copyStage->SetFirstChild( rootStage );
+ rootStage = copyStage;
+
+ CTextureCompositor* texCompositor = new CTextureCompositor( _w, _h, nTeamNum, pCompositeName, nRandomSeed, nTexCompositeCreateFlags );
+ if ( pTemplateName )
+ texCompositor->SetTemplate( pTemplateName );
+
+ texCompositor->SetRootStage( rootStage );
+
+ SafeRelease( &rootStage );
+
+ return texCompositor;
+}
+
+// ------------------------------------------------------------------------------------------------
+CTextureCompositorTemplate* CTextureCompositorTemplate::Create( const char* pName, KeyValues* pTmplDesc )
+{
+ if ( !pName || !pTmplDesc )
+ return NULL;
+
+ CTextureCompositor* texCompositor = CreateTextureCompositor( 1, 1, pName, 2, 0, pTmplDesc, TEX_COMPOSITE_CREATE_FLAGS_VERIFY_SCHEMA_ONLY | TEX_COMPOSITE_CREATE_FLAGS_VERIFY_TEMPLATE_ONLY );
+
+ if ( texCompositor )
+ {
+ CTextureCompositorTemplate* pTemplate = new CTextureCompositorTemplate( pName, pTmplDesc );
+ if ( texCompositor->UsesTemplate() )
+ {
+ pTemplate->SetImplementsName( texCompositor->GetTemplateName() );
+ }
+ // Bump then release the ref.
+ texCompositor->AddRef();
+ texCompositor->Release();
+
+ return pTemplate;
+ }
+
+ return NULL;
+}
+
+// ------------------------------------------------------------------------------------------------
+CTextureCompositorTemplate::~CTextureCompositorTemplate()
+{
+ // We don't own the KV we were created with--don't delete it.
+}
+
+// ------------------------------------------------------------------------------------------------
+bool CTextureCompositorTemplate::ResolveDependencies() const
+{
+ // If we don't reference another template, then our verification was validated at construction
+ // time.
+ if ( m_ImplementsName.IsEmpty() )
+ return true;
+
+ CTextureCompositorTemplate* pImplementsTmpl = TextureManager()->FindTextureCompositorTemplate( m_ImplementsName );
+
+ // If we couldn't find our child, then we are not okay.
+ if ( pImplementsTmpl == NULL )
+ {
+ Warning( "ERROR[paintkit_template %s]: Couldn't find template '%s' which we claim to implement.\n", (const char*) m_Name, (const char*)m_ImplementsName );
+ return false;
+ }
+
+ return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool CTextureCompositorTemplate::HasDependencyCycles()
+{
+ // Uses Floyd's algorithm to determine if there's a cycle.
+ TM_ZONE_DEFAULT( TELEMETRY_LEVEL1 );
+
+ if ( HasCycle( this ) )
+ {
+ // Print the cycle. This also marks the nodes as having been tested for cycles.
+ PrintMinimumCycle( this );
+ return true;
+ }
+ else
+ {
+ // Mark everything in this lineage as having been tested for cycles.
+ CTextureCompositorTemplate* pTmpl = this;
+ while ( pTmpl != NULL )
+ {
+ if ( pTmpl->HasCheckedForCycles() )
+ break;
+
+ pTmpl->SetCheckedForCycles( true );
+ pTmpl = Advance( pTmpl, 1 );
+ }
+ }
+
+ return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+void ComputeTextureMatrixFromRectangle( VMatrix* pOutMat, const Vector2D& bl, const Vector2D& tl, const Vector2D& tr )
+{
+ Assert( pOutMat != NULL );
+
+ Vector2D leftEdge = bl - tl;
+ Vector2D topEdge = tr - tl;
+ Vector2D topEdgePerpLeft( -topEdge.y, topEdge.x );
+
+ float magLeftEdge = leftEdge.Length();
+ float magTopEdge = topEdge.Length();
+
+ float xScalar = ( topEdgePerpLeft.Dot( leftEdge ) > 0 ) ? 1 : -1;
+
+
+ // Simplification of acos( ( A . L ) / ( mag( A ) * mag( L ) )
+ // Because A is ( 0, 1), which means A . L is just L.y
+ // and mag( A ) * mag( L ) is just mag( L )
+ float rotationD = RAD2DEG( acos( leftEdge.y / magLeftEdge ) )
+ * ( leftEdge.x < 0 ? 1 : -1 );
+
+ VMatrix tmpMat;
+ tmpMat.Identity();
+ MatrixTranslate( tmpMat, Vector( tl.x, tl.y, 0 ) );
+ MatrixRotate( tmpMat, Vector( 0, 0, 1 ), rotationD );
+ tmpMat = tmpMat.Scale( Vector( xScalar * magTopEdge, magLeftEdge, 1.0f ) );
+ MatrixInverseGeneral( tmpMat, *pOutMat );
+
+ // Copy W into Z because this is a 2-D matrix.
+ ( *pOutMat )[ 0 ][ 2 ] = ( *pOutMat )[ 0 ][ 3 ];
+ ( *pOutMat )[ 1 ][ 2 ] = ( *pOutMat )[ 1 ][ 3 ];
+ ( *pOutMat )[ 2 ][ 2 ] = 1.0f;
+}
+
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------------
+CTextureCompositorTemplate* Advance( CTextureCompositorTemplate* pTmpl, int nSteps )
+{
+ Assert( pTmpl != NULL );
+
+ for ( int i = 0; i < nSteps; ++i )
+ {
+ if ( pTmpl->ImplementsTemplate() )
+ {
+ pTmpl = TextureManager()->FindTextureCompositorTemplate( pTmpl->GetImplementsName() );
+ }
+ else
+ return NULL;
+ }
+
+ return pTmpl;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool HasCycle( CTextureCompositorTemplate* pStartTempl )
+{
+ Assert( pStartTempl != NULL );
+
+ CTextureCompositorTemplate* pTortoise = pStartTempl;
+ CTextureCompositorTemplate* pHare = Advance( pStartTempl, 1 );
+
+ while ( pHare != NULL )
+ {
+ Assert( pTortoise != NULL ); // pTortoise should never be NULL unless pHare already is.
+
+ if ( pTortoise == pHare )
+ return true;
+
+ // There may still actually be a cycle here, but we've already reported it if so,
+ // so go ahead and bail out and say "no cycle found."
+ if ( pTortoise->HasCheckedForCycles() || pHare->HasCheckedForCycles() )
+ return false;
+
+ pTortoise = Advance( pTortoise, 1 );
+ pHare = Advance( pHare, 1 );
+ }
+
+ return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+void PrintMinimumCycle( CTextureCompositorTemplate* pTmpl )
+{
+ TM_ZONE_DEFAULT( TELEMETRY_LEVEL1 );
+
+ const char* pFirstNodeName = pTmpl->GetName();
+ // Also mark the nodes as having been cycle-tested to save execution of retesting the same templates.
+
+ // Finding a minimum cycle is O( n log n ) using a map, but we only do this when there's an error.
+ CUtlMap< CTextureCompositorTemplate*, int > cycles( DefLessFunc( CTextureCompositorTemplate* ) );
+ CUtlLinkedList< const char* > cycleBuilder;
+
+ while ( pTmpl != NULL)
+ {
+ // Add before we bail so that the first looping element is in the list twice.
+ cycleBuilder.AddToTail( pTmpl->GetName() );
+
+ if ( cycles.IsValidIndex( cycles.Find( pTmpl ) ) )
+ break;
+
+ pTmpl->SetCheckedForCycles( true );
+ cycles.Insert( pTmpl );
+ pTmpl = Advance( pTmpl, 1 );
+ }
+
+ // If this hits, we didn't actually have a cycle. What?
+ Assert( pTmpl );
+
+ Warning( "ERROR[paintkit_template %s]: Detected cycle in paintkit template dependency chain: ", pFirstNodeName );
+ FOR_EACH_LL( cycleBuilder, i )
+ {
+ Warning( "%s -> ", cycleBuilder[ i ] );
+ }
+
+ Warning( "...\n" );
+}
+