From 609614f7350a0c85925dd68a115ef60bd7c69f4f Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 16 Mar 2021 23:55:45 +0100 Subject: [PATCH 1/3] Const-qualify ComputeHash Needed for an upcoming commit --- src/common/util/hasher.cpp | 2 +- src/common/util/hasher.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/util/hasher.cpp b/src/common/util/hasher.cpp index 7829d1559..5e4bb9633 100644 --- a/src/common/util/hasher.cpp +++ b/src/common/util/hasher.cpp @@ -27,7 +27,7 @@ void InitHasher() } } -uint64_t ComputeHash(void* data, size_t len) +uint64_t ComputeHash(const void* data, size_t len) { if (g_HashAlgorithm == HASH_NONE) { InitHasher(); diff --git a/src/common/util/hasher.h b/src/common/util/hasher.h index 5dc817742..a19a539ca 100644 --- a/src/common/util/hasher.h +++ b/src/common/util/hasher.h @@ -29,6 +29,6 @@ #include -uint64_t ComputeHash(void* data, size_t len); +uint64_t ComputeHash(const void* data, size_t len); #endif From 977fdd360cdcf8bc36ef99bef018b3bf3d38fee5 Mon Sep 17 00:00:00 2001 From: Silent Date: Wed, 17 Mar 2021 18:24:57 +0100 Subject: [PATCH 2/3] Use a compound key in the vertex cache to reduce false cache evictions Now same data viewed with different vertex elements can coexist in the cache instead of evicting elements continuously --- src/core/hle/D3D8/XbVertexBuffer.cpp | 20 ++++++++++---------- src/core/hle/D3D8/XbVertexBuffer.h | 22 ++++++++++++++++++++-- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/core/hle/D3D8/XbVertexBuffer.cpp b/src/core/hle/D3D8/XbVertexBuffer.cpp index ebe28350a..0ab5a1443 100644 --- a/src/core/hle/D3D8/XbVertexBuffer.cpp +++ b/src/core/hle/D3D8/XbVertexBuffer.cpp @@ -195,9 +195,11 @@ inline FLOAT ByteToFloat(const BYTE value) return ((FLOAT)value) / 255.0f; } -CxbxPatchedStream& CxbxVertexBufferConverter::GetPatchedStream(uint64_t key) +CxbxPatchedStream& CxbxVertexBufferConverter::GetPatchedStream(uint64_t dataKey, uint64_t streamInfoKey) { // First, attempt to fetch an existing patched stream + const StreamKey key{ dataKey, streamInfoKey }; + auto it = m_PatchedStreams.find(key); if (it != m_PatchedStreams.end()) { m_PatchedStreamUsageList.splice(m_PatchedStreamUsageList.begin(), m_PatchedStreamUsageList, it->second); @@ -214,7 +216,9 @@ CxbxPatchedStream& CxbxVertexBufferConverter::GetPatchedStream(uint64_t key) // If the cache has exceeded it's upper bound, discard the oldest entries in the cache if (m_PatchedStreams.size() > (m_MaxCacheSize + m_CacheElasticity)) { while (m_PatchedStreams.size() > m_MaxCacheSize) { - m_PatchedStreams.erase(m_PatchedStreamUsageList.back().uiVertexDataHash); + const CxbxPatchedStream& streamToDelete = m_PatchedStreamUsageList.back(); + + m_PatchedStreams.erase({ streamToDelete.uiVertexDataHash, streamToDelete.uiVertexStreamInformationHash }); m_PatchedStreamUsageList.pop_back(); } } @@ -327,20 +331,16 @@ void CxbxVertexBufferConverter::ConvertStream const UINT uiVertexCount = pDrawContext->NumVerticesToUse; const DWORD dwHostVertexDataSize = uiVertexCount * uiHostVertexStride; const DWORD xboxVertexDataSize = uiVertexCount * uiXboxVertexStride; - uint64_t vertexDataHash = ComputeHash(pXboxVertexData, xboxVertexDataSize); - uint64_t pVertexShaderSteamInfoHash = 0; - - if (pVertexShaderStreamInfo != nullptr) { - pVertexShaderSteamInfoHash = ComputeHash(pVertexShaderStreamInfo, sizeof(CxbxVertexShaderStreamInfo)); - } + const uint64_t vertexDataHash = ComputeHash(pXboxVertexData, xboxVertexDataSize); + const uint64_t pVertexShaderSteamInfoHash = pVertexShaderStreamInfo != nullptr ? ComputeHash(pVertexShaderStreamInfo->VertexElements, + sizeof(pVertexShaderStreamInfo->VertexElements[0]) * pVertexShaderStreamInfo->NumberOfVertexElements) : 0; // Lookup implicity inserts a new entry if not exists, so this always works - CxbxPatchedStream& patchedStream = GetPatchedStream(vertexDataHash); + CxbxPatchedStream& patchedStream = GetPatchedStream(vertexDataHash, pVertexShaderSteamInfoHash); // We check a few fields of the patched stream to protect against hash collisions (rare) // but also to protect against games using the exact same vertex data for different vertex formats (Test Case: Burnout) if (patchedStream.isValid && // Check that we found a cached stream - patchedStream.uiVertexStreamInformationHash == pVertexShaderSteamInfoHash && // Check that the vertex conversion is valid patchedStream.uiCachedHostVertexStride == patchedStream.uiCachedHostVertexStride && // Make sure the host stride didn't change patchedStream.uiCachedXboxVertexStride == uiXboxVertexStride && // Make sure the Xbox Stride didn't change patchedStream.uiCachedXboxVertexDataSize == xboxVertexDataSize ) { // Make sure the Xbox Data Size also didn't change diff --git a/src/core/hle/D3D8/XbVertexBuffer.h b/src/core/hle/D3D8/XbVertexBuffer.h index 0ba41fe6d..2e7d1608c 100644 --- a/src/core/hle/D3D8/XbVertexBuffer.h +++ b/src/core/hle/D3D8/XbVertexBuffer.h @@ -31,6 +31,7 @@ #include "Cxbx.h" #include "core\hle\D3D8\XbVertexShader.h" +#include "common\util\hasher.h" // For ComputeHash typedef struct _CxbxDrawContext { @@ -79,6 +80,23 @@ class CxbxVertexBufferConverter void Apply(CxbxDrawContext *pPatchDesc); void PrintStats(); private: + struct StreamKey + { + uint64_t dataKey; + uint64_t streamInfoKey; + + bool operator==(const StreamKey& rhs) const { + return this->dataKey == rhs.dataKey && this->streamInfoKey == rhs.streamInfoKey; + } + }; + + struct StreamKeyHash + { + std::size_t operator()(const StreamKey& k) const { + return static_cast(ComputeHash(&k, sizeof(k))); + } + }; + UINT m_uiNbrStreams; // Stack tracking @@ -87,9 +105,9 @@ class CxbxVertexBufferConverter UINT m_MaxCacheSize = 10000; // Maximum number of entries in the cache UINT m_CacheElasticity = 200; // Cache is allowed to grow this much more than maximum before being purged to maximum - std::unordered_map::iterator> m_PatchedStreams; // Stores references to patched streams for fast lookup + std::unordered_map::iterator, StreamKeyHash> m_PatchedStreams; // Stores references to patched streams for fast lookup std::list m_PatchedStreamUsageList; // Linked list of vertex streams, least recently used is last in the list - CxbxPatchedStream& GetPatchedStream(uint64_t); // Fetches (or inserts) a patched stream associated with the given key + CxbxPatchedStream& GetPatchedStream(uint64_t dataKey, uint64_t streamInfoKey); // Fetches (or inserts) a patched stream associated with the given key CxbxVertexDeclaration *m_pCxbxVertexDeclaration; From dd5196f3ca0f39f885b1dfd74960494e2a19fb88 Mon Sep 17 00:00:00 2001 From: Silent Date: Wed, 17 Mar 2021 18:46:20 +0100 Subject: [PATCH 3/3] Reduce the amount of state in CxbxVertexBufferConverter Also removes some legacy code in XbVertexBuffer --- src/core/hle/D3D8/XbVertexBuffer.cpp | 38 ++++++++++------------------ src/core/hle/D3D8/XbVertexBuffer.h | 16 +++++------- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/core/hle/D3D8/XbVertexBuffer.cpp b/src/core/hle/D3D8/XbVertexBuffer.cpp index 0ab5a1443..eb3c8d526 100644 --- a/src/core/hle/D3D8/XbVertexBuffer.cpp +++ b/src/core/hle/D3D8/XbVertexBuffer.cpp @@ -111,11 +111,6 @@ void CxbxPatchedStream::Activate(CxbxDrawContext *pDrawContext, UINT HostStreamN } } -CxbxPatchedStream::CxbxPatchedStream() -{ - isValid = false; -} - void CxbxPatchedStream::Clear() { if (bCachedHostVertexStreamZeroDataIsAllocated) { @@ -136,12 +131,6 @@ CxbxPatchedStream::~CxbxPatchedStream() Clear(); } -CxbxVertexBufferConverter::CxbxVertexBufferConverter() -{ - m_uiNbrStreams = 0; - m_pCxbxVertexDeclaration = nullptr; -} - // TODO: CountActiveD3DStreams must be removed once we can rely on CxbxGetVertexDeclaration always being set int CountActiveD3DStreams() { @@ -155,7 +144,7 @@ int CountActiveD3DStreams() return StreamCount; } -UINT CxbxVertexBufferConverter::GetNbrStreams(CxbxDrawContext *pDrawContext) +UINT CxbxVertexBufferConverter::GetNbrStreams(CxbxDrawContext *pDrawContext) const { // Draw..Up always have one stream if (pDrawContext->pXboxVertexStreamZeroData != xbox::zeroptr) { @@ -236,7 +225,8 @@ void CxbxVertexBufferConverter::PrintStats() void CxbxVertexBufferConverter::ConvertStream ( - CxbxDrawContext *pDrawContext, + CxbxDrawContext *pDrawContext, + CxbxVertexDeclaration* pCxbxVertexDeclaration, UINT uiStream ) { @@ -245,13 +235,13 @@ void CxbxVertexBufferConverter::ConvertStream CxbxVertexShaderStreamInfo *pVertexShaderStreamInfo = nullptr; UINT XboxStreamNumber = uiStream; - if (m_pCxbxVertexDeclaration != nullptr) { - if (uiStream > m_pCxbxVertexDeclaration->NumberOfVertexStreams) { + if (pCxbxVertexDeclaration != nullptr) { + if (uiStream > pCxbxVertexDeclaration->NumberOfVertexStreams) { LOG_TEST_CASE("uiStream > NumberOfVertexStreams"); return; } - pVertexShaderStreamInfo = &(m_pCxbxVertexDeclaration->VertexStreams[uiStream]); + pVertexShaderStreamInfo = &(pCxbxVertexDeclaration->VertexStreams[uiStream]); XboxStreamNumber = pVertexShaderStreamInfo->XboxStreamIndex; } @@ -631,7 +621,7 @@ void CxbxVertexBufferConverter::Apply(CxbxDrawContext *pDrawContext) if ((pDrawContext->XboxPrimitiveType < xbox::X_D3DPT_POINTLIST) || (pDrawContext->XboxPrimitiveType > xbox::X_D3DPT_POLYGON)) CxbxKrnlCleanup("Unknown primitive type: 0x%.02X\n", pDrawContext->XboxPrimitiveType); - m_pCxbxVertexDeclaration = CxbxGetVertexDeclaration(); + CxbxVertexDeclaration* pCxbxVertexDeclaration = CxbxGetVertexDeclaration(); // When this is an indexed draw, take the index buffer into account if (pDrawContext->pXboxIndexData) { @@ -652,14 +642,14 @@ void CxbxVertexBufferConverter::Apply(CxbxDrawContext *pDrawContext) } // Get the number of streams - m_uiNbrStreams = GetNbrStreams(pDrawContext); - if (m_uiNbrStreams > X_VSH_MAX_STREAMS) { - LOG_TEST_CASE("m_uiNbrStreams count > max number of streams"); - m_uiNbrStreams = X_VSH_MAX_STREAMS; - } + UINT nbrStreams = GetNbrStreams(pDrawContext); + if (nbrStreams > X_VSH_MAX_STREAMS) { + LOG_TEST_CASE("nbrStreams count > max number of streams"); + nbrStreams = X_VSH_MAX_STREAMS; + } - for(UINT i = 0; i < m_uiNbrStreams; i++) { - ConvertStream(pDrawContext, i); + for(UINT i = 0; i < nbrStreams; i++) { + ConvertStream(pDrawContext, pCxbxVertexDeclaration, i); } if (pDrawContext->XboxPrimitiveType == xbox::X_D3DPT_QUADSTRIP) { diff --git a/src/core/hle/D3D8/XbVertexBuffer.h b/src/core/hle/D3D8/XbVertexBuffer.h index 2e7d1608c..cad1fd7c9 100644 --- a/src/core/hle/D3D8/XbVertexBuffer.h +++ b/src/core/hle/D3D8/XbVertexBuffer.h @@ -55,7 +55,7 @@ CxbxDrawContext; class CxbxPatchedStream { public: - CxbxPatchedStream(); + CxbxPatchedStream() = default; ~CxbxPatchedStream(); void Clear(); void Activate(CxbxDrawContext *pDrawContext, UINT HostStreamNumber) const; @@ -76,7 +76,7 @@ public: class CxbxVertexBufferConverter { public: - CxbxVertexBufferConverter(); + CxbxVertexBufferConverter() = default; void Apply(CxbxDrawContext *pPatchDesc); void PrintStats(); private: @@ -97,25 +97,21 @@ class CxbxVertexBufferConverter } }; - UINT m_uiNbrStreams; - // Stack tracking ULONG m_TotalCacheHits = 0; ULONG m_TotalCacheMisses = 0; - UINT m_MaxCacheSize = 10000; // Maximum number of entries in the cache - UINT m_CacheElasticity = 200; // Cache is allowed to grow this much more than maximum before being purged to maximum + const UINT m_MaxCacheSize = 10000; // Maximum number of entries in the cache + const UINT m_CacheElasticity = 200; // Cache is allowed to grow this much more than maximum before being purged to maximum std::unordered_map::iterator, StreamKeyHash> m_PatchedStreams; // Stores references to patched streams for fast lookup std::list m_PatchedStreamUsageList; // Linked list of vertex streams, least recently used is last in the list CxbxPatchedStream& GetPatchedStream(uint64_t dataKey, uint64_t streamInfoKey); // Fetches (or inserts) a patched stream associated with the given key - CxbxVertexDeclaration *m_pCxbxVertexDeclaration; - // Returns the number of streams of a patch - UINT GetNbrStreams(CxbxDrawContext *pPatchDesc); + UINT GetNbrStreams(CxbxDrawContext *pPatchDesc) const; // Patches the types of the stream - void ConvertStream(CxbxDrawContext *pPatchDesc, UINT uiStream); + void ConvertStream(CxbxDrawContext *pPatchDesc, CxbxVertexDeclaration* pCxbxVertexDeclaration, UINT uiStream); }; // Inline vertex buffer emulation