GS: say whether replacement lookups are hitting, not just how many indexed

"My texture pack does nothing" has two failure stages and the log only covered
the first. The indexed count proves the FILES were found and their names parsed;
it says nothing about whether any draw ever asks for one. A pack that indexes
thousands of textures and misses every lookup is a hash problem -- wrong dump
settings, paltex vs CLUT, wrong upscale -- and from outside it looks exactly
like a pack that never loaded at all. Both read as "the mods are not applying".

Count hits and misses in LookupReplacementTexture and summarise one line per
20k lookups. Summarised rather than logged per lookup because this runs per
draw, and log volume on its own is enough to stall the emulator.

Also counts how many misses WOULD have matched with the CLUT hash zeroed. That
separates "the pack does not contain this texture" from "it does, but the
palette hash differs", which is the usual answer for paletted UI art -- fonts
and menu panels, the exact things a Persona 3 FES mod replaces.

Counters reset in ReloadReplacementMap: carried over from a previous game, a
stale hit count reads as a healthy pack.

Also -Wno-missing-braces on the savers target. Welsh's sources initialise nested
aggregates without inner braces throughout, which -Wall diagnoses once per site
per TU: ~5 million lines and an 842 MB build log, which is how it was noticed.
Upstream code we do not restyle, so the diagnostic has nothing to tell us. Purely
a diagnostic flag; no codegen change.
This commit is contained in:
jpolo1224
2026-08-21 17:41:13 -04:00
parent 81d0a3e510
commit b5b2ed24e1
2 changed files with 52 additions and 1 deletions
@@ -517,11 +517,24 @@ static bool GetWrongCasePath(std::string* output, const char* dir, std::string_v
return false;
}
// Lookup accounting for the diagnostic line below. Plain counters on the GS thread, which is the
// only caller of LookupReplacementTexture.
static u64 s_lookup_hits = 0;
static u64 s_lookup_misses = 0;
static u64 s_lookup_clut_only_misses = 0;
static u64 s_lookup_next_report = 0;
void GSTextureReplacements::ReloadReplacementMap()
{
SyncWorkerThread();
ScopedGuard startup_complete_guard([]() { NotifyStartupCompleteForCurrentGame(); });
// Per game: a carried-over hit count from the previous title would read as a healthy pack.
s_lookup_hits = 0;
s_lookup_misses = 0;
s_lookup_clut_only_misses = 0;
s_lookup_next_report = 0;
// clear out the caches
{
s_replacement_texture_filenames.clear();
@@ -687,6 +700,18 @@ bool GSTextureReplacements::HasReplacementTextureWithOtherPalette(const GSTextur
return s_replacement_textures_without_clut_hash.find(name) != s_replacement_textures_without_clut_hash.end();
}
// One line per 20k lookups. Frequent enough to appear within seconds of entering a scene, rare
// enough that it cannot become the log spam that itself slows the emulator down.
static void ReportLookupStatsIfDue()
{
const u64 total = s_lookup_hits + s_lookup_misses;
if (total < s_lookup_next_report)
return;
s_lookup_next_report = total + 20000;
Console.WriteLnFmt("Texture replacements: {} hits, {} misses ({} of the misses match a name whose CLUT hash differs)",
s_lookup_hits, s_lookup_misses, s_lookup_clut_only_misses);
}
GSTexture* GSTextureReplacements::LookupReplacementTexture(const GSTextureCache::HashCacheKey& hash, bool mipmap,
bool* pending, std::pair<u8, u8>* alpha_minmax)
{
@@ -696,7 +721,28 @@ GSTexture* GSTextureReplacements::LookupReplacementTexture(const GSTextureCache:
// replacement for this name exists?
auto fnit = s_replacement_texture_filenames.find(name);
if (fnit == s_replacement_texture_filenames.end())
{
// ★ The second half of the "my pack does nothing" diagnosis, and the half that was
// missing. The indexed count at load time proves the FILES were found; it says nothing
// about whether any draw ever asks for one of them. A pack that indexes thousands of
// textures and then misses every lookup is a HASH problem (wrong dump settings, paltex
// vs CLUT, wrong upscale) and looks identical, from outside, to a pack that never
// loaded at all.
//
// Summarised rather than logged per lookup: this runs per draw, and log volume alone
// can stall the emulator. Also counts how many of those misses would have matched with
// the CLUT hash zeroed, which separates "the pack does not contain this texture" from
// "it does, but the palette hash differs" — the usual cause for paletted UI art.
s_lookup_misses++;
TextureName clutless(name);
clutless.CLUTHash = 0;
if (s_replacement_textures_without_clut_hash.find(clutless) != s_replacement_textures_without_clut_hash.end())
s_lookup_clut_only_misses++;
ReportLookupStatsIfDue();
return nullptr;
}
s_lookup_hits++;
ReportLookupStatsIfDue();
// try the full cache first, to avoid reloading from disk
{
@@ -194,7 +194,12 @@ target_compile_definitions(savers PRIVATE RS_XSCREENSAVER)
set_target_properties(savers PROPERTIES C_STANDARD 99 CXX_STANDARD 17)
# -ffast-math for the same reason as Flurry: particle simulation judged entirely by eye.
target_compile_options(savers PRIVATE -O2 -ffast-math -Wall -Wno-unused-parameter)
# -Wno-missing-braces: Welsh's code initialises nested aggregates without inner braces
# throughout, which -Wall diagnoses once per site per TU -- ~5 MILLION lines and an 842 MB
# build log, which is how it was found. These sources are upstream and are not ours to
# restyle, so the diagnostic has nothing to tell us. Purely a diagnostic flag: no codegen
# difference.
target_compile_options(savers PRIVATE -O2 -ffast-math -Wall -Wno-unused-parameter -Wno-missing-braces)
target_link_libraries(savers log GLESv2 m)