diff options
Diffstat (limited to 'materialsystem/morph.cpp')
| -rw-r--r-- | materialsystem/morph.cpp | 2280 |
1 files changed, 2280 insertions, 0 deletions
diff --git a/materialsystem/morph.cpp b/materialsystem/morph.cpp new file mode 100644 index 0000000..fba110b --- /dev/null +++ b/materialsystem/morph.cpp @@ -0,0 +1,2280 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "imorphinternal.h" +#include "tier0/dbg.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/MaterialSystemUtil.h" +#include "materialsystem/itexture.h" +#include "materialsystem/imesh.h" +#include "UtlSortVector.h" +#include "materialsystem_global.h" +#include "IHardwareConfigInternal.h" +#include "pixelwriter.h" +#include "itextureinternal.h" +#include "tier1/KeyValues.h" +#include "texturemanager.h" +#include "imaterialsysteminternal.h" +#include "imatrendercontextinternal.h" +#include "studio.h" +#include "tier0/vprof.h" +#include "renderparm.h" +#include "tier2/renderutils.h" +#include "bitmap/imageformat.h" +#include "materialsystem/IShader.h" +#include "imaterialinternal.h" + + +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Activate to get stats +//----------------------------------------------------------------------------- +//#define REPORT_MORPH_STATS 1 + + +//----------------------------------------------------------------------------- +// Used to collapse quads with small gaps +//----------------------------------------------------------------------------- +#define MIN_SEGMENT_GAP_SIZE 12 + + +//----------------------------------------------------------------------------- +// Used to compile the morph data into a vertex texture +//----------------------------------------------------------------------------- +class CVertexMorphDict +{ +public: + CVertexMorphDict(); + + // Adds a morph to the dictionary + void AddMorph( const MorphVertexInfo_t &info ); + + // Sets up, cleans up the morph information + void Setup( ); + void CleanUp(); + + // Gets at morph info + int MorphCount() const; + int GetMorphTargetId( int nMorphTargetIndex ) const; + int GetMorphVertexCount( int nMorphTargetIndex ) const; + const MorphVertexInfo_t &GetMorphVertexInfo( int nMorphTargetIndex, int nIndex ) const; + + // Sorts deltas by destination vertex + void SortDeltas(); + +private: + // Sort method for each morph target's vertices + class CMorphVertexListLess + { + public: + bool Less( const MorphVertexInfo_t& src1, const MorphVertexInfo_t& src2, void *pCtx ) + { + return src1.m_nVertexId < src2.m_nVertexId; + } + }; + + // A list of all vertices affecting a particular morph target + struct MorphVertexList_t + { + MorphVertexList_t() {} + MorphVertexList_t( const MorphVertexList_t& src ) : m_nMorphTargetId( src.m_nMorphTargetId ) {} + + int m_nMorphTargetId; + CUtlSortVector< MorphVertexInfo_t, CMorphVertexListLess > m_MorphInfo; + }; + + // Sort function for the morph lists + class VertexMorphDictLess + { + public: + bool Less( const MorphVertexList_t& src1, const MorphVertexList_t& src2, void *pCtx ); + }; + + // For each morph, store all target vertex indices + // List of all morphs affecting all vertices, used for constructing the morph only + CUtlSortVector< MorphVertexList_t, VertexMorphDictLess > m_MorphLists; +}; + + +//----------------------------------------------------------------------------- +// Used to sort the morphs affecting a particular vertex +//----------------------------------------------------------------------------- +bool CVertexMorphDict::VertexMorphDictLess::Less( const CVertexMorphDict::MorphVertexList_t& src1, const CVertexMorphDict::MorphVertexList_t& src2, void *pCtx ) +{ + return src1.m_nMorphTargetId < src2.m_nMorphTargetId; +} + + +//----------------------------------------------------------------------------- +// Dictionary of morphs affecting a particular vertex +//----------------------------------------------------------------------------- +CVertexMorphDict::CVertexMorphDict() : m_MorphLists() +{ +} + + +//----------------------------------------------------------------------------- +// Adds a morph to the dictionary +//----------------------------------------------------------------------------- +void CVertexMorphDict::AddMorph( const MorphVertexInfo_t &info ) +{ + Assert( info.m_nVertexId != 65535 ); + + MorphVertexList_t find; + find.m_nMorphTargetId = info.m_nMorphTargetId; + int nIndex = m_MorphLists.Find( find ); + if ( nIndex == m_MorphLists.InvalidIndex() ) + { + m_MorphLists.Insert( find ); + nIndex = m_MorphLists.Find( find ); + } + + m_MorphLists[nIndex].m_MorphInfo.InsertNoSort( info ); +} + + +//----------------------------------------------------------------------------- +// Sets up, cleans up the morph information +//----------------------------------------------------------------------------- +void CVertexMorphDict::Setup( ) +{ + m_MorphLists.Purge(); +} + +void CVertexMorphDict::CleanUp( ) +{ +} + + +//----------------------------------------------------------------------------- +// Gets at the dictionary elemenst +//----------------------------------------------------------------------------- +int CVertexMorphDict::MorphCount() const +{ + return m_MorphLists.Count(); +} + +int CVertexMorphDict::GetMorphTargetId( int i ) const +{ + if ( i >= m_MorphLists.Count() ) + return -1; + + return m_MorphLists[i].m_nMorphTargetId; +} + +int CVertexMorphDict::GetMorphVertexCount( int nMorphTarget ) const +{ + return m_MorphLists[nMorphTarget].m_MorphInfo.Count(); +} + +const MorphVertexInfo_t &CVertexMorphDict::GetMorphVertexInfo( int nMorphTarget, int j ) const +{ + return m_MorphLists[nMorphTarget].m_MorphInfo[j]; +} + + +//----------------------------------------------------------------------------- +// Sorts deltas by destination vertex +//----------------------------------------------------------------------------- +void CVertexMorphDict::SortDeltas() +{ + int nMorphTargetCount = m_MorphLists.Count(); + for ( int i = 0; i < nMorphTargetCount; ++i ) + { + m_MorphLists[i].m_MorphInfo.RedoSort(); + } +} + + +//----------------------------------------------------------------------------- +// +// Morph data class +// +//----------------------------------------------------------------------------- +class CMorph : public IMorphInternal, public ITextureRegenerator +{ +public: + // Constructor, destructor + CMorph(); + ~CMorph(); + + // Inherited from IMorph + virtual void Lock( float flFloatToFixedScale ); + virtual void AddMorph( const MorphVertexInfo_t &info ); + virtual void Unlock( ); + + // Inherited from IMorphInternal + virtual void Init( MorphFormat_t format, const char *pDebugName ); + virtual void Bind( IMorphMgrRenderContext *pRenderContext ); + virtual MorphFormat_t GetMorphFormat() const; + + // Other public methods + bool RenderMorphWeights( IMatRenderContext *pRenderContext, int nRenderId, int nWeightCount, const MorphWeight_t* pWeights ); + void AccumulateMorph( int nRenderId ); + +private: + // A list of all morphs affecting a particular vertex + // Assume that consecutive morphs are stored under each other in V coordinates + // both in the src texture and destination texture (which is the morph accumulation texture). + struct MorphSegment_t + { + unsigned int m_nFirstSrc; + unsigned short m_nFirstDest; + unsigned short m_nCount; + }; + + struct MorphQuad_t + { + unsigned int m_nFirstSrc; + unsigned short m_nFirstDest; + unsigned short m_nCount; + unsigned short m_nQuadIndex; + }; + + enum MorphTextureId_t + { + MORPH_TEXTURE_POS_NORMAL_DELTA = 0, + MORPH_TEXTURE_SPEED_SIDE_MAP, + + MORPH_TEXTURE_COUNT + }; + + typedef void (CMorph::*MorphPixelWriter_t)( CPixelWriter &pixelWriter, int x, int y, const MorphVertexInfo_t &info ); + + typedef CUtlVector< MorphSegment_t > MorphSegmentList_t; + typedef CUtlVector< MorphQuad_t > MorphQuadList_t; + +private: + // Inherited from ITextureRegenerator + virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); + virtual void Release() {} + + // Packs all morph data in the dictionary into a vertex texture layout + void PackMorphData( ); + + // Builds the list of segments to render, returns total # of src texels to read from + void BuildSegmentList( CUtlVector< MorphSegmentList_t > &morphSegments ); + + // Builds the list of quads to render + void BuildQuadList( const CUtlVector< MorphSegmentList_t > &morphSegments ); + + // Computes the vertex texture width + void ComputeTextureDimensions( const CUtlVector< MorphSegmentList_t > &morphSegments ); + + // Writes a morph delta into the texture + void WriteDeltaPositionNormalToTexture( CPixelWriter &pixelWriter, int x, int y, const MorphVertexInfo_t &info ); + void WriteSideSpeedToTexture( CPixelWriter &pixelWriter, int x, int y, const MorphVertexInfo_t &info ); + + // Computes the morph target 4tuple count + int Get4TupleCount( MorphFormat_t format ) const; + + // Cleans up vertex textures + void CleanUp( ); + + // Is the morph locked? + bool IsLocked() const; + + // Creates a material for use to do the morph accumulation + void CreateAccumulatorMaterial( int nMaterialIndex ); + + // Renders to the morph accumulator texture + void RenderMorphQuads( IMatRenderContext *pRenderContext, int nRenderId, int nTotalQuadCount, int nWeightCount, int *pWeightLookup, const MorphWeight_t* pWeights ); + + // Displays static morph data statistics + void DisplayMorphStats(); + + // Dynamic stat data + void ClearMorphStats(); + void AccumulateMorphStats( int nActiveMorphCount, int nQuadsRendered, int nTexelsRendered ); + void ReportMorphStats( ); + void HandleMorphStats( int nActiveMorphCount, int nQuadsRendered, int nTexelsRendered ); + + // Computes morph texture size in bytes + int ComputeMorphTextureSizeInBytes( ) const; + + // Counts the total number of vertices to place in the static mesh + int CountStaticMeshVertices() const; + + // Determines mesh vertex format + VertexFormat_t ComputeVertexFormat( IMaterial * pMaterial ) const; + + // Builds the list of quads to render + void CreateStaticMesh(); + + // Builds a list of non-zero morph targets + int BuildNonZeroMorphList( int *pWeightIndices, int nWeightCount, const MorphWeight_t* pWeights ); + + // Determines the total number of deltas + int DetermineTotalDeltaCount( const CUtlVector< MorphSegmentList_t > &morphSegments ) const; + + // Binds the morph weight texture + void BindMorphWeight( int nRenderId ); + +private: + // Used when constructing the morph targets + CVertexMorphDict m_MorphDict; + bool m_bLocked; + + // The morph format + MorphFormat_t m_Format; + + // The compiled vertex textures + ITextureInternal *m_pMorphTexture[MORPH_TEXTURE_COUNT]; + + // The compiled vertex streams + IMesh* m_pMorphBuffer; + + // Describes all morph line segments required to draw a particular morph + CUtlVector< MorphQuadList_t > m_MorphQuads; + CUtlVector< int > m_MorphTargetIdToQuadIndex; + + // Caches off the morph weights when in the middle of performing morph accumulation + int m_nMaxMorphTargetCount; + MorphWeight_t *m_pRenderMorphWeight; + + CMaterialReference m_MorphAccumulationMaterial; + + // Float->fixed scale + float m_flFloatToFixedScale; + + // Morph input texture size + int m_nTextureWidth; + int m_nTextureHeight; + +#ifdef _DEBUG + CUtlString m_pDebugName; +#endif + + // Used to unique-ify morph texture names + static int s_nUniqueId; +}; + + +//----------------------------------------------------------------------------- +// Render context for morphing. Only is used to determine +// where in the morph accumulator to put the texture. +//----------------------------------------------------------------------------- +class CMorphMgrRenderContext : public IMorphMgrRenderContext +{ +public: + enum UnnamedEnumsAreNotLegal + { + MAX_MODEL_MORPHS = 4, + }; + + CMorphMgrRenderContext(); + int GetRenderId( CMorph* pMorph ); + +public: + int m_nMorphCount; + CMorph *m_pMorphsToAccumulate[MAX_MODEL_MORPHS]; + +#ifdef DBGFLAG_ASSERT + bool m_bInMorphAccumulation; +#endif +}; + + +//----------------------------------------------------------------------------- +// Morph manager class +//----------------------------------------------------------------------------- +class CMorphMgr : public IMorphMgr +{ +public: + CMorphMgr(); + + // Methods of IMorphMgr + virtual bool ShouldAllocateScratchTextures(); + virtual void AllocateScratchTextures(); + virtual void FreeScratchTextures(); + virtual void AllocateMaterials(); + virtual void FreeMaterials(); + virtual ITextureInternal *MorphAccumulator(); + virtual ITextureInternal *MorphWeights(); + virtual IMorphInternal *CreateMorph(); + virtual void DestroyMorph( IMorphInternal *pMorphData ); + virtual int MaxHWMorphBatchCount() const; + virtual void BeginMorphAccumulation( IMorphMgrRenderContext *pIRenderContext ); + virtual void EndMorphAccumulation( IMorphMgrRenderContext *pIRenderContext ); + virtual void AccumulateMorph( IMorphMgrRenderContext *pIRenderContext, IMorph* pMorph, int nMorphCount, const MorphWeight_t* pWeights ); + virtual void AdvanceFrame(); + virtual bool GetMorphAccumulatorTexCoord( IMorphMgrRenderContext *pRenderContext, Vector2D *pTexCoord, IMorph *pMorph, int nVertex ); + virtual IMorphMgrRenderContext *AllocateRenderContext(); + virtual void FreeRenderContext( IMorphMgrRenderContext *pRenderContext ); + + // Other public methods +public: + // Computes texel offsets for the upper corner of the morph accumulator for a particular block + void ComputeAccumulatorSubrect( int *pXOffset, int *pYOffset, int *pWidth, int *pHeight, int nMorphAccumBlockId ); + void GetAccumulatorSubrectDimensions( int *pWidth, int *pHeight ); + int GetAccumulator4TupleCount() const; + + // Computes texel offsets for the upper corner of the morph weight texture for a particular block + void ComputeWeightSubrect( int *pXOffset, int *pYOffset, int *pWidth, int *pHeight, int nMorphAccumBlockId ); + + // Used to compute stats of memory used + void RegisterMorphSizeInBytes( int nSizeInBytes ); + int GetTotalMemoryUsage() const; + + // Are we using the constant register method? + bool IsUsingConstantRegisters() const { return m_bUsingConstantRegisters; } + +private: + // Displays 32bit float texture data + void Display32FTextureData( float *pBuf, int nTexelID, int *pSubRect, ITexture *pTexture, int n4TupleCount ); + + // A debugging utility to display the morph accumulator + void DebugMorphAccumulator( IMatRenderContext *pRenderContext ); + + // A debugging utility to display the morph weights + void DebugMorphWeights( IMatRenderContext *pRenderContext ); + + // Draws the morph accumulator + morph weights + void DrawMorphTempTexture( IMatRenderContext *pRenderContext, IMaterial *pMaterial, ITexture *pTexture ); + +private: + enum + { + MAX_MORPH_ACCUMULATOR_VERTICES = 32768, + MORPH_ACCUMULATOR_4TUPLES = 2, // 1 for pos + wrinkle, 1 for normal + }; + + int m_nAccumulatorWidth; + int m_nAccumulatorHeight; + int m_nSubrectVerticalCount; + int m_nWeightWidth; + int m_nWeightHeight; + int m_nFrameCount; + int m_nTotalMorphSizeInBytes; + IMaterial *m_pPrevMaterial; + void *m_pPrevProxy; + int m_nPrevBoneCount; + MaterialHeightClipMode_t m_nPrevClipMode; + bool m_bPrevClippingEnabled; + bool m_bUsingConstantRegisters; + bool m_bFlashlightMode; + + ITextureInternal *m_pMorphAccumTexture; + ITextureInternal *m_pMorphWeightTexture; + IMaterial *m_pVisualizeMorphAccum; + IMaterial *m_pVisualizeMorphWeight; + IMaterial *m_pRenderMorphWeight; +}; + + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +static CMorphMgr s_MorphMgr; +IMorphMgr *g_pMorphMgr = &s_MorphMgr; + + + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +int CMorph::s_nUniqueId = 0; + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CMorph::CMorph() +{ + memset( m_pMorphTexture, 0, sizeof(m_pMorphTexture) ); + m_pMorphBuffer = NULL; + m_nTextureWidth = 0; + m_nTextureHeight = 0; + m_bLocked = false; + m_Format = 0; + m_flFloatToFixedScale = 1.0f; + m_pRenderMorphWeight = 0; + m_nMaxMorphTargetCount = 0; +} + +CMorph::~CMorph() +{ + CleanUp(); +} + + +//----------------------------------------------------------------------------- +// Initialization +//----------------------------------------------------------------------------- +void CMorph::Init( MorphFormat_t format, const char *pDebugName ) +{ + m_Format = format; + +#ifdef _DEBUG + m_pDebugName = pDebugName; +#endif +} + + +//----------------------------------------------------------------------------- +// Returns the morph format +//----------------------------------------------------------------------------- +MorphFormat_t CMorph::GetMorphFormat() const +{ + return m_Format; +} + + +//----------------------------------------------------------------------------- +// Binds morph accumulator, morph weights +//----------------------------------------------------------------------------- +void CMorph::Bind( IMorphMgrRenderContext *pIRenderContext ) +{ + CMorphMgrRenderContext *pMorphRenderContext = static_cast< CMorphMgrRenderContext* >( pIRenderContext ); + int nRenderId = pMorphRenderContext->GetRenderId( this ); + if ( nRenderId < 0 ) + return; + + int nXOffset, nYOffset, nWidth, nHeight; + s_MorphMgr.ComputeAccumulatorSubrect( &nXOffset, &nYOffset, &nWidth, &nHeight, nRenderId ); + + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_ACCUMULATOR_4TUPLE_COUNT, s_MorphMgr.GetAccumulator4TupleCount() ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_ACCUMULATOR_X_OFFSET, nXOffset ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_ACCUMULATOR_Y_OFFSET, nYOffset ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_ACCUMULATOR_SUBRECT_WIDTH, nWidth ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_ACCUMULATOR_SUBRECT_HEIGHT, nHeight ); +} + +void CMorph::BindMorphWeight( int nRenderId ) +{ + int nXOffset, nYOffset, nWidth, nHeight; + s_MorphMgr.ComputeWeightSubrect( &nXOffset, &nYOffset, &nWidth, &nHeight, nRenderId ); + + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_WEIGHT_X_OFFSET, nXOffset ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_WEIGHT_Y_OFFSET, nYOffset ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_WEIGHT_SUBRECT_WIDTH, nWidth ); + g_pShaderAPI->SetIntRenderingParameter( INT_RENDERPARM_MORPH_WEIGHT_SUBRECT_HEIGHT, nHeight ); +} + + +//----------------------------------------------------------------------------- +// Computes morph texture size in bytes +//----------------------------------------------------------------------------- +int CMorph::ComputeMorphTextureSizeInBytes( ) const +{ + int nSize = 0; + + if ( m_pMorphTexture[MORPH_TEXTURE_POS_NORMAL_DELTA] ) + { + int nTotal4Tuples = Get4TupleCount( m_Format ); + nSize += m_nTextureWidth * m_nTextureHeight * nTotal4Tuples * ImageLoader::SizeInBytes( IMAGE_FORMAT_RGBA16161616 ); + } + + if ( m_pMorphTexture[MORPH_TEXTURE_SPEED_SIDE_MAP] ) + { + nSize += m_nTextureWidth * m_nTextureHeight * ImageLoader::SizeInBytes( IMAGE_FORMAT_RGBA8888 ); + } + + // NOTE: Vertex size here is kind of a hack, but whatever. + int nVertexCount = CountStaticMeshVertices(); + nSize += nVertexCount * 5 * sizeof(float); + return nSize; +} + + +//----------------------------------------------------------------------------- +// Cleans up vertex textures +//----------------------------------------------------------------------------- +void CMorph::CleanUp( ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + int nMorphTextureSize = ComputeMorphTextureSizeInBytes(); + s_MorphMgr.RegisterMorphSizeInBytes( -nMorphTextureSize ); + + IMaterial *pMat = m_MorphAccumulationMaterial; + m_MorphAccumulationMaterial.Shutdown(); + if ( pMat ) + { + pMat->DeleteIfUnreferenced(); + } + + if ( m_pMorphBuffer ) + { + pRenderContext->DestroyStaticMesh( m_pMorphBuffer ); + m_pMorphBuffer = NULL; + } + + for ( int i = 0; i < MORPH_TEXTURE_COUNT; ++i ) + { + if ( m_pMorphTexture[i] ) + { + m_pMorphTexture[i]->SetTextureRegenerator( NULL ); + m_pMorphTexture[i]->DecrementReferenceCount( ); + m_pMorphTexture[i]->DeleteIfUnreferenced(); + m_pMorphTexture[i] = NULL; + } + } + + if ( m_pRenderMorphWeight ) + { + delete[] m_pRenderMorphWeight; + m_pRenderMorphWeight = NULL; + } + + m_nMaxMorphTargetCount = 0; +} + + +//----------------------------------------------------------------------------- +// Is the morph locked? +//----------------------------------------------------------------------------- +bool CMorph::IsLocked() const +{ + return m_bLocked; +} + + +//----------------------------------------------------------------------------- +// Locks the morph data +//----------------------------------------------------------------------------- +void CMorph::Lock( float flFloatToFixedScale ) +{ + Assert( !IsLocked() ); + m_bLocked = true; + CleanUp(); + m_flFloatToFixedScale = flFloatToFixedScale; + m_MorphQuads.Purge(); + m_MorphTargetIdToQuadIndex.RemoveAll(); + m_MorphDict.Setup( ); +} + + +//----------------------------------------------------------------------------- +// Adds morph data to the morph dictionary +//----------------------------------------------------------------------------- +void CMorph::AddMorph( const MorphVertexInfo_t &info ) +{ + Assert( IsLocked() ); + m_MorphDict.AddMorph( info ); +} + + +//----------------------------------------------------------------------------- +// Unlocks the morph data, builds the vertex textures +//----------------------------------------------------------------------------- +void CMorph::Unlock( ) +{ + Assert( IsLocked() ); + + // Sort the deltas by destination vertex + m_MorphDict.SortDeltas(); + + // Now lay out morph data as if it were in a vertex texture + PackMorphData( ); + + // Free up temporary memory used in building + m_MorphDict.CleanUp(); + + m_bLocked = false; + + // Gather stats + int nMorphTextureSize = ComputeMorphTextureSizeInBytes(); + s_MorphMgr.RegisterMorphSizeInBytes( nMorphTextureSize ); +} + + +//----------------------------------------------------------------------------- +// Creates a material for use to do the morph accumulation +//----------------------------------------------------------------------------- +void CMorph::CreateAccumulatorMaterial( int nMaterialIndex ) +{ + // NOTE: Delta scale is a little tricky. The numbers are store in fixed-point 16 bit. + // The pixel shader will interpret 65536 as 1.0, and 0 as 0.0. In the pixel shader, + // we will read the delta, multiply it by 2, and subtract 1 to get a -1 to 1 range. + // The float to fixed scale is applied prior to writing it in (delta * scale + 32768). + // Therefore the max representable positive value = + // 65536 = max positive delta * scale + 32768 + // max positive delta = 32768 / scale + // This is what we will multiply our -1 to 1 values by in the pixel shader. + char pTemp[256]; + KeyValues *pVMTKeyValues = new KeyValues( "MorphAccumulate" ); + pVMTKeyValues->SetInt( "$nocull", 1 ); + pVMTKeyValues->SetFloat( "$deltascale", ( m_flFloatToFixedScale != 0.0f ) ? 32768.0f / m_flFloatToFixedScale : 1.0f ); + if ( m_pMorphTexture[MORPH_TEXTURE_POS_NORMAL_DELTA] ) + { + pVMTKeyValues->SetString( "$delta", m_pMorphTexture[MORPH_TEXTURE_POS_NORMAL_DELTA]->GetName() ); + } + if ( m_pMorphTexture[MORPH_TEXTURE_SPEED_SIDE_MAP] ) + { + pVMTKeyValues->SetString( "$sidespeed", m_pMorphTexture[MORPH_TEXTURE_SPEED_SIDE_MAP]->GetName() ); + } + Q_snprintf( pTemp, sizeof(pTemp), "[%d %d %d]", m_nTextureWidth, m_nTextureHeight, Get4TupleCount(m_Format) ); + pVMTKeyValues->SetString( "$dimensions", pTemp ); + + Q_snprintf( pTemp, sizeof(pTemp), "___AccumulateMorph%d.vmt", nMaterialIndex ); + m_MorphAccumulationMaterial.Init( pTemp, pVMTKeyValues ); +} + + + +//----------------------------------------------------------------------------- +// Computes the morph target field count +//----------------------------------------------------------------------------- +int CMorph::Get4TupleCount( MorphFormat_t format ) const +{ + int nSize = 0; + if ( format & ( MORPH_POSITION | MORPH_WRINKLE ) ) + { + ++nSize; + } + if ( format & MORPH_NORMAL ) + { + ++nSize; + } + return nSize; +} + + +//----------------------------------------------------------------------------- +// Determines the total number of deltas +//----------------------------------------------------------------------------- +int CMorph::DetermineTotalDeltaCount( const CUtlVector< MorphSegmentList_t > &morphSegments ) const +{ + int nDeltaCount = 0; + int nMorphCount = morphSegments.Count(); + for ( int i = 0; i < nMorphCount; ++i ) + { + const MorphSegmentList_t& list = morphSegments[i]; + int nSegmentCount = list.Count(); + for ( int j = 0; j < nSegmentCount; ++j ) + { + nDeltaCount += list[j].m_nCount; + } + } + return nDeltaCount; +} + + +//----------------------------------------------------------------------------- +// Computes the texture width +//----------------------------------------------------------------------------- +void CMorph::ComputeTextureDimensions( const CUtlVector< MorphSegmentList_t > &morphSegments ) +{ + int nTotalDeltas = DetermineTotalDeltaCount( morphSegments ); + m_nTextureHeight = ceil( sqrt( (float)nTotalDeltas ) ); + + // Round the dimension up to a multiple of 4 + m_nTextureHeight = ( m_nTextureHeight + 3 ) & ( ~0x3 ); + m_nTextureWidth = ( m_nTextureHeight != 0 ) ? ( nTotalDeltas + ( m_nTextureHeight - 1 ) ) / m_nTextureHeight : 0; + m_nTextureWidth = ( m_nTextureWidth + 3 ) & ( ~0x3 ); + + int nTotal4Tuples = Get4TupleCount( m_Format ); + + // Make sure it obeys bounds + int nMaxTextureWidth = HardwareConfig()->MaxTextureWidth(); + int nMaxTextureHeight = HardwareConfig()->MaxTextureHeight(); + while( m_nTextureWidth * nTotal4Tuples > nMaxTextureWidth ) + { + m_nTextureWidth >>= 1; + m_nTextureHeight <<= 1; + if ( m_nTextureHeight > nMaxTextureHeight ) + { + Warning( "Morph texture is too big!!! Make brian add support for morphs having multiple textures.\n" ); + Assert( 0 ); + m_nTextureHeight = nMaxTextureHeight; + break; + } + } +} + + +//----------------------------------------------------------------------------- +// Displays morph data statistics +//----------------------------------------------------------------------------- +void CMorph::DisplayMorphStats() +{ + ITexture *pDest = g_pMorphMgr->MorphAccumulator( ); + int nDestTextureHeight = pDest->GetActualHeight(); + +#ifdef _DEBUG + Msg( "Morph %s:\n", m_pDebugName.Get() ); +#else + Msg( "Morph :\n" ); +#endif + + int nMorphCount = m_MorphQuads.Count(); + Msg( "\tMorph Target Count : %d\n", nMorphCount ); + + int nTotalQuadCount = 0; + int nTotalVertexCount = 0; + CUtlVector<int> quadHisto; + CUtlVector<int> vertexHisto; + CUtlVector<int> gapSizeHisto; + for ( int i = 0; i < nMorphCount; ++i ) + { + MorphQuadList_t &list = m_MorphQuads[i]; + int nQuadCount = list.Count(); + int nVertexCount = 0; + for ( int j = 0; j < nQuadCount; ++j ) + { + nVertexCount += list[j].m_nCount; + if ( j != 0 ) + { + // Filter out src gaps + wraparound gaps + if ( ( list[j].m_nFirstDest / nDestTextureHeight == list[j-1].m_nFirstDest / nDestTextureHeight ) && + ( list[j].m_nFirstSrc / m_nTextureHeight == list[j-1].m_nFirstSrc / m_nTextureHeight ) ) + { + int nGapSize = list[j].m_nFirstDest - ( list[j-1].m_nFirstDest + list[j-1].m_nCount ); + while ( nGapSize >= gapSizeHisto.Count() ) + { + gapSizeHisto.AddToTail( 0 ); + } + gapSizeHisto[nGapSize] += 1; + } + } + } + while ( nQuadCount >= quadHisto.Count() ) + { + quadHisto.AddToTail( 0 ); + } + while ( nVertexCount >= vertexHisto.Count() ) + { + vertexHisto.AddToTail( 0 ); + } + quadHisto[nQuadCount]+=1; + vertexHisto[nVertexCount]+=1; + nTotalQuadCount += nQuadCount; + nTotalVertexCount += nVertexCount; + } + + Msg( "\tAverage # of vertices per target: %d\n", nTotalVertexCount / nMorphCount ); + Msg( "\tAverage # of quad draws per target: %d\n", nTotalQuadCount / nMorphCount ); + + Msg( "\tQuad Count Histogram :\n\t\t" ); + for ( int i = 0; i < quadHisto.Count(); ++i ) + { + if ( quadHisto[i] == 0 ) + continue; + Msg( "[%d : %d] ", i, quadHisto[i] ); + } + Msg( "\n\tVertex Count Histogram :\n\t\t" ); + for ( int i = 0; i < vertexHisto.Count(); ++i ) + { + if ( vertexHisto[i] == 0 ) + continue; + Msg( "[%d : %d] ", i, vertexHisto[i] ); + } + Msg( "\n\tGap size Count Histogram :\n\t\t" ); + for ( int i = 0; i < gapSizeHisto.Count(); ++i ) + { + if ( gapSizeHisto[i] == 0 ) + continue; + Msg( "[%d : %d] ", i, gapSizeHisto[i] ); + } + Msg( "\n" ); +} + + +//----------------------------------------------------------------------------- +// Packs all morph data in the dictionary into a vertex texture layout +//----------------------------------------------------------------------------- +void CMorph::PackMorphData( ) +{ + CUtlVector< MorphSegmentList_t > morphSegments; + + BuildSegmentList( morphSegments ); + ComputeTextureDimensions( morphSegments ); + BuildQuadList( morphSegments ); + + if ( m_nTextureWidth == 0 || m_nTextureHeight == 0 ) + return; + + char pTemp[512]; + if ( m_Format & ( MORPH_POSITION | MORPH_WRINKLE | MORPH_NORMAL ) ) + { + Q_snprintf( pTemp, sizeof(pTemp), "__morphtarget[%d]: pos/norm", s_nUniqueId ); + + int nTotal4Tuples = Get4TupleCount( m_Format ); + ITexture *pTexture = g_pMaterialSystem->CreateProceduralTexture( pTemp, TEXTURE_GROUP_MORPH_TARGETS, + m_nTextureWidth * nTotal4Tuples, m_nTextureHeight, IMAGE_FORMAT_RGBA16161616, + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_NODEBUGOVERRIDE | + TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE ); + m_pMorphTexture[MORPH_TEXTURE_POS_NORMAL_DELTA] = static_cast<ITextureInternal*>( pTexture ); + } + + if ( m_Format & ( MORPH_SIDE | MORPH_SPEED ) ) + { + Q_snprintf( pTemp, sizeof(pTemp), "__morphtarget[%d]: side/speed", s_nUniqueId ); + + ITexture *pTexture = g_pMaterialSystem->CreateProceduralTexture( pTemp, TEXTURE_GROUP_MORPH_TARGETS, + m_nTextureWidth, m_nTextureHeight, IMAGE_FORMAT_RGBA8888, + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_NODEBUGOVERRIDE | + TEXTUREFLAGS_SINGLECOPY | TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE ); + m_pMorphTexture[MORPH_TEXTURE_SPEED_SIDE_MAP] = static_cast<ITextureInternal*>( pTexture ); + } + + for ( int i = 0; i < MORPH_TEXTURE_COUNT; ++i ) + { + if ( m_pMorphTexture[i] ) + { + m_pMorphTexture[i]->SetTextureRegenerator( this ); + m_pMorphTexture[i]->Download(); + } + } + + CreateAccumulatorMaterial( s_nUniqueId ); + ++s_nUniqueId; + + CreateStaticMesh(); + +#ifdef REPORT_MORPH_STATS + DisplayMorphStats( ); +#endif +} + + +//----------------------------------------------------------------------------- +// Writes a morph delta into the texture +//----------------------------------------------------------------------------- +void CMorph::WriteDeltaPositionNormalToTexture( CPixelWriter &pixelWriter, int x, int y, const MorphVertexInfo_t &info ) +{ + // NOTE: 0 = -max range, 32767 = 0, 65534, 65535 = maxrange. + // This way we can encode +/- maxrange and 0 exactly + Assert ( m_Format & ( MORPH_POSITION | MORPH_WRINKLE | MORPH_NORMAL ) ); + + int n4TupleCount = Get4TupleCount( m_Format ); + pixelWriter.Seek( x*n4TupleCount, y ); + + // NOTE: int cast is where it is to force round-to-zero prior to offset + if ( m_Format & ( MORPH_POSITION | MORPH_WRINKLE ) ) + { + int r = 32767, g = 32767, b = 32767, a = 32767; + if ( m_Format & MORPH_POSITION ) + { + r = (int)( info.m_PositionDelta.x * m_flFloatToFixedScale ) + 32767; + g = (int)( info.m_PositionDelta.y * m_flFloatToFixedScale ) + 32767; + b = (int)( info.m_PositionDelta.z * m_flFloatToFixedScale ) + 32767; + r = clamp( r, 0, 65534 ); + g = clamp( g, 0, 65534 ); + b = clamp( b, 0, 65534 ); + } + if ( m_Format & MORPH_WRINKLE ) + { + a = (int)( info.m_flWrinkleDelta * m_flFloatToFixedScale ) + 32767; + a = clamp( a, 0, 65534 ); + } + pixelWriter.WritePixel( r, g, b, a ); + } + + if ( m_Format & MORPH_NORMAL ) + { + int r = 32767, g = 32767, b = 32767, a = 32767; + r = (int)( info.m_NormalDelta.x * m_flFloatToFixedScale ) + 32767; + g = (int)( info.m_NormalDelta.y * m_flFloatToFixedScale ) + 32767; + b = (int)( info.m_NormalDelta.z * m_flFloatToFixedScale ) + 32767; + r = clamp( r, 0, 65534 ); + g = clamp( g, 0, 65534 ); + b = clamp( b, 0, 65534 ); + + pixelWriter.WritePixel( r, g, b, a ); + } +} + +void CMorph::WriteSideSpeedToTexture( CPixelWriter &pixelWriter, int x, int y, const MorphVertexInfo_t &info ) +{ + Assert ( m_Format & ( MORPH_SPEED | MORPH_SIDE ) ); + + // Speed + size go from 0 to 1. + int r = 0, g = 0, b = 0, a = 0; + if ( m_Format & MORPH_SIDE ) + { + r = info.m_flSide * 255; + } + if ( m_Format & MORPH_SPEED ) + { + g = info.m_flSpeed * 255; + } + r = clamp( r, 0, 255 ); + g = clamp( g, 0, 255 ); + + pixelWriter.Seek( x, y ); + pixelWriter.WritePixel( r, g, b, a ); +} + + +//----------------------------------------------------------------------------- +// Builds the list of segments to render +//----------------------------------------------------------------------------- +void CMorph::BuildSegmentList( CUtlVector< MorphSegmentList_t > &morphSegments ) +{ + // Find the dimensions of the destination texture + int nDestTextureWidth, nDestTextureHeight; + s_MorphMgr.GetAccumulatorSubrectDimensions( &nDestTextureWidth, &nDestTextureHeight ); + + // Prepares the morph segments array + m_nMaxMorphTargetCount = 0; + int nMorphTargetCount = m_MorphDict.MorphCount(); + for ( int i = 0; i < nMorphTargetCount; ++i ) + { + if ( m_nMaxMorphTargetCount <= m_MorphDict.GetMorphTargetId(i) ) + { + m_nMaxMorphTargetCount = m_MorphDict.GetMorphTargetId(i) + 1; + } + } + + // Allocate space to cache off the morph weights when in the middle of performing morph accumulation + Assert( !m_pRenderMorphWeight ); + m_pRenderMorphWeight = new MorphWeight_t[ m_nMaxMorphTargetCount ]; + + Assert( m_nMaxMorphTargetCount < 1024 ); // This algorithm of storing a full array is bogus if this isn't true + m_MorphTargetIdToQuadIndex.SetCount( m_nMaxMorphTargetCount ); + memset( m_MorphTargetIdToQuadIndex.Base(), 0xFF, m_nMaxMorphTargetCount * sizeof(int) ); + + // Builds the segment list + int nSrcIndex = 0; + for ( int i = 0; i < nMorphTargetCount; ++i ) + { + int nMorphTargetId = m_MorphDict.GetMorphTargetId( i ); + m_MorphTargetIdToQuadIndex[nMorphTargetId] = i; + + int nSegmentIndex = morphSegments.AddToTail(); + MorphSegmentList_t &list = morphSegments[nSegmentIndex]; + Assert( nSegmentIndex == i ); + + MorphSegment_t segment; + segment.m_nCount = 0; + + int nVertexCount = m_MorphDict.GetMorphVertexCount( i ); + int nLastDestIndex = -1; + for ( int j = 0; j < nVertexCount; ++j ) + { + const MorphVertexInfo_t &info = m_MorphDict.GetMorphVertexInfo( i, j ); + + // Check for segment break conditions + if ( segment.m_nCount ) + { + // Vertical overflow, non-contiguous destination verts, or contiguous dest + // verts which happen to lie in different columns are the break conditions + if ( ( nLastDestIndex < 0 ) || ( info.m_nVertexId > nLastDestIndex + MIN_SEGMENT_GAP_SIZE ) || + ( info.m_nVertexId / nDestTextureHeight != nLastDestIndex / nDestTextureHeight ) ) + { + list.AddToTail( segment ); + nLastDestIndex = -1; + } + } + + // Start new segment, or append to existing segment + if ( nLastDestIndex < 0 ) + { + segment.m_nFirstSrc = nSrcIndex; + segment.m_nFirstDest = info.m_nVertexId; + segment.m_nCount = 1; + ++nSrcIndex; + } + else + { + int nSegmentCount = info.m_nVertexId - nLastDestIndex; + segment.m_nCount += nSegmentCount; + nSrcIndex += nSegmentCount; + } + nLastDestIndex = info.m_nVertexId; + } + + // Add any trailing segment + if ( segment.m_nCount ) + { + list.AddToTail( segment ); + } + } +} + + +//----------------------------------------------------------------------------- +// Builds the list of quads to render +//----------------------------------------------------------------------------- +void CMorph::BuildQuadList( const CUtlVector< MorphSegmentList_t > &morphSegments ) +{ + m_MorphQuads.RemoveAll(); + + int nQuadIndex = 0; + int nMorphCount = morphSegments.Count(); + for ( int i = 0; i < nMorphCount; ++i ) + { + int k = m_MorphQuads.AddToTail(); + MorphQuadList_t &quadList = m_MorphQuads[k]; + + const MorphSegmentList_t& segmentList = morphSegments[i]; + int nSegmentCount = segmentList.Count(); + for ( int j = 0; j < nSegmentCount; ++j ) + { + const MorphSegment_t &segment = segmentList[j]; + + int nSrc = segment.m_nFirstSrc; + int nDest = segment.m_nFirstDest; + int nTotalCount = segment.m_nCount; + + do + { + int sx = nSrc / m_nTextureHeight; + int sy = nSrc - sx * m_nTextureHeight; + + int nMaxCount = m_nTextureHeight - sy; + int nCount = min( nMaxCount, nTotalCount ); + nTotalCount -= nCount; + + int l = quadList.AddToTail(); + MorphQuad_t &quad = quadList[l]; + quad.m_nQuadIndex = nQuadIndex++; + quad.m_nCount = nCount; + quad.m_nFirstSrc = nSrc; + quad.m_nFirstDest = nDest; + + nSrc += nCount; + nDest += nCount; + } while ( nTotalCount > 0 ); + } + } +} + + +//----------------------------------------------------------------------------- +// Counts the total number of vertices to place in the static mesh +//----------------------------------------------------------------------------- +int CMorph::CountStaticMeshVertices() const +{ + // FIXME: I'm doing the simple thing here of 4 verts per segment. + // I believe I should be able to share any edge that isn't on the edges of the texture + // so I should be able to get down to nearly 2 (or is it 1?) verts per segment. + int nVertexCount = 0; + int nMorphCount = m_MorphQuads.Count(); + for ( int i = 0; i < nMorphCount; ++i ) + { + const MorphQuadList_t& quadList = m_MorphQuads[i]; + nVertexCount += quadList.Count() * 4; + } + + return nVertexCount; +} + +//----------------------------------------------------------------------------- +// Determines mesh vertex format +//----------------------------------------------------------------------------- +VertexFormat_t CMorph::ComputeVertexFormat( IMaterial * pMaterial ) const +{ + // We believe this material's vertex format is reliable (unlike many others as of June 07) + VertexFormat_t vertexFormat = pMaterial->GetVertexFormat(); + + // UNDONE: optimize the vertex format to compress or remove elements where possible + vertexFormat &= ~VERTEX_FORMAT_COMPRESSED; + + return vertexFormat; +} + +//----------------------------------------------------------------------------- +// Builds the list of segments to render +//----------------------------------------------------------------------------- +void CMorph::CreateStaticMesh() +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + m_MorphAccumulationMaterial->Refresh(); + VertexFormat_t vertexFormat = ComputeVertexFormat( m_MorphAccumulationMaterial ); + m_pMorphBuffer = pRenderContext->CreateStaticMesh( vertexFormat, TEXTURE_GROUP_MORPH_TARGETS, m_MorphAccumulationMaterial ); + + int nVertexCount = CountStaticMeshVertices(); + if ( nVertexCount >= 65535 ) + { + Warning( "Too many morph vertices! Call brian\n" ); + } + Assert( nVertexCount < 65535 ); + + int n4TupleCount = Get4TupleCount( m_Format ); + + float flOOTexWidth = 1.0f / ( n4TupleCount * m_nTextureWidth ); + float flOOTexHeight = 1.0f / m_nTextureHeight; + + int nDestTextureWidth, nDestTextureHeight; + s_MorphMgr.GetAccumulatorSubrectDimensions( &nDestTextureWidth, &nDestTextureHeight ); + float flOODestWidth = 1.0f / nDestTextureWidth; + float flOODestHeight = 1.0f / nDestTextureHeight; + + // NOTE: zero index count implies no index buffer + CMeshBuilder meshBuilder; + meshBuilder.Begin( m_pMorphBuffer, MATERIAL_TRIANGLES, nVertexCount, 0 ); + + int nMorphCount = m_MorphQuads.Count(); + for ( int i = 0; i < nMorphCount; ++i ) + { + MorphQuadList_t& quadList = m_MorphQuads[i]; + int nQuadCount = quadList.Count(); + for ( int j = 0; j < nQuadCount; ++j ) + { + MorphQuad_t &quad = quadList[j]; + + int sx = quad.m_nFirstSrc / m_nTextureHeight; + int sy = quad.m_nFirstSrc - sx * m_nTextureHeight; + int dx = quad.m_nFirstDest / nDestTextureHeight; + int dy = quad.m_nFirstDest - dx * nDestTextureHeight; + sx *= n4TupleCount; dx *= n4TupleCount; + + meshBuilder.TexCoord4f( 0, sx * flOOTexWidth, sy * flOOTexHeight, + ( dx - 0.5f ) * flOODestWidth, ( dy - 0.5f ) * flOODestHeight ); // Stores the source to read from + meshBuilder.TexCoord1f( 1, i ); + meshBuilder.AdvanceVertex(); + + meshBuilder.TexCoord4f( 0, sx * flOOTexWidth, ( sy + quad.m_nCount ) * flOOTexHeight, + ( dx - 0.5f ) * flOODestWidth, ( dy + quad.m_nCount - 0.5f ) * flOODestHeight ); // Stores the source to read from + meshBuilder.TexCoord1f( 1, i ); + meshBuilder.AdvanceVertex(); + + meshBuilder.TexCoord4f( 0, (sx + n4TupleCount) * flOOTexWidth, ( sy + quad.m_nCount ) * flOOTexHeight, + ( dx + n4TupleCount - 0.5f ) * flOODestWidth, ( dy + quad.m_nCount - 0.5f ) * flOODestHeight ); // Stores the source to read from + meshBuilder.TexCoord1f( 1, i ); + meshBuilder.AdvanceVertex(); + + meshBuilder.TexCoord4f( 0, (sx + n4TupleCount) * flOOTexWidth, sy * flOOTexHeight, + ( dx + n4TupleCount - 0.5f ) * flOODestWidth, ( dy - 0.5f ) * flOODestHeight ); // Stores the source to read from + meshBuilder.TexCoord1f( 1, i ); + meshBuilder.AdvanceVertex(); + } + } + + meshBuilder.End(); +} + + +//----------------------------------------------------------------------------- +// Inherited from ITextureRegenerator +//----------------------------------------------------------------------------- +void CMorph::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) +{ + Assert( pVTFTexture->FrameCount() == 1 && pVTFTexture->FaceCount() == 1 ); + Assert( pVTFTexture->Height() == m_nTextureHeight ); + + int nTextureType; + for ( nTextureType = 0; nTextureType < MORPH_TEXTURE_COUNT; ++nTextureType ) + { + if ( pTexture == m_pMorphTexture[nTextureType] ) + break; + } + Assert( nTextureType < MORPH_TEXTURE_COUNT ); + MorphPixelWriter_t pWriteFuncs[MORPH_TEXTURE_COUNT] = + { + &CMorph::WriteDeltaPositionNormalToTexture, + &CMorph::WriteSideSpeedToTexture, + }; + + MorphPixelWriter_t writeFunc = pWriteFuncs[nTextureType]; + + CPixelWriter pixelWriter; + pixelWriter.SetPixelMemory( pVTFTexture->Format(), pVTFTexture->ImageData(), pVTFTexture->RowSizeInBytes( 0 ) ); + + // Clear the buffer + MorphVertexInfo_t zeroDelta; + zeroDelta.m_PositionDelta.Init(); + zeroDelta.m_NormalDelta.Init(); + zeroDelta.m_flWrinkleDelta = 0.0f; + zeroDelta.m_flSpeed = 1.0f; + zeroDelta.m_flSide = 0.5f; + + int nWidth = pVTFTexture->Width() / Get4TupleCount( m_Format ); + int nHeight = pVTFTexture->Height(); + for ( int i = 0; i < nHeight; ++i ) + { + for ( int j = 0; j < nWidth; ++j ) + { + (this->*writeFunc)( pixelWriter, j, i, zeroDelta ); + } + } + + int nQuadListCount = m_MorphQuads.Count(); + for ( int i = 0; i < nQuadListCount; ++i ) + { + MorphQuadList_t &quadList = m_MorphQuads[i]; + int nQuadCount = quadList.Count(); + int nVertIndex = 0; + for ( int j = 0; j < nQuadCount; ++j ) + { + MorphQuad_t &quad = quadList[j]; + int sx = quad.m_nFirstSrc / m_nTextureHeight; + int sy = quad.m_nFirstSrc - sx * m_nTextureHeight; + int nDest = quad.m_nFirstDest; + for ( int k = 0; k < quad.m_nCount; ++k ) + { + const MorphVertexInfo_t &info = m_MorphDict.GetMorphVertexInfo( i, nVertIndex ); + if ( info.m_nVertexId > nDest ) + { + (this->*writeFunc)( pixelWriter, sx, sy+k, zeroDelta ); + } + else + { + (this->*writeFunc)( pixelWriter, sx, sy+k, info ); + ++nVertIndex; + } + ++nDest; + } + } + } +} + + +//----------------------------------------------------------------------------- +// Deals with morph stats +//----------------------------------------------------------------------------- +static ConVar mat_morphstats( "mat_morphstats", "0", FCVAR_CHEAT ); +static CUtlVector<int> s_ActiveMorphHisto; +static CUtlVector<int> s_RenderedQuadHisto; +static CUtlVector<int> s_RenderedTexelHisto; +static int s_nStatFrameCount = 0; +static int s_nStatMorphCount = 0; +static int s_nTotalMorphCount = 0; +static int s_nTotalQuadCount = 0; +static int s_nTotalTexelCount = 0; + +void CMorph::ClearMorphStats() +{ + s_ActiveMorphHisto.Purge(); + s_RenderedQuadHisto.Purge(); + s_RenderedTexelHisto.Purge(); + + s_nStatFrameCount = 0; + s_nTotalMorphCount = 0; + s_nTotalQuadCount = 0; + s_nTotalTexelCount = 0; +} + +void CMorph::AccumulateMorphStats( int nActiveMorphCount, int nQuadsRendered, int nTexelsRendered ) +{ + while ( nActiveMorphCount >= s_ActiveMorphHisto.Count() ) + { + s_ActiveMorphHisto.AddToTail( 0 ); + } + while ( nQuadsRendered >= s_RenderedQuadHisto.Count() ) + { + s_RenderedQuadHisto.AddToTail( 0 ); + } + while ( nTexelsRendered >= s_RenderedTexelHisto.Count() ) + { + s_RenderedTexelHisto.AddToTail( 0 ); + } + s_ActiveMorphHisto[nActiveMorphCount] += 1; + s_RenderedQuadHisto[nQuadsRendered] += 1; + s_RenderedTexelHisto[nTexelsRendered] += 1; + + s_nStatMorphCount++; + s_nTotalMorphCount += nActiveMorphCount; + s_nTotalQuadCount += nQuadsRendered; + s_nTotalTexelCount += nTexelsRendered; +} + +void CMorph::ReportMorphStats( ) +{ + Msg( "Morph stats:\n" ); + if ( s_nStatMorphCount == 0 ) + { + Msg( "\tNo morphing done\n" ); + return; + } + + Msg( "\tAverage # of active morph targets per mesh group: %d\n", s_nTotalMorphCount / s_nStatMorphCount ); + Msg( "\tAverage # of actual quad draws per morph: %d\n", s_nTotalQuadCount / s_nStatMorphCount ); + Msg( "\tAverage # of actual rendered texels per morph: %d\n", s_nTotalTexelCount / s_nStatMorphCount ); + + Msg( "\tRendered Quad Count Histogram :\n\t\t" ); + for ( int i = 0; i < s_RenderedQuadHisto.Count(); ++i ) + { + if ( s_RenderedQuadHisto[i] == 0 ) + continue; + Msg( "[%d : %d] ", i, s_RenderedQuadHisto[i] ); + } + Msg( "\n\tRendered Texel Count Histogram :\n\t\t" ); + for ( int i = 0; i < s_RenderedTexelHisto.Count(); ++i ) + { + if ( s_RenderedTexelHisto[i] == 0 ) + continue; + Msg( "[%d : %d] ", i, s_RenderedTexelHisto[i] ); + } + Msg( "\n\tActive morph target Count Histogram :\n\t\t" ); + for ( int i = 0; i < s_ActiveMorphHisto.Count(); ++i ) + { + if ( s_ActiveMorphHisto[i] == 0 ) + continue; + Msg( "[%d : %d] ", i, s_ActiveMorphHisto[i] ); + } + Msg( "\n" ); +} + +void CMorph::HandleMorphStats( int nActiveMorphCount, int nQuadsRendered, int nTexelsRendered ) +{ + static bool s_bLastMorphStats = false; + bool bDoStats = mat_morphstats.GetInt() != 0; + if ( bDoStats ) + { + if ( !s_bLastMorphStats ) + { + ClearMorphStats(); + } + AccumulateMorphStats( nActiveMorphCount, nQuadsRendered, nTexelsRendered ); + } + else + { + if ( s_bLastMorphStats ) + { + ReportMorphStats(); + ClearMorphStats(); + } + } + + s_bLastMorphStats = bDoStats; +} + + +//----------------------------------------------------------------------------- +// Renders to the morph accumulator texture +//----------------------------------------------------------------------------- +void CMorph::RenderMorphQuads( IMatRenderContext *pRenderContext, int nRenderId, int nTotalQuadCount, int nWeightCount, int *pWeightLookup, const MorphWeight_t* pWeights ) +{ + if ( s_MorphMgr.IsUsingConstantRegisters() ) + { + pRenderContext->SetFlexWeights( 0, m_nMaxMorphTargetCount, m_pRenderMorphWeight ); + } + else + { + BindMorphWeight( nRenderId ); + } + + int nXOffset, nYOffset, nWidth, nHeight; + s_MorphMgr.ComputeAccumulatorSubrect( &nXOffset, &nYOffset, &nWidth, &nHeight, nRenderId ); + pRenderContext->Viewport( nXOffset, nYOffset, nWidth, nHeight ); + + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( false, m_pMorphBuffer ); + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 0, nTotalQuadCount * 6 ); + +#ifdef REPORT_MORPH_STATS + int nTexelsRendered = 0; +#endif + + for ( int i = 0; i < nWeightCount; ++i ) + { + int nMorphIndex = m_MorphTargetIdToQuadIndex[ pWeightLookup[i] ]; + if ( nMorphIndex < 0 ) + continue; + + const MorphQuadList_t& quadList = m_MorphQuads[nMorphIndex]; + int nQuadCount = quadList.Count(); + for ( int j = 0; j < nQuadCount; ++j ) + { + const MorphQuad_t &quad = quadList[j]; + +#ifdef _DEBUG + static int s_nMinDest = -1, s_nMaxDest = -1; + if ( s_nMinDest >= 0 && quad.m_nFirstDest + quad.m_nCount <= s_nMinDest ) + continue; + if ( s_nMaxDest >= 0 && quad.m_nFirstDest > s_nMaxDest ) + continue; +#endif + +#ifdef REPORT_MORPH_STATS + nTexelsRendered += n4TupleCount * quad.m_nCount; +#endif + + int nBaseIndex = quad.m_nQuadIndex * 4; + meshBuilder.FastIndex( nBaseIndex ); + meshBuilder.FastIndex( nBaseIndex+1 ); + meshBuilder.FastIndex( nBaseIndex+2 ); + + meshBuilder.FastIndex( nBaseIndex ); + meshBuilder.FastIndex( nBaseIndex+2 ); + meshBuilder.FastIndex( nBaseIndex+3 ); + } + } + meshBuilder.End(); + pMesh->Draw(); + +#ifdef REPORT_MORPH_STATS + HandleMorphStats( nWeightCount, nTotalQuadCount, nTexelsRendered ); +#endif +} + + +//----------------------------------------------------------------------------- +// Should a morph weight be treated as zero +//----------------------------------------------------------------------------- +static inline bool IsMorphWeightZero( const MorphWeight_t &weight ) +{ + return ( FloatMakePositive( weight.m_pWeight[MORPH_WEIGHT] ) < 0.001 && + FloatMakePositive( weight.m_pWeight[MORPH_WEIGHT_LAGGED] ) < 0.001 && + FloatMakePositive( weight.m_pWeight[MORPH_WEIGHT_STEREO] ) < 0.001 && + FloatMakePositive( weight.m_pWeight[MORPH_WEIGHT_STEREO_LAGGED] ) < 0.001 ); +} + + +//----------------------------------------------------------------------------- +// Builds a list of non-zero morph targets +//----------------------------------------------------------------------------- +int CMorph::BuildNonZeroMorphList( int *pWeightIndices, int nWeightCount, const MorphWeight_t* pWeights ) +{ + int nWeightIndexCount = 0; + for ( int i = 0; i < m_nMaxMorphTargetCount; ++i ) + { + const MorphWeight_t& weight = pWeights[i]; + + // Don't bother with weights that aren't represented in the morph + if ( m_MorphTargetIdToQuadIndex[i] < 0 ) + continue; + + // Don't bother with small weights + if ( IsMorphWeightZero( weight ) ) + continue; + + pWeightIndices[nWeightIndexCount++] = i; + } + + return nWeightIndexCount; +} + + +//----------------------------------------------------------------------------- +// Renders to the morph weight texture +//----------------------------------------------------------------------------- +bool CMorph::RenderMorphWeights( IMatRenderContext *pRenderContext, int nRenderId, int nWeightCount, const MorphWeight_t* pWeights ) +{ + VPROF_BUDGET( "CMorph::RenderMorphWeights", _T("HW Morphing") ); + if ( m_nMaxMorphTargetCount == 0 ) + return false; + + // Cache off the weights, we need them when we accumulate the morphs later. + int nCountToCopy = min( nWeightCount, m_nMaxMorphTargetCount ); + memcpy( m_pRenderMorphWeight, pWeights, nCountToCopy * sizeof(MorphWeight_t) ); + int nCountToClear = m_nMaxMorphTargetCount - nWeightCount; + if ( nCountToClear > 0 ) + { + memset( &m_pRenderMorphWeight[nCountToCopy], 0, nCountToClear * sizeof(MorphWeight_t) ); + } + + int *pWeightIndices = (int*)_alloca( nCountToCopy * sizeof(int) ); + int nIndexCount = BuildNonZeroMorphList( pWeightIndices, nCountToCopy, pWeights ); + if ( nIndexCount == 0 ) + return false; + + if ( s_MorphMgr.IsUsingConstantRegisters() ) + return true; + + int x, y, w, h; + s_MorphMgr.ComputeWeightSubrect( &x, &y, &w, &h, nRenderId ); + + ITexture *pMorphWeightTexture = s_MorphMgr.MorphWeights(); + int nWidth = pMorphWeightTexture->GetActualWidth(); + int nHeight = pMorphWeightTexture->GetActualHeight(); + float flOOWidth = ( nWidth != 0 ) ? 1.0f / nWidth : 1.0f; + float flOOHeight = ( nHeight != 0 ) ? 1.0f / nHeight : 1.0f; + + // Render the weights into the morph weight texture + CMeshBuilder meshBuilder; + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + meshBuilder.Begin( pMesh, MATERIAL_POINTS, nCountToCopy ); + + for ( int i = 0; i < nIndexCount; ++i ) + { + int nMorphId = pWeightIndices[i]; + const MorphWeight_t& weight = pWeights[ nMorphId ]; + + int nLocalX = nMorphId / h; + int nLocalY = nMorphId - nLocalX * h; + meshBuilder.TexCoord2f( 0, ( nLocalX + x ) * flOOWidth, ( nLocalY + y ) * flOOHeight ); + meshBuilder.TexCoord4fv( 1, weight.m_pWeight ); + meshBuilder.AdvanceVertex(); + } + + meshBuilder.End(); + pMesh->Draw(); + + return true; +} + + +//----------------------------------------------------------------------------- +// This will generate an accumulated morph target based on the passed-in weights +//----------------------------------------------------------------------------- +void CMorph::AccumulateMorph( int nRenderId ) +{ + VPROF_BUDGET( "CMorph::AccumulateMorph", _T("HW Morphing") ); + + // Build a non-zero weight list and a total quad count + int *pTargets = (int*)_alloca( m_nMaxMorphTargetCount * sizeof(int) ); + int nTargetCount = BuildNonZeroMorphList( pTargets, m_nMaxMorphTargetCount, m_pRenderMorphWeight ); + + // Count the total number of quads to draw + int nTotalQuadCount = 0; + for ( int i = 0; i < nTargetCount; ++i ) + { + int nMorphIndex = m_MorphTargetIdToQuadIndex[ pTargets[i] ]; + if ( nMorphIndex < 0 ) + continue; + + const MorphQuadList_t& quadList = m_MorphQuads[ nMorphIndex ]; + nTotalQuadCount += quadList.Count(); + } + + // Clear the morph accumulator + // FIXME: Can I avoid even changing the render target if I know the last time + // the morph accumulator was used that it was also cleared to black? Yes, but + // I need to deal with alt-tab. + bool bRenderQuads = ( nTotalQuadCount != 0 ) && ( m_nTextureWidth != 0 ) && ( m_nTextureHeight != 0 ); + if ( !bRenderQuads ) + return; + + // Next, iterate over all non-zero morphs and add them in. + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( m_MorphAccumulationMaterial ); + RenderMorphQuads( pRenderContext, nRenderId, nTotalQuadCount, nTargetCount, pTargets, m_pRenderMorphWeight ); +} + + +//----------------------------------------------------------------------------- +// +// Morph mgr render context +// +//----------------------------------------------------------------------------- +CMorphMgrRenderContext::CMorphMgrRenderContext() +{ + m_nMorphCount = 0; + +#ifdef DBGFLAG_ASSERT + m_bInMorphAccumulation = false; +#endif +} + +int CMorphMgrRenderContext::GetRenderId( CMorph* pMorph ) +{ + // FIXME: This could be done without all these comparisons, at the cost of memory + complexity. + // NOTE: m_nMorphCount <= 4. + for ( int i = 0; i < m_nMorphCount; ++i ) + { + if ( m_pMorphsToAccumulate[i] == pMorph ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// +// Morph manager implementation starts here +// +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CMorphMgr::CMorphMgr() +{ + m_pMorphAccumTexture = NULL; + m_pMorphWeightTexture = NULL; + m_pVisualizeMorphAccum = NULL; + m_pVisualizeMorphWeight = NULL; + m_pRenderMorphWeight = NULL; + m_nFrameCount = 0; + m_nTotalMorphSizeInBytes = 0; + m_bUsingConstantRegisters = false; +} + + +//----------------------------------------------------------------------------- +// Should we allocate textures? +//----------------------------------------------------------------------------- +bool CMorphMgr::ShouldAllocateScratchTextures() +{ + return g_pMaterialSystemHardwareConfig->HasFastVertexTextures(); +} + + +//----------------------------------------------------------------------------- +// Allocates scratch textures used in hw morphing +//----------------------------------------------------------------------------- +void CMorphMgr::AllocateScratchTextures() +{ + // Debug using 32323232F because we can read that back reasonably. +#ifdef _DEBUG + ImageFormat fmt = IMAGE_FORMAT_RGBA32323232F; +#else + ImageFormat fmt = IMAGE_FORMAT_RGBA16161616F; +#endif + + // NOTE: I'm not writing code to compute an appropriate width and height + // given a MAX_MORPH_ACCUMULATOR_VERTICES and MORPH_ACCUMULATOR_4TUPLES + // because this will rarely change. Just hard code it to something that will fit it. + m_nAccumulatorWidth = 256; + m_nAccumulatorHeight = 256; + Assert( m_nAccumulatorWidth * m_nAccumulatorHeight == MAX_MORPH_ACCUMULATOR_VERTICES * MORPH_ACCUMULATOR_4TUPLES ); + + Assert( IsPowerOfTwo( CMorphMgrRenderContext::MAX_MODEL_MORPHS ) ); + int nMultFactor = sqrt( (float)CMorphMgrRenderContext::MAX_MODEL_MORPHS ); + m_nSubrectVerticalCount = nMultFactor; + + m_pMorphAccumTexture = TextureManager()->CreateRenderTargetTexture( "_rt_MorphAccumulator", + m_nAccumulatorWidth * nMultFactor, m_nAccumulatorHeight * nMultFactor, + RT_SIZE_OFFSCREEN, fmt, RENDER_TARGET_NO_DEPTH, + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_NODEBUGOVERRIDE | + TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE | TEXTUREFLAGS_VERTEXTEXTURE, + 0 ); + m_pMorphAccumTexture->IncrementReferenceCount(); + + int nDim = (int)sqrt( (float)MAXSTUDIOFLEXDESC ); + while( nDim * nDim < MAXSTUDIOFLEXDESC ) + { + ++nDim; + } + + m_nWeightWidth = m_nWeightHeight = nDim; + + // FIXME: Re-enable if NVidia gets a fast implementation using more shader constants + m_bUsingConstantRegisters = false; //( g_pMaterialSystemHardwareConfig->NumVertexShaderConstants() >= VERTEX_SHADER_FLEX_WEIGHTS + VERTEX_SHADER_MAX_FLEX_WEIGHT_COUNT ); + + if ( !m_bUsingConstantRegisters ) + { + m_pMorphWeightTexture = TextureManager()->CreateRenderTargetTexture( "_rt_MorphWeight", + m_nWeightWidth * nMultFactor, m_nWeightHeight * nMultFactor, + RT_SIZE_OFFSCREEN, fmt, RENDER_TARGET_NO_DEPTH, + TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD | TEXTUREFLAGS_NODEBUGOVERRIDE | + TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_POINTSAMPLE, + 0 ); + m_pMorphWeightTexture->IncrementReferenceCount(); + } +} + +void CMorphMgr::FreeScratchTextures() +{ + if ( m_pMorphAccumTexture ) + { + m_pMorphAccumTexture->DecrementReferenceCount(); + m_pMorphAccumTexture->DeleteIfUnreferenced(); + m_pMorphAccumTexture = NULL; + } + + if ( m_pMorphWeightTexture ) + { + m_pMorphWeightTexture->DecrementReferenceCount(); + m_pMorphWeightTexture->DeleteIfUnreferenced(); + m_pMorphWeightTexture = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Allocates, frees materials used in hw morphing +//----------------------------------------------------------------------------- +void CMorphMgr::AllocateMaterials() +{ + KeyValues *pVMTKeyValues = new KeyValues( "debugmorphaccumulator" ); + pVMTKeyValues->SetString( "$basetexture", "_rt_MorphAccumulator" ); + pVMTKeyValues->SetString( "$nocull", "1" ); + pVMTKeyValues->SetString( "$ignorez", "1" ); + m_pVisualizeMorphAccum = g_pMaterialSystem->CreateMaterial( "___visualizeMorphAccum.vmt", pVMTKeyValues ); + + if ( !m_bUsingConstantRegisters ) + { + pVMTKeyValues = new KeyValues( "morphweight" ); + pVMTKeyValues->SetString( "$model", "0" ); + pVMTKeyValues->SetString( "$nocull", "1" ); + pVMTKeyValues->SetString( "$ignorez", "1" ); + m_pRenderMorphWeight = g_pMaterialSystem->CreateMaterial( "___morphweight.vmt", pVMTKeyValues ); + + pVMTKeyValues = new KeyValues( "debugmorphaccumulator" ); + pVMTKeyValues->SetString( "$basetexture", "_rt_MorphWeight" ); + pVMTKeyValues->SetString( "$nocull", "1" ); + pVMTKeyValues->SetString( "$ignorez", "1" ); + m_pVisualizeMorphWeight = g_pMaterialSystem->CreateMaterial( "___visualizeMorphWeight.vmt", pVMTKeyValues ); + } +} + + +void CMorphMgr::FreeMaterials() +{ + if ( m_pVisualizeMorphAccum ) + { + m_pVisualizeMorphAccum->DecrementReferenceCount(); + m_pVisualizeMorphAccum->DeleteIfUnreferenced(); + m_pVisualizeMorphAccum = NULL; + } + + if ( m_pVisualizeMorphWeight ) + { + m_pVisualizeMorphWeight->DecrementReferenceCount(); + m_pVisualizeMorphWeight->DeleteIfUnreferenced(); + m_pVisualizeMorphWeight = NULL; + } + + if ( m_pRenderMorphWeight ) + { + m_pRenderMorphWeight->DecrementReferenceCount(); + m_pRenderMorphWeight->DeleteIfUnreferenced(); + m_pRenderMorphWeight = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Morph render context +//----------------------------------------------------------------------------- +IMorphMgrRenderContext *CMorphMgr::AllocateRenderContext() +{ + return new CMorphMgrRenderContext; +} + +void CMorphMgr::FreeRenderContext( IMorphMgrRenderContext *pRenderContext ) +{ + delete static_cast< CMorphMgrRenderContext* >( pRenderContext ); +} + + +//----------------------------------------------------------------------------- +// Returns the morph accumulation texture +//----------------------------------------------------------------------------- +ITextureInternal *CMorphMgr::MorphAccumulator() +{ + return m_pMorphAccumTexture; +} + +ITextureInternal *CMorphMgr::MorphWeights() +{ + return m_pMorphWeightTexture; +} + + +//----------------------------------------------------------------------------- +// Class factory +//----------------------------------------------------------------------------- +IMorphInternal *CMorphMgr::CreateMorph() +{ + return new CMorph; +} + +void CMorphMgr::DestroyMorph( IMorphInternal *pMorphData ) +{ + if ( pMorphData ) + { + delete static_cast< CMorph*>( pMorphData ); + } +} + + +//----------------------------------------------------------------------------- +// Advances the frame (for debugging) +//----------------------------------------------------------------------------- +void CMorphMgr::AdvanceFrame() +{ + ++m_nFrameCount; +} + + +//----------------------------------------------------------------------------- +// Computes texel offsets for the upper corner of the morph weight texture for a particular block +//----------------------------------------------------------------------------- +void CMorphMgr::ComputeWeightSubrect( int *pXOffset, int *pYOffset, int *pWidth, int *pHeight, int nMorphAccumBlockId ) +{ + *pXOffset = nMorphAccumBlockId / m_nSubrectVerticalCount; + *pYOffset = nMorphAccumBlockId - m_nSubrectVerticalCount * (*pXOffset); + *pXOffset *= m_nWeightWidth; + *pYOffset *= m_nWeightHeight; + *pWidth = m_nWeightWidth; + *pHeight = m_nWeightHeight; +} + + +//----------------------------------------------------------------------------- +// Computes texel offsets for the upper corner of the morph accumulator for a particular block +//----------------------------------------------------------------------------- +void CMorphMgr::ComputeAccumulatorSubrect( int *pXOffset, int *pYOffset, int *pWidth, int *pHeight, int nMorphAccumBlockId ) +{ + *pXOffset = nMorphAccumBlockId / m_nSubrectVerticalCount; + *pYOffset = nMorphAccumBlockId - m_nSubrectVerticalCount * (*pXOffset); + *pXOffset *= m_nAccumulatorWidth; + *pYOffset *= m_nAccumulatorHeight; + *pWidth = m_nAccumulatorWidth; + *pHeight = m_nAccumulatorHeight; +} + +void CMorphMgr::GetAccumulatorSubrectDimensions( int *pWidth, int *pHeight ) +{ + *pWidth = m_nAccumulatorWidth; + *pHeight = m_nAccumulatorHeight; +} + +int CMorphMgr::GetAccumulator4TupleCount() const +{ + return MORPH_ACCUMULATOR_4TUPLES; +} + + +//----------------------------------------------------------------------------- +// Used to compute stats of memory used +//----------------------------------------------------------------------------- +CON_COMMAND_F( mat_reporthwmorphmemory, "Reports the amount of size in bytes taken up by hardware morph textures.", FCVAR_CHEAT ) +{ + ConMsg( "Total HW Morph memory used: %dk\n", s_MorphMgr.GetTotalMemoryUsage() /1024 ); +} + +void CMorphMgr::RegisterMorphSizeInBytes( int nSizeInBytes ) +{ + m_nTotalMorphSizeInBytes += nSizeInBytes; + Assert( m_nTotalMorphSizeInBytes >= 0 ); +} + +int CMorphMgr::GetTotalMemoryUsage() const +{ + int nSize = 0; + if ( m_pMorphAccumTexture ) + { + nSize += m_pMorphAccumTexture->GetActualWidth() * m_pMorphAccumTexture->GetActualHeight() * + ImageLoader::SizeInBytes( m_pMorphAccumTexture->GetImageFormat() ); + } + if ( m_pMorphWeightTexture ) + { + nSize += m_pMorphWeightTexture->GetActualWidth() * m_pMorphWeightTexture->GetActualHeight() * + ImageLoader::SizeInBytes( m_pMorphWeightTexture->GetImageFormat() ); + } + nSize += m_nTotalMorphSizeInBytes; + return nSize; +} + + +//----------------------------------------------------------------------------- +// Displays 32bit float texture data +//----------------------------------------------------------------------------- +void CMorphMgr::Display32FTextureData( float *pBuf, int nTexelID, int *pSubRect, ITexture *pTexture, int n4TupleCount ) +{ + int nColumn = nTexelID / pSubRect[3]; + int nRow = nTexelID - nColumn * pSubRect[3]; + nColumn *= n4TupleCount; + nColumn += pSubRect[0]; + nRow += pSubRect[1]; + + Msg( "[%d] : ", nTexelID ); + for ( int i = 0; i < n4TupleCount; ++i ) + { + float *pBase = &pBuf[ (nRow * pTexture->GetActualWidth() + nColumn + i ) * 4 ]; + Msg( "[ %.4f %.4f %.4f %.4f ] ", pBase[0], pBase[1], pBase[2], pBase[3] ); + } + Msg( "\n" ); +} + + +//----------------------------------------------------------------------------- +// A debugging utility to display the morph accumulator +//----------------------------------------------------------------------------- +void CMorphMgr::DebugMorphAccumulator( IMatRenderContext *pRenderContext ) +{ + static bool s_bDebug = false; + if ( !s_bDebug ) + return; + + ITexture *pDest = g_pMorphMgr->MorphAccumulator( ); + if ( pDest->GetImageFormat() != IMAGE_FORMAT_RGBA32323232F ) + return; + + int nDestWidth = pDest->GetActualWidth(); + int nDestHeight = pDest->GetActualHeight(); + + float* pBuf = (float*)malloc( nDestWidth * nDestHeight * 4 * sizeof(float) ); + pRenderContext->ReadPixels( 0, 0, nDestWidth, nDestHeight, (unsigned char*)pBuf, IMAGE_FORMAT_RGBA32323232F ); + + Msg( "Morph Accumulator:\n" ); + + static int s_nMinDisplay = 0; + static int s_nMaxDisplay = -1; + static int s_nMorphIndex = 0; + + int pSubRect[4]; + ComputeAccumulatorSubrect( &pSubRect[0], &pSubRect[1], &pSubRect[2], &pSubRect[3], s_nMorphIndex ); + + if ( s_nMaxDisplay < 0 ) + { + Display32FTextureData( pBuf, s_nMinDisplay, pSubRect, pDest, MORPH_ACCUMULATOR_4TUPLES ); + } + else + { + for ( int i = s_nMinDisplay; i <= s_nMaxDisplay; ++i ) + { + Display32FTextureData( pBuf, i, pSubRect, pDest, MORPH_ACCUMULATOR_4TUPLES ); + } + } + free( pBuf ); +} + + +//----------------------------------------------------------------------------- +// A debugging utility to display the morph weights +//----------------------------------------------------------------------------- +void CMorphMgr::DebugMorphWeights( IMatRenderContext *pRenderContext ) +{ + static bool s_bDebug = false; + if ( !s_bDebug ) + return; + + ITexture *pTexture = MorphWeights(); + int nWidth = pTexture->GetActualWidth(); + int nHeight = pTexture->GetActualHeight(); + if ( pTexture->GetImageFormat() != IMAGE_FORMAT_RGBA32323232F ) + return; + + pRenderContext->Flush(); + float* pBuf = (float*)malloc( nWidth * nHeight * 4 * sizeof(float) ); + pRenderContext->ReadPixels( 0, 0, nWidth, nHeight, (unsigned char*)pBuf, IMAGE_FORMAT_RGBA32323232F ); + + Msg( "Morph Weights:\n" ); + + static int s_nMinDisplay = 0; + static int s_nMaxDisplay = -1; + static int s_nMorphIndex = 0; + + int pSubRect[4]; + ComputeWeightSubrect( &pSubRect[0], &pSubRect[1], &pSubRect[2], &pSubRect[3], s_nMorphIndex ); + + if ( s_nMaxDisplay < 0 ) + { + Display32FTextureData( pBuf, s_nMinDisplay, pSubRect, pTexture, 1 ); + } + else + { + for ( int i = s_nMinDisplay; i <= s_nMaxDisplay; ++i ) + { + Display32FTextureData( pBuf, i, pSubRect, pTexture, 1 ); + } + } + free( pBuf ); +} + + +//----------------------------------------------------------------------------- +// Draws the morph accumulator +//----------------------------------------------------------------------------- +#ifdef _DEBUG +ConVar mat_drawmorphaccumulator( "mat_drawmorphaccumulator", "0", FCVAR_CHEAT ); +ConVar mat_drawmorphweights( "mat_drawmorphweights", "0", FCVAR_CHEAT ); +#endif + +void CMorphMgr::DrawMorphTempTexture( IMatRenderContext *pRenderContext, IMaterial *pMaterial, ITexture *pTexture ) +{ + pMaterial = ((IMaterialInternal *)pMaterial)->GetRealTimeVersion(); //always work with the real time version of materials internally. + + static int s_nLastFrameCount = -1; + static int s_nX = 0, s_nY = 0; + if ( s_nLastFrameCount != m_nFrameCount ) + { + s_nX = 0; s_nY = 0; + s_nLastFrameCount = m_nFrameCount; + } + + pRenderContext->Flush(); + + int nWidth = pTexture->GetActualWidth(); + int nHeight = pTexture->GetActualHeight(); + ::DrawScreenSpaceRectangle( pMaterial, s_nX, s_nY, nWidth, nHeight, + 0, 0, nWidth-1, nHeight-1, nWidth, nHeight ); + + s_nX += nWidth; + if ( s_nX > 1024 ) + { + s_nX = 0; + s_nY += nHeight; + } + pRenderContext->Flush(); +} + + +//----------------------------------------------------------------------------- +// Starts, ends morph accumulation. +//----------------------------------------------------------------------------- +int CMorphMgr::MaxHWMorphBatchCount() const +{ + return CMorphMgrRenderContext::MAX_MODEL_MORPHS; +} + + +//----------------------------------------------------------------------------- +// Returns the texcoord associated with a morph +//----------------------------------------------------------------------------- +bool CMorphMgr::GetMorphAccumulatorTexCoord( IMorphMgrRenderContext *pRenderContext, Vector2D *pTexCoord, IMorph *pMorph, int nVertex ) +{ + CMorphMgrRenderContext *pMorphRenderContext = static_cast< CMorphMgrRenderContext* >( pRenderContext ); + int nRenderId = pMorphRenderContext->GetRenderId( static_cast<CMorph*>( pMorph ) ); + if ( nRenderId < 0 ) + { + pTexCoord->Init(); + return false; + } + + int nWidth = m_pMorphAccumTexture->GetActualWidth(); + int nHeight = m_pMorphAccumTexture->GetActualHeight(); + if ( !nWidth || !nHeight ) + { + pTexCoord->Init(); + return false; + } + + float flOOWidth = ( nWidth != 0 ) ? 1.0f / nWidth : 1.0f; + float flOOHeight = ( nHeight != 0 ) ? 1.0f / nHeight : 1.0f; + + int x, y, w, h; + ComputeAccumulatorSubrect( &x, &y, &w, &h, nRenderId ); + int nColumn = nVertex / h; + int nRow = nVertex - h * nColumn; + nColumn *= MORPH_ACCUMULATOR_4TUPLES; + + pTexCoord->x = ( x + nColumn + 0.5f ) * flOOWidth; + pTexCoord->y = ( y + nRow + 0.5f ) * flOOHeight; + Assert( IsFinite( pTexCoord->x ) && IsFinite( pTexCoord->y ) ); + return true; +} + + +//----------------------------------------------------------------------------- +// Starts, ends morph accumulation. +//----------------------------------------------------------------------------- +void CMorphMgr::BeginMorphAccumulation( IMorphMgrRenderContext *pIRenderContext ) +{ + VPROF_BUDGET( "CMorph::BeginMorphAccumulation", _T("HW Morphing") ); + + // Set up the render context + CMorphMgrRenderContext *pMorphRenderContext = static_cast< CMorphMgrRenderContext* >( pIRenderContext ); + Assert( !pMorphRenderContext->m_bInMorphAccumulation ); + pMorphRenderContext->m_nMorphCount = 0; + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + IMatRenderContextInternal *pRenderContextInternal = static_cast<IMatRenderContextInternal*>( (IMatRenderContext*)pRenderContext ); + + // Cache off the current material and other render state + // NOTE: We always have to do this because pushing the morph accumulator + // may cause it to be unbound; therefore we must force a rebind of the material. + m_pPrevMaterial = pRenderContextInternal->GetCurrentMaterial(); + m_pPrevProxy = pRenderContextInternal->GetCurrentProxy(); + m_nPrevBoneCount = pRenderContextInternal->GetCurrentNumBones(); + m_nPrevClipMode = pRenderContext->GetHeightClipMode( ); + m_bPrevClippingEnabled = pRenderContext->EnableClipping( false ); + m_bFlashlightMode = pRenderContext->GetFlashlightMode(); + pRenderContext->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_DISABLE ); + pRenderContext->SetNumBoneWeights( 0 ); + pRenderContext->SetFlashlightMode( false ); + + if ( !m_bUsingConstantRegisters ) + { + // FIXME: We could theoretically avoid pushing this if we copied off all the + // weights and set the weight texture only at the end if any non-zero weights + // were sent down + pRenderContext->PushRenderTargetAndViewport( m_pMorphWeightTexture ); + +#ifdef _DEBUG + // NOTE: No need to clear the texture; we will only be reading out of that + // texture at points where we've rendered to in this pass. + // But, we'll do it for debugging reasons. + // I believe this pattern of weights is the least likely to occur naturally. + pRenderContext->ClearColor4ub( 0, 0, 0, 0 ); + pRenderContext->ClearBuffers( true, false, false ); +#endif + } + +#ifdef DBGFLAG_ASSERT + pMorphRenderContext->m_bInMorphAccumulation = true; +#endif +} + +void CMorphMgr::EndMorphAccumulation( IMorphMgrRenderContext *pIRenderContext ) +{ + VPROF_BUDGET( "CMorph::EndMorphAccumulation", _T("HW Morphing") ); + + CMorphMgrRenderContext *pMorphRenderContext = static_cast< CMorphMgrRenderContext* >( pIRenderContext ); + Assert( pMorphRenderContext->m_bInMorphAccumulation ); + VPROF_INCREMENT_COUNTER( "HW Morph Count", pMorphRenderContext->m_nMorphCount ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + +#ifdef _DEBUG + if ( !m_bUsingConstantRegisters ) + { + DebugMorphWeights( pRenderContext ); + } +#endif + + // Now that all the weights have been rendered, accumulate the morphs + // First, clear the morph accumulation texture + int nWidth = m_pMorphAccumTexture->GetActualWidth(); + int nHeight = m_pMorphAccumTexture->GetActualHeight(); + if ( !m_bUsingConstantRegisters ) + { + pRenderContext->SetRenderTargetEx( 0, m_pMorphAccumTexture ); + pRenderContext->Viewport( 0, 0, nWidth, nHeight ); + } + else + { + pRenderContext->PushRenderTargetAndViewport( m_pMorphAccumTexture ); + } + pRenderContext->ClearColor4ub( 0, 0, 0, 0 ); + pRenderContext->ClearBuffers( true, false, false ); + + for ( int i = 0; i < pMorphRenderContext->m_nMorphCount; ++i ) + { + pMorphRenderContext->m_pMorphsToAccumulate[i]->AccumulateMorph( i ); + } +#ifdef _DEBUG + DebugMorphAccumulator( pRenderContext ); +#endif + pRenderContext->PopRenderTargetAndViewport(); + +#ifdef _DEBUG + if ( mat_drawmorphweights.GetInt() ) + { + if ( !m_bUsingConstantRegisters ) + { + DrawMorphTempTexture( pRenderContext, m_pVisualizeMorphWeight, MorphWeights( ) ); + } + } + if ( mat_drawmorphaccumulator.GetInt() ) + { + DrawMorphTempTexture( pRenderContext, m_pVisualizeMorphAccum, MorphAccumulator( ) ); + } +#endif + + pRenderContext->Bind( m_pPrevMaterial, m_pPrevProxy ); + pRenderContext->SetNumBoneWeights( m_nPrevBoneCount ); + pRenderContext->SetHeightClipMode( m_nPrevClipMode ); + pRenderContext->EnableClipping( m_bPrevClippingEnabled ); + pRenderContext->SetFlashlightMode( m_bFlashlightMode ); + +#ifdef DBGFLAG_ASSERT + pMorphRenderContext->m_bInMorphAccumulation = false; +#endif +} + + +//----------------------------------------------------------------------------- +// Accumulates a morph target into the morph texture +//----------------------------------------------------------------------------- +void CMorphMgr::AccumulateMorph( IMorphMgrRenderContext *pIRenderContext, IMorph* pMorph, int nMorphCount, const MorphWeight_t* pWeights ) +{ + CMorphMgrRenderContext *pMorphRenderContext = static_cast< CMorphMgrRenderContext* >( pIRenderContext ); + Assert( pMorphRenderContext->m_bInMorphAccumulation ); + + Assert( pMorphRenderContext->m_nMorphCount < CMorphMgrRenderContext::MAX_MODEL_MORPHS ); + if ( pMorphRenderContext->m_nMorphCount >= CMorphMgrRenderContext::MAX_MODEL_MORPHS ) + { + Warning( "Attempted to morph too many meshes in a single model!\n" ); + Assert(0); + return; + } + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + CMorph *pMorphInternal = static_cast<CMorph*>( pMorph ); + if ( !m_bUsingConstantRegisters ) + { + pRenderContext->Bind( m_pRenderMorphWeight ); + } + if ( pMorphInternal->RenderMorphWeights( pRenderContext, pMorphRenderContext->m_nMorphCount, nMorphCount, pWeights ) ) + { + pMorphRenderContext->m_pMorphsToAccumulate[pMorphRenderContext->m_nMorphCount] = pMorphInternal; + ++pMorphRenderContext->m_nMorphCount; + } +} + |