//===== Copyright © 1996-2005, 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 bool 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 { 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 //----------------------------------------------------------------------------- bool CMorph::Bind( IMorphMgrRenderContext *pIRenderContext ) { CMorphMgrRenderContext *pMorphRenderContext = static_cast< CMorphMgrRenderContext* >( pIRenderContext ); int nRenderId = pMorphRenderContext->GetRenderId( this ); if ( nRenderId < 0 ) return false; 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 ); return true; } 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.String() ); #else Msg( "Morph :\n" ); #endif int nMorphCount = m_MorphQuads.Count(); Msg( "\tMorph Target Count : %d\n", nMorphCount ); int nTotalQuadCount = 0; int nTotalVertexCount = 0; CUtlVector quadHisto; CUtlVector vertexHisto; CUtlVector 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( 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( 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 s_ActiveMorphHisto; static CUtlVector s_RenderedQuadHisto; static CUtlVector 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 += Get4TupleCount( m_Format ) * 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*)stackalloc( 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*)stackalloc( 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->ActualHasFastVertexTextures() && !IsOpenGL(); } //----------------------------------------------------------------------------- // Allocates scratch textures used in hw morphing //----------------------------------------------------------------------------- void CMorphMgr::AllocateScratchTextures() { // Debug using 32323232F because we can read that back reasonably. #if defined(_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, false ); 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, false ); 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; 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( 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( (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( 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; } }