You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2842 lines
90 KiB
2842 lines
90 KiB
//========= 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( ¤tIndex, 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" ); |
|
} |
|
|
|
|