Stop stacking duplicate video entries, and stop hashing video textures

NotifyWriteFormattedFromMemory appends to videos_ unconditionally. A game
blitting its decoded frame to the display buffer does that every displayed
frame while it waits for the next one to decode, so the same two or three
addresses come back over and over: Death Jr pushes 733 entries where there are
two distinct buffers, Tekken 6 around 53,000 where there are three. IsVideo()
walks that vector linearly on every texture. Refresh the matching entry instead
of appending a new one - Death Jr now holds 2 entries and Tekken 6 holds 18,
peak size 2 and 3.

The other half is the two TODOs that were already sitting there. A video
texture is new every frame by definition, so re-hashing it only confirms what
the VIDEO flag already said, and the secondary cache has nothing to offer a
frame that will never recur. Skip both, and with them the secondary lookup that
would otherwise key off a hash we no longer compute.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-09-22 16:10:00 -06:00
1 parent ffc32d02fe
commit 14bfd332dc
3 files changed
+28 -8

No files matched your search

+24 -6
View File
@@ -153,6 +153,7 @@ void TextureCacheCommon::StartFrame() {
gpuStats.perFrame.numReplacerTrackedTex = replacer_.GetNumTrackedTextures();
gpuStats.perFrame.numCachedReplacedTextures = replacer_.GetNumCachedReplacedTextures();
}
gpuStats.perFrame.numVideoTextures = (int)videos_.size();
if (texelsScaledThisFrame_) {
VERBOSE_LOG(Log::TexCache, "Scaled %d texels", texelsScaledThisFrame_);
@@ -710,8 +711,12 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
_dbg_assert_(h == gstate.getTextureHeight(0));
_dbg_assert_(entry->addr == texaddr);
UpdateMaxSeenV(entry, gstate.isModeThrough());
const u32 newFullHash = ComputeTextureHash(replacer_, entry->addr, entry->bufw, w, h, swizzled, entry);
if (newFullHash != entry->fullhash) {
// A video texture is new every frame by definition, so hashing it only confirms what the
// VIDEO flag already said. The replacer is the one thing that still wants the hash, and
// only when it has been told to replace video.
const bool skipHash = isVideo && !(replacer_.Enabled() && replacer_.AllowVideo());
const u32 newFullHash = skipHash ? 0 : ComputeTextureHash(replacer_, entry->addr, entry->bufw, w, h, swizzled, entry);
if (skipHash || newFullHash != entry->fullhash) {
// The texture changed. Throw it in the secondary cache. Then we'll create a new entry later.
gpuStats.perFrame.numTexturesChanged++;
if (!isVideo) {
@@ -765,7 +770,9 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
// Now, look for the new hash in the secondary cache.
// If we find it, we can use that instead of building a new texture. We then pull it out from
// the secondary cache and move it to the main cache.
TexCache::iterator secondIterNew = secondCache_.find(secondKeyNew);
// Without a hash there is nothing to look up by, and a frame that will never recur has
// nothing to gain from the secondary cache anyway.
TexCache::iterator secondIterNew = skipHash ? secondCache_.end() : secondCache_.find(secondKeyNew);
if (secondIterNew != secondCache_.end()) {
// Found it, but does it match our current params? If not, abort.
if (secondIterNew->second->MatchesProperties(dim, texFormat, maxLevel)) {
@@ -904,7 +911,8 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
if (clutInShader) {
entry->status |= TexStatus::CLUT8_INDEXED;
}
if (IsVideo(entry->addr)) {
const bool isVideo = IsVideo(entry->addr);
if (isVideo) {
entry->status |= TexStatus::VIDEO;
}
@@ -912,8 +920,8 @@ TextureApplyResult TextureCacheCommon::ApplyTexture(bool doBind) {
gstate_c.curTextureHeight = h;
UpdateMaxSeenV(entry, gstate.isModeThrough()); // Critical to update this before hashing! As it's used to decide the hash range.
// TODO: Avoid hashing known video textures.
if (!(entry->status & TexStatus::IS_PPGE_ATLAS)) {
const bool skipHash = isVideo && !(replacer_.Enabled() && replacer_.AllowVideo());
if (!(entry->status & TexStatus::IS_PPGE_ATLAS) && !skipHash) {
entry->fullhash = ComputeTextureHash(replacer_, entry->addr, entry->bufw, w, h, swizzled, entry);
}
@@ -1501,6 +1509,16 @@ void TextureCacheCommon::NotifyConfigChanged() {
void TextureCacheCommon::NotifyWriteFormattedFromMemory(u32 addr, int size, int width, GEBufferFormat fmt) {
addr &= 0x3FFFFFFF;
// A game blits its video frame every displayed frame while waiting for the next one, so the
// same few display buffers arrive over and over. Refresh the one we already have rather than
// stacking a duplicate per frame - IsVideo() scans this linearly.
for (VideoInfo &info : videos_) {
if (info.addr == addr) {
info.size = (u32)size;
info.flips = gpuStats.totals.numFlips;
return;
}
}
videos_.push_back({ addr, (u32)size, gpuStats.totals.numFlips });
}
+1
View File
@@ -112,6 +112,7 @@ struct GPUStatsPerFrame {
int numTexturesHashed;
int numTextureDataBytesHashed;
int numTexturesDecoded;
int numVideoTextures;
int numFramebufferEvaluations;
int numFBOsCreated;
int numBlockingReadbacks;
+3 -2
View File
@@ -1748,8 +1748,8 @@ void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) {
gpuStats.perFrame.vertexGPUCycles + gpuStats.perFrame.otherGPUCycles,
vertexAverageCycles);
w.F("FBOs active: %d (evaluations: %d, created %d)\n"
"Textures: %d (s: %d), dec: %d, invalidated: %d, changed %d, hashed: %d kB, clut %d\n"
"readbacks %d (%d non-block), upload %d (cached %d), depal %d\n"
"Text: %d (s: %d), dec: %d, invalidated: %d, changed %d, hashed: %d kB, clut %d\n"
"readbacks %d (%d non-block), upload %d (cached %d), depal %d, video %d\n"
"block transfers: %d\n"
"Cpy: depth %d, color %d, reint %d, blend %d, self %d\n",
(int)framebufferManager_->NumVFBs(),
@@ -1767,6 +1767,7 @@ void GPUCommonHW::FormatGPUStatsCommon(StringWriter &w) {
gpuStats.perFrame.numUploads,
gpuStats.perFrame.numCachedUploads,
gpuStats.perFrame.numDepal,
gpuStats.perFrame.numVideoTextures,
gpuStats.perFrame.numBlockTransfers,
gpuStats.perFrame.numDepthCopies,
gpuStats.perFrame.numColorCopies,