//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //===========================================================================// #define DISABLE_PROTECTED_THINGS #include "locald3dtypes.h" #include "imeshdx8.h" #include "shaderapidx8_global.h" #include "materialsystem/IShader.h" #include "tier0/vprof.h" #include "studio.h" #include "tier1/fmtstr.h" #include "tier0/platform.h" #include "tier0/systeminformation.h" // fixme - stick this in a header file. #if defined( _DEBUG ) && !defined( _X360 ) // define this if you want to range check all indices when drawing #define CHECK_INDICES #endif #ifdef CHECK_INDICES #define CHECK_INDICES_MAX_NUM_STREAMS 2 #endif #include "dynamicib.h" #include "dynamicvb.h" #include "utlvector.h" #include "shaderapi/ishaderapi.h" #include "imaterialinternal.h" #include "imaterialsysteminternal.h" #include "shaderapidx8.h" #include "shaderapi/ishaderutil.h" #include "materialsystem/imaterialsystemhardwareconfig.h" #include "materialsystem/materialsystem_config.h" #include "materialsystem/ivballoctracker.h" #include "tier1/strtools.h" #include "convar.h" #include "shaderdevicedx8.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //----------------------------------------------------------------------------- // Uncomment this to test buffered state //----------------------------------------------------------------------------- //#define DEBUG_BUFFERED_MESHES 1 #define MAX_DX8_STREAMS 16 #define VERTEX_FORMAT_INVALID 0xFFFFFFFFFFFFFFFFull // this is hooked into the engines convar extern ConVar mat_debugalttab; //#define DRAW_SELECTION 1 static bool g_bDrawSelection = true; // only used in DRAW_SELECTION static unsigned short g_nScratchIndexBuffer[6]; // large enough for a fast quad; used when device is not active #ifdef _DEBUG int CVertexBuffer::s_BufferCount = 0; int CIndexBuffer::s_BufferCount = 0; #endif //----------------------------------------------------------------------------- // Important enumerations //----------------------------------------------------------------------------- enum { VERTEX_BUFFER_SIZE = 32768, MAX_QUAD_INDICES = 16384, }; //----------------------------------------------------------------------------- // // Code related to vertex buffers start here // //----------------------------------------------------------------------------- class CVertexBufferDx8 : public CVertexBufferBase { typedef CVertexBufferBase BaseClass; // Methods of IVertexBuffer public: virtual int VertexCount() const; virtual VertexFormat_t GetVertexFormat() const; virtual bool IsDynamic() const; virtual void BeginCastBuffer( VertexFormat_t format ); virtual void EndCastBuffer( ); virtual int GetRoomRemaining() const; virtual bool Lock( int nVertexCount, bool bAppend, VertexDesc_t &desc ); virtual void Unlock( int nVertexCount, VertexDesc_t &desc ); public: // constructor CVertexBufferDx8( ShaderBufferType_t type, VertexFormat_t fmt, int nVertexCount, const char *pBudgetGroupName ); virtual ~CVertexBufferDx8(); // Allocates, deallocates the index buffer bool Allocate( ); void Free(); // Returns the vertex size int VertexSize() const; // Only used by dynamic buffers, indicates the next lock should perform a discard. void Flush(); // Returns the D3D buffer IDirect3DVertexBuffer9* GetDx9Buffer(); // Used to measure how much static buffer memory is touched each frame void HandlePerFrameTextureStats( int nFrame ); protected: IDirect3DVertexBuffer9 *m_pVertexBuffer; VertexFormat_t m_VertexFormat; int m_nVertexCount; int m_nBufferSize; int m_nFirstUnwrittenOffset; // Used only for dynamic buffers, indicates where it's safe to write (nooverwrite) // Is it locked? bool m_bIsLocked : 1; bool m_bIsDynamic : 1; bool m_bFlush : 1; // Used only for dynamic buffers, indicates to discard the next time #ifdef VPROF_ENABLED int m_nVProfFrame; int *m_pFrameCounter; int *m_pGlobalCounter; #endif #ifdef _DEBUG static int s_nBufferCount; #endif }; //----------------------------------------------------------------------------- // // Code related to index buffers start here // //----------------------------------------------------------------------------- class CIndexBufferDx8 : public CIndexBufferBase { typedef CIndexBufferBase BaseClass; // Methods of IIndexBuffer public: virtual int IndexCount( ) const; virtual MaterialIndexFormat_t IndexFormat() const; virtual int GetRoomRemaining() const; virtual bool Lock( int nIndexCount, bool bAppend, IndexDesc_t &desc ); virtual void Unlock( int nIndexCount, IndexDesc_t &desc ); virtual void BeginCastBuffer( MaterialIndexFormat_t format ); virtual void EndCastBuffer( ); virtual bool IsDynamic() const; virtual void ModifyBegin( bool bReadOnly, int nFirstIndex, int nIndexCount, IndexDesc_t& desc ) { Assert(0); } virtual void ModifyEnd( IndexDesc_t& desc ) { Assert(0); } public: // constructor CIndexBufferDx8( ShaderBufferType_t bufferType, MaterialIndexFormat_t fmt, int nIndexCount, const char *pBudgetGroupName ); virtual ~CIndexBufferDx8(); // Allocates, deallocates the index buffer bool Allocate( ); void Free(); // Returns the index size int IndexSize() const; // Only used by dynamic buffers, indicates the next lock should perform a discard. void Flush(); // Returns the D3D buffer IDirect3DIndexBuffer9* GetDx9Buffer(); // Used to measure how much static buffer memory is touched each frame void HandlePerFrameTextureStats( int nFrame ); #ifdef CHECK_INDICES unsigned short GetShadowIndex( int i ) const; #endif private: IDirect3DIndexBuffer9 *m_pIndexBuffer; MaterialIndexFormat_t m_IndexFormat; int m_nIndexCount; int m_nBufferSize; int m_nFirstUnwrittenOffset; // Used only for dynamic buffers, indicates where it's safe to write (nooverwrite) // Is it locked? bool m_bIsLocked : 1; bool m_bIsDynamic : 1; bool m_bFlush : 1; // Used only for dynamic buffers, indicates to discard the next time #ifdef CHECK_INDICES unsigned char *m_pShadowIndices; void *m_pLockIndexBuffer; int m_nLockIndexBufferSize; int m_nLockIndexOffset; #endif #ifdef VPROF_ENABLED int m_nVProfFrame; #endif #ifdef _DEBUG static int s_nBufferCount; #endif }; //----------------------------------------------------------------------------- // // Backward compat mesh code; will go away soon // //----------------------------------------------------------------------------- abstract_class CBaseMeshDX8 : public CMeshBase { public: // constructor, destructor CBaseMeshDX8(); virtual ~CBaseMeshDX8(); // FIXME: Make this work! Unsupported methods of IIndexBuffer + IVertexBuffer virtual bool Lock( int nMaxIndexCount, bool bAppend, IndexDesc_t& desc ) { Assert(0); return false; } virtual void Unlock( int nWrittenIndexCount, IndexDesc_t& desc ) { Assert(0); } virtual void ModifyBegin( bool bReadOnly, int nFirstIndex, int nIndexCount, IndexDesc_t& desc ) { Assert(0); } virtual void ModifyEnd( IndexDesc_t& desc ) { Assert(0); } virtual void Spew( int nIndexCount, const IndexDesc_t & desc ) { Assert(0); } virtual void ValidateData( int nIndexCount, const IndexDesc_t &desc ) { Assert(0); } virtual bool Lock( int nVertexCount, bool bAppend, VertexDesc_t &desc ) { Assert(0); return false; } virtual void Unlock( int nVertexCount, VertexDesc_t &desc ) { Assert(0); } virtual void Spew( int nVertexCount, const VertexDesc_t &desc ) { Assert(0); } virtual void ValidateData( int nVertexCount, const VertexDesc_t & desc ) { Assert(0); } // Locks mesh for modifying void ModifyBeginEx( bool bReadOnly, int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ); void ModifyBegin( int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ); void ModifyEnd( MeshDesc_t& desc ); // Sets/gets the vertex format virtual void SetVertexFormat( VertexFormat_t format ); virtual VertexFormat_t GetVertexFormat() const; // Sets/gets the morph format virtual void SetMorphFormat( MorphFormat_t format ); virtual MorphFormat_t GetMorphFormat() const; // Am I using morph data? virtual bool IsUsingMorphData() const; bool IsUsingVertexID() const { return ShaderAPI()->GetBoundMaterial()->IsUsingVertexID(); } // Sets the material virtual void SetMaterial( IMaterial* pMaterial ); // returns the # of vertices (static meshes only) int VertexCount() const { return 0; } void SetColorMesh( IMesh *pColorMesh, int nVertexOffsetInBytes ) { Assert( 0 ); } void SetFlexMesh( IMesh *pMesh, int nVertexOffsetInBytes ) { Assert( pMesh == NULL && nVertexOffsetInBytes == 0 ); } void DisableFlexMesh( ) { Assert( 0 ); } void MarkAsDrawn() {} bool HasColorMesh( ) const { return false; } bool HasFlexMesh( ) const { return false; } // Draws the mesh void DrawMesh( ); // Begins a pass void BeginPass( ); // Spews the mesh data virtual void Spew( int nVertexCount, int nIndexCount, const MeshDesc_t & desc ); // Call this in debug mode to make sure our data is good. virtual void ValidateData( int nVertexCount, int nIndexCount, const MeshDesc_t & desc ); virtual void HandleLateCreation( ) = 0; void Draw( CPrimList *pLists, int nLists ); // Copy verts and/or indices to a mesh builder. This only works for temp meshes! virtual void CopyToMeshBuilder( int iStartVert, // Which vertices to copy. int nVerts, int iStartIndex, // Which indices to copy. int nIndices, int indexOffset, // This is added to each index. CMeshBuilder &builder ); // returns the primitive type virtual MaterialPrimitiveType_t GetPrimitiveType() const = 0; // Returns the number of indices in a mesh.. virtual int IndexCount( ) const = 0; // FIXME: Make this work! virtual MaterialIndexFormat_t IndexFormat() const { return MATERIAL_INDEX_FORMAT_16BIT; } // NOTE: For dynamic index buffers only! // Casts the memory of the dynamic index buffer to the appropriate type virtual void BeginCastBuffer( MaterialIndexFormat_t format ) { Assert(0); } virtual void BeginCastBuffer( VertexFormat_t format ) { Assert(0); } virtual void EndCastBuffer( ) { Assert(0); } virtual int GetRoomRemaining() const { Assert(0); return 0; } // returns a static vertex buffer... virtual CVertexBuffer *GetVertexBuffer() { return 0; } virtual CIndexBuffer *GetIndexBuffer() { return 0; } // Do I need to reset the vertex format? virtual bool NeedsVertexFormatReset( VertexFormat_t fmt ) const; // Do I have enough room? virtual bool HasEnoughRoom( int nVertexCount, int nIndexCount ) const; // Operation to do pre-lock virtual void PreLock() {} virtual unsigned ComputeMemoryUsed(); bool m_bMeshLocked; protected: bool DebugTrace() const; // The vertex format we're using... VertexFormat_t m_VertexFormat; // The morph format we're using MorphFormat_t m_MorphFormat; #ifdef DBGFLAG_ASSERT IMaterialInternal* m_pMaterial; bool m_IsDrawing; #endif }; //----------------------------------------------------------------------------- // Implementation of the mesh //----------------------------------------------------------------------------- class CMeshDX8 : public CBaseMeshDX8 { public: // constructor CMeshDX8( const char *pTextureGroupName ); virtual ~CMeshDX8(); // Locks/unlocks the mesh void LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ); void UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ); // Locks mesh for modifying void ModifyBeginEx( bool bReadOnly, int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ); void ModifyBegin( int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ); void ModifyEnd( MeshDesc_t& desc ); // returns the # of vertices (static meshes only) int VertexCount() const; // returns the # of indices virtual int IndexCount( ) const; // Sets up the vertex and index buffers void UseIndexBuffer( CIndexBuffer* pBuffer ); void UseVertexBuffer( CVertexBuffer* pBuffer ); // returns a static vertex buffer... CVertexBuffer *GetVertexBuffer() { return m_pVertexBuffer; } CIndexBuffer *GetIndexBuffer() { return m_pIndexBuffer; } void SetColorMesh( IMesh *pColorMesh, int nVertexOffsetInBytes ); void SetFlexMesh( IMesh *pMesh, int nVertexOffsetInBytes ); void DisableFlexMesh(); virtual void HandleLateCreation( ); bool HasColorMesh( ) const; bool HasFlexMesh( ) const; // Draws the mesh void Draw( int nFirstIndex, int nIndexCount ); void Draw( CPrimList *pLists, int nLists ); void DrawInternal( CPrimList *pLists, int nLists ); // Draws a single pass void RenderPass(); // Sets the primitive type void SetPrimitiveType( MaterialPrimitiveType_t type ); MaterialPrimitiveType_t GetPrimitiveType() const; bool IsDynamic() const { return false; } protected: // Sets the render state. bool SetRenderState( int nVertexOffsetInBytes, int nFirstVertexIdx, VertexFormat_t vertexFormat = VERTEX_FORMAT_INVALID ); // Is the vertex format valid? bool IsValidVertexFormat( VertexFormat_t vertexFormat = VERTEX_FORMAT_INVALID ); // Locks/ unlocks the vertex buffer bool Lock( int nVertexCount, bool bAppend, VertexDesc_t &desc ); void Unlock( int nVertexCount, VertexDesc_t &desc ); // Locks/unlocks the index buffer // Pass in nFirstIndex=-1 to lock wherever the index buffer is. Pass in a value // >= 0 to specify where to lock. int Lock( bool bReadOnly, int nFirstIndex, int nIndexCount, IndexDesc_t &pIndices ); void Unlock( int nIndexCount, IndexDesc_t &desc ); // computes how many primitives we've got int NumPrimitives( int nVertexCount, int nIndexCount ) const; // Debugging output... void SpewMaterialVerts( ); // Stream source setting methods void SetVertexIDStreamState( ); void SetColorStreamState( ); void SetVertexStreamState( int nVertOffsetInBytes ); void SetIndexStreamState( int firstVertexIdx ); void CheckIndices( CPrimList *pPrim, int numPrimitives ); // The vertex and index buffers CVertexBuffer* m_pVertexBuffer; CIndexBuffer* m_pIndexBuffer; // The current color mesh (to be bound to stream 1) // The vertex offset allows use of a global, shared color mesh VB CMeshDX8 * m_pColorMesh; int m_nColorMeshVertOffsetInBytes; CVertexBuffer *m_pFlexVertexBuffer; bool m_bHasFlexVerts; int m_nFlexVertOffsetInBytes; int m_flexVertCount; // Primitive type MaterialPrimitiveType_t m_Type; // Primitive mode D3DPRIMITIVETYPE m_Mode; // Number of primitives unsigned int m_NumIndices; unsigned short m_NumVertices; // Is it locked? bool m_IsVBLocked; bool m_IsIBLocked; // Used in rendering sub-parts of the mesh static CPrimList *s_pPrims; static int s_nPrims; static unsigned int s_FirstVertex; // Gets reset during CMeshDX8::DrawInternal static unsigned int s_NumVertices; int m_FirstIndex; #ifdef RECORDING int m_LockVertexBufferSize; void *m_LockVertexBuffer; #endif #if defined( RECORDING ) || defined( CHECK_INDICES ) void *m_LockIndexBuffer; int m_LockIndexBufferSize; #endif const char *m_pTextureGroupName; friend class CMeshMgr; // MESHFIXME }; //----------------------------------------------------------------------------- // A little extra stuff for the dynamic version //----------------------------------------------------------------------------- class CDynamicMeshDX8 : public CMeshDX8 { public: // constructor, destructor CDynamicMeshDX8(); virtual ~CDynamicMeshDX8(); // Initializes the dynamic mesh void Init( int nBufferId ); // Sets the vertex format virtual void SetVertexFormat( VertexFormat_t format ); // Resets the state in case of a task switch void Reset(); // Do I have enough room in the buffer? bool HasEnoughRoom( int nVertexCount, int nIndexCount ) const; // returns the # of indices int IndexCount( ) const; // Locks the mesh void LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ); // Unlocks the mesh void UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ); // Override vertex + index buffer void OverrideVertexBuffer( CVertexBuffer *pStaticVertexBuffer ); void OverrideIndexBuffer( CIndexBuffer *pStaticIndexBuffer ); // Do I need to reset the vertex format? bool NeedsVertexFormatReset(VertexFormat_t fmt) const; // Draws it void Draw( int nFirstIndex, int nIndexCount ); void MarkAsDrawn() { m_HasDrawn = true; } // Simply draws what's been buffered up immediately, without state change void DrawSinglePassImmediately(); // Operation to do pre-lock void PreLock(); bool IsDynamic() const { return true; } private: // Resets buffering state void ResetVertexAndIndexCounts(); // Buffer Id int m_nBufferId; // total queued vertices int m_TotalVertices; int m_TotalIndices; // the first vertex and index since the last draw int m_nFirstVertex; int m_FirstIndex; // Have we drawn since the last lock? bool m_HasDrawn; // Any overrides? bool m_VertexOverride; bool m_IndexOverride; }; //----------------------------------------------------------------------------- // A mesh that stores temporary vertex data in the correct format (for modification) //----------------------------------------------------------------------------- class CTempMeshDX8 : public CBaseMeshDX8 { public: // constructor, destructor CTempMeshDX8( bool isDynamic ); virtual ~CTempMeshDX8(); // Sets the material virtual void SetVertexFormat( VertexFormat_t format ); // Locks/unlocks the mesh void LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t &desc ); void UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t &desc ); // Locks mesh for modifying virtual void ModifyBeginEx( bool bReadOnly, int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ); virtual void ModifyBegin( int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ); virtual void ModifyEnd( MeshDesc_t& desc ); // Number of indices + vertices int VertexCount() const; virtual int IndexCount() const; virtual bool IsDynamic() const; // Sets the primitive type void SetPrimitiveType( MaterialPrimitiveType_t type ); MaterialPrimitiveType_t GetPrimitiveType() const; // Begins a pass void BeginPass( ); // Draws a single pass void RenderPass(); virtual void HandleLateCreation() { Assert( !"TBD - CTempMeshDX8::HandleLateCreation()" ); } // Draws the entire beast void Draw( int nFirstIndex, int nIndexCount ); virtual void CopyToMeshBuilder( int iStartVert, // Which vertices to copy. int nVerts, int iStartIndex, // Which indices to copy. int nIndices, int indexOffset, // This is added to each index. CMeshBuilder &builder ); private: // Selection mode void TestSelection( ); void ClipTriangle( D3DXVECTOR3 **ppVert, float zNear, D3DXMATRIX &proj ); CDynamicMeshDX8 *GetDynamicMesh(); CUtlVector< unsigned char, CUtlMemoryAligned< unsigned char, 32 > > m_VertexData; CUtlVector< unsigned short > m_IndexData; unsigned short m_VertexSize; MaterialPrimitiveType_t m_Type; int m_LockedVerts; int m_LockedIndices; bool m_IsDynamic; // Used in rendering sub-parts of the mesh static unsigned int s_NumIndices; static unsigned int s_FirstIndex; #ifdef DBGFLAG_ASSERT bool m_Locked; bool m_InPass; #endif }; #if 0 //----------------------------------------------------------------------------- // A mesh that stores temporary vertex data in the correct format (for modification) //----------------------------------------------------------------------------- class CTempIndexBufferDX8 : public CIndexBufferBase { public: // constructor, destructor CTempIndexBufferDX8( bool isDynamic ); virtual ~CTempIndexBufferDX8(); // Locks/unlocks the mesh void LockIndexBuffer( int nIndexCount ); void UnlockMesh( int nIndexCount ); // Locks mesh for modifying virtual void ModifyBeginEx( bool bReadOnly, int nFirstIndex, int nIndexCount ); virtual void ModifyEnd(); // Number of indices virtual int IndexCount() const; virtual bool IsDynamic() const; virtual void CopyToIndexBuilder( int iStartIndex, // Which indices to copy. int nIndices, int indexOffset, // This is added to each index. CIndexBuilder &builder ); private: // Selection mode void TestSelection( ); CDynamicMeshDX8 *GetDynamicMesh(); CUtlVector< unsigned short > m_IndexData; MaterialPrimitiveType_t m_Type; int m_LockedIndices; bool m_IsDynamic; // Used in rendering sub-parts of the mesh static unsigned int s_NumIndices; static unsigned int s_FirstIndex; #ifdef DBGFLAG_ASSERT bool m_Locked; bool m_InPass; #endif }; #endif //----------------------------------------------------------------------------- // This is a version that buffers up vertex data so we can blast through it later //----------------------------------------------------------------------------- class CBufferedMeshDX8 : public CBaseMeshDX8 { public: // constructor, destructor CBufferedMeshDX8(); virtual ~CBufferedMeshDX8(); // checks to see if it was rendered.. void ResetRendered(); bool WasNotRendered() const; // Sets the mesh we're really going to draw into void SetMesh( CBaseMeshDX8* pMesh ); const CBaseMeshDX8* GetMesh() const { return m_pMesh; } // Spews the mesh data virtual void Spew( int nVertexCount, int nIndexCount, const MeshDesc_t &spewDesc ); // Sets the vertex format virtual void SetVertexFormat( VertexFormat_t format ); virtual VertexFormat_t GetVertexFormat() const; // Sets the morph format virtual void SetMorphFormat( MorphFormat_t format ); // Sets the material void SetMaterial( IMaterial *pMaterial ); // returns the number of indices (should never be called!) virtual int IndexCount() const { Assert(0); return 0; } virtual MaterialIndexFormat_t IndexFormat() const { Assert(0); return MATERIAL_INDEX_FORMAT_16BIT; } virtual bool IsDynamic() const { Assert(0); return true; } virtual void BeginCastBuffer( MaterialIndexFormat_t format ) { Assert(0); } virtual void EndCastBuffer( ) { Assert(0); } virtual int GetRoomRemaining() const { Assert(0); return 0; } // Locks the mesh void LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ); void UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ); // Sets the primitive type void SetPrimitiveType( MaterialPrimitiveType_t type ); MaterialPrimitiveType_t GetPrimitiveType( ) const; void ValidateData( int nVertexCount, int nIndexCount, const MeshDesc_t & spewDesc ); virtual void HandleLateCreation( ) { if ( m_pMesh ) { m_pMesh->HandleLateCreation(); } } // Draws it void Draw( int nFirstIndex, int nIndexCount ); // Renders a pass void RenderPass(); // Flushes queued data void Flush( ); void SetFlexMesh( IMesh *pMesh, int nVertexOffsetInBytes ); private: // The actual mesh we need to render.... CBaseMeshDX8* m_pMesh; // The index of the last vertex (for tristrip fixup) unsigned short m_LastIndex; // Extra padding indices for tristrips unsigned short m_ExtraIndices; // Am I currently flushing? bool m_IsFlushing; // has the dynamic mesh been rendered? bool m_WasRendered; // Do I need to flush? bool m_FlushNeeded; #ifdef DEBUG_BUFFERED_MESHES // for debugging only bool m_BufferedStateSet; BufferedState_t m_BufferedState; #endif }; //----------------------------------------------------------------------------- // Implementation of the mesh manager //----------------------------------------------------------------------------- class CMeshMgr : public IMeshMgr { public: // constructor, destructor CMeshMgr(); virtual ~CMeshMgr(); // Initialize, shutdown void Init(); void Shutdown(); // Task switch... void ReleaseBuffers(); void RestoreBuffers(); // Releases all dynamic vertex buffers void DestroyVertexBuffers(); // Flushes the dynamic mesh void Flush(); // Flushes the vertex buffers void DiscardVertexBuffers(); // Creates, destroys static meshes IMesh *CreateStaticMesh( VertexFormat_t vertexFormat, const char *pTextureBudgetGroup, IMaterial *pMaterial = NULL ); void DestroyStaticMesh( IMesh *pMesh ); // Gets at the dynamic mesh (spoofs it though) IMesh *GetDynamicMesh( IMaterial *pMaterial, VertexFormat_t vertexFormat, int nHWSkinBoneCount, bool buffered, IMesh *pVertexOverride, IMesh *pIndexOverride ); // ----------------------------------------------------------- // ------------ New Vertex/Index Buffer interface ---------------------------- // Do we need support for bForceTempMesh and bSoftwareVertexShader? // I don't think we use bSoftwareVertexShader anymore. .need to look into bForceTempMesh. IVertexBuffer *CreateVertexBuffer( ShaderBufferType_t type, VertexFormat_t fmt, int nVertexCount, const char *pBudgetGroup ); IIndexBuffer *CreateIndexBuffer( ShaderBufferType_t bufferType, MaterialIndexFormat_t fmt, int nIndexCount, const char *pBudgetGroup ); void DestroyVertexBuffer( IVertexBuffer * ); void DestroyIndexBuffer( IIndexBuffer * ); // Do we need to specify the stream here in the case of locking multiple dynamic VBs on different streams? IVertexBuffer *GetDynamicVertexBuffer( int streamID, VertexFormat_t vertexFormat, bool bBuffered = true ); IIndexBuffer *GetDynamicIndexBuffer( MaterialIndexFormat_t fmt, bool bBuffered = true ); void BindVertexBuffer( int streamID, IVertexBuffer *pVertexBuffer, int nOffsetInBytes, int nFirstVertex, int nVertexCount, VertexFormat_t fmt, int nRepetitions = 1 ); void BindIndexBuffer( IIndexBuffer *pIndexBuffer, int nOffsetInBytes ); void Draw( MaterialPrimitiveType_t primitiveType, int nFirstIndex, int nIndexCount ); // ------------ End ---------------------------- void RenderPassWithVertexAndIndexBuffers( void ); VertexFormat_t GetCurrentVertexFormat( void ) const { return m_CurrentVertexFormat; } // Gets at the *actual* dynamic mesh IMesh* GetActualDynamicMesh( VertexFormat_t vertexFormat ); IMesh *GetFlexMesh(); // Computes vertex format from a list of ingredients VertexFormat_t ComputeVertexFormat( unsigned int flags, int numTexCoords, int *pTexCoordDimensions, int numBoneWeights, int userDataSize ) const; // Use fat vertices (for tools) virtual void UseFatVertices( bool bUseFat ); // Returns the number of vertices we can render using the dynamic mesh virtual void GetMaxToRender( IMesh *pMesh, bool bMaxUntilFlush, int *pMaxVerts, int *pMaxIndices ); virtual int GetMaxVerticesToRender( IMaterial *pMaterial ); virtual int GetMaxIndicesToRender( ); // Returns a vertex buffer appropriate for the flags CVertexBuffer *FindOrCreateVertexBuffer( int nDynamicBufferId, VertexFormat_t fmt ); CIndexBuffer *GetDynamicIndexBuffer(); // Is the mesh dynamic? bool IsDynamicMesh( IMesh *pMesh ) const; bool IsBufferedDynamicMesh( IMesh *pMesh ) const; // Is the vertex buffer dynamic? bool IsDynamicVertexBuffer( IVertexBuffer *pVertexBuffer ) const; // Is the index buffer dynamic? bool IsDynamicIndexBuffer( IIndexBuffer *pIndexBuffer ) const; // Returns the vertex size int VertexFormatSize( VertexFormat_t vertexFormat ) const { return CVertexBufferBase::VertexFormatSize( vertexFormat ); } // Computes the vertex buffer pointers void ComputeVertexDescription( unsigned char *pBuffer, VertexFormat_t vertexFormat, MeshDesc_t &desc ) const; // Returns the number of buffers... int BufferCount() const { #ifdef _DEBUG return CVertexBuffer::BufferCount() + CIndexBuffer::BufferCount(); #else return 0; #endif } CVertexBuffer *GetVertexIDBuffer(); IVertexBuffer *GetDynamicVertexBuffer( IMaterial *pMaterial, bool buffered = true ); IIndexBuffer *GetDynamicIndexBuffer( IMaterial *pMaterial, bool buffered = true ); virtual void MarkUnusedVertexFields( unsigned int nFlags, int nTexCoordCount, bool *pUnusedTexCoords ); int UnusedVertexFields() const { return m_nUnusedVertexFields; } int UnusedTextureCoords() const { return m_nUnusedTextureCoords; } IDirect3DVertexBuffer9 *GetZeroVertexBuffer() const { return m_pZeroVertexBuffer; } private: void SetVertexIDStreamState( ); void SetColorStreamState( ); void SetVertexStreamState( int nVertOffsetInBytes, int nVertexStride ); void SetIndexStreamState( int firstVertexIdx ); bool SetRenderState( int nVertexOffsetInBytes, int nFirstVertexIdx, VertexFormat_t vertexFormat, int nVertexStride ); struct VertexBufferLookup_t { CVertexBuffer* m_pBuffer; int m_VertexSize; }; void CopyStaticMeshIndexBufferToTempMeshIndexBuffer( CTempMeshDX8 *pDstIndexMesh, CMeshDX8 *pSrcIndexMesh ); // Cleans up the class void CleanUp(); // Creates, destroys the dynamic index void CreateDynamicIndexBuffer(); void DestroyDynamicIndexBuffer(); // Creates, destroys the vertexID buffer void CreateVertexIDBuffer(); void DestroyVertexIDBuffer(); void CreateZeroVertexBuffer(); void DestroyZeroVertexBuffer(); // Fills a vertexID buffer void FillVertexIDBuffer( CVertexBuffer *pVertexIDBuffer, int nCount ); // The dynamic index buffer CIndexBuffer *m_pDynamicIndexBuffer; // A static vertexID buffer CVertexBuffer *m_pVertexIDBuffer; // The dynamic vertex buffers CUtlVector< VertexBufferLookup_t > m_DynamicVertexBuffers; // The buffered mesh CBufferedMeshDX8 m_BufferedMesh; // The current dynamic mesh CDynamicMeshDX8 m_DynamicMesh; CDynamicMeshDX8 m_DynamicFlexMesh; // The current dynamic vertex buffer CVertexBufferDx8 m_DynamicVertexBuffer; // The current dynamic index buffer CIndexBufferDx8 m_DynamicIndexBuffer; // The dynamic mesh temp version (for shaders that modify vertex data) CTempMeshDX8 m_DynamicTempMesh; // Am I buffering or not? bool m_BufferedMode; // Using fat vertices? bool m_bUseFatVertices; CVertexBufferDx8 *m_pCurrentVertexBuffer; VertexFormat_t m_CurrentVertexFormat; int m_pVertexBufferOffset[MAX_DX8_STREAMS]; int m_pCurrentVertexStride[MAX_DX8_STREAMS]; int m_pFirstVertex[MAX_DX8_STREAMS]; int m_pVertexCount[MAX_DX8_STREAMS]; CIndexBufferBase *m_pCurrentIndexBuffer; int m_nIndexBufferOffset; MaterialPrimitiveType_t m_PrimitiveType; int m_nFirstIndex; int m_nNumIndices; unsigned int m_nUnusedVertexFields; unsigned int m_nUnusedTextureCoords; // 4096 byte static VB containing all-zeros IDirect3DVertexBuffer9 *m_pZeroVertexBuffer; }; //----------------------------------------------------------------------------- // Singleton... //----------------------------------------------------------------------------- static CMeshMgr g_MeshMgr; IMeshMgr* MeshMgr() { return &g_MeshMgr; } //----------------------------------------------------------------------------- // Tracks stream state and queued data //----------------------------------------------------------------------------- static CIndexBuffer *g_pLastIndex = NULL; static IDirect3DIndexBuffer9 *g_pLastIndexBuffer = NULL; static CVertexBuffer *g_pLastVertex = NULL; static IDirect3DVertexBuffer9 *g_pLastVertexBuffer = NULL; static int g_nLastVertOffsetInBytes = 0; static int g_nLastVertStride = 0; static int g_LastVertexIdx = -1; static CMeshDX8 *g_pLastColorMesh = NULL; static int g_nLastColorMeshVertOffsetInBytes = 0; static bool g_bUsingVertexID = false; static bool g_bFlexMeshStreamSet = false; static VertexFormat_t g_LastVertexFormat = 0; inline void D3DSetStreamSource( unsigned int streamNumber, IDirect3DVertexBuffer9 *pStreamData, unsigned int nVertexOffsetInBytes, unsigned int stride ) { Dx9Device()->SetStreamSource( streamNumber, pStreamData, nVertexOffsetInBytes, stride ); } //----------------------------------------------------------------------------- // Tracks stream state and queued data //----------------------------------------------------------------------------- void Unbind( IDirect3DIndexBuffer9 *pIndexBuffer ) { #ifdef _X360 IDirect3DIndexBuffer9 *pBoundBuffer; Dx9Device()->GetIndices( &pBoundBuffer ); if ( pBoundBuffer == pIndexBuffer ) { // xboxissue - cannot lock indexes set in a d3d device, clear possibly set indices Dx9Device()->SetIndices( NULL ); g_pLastIndex = NULL; g_pLastIndexBuffer = NULL; } if ( pBoundBuffer ) { pBoundBuffer->Release(); } #endif } void Unbind( IDirect3DVertexBuffer9 *pVertexBuffer ) { #ifdef _X360 UINT nOffset, nStride; IDirect3DVertexBuffer9 *pBoundBuffer; for ( int i = 0; i < MAX_DX8_STREAMS; ++i ) { Dx9Device()->GetStreamSource( i, &pBoundBuffer, &nOffset, &nStride ); if ( pBoundBuffer == pVertexBuffer ) { // xboxissue - cannot lock indexes set in a d3d device, clear possibly set indices Dx9Device()->SetStreamSource( i, 0, 0, 0 ); switch ( i ) { case 0: g_pLastVertex = NULL; g_pLastVertexBuffer = NULL; break; case 1: g_pLastColorMesh = NULL; g_nLastColorMeshVertOffsetInBytes = 0; break; } } if ( pBoundBuffer ) { pBoundBuffer->Release(); } } #endif } //----------------------------------------------------------------------------- // Helpers to count texture coordinates //----------------------------------------------------------------------------- static int NumTextureCoordinates( VertexFormat_t vertexFormat ) { int nTexCoordCount = 0; for ( int i = 0; i < VERTEX_MAX_TEXTURE_COORDINATES; ++i ) { if ( TexCoordSize( i, vertexFormat ) == 0 ) continue; ++nTexCoordCount; } return nTexCoordCount; } //----------------------------------------------------------------------------- // Makes sure that the render state is always set next time //----------------------------------------------------------------------------- static void ResetMeshRenderState() { SafeRelease( &g_pLastIndex ); g_pLastIndexBuffer = 0; g_pLastVertex = 0; g_nLastVertOffsetInBytes = 0; g_pLastColorMesh = 0; g_nLastColorMeshVertOffsetInBytes = 0; g_LastVertexIdx = -1; g_bUsingVertexID = false; g_bFlexMeshStreamSet = false; g_LastVertexFormat = 0; } //----------------------------------------------------------------------------- // Makes sure that the render state is always set next time //----------------------------------------------------------------------------- static void ResetIndexBufferRenderState() { SafeRelease( &g_pLastIndex ); g_pLastIndexBuffer = 0; g_LastVertexIdx = -1; } //----------------------------------------------------------------------------- // // Index Buffer implementations begin here // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Globals //----------------------------------------------------------------------------- #ifdef _DEBUG int CIndexBufferDx8::s_nBufferCount = 0; #endif //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CIndexBufferDx8::CIndexBufferDx8( ShaderBufferType_t bufferType, MaterialIndexFormat_t fmt, int nIndexCount, const char *pBudgetGroupName ) : BaseClass( pBudgetGroupName ) { // Debugger(); Assert( nIndexCount != 0 ); // NOTE: MATERIAL_INDEX_FORMAT_UNKNOWN can't be dealt with under dx9 // because format is bound at buffer creation time. What we'll do // is just arbitrarily choose to use a 16-bit index buffer of the same size if ( fmt == MATERIAL_INDEX_FORMAT_UNKNOWN ) { fmt = MATERIAL_INDEX_FORMAT_16BIT; nIndexCount /= 2; } m_pIndexBuffer = NULL; m_IndexFormat = fmt; m_nBufferSize = nIndexCount * IndexSize(); m_nIndexCount = nIndexCount; m_nFirstUnwrittenOffset = 0; m_bIsLocked = false; m_bIsDynamic = IsDynamicBufferType( bufferType ); m_bFlush = false; #ifdef CHECK_INDICES m_pShadowIndices = NULL; #endif #ifdef VPROF_ENABLED m_nVProfFrame = -1; #endif } CIndexBufferDx8::~CIndexBufferDx8() { Free(); } //----------------------------------------------------------------------------- // Returns the index size //----------------------------------------------------------------------------- inline int CIndexBufferDx8::IndexSize() const { Assert( m_IndexFormat != MATERIAL_INDEX_FORMAT_UNKNOWN ); return ( m_IndexFormat == MATERIAL_INDEX_FORMAT_16BIT ) ? 2 : 4; } //----------------------------------------------------------------------------- // Creates, destroys the index buffer //----------------------------------------------------------------------------- bool CIndexBufferDx8::Allocate() { #ifdef OSX Debugger(); #endif Assert( !m_pIndexBuffer ); m_nFirstUnwrittenOffset = 0; // FIXME: This doesn't really work for dynamic buffers; dynamic buffers // can't have mixed-type indices in them. Bleah. D3DFORMAT format = ( m_IndexFormat == MATERIAL_INDEX_FORMAT_32BIT ) ? D3DFMT_INDEX32 : D3DFMT_INDEX16; DWORD usage = D3DUSAGE_WRITEONLY; if ( m_bIsDynamic ) { usage |= D3DUSAGE_DYNAMIC; } HRESULT hr = Dx9Device()->CreateIndexBuffer( m_nBufferSize, usage, format, D3DPOOL_DEFAULT, &m_pIndexBuffer, NULL ); #if !defined( _X360 ) if ( ( hr == D3DERR_OUTOFVIDEOMEMORY ) || ( hr == E_OUTOFMEMORY ) ) { // Don't have the memory for this. Try flushing all managed resources // out of vid mem and try again. // FIXME: need to record this Dx9Device()->EvictManagedResources(); hr = Dx9Device()->CreateIndexBuffer( m_nBufferSize, usage, format, D3DPOOL_DEFAULT, &m_pIndexBuffer, NULL ); } #endif // !X360 if ( FAILED(hr) || ( m_pIndexBuffer == NULL ) ) { Warning( "CIndexBufferDx8::Allocate: CreateIndexBuffer failed!\n" ); return false; } if ( !m_bIsDynamic ) { VPROF_INCREMENT_GROUP_COUNTER( "TexGroup_global_" TEXTURE_GROUP_STATIC_INDEX_BUFFER, COUNTER_GROUP_TEXTURE_GLOBAL, m_nBufferSize ); } else { VPROF_INCREMENT_GROUP_COUNTER( "TexGroup_global_" TEXTURE_GROUP_DYNAMIC_INDEX_BUFFER, COUNTER_GROUP_TEXTURE_GLOBAL, m_nBufferSize ); } #ifdef CHECK_INDICES Assert ( !m_pShadowIndices ); m_pShadowIndices = new unsigned char[ m_nBufferSize ]; memset( m_pShadowIndices, 0xFF, m_nBufferSize ); #endif // CHECK_INDICES #ifdef _DEBUG ++s_nBufferCount; #endif return true; } void CIndexBufferDx8::Free() { // FIXME: Unlock(0); if ( m_pIndexBuffer ) { #ifdef _DEBUG --s_nBufferCount; #endif m_pIndexBuffer->Release(); m_pIndexBuffer = NULL; if ( !m_bIsDynamic ) { VPROF_INCREMENT_GROUP_COUNTER( "TexGroup_global_" TEXTURE_GROUP_STATIC_INDEX_BUFFER, COUNTER_GROUP_TEXTURE_GLOBAL, - m_nBufferSize ); } else { VPROF_INCREMENT_GROUP_COUNTER( "TexGroup_global_" TEXTURE_GROUP_DYNAMIC_INDEX_BUFFER, COUNTER_GROUP_TEXTURE_GLOBAL, - m_nBufferSize ); } } #ifdef CHECK_INDICES if ( m_pShadowIndices ) { delete[] m_pShadowIndices; m_pShadowIndices = NULL; } #endif // CHECK_INDICES } //----------------------------------------------------------------------------- // Index buffer information //----------------------------------------------------------------------------- int CIndexBufferDx8::IndexCount( ) const { Assert( !m_bIsDynamic ); return m_nIndexCount; } MaterialIndexFormat_t CIndexBufferDx8::IndexFormat() const { Assert( !m_bIsDynamic ); return m_IndexFormat; } //----------------------------------------------------------------------------- // Returns true if the buffer is dynamic //----------------------------------------------------------------------------- bool CIndexBufferDx8::IsDynamic() const { return m_bIsDynamic; } //----------------------------------------------------------------------------- // Only used by dynamic buffers, indicates the next lock should perform a discard. //----------------------------------------------------------------------------- void CIndexBufferDx8::Flush() { // This strange-looking line makes a flush only occur if the buffer is dynamic. m_bFlush = m_bIsDynamic; } //----------------------------------------------------------------------------- // Returns the D3D buffer //----------------------------------------------------------------------------- IDirect3DIndexBuffer9* CIndexBufferDx8::GetDx9Buffer() { return m_pIndexBuffer; } //----------------------------------------------------------------------------- // Returns a shadowed index, for validation //----------------------------------------------------------------------------- #ifdef CHECK_INDICES unsigned short CIndexBufferDx8::GetShadowIndex( int i ) const { Assert( i >= 0 && i < m_nIndexCount ); Assert( m_IndexFormat == MATERIAL_INDEX_FORMAT_16BIT ); return *(unsigned short*)( &m_pShadowIndices[ i * IndexSize() ] ); } #endif // CHECK_INDICES //----------------------------------------------------------------------------- // Used to measure how much static buffer memory is touched each frame //----------------------------------------------------------------------------- void CIndexBufferDx8::HandlePerFrameTextureStats( int nFrame ) { #ifdef VPROF_ENABLED if ( m_nVProfFrame != nFrame && !m_bIsDynamic ) { m_nVProfFrame = nFrame; VPROF_INCREMENT_GROUP_COUNTER( "TexGroup_frame_" TEXTURE_GROUP_STATIC_INDEX_BUFFER, COUNTER_GROUP_TEXTURE_PER_FRAME, m_nBufferSize ); } #endif } //----------------------------------------------------------------------------- // Casts a dynamic buffer to be a particular index type //----------------------------------------------------------------------------- void CIndexBufferDx8::BeginCastBuffer( MaterialIndexFormat_t format ) { // NOTE: This should have no effect under Dx9, since we can't recast index buffers. Assert( format != MATERIAL_INDEX_FORMAT_UNKNOWN ); Assert( m_bIsDynamic && ( m_IndexFormat == format ) ); } void CIndexBufferDx8::EndCastBuffer( ) { // NOTE: This should have no effect under Dx9, since we can't recast index buffers. } int CIndexBufferDx8::GetRoomRemaining() const { return ( m_nBufferSize - m_nFirstUnwrittenOffset ) / IndexSize(); } //----------------------------------------------------------------------------- // Locks/unlocks the index buffer //----------------------------------------------------------------------------- bool CIndexBufferDx8::Lock( int nMaxIndexCount, bool bAppend, IndexDesc_t &desc ) { Assert( !m_bIsLocked && ( nMaxIndexCount != 0 ) && ( nMaxIndexCount <= m_nIndexCount ) ); Assert( m_IndexFormat != MATERIAL_INDEX_FORMAT_UNKNOWN ); // FIXME: Why do we need to sync matrices now? ShaderUtil()->SyncMatrices(); g_ShaderMutex.Lock(); VPROF( "CIndexBufferX8::Lock" ); void *pLockedData = NULL; HRESULT hr; int nMemoryRequired; bool bHasEnoughMemory; UINT nLockFlags; // This can happen if the buffer was locked but a type wasn't bound if ( m_IndexFormat == MATERIAL_INDEX_FORMAT_UNKNOWN ) goto indexBufferLockFailed; // Just give the app crap buffers to fill up while we're suppressed... if ( g_pShaderDeviceDx8->IsDeactivated() || ( nMaxIndexCount == 0 ) ) goto indexBufferLockFailed; // Did we ask for something too large? if ( nMaxIndexCount > m_nIndexCount ) { Warning( "Too many indices for index buffer. . tell a programmer (%d>%d)\n", nMaxIndexCount, m_nIndexCount ); goto indexBufferLockFailed; } // We might not have a buffer owing to alt-tab type stuff if ( !m_pIndexBuffer ) { if ( !Allocate() ) goto indexBufferLockFailed; } // Check to see if we have enough memory nMemoryRequired = nMaxIndexCount * IndexSize(); bHasEnoughMemory = ( m_nFirstUnwrittenOffset + nMemoryRequired <= m_nBufferSize ); nLockFlags = D3DLOCK_NOSYSLOCK; if ( bAppend ) { // Can't have the first lock after a flush be an appending lock Assert( !m_bFlush ); // If we're appending and we don't have enough room, then puke! if ( !bHasEnoughMemory || m_bFlush ) goto indexBufferLockFailed; nLockFlags |= ( m_nFirstUnwrittenOffset == 0 ) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; } else { // If we're not appending, no overwrite unless we don't have enough room // If we're a static buffer, always discard if we're not appending if ( !m_bFlush && bHasEnoughMemory && m_bIsDynamic ) { nLockFlags |= ( m_nFirstUnwrittenOffset == 0 ) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; } else { if ( m_bIsDynamic ) { nLockFlags |= D3DLOCK_DISCARD; } m_nFirstUnwrittenOffset = 0; m_bFlush = false; } } #if !defined( _X360 ) hr = m_pIndexBuffer->Lock( m_nFirstUnwrittenOffset, nMemoryRequired, &pLockedData, nLockFlags ); #else hr = m_pIndexBuffer->Lock( 0, 0, &pLockedData, nLockFlags ); pLockedData = ( ( unsigned char * )pLockedData + m_nFirstUnwrittenOffset ); #endif if ( FAILED( hr ) ) { Warning( "Failed to lock index buffer in CIndexBufferDx8::LockIndexBuffer\n" ); goto indexBufferLockFailed; } desc.m_pIndices = (unsigned short*)( pLockedData ); desc.m_nIndexSize = IndexSize() >> 1; if ( g_pHardwareConfig->SupportsStreamOffset() ) { desc.m_nFirstIndex = 0; desc.m_nOffset = m_nFirstUnwrittenOffset; } else { desc.m_nFirstIndex = m_nFirstUnwrittenOffset / IndexSize(); Assert( (int)( desc.m_nFirstIndex * IndexSize() ) == m_nFirstUnwrittenOffset ); desc.m_nOffset = 0; } m_bIsLocked = true; #ifdef CHECK_INDICES m_nLockIndexBufferSize = nMemoryRequired; m_pLockIndexBuffer = desc.m_pIndices; m_nLockIndexOffset = m_nFirstUnwrittenOffset; #endif // CHECK_INDICES return true; indexBufferLockFailed: g_ShaderMutex.Unlock(); // Set up a bogus index descriptor desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; desc.m_nFirstIndex = 0; desc.m_nOffset = 0; return false; } void CIndexBufferDx8::Unlock( int nWrittenIndexCount, IndexDesc_t &desc ) { Assert( nWrittenIndexCount <= m_nIndexCount ); // NOTE: This can happen if another application finishes // initializing during the construction of a mesh if ( !m_bIsLocked ) return; #ifdef CHECK_INDICES memcpy( (unsigned char*)m_pShadowIndices + m_nLockIndexOffset, m_pLockIndexBuffer, nWrittenIndexCount * IndexSize() ); #endif // CHECK_INDICES if ( m_pIndexBuffer ) { m_pIndexBuffer->Unlock(); } m_nFirstUnwrittenOffset += nWrittenIndexCount * IndexSize(); m_bIsLocked = false; g_ShaderMutex.Unlock(); } //----------------------------------------------------------------------------- // // Vertex Buffer implementations begin here // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // globals //----------------------------------------------------------------------------- #ifdef _DEBUG int CVertexBufferDx8::s_nBufferCount = 0; #endif //----------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------- CVertexBufferDx8::CVertexBufferDx8( ShaderBufferType_t type, VertexFormat_t fmt, int nVertexCount, const char *pBudgetGroupName ) : BaseClass( pBudgetGroupName ) { // Debugger(); Assert( nVertexCount != 0 ); m_pVertexBuffer = NULL; m_VertexFormat = fmt; m_nVertexCount = ( fmt == VERTEX_FORMAT_UNKNOWN ) ? 0 : nVertexCount; m_nBufferSize = ( fmt == VERTEX_FORMAT_UNKNOWN ) ? nVertexCount : nVertexCount * VertexSize(); m_nFirstUnwrittenOffset = 0; m_bIsLocked = false; m_bIsDynamic = ( type == SHADER_BUFFER_TYPE_DYNAMIC ) || ( type == SHADER_BUFFER_TYPE_DYNAMIC_TEMP ); m_bFlush = false; #ifdef VPROF_ENABLED if ( !m_bIsDynamic ) { char name[256]; V_strcpy_safe( name, "TexGroup_global_" ); V_strcat_safe( name, pBudgetGroupName, sizeof(name) ); m_pGlobalCounter = g_VProfCurrentProfile.FindOrCreateCounter( name, COUNTER_GROUP_TEXTURE_GLOBAL ); V_strcpy_safe( name, "TexGroup_frame_" ); V_strcat_safe( name, pBudgetGroupName, sizeof(name) ); m_pFrameCounter = g_VProfCurrentProfile.FindOrCreateCounter( name, COUNTER_GROUP_TEXTURE_PER_FRAME ); } else { m_pGlobalCounter = g_VProfCurrentProfile.FindOrCreateCounter( "TexGroup_global_" TEXTURE_GROUP_DYNAMIC_VERTEX_BUFFER, COUNTER_GROUP_TEXTURE_GLOBAL ); m_pFrameCounter = NULL; } m_nVProfFrame = -1; #endif } CVertexBufferDx8::~CVertexBufferDx8() { Free(); } //----------------------------------------------------------------------------- // Returns the vertex size //----------------------------------------------------------------------------- inline int CVertexBufferDx8::VertexSize() const { Assert( m_VertexFormat != VERTEX_FORMAT_UNKNOWN ); return VertexFormatSize( m_VertexFormat ); } //----------------------------------------------------------------------------- // Creates, destroys the vertex buffer //----------------------------------------------------------------------------- bool CVertexBufferDx8::Allocate() { Assert( !m_pVertexBuffer ); m_nFirstUnwrittenOffset = 0; D3DPOOL pool = D3DPOOL_MANAGED; #if defined(IS_WINDOWS_PC) && defined(SHADERAPIDX9) extern bool g_ShaderDeviceUsingD3D9Ex; if ( g_ShaderDeviceUsingD3D9Ex ) { pool = D3DPOOL_DEFAULT; } #endif DWORD usage = D3DUSAGE_WRITEONLY; if ( m_bIsDynamic ) { usage |= D3DUSAGE_DYNAMIC; pool = D3DPOOL_DEFAULT; // Dynamic meshes should never be compressed (slows down writing to them) Assert( CompressionType( GetVertexFormat() ) == VERTEX_COMPRESSION_NONE ); } HRESULT hr = Dx9Device()->CreateVertexBuffer( m_nBufferSize, usage, 0, pool, &m_pVertexBuffer, NULL ); #if !defined( _X360 ) if ( ( hr == D3DERR_OUTOFVIDEOMEMORY ) || ( hr == E_OUTOFMEMORY ) ) { // Don't have the memory for this. Try flushing all managed resources // out of vid mem and try again. // FIXME: need to record this Dx9Device()->EvictManagedResources(); hr = Dx9Device()->CreateVertexBuffer( m_nBufferSize, usage, 0, pool, &m_pVertexBuffer, NULL ); } #endif // !X360 if ( FAILED(hr) || ( m_pVertexBuffer == NULL ) ) { Warning( "CVertexBufferDx8::Allocate: CreateVertexBuffer failed!\n" ); return false; } // Track VB allocations g_VBAllocTracker->CountVB( m_pVertexBuffer, m_bIsDynamic, m_nBufferSize, VertexSize(), GetVertexFormat() ); #ifdef VPROF_ENABLED if ( IsX360() || !m_bIsDynamic ) { Assert( m_pGlobalCounter ); *m_pGlobalCounter += m_nBufferSize; } #endif #ifdef _DEBUG ++s_nBufferCount; #endif return true; } void CVertexBufferDx8::Free() { // FIXME: Unlock(0); if ( m_pVertexBuffer ) { #ifdef _DEBUG --s_nBufferCount; #endif // Track VB allocations g_VBAllocTracker->UnCountVB( m_pVertexBuffer ); #ifdef VPROF_ENABLED if ( IsX360() || !m_bIsDynamic ) { Assert( m_pGlobalCounter ); *m_pGlobalCounter -= m_nBufferSize; } #endif m_pVertexBuffer->Release(); m_pVertexBuffer = NULL; } } //----------------------------------------------------------------------------- // Vertex buffer information //----------------------------------------------------------------------------- int CVertexBufferDx8::VertexCount() const { Assert( !m_bIsDynamic ); return m_nVertexCount; } VertexFormat_t CVertexBufferDx8::GetVertexFormat() const { Assert( !m_bIsDynamic ); return m_VertexFormat; } //----------------------------------------------------------------------------- // Returns true if the buffer is dynamic //----------------------------------------------------------------------------- bool CVertexBufferDx8::IsDynamic() const { return m_bIsDynamic; } //----------------------------------------------------------------------------- // Only used by dynamic buffers, indicates the next lock should perform a discard. //----------------------------------------------------------------------------- void CVertexBufferDx8::Flush() { // This strange-looking line makes a flush only occur if the buffer is dynamic. m_bFlush = m_bIsDynamic; } //----------------------------------------------------------------------------- // Returns the D3D buffer //----------------------------------------------------------------------------- IDirect3DVertexBuffer9* CVertexBufferDx8::GetDx9Buffer() { return m_pVertexBuffer; } //----------------------------------------------------------------------------- // Casts a dynamic buffer to be a particular vertex type //----------------------------------------------------------------------------- void CVertexBufferDx8::BeginCastBuffer( VertexFormat_t format ) { Assert( format != MATERIAL_INDEX_FORMAT_UNKNOWN ); Assert( m_bIsDynamic && ( m_VertexFormat == 0 || m_VertexFormat == format ) ); if ( !m_bIsDynamic ) return; m_VertexFormat = format; int nVertexSize = VertexSize(); m_nVertexCount = m_nBufferSize / nVertexSize; // snap current position up to the next position based on expected size // so append can safely guarantee nooverwrite regardless of a format growth or shrinkage if ( !g_pHardwareConfig->SupportsStreamOffset() ) { m_nFirstUnwrittenOffset = ( m_nFirstUnwrittenOffset + nVertexSize - 1 ) / nVertexSize; m_nFirstUnwrittenOffset *= nVertexSize; if ( m_nFirstUnwrittenOffset > m_nBufferSize ) { m_nFirstUnwrittenOffset = m_nBufferSize; } } } void CVertexBufferDx8::EndCastBuffer( ) { Assert( m_bIsDynamic && m_VertexFormat != 0 ); if ( !m_bIsDynamic ) return; m_VertexFormat = 0; m_nVertexCount = 0; } //----------------------------------------------------------------------------- // Returns the number of vertices we can still write into the buffer //----------------------------------------------------------------------------- int CVertexBufferDx8::GetRoomRemaining() const { return ( m_nBufferSize - m_nFirstUnwrittenOffset ) / VertexSize(); } //----------------------------------------------------------------------------- // Locks/unlocks the vertex buffer mesh //----------------------------------------------------------------------------- bool CVertexBufferDx8::Lock( int nMaxVertexCount, bool bAppend, VertexDesc_t &desc ) { Assert( !m_bIsLocked && ( nMaxVertexCount != 0 ) && ( nMaxVertexCount <= m_nVertexCount ) ); Assert( m_VertexFormat != VERTEX_FORMAT_UNKNOWN ); // FIXME: Why do we need to sync matrices now? ShaderUtil()->SyncMatrices(); g_ShaderMutex.Lock(); VPROF( "CVertexBufferDx8::Lock" ); void *pLockedData = NULL; HRESULT hr; int nMemoryRequired; bool bHasEnoughMemory; UINT nLockFlags; // This can happen if the buffer was locked but a type wasn't bound if ( m_VertexFormat == VERTEX_FORMAT_UNKNOWN ) goto vertexBufferLockFailed; // Just give the app crap buffers to fill up while we're suppressed... if ( g_pShaderDeviceDx8->IsDeactivated() || ( nMaxVertexCount == 0 ) ) goto vertexBufferLockFailed; // Did we ask for something too large? if ( nMaxVertexCount > m_nVertexCount ) { Warning( "Too many vertices for vertex buffer. . tell a programmer (%d>%d)\n", nMaxVertexCount, m_nVertexCount ); goto vertexBufferLockFailed; } // We might not have a buffer owing to alt-tab type stuff if ( !m_pVertexBuffer ) { if ( !Allocate() ) goto vertexBufferLockFailed; } // Check to see if we have enough memory nMemoryRequired = nMaxVertexCount * VertexSize(); bHasEnoughMemory = ( m_nFirstUnwrittenOffset + nMemoryRequired <= m_nBufferSize ); nLockFlags = D3DLOCK_NOSYSLOCK; if ( bAppend ) { // Can't have the first lock after a flush be an appending lock Assert( !m_bFlush ); // If we're appending and we don't have enough room, then puke! if ( !bHasEnoughMemory || m_bFlush ) goto vertexBufferLockFailed; nLockFlags |= ( m_nFirstUnwrittenOffset == 0 ) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; } else { // If we're not appending, no overwrite unless we don't have enough room // If we're a static buffer, always discard if we're not appending if ( !m_bFlush && bHasEnoughMemory && m_bIsDynamic ) { nLockFlags |= ( m_nFirstUnwrittenOffset == 0 ) ? D3DLOCK_DISCARD : D3DLOCK_NOOVERWRITE; } else { if ( m_bIsDynamic ) { nLockFlags |= D3DLOCK_DISCARD; } m_nFirstUnwrittenOffset = 0; m_bFlush = false; } } #if !defined( _X360 ) hr = m_pVertexBuffer->Lock( m_nFirstUnwrittenOffset, nMemoryRequired, &pLockedData, nLockFlags ); #else hr = m_pVertexBuffer->Lock( 0, 0, &pLockedData, nLockFlags ); pLockedData = (unsigned char*)pLockedData + m_nFirstUnwrittenOffset; #endif if ( FAILED( hr ) ) { // Check if paged pool is in critical state ( < 5% free ) PAGED_POOL_INFO_t ppi; if ( ( SYSCALL_SUCCESS == Plat_GetPagedPoolInfo( &ppi ) ) && ( ( ppi.numPagesFree * 20 ) < ( ppi.numPagesUsed + ppi.numPagesFree ) ) ) { Error( "Out of OS Paged Pool Memory! For more information, please see\nhttp://support.steampowered.com/cgi-bin/steampowered.cfg/php/enduser/std_adp.php?p_faqid=150\n" ); } else { Warning( "Failed to lock vertex buffer in CVertexBufferDx8::Lock\n" ); } goto vertexBufferLockFailed; } ComputeVertexDescription( (unsigned char*)pLockedData, m_VertexFormat, desc ); if ( g_pHardwareConfig->SupportsStreamOffset() ) { desc.m_nFirstVertex = 0; desc.m_nOffset = m_nFirstUnwrittenOffset; } else { desc.m_nFirstVertex = m_nFirstUnwrittenOffset / VertexSize(); desc.m_nOffset = 0; Assert( m_nFirstUnwrittenOffset == VertexSize() * desc.m_nFirstVertex ); } m_bIsLocked = true; return true; vertexBufferLockFailed: ComputeVertexDescription( 0, 0, desc ); desc.m_nFirstVertex = 0; desc.m_nOffset = 0; return false; } void CVertexBufferDx8::Unlock( int nWrittenVertexCount, VertexDesc_t &desc ) { Assert( nWrittenVertexCount <= m_nVertexCount ); // NOTE: This can happen if another application finishes // initializing during the construction of a mesh if ( !m_bIsLocked ) return; if ( m_pVertexBuffer ) { m_pVertexBuffer->Unlock(); } m_nFirstUnwrittenOffset += nWrittenVertexCount * VertexSize(); m_bIsLocked = false; g_ShaderMutex.Unlock(); } //----------------------------------------------------------------------------- // Used to measure how much static buffer memory is touched each frame //----------------------------------------------------------------------------- void CVertexBufferDx8::HandlePerFrameTextureStats( int nFrame ) { #ifdef VPROF_ENABLED if ( m_nVProfFrame != nFrame && !m_bIsDynamic ) { m_nVProfFrame = nFrame; m_pFrameCounter += m_nBufferSize; } #endif } //----------------------------------------------------------------------------- // Helpers with meshdescs... //----------------------------------------------------------------------------- // FIXME: add compression-agnostic read-accessors (which decompress and return by value, checking desc.m_CompressionType) inline D3DXVECTOR3 &Position( MeshDesc_t const &desc, int vert ) { return *(D3DXVECTOR3*)((unsigned char*)desc.m_pPosition + vert * desc.m_VertexSize_Position ); } inline float Wrinkle( MeshDesc_t const &desc, int vert ) { return *(float*)((unsigned char*)desc.m_pWrinkle + vert * desc.m_VertexSize_Wrinkle ); } inline D3DXVECTOR3 &BoneWeight( MeshDesc_t const &desc, int vert ) { Assert( desc.m_CompressionType == VERTEX_COMPRESSION_NONE ); return *(D3DXVECTOR3*)((unsigned char*)desc.m_pBoneWeight + vert * desc.m_VertexSize_BoneWeight ); } inline unsigned char *BoneIndex( MeshDesc_t const &desc, int vert ) { return desc.m_pBoneMatrixIndex + vert * desc.m_VertexSize_BoneMatrixIndex; } inline D3DXVECTOR3 &Normal( MeshDesc_t const &desc, int vert ) { Assert( desc.m_CompressionType == VERTEX_COMPRESSION_NONE ); return *(D3DXVECTOR3*)((unsigned char*)desc.m_pNormal + vert * desc.m_VertexSize_Normal ); } inline unsigned char *Color( MeshDesc_t const &desc, int vert ) { return desc.m_pColor + vert * desc.m_VertexSize_Color; } inline D3DXVECTOR2 &TexCoord( MeshDesc_t const &desc, int vert, int stage ) { return *(D3DXVECTOR2*)((unsigned char*)desc.m_pTexCoord[stage] + vert * desc.m_VertexSize_TexCoord[stage] ); } inline D3DXVECTOR3 &TangentS( MeshDesc_t const &desc, int vert ) { return *(D3DXVECTOR3*)((unsigned char*)desc.m_pTangentS + vert * desc.m_VertexSize_TangentS ); } inline D3DXVECTOR3 &TangentT( MeshDesc_t const &desc, int vert ) { return *(D3DXVECTOR3*)((unsigned char*)desc.m_pTangentT + vert * desc.m_VertexSize_TangentT ); } //----------------------------------------------------------------------------- // // Base mesh // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- CBaseMeshDX8::CBaseMeshDX8() : m_VertexFormat(0) { m_bMeshLocked = false; #ifdef DBGFLAG_ASSERT m_IsDrawing = false; m_pMaterial = 0; #endif } CBaseMeshDX8::~CBaseMeshDX8() { } //----------------------------------------------------------------------------- // For debugging... //----------------------------------------------------------------------------- bool CBaseMeshDX8::DebugTrace() const { #ifdef _DEBUG if (m_pMaterial) return m_pMaterial->PerformDebugTrace(); #endif return false; } void CBaseMeshDX8::SetMaterial( IMaterial *pMaterial ) { #ifdef DBGFLAG_ASSERT m_pMaterial = static_cast(pMaterial); #endif } //----------------------------------------------------------------------------- // Sets, gets the vertex format //----------------------------------------------------------------------------- void CBaseMeshDX8::SetVertexFormat( VertexFormat_t format ) { m_VertexFormat = format; } VertexFormat_t CBaseMeshDX8::GetVertexFormat() const { return m_VertexFormat; } //----------------------------------------------------------------------------- // Sets/gets the morph format //----------------------------------------------------------------------------- void CBaseMeshDX8::SetMorphFormat( MorphFormat_t format ) { m_MorphFormat = format; } MorphFormat_t CBaseMeshDX8::GetMorphFormat() const { return m_MorphFormat; } //----------------------------------------------------------------------------- // Am I using morph data? //----------------------------------------------------------------------------- bool CBaseMeshDX8::IsUsingMorphData() const { LOCK_SHADERAPI(); // We're not using a morph unless the bound morph is a superset of what the rendermesh needs MorphFormat_t morphFormat = GetMorphFormat(); if ( !morphFormat ) return false; return ( ( morphFormat & ShaderUtil()->GetBoundMorphFormat() ) == morphFormat ); } //----------------------------------------------------------------------------- // Do I need to reset the vertex format? //----------------------------------------------------------------------------- bool CBaseMeshDX8::NeedsVertexFormatReset( VertexFormat_t fmt ) const { return m_VertexFormat != fmt; } //----------------------------------------------------------------------------- // Do I have enough room? //----------------------------------------------------------------------------- bool CBaseMeshDX8::HasEnoughRoom( int nVertexCount, int nIndexCount ) const { // by default, we do return true; } //----------------------------------------------------------------------------- // Estimate the memory used //----------------------------------------------------------------------------- unsigned CBaseMeshDX8::ComputeMemoryUsed() { unsigned size = 0; if ( GetVertexBuffer() ) { size += GetVertexBuffer()->VertexCount() * GetVertexBuffer()->VertexSize(); } if ( GetIndexBuffer() ) { size += GetIndexBuffer()->IndexCount() * GetIndexBuffer()->IndexSize(); } return size; } //----------------------------------------------------------------------------- // Locks mesh for modifying //----------------------------------------------------------------------------- void CBaseMeshDX8::ModifyBeginEx( bool bReadOnly, int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ) { LOCK_SHADERAPI(); // for the time being, disallow for most cases Assert(0); } void CBaseMeshDX8::ModifyBegin( int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ) { LOCK_SHADERAPI(); // for the time being, disallow for most cases Assert(0); } void CBaseMeshDX8::ModifyEnd( MeshDesc_t& desc ) { LOCK_SHADERAPI(); // for the time being, disallow for most cases Assert(0); } //----------------------------------------------------------------------------- // Begins a pass //----------------------------------------------------------------------------- void CBaseMeshDX8::BeginPass( ) { LOCK_SHADERAPI(); } //----------------------------------------------------------------------------- // Sets the render state and gets the drawing going //----------------------------------------------------------------------------- inline void CBaseMeshDX8::DrawMesh( ) { #ifdef DBGFLAG_ASSERT // Make sure we're not drawing... Assert( !m_IsDrawing ); m_IsDrawing = true; #endif // This is going to cause RenderPass to get called a bunch ShaderAPI()->DrawMesh( this ); #ifdef DBGFLAG_ASSERT m_IsDrawing = false; #endif } //----------------------------------------------------------------------------- // Spews the mesh data //----------------------------------------------------------------------------- void CBaseMeshDX8::Spew( int nVertexCount, int nIndexCount, const MeshDesc_t &spewDesc ) { LOCK_SHADERAPI(); // This has regressed. int i; // FIXME: just fall back to the base class (CVertexBufferBase) version of this function! #ifdef _DEBUG if( m_pMaterial ) { Plat_DebugString( ( const char * )m_pMaterial->GetName() ); Plat_DebugString( "\n" ); } #endif // _DEBUG // This is needed so buffering can just use this VertexFormat_t fmt = m_VertexFormat; // Set up the vertex descriptor MeshDesc_t desc = spewDesc; char tempbuf[256]; char* temp = tempbuf; sprintf( tempbuf,"\nVerts: (Vertex Format %llx)\n", fmt); Plat_DebugString(tempbuf); CVertexBufferBase::PrintVertexFormat( fmt ); int numBoneWeights = NumBoneWeights( fmt ); for ( i = 0; i < nVertexCount; ++i ) { temp += sprintf( temp, "[%4d] ", i + desc.m_nFirstVertex ); if( fmt & VERTEX_POSITION ) { D3DXVECTOR3& pos = Position( desc, i ); temp += sprintf(temp, "P %8.2f %8.2f %8.2f ", pos[0], pos[1], pos[2]); } if ( fmt & VERTEX_WRINKLE ) { float flWrinkle = Wrinkle( desc, i ); temp += sprintf(temp, "Wr %8.2f ",flWrinkle ); } if (numBoneWeights > 0) { temp += sprintf(temp, "BW "); float* pWeight = BoneWeight( desc, i ); for (int j = 0; j < numBoneWeights; ++j) { temp += sprintf(temp, "%1.2f ", pWeight[j]); } } if ( fmt & VERTEX_BONE_INDEX ) { unsigned char *pIndex = BoneIndex( desc, i ); temp += sprintf( temp, "BI %d %d %d %d ", ( int )pIndex[0], ( int )pIndex[1], ( int )pIndex[2], ( int )pIndex[3] ); Assert( pIndex[0] < 16 ); Assert( pIndex[1] < 16 ); Assert( pIndex[2] < 16 ); Assert( pIndex[3] < 16 ); } if ( fmt & VERTEX_NORMAL ) { D3DXVECTOR3& normal = Normal( desc, i ); temp += sprintf(temp, "N %1.2f %1.2f %1.2f ", normal[0], normal[1], normal[2]); } if (fmt & VERTEX_COLOR) { unsigned char* pColor = Color( desc, i ); temp += sprintf(temp, "C b %3d g %3d r %3d a %3d ", pColor[0], pColor[1], pColor[2], pColor[3]); } for (int j = 0; j < VERTEX_MAX_TEXTURE_COORDINATES; ++j) { if( TexCoordSize( j, fmt ) > 0) { D3DXVECTOR2& texcoord = TexCoord( desc, i, j ); temp += sprintf(temp, "T%d %.2f %.2f ", j,texcoord[0], texcoord[1]); } } if (fmt & VERTEX_TANGENT_S) { D3DXVECTOR3& tangentS = TangentS( desc, i ); temp += sprintf(temp, "S %1.2f %1.2f %1.2f ", tangentS[0], tangentS[1], tangentS[2]); } if (fmt & VERTEX_TANGENT_T) { D3DXVECTOR3& tangentT = TangentT( desc, i ); temp += sprintf(temp, "T %1.2f %1.2f %1.2f ", tangentT[0], tangentT[1], tangentT[2]); } sprintf(temp,"\n"); Plat_DebugString(tempbuf); temp = tempbuf; } sprintf( tempbuf,"\nIndices: %d\n", nIndexCount ); Plat_DebugString(tempbuf); for ( i = 0; i < nIndexCount; ++i ) { temp += sprintf( temp, "%d ", ( int )desc.m_pIndices[i] ); if ((i & 0x0F) == 0x0F) { sprintf( temp, "\n" ); Plat_DebugString(tempbuf); tempbuf[0] = '\0'; temp = tempbuf; } } sprintf(temp,"\n"); Plat_DebugString( tempbuf ); } void CBaseMeshDX8::ValidateData( int nVertexCount, int nIndexCount, const MeshDesc_t &spewDesc ) { LOCK_SHADERAPI(); #ifdef VALIDATE_DEBUG int i; // FIXME: just fall back to the base class (CVertexBufferBase) version of this function! // This is needed so buffering can just use this VertexFormat_t fmt = m_pMaterial->GetVertexUsage(); // Set up the vertex descriptor MeshDesc_t desc = spewDesc; int numBoneWeights = NumBoneWeights( fmt ); for ( i = 0; i < nVertexCount; ++i ) { if( fmt & VERTEX_POSITION ) { D3DXVECTOR3& pos = Position( desc, i ); Assert( IsFinite( pos[0] ) && IsFinite( pos[1] ) && IsFinite( pos[2] ) ); } if( fmt & VERTEX_WRINKLE ) { float flWrinkle = Wrinkle( desc, i ); Assert( IsFinite( flWrinkle ) ); } if (numBoneWeights > 0) { float* pWeight = BoneWeight( desc, i ); for (int j = 0; j < numBoneWeights; ++j) { Assert( pWeight[j] >= 0.0f && pWeight[j] <= 1.0f ); } } if( fmt & VERTEX_BONE_INDEX ) { unsigned char *pIndex = BoneIndex( desc, i ); Assert( pIndex[0] >= 0 && pIndex[0] < 16 ); Assert( pIndex[1] >= 0 && pIndex[1] < 16 ); Assert( pIndex[2] >= 0 && pIndex[2] < 16 ); Assert( pIndex[3] >= 0 && pIndex[3] < 16 ); } if( fmt & VERTEX_NORMAL ) { D3DXVECTOR3& normal = Normal( desc, i ); Assert( normal[0] >= -1.05f && normal[0] <= 1.05f ); Assert( normal[1] >= -1.05f && normal[1] <= 1.05f ); Assert( normal[2] >= -1.05f && normal[2] <= 1.05f ); } if (fmt & VERTEX_COLOR) { int* pColor = (int*)Color( desc, i ); Assert( *pColor != FLOAT32_NAN_BITS ); } for (int j = 0; j < VERTEX_MAX_TEXTURE_COORDINATES; ++j) { if( TexCoordSize( j, fmt ) > 0) { D3DXVECTOR2& texcoord = TexCoord( desc, i, j ); Assert( IsFinite( texcoord[0] ) && IsFinite( texcoord[1] ) ); } } if (fmt & VERTEX_TANGENT_S) { D3DXVECTOR3& tangentS = TangentS( desc, i ); Assert( IsFinite( tangentS[0] ) && IsFinite( tangentS[1] ) && IsFinite( tangentS[2] ) ); } if (fmt & VERTEX_TANGENT_T) { D3DXVECTOR3& tangentT = TangentT( desc, i ); Assert( IsFinite( tangentT[0] ) && IsFinite( tangentT[1] ) && IsFinite( tangentT[2] ) ); } } #endif // _DEBUG } void CBaseMeshDX8::Draw( CPrimList *pLists, int nLists ) { LOCK_SHADERAPI(); Assert( !"CBaseMeshDX8::Draw(CPrimList, int): should never get here." ); } // Copy verts and/or indices to a mesh builder. This only works for temp meshes! void CBaseMeshDX8::CopyToMeshBuilder( int iStartVert, // Which vertices to copy. int nVerts, int iStartIndex, // Which indices to copy. int nIndices, int indexOffset, // This is added to each index. CMeshBuilder &builder ) { LOCK_SHADERAPI(); Assert( false ); Warning( "CopyToMeshBuilder called on something other than a temp mesh.\n" ); } //----------------------------------------------------------------------------- // // static mesh // //----------------------------------------------------------------------------- CPrimList *CMeshDX8::s_pPrims; int CMeshDX8::s_nPrims; unsigned int CMeshDX8::s_FirstVertex; unsigned int CMeshDX8::s_NumVertices; //----------------------------------------------------------------------------- // Computes the mode //----------------------------------------------------------------------------- inline D3DPRIMITIVETYPE ComputeMode( MaterialPrimitiveType_t type ) { switch(type) { #ifdef _X360 case MATERIAL_INSTANCED_QUADS: return D3DPT_QUADLIST; #endif case MATERIAL_POINTS: return D3DPT_POINTLIST; case MATERIAL_LINES: return D3DPT_LINELIST; case MATERIAL_TRIANGLES: return D3DPT_TRIANGLELIST; case MATERIAL_TRIANGLE_STRIP: return D3DPT_TRIANGLESTRIP; // Here, we expect to have the type set later. only works for static meshes case MATERIAL_HETEROGENOUS: return (D3DPRIMITIVETYPE)-1; default: Assert(0); return (D3DPRIMITIVETYPE)-1; } } //----------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------- CMeshDX8::CMeshDX8( const char *pTextureGroupName ) : m_NumVertices(0), m_NumIndices(0), m_pVertexBuffer(0), m_pColorMesh( 0 ), m_nColorMeshVertOffsetInBytes( 0 ), m_pIndexBuffer(0), m_Type(MATERIAL_TRIANGLES), m_IsVBLocked(false), m_IsIBLocked(false) { m_pTextureGroupName = pTextureGroupName; m_Mode = ComputeMode(m_Type); m_bHasFlexVerts = false; m_pFlexVertexBuffer = NULL; m_nFlexVertOffsetInBytes = 0; } CMeshDX8::~CMeshDX8() { // Don't release the vertex buffer if (!g_MeshMgr.IsDynamicMesh(this)) { if (m_pVertexBuffer) { delete m_pVertexBuffer; } if (m_pIndexBuffer) { SafeRelease( &m_pIndexBuffer ); } } } void CMeshDX8::SetFlexMesh( IMesh *pMesh, int nVertexOffsetInBytes ) { if ( !ShaderUtil()->OnSetFlexMesh( this, pMesh, nVertexOffsetInBytes ) ) return; LOCK_SHADERAPI(); m_nFlexVertOffsetInBytes = nVertexOffsetInBytes; // Offset into dynamic mesh (in bytes) if ( pMesh ) { m_flexVertCount = pMesh->VertexCount(); pMesh->MarkAsDrawn(); CBaseMeshDX8 *pBaseMesh = static_cast(pMesh); m_pFlexVertexBuffer = pBaseMesh->GetVertexBuffer(); m_bHasFlexVerts = true; } else { m_flexVertCount = 0; m_pFlexVertexBuffer = NULL; m_bHasFlexVerts = false; } } void CMeshDX8::DisableFlexMesh( ) { CMeshDX8::SetFlexMesh( NULL, 0 ); } bool CMeshDX8::HasFlexMesh( ) const { LOCK_SHADERAPI(); return m_bHasFlexVerts; } void CMeshDX8::SetColorMesh( IMesh *pColorMesh, int nVertexOffsetInBytes ) { if ( !ShaderUtil()->OnSetColorMesh( this, pColorMesh, nVertexOffsetInBytes ) ) return; LOCK_SHADERAPI(); m_pColorMesh = ( CMeshDX8 * )pColorMesh; // dangerous conversion! garymcthack m_nColorMeshVertOffsetInBytes = nVertexOffsetInBytes; Assert( m_pColorMesh || ( nVertexOffsetInBytes == 0 ) ); #ifdef _DEBUG if ( pColorMesh ) { int nVertexCount = VertexCount(); int numVertsColorMesh = m_pColorMesh->VertexCount(); Assert( numVertsColorMesh >= nVertexCount ); } #endif } void CMeshDX8::HandleLateCreation( ) { if ( m_pVertexBuffer ) { m_pVertexBuffer->HandleLateCreation(); } if ( m_pIndexBuffer ) { m_pIndexBuffer->HandleLateCreation(); } if ( m_pFlexVertexBuffer ) { m_pFlexVertexBuffer->HandleLateCreation(); } if ( m_pColorMesh ) { m_pColorMesh->HandleLateCreation(); } } bool CMeshDX8::HasColorMesh( ) const { LOCK_SHADERAPI(); return (m_pColorMesh != NULL); } //----------------------------------------------------------------------------- // Locks/ unlocks the vertex buffer //----------------------------------------------------------------------------- bool CMeshDX8::Lock( int nVertexCount, bool bAppend, VertexDesc_t &desc ) { Assert( !m_IsVBLocked ); // Just give the app crap buffers to fill up while we're suppressed... if ( g_pShaderDeviceDx8->IsDeactivated() || (nVertexCount == 0)) { // Set up the vertex descriptor CVertexBufferBase::ComputeVertexDescription( 0, 0, desc ); desc.m_nFirstVertex = 0; return false; } // Static vertex buffer case if (!m_pVertexBuffer) { int size = g_MeshMgr.VertexFormatSize( m_VertexFormat ); m_pVertexBuffer = new CVertexBuffer( Dx9Device(), m_VertexFormat, 0, size, nVertexCount, m_pTextureGroupName, ShaderAPI()->UsingSoftwareVertexProcessing() ); } // Lock it baby int nMaxVerts, nMaxIndices; g_MeshMgr.GetMaxToRender( this, false, &nMaxVerts, &nMaxIndices ); if ( !g_pHardwareConfig->SupportsStreamOffset() ) { // Without stream offset, we can't use VBs greater than 65535 verts (due to our using 16-bit indices) Assert( nVertexCount <= nMaxVerts ); } unsigned char *pVertexMemory = m_pVertexBuffer->Lock( nVertexCount, desc.m_nFirstVertex ); if ( !pVertexMemory ) { if ( nVertexCount > nMaxVerts ) { Assert( 0 ); Error( "Too many verts for a dynamic vertex buffer (%d>%d) Tell a programmer to up VERTEX_BUFFER_SIZE.\n", ( int )nVertexCount, ( int )nMaxVerts ); } else { // Check if paged pool is in critical state ( < 5% free ) PAGED_POOL_INFO_t ppi; if ( ( SYSCALL_SUCCESS == Plat_GetPagedPoolInfo( &ppi ) ) && ( ( ppi.numPagesFree * 20 ) < ( ppi.numPagesUsed + ppi.numPagesFree ) ) ) { Error( "Out of OS Paged Pool Memory! For more information, please see\nhttp://support.steampowered.com/cgi-bin/steampowered.cfg/php/enduser/std_adp.php?p_faqid=150\n" ); } else { Assert( 0 ); Error( "failed to lock vertex buffer in CMeshDX8::LockVertexBuffer: nVertexCount=%d, nFirstVertex=%d\n", nVertexCount, desc.m_nFirstVertex ); } } CVertexBufferBase::ComputeVertexDescription( 0, 0, desc ); return false; } // Set up the vertex descriptor CVertexBufferBase::ComputeVertexDescription( pVertexMemory, m_VertexFormat, desc ); m_IsVBLocked = true; #ifdef RECORDING m_LockVertexBufferSize = nVertexCount * desc.m_ActualVertexSize; m_LockVertexBuffer = pVertexMemory; #endif return true; } void CMeshDX8::Unlock( int nVertexCount, VertexDesc_t& desc ) { // NOTE: This can happen if another application finishes // initializing during the construction of a mesh if (!m_IsVBLocked) return; // This is recorded for debugging. . not sent to dx. RECORD_COMMAND( DX8_SET_VERTEX_BUFFER_FORMAT, 2 ); RECORD_INT( m_pVertexBuffer->UID() ); RECORD_INT( m_VertexFormat ); RECORD_COMMAND( DX8_VERTEX_DATA, 3 ); RECORD_INT( m_pVertexBuffer->UID() ); RECORD_INT( m_LockVertexBufferSize ); RECORD_STRUCT( m_LockVertexBuffer, m_LockVertexBufferSize ); Assert(m_pVertexBuffer); m_pVertexBuffer->Unlock(nVertexCount); m_IsVBLocked = false; } //----------------------------------------------------------------------------- // Locks/unlocks the index buffer //----------------------------------------------------------------------------- int CMeshDX8::Lock( bool bReadOnly, int nFirstIndex, int nIndexCount, IndexDesc_t &desc ) { Assert( !m_IsIBLocked ); // Just give the app crap buffers to fill up while we're suppressed... if ( g_pShaderDeviceDx8->IsDeactivated() || (nIndexCount == 0)) { // Set up a bogus index descriptor desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; return 0; } // Static vertex buffer case if (!m_pIndexBuffer) { SafeAssign( &m_pIndexBuffer, new CIndexBuffer( Dx9Device(), nIndexCount, ShaderAPI()->UsingSoftwareVertexProcessing() ) ); } int startIndex; desc.m_pIndices = m_pIndexBuffer->Lock( bReadOnly, nIndexCount, startIndex, nFirstIndex ); if( !desc.m_pIndices ) { desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; // Check if paged pool is in critical state ( < 5% free ) PAGED_POOL_INFO_t ppi; if ( ( SYSCALL_SUCCESS == Plat_GetPagedPoolInfo( &ppi ) ) && ( ( ppi.numPagesFree * 20 ) < ( ppi.numPagesUsed + ppi.numPagesFree ) ) ) { Error( "Out of OS Paged Pool Memory! For more information, please see\nhttp://support.steampowered.com/cgi-bin/steampowered.cfg/php/enduser/std_adp.php?p_faqid=150\n" ); } else { Assert( 0 ); Error( "failed to lock index buffer in CMeshDX8::LockIndexBuffer\n" ); } return 0; } desc.m_nIndexSize = 1; m_IsIBLocked = true; #if defined( RECORDING ) || defined( CHECK_INDICES ) m_LockIndexBufferSize = nIndexCount * 2; m_LockIndexBuffer = desc.m_pIndices; #endif return startIndex; } void CMeshDX8::Unlock( int nIndexCount, IndexDesc_t &desc ) { // NOTE: This can happen if another application finishes // initializing during the construction of a mesh if (!m_IsIBLocked) return; RECORD_COMMAND( DX8_INDEX_DATA, 3 ); RECORD_INT( m_pIndexBuffer->UID() ); RECORD_INT( m_LockIndexBufferSize ); RECORD_STRUCT( m_LockIndexBuffer, m_LockIndexBufferSize ); Assert(m_pIndexBuffer); #ifdef CHECK_INDICES m_pIndexBuffer->UpdateShadowIndices( ( unsigned short * )m_LockIndexBuffer ); #endif // CHECK_INDICES // Unlock, and indicate how many vertices we actually used m_pIndexBuffer->Unlock(nIndexCount); m_IsIBLocked = false; } //----------------------------------------------------------------------------- // Locks/unlocks the entire mesh //----------------------------------------------------------------------------- void CMeshDX8::LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { ShaderUtil()->SyncMatrices(); g_ShaderMutex.Lock(); VPROF( "CMeshDX8::LockMesh" ); Lock( nVertexCount, false, *static_cast( &desc ) ); if ( m_Type != MATERIAL_POINTS ) { Lock( false, -1, nIndexCount, *static_cast( &desc ) ); } else { desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; } CBaseMeshDX8::m_bMeshLocked = true; } void CMeshDX8::UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { VPROF( "CMeshDX8::UnlockMesh" ); Assert( CBaseMeshDX8::m_bMeshLocked ); Unlock( nVertexCount, *static_cast( &desc ) ); if ( m_Type != MATERIAL_POINTS ) { Unlock( nIndexCount, *static_cast( &desc ) ); } // The actual # we wrote m_NumVertices = nVertexCount; m_NumIndices = nIndexCount; CBaseMeshDX8::m_bMeshLocked = false; g_ShaderMutex.Unlock(); } //----------------------------------------------------------------------------- // Locks mesh for modifying //----------------------------------------------------------------------------- void CMeshDX8::ModifyBeginEx( bool bReadOnly, int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ) { VPROF( "CMeshDX8::ModifyBegin" ); // Just give the app crap buffers to fill up while we're suppressed... if ( g_pShaderDeviceDx8->IsDeactivated()) { // Set up a bogus descriptor g_MeshMgr.ComputeVertexDescription( 0, 0, desc ); desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; return; } Assert( m_pVertexBuffer ); // Lock it baby unsigned char* pVertexMemory = m_pVertexBuffer->Modify( bReadOnly, nFirstVertex, nVertexCount ); if ( pVertexMemory ) { m_IsVBLocked = true; g_MeshMgr.ComputeVertexDescription( pVertexMemory, m_VertexFormat, desc ); #ifdef RECORDING m_LockVertexBufferSize = nVertexCount * desc.m_ActualVertexSize; m_LockVertexBuffer = pVertexMemory; #endif } desc.m_nFirstVertex = nFirstVertex; Lock( bReadOnly, nFirstIndex, nIndexCount, *static_cast( &desc ) ); } void CMeshDX8::ModifyBegin( int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ) { ModifyBeginEx( false, nFirstVertex, nVertexCount, nFirstIndex, nIndexCount, desc ); } void CMeshDX8::ModifyEnd( MeshDesc_t& desc ) { VPROF( "CMeshDX8::ModifyEnd" ); Unlock( 0, *static_cast( &desc ) ); Unlock( 0, *static_cast( &desc ) ); } //----------------------------------------------------------------------------- // returns the # of vertices (static meshes only) //----------------------------------------------------------------------------- int CMeshDX8::VertexCount() const { return m_pVertexBuffer ? m_pVertexBuffer->VertexCount() : 0; } //----------------------------------------------------------------------------- // returns the # of indices //----------------------------------------------------------------------------- int CMeshDX8::IndexCount( ) const { return m_pIndexBuffer ? m_pIndexBuffer->IndexCount() : 0; } //----------------------------------------------------------------------------- // Sets up the vertex and index buffers //----------------------------------------------------------------------------- void CMeshDX8::UseIndexBuffer( CIndexBuffer* pBuffer ) { SafeAssign( &m_pIndexBuffer, pBuffer ); } void CMeshDX8::UseVertexBuffer( CVertexBuffer* pBuffer ) { m_pVertexBuffer = pBuffer; } //----------------------------------------------------------------------------- // Sets the primitive type //----------------------------------------------------------------------------- void CMeshDX8::SetPrimitiveType( MaterialPrimitiveType_t type ) { Assert( IsX360() || ( type != MATERIAL_INSTANCED_QUADS ) ); if ( !ShaderUtil()->OnSetPrimitiveType( this, type ) ) { return; } LOCK_SHADERAPI(); m_Type = type; m_Mode = ComputeMode( type ); } MaterialPrimitiveType_t CMeshDX8::GetPrimitiveType( ) const { return m_Type; } //----------------------------------------------------------------------------- // Computes the number of primitives we're gonna draw //----------------------------------------------------------------------------- int CMeshDX8::NumPrimitives( int nVertexCount, int nIndexCount ) const { switch(m_Mode) { case D3DPT_POINTLIST: return nVertexCount; case D3DPT_LINELIST: return nIndexCount / 2; case D3DPT_TRIANGLELIST: return nIndexCount / 3; case D3DPT_TRIANGLESTRIP: return nIndexCount - 2; default: // invalid, baby! Assert(0); } return 0; } //----------------------------------------------------------------------------- // Checks if it's a valid format //----------------------------------------------------------------------------- #ifdef _DEBUG static void OutputVertexFormat( VertexFormat_t format ) { // FIXME: this is a duplicate of the function in meshdx8.cpp VertexCompressionType_t compressionType = CompressionType( format ); if ( format & VERTEX_POSITION ) { Warning( "VERTEX_POSITION|" ); } if ( format & VERTEX_NORMAL ) { if ( compressionType == VERTEX_COMPRESSION_ON ) Warning( "VERTEX_NORMAL[COMPRESSED]|" ); else Warning( "VERTEX_NORMAL|" ); } if ( format & VERTEX_COLOR ) { Warning( "VERTEX_COLOR|" ); } if ( format & VERTEX_SPECULAR ) { Warning( "VERTEX_SPECULAR|" ); } if ( format & VERTEX_TANGENT_S ) { Warning( "VERTEX_TANGENT_S|" ); } if ( format & VERTEX_TANGENT_T ) { Warning( "VERTEX_TANGENT_T|" ); } if ( format & VERTEX_BONE_INDEX ) { Warning( "VERTEX_BONE_INDEX|" ); } if ( format & VERTEX_FORMAT_VERTEX_SHADER ) { Warning( "VERTEX_FORMAT_VERTEX_SHADER|" ); } Warning( "\nBone weights: %d (%s)\n", NumBoneWeights( format ), ( CompressionType( format ) == VERTEX_COMPRESSION_ON ? "compressed" : "uncompressed" ) ); Warning( "user data size: %d (%s)\n", UserDataSize( format ), ( CompressionType( format ) == VERTEX_COMPRESSION_ON ? "compressed" : "uncompressed" ) ); Warning( "num tex coords: %d\n", NumTextureCoordinates( format ) ); // NOTE: This doesn't print texcoord sizes. } #endif bool CMeshDX8::IsValidVertexFormat( VertexFormat_t vertexFormat ) { // FIXME: Make this a debug-only check on say 6th July 2007 (after a week or so's testing) // (i.e. avoid the 360 release build perf. hit for when we ship) bool bCheckCompression = ( m_VertexFormat & VERTEX_FORMAT_COMPRESSED ) && ( ( vertexFormat == VERTEX_FORMAT_INVALID ) || ( ( vertexFormat & VERTEX_FORMAT_COMPRESSED ) == 0 ) ); if ( bCheckCompression || IsPC() || IsDebug() ) { IMaterialInternal* pMaterial = ShaderAPI()->GetBoundMaterial(); Assert( pMaterial ); // the material format should match the vertex usage, unless another format is passed in if ( vertexFormat == VERTEX_FORMAT_INVALID ) { vertexFormat = pMaterial->GetVertexUsage() & ~( VERTEX_FORMAT_VERTEX_SHADER | VERTEX_FORMAT_USE_EXACT_FORMAT ); // Blat out unused fields vertexFormat &= ~g_MeshMgr.UnusedVertexFields(); int nUnusedTextureCoords = g_MeshMgr.UnusedTextureCoords(); for ( int i = 0; i < VERTEX_MAX_TEXTURE_COORDINATES; ++i ) { if ( nUnusedTextureCoords & ( 1 << i ) ) { vertexFormat &= ~VERTEX_TEXCOORD_MASK( i ); } } } else { vertexFormat &= ~( VERTEX_FORMAT_VERTEX_SHADER | VERTEX_FORMAT_USE_EXACT_FORMAT ); } bool bIsValid = (( VERTEX_FORMAT_FIELD_MASK & vertexFormat ) & ( VERTEX_FORMAT_FIELD_MASK & ~m_VertexFormat )) == 0; if ( m_VertexFormat & VERTEX_FORMAT_COMPRESSED ) { // We shouldn't get compressed verts if this material doesn't support them! if ( ( vertexFormat & VERTEX_FORMAT_COMPRESSED ) == 0 ) { static int numWarnings = 0; if ( numWarnings++ == 0 ) { // NOTE: ComputeVertexFormat() will make sure no materials support VERTEX_FORMAT_COMPRESSED // if vertex compression is disabled in the config if ( g_pHardwareConfig->SupportsCompressedVertices() == VERTEX_COMPRESSION_NONE ) Warning( "ERROR: Compressed vertices in use but vertex compression is disabled (or not supported on this hardware)!\n" ); else Warning( "ERROR: Compressed vertices in use but material does not support them!\n" ); } Assert( 0 ); bIsValid = false; } } bIsValid = bIsValid && UserDataSize( m_VertexFormat ) >= UserDataSize( vertexFormat ); for ( int i=0; i < VERTEX_MAX_TEXTURE_COORDINATES; i++ ) { if ( TexCoordSize( i, m_VertexFormat ) < TexCoordSize( i, vertexFormat ) ) { bIsValid = false; } } // NOTE: It can totally be valid to have more weights than the current number of bones. // The -1 here is because if we have N bones, we can have only (N-1) weights, // since the Nth is implied (the weights sum to 1). int nWeightCount = NumBoneWeights( m_VertexFormat ); bIsValid = bIsValid && ( nWeightCount >= ( g_pShaderAPI->GetCurrentNumBones() - 1 ) ); #ifdef _DEBUG if ( !bIsValid ) { Warning( "Material Format:" ); if ( g_pShaderAPI->GetCurrentNumBones() > 0 ) { vertexFormat |= VERTEX_BONE_INDEX; vertexFormat &= ~VERTEX_BONE_WEIGHT_MASK; vertexFormat |= VERTEX_BONEWEIGHT( 2 ); } OutputVertexFormat( vertexFormat ); Warning( "Mesh Format:" ); OutputVertexFormat( m_VertexFormat ); } #endif return bIsValid; } return true; } //----------------------------------------------------------------------------- // Stream source setting methods //----------------------------------------------------------------------------- void CMeshDX8::SetVertexIDStreamState() { // FIXME: this method duplicates the code in CMeshMgr::SetVertexIDStreamState if ( IsX360() ) return; bool bUsingVertexID = IsUsingVertexID(); if ( bUsingVertexID != g_bUsingVertexID ) { if ( bUsingVertexID ) { // NOTE: Morphing doesn't work with dynamic buffers!!! BLEAH // It's because the indices (which are not 0 based for dynamic buffers) // are accessing both the vertexID buffer + the regular vertex buffer. // This *might* be fixable with baseVertexIndex? // NOTE: At the moment, vertex id is only used for hw morphing. I've got it // set up so that a shader that supports hw morphing always says it uses vertex id. // If we ever use vertex id for something other than hw morphing, we're going // to have to revisit how those shaders say they want to use vertex id // or fix this some other way Assert( !g_pShaderAPI->IsHWMorphingEnabled() || !m_pVertexBuffer->IsDynamic() ); CVertexBuffer *pVertexIDBuffer = g_MeshMgr.GetVertexIDBuffer( ); RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( pVertexIDBuffer->UID() ); RECORD_INT( 3 ); RECORD_INT( 0 ); RECORD_INT( pVertexIDBuffer->VertexSize() ); D3DSetStreamSource( 3, pVertexIDBuffer->GetInterface(), 0, pVertexIDBuffer->VertexSize() ); pVertexIDBuffer->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); } else { RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); // vertex buffer id RECORD_INT( 3 ); // stream RECORD_INT( 0 ); // vertex offset RECORD_INT( 0 ); // vertex size D3DSetStreamSource( 3, 0, 0, 0 ); } g_bUsingVertexID = bUsingVertexID; } } void CMeshDX8::SetColorStreamState() { if ( ( m_pColorMesh != g_pLastColorMesh ) || ( m_nColorMeshVertOffsetInBytes != g_nLastColorMeshVertOffsetInBytes ) ) { if ( m_pColorMesh ) { RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( m_pColorMesh->GetVertexBuffer()->UID() ); RECORD_INT( 1 ); RECORD_INT( m_nColorMeshVertOffsetInBytes ); RECORD_INT( m_pColorMesh->GetVertexBuffer()->VertexSize() ); D3DSetStreamSource( 1, m_pColorMesh->GetVertexBuffer()->GetInterface(), m_nColorMeshVertOffsetInBytes, m_pColorMesh->GetVertexBuffer()->VertexSize() ); m_pColorMesh->GetVertexBuffer()->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); } else { RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); // vertex buffer id RECORD_INT( 1 ); // stream RECORD_INT( 0 ); // vertex offset RECORD_INT( 0 ); // vertex size D3DSetStreamSource( 1, 0, 0, 0 ); } g_pLastColorMesh = m_pColorMesh; g_nLastColorMeshVertOffsetInBytes = m_nColorMeshVertOffsetInBytes; } } void CMeshDX8::SetVertexStreamState( int nVertOffsetInBytes ) { // Calls in here assume shader support... if ( HardwareConfig()->SupportsVertexAndPixelShaders() ) { if ( HasFlexMesh() ) { // m_pFlexVertexBuffer is the flex buffer down inside the CMeshMgr singleton D3DSetStreamSource( 2, m_pFlexVertexBuffer->GetInterface(), m_nFlexVertOffsetInBytes, m_pFlexVertexBuffer->VertexSize() ); if ( g_pHardwareConfig->Caps().m_SupportsVertexShaders_2_0 ) { float c[4] = { 1.0f, g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_b() ? 1.0f : 0.0f, 0.0f, 0.0f }; ShaderAPI()->SetVertexShaderConstant( VERTEX_SHADER_FLEXSCALE, c, 1 ); } g_bFlexMeshStreamSet = true; } else { Assert( nVertOffsetInBytes == 0 ); Assert( m_pVertexBuffer ); // HACK...point stream 2 at the same VB which is bound to stream 0... // NOTE: D3D debug DLLs will RIP if stream 0 has a smaller stride than the largest // offset in the stream 2 vertex decl elements (which are position(12)+wrinkle(4)+normal(12)) // If this fires, go find the material/shader which is requesting a really 'thin' // stream 0 vertex, and fatten it up slightly (e.g. add a D3DCOLOR element) int minimumStreamZeroStride = 4 * sizeof( float ); Assert( m_pVertexBuffer->VertexSize() >= minimumStreamZeroStride ); if ( m_pVertexBuffer->VertexSize() < minimumStreamZeroStride ) { static bool bWarned = false; if( !bWarned ) { Warning( "Shader specifying too-thin vertex format, should be at least %d bytes! (Suppressing furthur warnings)\n", minimumStreamZeroStride ); bWarned = true; } } // Set a 4kb all-zero static VB into the flex/wrinkle stream with a stride of 1 bytes, so the vertex shader always reads valid floating point values (otherwise it can get NaN's/Inf's, and under OpenGL this is bad on NVidia) // togl requires non-zero strides, but on D3D9 we can set a stride of 0 for a little more efficiency. D3DSetStreamSource( 2, g_MeshMgr.GetZeroVertexBuffer(), 0, IsOpenGL() ? 4 : 0 ); if ( g_pHardwareConfig->Caps().m_SupportsVertexShaders_2_0 ) { float c[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; ShaderAPI()->SetVertexShaderConstant( VERTEX_SHADER_FLEXSCALE, c, 1 ); } g_bFlexMeshStreamSet = false; } } // MESHFIXME: Make sure this jives between the mesh/ib/vb version. #ifdef _X360 if ( ( g_pLastVertex != m_pVertexBuffer ) || ( m_pVertexBuffer->IsDynamic() ) || ( g_nLastVertOffsetInBytes != nVertOffsetInBytes ) ) #else if ( ( g_pLastVertex != m_pVertexBuffer ) || ( g_nLastVertOffsetInBytes != nVertOffsetInBytes ) ) #endif { Assert( m_pVertexBuffer ); RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( m_pVertexBuffer->UID() ); RECORD_INT( 0 ); RECORD_INT( nVertOffsetInBytes ); RECORD_INT( m_pVertexBuffer->VertexSize() ); D3DSetStreamSource( 0, m_pVertexBuffer->GetInterface(), nVertOffsetInBytes, m_pVertexBuffer->VertexSize() ); m_pVertexBuffer->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); g_pLastVertex = m_pVertexBuffer; g_nLastVertOffsetInBytes = nVertOffsetInBytes; } } void CMeshDX8::SetIndexStreamState( int firstVertexIdx ) { #ifdef _X360 if ( ( g_pLastIndexBuffer != NULL ) || (g_pLastIndex != m_pIndexBuffer) || ( m_pIndexBuffer->IsDynamic() ) || ( firstVertexIdx != g_LastVertexIdx ) ) #else if ( ( g_pLastIndexBuffer != NULL ) || (g_pLastIndex != m_pIndexBuffer) || ( firstVertexIdx != g_LastVertexIdx ) ) #endif { Assert( m_pIndexBuffer ); RECORD_COMMAND( DX8_SET_INDICES, 2 ); RECORD_INT( m_pIndexBuffer->UID() ); RECORD_INT( firstVertexIdx ); Dx9Device()->SetIndices( m_pIndexBuffer->GetInterface() ); m_pIndexBuffer->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); m_FirstIndex = firstVertexIdx; SafeAssign( &g_pLastIndex, m_pIndexBuffer ); g_pLastIndexBuffer = NULL; g_LastVertexIdx = firstVertexIdx; } } bool CMeshDX8::SetRenderState( int nVertexOffsetInBytes, int nFirstVertexIdx, VertexFormat_t vertexFormat ) { // Can't set the state if we're deactivated if ( g_pShaderDeviceDx8->IsDeactivated() ) { ResetMeshRenderState(); return false; } g_LastVertexFormat = vertexFormat; SetVertexIDStreamState(); SetColorStreamState(); SetVertexStreamState( nVertexOffsetInBytes ); SetIndexStreamState( nFirstVertexIdx ); return true; } //----------------------------------------------------------------------------- // Draws the static mesh //----------------------------------------------------------------------------- void CMeshDX8::Draw( int nFirstIndex, int nIndexCount ) { Assert( m_pVertexBuffer ); if ( !m_pVertexBuffer ) { return; } if ( !ShaderUtil()->OnDrawMesh( this, nFirstIndex, nIndexCount ) ) { MarkAsDrawn(); return; } CPrimList primList; if( nFirstIndex == -1 || nIndexCount == 0 ) { primList.m_FirstIndex = 0; primList.m_NumIndices = m_NumIndices; } else { primList.m_FirstIndex = nFirstIndex; primList.m_NumIndices = nIndexCount; } DrawInternal( &primList, 1 ); } void CMeshDX8::Draw( CPrimList *pLists, int nLists ) { Assert( m_pVertexBuffer ); if ( !m_pVertexBuffer ) { return; } if ( !ShaderUtil()->OnDrawMesh( this, pLists, nLists ) ) { MarkAsDrawn(); return; } DrawInternal( pLists, nLists ); } void CMeshDX8::DrawInternal( CPrimList *pLists, int nLists ) { HandleLateCreation(); // Make sure there's something to draw.. int i; for ( i=0; i < nLists; i++ ) { if ( pLists[i].m_NumIndices > 0 ) break; } if ( i == nLists ) return; // can't do these in selection mode! Assert( !ShaderAPI()->IsInSelectionMode() ); if ( !SetRenderState( 0, 0 ) ) return; s_pPrims = pLists; s_nPrims = nLists; #ifdef _DEBUG for ( i = 0; i < nLists; ++i) { Assert( pLists[i].m_NumIndices > 0 ); } #endif s_FirstVertex = 0; s_NumVertices = m_pVertexBuffer->VertexCount(); DrawMesh(); } #ifdef CHECK_INDICES void CMeshDX8::CheckIndices( CPrimList *pPrim, int numPrimitives ) { // g_pLastVertex - this is the current vertex buffer // g_pLastColorMesh - this is the current color mesh, if there is one. // g_pLastIndex - this is the current index buffer. // vertoffset : m_FirstIndex if( m_Mode == D3DPT_TRIANGLELIST || m_Mode == D3DPT_TRIANGLESTRIP ) { Assert( pPrim->m_FirstIndex >= 0 && pPrim->m_FirstIndex < g_pLastIndex->IndexCount() ); int i; for( i = 0; i < 2; i++ ) { CVertexBuffer *pMesh; if( i == 0 ) { pMesh = g_pLastVertex; Assert( pMesh ); } else { if( !g_pLastColorMesh ) { continue; } pMesh = g_pLastColorMesh->m_pVertexBuffer; if( !pMesh ) { continue; } } Assert( s_FirstVertex >= 0 && (int)( s_FirstVertex + m_FirstIndex ) < pMesh->VertexCount() ); int nIndexCount = 0; if( m_Mode == D3DPT_TRIANGLELIST ) { nIndexCount = numPrimitives * 3; } else if( m_Mode == D3DPT_TRIANGLESTRIP ) { nIndexCount = numPrimitives + 2; } else { Assert( 0 ); } int j; for( j = 0; j < nIndexCount; j++ ) { int index = g_pLastIndex->GetShadowIndex( j + pPrim->m_FirstIndex ); if ( ( index < (int)s_FirstVertex ) || ( index >= (int)( s_FirstVertex + s_NumVertices ) ) ) Warning("%s invalid index: %d [%u..%u]\n", __FUNCTION__, index, s_FirstVertex, s_FirstVertex + s_NumVertices - 1 ); Assert( index >= (int)s_FirstVertex ); Assert( index < (int)(s_FirstVertex + s_NumVertices) ); } } } } #endif // CHECK_INDICES //----------------------------------------------------------------------------- // Actually does the dirty deed of rendering //----------------------------------------------------------------------------- void CMeshDX8::RenderPass() { LOCK_SHADERAPI(); VPROF( "CMeshDX8::RenderPass" ); HandleLateCreation(); Assert( m_Type != MATERIAL_HETEROGENOUS ); // make sure the vertex format is a superset of the current material's // vertex format... if ( !IsValidVertexFormat( g_LastVertexFormat ) ) { Warning( "Material %s does not support vertex format used by the mesh (maybe missing fields or mismatched vertex compression?), mesh will not be rendered. Grab a programmer!\n", ShaderAPI()->GetBoundMaterial()->GetName() ); return; } for ( int iPrim=0; iPrim < s_nPrims; iPrim++ ) { CPrimList *pPrim = &s_pPrims[iPrim]; if ( pPrim->m_NumIndices == 0 ) continue; if ( ( m_Type == MATERIAL_POINTS ) || ( m_Type == MATERIAL_INSTANCED_QUADS ) ) { tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "Dx9Device_DrawPrimitive" ); // (For point/instanced-quad lists, we don't actually fill in indices, but we treat it as // though there are indices for the list up until here). Dx9Device()->DrawPrimitive( m_Mode, s_FirstVertex, pPrim->m_NumIndices ); } else { int numPrimitives = NumPrimitives( s_NumVertices, pPrim->m_NumIndices ); #ifdef CHECK_INDICES CheckIndices( pPrim, numPrimitives ); #endif // CHECK_INDICES { VPROF( "Dx9Device()->DrawIndexedPrimitive" ); VPROF_INCREMENT_COUNTER( "DrawIndexedPrimitive", 1 ); VPROF_INCREMENT_COUNTER( "numPrimitives", numPrimitives ); VPROF_INCREMENT_GROUP_COUNTER( "render/DrawIndexedPrimitive", COUNTER_GROUP_TELEMETRY, 1 ); VPROF_INCREMENT_GROUP_COUNTER( "render/numPrimitives", COUNTER_GROUP_TELEMETRY, 1 ); Dx9Device()->DrawIndexedPrimitive( m_Mode, // Member of the D3DPRIMITIVETYPE enumerated type, describing the type of primitive to render. D3DPT_POINTLIST is not supported with this method. m_FirstIndex, // Offset from the start of the vertex buffer to the first vertex index. An index of 0 in the index buffer refers to this location in the vertex buffer. s_FirstVertex, // Minimum vertex index for vertices used during this call. This is a zero based index relative to BaseVertexIndex. // The first Vertex in the vertexbuffer that we are currently using for the current batch. s_NumVertices, // Number of vertices used during this call. The first vertex is located at index: BaseVertexIndex + MinIndex. pPrim->m_FirstIndex, // Index of the first index to use when accesssing the vertex buffer. Beginning at StartIndex to index vertices from the vertex buffer. numPrimitives );// Number of primitives to render. The number of vertices used is a function of the primitive count and the primitive type. } } } if ( g_pLastVertex ) { g_pLastVertex->MarkUsedInRendering(); } if( g_pLastIndex ) { g_pLastIndex->MarkUsedInRendering(); } } //----------------------------------------------------------------------------- // // Dynamic mesh implementation // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- CDynamicMeshDX8::CDynamicMeshDX8() : CMeshDX8( "CDynamicMeshDX8" ) { m_nBufferId = 0; ResetVertexAndIndexCounts(); } CDynamicMeshDX8::~CDynamicMeshDX8() { } //----------------------------------------------------------------------------- // Initializes the dynamic mesh //----------------------------------------------------------------------------- void CDynamicMeshDX8::Init( int nBufferId ) { m_nBufferId = nBufferId; } //----------------------------------------------------------------------------- // Resets buffering state //----------------------------------------------------------------------------- void CDynamicMeshDX8::ResetVertexAndIndexCounts() { m_TotalVertices = m_TotalIndices = 0; m_FirstIndex = m_nFirstVertex = -1; m_HasDrawn = false; } //----------------------------------------------------------------------------- // Resets the state in case of a task switch //----------------------------------------------------------------------------- void CDynamicMeshDX8::Reset() { m_VertexFormat = 0; m_pVertexBuffer = 0; SafeRelease( &m_pIndexBuffer ); ResetVertexAndIndexCounts(); // Force the render state to be updated next time ResetMeshRenderState(); } //----------------------------------------------------------------------------- // Sets the vertex format associated with the dynamic mesh //----------------------------------------------------------------------------- void CDynamicMeshDX8::SetVertexFormat( VertexFormat_t format ) { if ( g_pShaderDeviceDx8->IsDeactivated()) return; if ( CompressionType( format ) != VERTEX_COMPRESSION_NONE ) { // UNDONE: support compressed dynamic meshes if needed (pro: less VB memory, con: CMeshBuilder gets slower) Warning( "ERROR: dynamic meshes cannot use compressed vertices!\n" ); Assert( 0 ); format &= ~VERTEX_FORMAT_COMPRESSED; } if ((format != m_VertexFormat) || m_VertexOverride || m_IndexOverride) { m_VertexFormat = format; UseVertexBuffer( g_MeshMgr.FindOrCreateVertexBuffer( m_nBufferId, format ) ); if ( m_nBufferId == 0 ) { UseIndexBuffer( g_MeshMgr.GetDynamicIndexBuffer() ); } m_VertexOverride = m_IndexOverride = false; } } void CDynamicMeshDX8::OverrideVertexBuffer( CVertexBuffer* pVertexBuffer ) { UseVertexBuffer( pVertexBuffer ); m_VertexOverride = true; } void CDynamicMeshDX8::OverrideIndexBuffer( CIndexBuffer* pIndexBuffer ) { UseIndexBuffer( pIndexBuffer ); m_IndexOverride = true; } //----------------------------------------------------------------------------- // Do I need to reset the vertex format? //----------------------------------------------------------------------------- bool CDynamicMeshDX8::NeedsVertexFormatReset( VertexFormat_t fmt ) const { return m_VertexOverride || m_IndexOverride || (m_VertexFormat != fmt); } //----------------------------------------------------------------------------- // Locks/unlocks the entire mesh //----------------------------------------------------------------------------- bool CDynamicMeshDX8::HasEnoughRoom( int nVertexCount, int nIndexCount ) const { if ( g_pShaderDeviceDx8->IsDeactivated() ) return false; Assert( m_pVertexBuffer != NULL ); // We need space in both the vertex and index buffer return m_pVertexBuffer->HasEnoughRoom( nVertexCount ) && m_pIndexBuffer->HasEnoughRoom( nIndexCount ); } //----------------------------------------------------------------------------- // returns the number of indices in the mesh //----------------------------------------------------------------------------- int CDynamicMeshDX8::IndexCount( ) const { return m_TotalIndices; } //----------------------------------------------------------------------------- // Operation to do pre-lock (only called for buffered meshes) //----------------------------------------------------------------------------- void CDynamicMeshDX8::PreLock() { if (m_HasDrawn) { // Start again then ResetVertexAndIndexCounts(); } } //----------------------------------------------------------------------------- // Locks/unlocks the entire mesh //----------------------------------------------------------------------------- void CDynamicMeshDX8::LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %d %d", __FUNCTION__, nVertexCount, nIndexCount ); ShaderUtil()->SyncMatrices(); { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "g_ShaderMutex.Lock" ); g_ShaderMutex.Lock(); } // Yes, this may well also be called from BufferedMesh but that's ok { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "PreLock" ); PreLock(); } if (m_VertexOverride) { nVertexCount = 0; } if (m_IndexOverride) { nIndexCount = 0; } { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "Lock" ); Lock( nVertexCount, false, *static_cast( &desc ) ); } if (m_nFirstVertex < 0) { m_nFirstVertex = desc.m_nFirstVertex; } // When we're using a static index buffer or a flex mesh, the indices assume vertices start at 0 if ( m_IndexOverride || HasFlexMesh() ) { desc.m_nFirstVertex -= m_nFirstVertex; } // Don't add indices for points; DrawIndexedPrimitive not supported for them. if ( m_Type != MATERIAL_POINTS ) { int nFirstIndex; { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "Lock nFirstIndex" ); nFirstIndex = Lock( false, -1, nIndexCount, *static_cast( &desc ) ); } if (m_FirstIndex < 0) { m_FirstIndex = nFirstIndex; } } else { desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; } CBaseMeshDX8::m_bMeshLocked = true; } //----------------------------------------------------------------------------- // Unlocks the mesh //----------------------------------------------------------------------------- void CDynamicMeshDX8::UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { m_TotalVertices += nVertexCount; m_TotalIndices += nIndexCount; if (DebugTrace()) { Spew( nVertexCount, nIndexCount, desc ); } CMeshDX8::UnlockMesh( nVertexCount, nIndexCount, desc ); // This is handled in the CMeshDX8::UnlockMesh above. //CBaseMeshDX8::m_bMeshLocked = false; } //----------------------------------------------------------------------------- // Draws it //----------------------------------------------------------------------------- void CDynamicMeshDX8::Draw( int nFirstIndex, int nIndexCount ) { if ( !ShaderUtil()->OnDrawMesh( this, nFirstIndex, nIndexCount ) ) { MarkAsDrawn(); return; } VPROF( "CDynamicMeshDX8::Draw" ); m_HasDrawn = true; if (m_IndexOverride || m_VertexOverride || ( ( m_TotalVertices > 0 ) && ( m_TotalIndices > 0 || m_Type == MATERIAL_POINTS || m_Type == MATERIAL_INSTANCED_QUADS ) ) ) { Assert( !m_IsDrawing ); HandleLateCreation( ); // only have a non-zero first vertex when we are using static indices int nFirstVertex = m_VertexOverride ? 0 : m_nFirstVertex; int actualFirstVertex = m_IndexOverride ? nFirstVertex : 0; int nVertexOffsetInBytes = HasFlexMesh() ? nFirstVertex * g_MeshMgr.VertexFormatSize( GetVertexFormat() ) : 0; int baseIndex = m_IndexOverride ? 0 : m_FirstIndex; // Overriding with the dynamic index buffer, preserve state! if ( m_IndexOverride && m_pIndexBuffer == g_MeshMgr.GetDynamicIndexBuffer() ) { baseIndex = m_FirstIndex; } VertexFormat_t fmt = m_VertexOverride ? GetVertexFormat() : VERTEX_FORMAT_INVALID; if ( !SetRenderState( nVertexOffsetInBytes, actualFirstVertex, fmt ) ) return; // Draws a portion of the mesh int numVertices = m_VertexOverride ? m_pVertexBuffer->VertexCount() : m_TotalVertices; if ((nFirstIndex != -1) && (nIndexCount != 0)) { nFirstIndex += baseIndex; } else { // by default we draw the whole thing nFirstIndex = baseIndex; if( m_IndexOverride ) { nIndexCount = m_pIndexBuffer->IndexCount(); Assert( nIndexCount != 0 ); } else { nIndexCount = m_TotalIndices; // Fake out the index count if we're drawing points/instanced-quads if ( ( m_Type == MATERIAL_POINTS ) || ( m_Type == MATERIAL_INSTANCED_QUADS ) ) { nIndexCount = m_TotalVertices; } Assert( nIndexCount != 0 ); } } // Fix up nFirstVertex to indicate the first vertex used in the data if ( !HasFlexMesh() ) { actualFirstVertex = nFirstVertex - actualFirstVertex; } s_FirstVertex = actualFirstVertex; s_NumVertices = numVertices; // Build a primlist with 1 element.. CPrimList prim; prim.m_FirstIndex = nFirstIndex; prim.m_NumIndices = nIndexCount; Assert( nIndexCount != 0 ); s_pPrims = &prim; s_nPrims = 1; DrawMesh(); s_pPrims = NULL; } } //----------------------------------------------------------------------------- // This is useful when we need to dynamically modify data; just set the // render state and draw the pass immediately //----------------------------------------------------------------------------- void CDynamicMeshDX8::DrawSinglePassImmediately() { if ((m_TotalVertices > 0) || (m_TotalIndices > 0)) { Assert( !m_IsDrawing ); // Set the render state if ( SetRenderState( 0, 0 ) ) { s_FirstVertex = m_nFirstVertex; s_NumVertices = m_TotalVertices; // Make a temporary PrimList to hold the indices. CPrimList prim( m_FirstIndex, m_TotalIndices ); Assert( m_TotalIndices != 0 ); s_pPrims = &prim; s_nPrims = 1; // Render it RenderPass(); } // We're done with our data ResetVertexAndIndexCounts(); } } //----------------------------------------------------------------------------- // // A mesh that stores temporary vertex data in the correct format (for modification) // //----------------------------------------------------------------------------- // Used in rendering sub-parts of the mesh unsigned int CTempMeshDX8::s_NumIndices; unsigned int CTempMeshDX8::s_FirstIndex; //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- CTempMeshDX8::CTempMeshDX8( bool isDynamic ) : m_VertexSize(0xFFFF), m_IsDynamic(isDynamic) { #ifdef DBGFLAG_ASSERT m_Locked = false; m_InPass = false; #endif } CTempMeshDX8::~CTempMeshDX8() { } //----------------------------------------------------------------------------- // Is the temp mesh dynamic? //----------------------------------------------------------------------------- bool CTempMeshDX8::IsDynamic() const { return m_IsDynamic; } //----------------------------------------------------------------------------- // Sets the vertex format //----------------------------------------------------------------------------- void CTempMeshDX8::SetVertexFormat( VertexFormat_t format ) { CBaseMeshDX8::SetVertexFormat(format); m_VertexSize = g_MeshMgr.VertexFormatSize( format ); } //----------------------------------------------------------------------------- // returns the # of vertices (static meshes only) //----------------------------------------------------------------------------- int CTempMeshDX8::VertexCount() const { return m_VertexSize ? m_VertexData.Count() / m_VertexSize : 0; } //----------------------------------------------------------------------------- // returns the # of indices //----------------------------------------------------------------------------- int CTempMeshDX8::IndexCount( ) const { return m_IndexData.Count(); } void CTempMeshDX8::ModifyBeginEx( bool bReadOnly, int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ) { Assert( !m_Locked ); m_LockedVerts = nVertexCount; m_LockedIndices = nIndexCount; if( nVertexCount > 0 ) { int vertexByteOffset = m_VertexSize * nFirstVertex; // Lock it baby unsigned char* pVertexMemory = &m_VertexData[vertexByteOffset]; // Compute the vertex index.. desc.m_nFirstVertex = vertexByteOffset / m_VertexSize; // Set up the mesh descriptor g_MeshMgr.ComputeVertexDescription( pVertexMemory, m_VertexFormat, desc ); } else { desc.m_nFirstVertex = 0; // Set up the mesh descriptor g_MeshMgr.ComputeVertexDescription( 0, 0, desc ); } if (m_Type != MATERIAL_POINTS && nIndexCount > 0 ) { desc.m_pIndices = &m_IndexData[nFirstIndex]; desc.m_nIndexSize = 1; } else { desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; } #ifdef DBGFLAG_ASSERT m_Locked = true; #endif } void CTempMeshDX8::ModifyBegin( int nFirstVertex, int nVertexCount, int nFirstIndex, int nIndexCount, MeshDesc_t& desc ) { ModifyBeginEx( false, nFirstVertex, nVertexCount, nFirstIndex, nIndexCount, desc ); } void CTempMeshDX8::ModifyEnd( MeshDesc_t& desc ) { #ifdef DBGFLAG_ASSERT Assert( m_Locked ); m_Locked = false; #endif } //----------------------------------------------------------------------------- // Locks/unlocks the mesh //----------------------------------------------------------------------------- void CTempMeshDX8::LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { ShaderUtil()->SyncMatrices(); g_ShaderMutex.Lock(); Assert( !m_Locked ); m_LockedVerts = nVertexCount; m_LockedIndices = nIndexCount; if( nVertexCount > 0 ) { int vertexByteOffset = m_VertexData.AddMultipleToTail( m_VertexSize * nVertexCount ); // Lock it baby unsigned char* pVertexMemory = &m_VertexData[vertexByteOffset]; // Compute the vertex index.. desc.m_nFirstVertex = vertexByteOffset / m_VertexSize; // Set up the mesh descriptor g_MeshMgr.ComputeVertexDescription( pVertexMemory, m_VertexFormat, desc ); } else { desc.m_nFirstVertex = 0; // Set up the mesh descriptor g_MeshMgr.ComputeVertexDescription( 0, 0, desc ); } if (m_Type != MATERIAL_POINTS && nIndexCount > 0 ) { int nFirstIndex = m_IndexData.AddMultipleToTail( nIndexCount ); desc.m_pIndices = &m_IndexData[nFirstIndex]; desc.m_nIndexSize = 1; } else { desc.m_pIndices = g_nScratchIndexBuffer; desc.m_nIndexSize = 0; } #ifdef DBGFLAG_ASSERT m_Locked = true; #endif CBaseMeshDX8::m_bMeshLocked = true; } void CTempMeshDX8::UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { Assert( m_Locked ); // Remove unused vertices and indices int verticesToRemove = m_LockedVerts - nVertexCount; if( verticesToRemove != 0 ) { m_VertexData.RemoveMultiple( m_VertexData.Count() - verticesToRemove, verticesToRemove ); } int indicesToRemove = m_LockedIndices - nIndexCount; if( indicesToRemove != 0 ) { m_IndexData.RemoveMultiple( m_IndexData.Count() - indicesToRemove, indicesToRemove ); } #ifdef DBGFLAG_ASSERT m_Locked = false; #endif CBaseMeshDX8::m_bMeshLocked = false; g_ShaderMutex.Unlock(); } //----------------------------------------------------------------------------- // Sets the primitive type //----------------------------------------------------------------------------- void CTempMeshDX8::SetPrimitiveType( MaterialPrimitiveType_t type ) { // FIXME: Support MATERIAL_INSTANCED_QUADS for CTempMeshDX8 (X360 only) Assert( ( type != MATERIAL_INSTANCED_QUADS ) /* || IsX360() */ ); m_Type = type; } MaterialPrimitiveType_t CTempMeshDX8::GetPrimitiveType( ) const { return m_Type; } //----------------------------------------------------------------------------- // Gets the dynamic mesh //----------------------------------------------------------------------------- CDynamicMeshDX8* CTempMeshDX8::GetDynamicMesh( ) { return static_cast(g_MeshMgr.GetActualDynamicMesh( m_VertexFormat )); } //----------------------------------------------------------------------------- // Draws the entire mesh //----------------------------------------------------------------------------- void CTempMeshDX8::Draw( int nFirstIndex, int nIndexCount ) { if ( !ShaderUtil()->OnDrawMesh( this, nFirstIndex, nIndexCount ) ) { MarkAsDrawn(); return; } if (m_VertexData.Count() > 0) { if ( !g_pShaderDeviceDx8->IsDeactivated() ) { #ifdef DRAW_SELECTION if (!g_bDrawSelection && !ShaderAPI()->IsInSelectionMode()) #else if (!ShaderAPI()->IsInSelectionMode()) #endif { s_FirstIndex = nFirstIndex; s_NumIndices = nIndexCount; DrawMesh( ); // This assertion fails if a BeginPass() call was not matched by // a RenderPass() call Assert(!m_InPass); } else { TestSelection(); } } // Clear out the data if this temp mesh is a dynamic one... if (m_IsDynamic) { m_VertexData.RemoveAll(); m_IndexData.RemoveAll(); } } } void CTempMeshDX8::CopyToMeshBuilder( int iStartVert, // Which vertices to copy. int nVerts, int iStartIndex, // Which indices to copy. int nIndices, int indexOffset, // This is added to each index. CMeshBuilder &builder ) { int startOffset = iStartVert * m_VertexSize; int endOffset = (iStartVert + nVerts) * m_VertexSize; Assert( startOffset >= 0 && startOffset <= m_VertexData.Count() ); Assert( endOffset >= 0 && endOffset <= m_VertexData.Count() && endOffset >= startOffset ); if ( endOffset > startOffset ) { // FIXME: make this a method of CMeshBuilder (so the 'Position' pointer accessor can be removed) // make sure it takes a VertexFormat_t parameter for src/dest match validation memcpy( (void*)builder.Position(), &m_VertexData[startOffset], endOffset - startOffset ); builder.AdvanceVertices( nVerts ); } for ( int i = 0; i < nIndices; ++i ) { builder.Index( m_IndexData[iStartIndex+i] + indexOffset ); builder.AdvanceIndex(); } } //----------------------------------------------------------------------------- // Selection mode helper functions //----------------------------------------------------------------------------- static void ComputeModelToView( D3DXMATRIX& modelToView ) { // Get the modelview matrix... D3DXMATRIX world, view; ShaderAPI()->GetMatrix( MATERIAL_MODEL, (float*)&world ); ShaderAPI()->GetMatrix( MATERIAL_VIEW, (float*)&view ); D3DXMatrixMultiply( &modelToView, &world, &view ); } static float ComputeCullFactor( ) { D3DCULL cullMode = ShaderAPI()->GetCullMode(); float cullFactor; switch(cullMode) { case D3DCULL_CCW: cullFactor = -1.0f; break; case D3DCULL_CW: cullFactor = 1.0f; break; default: cullFactor = 0.0f; break; }; return cullFactor; } //----------------------------------------------------------------------------- // Clip to viewport //----------------------------------------------------------------------------- static int g_NumClipVerts; static D3DXVECTOR3 g_ClipVerts[16]; static bool PointInsidePlane( D3DXVECTOR3* pVert, int normalInd, float val, bool nearClip ) { if ((val > 0) || nearClip) return (val - (*pVert)[normalInd] >= 0); else return ((*pVert)[normalInd] - val >= 0); } static void IntersectPlane( D3DXVECTOR3* pStart, D3DXVECTOR3* pEnd, int normalInd, float val, D3DXVECTOR3* pOutVert ) { D3DXVECTOR3 dir; D3DXVec3Subtract( &dir, pEnd, pStart ); Assert( dir[normalInd] != 0.0f ); float t = (val - (*pStart)[normalInd]) / dir[normalInd]; pOutVert->x = pStart->x + dir.x * t; pOutVert->y = pStart->y + dir.y * t; pOutVert->z = pStart->z + dir.z * t; // Avoid any precision problems. (*pOutVert)[normalInd] = val; } static int ClipTriangleAgainstPlane( D3DXVECTOR3** ppVert, int nVertexCount, D3DXVECTOR3** ppOutVert, int normalInd, float val, bool nearClip = false ) { // Ye Olde Sutherland-Hodgman clipping algorithm int numOutVerts = 0; D3DXVECTOR3* pStart = ppVert[nVertexCount-1]; bool startInside = PointInsidePlane( pStart, normalInd, val, nearClip ); for (int i = 0; i < nVertexCount; ++i) { D3DXVECTOR3* pEnd = ppVert[i]; bool endInside = PointInsidePlane( pEnd, normalInd, val, nearClip ); if (endInside) { if (!startInside) { IntersectPlane( pStart, pEnd, normalInd, val, &g_ClipVerts[g_NumClipVerts] ); ppOutVert[numOutVerts++] = &g_ClipVerts[g_NumClipVerts++]; } ppOutVert[numOutVerts++] = pEnd; } else { if (startInside) { IntersectPlane( pStart, pEnd, normalInd, val, &g_ClipVerts[g_NumClipVerts] ); ppOutVert[numOutVerts++] = &g_ClipVerts[g_NumClipVerts++]; } } pStart = pEnd; startInside = endInside; } return numOutVerts; } void CTempMeshDX8::ClipTriangle( D3DXVECTOR3** ppVert, float zNear, D3DXMATRIX& projection ) { int i; int nVertexCount = 3; D3DXVECTOR3* ppClipVert1[10]; D3DXVECTOR3* ppClipVert2[10]; g_NumClipVerts = 0; // Clip against the near plane in view space to prevent negative w. // Clip against each plane nVertexCount = ClipTriangleAgainstPlane( ppVert, nVertexCount, ppClipVert1, 2, zNear, true ); if (nVertexCount < 3) return; // Sucks that I have to do this, but I have to clip near plane in view space // Clipping in projection space is screwy when w < 0 // Transform the clipped points into projection space Assert( g_NumClipVerts <= 2 ); for (i = 0; i < nVertexCount; ++i) { if (ppClipVert1[i] == &g_ClipVerts[0]) { D3DXVec3TransformCoord( &g_ClipVerts[0], ppClipVert1[i], &projection ); } else if (ppClipVert1[i] == &g_ClipVerts[1]) { D3DXVec3TransformCoord( &g_ClipVerts[1], ppClipVert1[i], &projection ); } else { D3DXVec3TransformCoord( &g_ClipVerts[g_NumClipVerts], ppClipVert1[i], &projection ); ppClipVert1[i] = &g_ClipVerts[g_NumClipVerts]; ++g_NumClipVerts; } } nVertexCount = ClipTriangleAgainstPlane( ppClipVert1, nVertexCount, ppClipVert2, 2, 1.0f ); if (nVertexCount < 3) return; nVertexCount = ClipTriangleAgainstPlane( ppClipVert2, nVertexCount, ppClipVert1, 0, 1.0f ); if (nVertexCount < 3) return; nVertexCount = ClipTriangleAgainstPlane( ppClipVert1, nVertexCount, ppClipVert2, 0, -1.0f ); if (nVertexCount < 3) return; nVertexCount = ClipTriangleAgainstPlane( ppClipVert2, nVertexCount, ppClipVert1, 1, 1.0f ); if (nVertexCount < 3) return; nVertexCount = ClipTriangleAgainstPlane( ppClipVert1, nVertexCount, ppClipVert2, 1, -1.0f ); if (nVertexCount < 3) return; #ifdef DRAW_SELECTION if( 1 || g_bDrawSelection ) { srand( *(int*)(&ppClipVert2[0]->x) ); unsigned char r = (unsigned char)(rand() * 191.0f / VALVE_RAND_MAX) + 64; unsigned char g = (unsigned char)(rand() * 191.0f / VALVE_RAND_MAX) + 64; unsigned char b = (unsigned char)(rand() * 191.0f / VALVE_RAND_MAX) + 64; ShaderAPI()->SetupSelectionModeVisualizationState(); CMeshBuilder* pMeshBuilder = ShaderAPI()->GetVertexModifyBuilder(); IMesh* pMesh = GetDynamicMesh(); pMeshBuilder->Begin( pMesh, MATERIAL_POLYGON, nVertexCount ); for ( i = 0; i < nVertexCount; ++i) { pMeshBuilder->Position3fv( *ppClipVert2[i] ); pMeshBuilder->Color3ub( r, g, b ); pMeshBuilder->AdvanceVertex(); } pMeshBuilder->End(); pMesh->Draw(); pMeshBuilder->Begin( pMesh, MATERIAL_LINE_LOOP, nVertexCount ); for ( i = 0; i < nVertexCount; ++i) { pMeshBuilder->Position3fv( *ppClipVert2[i] ); pMeshBuilder->Color3ub( 255, 255, 255 ); pMeshBuilder->AdvanceVertex(); } pMeshBuilder->End(); pMesh->Draw(); } #endif // Compute closest and furthest verts float minz = ppClipVert2[0]->z; float maxz = ppClipVert2[0]->z; for ( i = 1; i < nVertexCount; ++i ) { if (ppClipVert2[i]->z < minz) minz = ppClipVert2[i]->z; else if (ppClipVert2[i]->z > maxz) maxz = ppClipVert2[i]->z; } ShaderAPI()->RegisterSelectionHit( minz, maxz ); } //----------------------------------------------------------------------------- // Selection mode //----------------------------------------------------------------------------- void CTempMeshDX8::TestSelection() { // Note that this doesn't take into account any vertex modification // done in a vertex shader. Also it doesn't take into account any clipping // done in hardware // Blow off points and lines; they don't matter if ((m_Type != MATERIAL_TRIANGLES) && (m_Type != MATERIAL_TRIANGLE_STRIP)) return; D3DXMATRIX modelToView, projection; ComputeModelToView( modelToView ); ShaderAPI()->GetMatrix( MATERIAL_PROJECTION, (float*)&projection ); float zNear = -projection.m[3][2] / projection.m[2][2]; D3DXVECTOR3* pPos[3]; D3DXVECTOR3 edge[2]; D3DXVECTOR3 normal; int numTriangles; if (m_Type == MATERIAL_TRIANGLES) numTriangles = m_IndexData.Count() / 3; else numTriangles = m_IndexData.Count() - 2; float cullFactor = ComputeCullFactor(); // Makes the lovely loop simpler if (m_Type == MATERIAL_TRIANGLE_STRIP) cullFactor *= -1.0f; // We'll need some temporary memory to tell us if we're transformed the vert int nVertexCount = m_VertexData.Count() / m_VertexSize; static CUtlVector< unsigned char > transformedVert; int transformedVertSize = (nVertexCount + 7) >> 3; transformedVert.RemoveAll(); transformedVert.EnsureCapacity( transformedVertSize ); transformedVert.AddMultipleToTail( transformedVertSize ); memset( transformedVert.Base(), 0, transformedVertSize ); int indexPos; for (int i = 0; i < numTriangles; ++i) { // Get the three indices if (m_Type == MATERIAL_TRIANGLES) { indexPos = i * 3; } else { Assert( m_Type == MATERIAL_TRIANGLE_STRIP ); cullFactor *= -1.0f; indexPos = i; } // BAH. Gotta clip to the near clip plane in view space to prevent // negative w coords; negative coords throw off the projection-space clipper. // Get the three positions in view space int inFrontIdx = -1; for (int j = 0; j < 3; ++j) { int index = m_IndexData[indexPos]; D3DXVECTOR3* pPosition = (D3DXVECTOR3*)&m_VertexData[index * m_VertexSize]; if ((transformedVert[index >> 3] & (1 << (index & 0x7))) == 0) { D3DXVec3TransformCoord( pPosition, pPosition, &modelToView ); transformedVert[index >> 3] |= (1 << (index & 0x7)); } pPos[j] = pPosition; if (pPos[j]->z < 0.0f) inFrontIdx = j; ++indexPos; } // all points are behind the camera if (inFrontIdx < 0) continue; // backface cull.... D3DXVec3Subtract( &edge[0], pPos[1], pPos[0] ); D3DXVec3Subtract( &edge[1], pPos[2], pPos[0] ); D3DXVec3Cross( &normal, &edge[0], &edge[1] ); float dot = D3DXVec3Dot( &normal, pPos[inFrontIdx] ); if (dot * cullFactor > 0.0f) continue; // Clip to viewport ClipTriangle( pPos, zNear, projection ); } } //----------------------------------------------------------------------------- // Begins a render pass //----------------------------------------------------------------------------- void CTempMeshDX8::BeginPass( ) { Assert( !m_InPass ); #ifdef DBGFLAG_ASSERT m_InPass = true; #endif CMeshBuilder* pMeshBuilder = ShaderAPI()->GetVertexModifyBuilder(); CDynamicMeshDX8* pMesh = GetDynamicMesh( ); int nIndexCount; int nFirstIndex; if ((s_FirstIndex == -1) && (s_NumIndices == 0)) { nIndexCount = m_IndexData.Count(); nFirstIndex = 0; } else { nIndexCount = s_NumIndices; nFirstIndex = s_FirstIndex; } int i; int nVertexCount = m_VertexData.Count() / m_VertexSize; pMeshBuilder->Begin( pMesh, m_Type, nVertexCount, nIndexCount ); // Copy in the vertex data... // Note that since we pad the vertices, it's faster for us to simply // copy the fields we're using... Assert( pMeshBuilder->BaseVertexData() ); memcpy( pMeshBuilder->BaseVertexData(), m_VertexData.Base(), m_VertexData.Count() ); pMeshBuilder->AdvanceVertices( m_VertexData.Count() / m_VertexSize ); for ( i = 0; i < nIndexCount; ++i ) { pMeshBuilder->Index( m_IndexData[nFirstIndex+i] ); pMeshBuilder->AdvanceIndex(); } // NOTE: The client is expected to modify the data after this call is made pMeshBuilder->Reset(); } //----------------------------------------------------------------------------- // Draws a single pass //----------------------------------------------------------------------------- void CTempMeshDX8::RenderPass() { Assert( m_InPass ); #ifdef DBGFLAG_ASSERT m_InPass = false; #endif // Have the shader API modify the vertex data as it needs // This vertex data is modified based on state set by the material ShaderAPI()->ModifyVertexData( ); // Done building the mesh ShaderAPI()->GetVertexModifyBuilder()->End(); // Have the dynamic mesh render a single pass... GetDynamicMesh()->DrawSinglePassImmediately(); } //----------------------------------------------------------------------------- // // Buffered mesh implementation // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // constructor, destructor //----------------------------------------------------------------------------- CBufferedMeshDX8::CBufferedMeshDX8() : m_IsFlushing(false), m_WasRendered(true) { m_pMesh = NULL; #ifdef DEBUG_BUFFERED_STATE m_BufferedStateSet = false; #endif } CBufferedMeshDX8::~CBufferedMeshDX8() { } //----------------------------------------------------------------------------- // Sets the mesh //----------------------------------------------------------------------------- void CBufferedMeshDX8::SetMesh( CBaseMeshDX8* pMesh ) { if (m_pMesh != pMesh) { ShaderAPI()->FlushBufferedPrimitives(); m_pMesh = pMesh; } } //----------------------------------------------------------------------------- // Spews the mesh data //----------------------------------------------------------------------------- void CBufferedMeshDX8::Spew( int nVertexCount, int nIndexCount, const MeshDesc_t &spewDesc ) { if ( m_pMesh ) { m_pMesh->Spew( nVertexCount, nIndexCount, spewDesc ); } } //----------------------------------------------------------------------------- // Sets the material //----------------------------------------------------------------------------- void CBufferedMeshDX8::SetVertexFormat( VertexFormat_t format ) { Assert( m_pMesh ); if (m_pMesh->NeedsVertexFormatReset(format)) { ShaderAPI()->FlushBufferedPrimitives(); m_pMesh->SetVertexFormat( format ); } } void CBufferedMeshDX8::SetMorphFormat( MorphFormat_t format ) { Assert( m_pMesh ); m_pMesh->SetMorphFormat( format ); } VertexFormat_t CBufferedMeshDX8::GetVertexFormat( ) const { Assert( m_pMesh ); return m_pMesh->GetVertexFormat(); } void CBufferedMeshDX8::SetMaterial( IMaterial* pMaterial ) { #if _DEBUG Assert( m_pMesh ); m_pMesh->SetMaterial( pMaterial ); #endif } void CBufferedMeshDX8::ValidateData( int nVertexCount, int nIndexCount, const MeshDesc_t & spewDesc ) { #if _DEBUG Assert( m_pMesh ); m_pMesh->ValidateData( nVertexCount, nIndexCount, spewDesc ); #endif } //----------------------------------------------------------------------------- // Sets the flex mesh to render with this mesh //----------------------------------------------------------------------------- void CBufferedMeshDX8::SetFlexMesh( IMesh *pMesh, int nVertexOffsetInBytes ) { // FIXME: Probably are situations where we don't need to flush, // but this is going to look different in a very short while, so I'm not going to bother ShaderAPI()->FlushBufferedPrimitives(); m_pMesh->SetFlexMesh( pMesh, nVertexOffsetInBytes ); } //----------------------------------------------------------------------------- // checks to see if it was rendered.. //----------------------------------------------------------------------------- void CBufferedMeshDX8::ResetRendered() { m_WasRendered = false; } bool CBufferedMeshDX8::WasNotRendered() const { return !m_WasRendered; } //----------------------------------------------------------------------------- // "Draws" it //----------------------------------------------------------------------------- void CBufferedMeshDX8::Draw( int nFirstIndex, int nIndexCount ) { if ( !ShaderUtil()->OnDrawMesh( this, nFirstIndex, nIndexCount ) ) { m_WasRendered = true; MarkAsDrawn(); return; } Assert( !m_IsFlushing && !m_WasRendered ); // Gotta draw all of the buffered mesh Assert( (nFirstIndex == -1) && (nIndexCount == 0) ); // No need to draw it more than once... m_WasRendered = true; // We've got something to flush m_FlushNeeded = true; // Less than 0 indices indicates we were using a standard buffer if ( m_pMesh->HasFlexMesh() || !ShaderUtil()->GetConfig().bBufferPrimitives ) { ShaderAPI()->FlushBufferedPrimitives(); } } //----------------------------------------------------------------------------- // Sets the primitive mode //----------------------------------------------------------------------------- void CBufferedMeshDX8::SetPrimitiveType( MaterialPrimitiveType_t type ) { Assert( IsX360() || ( type != MATERIAL_INSTANCED_QUADS ) ); Assert( type != MATERIAL_HETEROGENOUS ); if (type != GetPrimitiveType()) { ShaderAPI()->FlushBufferedPrimitives(); m_pMesh->SetPrimitiveType(type); } } MaterialPrimitiveType_t CBufferedMeshDX8::GetPrimitiveType( ) const { return m_pMesh->GetPrimitiveType(); } //----------------------------------------------------------------------------- // Locks/unlocks the entire mesh //----------------------------------------------------------------------------- void CBufferedMeshDX8::LockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { ShaderUtil()->SyncMatrices(); Assert( m_pMesh ); Assert( m_WasRendered ); // Do some pre-lock processing m_pMesh->PreLock(); // for tristrips, gotta make degenerate ones... m_ExtraIndices = 0; bool tristripFixup = (m_pMesh->IndexCount() != 0) && (m_pMesh->GetPrimitiveType() == MATERIAL_TRIANGLE_STRIP); if (tristripFixup) { m_ExtraIndices = (m_pMesh->IndexCount() & 0x1) != 0 ? 3 : 2; nIndexCount += m_ExtraIndices; } // Flush if we gotta if (!m_pMesh->HasEnoughRoom(nVertexCount, nIndexCount)) { ShaderAPI()->FlushBufferedPrimitives(); } m_pMesh->LockMesh( nVertexCount, nIndexCount, desc ); // This is taken care of in the function above. // CBaseMeshDX8::m_bMeshLocked = true; // Deal with fixing up the tristrip.. if ( tristripFixup && desc.m_nIndexSize ) { char buf[32]; if (DebugTrace()) { if (m_ExtraIndices == 3) sprintf(buf,"Link Index: %d %d\n", m_LastIndex, m_LastIndex); else sprintf(buf,"Link Index: %d\n", m_LastIndex); Plat_DebugString(buf); } *desc.m_pIndices++ = m_LastIndex; if (m_ExtraIndices == 3) { *desc.m_pIndices++ = m_LastIndex; } // Leave room for the last padding index ++desc.m_pIndices; } m_WasRendered = false; #ifdef DEBUG_BUFFERED_MESHES if (m_BufferedStateSet) { BufferedState_t compare; ShaderAPI()->GetBufferedState( compare ); Assert( !memcmp( &compare, &m_BufferedState, sizeof(compare) ) ); } else { ShaderAPI()->GetBufferedState( m_BufferedState ); m_BufferedStateSet = true; } #endif } //----------------------------------------------------------------------------- // Locks/unlocks the entire mesh //----------------------------------------------------------------------------- void CBufferedMeshDX8::UnlockMesh( int nVertexCount, int nIndexCount, MeshDesc_t& desc ) { Assert( m_pMesh ); // Gotta fix up the first index to batch strips reasonably if ((m_pMesh->GetPrimitiveType() == MATERIAL_TRIANGLE_STRIP) && desc.m_nIndexSize ) { if (m_ExtraIndices > 0) { *(desc.m_pIndices - 1) = *desc.m_pIndices; if (DebugTrace()) { char buf[32]; sprintf(buf,"Link Index: %d\n", *desc.m_pIndices); Plat_DebugString(buf); } } // Remember the last index for next time m_LastIndex = desc.m_pIndices[nIndexCount - 1]; nIndexCount += m_ExtraIndices; } m_pMesh->UnlockMesh( nVertexCount, nIndexCount, desc ); // This is taken care of in the function above. // CBaseMeshDX8::m_bMeshLocked = false; } //----------------------------------------------------------------------------- // Renders a pass //----------------------------------------------------------------------------- void CBufferedMeshDX8::RenderPass() { // this should never be called! Assert(0); } //----------------------------------------------------------------------------- // Flushes queued data //----------------------------------------------------------------------------- void CBufferedMeshDX8::Flush( ) { // If you are hitting this assert you are causing a flush between a // meshbuilder begin/end and you are more than likely losing rendering data. AssertOnce( !CBaseMeshDX8::m_bMeshLocked ); if ( m_pMesh && !m_IsFlushing && m_FlushNeeded ) { VPROF( "CBufferedMeshDX8::Flush" ); #ifdef DEBUG_BUFFERED_MESHES if( m_BufferedStateSet ) { BufferedState_t compare; ShaderAPI()->GetBufferedState( compare ); Assert( !memcmp( &compare, &m_BufferedState, sizeof(compare) ) ); m_BufferedStateSet = false; } #endif m_IsFlushing = true; // Actually draws the data using the mesh's material static_cast(m_pMesh)->Draw(); m_IsFlushing = false; m_FlushNeeded = false; m_pMesh->SetFlexMesh( NULL, 0 ); } } //----------------------------------------------------------------------------- // // Mesh manager implementation // //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Constructor, destructor //----------------------------------------------------------------------------- CMeshMgr::CMeshMgr() : m_pDynamicIndexBuffer(0), m_DynamicTempMesh(true), m_pVertexIDBuffer(0), m_pCurrentVertexBuffer( NULL ), m_CurrentVertexFormat( 0 ), m_pCurrentIndexBuffer( NULL ), m_DynamicIndexBuffer( SHADER_BUFFER_TYPE_DYNAMIC, MATERIAL_INDEX_FORMAT_16BIT, INDEX_BUFFER_SIZE, "dynamic" ), m_DynamicVertexBuffer( SHADER_BUFFER_TYPE_DYNAMIC, VERTEX_FORMAT_UNKNOWN, DYNAMIC_VERTEX_BUFFER_MEMORY, "dynamic" ) { m_bUseFatVertices = false; m_nIndexBufferOffset = 0; memset( m_pVertexBufferOffset, 0, sizeof(m_pVertexBufferOffset) ); memset( m_pCurrentVertexStride, 0, sizeof(m_pCurrentVertexStride) ); memset( m_pFirstVertex, 0, sizeof(m_pFirstVertex) ); memset( m_pVertexCount, 0, sizeof(m_pVertexCount) ); m_nUnusedVertexFields = 0; m_nUnusedTextureCoords = 0; m_pZeroVertexBuffer = NULL; } CMeshMgr::~CMeshMgr() { } //----------------------------------------------------------------------------- // Initialize, shutdown //----------------------------------------------------------------------------- void CMeshMgr::Init() { m_DynamicMesh.Init( 0 ); m_DynamicFlexMesh.Init( 1 ); CreateDynamicIndexBuffer(); // If we're running in vs3.0, allocate a vertexID buffer CreateVertexIDBuffer(); CreateZeroVertexBuffer(); m_BufferedMode = !IsX360(); } void CMeshMgr::Shutdown() { CleanUp(); } //----------------------------------------------------------------------------- // Task switch... //----------------------------------------------------------------------------- void CMeshMgr::ReleaseBuffers() { if ( IsPC() && mat_debugalttab.GetBool() ) { Warning( "mat_debugalttab: CMeshMgr::ReleaseBuffers\n" ); } CleanUp(); m_DynamicMesh.Reset( ); m_DynamicFlexMesh.Reset( ); } void CMeshMgr::RestoreBuffers() { if ( IsPC() && mat_debugalttab.GetBool() ) { Warning( "mat_debugalttab: CMeshMgr::RestoreBuffers\n" ); } Init(); } //----------------------------------------------------------------------------- // Cleans up vertex and index buffers //----------------------------------------------------------------------------- void CMeshMgr::CleanUp() { DestroyDynamicIndexBuffer(); DestroyVertexBuffers(); // If we're running in vs3.0, allocate a vertexID buffer DestroyVertexIDBuffer(); DestroyZeroVertexBuffer(); } //----------------------------------------------------------------------------- // Fills a vertexID buffer //----------------------------------------------------------------------------- void CMeshMgr::FillVertexIDBuffer( CVertexBuffer *pVertexIDBuffer, int nCount ) { if ( IsX360() ) return; // Fill the buffer with the values 0->(nCount-1) int nBaseVertexIndex = 0; float *pBuffer = (float*)pVertexIDBuffer->Lock( nCount, nBaseVertexIndex ); for ( int i = 0; i < nCount; ++i ) { *pBuffer++ = (float)i; } pVertexIDBuffer->Unlock( nCount ); } //----------------------------------------------------------------------------- // Creates, destroys the dynamic index buffer //----------------------------------------------------------------------------- void CMeshMgr::CreateDynamicIndexBuffer() { DestroyDynamicIndexBuffer(); SafeAssign( &m_pDynamicIndexBuffer, new CIndexBuffer( Dx9Device(), INDEX_BUFFER_SIZE, ShaderAPI()->UsingSoftwareVertexProcessing(), true ) ); } void CMeshMgr::DestroyDynamicIndexBuffer() { SafeRelease( &m_pDynamicIndexBuffer ); } //----------------------------------------------------------------------------- // Creates, destroys the vertexID buffer //----------------------------------------------------------------------------- void CMeshMgr::CreateVertexIDBuffer() { if ( IsX360() ) return; DestroyVertexIDBuffer(); // Track mesh allocations g_VBAllocTracker->TrackMeshAllocations( "CreateVertexIDBuffer" ); if ( g_pHardwareConfig->HasFastVertexTextures() ) { m_pVertexIDBuffer = new CVertexBuffer( Dx9Device(), 0, 0, sizeof(float), VERTEX_BUFFER_SIZE, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_OTHER, ShaderAPI()->UsingSoftwareVertexProcessing() ); FillVertexIDBuffer( m_pVertexIDBuffer, VERTEX_BUFFER_SIZE ); } g_VBAllocTracker->TrackMeshAllocations( NULL ); } void CMeshMgr::CreateZeroVertexBuffer() { if ( !m_pZeroVertexBuffer ) { // In GL glVertexAttribPointer() doesn't support strides of 0, so we need to allocate a dummy vertex buffer large enough to handle 16-bit indices with a stride of 4 byte per vertex, plus a bit more for safety (in case basevertexindex is > 0). // We could also try just disabling any vertex attribs that fetch from stream 2 and need 0's, but AMD reports this could hit a slow path in the driver. Argh. uint nBufSize = IsOpenGL() ? ( 65536 * 2 * 4 ) : 4096; HRESULT hr = Dx9Device()->CreateVertexBuffer( nBufSize, D3DUSAGE_WRITEONLY, 0, D3DPOOL_DEFAULT, &m_pZeroVertexBuffer, NULL ); if ( !FAILED( hr ) ) { void *pData = NULL; m_pZeroVertexBuffer->Lock( 0, nBufSize, &pData, D3DLOCK_NOSYSLOCK ); if ( pData ) { V_memset( pData, 0, nBufSize ); m_pZeroVertexBuffer->Unlock(); } } } } void CMeshMgr::DestroyZeroVertexBuffer() { if ( m_pZeroVertexBuffer ) { m_pZeroVertexBuffer->Release(); m_pZeroVertexBuffer = NULL; } } void CMeshMgr::DestroyVertexIDBuffer() { if ( m_pVertexIDBuffer ) { delete m_pVertexIDBuffer; m_pVertexIDBuffer = NULL; } } CVertexBuffer *CMeshMgr::GetVertexIDBuffer( ) { return m_pVertexIDBuffer; } //----------------------------------------------------------------------------- // Unused vertex fields //----------------------------------------------------------------------------- void CMeshMgr::MarkUnusedVertexFields( unsigned int nFlags, int nTexCoordCount, bool *pUnusedTexCoords ) { m_nUnusedVertexFields = nFlags; m_nUnusedTextureCoords = 0; for ( int i = 0; i < nTexCoordCount; ++i ) { if ( pUnusedTexCoords[i] ) { m_nUnusedTextureCoords |= ( 1 << i ); } } } //----------------------------------------------------------------------------- // Is the mesh dynamic? //----------------------------------------------------------------------------- bool CMeshMgr::IsDynamicMesh( IMesh* pMesh ) const { return ( pMesh == &m_DynamicMesh ) || ( pMesh == &m_DynamicFlexMesh ); } bool CMeshMgr::IsBufferedDynamicMesh( IMesh* pMesh ) const { return ( pMesh == &m_BufferedMesh ); } bool CMeshMgr::IsDynamicVertexBuffer( IVertexBuffer *pVertexBuffer ) const { return ( pVertexBuffer == &m_DynamicVertexBuffer ); } bool CMeshMgr::IsDynamicIndexBuffer( IIndexBuffer *pIndexBuffer ) const { return ( pIndexBuffer == &m_DynamicIndexBuffer ); } //----------------------------------------------------------------------------- // Discards the dynamic vertex and index buffer //----------------------------------------------------------------------------- void CMeshMgr::DiscardVertexBuffers() { VPROF_BUDGET( "CMeshMgr::DiscardVertexBuffers", VPROF_BUDGETGROUP_SWAP_BUFFERS ); // This shouldn't be necessary, but it seems to be on GeForce 2 // It helps when running WC and the engine simultaneously. ResetMeshRenderState(); if ( !g_pShaderDeviceDx8->IsDeactivated() ) { for (int i = m_DynamicVertexBuffers.Count(); --i >= 0; ) { m_DynamicVertexBuffers[i].m_pBuffer->FlushAtFrameStart(); } m_pDynamicIndexBuffer->FlushAtFrameStart(); } } //----------------------------------------------------------------------------- // Releases all dynamic vertex buffers //----------------------------------------------------------------------------- void CMeshMgr::DestroyVertexBuffers() { // Necessary for cleanup RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); RECORD_INT( 0 ); RECORD_INT( 0 ); RECORD_INT( 0 ); D3DSetStreamSource( 0, 0, 0, 0 ); RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); RECORD_INT( 1 ); RECORD_INT( 0 ); RECORD_INT( 0 ); D3DSetStreamSource( 1, 0, 0, 0 ); RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); RECORD_INT( 2 ); RECORD_INT( 0 ); RECORD_INT( 0 ); D3DSetStreamSource( 2, 0, 0, 0 ); #ifndef _X360 RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); RECORD_INT( 3 ); RECORD_INT( 0 ); RECORD_INT( 0 ); D3DSetStreamSource( 3, 0, 0, 0 ); #endif for (int i = m_DynamicVertexBuffers.Count(); --i >= 0; ) { if (m_DynamicVertexBuffers[i].m_pBuffer) { delete m_DynamicVertexBuffers[i].m_pBuffer; } } m_DynamicVertexBuffers.RemoveAll(); m_DynamicMesh.Reset(); m_DynamicFlexMesh.Reset(); } //----------------------------------------------------------------------------- // Flushes the dynamic mesh //----------------------------------------------------------------------------- void CMeshMgr::Flush() { if ( IsPC() ) { m_BufferedMesh.HandleLateCreation(); m_BufferedMesh.Flush(); } } //----------------------------------------------------------------------------- // Creates, destroys static meshes //----------------------------------------------------------------------------- IMesh* CMeshMgr::CreateStaticMesh( VertexFormat_t format, const char *pTextureBudgetGroup, IMaterial * pMaterial ) { // FIXME: Use a fixed-size allocator CMeshDX8* pNewMesh = new CMeshDX8( pTextureBudgetGroup ); pNewMesh->SetVertexFormat( format ); if ( pMaterial != NULL ) { pNewMesh->SetMorphFormat( pMaterial->GetMorphFormat() ); pNewMesh->SetMaterial( pMaterial ); } return pNewMesh; } void CMeshMgr::DestroyStaticMesh( IMesh* pMesh ) { // Don't destroy the dynamic mesh! Assert( !IsDynamicMesh( pMesh ) ); CBaseMeshDX8* pMeshImp = static_cast(pMesh); if (pMeshImp) { delete pMeshImp; } } //----------------------------------------------------------------------------- // Gets at the *real* dynamic mesh //----------------------------------------------------------------------------- IMesh* CMeshMgr::GetActualDynamicMesh( VertexFormat_t format ) { m_DynamicMesh.SetVertexFormat( format ); return &m_DynamicMesh; } //----------------------------------------------------------------------------- // Copy a static mesh index buffer to a dynamic mesh index buffer //----------------------------------------------------------------------------- void CMeshMgr::CopyStaticMeshIndexBufferToTempMeshIndexBuffer( CTempMeshDX8 *pDstIndexMesh, CMeshDX8 *pSrcIndexMesh ) { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s", __FUNCTION__ ); Assert( !pSrcIndexMesh->IsDynamic() ); int nIndexCount = pSrcIndexMesh->IndexCount(); CMeshBuilder dstMeshBuilder; dstMeshBuilder.Begin( pDstIndexMesh, pSrcIndexMesh->GetPrimitiveType(), 0, nIndexCount ); CIndexBuffer *srcIndexBuffer = pSrcIndexMesh->GetIndexBuffer(); int dummy = 0; unsigned short *srcIndexArray = srcIndexBuffer->Lock( false, nIndexCount, dummy, 0 ); int i; for( i = 0; i < nIndexCount; i++ ) { dstMeshBuilder.Index( srcIndexArray[i] ); dstMeshBuilder.AdvanceIndex(); } srcIndexBuffer->Unlock( 0 ); dstMeshBuilder.End(); } IMesh *CMeshMgr::GetFlexMesh() { if ( g_pMaterialSystemHardwareConfig->SupportsPixelShaders_2_b() ) { // FIXME: Kinda ugly size.. 28 bytes m_DynamicFlexMesh.SetVertexFormat( VERTEX_POSITION | VERTEX_NORMAL | VERTEX_WRINKLE | VERTEX_FORMAT_USE_EXACT_FORMAT ); } else { // Same size as a pair of float3s (24 bytes) m_DynamicFlexMesh.SetVertexFormat( VERTEX_POSITION | VERTEX_NORMAL | VERTEX_FORMAT_USE_EXACT_FORMAT ); } return &m_DynamicFlexMesh; } //----------------------------------------------------------------------------- // Gets at the dynamic mesh //----------------------------------------------------------------------------- IMesh* CMeshMgr::GetDynamicMesh( IMaterial* pMaterial, VertexFormat_t vertexFormat, int nHWSkinBoneCount, bool buffered, IMesh* pVertexOverride, IMesh* pIndexOverride ) { tmZoneFiltered( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s", __FUNCTION__ ); Assert( (pMaterial == NULL) || ((IMaterialInternal *)pMaterial)->IsRealTimeVersion() ); if ( IsX360() ) { buffered = false; } // Can't be buffered if we're overriding the buffers if ( pVertexOverride || pIndexOverride ) { buffered = false; } // When going from buffered to unbuffered mode, need to flush.. if ((m_BufferedMode != buffered) && m_BufferedMode) { m_BufferedMesh.SetMesh(0); } m_BufferedMode = buffered; IMaterialInternal* pMatInternal = static_cast(pMaterial); bool needTempMesh = ShaderAPI()->IsInSelectionMode(); #ifdef DRAW_SELECTION if( g_bDrawSelection ) { needTempMesh = true; } #endif CBaseMeshDX8* pMesh; if ( needTempMesh ) { // These haven't been implemented yet for temp meshes! // I'm not a hundred percent sure how to implement them; it would // involve a lock and a copy at least, which would stall the entire // rendering pipeline. Assert( !pVertexOverride ); if( pIndexOverride ) { CopyStaticMeshIndexBufferToTempMeshIndexBuffer( &m_DynamicTempMesh, ( CMeshDX8 * )pIndexOverride ); } pMesh = &m_DynamicTempMesh; } else { pMesh = &m_DynamicMesh; } if ( m_BufferedMode ) { Assert( !m_BufferedMesh.WasNotRendered() ); m_BufferedMesh.SetMesh( pMesh ); pMesh = &m_BufferedMesh; } if( !pVertexOverride ) { // Remove VERTEX_FORMAT_COMPRESSED from the material's format (dynamic meshes don't // support compression, and all materials should support uncompressed verts too) VertexFormat_t materialFormat = pMatInternal->GetVertexFormat() & ~VERTEX_FORMAT_COMPRESSED; VertexFormat_t fmt = ( vertexFormat != 0 ) ? vertexFormat : materialFormat; if ( vertexFormat != 0 ) { int nVertexFormatBoneWeights = NumBoneWeights( vertexFormat ); if ( nHWSkinBoneCount < nVertexFormatBoneWeights ) { nHWSkinBoneCount = nVertexFormatBoneWeights; } } // Force the requested number of bone weights fmt &= ~VERTEX_BONE_WEIGHT_MASK; if ( nHWSkinBoneCount > 0 ) { fmt |= VERTEX_BONEWEIGHT( 2 ); fmt |= VERTEX_BONE_INDEX; } pMesh->SetVertexFormat( fmt ); } else { CBaseMeshDX8 *pDX8Mesh = static_cast(pVertexOverride); pMesh->SetVertexFormat( pDX8Mesh->GetVertexFormat() ); } pMesh->SetMorphFormat( pMatInternal->GetMorphFormat() ); pMesh->SetMaterial( pMatInternal ); // Note this works because we're guaranteed to not be using a buffered mesh // when we have overrides on // FIXME: Make work for temp meshes if ( pMesh == &m_DynamicMesh ) { CBaseMeshDX8* pBaseVertex = static_cast( pVertexOverride ); if ( pBaseVertex ) { m_DynamicMesh.OverrideVertexBuffer( pBaseVertex->GetVertexBuffer() ); } CBaseMeshDX8* pBaseIndex = static_cast( pIndexOverride ); if ( pBaseIndex ) { m_DynamicMesh.OverrideIndexBuffer( pBaseIndex->GetIndexBuffer() ); } } return pMesh; } //----------------------------------------------------------------------------- // Used to construct vertex data //----------------------------------------------------------------------------- void CMeshMgr::ComputeVertexDescription( unsigned char* pBuffer, VertexFormat_t vertexFormat, MeshDesc_t& desc ) const { ComputeVertexDesc( pBuffer, vertexFormat, (VertexDesc_t &)desc ); } //----------------------------------------------------------------------------- // Computes the vertex format //----------------------------------------------------------------------------- VertexFormat_t CMeshMgr::ComputeVertexFormat( unsigned int flags, int nTexCoordArraySize, int* pTexCoordDimensions, int numBoneWeights, int userDataSize ) const { // Construct a bitfield that makes sense and is unique from the standard FVF formats VertexFormat_t fmt = flags & ~VERTEX_FORMAT_USE_EXACT_FORMAT; if ( g_pHardwareConfig->SupportsCompressedVertices() == VERTEX_COMPRESSION_NONE ) { // Vertex compression is disabled - make sure all materials // say "No!" to compressed verts ( tested in IsValidVertexFormat() ) fmt &= ~VERTEX_FORMAT_COMPRESSED; } // This'll take 3 bits at most Assert( numBoneWeights <= 4 ); if ( numBoneWeights > 0 ) { fmt |= VERTEX_BONEWEIGHT( 2 ); // Always exactly two weights } // Size is measured in # of floats Assert( userDataSize <= 4 ); fmt |= VERTEX_USERDATA_SIZE(userDataSize); // NOTE: If pTexCoordDimensions isn't specified, then nTexCoordArraySize // is interpreted as meaning that we have n 2D texcoords in the first N texcoord slots nTexCoordArraySize = min( nTexCoordArraySize, (int)VERTEX_MAX_TEXTURE_COORDINATES ); for ( int i = 0; i < nTexCoordArraySize; ++i ) { if ( pTexCoordDimensions ) { Assert( pTexCoordDimensions[i] >= 0 && pTexCoordDimensions[i] <= 4 ); fmt |= VERTEX_TEXCOORD_SIZE( (TextureStage_t)i, pTexCoordDimensions[i] ); } else { fmt |= VERTEX_TEXCOORD_SIZE( (TextureStage_t)i, 2 ); } } return fmt; } //----------------------------------------------------------------------------- // Use fat vertices (for tools) //----------------------------------------------------------------------------- void CMeshMgr::UseFatVertices( bool bUseFat ) { m_bUseFatVertices = bUseFat; } //----------------------------------------------------------------------------- // Returns the number of vertices we can render using the dynamic mesh //----------------------------------------------------------------------------- void CMeshMgr::GetMaxToRender( IMesh *pMesh, bool bMaxUntilFlush, int *pMaxVerts, int *pMaxIndices ) { CBaseMeshDX8 *pBaseMesh = static_cast( pMesh ); if ( !pBaseMesh ) { *pMaxVerts = 0; *pMaxIndices = m_pDynamicIndexBuffer->IndexCount(); return; } if ( IsBufferedDynamicMesh( pMesh ) ) { pBaseMesh = (CBaseMeshDX8*)static_cast( pBaseMesh )->GetMesh(); pMesh = pBaseMesh; } // Static mesh? Max you can use is 65535 if ( !IsDynamicMesh( pMesh ) ) { *pMaxVerts = 65535; *pMaxIndices = 65535; return; } CVertexBuffer *pVertexBuffer = pBaseMesh->GetVertexBuffer(); CIndexBuffer *pIndexBuffer = pBaseMesh->GetIndexBuffer(); if ( !pVertexBuffer ) { *pMaxVerts = 0; *pMaxIndices = 0; return; } if ( !bMaxUntilFlush ) { *pMaxVerts = ShaderAPI()->GetCurrentDynamicVBSize() / pVertexBuffer->VertexSize(); if ( *pMaxVerts > 65535 ) { *pMaxVerts = 65535; } *pMaxIndices = pIndexBuffer ? pIndexBuffer->IndexCount() : 0; return; } *pMaxVerts = pVertexBuffer->NumVerticesUntilFlush(); *pMaxIndices = pIndexBuffer ? pIndexBuffer->IndexCount() - pIndexBuffer->IndexPosition() : 0; if ( *pMaxVerts == 0 ) { *pMaxVerts = ShaderAPI()->GetCurrentDynamicVBSize() / pVertexBuffer->VertexSize(); } if ( *pMaxVerts > 65535 ) { *pMaxVerts = 65535; } if ( *pMaxIndices == 0 ) { *pMaxIndices = pIndexBuffer ? pIndexBuffer->IndexCount() : 0; } } int CMeshMgr::GetMaxVerticesToRender( IMaterial *pMaterial ) { Assert( (pMaterial == NULL) || ((IMaterialInternal *)pMaterial)->IsRealTimeVersion() ); // Be conservative, assume no compression (in here, we don't know if the caller will used a compressed VB or not) // FIXME: allow the caller to specify which compression type should be used to compute size from the vertex format // (this can vary between multiple VBs/Meshes using the same material) VertexFormat_t fmt = pMaterial->GetVertexFormat() & ~VERTEX_FORMAT_COMPRESSED; int nVertexSize = VertexFormatSize( fmt ); if ( nVertexSize == 0 ) { // unable to determine vertex format information, possibly due to device loss. Warning( "bad vertex size for material %s\n", pMaterial->GetName() ); return 0; } int nMaxVerts = ShaderAPI()->GetCurrentDynamicVBSize() / nVertexSize; return MIN( nMaxVerts, 65535 ); } int CMeshMgr::GetMaxIndicesToRender( ) { return INDEX_BUFFER_SIZE; } //----------------------------------------------------------------------------- // Returns a vertex buffer appropriate for the flags //----------------------------------------------------------------------------- CVertexBuffer *CMeshMgr::FindOrCreateVertexBuffer( int nDynamicBufferId, VertexFormat_t vertexFormat ) { int vertexSize = VertexFormatSize( vertexFormat ); while ( m_DynamicVertexBuffers.Count() <= nDynamicBufferId ) { // Track VB allocations (override any prior allocator string set higher up on the callstack) g_VBAllocTracker->TrackMeshAllocations( NULL ); g_VBAllocTracker->TrackMeshAllocations( "CMeshMgr::FindOrCreateVertexBuffer (dynamic VB)" ); // create the single 1MB dynamic vb that will be shared amongst all consumers // the correct thing is to use the largest expected vertex format size of max elements, but this // creates an undesirably large buffer - instead create the buffer we want, and fix consumers that bork // NOTE: GetCurrentDynamicVBSize returns a smaller value during level transitions int nBufferMemory = ShaderAPI()->GetCurrentDynamicVBSize(); int nIndex = m_DynamicVertexBuffers.AddToTail(); m_DynamicVertexBuffers[nIndex].m_VertexSize = 0; m_DynamicVertexBuffers[nIndex].m_pBuffer = new CVertexBuffer( Dx9Device(), 0, 0, nBufferMemory / VERTEX_BUFFER_SIZE, VERTEX_BUFFER_SIZE, TEXTURE_GROUP_STATIC_VERTEX_BUFFER_OTHER, ShaderAPI()->UsingSoftwareVertexProcessing(), true ); g_VBAllocTracker->TrackMeshAllocations( NULL ); } if ( m_DynamicVertexBuffers[nDynamicBufferId].m_VertexSize != vertexSize ) { // provide caller with dynamic vb in expected format // NOTE: GetCurrentDynamicVBSize returns a smaller value during level transitions int nBufferMemory = ShaderAPI()->GetCurrentDynamicVBSize(); m_DynamicVertexBuffers[nDynamicBufferId].m_VertexSize = vertexSize; m_DynamicVertexBuffers[nDynamicBufferId].m_pBuffer->ChangeConfiguration( vertexSize, nBufferMemory ); // size changed means stream stride needs update // mark cached stream state as invalid to reset stream if ( nDynamicBufferId == 0 ) { g_pLastVertex = NULL; } } return m_DynamicVertexBuffers[nDynamicBufferId].m_pBuffer; } CIndexBuffer *CMeshMgr::GetDynamicIndexBuffer() { return m_pDynamicIndexBuffer; } IVertexBuffer *CMeshMgr::GetDynamicVertexBuffer( IMaterial *pMaterial, bool buffered ) { Assert( 0 ); return NULL; // return ( IMeshDX8 * )GetDynamicMesh( pMaterial, buffered, NULL, NULL ); } IIndexBuffer *CMeshMgr::GetDynamicIndexBuffer( IMaterial *pMaterial, bool buffered ) { Assert( 0 ); return NULL; // return ( IMeshDX8 * )GetDynamicMesh( pMaterial, buffered, NULL, NULL ); } //----------------------------------------------------------------------------- IVertexBuffer *CMeshMgr::CreateVertexBuffer( ShaderBufferType_t type, VertexFormat_t fmt, int nVertexCount, const char *pBudgetGroup ) { // FIXME: Use a fixed-size allocator CVertexBufferDx8 *pNewVertexBuffer = new CVertexBufferDx8( type, fmt, nVertexCount, pBudgetGroup ); return pNewVertexBuffer; } IIndexBuffer *CMeshMgr::CreateIndexBuffer( ShaderBufferType_t bufferType, MaterialIndexFormat_t fmt, int nIndexCount, const char *pBudgetGroup ) { switch( bufferType ) { case SHADER_BUFFER_TYPE_STATIC: case SHADER_BUFFER_TYPE_DYNAMIC: { CIndexBufferDx8 *pIndexBuffer = new CIndexBufferDx8( bufferType, fmt, nIndexCount, pBudgetGroup ); return pIndexBuffer; } case SHADER_BUFFER_TYPE_STATIC_TEMP: case SHADER_BUFFER_TYPE_DYNAMIC_TEMP: Assert( 0 ); return NULL; default: Assert( 0 ); return NULL; } } void CMeshMgr::DestroyVertexBuffer( IVertexBuffer *pVertexBuffer ) { if ( pVertexBuffer && !IsDynamicVertexBuffer( pVertexBuffer ) ) { delete pVertexBuffer; } } void CMeshMgr::DestroyIndexBuffer( IIndexBuffer *pIndexBuffer ) { if ( pIndexBuffer && !IsDynamicIndexBuffer( pIndexBuffer ) ) { delete pIndexBuffer; } } // Do we need to specify the stream here in the case of locking multiple dynamic VBs on different streams? IVertexBuffer *CMeshMgr::GetDynamicVertexBuffer( int streamID, VertexFormat_t vertexFormat, bool bBuffered ) { if ( IsX360() ) { bBuffered = false; } if ( CompressionType( vertexFormat ) != VERTEX_COMPRESSION_NONE ) { // UNDONE: support compressed dynamic meshes if needed (pro: less VB memory, con: time spent compressing) DebuggerBreak(); return NULL; } // MESHFIXME #if 0 if ( ( m_BufferedMode != bBuffered ) && m_BufferedMode ) { m_BufferedIndexBuffer.SetIndexBuffer( NULL ); } #endif m_BufferedMode = bBuffered; Assert( !m_BufferedMode ); // MESHFIXME: don't deal with buffered VBs yet. bool needTempMesh = ShaderAPI()->IsInSelectionMode(); #ifdef DRAW_SELECTION if( g_bDrawSelection ) { needTempMesh = true; } #endif Assert( !needTempMesh ); // MESHFIXME: don't support temp meshes here yet. CVertexBufferDx8 *pVertexBuffer; if ( needTempMesh ) { Assert( 0 ); // MESHFIXME: don't do this yet. // pVertexBuffer = &m_DynamicTempVertexBuffer; pVertexBuffer = NULL; } else { pVertexBuffer = &m_DynamicVertexBuffer; } if ( m_BufferedMode ) { Assert( 0 ); // don't support this yet. #if 0 Assert( !m_BufferedMesh.WasNotRendered() ); m_BufferedMesh.SetMesh( pMesh ); pMesh = &m_BufferedMesh; #endif } return pVertexBuffer; } IIndexBuffer *CMeshMgr::GetDynamicIndexBuffer( MaterialIndexFormat_t fmt, bool bBuffered ) { if ( IsX360() ) { bBuffered = false; } m_BufferedMode = bBuffered; Assert( !m_BufferedMode ); #ifdef DBGFLAG_ASSERT bool needTempMesh = #endif ShaderAPI()->IsInSelectionMode(); #ifdef DRAW_SELECTION if( g_bDrawSelection ) { needTempMesh = true; } #endif Assert( !needTempMesh ); // don't handle this yet. MESHFIXME CIndexBufferBase *pIndexBuffer = &m_DynamicIndexBuffer; return pIndexBuffer; } void CMeshMgr::SetVertexIDStreamState() { if ( IsX360() ) return; // MESHFIXME : This path is only used for the new index/vertex buffer interfaces. // MESHFIXME : This path is only used for the new index/vertex buffer interfaces. bool bUsingVertexID = false;//IsUsingVertexID(); // if ( bUsingVertexID != g_bUsingVertexID ) { if ( bUsingVertexID ) { // NOTE: Morphing doesn't work with dynamic buffers!!! BLEAH // It's because the indices (which are not 0 based for dynamic buffers) // are accessing both the vertexID buffer + the regular vertex buffer. // This *might* be fixable with baseVertexIndex? Assert( !m_pCurrentVertexBuffer->IsDynamic() ); CVertexBuffer *pVertexIDBuffer = g_MeshMgr.GetVertexIDBuffer( ); RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( pVertexIDBuffer->UID() ); RECORD_INT( 3 ); RECORD_INT( 0 ); RECORD_INT( pVertexIDBuffer->VertexSize() ); D3DSetStreamSource( 3, pVertexIDBuffer->GetInterface(), 0, pVertexIDBuffer->VertexSize() ); pVertexIDBuffer->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); } else { RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); // vertex buffer id RECORD_INT( 3 ); // stream RECORD_INT( 0 ); // vertex offset RECORD_INT( 0 ); // vertex size D3DSetStreamSource( 3, 0, 0, 0 ); } g_bUsingVertexID = bUsingVertexID; } } void CMeshMgr::SetColorStreamState() { if ( g_pLastColorMesh ) { RECORD_COMMAND( DX8_SET_STREAM_SOURCE, 4 ); RECORD_INT( -1 ); // vertex buffer id RECORD_INT( 1 ); // stream RECORD_INT( 0 ); // vertex offset RECORD_INT( 0 ); // vertex size D3DSetStreamSource( 1, 0, 0, 0 ); } g_pLastColorMesh = NULL; g_nLastColorMeshVertOffsetInBytes = 0; } void CMeshMgr::SetVertexStreamState( int nVertOffsetInBytes, int nVertexStride ) { // Calls in here assume shader support... if ( HardwareConfig()->SupportsVertexAndPixelShaders() ) { // Set a 4kb all-zero static VB into the flex/wrinkle stream with a stride of 0 bytes, so the vertex shader always reads valid floating point values (otherwise it can get NaN's/Inf's, and under OpenGL this is bad on NVidia) // togl requires non-zero strides, but on D3D9 we can set a stride of 0 for a little more efficiency. D3DSetStreamSource( 2, g_MeshMgr.GetZeroVertexBuffer(), 0, IsOpenGL() ? 4 : 0 ); // cFlexScale.x masks flex in vertex shader if ( g_pHardwareConfig->Caps().m_SupportsVertexShaders_2_0 ) { float c[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; ShaderAPI()->SetVertexShaderConstant( VERTEX_SHADER_FLEXSCALE, c, 1 ); } g_bFlexMeshStreamSet = false; } // MESHFIXME : This path is only used for the new index/vertex buffer interfaces. if ( g_pLastVertex || ( g_pLastVertexBuffer != m_pCurrentVertexBuffer->GetDx9Buffer() ) || ( g_nLastVertOffsetInBytes != nVertOffsetInBytes ) || ( g_nLastVertStride != nVertexStride )) { Assert( m_pCurrentVertexBuffer && m_pCurrentVertexBuffer->GetDx9Buffer() ); D3DSetStreamSource( 0, m_pCurrentVertexBuffer->GetDx9Buffer(), nVertOffsetInBytes, nVertexStride ); m_pCurrentVertexBuffer->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); g_pLastVertex = NULL; g_nLastVertStride = nVertexStride; g_pLastVertexBuffer = m_pCurrentVertexBuffer->GetDx9Buffer(); g_nLastVertOffsetInBytes = nVertOffsetInBytes; } } bool CMeshMgr::SetRenderState( int nVertexOffsetInBytes, int nFirstVertexIdx, VertexFormat_t vertexFormat, int nVertexStride ) { // Can't set the state if we're deactivated if ( g_pShaderDeviceDx8->IsDeactivated() ) { ResetMeshRenderState(); return false; } // make sure the vertex format is a superset of the current material's // vertex format... // MESHFIXME : This path is only used for the new index/vertex buffer interfaces. #if 0 // FIXME if ( !IsValidVertexFormat( vertexFormat ) ) { Warning( "Material %s is being applied to a model, you need $model=1 in the .vmt file!\n", ShaderAPI()->GetBoundMaterial()->GetName() ); return false; } #endif SetVertexIDStreamState(); SetColorStreamState(); SetVertexStreamState( nVertexOffsetInBytes, nVertexStride ); SetIndexStreamState( nFirstVertexIdx ); return true; } void CMeshMgr::BindVertexBuffer( int nStreamID, IVertexBuffer *pVertexBuffer, int nOffsetInBytes, int nFirstVertex, int nVertexCount, VertexFormat_t fmt, int nRepetitions ) { // FIXME: Multiple stream support isn't implemented yet Assert( nStreamID == 0 ); m_pCurrentVertexBuffer = static_cast< CVertexBufferDx8 * >( pVertexBuffer ); m_CurrentVertexFormat = fmt; m_pVertexBufferOffset[nStreamID] = nOffsetInBytes; m_pCurrentVertexStride[nStreamID] = m_pCurrentVertexBuffer->VertexSize(); m_pFirstVertex[nStreamID] = nFirstVertex; m_pVertexCount[nStreamID] = nVertexCount, m_pVertexIDBuffer = NULL; } void CMeshMgr::BindIndexBuffer( IIndexBuffer *pIndexBuffer, int nOffsetInBytes ) { m_pCurrentIndexBuffer = static_cast< CIndexBufferBase * >( pIndexBuffer ); m_nIndexBufferOffset = nOffsetInBytes; } void CMeshMgr::Draw( MaterialPrimitiveType_t primitiveType, int nFirstIndex, int nIndexCount ) { // MESHFIXME : This path is only used for the new index/vertex buffer interfaces. // make sure we aren't using a morph stream for this path. // Assert( !IsUsingMorphData() ); // Assert( !m_pColorMesh ); SetRenderState( m_pVertexBufferOffset[0], /* nFirstVertexIdx */0, m_CurrentVertexFormat, m_pCurrentVertexStride[0] ); m_PrimitiveType = MATERIAL_TRIANGLES; Assert( primitiveType == MATERIAL_TRIANGLES ); m_nFirstIndex = nFirstIndex; m_nNumIndices = nIndexCount; ShaderAPI()->DrawWithVertexAndIndexBuffers(); } void CMeshMgr::RenderPassWithVertexAndIndexBuffers( void ) { // LOCK_SHADERAPI(); MESHFIXME VPROF( "CShaderAPIDX8::RenderPassWithVertexAndIndexBuffers" ); Assert( m_PrimitiveType != MATERIAL_HETEROGENOUS ); // for ( int iPrim=0; iPrim < s_nPrims; iPrim++ ) { // CPrimList *pPrim = &s_pPrims[iPrim]; // if ( pPrim->m_NumIndices == 0 ) // continue; if ( m_PrimitiveType == MATERIAL_POINTS ) { // (For point lists, we don't actually fill in indices, but we treat it as // though there are indices for the list up until here). Assert( 0 ); // Dx9Device()->DrawPrimitive( ComputeMode( m_PrimitiveType ), s_FirstVertex, pPrim->m_NumIndices ); } else { // int numPrimitives = NumPrimitives( s_NumVertices, pPrim->m_NumIndices ); // Warning( "CMeshMgr::RenderPassWithVertexAndIndexBuffers: DrawIndexedPrimitive: m_nFirstIndex = %d numPrimitives = %d\n", ( int )( ( CDynamiCIndexBufferDx8 * )m_pCurrentIndexBuffer )->m_FirstIndex, ( int )( m_nNumIndices / 3 ) ); { VPROF( "Dx9Device()->DrawIndexedPrimitive" ); // VPROF_INCREMENT_COUNTER( "DrawIndexedPrimitive", 1 ); // VPROF_INCREMENT_COUNTER( "numPrimitives", numPrimitives ); // Dx9Device()->DrawIndexedPrimitive( // m_Mode, // m_FirstIndex, // s_FirstVertex, // s_NumVertices, // pPrim->m_FirstIndex, // numPrimitives ); Assert( m_nFirstIndex >= 0 ); #ifdef CHECK_INDICES // g_pLastVertex - this is the current vertex buffer // g_pLastColorMesh - this is the curent color mesh, if there is one. // g_pLastIndex - this is the current index buffer. // vertoffset : m_FirstIndex CIndexBufferDx8 *pIndexBuffer = assert_cast< CIndexBufferDx8 * >( m_pCurrentIndexBuffer ); if( m_PrimitiveType == MATERIAL_TRIANGLES || m_PrimitiveType == MATERIAL_TRIANGLE_STRIP ) { // FIXME: need to be able to deal with multiple stream here, but don't bother for now. int j; int numVerts = m_pVertexCount[0]; for( j = 0; j < m_nNumIndices; j++ ) { int index = pIndexBuffer->GetShadowIndex( j + m_nFirstIndex ); Assert( index >= m_pFirstVertex[0] ); Assert( index < m_pFirstVertex[0] + numVerts ); } } #endif // CHECK_INDICES Dx9Device()->DrawIndexedPrimitive( ComputeMode( m_PrimitiveType ), // Member of the D3DPRIMITIVETYPE enumerated type, describing the type of primitive to render. D3DPT_POINTLIST is not supported with this method. /*m_FirstIndex*/ 0, // Offset from the start of the vertex buffer to the first vertex index. An index of 0 in the index buffer refers to this location in the vertex buffer. /*s_FirstVertex*/ m_pFirstVertex[0],// Minimum vertex index for vertices used during this call. This is a zero based index relative to BaseVertexIndex. // This is zero for now since we don't do more than one batch yet with the new mesh interface. /*s_NumVertices*/ m_pVertexCount[0], // Number of vertices used during this call. The first vertex is located at index: BaseVertexIndex + MinIndex. // This is simple the number of verts in the current vertex buffer for now since we don't do more than one batch with the new mesh interface. m_nFirstIndex /*pPrim->m_FirstIndex*/, // Index of the first index to use when accesssing the vertex buffer. Beginning at StartIndex to index vertices from the vertex buffer. m_nNumIndices / 3/*numPrimitives*/ // Number of primitives to render. The number of vertices used is a function of the primitive count and the primitive type. ); Assert( CMeshDX8::s_FirstVertex == 0 ); Assert( CMeshDX8::s_NumVertices == 0 ); } } } } //----------------------------------------------------------------------------- void CMeshMgr::SetIndexStreamState( int firstVertexIdx ) { CIndexBufferDx8 *pIndexBuffer = assert_cast< CIndexBufferDx8* >( m_pCurrentIndexBuffer ); IDirect3DIndexBuffer9 *pDx9Buffer = pIndexBuffer ? pIndexBuffer->GetDx9Buffer() : NULL; if ( g_pLastIndex || g_pLastIndexBuffer != pDx9Buffer ) { Dx9Device()->SetIndices( pDx9Buffer ); pIndexBuffer->HandlePerFrameTextureStats( ShaderAPI()->GetCurrentFrameCounter() ); g_pLastIndexBuffer = pDx9Buffer; SafeRelease( &g_pLastIndex ); g_LastVertexIdx = -1; } }