//========= 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 ( ( intp ) 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 ( ( intp ) 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" );
}

