Restore and plug back in some of the texture invalidation logic. Fixes GT, not much other impact.

This commit is contained in:
Henrik Rydgård
2026-06-09 23:45:24 +02:00
parent e8a1638c8d
commit 9c9e709bfa
2 changed files with 75 additions and 29 deletions
+70 -24
View File
@@ -507,15 +507,10 @@ TexCacheEntry *TextureCacheCommon::SetTexture() {
// We don't set rehash = true here, the declaration above did it properly.
}
// First let's see if another texture with the same address had a hashfail.
if (entry->status & TexStatus::CLUT_RECHECK) {
// Always rehash in this case, if one changed the rest all probably did.
if (entry->status & (TexStatus::HASH_RECHECK | TexStatus::CLUT_RECHECK)) {
// If it's reliable, we can skip the sync domain check, since it won't change under us.
rehash = true;
entry->status &= ~TexStatus::CLUT_RECHECK;
} else if (!gstate_c.IsDirty(DIRTY_TEXTURE_IMAGE)) {
// TODO: This check looks bad.
// Okay, just some parameter change - the data didn't change, no need to rehash.
rehash = false;
entry->status &= ~(TexStatus::HASH_RECHECK | TexStatus::CLUT_RECHECK);
}
// Do we need to recreate?
@@ -794,8 +789,10 @@ void TextureCacheCommon::Decimate(TexCacheEntry *exceptThisOne, bool forcePressu
return;
}
if (forcePressure || cacheSizeEstimate_ >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate_;
// Compute the cache size.
s64 cacheSizeEstimate = CacheSizeEstimate();
if (forcePressure || cacheSizeEstimate >= TEXCACHE_MIN_PRESSURE) {
const u32 had = cacheSizeEstimate;
ForgetLastTexture();
int killAgeBase = lowMemoryMode_ ? TEXTURE_KILL_AGE_LOWMEM : TEXTURE_KILL_AGE;
@@ -813,12 +810,13 @@ void TextureCacheCommon::Decimate(TexCacheEntry *exceptThisOne, bool forcePressu
}
}
VERBOSE_LOG(Log::G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate_, cacheSizeEstimate_);
VERBOSE_LOG(Log::G3D, "Decimated texture cache, saved %d estimated bytes - now %d bytes", had - cacheSizeEstimate, cacheSizeEstimate);
}
s64 secondCacheSizeEstimate = SecondCacheSizeEstimate();
// If enabled, we also need to clear the secondary cache.
if (PSP_CoreParameter().compat.flags().SecondaryTextureCache && (forcePressure || secondCacheSizeEstimate_ >= TEXCACHE_SECOND_MIN_PRESSURE)) {
const u32 had = secondCacheSizeEstimate_;
if (PSP_CoreParameter().compat.flags().SecondaryTextureCache && (forcePressure || secondCacheSizeEstimate >= TEXCACHE_SECOND_MIN_PRESSURE)) {
const u32 had = secondCacheSizeEstimate;
for (TexCache::iterator iter = secondCache_.begin(); iter != secondCache_.end(); ) {
if (iter->second.get() == exceptThisOne) {
@@ -828,14 +826,14 @@ void TextureCacheCommon::Decimate(TexCacheEntry *exceptThisOne, bool forcePressu
// In low memory mode, we kill them all since secondary cache is disabled.
if (lowMemoryMode_ || iter->second->lastFrame + TEXTURE_SECOND_KILL_AGE < gpuStats.totals.numFlips) {
ReleaseTexture(iter->second.get(), true);
secondCacheSizeEstimate_ -= iter->second->EstimateTexMemoryUsage();
secondCacheSizeEstimate -= iter->second->EstimateTexMemoryUsage();
iter = secondCache_.erase(iter);
} else {
++iter;
}
}
VERBOSE_LOG(Log::G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate_, secondCacheSizeEstimate_);
VERBOSE_LOG(Log::G3D, "Decimated second texture cache, saved %d estimated bytes - now %d bytes", had - secondCacheSizeEstimate, secondCacheSizeEstimate);
}
// Decimate known videos.
@@ -864,7 +862,6 @@ bool TextureCacheCommon::IsVideo(u32 texaddr) const {
}
void TextureCacheCommon::HandleTextureChange(TexCacheEntry *const entry, const char *reason, bool initialMatch, bool doDelete) {
cacheSizeEstimate_ -= entry->EstimateTexMemoryUsage();
entry->numInvalidated++;
gpuStats.perFrame.numTexturesChanged++;
DEBUG_LOG(Log::G3D, "Texture different or overwritten, reloading at %08x: %s", entry->addr, reason);
@@ -1534,6 +1531,22 @@ u32 TexCacheEntry::EstimateTexMemoryUsage() const {
return pixelSize << (dimW + dimH);
}
const size_t TextureCacheCommon::CacheSizeEstimate() const {
s64 cacheSizeEstimate = 0;
for (auto &[_ , value] : cache_) {
cacheSizeEstimate += value->EstimateTexMemoryUsage();
}
return cacheSizeEstimate;
}
const size_t TextureCacheCommon::SecondCacheSizeEstimate() const {
s64 cacheSizeEstimate = 0;
for (auto &[_, value] : secondCache_) {
cacheSizeEstimate += value->EstimateTexMemoryUsage();
}
return cacheSizeEstimate;
}
ReplacedTexture *TextureCacheCommon::FindReplacement(TexCacheEntry *entry, int *w, int *h, int *d) {
if (*d != 1) {
// We don't yet support replacing 3D textures.
@@ -2516,8 +2529,6 @@ void TextureCacheCommon::Clear(bool delete_them) {
INFO_LOG(Log::G3D, "Texture cached cleared from %i textures", (int)(cache_.size() + secondCache_.size()));
cache_.clear();
secondCache_.clear();
cacheSizeEstimate_ = 0;
secondCacheSizeEstimate_ = 0;
}
videos_.clear();
@@ -2533,7 +2544,6 @@ void TextureCacheCommon::Clear(bool delete_them) {
void TextureCacheCommon::DeleteTexture(TexCache::iterator it) {
ReleaseTexture(it->second.get(), true);
cacheSizeEstimate_ -= it->second->EstimateTexMemoryUsage();
cache_.erase(it);
}
@@ -2580,7 +2590,6 @@ bool TextureCacheCommon::CheckFullHash(TexCacheEntry *entry, bool &doDelete) {
// It wasn't found, so we're about to throw away the entry and rebuild a texture.
// Let's save this in the secondary cache in case it gets used again.
secondKey = entry->fullhash | ((u64)entry->cluthash << 32);
secondCacheSizeEstimate_ += entry->EstimateTexMemoryUsage();
// If the entry already exists in the secondary texture cache, drop it nicely.
auto oldIter = secondCache_.find(secondKey);
@@ -2604,8 +2613,48 @@ bool TextureCacheCommon::CheckFullHash(TexCacheEntry *entry, bool &doDelete) {
return false;
}
// One type of texture update can happen without the involvement of the CPU: Block transfers.
// This is done for example for the text rendering in Gran Turismo, where it only uses a single letter
// worth of texture memory, and for each letter drawn, it just replaces it and then draws.
void TextureCacheCommon::Invalidate(u32 addr, int size, GPUInvalidationType type) {
// We don't really do anything here right now.
// They could invalidate inside the texture, let's just give a bit of leeway.
// TODO: Keep track of the largest texture size in bytes, and use that instead of this
// humongous unrealistic value.
const int LARGEST_TEXTURE_SIZE = 512 * 512 * 4;
addr &= 0x3FFFFFFF;
const u32 addr_end = addr + size;
if (type == GPU_INVALIDATE_ALL) {
// This is an active signal from the game that something in the texture cache may have changed.
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
} else {
// Do a quick check to see if the current texture could potentially be in range.
const u32 currentAddr = gstate.getTextureAddress(0);
// TODO: This can be made tighter.
if (addr_end >= currentAddr && addr < currentAddr + LARGEST_TEXTURE_SIZE) {
gstate_c.Dirty(DIRTY_TEXTURE_IMAGE);
}
}
const u64 startKey = (u64)(addr - LARGEST_TEXTURE_SIZE) << 32;
u64 endKey = (u64)(addr + size + LARGEST_TEXTURE_SIZE) << 32;
if (endKey < startKey) {
endKey = (u64)-1;
}
// We just loop through all textures in range, and tell them to rehash.
for (TexCache::iterator iter = cache_.lower_bound(startKey), end = cache_.upper_bound(endKey); iter != end; ++iter) {
auto &entry = iter->second;
u32 texAddr = entry->addr;
// Intentional underestimate here.
u32 texEnd = entry->addr + entry->SizeInRAM() / 2;
// Quick check for overlap. Yes the check is right.
if (addr < texEnd && addr_end > texAddr) {
entry->status |= TexStatus::HASH_RECHECK;
}
}
}
void TextureCacheCommon::InvalidateAll(GPUInvalidationType /*unused*/) {
@@ -2629,9 +2678,6 @@ std::string AttachCandidate::ToString() const {
bool TextureCacheCommon::PrepareBuildTexture(BuildTexturePlan &plan, TexCacheEntry *entry) {
gpuStats.perFrame.numTexturesDecoded++;
// For the estimate, we assume cluts always point to 8888 for simplicity.
cacheSizeEstimate_ += entry->EstimateTexMemoryUsage();
plan.badMipSizes = false;
// maxLevel here is the max level to upload. Not the count.
plan.levelsToLoad = entry->maxLevel + 1;
+5 -5
View File
@@ -139,6 +139,7 @@ enum class TexStatus : u16 {
MANY_CLUT_VARIANTS = (1 << 3), // Has multiple CLUT variants.
RELIABLE = (1 << 4), // Hash will never change. This only really applies to the font texture.
CLUT_RECHECK = (1 << 5), // Another texture with same addr had a hashfail.
HASH_RECHECK = (1 << 6), // Hash failed, but addr is the same, so we want to check again next time.
TO_SCALE = (1 << 7), // Pending texture scaling in a later frame.
IS_SCALED_OR_REPLACED = (1 << 8), // Has been scaled already (ignored for replacement checks).
TO_REPLACE = (1 << 9), // Pending texture replacement.
@@ -372,8 +373,8 @@ public:
const TexCache &Cache() const { return cache_; }
const TexCache &SecondCache() const { return secondCache_; }
const size_t CacheSizeEstimate() const { return cacheSizeEstimate_; }
const size_t SecondCacheSizeEstimate() const { return secondCacheSizeEstimate_; }
const size_t CacheSizeEstimate() const;
const size_t SecondCacheSizeEstimate() const;
struct VideoInfo {
u32 addr;
@@ -491,11 +492,10 @@ protected:
// Recomputed once per frame. Depends FPS and soon also config.
double replacementFrameBudgetSeconds_ = 0.5 / 60.0;
// The primary cache uses the texture address along with the clut hash as the key.
TexCache cache_;
u32 cacheSizeEstimate_ = 0;
// The secondary cache uses the texture hash and clut hash as the key.
TexCache secondCache_;
u32 secondCacheSizeEstimate_ = 0;
std::vector<VideoInfo> videos_;