mirror of https://github.com/PCSX2/pcsx2.git
GS/HW: Put a cap on the hash cache count
As well as VRAM usage. Stops Corvette allocating 16,000+ textures. Also reduce max age for hash cache sources, since they get kept around in the latter for another 30 frames.
This commit is contained in:
parent
a32ab4cc97
commit
a4e99366fb
|
@ -35,6 +35,9 @@ std::unique_ptr<GSTextureCache> g_texture_cache;
|
|||
|
||||
static u8* s_unswizzle_buffer;
|
||||
|
||||
/// List of candidates for purging when the hash cache gets too large.
|
||||
static std::vector<std::pair<GSTextureCache::HashCacheMap::iterator, s32>> s_hash_cache_purge_list;
|
||||
|
||||
GSTextureCache::GSTextureCache()
|
||||
{
|
||||
// In theory 4MB is enough but 9MB is safer for overflow (8MB
|
||||
|
@ -50,6 +53,7 @@ GSTextureCache::~GSTextureCache()
|
|||
{
|
||||
RemoveAll();
|
||||
|
||||
s_hash_cache_purge_list = {};
|
||||
_aligned_free(s_unswizzle_buffer);
|
||||
}
|
||||
|
||||
|
@ -2839,29 +2843,11 @@ void GSTextureCache::IncAge()
|
|||
Source* s = *i;
|
||||
|
||||
++i;
|
||||
if (++s->m_age > (s->CanPreload() ? max_preload_age : max_age))
|
||||
if (++s->m_age > ((!s->m_from_hash_cache && s->CanPreload()) ? max_preload_age : max_age))
|
||||
m_src.RemoveAt(s);
|
||||
}
|
||||
|
||||
const u32 max_hash_cache_age = 30;
|
||||
for (auto it = m_hash_cache.begin(); it != m_hash_cache.end();)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
if (e.refcount == 0 && ++e.age > max_hash_cache_age)
|
||||
{
|
||||
const u32 mem_usage = e.texture->GetMemUsage();
|
||||
if (e.is_replacement)
|
||||
m_hash_cache_replacement_memory_usage -= mem_usage;
|
||||
else
|
||||
m_hash_cache_memory_usage -= mem_usage;
|
||||
g_gs_device->Recycle(e.texture);
|
||||
m_hash_cache.erase(it++);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
AgeHashCache();
|
||||
|
||||
// Clearing of Rendertargets causes flickering in many scene transitions.
|
||||
// Sigh, this seems to be used to invalidate surfaces. So set a huge maxage to avoid flicker,
|
||||
|
@ -3707,6 +3693,70 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
|
|||
return &m_hash_cache.emplace(key, entry).first->second;
|
||||
}
|
||||
|
||||
void GSTextureCache::RemoveFromHashCache(HashCacheMap::iterator it)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
const u32 mem_usage = e.texture->GetMemUsage();
|
||||
if (e.is_replacement)
|
||||
m_hash_cache_replacement_memory_usage -= mem_usage;
|
||||
else
|
||||
m_hash_cache_memory_usage -= mem_usage;
|
||||
g_gs_device->Recycle(e.texture);
|
||||
m_hash_cache.erase(it);
|
||||
}
|
||||
|
||||
void GSTextureCache::AgeHashCache()
|
||||
{
|
||||
// Where did this number come from?
|
||||
// A game called Corvette draws its background FMVs with a ton of 17x17 tiles, which ends up
|
||||
// being about 600 texture uploads per frame. We'll use 800 as an upper bound for a bit of
|
||||
// a buffer, hopefully nothing's going to end up with more textures than that.
|
||||
constexpr u32 MAX_HASH_CACHE_SIZE = 800;
|
||||
constexpr u32 MAX_HASH_CACHE_AGE = 30;
|
||||
|
||||
bool might_need_cache_purge = (m_hash_cache.size() > MAX_HASH_CACHE_SIZE);
|
||||
if (might_need_cache_purge)
|
||||
s_hash_cache_purge_list.clear();
|
||||
|
||||
for (auto it = m_hash_cache.begin(); it != m_hash_cache.end();)
|
||||
{
|
||||
HashCacheEntry& e = it->second;
|
||||
if (e.refcount > 0)
|
||||
{
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++e.age > MAX_HASH_CACHE_AGE)
|
||||
{
|
||||
RemoveFromHashCache(it++);
|
||||
continue;
|
||||
}
|
||||
|
||||
// We might free up enough just with "normal" removals above.
|
||||
if (might_need_cache_purge)
|
||||
{
|
||||
might_need_cache_purge = (m_hash_cache.size() > MAX_HASH_CACHE_SIZE);
|
||||
if (might_need_cache_purge)
|
||||
s_hash_cache_purge_list.emplace_back(it, static_cast<s32>(e.age));
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
// Pushing to a list, sorting, and removing ends up faster than re-iterating the map.
|
||||
if (might_need_cache_purge)
|
||||
{
|
||||
std::sort(s_hash_cache_purge_list.begin(), s_hash_cache_purge_list.end(),
|
||||
[](const auto& lhs, const auto& rhs) { return lhs.second - rhs.second; });
|
||||
|
||||
const u32 entries_to_purge = std::min(static_cast<u32>(m_hash_cache.size() - MAX_HASH_CACHE_SIZE),
|
||||
static_cast<u32>(s_hash_cache_purge_list.size()));
|
||||
for (u32 i = 0; i < entries_to_purge; i++)
|
||||
RemoveFromHashCache(s_hash_cache_purge_list[i].first);
|
||||
}
|
||||
}
|
||||
|
||||
GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int w, int h, float scale, int type, const bool clear)
|
||||
{
|
||||
ASSERT(type == RenderTarget || type == DepthStencil);
|
||||
|
|
|
@ -123,6 +123,8 @@ public:
|
|||
bool is_replacement;
|
||||
};
|
||||
|
||||
using HashCacheMap = std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash>;
|
||||
|
||||
class Surface : public GSAlignedClass<32>
|
||||
{
|
||||
protected:
|
||||
|
@ -374,7 +376,7 @@ protected:
|
|||
PaletteMap m_palette_map;
|
||||
SourceMap m_src;
|
||||
u64 m_source_memory_usage = 0;
|
||||
std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash> m_hash_cache;
|
||||
HashCacheMap m_hash_cache;
|
||||
u64 m_hash_cache_memory_usage = 0;
|
||||
u64 m_hash_cache_replacement_memory_usage = 0;
|
||||
|
||||
|
@ -402,6 +404,8 @@ protected:
|
|||
bool PrepareDownloadTexture(u32 width, u32 height, GSTexture::Format format, std::unique_ptr<GSDownloadTexture>* tex);
|
||||
|
||||
HashCacheEntry* LookupHashCache(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool& paltex, const u32* clut, const GSVector2i* lod, SourceRegion region);
|
||||
void RemoveFromHashCache(HashCacheMap::iterator it);
|
||||
void AgeHashCache();
|
||||
|
||||
static void PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region, GSLocalMemory& mem, bool paltex, GSTexture* tex, u32 level);
|
||||
static HashType HashTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, SourceRegion region);
|
||||
|
|
Loading…
Reference in New Issue