GS/HW: Reduce duplicates in hash cache

- Don't include TCC in the hashed TEX0 bits.
 - Hash the region size, not rectangle.

Significantly reduces hash cache size in Ace Combat 5, over the course
of 30 frames from 1,000+ textures down to 400.

NOTE: This will change texture replacement hashes. Any "old" region
textures will transparently be converted to the new internal name format
upon loading.
This commit is contained in:
Stenzek 2024-03-10 16:05:20 +10:00 committed by Connor McLaughlin
parent b16bb14c58
commit 4fef86a635
3 changed files with 86 additions and 44 deletions

View File

@ -5035,7 +5035,7 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
if (it != m_hash_cache.end())
{
// super easy, cache hit. remove paltex if it's a replacement texture.
GL_CACHE("HC Hit: %" PRIx64 " %" PRIx64 " R-%" PRIx64, key.TEX0Hash, key.CLUTHash, key.region.bits);
GL_CACHE("HC Hit: %" PRIx64 " %" PRIx64 " R-%ux%u", key.TEX0Hash, key.CLUTHash, key.region_width, key.region_height);
HashCacheEntry* entry = &it->second;
paltex &= (entry->texture->GetFormat() == GSTexture::Format::UNorm8);
entry->refcount++;
@ -5043,7 +5043,7 @@ GSTextureCache::HashCacheEntry* GSTextureCache::LookupHashCache(const GIFRegTEX0
}
// cache miss.
GL_CACHE("HC Miss: %" PRIx64 " %" PRIx64 " R-%" PRIx64, key.TEX0Hash, key.CLUTHash, key.region.bits);
GL_CACHE("HC Miss: %" PRIx64 " %" PRIx64 " R-%ux%u", key.TEX0Hash, key.CLUTHash, key.region_width, key.region_height);
// check for a replacement texture with the full clut key
if (replace)
@ -7005,6 +7005,8 @@ void GSTextureCache::PreloadTexture(const GIFRegTEX0& TEX0, const GIFRegTEXA& TE
GSTextureCache::HashCacheKey::HashCacheKey()
: TEX0Hash(0)
, CLUTHash(0)
, region_width(0)
, region_height(0)
{
TEX0.U64 = 0;
TEXA.U64 = 0;
@ -7014,11 +7016,13 @@ GSTextureCache::HashCacheKey GSTextureCache::HashCacheKey::Create(const GIFRegTE
{
const GSLocalMemory::psm_t& psm = GSLocalMemory::m_psm[TEX0.PSM];
// This should arguably include CPSM, but old replacement textures didn't have it...
HashCacheKey ret;
ret.TEX0.U64 = TEX0.U64 & 0x00000007FFF00000ULL;
ret.TEX0.U64 = TEX0.U64 & 0x00000003FFFFC000ULL; // TBW, PSM, TW, TH
ret.TEXA.U64 = (psm.pal == 0 && psm.fmt > 0) ? (TEXA.U64 & 0x000000FF000080FFULL) : 0;
ret.CLUTHash = clut ? GSTextureCache::PaletteKeyHash{}({clut, psm.pal}) : 0;
ret.region = region;
ret.region_width = static_cast<u16>(region.GetWidth());
ret.region_height = static_cast<u16>(region.GetHeight());
BlockHashState hash_st;
BlockHashReset(hash_st);
@ -7058,6 +7062,7 @@ void GSTextureCache::HashCacheKey::RemoveCLUTHash()
u64 GSTextureCache::HashCacheKeyHash::operator()(const HashCacheKey& key) const
{
std::size_t h = 0;
HashCombine(h, key.TEX0Hash, key.CLUTHash, key.TEX0.U64, key.TEXA.U64, key.region.bits);
HashCombine(h, key.TEX0Hash, key.CLUTHash, key.TEX0.U64, key.TEXA.U64,
static_cast<u64>(key.region_width) | (static_cast<u64>(key.region_height) << 16));
return h;
}

View File

@ -89,7 +89,8 @@ public:
HashType TEX0Hash, CLUTHash;
GIFRegTEX0 TEX0;
GIFRegTEXA TEXA;
SourceRegion region;
u32 region_width;
u32 region_height;
HashCacheKey();
@ -102,6 +103,7 @@ public:
__fi bool operator!=(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) != 0; }
__fi bool operator<(const HashCacheKey& e) const { return std::memcmp(this, &e, sizeof(*this)) < 0; }
};
static_assert(sizeof(HashCacheKey) == 40, "HashCacheKey has no padding");
struct HashCacheKeyHash
{

View File

@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: LGPL-3.0+
#include "common/AlignedMalloc.h"
@ -13,6 +13,7 @@
#include "Config.h"
#include "Host.h"
#include "IconsFontAwesome5.h"
#include "GS/GSExtra.h"
#include "GS/GSLocalMemory.h"
#include "GS/Renderers/HW/GSTextureReplacements.h"
#include "VMManager.h"
@ -31,18 +32,21 @@
// this is a #define instead of a variable to avoid warnings from non-literal format strings
#define TEXTURE_FILENAME_FORMAT_STRING "%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_REGION_FORMAT_STRING "%" PRIx64 "-r%" PRIx64 "-" "-%08x"
#define TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-r%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_REGION_FORMAT_STRING "%" PRIx64 "-r%ux%u-%08x"
#define TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-r%ux%u-%08x"
#define TEXTURE_FILENAME_OLD_REGION_FORMAT_STRING "%" PRIx64 "-r%" PRIx64 "-%08x"
#define TEXTURE_FILENAME_OLD_REGION_CLUT_FORMAT_STRING "%" PRIx64 "-%" PRIx64 "-r%" PRIx64 "-%08x"
#define TEXTURE_REPLACEMENT_SUBDIRECTORY_NAME "replacements"
#define TEXTURE_DUMP_SUBDIRECTORY_NAME "dumps"
namespace
{
struct TextureName // 24 bytes
struct TextureName // 32 bytes
{
u64 TEX0Hash;
u64 CLUTHash;
GSTextureCache::SourceRegion region;
u32 region_width;
u32 region_height;
union
{
@ -51,7 +55,7 @@ namespace
u32 TEX0_PSM : 6;
u32 TEX0_TW : 4;
u32 TEX0_TH : 4;
u32 TEX0_TCC : 1;
u32 unused0 : 1; // was TCC
u32 TEXA_TA0 : 8;
u32 TEXA_AEM : 1;
u32 TEXA_TA1 : 8;
@ -60,25 +64,19 @@ namespace
};
u32 miplevel;
__fi u32 Width() const { return (region.HasX() ? region.GetWidth() : (1u << TEX0_TW)); }
__fi u32 Height() const { return (region.HasY() ? region.GetWidth() : (1u << TEX0_TH)); }
__fi u32 Width() const { return (region_width ? region_width : (1u << TEX0_TW)); }
__fi u32 Height() const { return (region_height ? region_height : (1u << TEX0_TH)); }
__fi bool HasPalette() const { return (GSLocalMemory::m_psm[TEX0_PSM].pal > 0); }
__fi bool HasRegion() const { return region.HasEither(); }
__fi bool HasRegion() const { return (region_width != 0 || region_height != 0); }
__fi bool operator==(const TextureName& rhs) const
__fi bool operator==(const TextureName& rhs) const { return BitEqual(*this, rhs); }
__fi bool operator!=(const TextureName& rhs) const { return !BitEqual(*this, rhs); }
__fi bool operator<(const TextureName& rhs) const { return (std::memcmp(this, &rhs, sizeof(*this)) < 0); }
__fi void RemoveUnusedBits()
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) ==
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
__fi bool operator!=(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) !=
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
}
__fi bool operator<(const TextureName& rhs) const
{
return std::tie(TEX0Hash, CLUTHash, region.bits, bits) <
std::tie(rhs.TEX0Hash, rhs.CLUTHash, region.bits, rhs.bits);
// Remove bits which were previously present, but no longer used.
unused0 = 0;
}
};
static_assert(sizeof(TextureName) == 32, "ReplacementTextureName is expected size");
@ -92,7 +90,9 @@ namespace std
std::size_t operator()(const TextureName& val) const
{
std::size_t h = 0;
HashCombine(h, val.TEX0Hash, val.CLUTHash, val.region.bits, val.bits, val.miplevel);
HashCombine(h, val.TEX0Hash, val.CLUTHash,
static_cast<u64>(val.region_width) | (static_cast<u64>(val.region_height) << 32),
static_cast<u64>(val.bits) | (static_cast<u64>(val.miplevel) << 32));
return h;
}
};
@ -157,30 +157,34 @@ TextureName GSTextureReplacements::CreateTextureName(const GSTextureCache::HashC
name.TEX0_PSM = hash.TEX0.PSM;
name.TEX0_TW = hash.TEX0.TW;
name.TEX0_TH = hash.TEX0.TH;
name.TEX0_TCC = hash.TEX0.TCC;
name.TEXA_TA0 = hash.TEXA.TA0;
name.TEXA_AEM = hash.TEXA.AEM;
name.TEXA_TA1 = hash.TEXA.TA1;
name.TEX0Hash = hash.TEX0Hash;
name.CLUTHash = name.HasPalette() ? hash.CLUTHash : 0;
name.miplevel = miplevel;
name.region = hash.region;
name.region_width = hash.region_width;
name.region_height = hash.region_height;
return name;
}
GSTextureCache::HashCacheKey GSTextureReplacements::HashCacheKeyFromTextureName(const TextureName& tn)
{
const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[tn.TEX0_PSM];
GSTextureCache::HashCacheKey key = {};
key.TEX0.PSM = tn.TEX0_PSM;
key.TEX0.TW = tn.TEX0_TW;
key.TEX0.TH = tn.TEX0_TH;
key.TEX0.TCC = tn.TEX0_TCC;
key.TEXA.TA0 = tn.TEXA_TA0;
key.TEXA.AEM = tn.TEXA_AEM;
key.TEXA.TA1 = tn.TEXA_TA1;
if (psm_s.pal == 0 && psm_s.fmt > 0)
{
key.TEXA.TA0 = tn.TEXA_TA0;
key.TEXA.AEM = tn.TEXA_AEM;
key.TEXA.TA1 = tn.TEXA_TA1;
}
key.TEX0Hash = tn.TEX0Hash;
key.CLUTHash = tn.HasPalette() ? tn.CLUTHash : 0;
key.region = tn.region;
key.region_width = tn.region_width;
key.region_height = tn.region_height;
return key;
}
@ -189,28 +193,56 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
TextureName ret;
ret.miplevel = 0;
GSTextureCache::SourceRegion full_region;
char extension_dot;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash,
&ret.region.bits, &ret.bits, &extension_dot) == 5 &&
&ret.region_width, &ret.region_height, &ret.bits, &extension_dot) == 6 &&
extension_dot == '.')
{
ret.RemoveUnusedBits();
return ret;
}
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.region.bits,
&ret.bits, &extension_dot) == 4 &&
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_REGION_FORMAT_STRING "%c", &ret.TEX0Hash,
&ret.region_width, &ret.region_height, &ret.bits, &extension_dot) == 5 &&
extension_dot == '.')
{
ret.RemoveUnusedBits();
ret.CLUTHash = 0;
return ret;
}
ret.region.bits = 0;
// Allow loading of dumped textures from older versions that included the full region bits.
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_OLD_REGION_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash,
&full_region.bits, &ret.bits, &extension_dot) == 5 &&
extension_dot == '.')
{
ret.RemoveUnusedBits();
ret.region_width = static_cast<u32>(full_region.GetWidth());
ret.region_height = static_cast<u32>(full_region.GetHeight());
return ret;
}
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_OLD_REGION_FORMAT_STRING "%c", &ret.TEX0Hash, &full_region.bits,
&ret.bits, &extension_dot) == 4 &&
extension_dot == '.')
{
ret.RemoveUnusedBits();
ret.CLUTHash = 0;
ret.region_width = static_cast<u32>(full_region.GetWidth());
ret.region_height = static_cast<u32>(full_region.GetHeight());
return ret;
}
ret.region_width = 0;
ret.region_height = 0;
if (std::sscanf(filename.c_str(), TEXTURE_FILENAME_CLUT_FORMAT_STRING "%c", &ret.TEX0Hash, &ret.CLUTHash, &ret.bits,
&extension_dot) == 4 &&
extension_dot == '.')
{
ret.RemoveUnusedBits();
return ret;
}
@ -218,6 +250,7 @@ std::optional<TextureName> GSTextureReplacements::ParseReplacementName(const std
3 &&
extension_dot == '.')
{
ret.RemoveUnusedBits();
ret.CLUTHash = 0;
return ret;
}
@ -258,16 +291,18 @@ std::string GSTextureReplacements::GetDumpFilename(const TextureName& name, u32
{
filename = (level > 0) ?
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING "-mip%u.png",
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits, level) :
name.TEX0Hash, name.CLUTHash, name.region_width, name.region_height, name.bits, level) :
StringUtil::StdStringFromFormat(TEXTURE_FILENAME_REGION_CLUT_FORMAT_STRING ".png",
name.TEX0Hash, name.CLUTHash, name.region.bits, name.bits);
name.TEX0Hash, name.CLUTHash, name.region_width, name.region_height, name.bits);
}
else
{
filename = (level > 0) ? StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING "-mip%u.png", name.TEX0Hash, name.bits, level) :
TEXTURE_FILENAME_REGION_FORMAT_STRING "-mip%u.png", name.TEX0Hash,
name.region_width, name.region_height, name.bits, level) :
StringUtil::StdStringFromFormat(
TEXTURE_FILENAME_FORMAT_STRING ".png", name.TEX0Hash, name.bits);
TEXTURE_FILENAME_REGION_FORMAT_STRING ".png", name.TEX0Hash,
name.region_width, name.region_height, name.bits);
}
}
else