diff --git a/Source/Core/VideoCommon/TextureCacheBase.cpp b/Source/Core/VideoCommon/TextureCacheBase.cpp index ee8491fb86..7d77f48392 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.cpp +++ b/Source/Core/VideoCommon/TextureCacheBase.cpp @@ -162,7 +162,7 @@ void TextureCacheBase::Cleanup(int _frameCount) if ((_frameCount - iter->second->frameCount) % TEXTURE_KILL_THRESHOLD == 1 && iter->second->hash != iter->second->CalculateHash()) { - iter = FreeTexture(iter); + iter = InvalidateTexture(iter); } else { @@ -171,7 +171,7 @@ void TextureCacheBase::Cleanup(int _frameCount) } else { - iter = FreeTexture(iter); + iter = InvalidateTexture(iter); } } else @@ -282,7 +282,7 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(TextureCacheBase::TCacheEntryBas newentry->textures_by_hash_iter = textures_by_hash.emplace((*entry)->hash, newentry); } - FreeTexture(GetTexCacheIter(*entry)); + InvalidateTexture(GetTexCacheIter(*entry)); *entry = newentry; textures_by_address.emplace((*entry)->addr, *entry); @@ -319,6 +319,7 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::DoPartialTextureUpdates(Tex TCacheEntryBase* entry = iter->second; if (entry != entry_to_update && entry->IsEfbCopy() + && entry->references.count(entry_to_update) == 0 && entry->OverlapsMemoryRange(entry_to_update->addr, entry_to_update->size_in_bytes) && entry->memory_stride == numBlocksX * block_size) { @@ -329,6 +330,8 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::DoPartialTextureUpdates(Tex TCacheEntryBase *decoded_entry = entry->ApplyPalette(palette, tlutfmt); if (decoded_entry) { + // Link the efb copy with the partially updated texture, so we won't apply this partial update again + entry->CreateReference(entry_to_update); // Mark the texture update as used, as if it was loaded directly entry->frameCount = FRAMECOUNT_INVALID; entry = decoded_entry; @@ -394,17 +397,26 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::DoPartialTextureUpdates(Tex dstrect.right = (dst_x + copy_width); dstrect.bottom = (dst_y + copy_height); entry_to_update->CopyRectangleFromTexture(entry, srcrect, dstrect); - // Mark the texture update as used, as if it was loaded directly - entry->frameCount = FRAMECOUNT_INVALID; - // Remove the converted texture, it won't be used anywhere else + if (isPaletteTexture) - FreeTexture(GetTexCacheIter(entry)); + { + // Remove the temporary converted texture, it won't be used anywhere else + // TODO: It would be nice to convert and copy in one step, but this code path isn't common + InvalidateTexture(GetTexCacheIter(entry)); + } + else + { + // Link the two textures together, so we won't apply this partial update again + entry->CreateReference(entry_to_update); + // Mark the texture update as used, as if it was loaded directly + entry->frameCount = FRAMECOUNT_INVALID; + } } else { // If the hash does not match, this EFB copy will not be used for anything, so remove it - iter = FreeTexture(iter); + iter = InvalidateTexture(iter); continue; } } @@ -612,7 +624,7 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage) // never be useful again. It's theoretically possible for a game to do // something weird where the copy could become useful in the future, but in // practice it doesn't happen. - iter = FreeTexture(iter); + iter = InvalidateTexture(iter); continue; } } @@ -680,7 +692,7 @@ TextureCacheBase::TCacheEntryBase* TextureCacheBase::Load(const u32 stage) if (temp_frameCount != 0x7fffffff) { // pool this texture and make a new one later - FreeTexture(oldest_entry); + InvalidateTexture(oldest_entry); } std::shared_ptr hires_tex; @@ -1128,13 +1140,17 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFo unsigned int scaled_tex_w = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledX(tex_w) : tex_w; unsigned int scaled_tex_h = g_ActiveConfig.bCopyEFBScaled ? Renderer::EFBToScaledY(tex_h) : tex_h; - // remove all texture cache entries at dstAddr + // Remove all texture cache entries at dstAddr + // It's not possible to have two EFB copies at the same address, this makes sure any old efb copies + // (or normal textures) are removed from texture cache. They are also un-linked from any partially + // updated textures, which forces that partially updated texture to be updated. + // TODO: This also wipes out non-efb copies, which is counterproductive. { std::pair iter_range = textures_by_address.equal_range((u64)dstAddr); TexCache::iterator iter = iter_range.first; while (iter != iter_range.second) { - iter = FreeTexture(iter); + iter = InvalidateTexture(iter); } } @@ -1215,6 +1231,8 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFo // Invalidate all textures that overlap the range of our efb copy. // Unless our efb copy has a weird stride, then we want avoid invalidating textures which // we might be able to do a partial texture update on. + // TODO: This also invalidates partial overlaps, which we currently don't have a better way + // of dealing with. if (dstStride == bytes_per_row || !copy_to_vram) { TexCache::iterator iter = textures_by_address.begin(); @@ -1223,7 +1241,7 @@ void TextureCacheBase::CopyRenderTargetToTexture(u32 dstAddr, unsigned int dstFo if (iter->second->addr + iter->second->size_in_bytes <= dstAddr || iter->second->addr >= dstAddr + num_blocks_y * dstStride) ++iter; else - iter = FreeTexture(iter); + iter = InvalidateTexture(iter); } } @@ -1301,7 +1319,7 @@ TextureCacheBase::TexCache::iterator TextureCacheBase::GetTexCacheIter(TextureCa return textures_by_address.end(); } -TextureCacheBase::TexCache::iterator TextureCacheBase::FreeTexture(TexCache::iterator iter) +TextureCacheBase::TexCache::iterator TextureCacheBase::InvalidateTexture(TexCache::iterator iter) { if (iter == textures_by_address.end()) return textures_by_address.end(); @@ -1314,6 +1332,8 @@ TextureCacheBase::TexCache::iterator TextureCacheBase::FreeTexture(TexCache::ite entry->textures_by_hash_iter = textures_by_hash.end(); } + entry->DestroyAllReferences(); + entry->frameCount = FRAMECOUNT_INVALID; texture_pool.emplace(entry->config, entry); diff --git a/Source/Core/VideoCommon/TextureCacheBase.h b/Source/Core/VideoCommon/TextureCacheBase.h index e87dfc29d1..0acbdfb361 100644 --- a/Source/Core/VideoCommon/TextureCacheBase.h +++ b/Source/Core/VideoCommon/TextureCacheBase.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "VideoCommon/BPMemory.h" @@ -68,6 +69,11 @@ public: // Keep an iterator to the entry in textures_by_hash, so it does not need to be searched when removing the cache entry std::multimap::iterator textures_by_hash_iter; + // This is used to keep track of both: + // * efb copies used by this partially updated texture + // * partially updated textures which refer to this efb copy + std::unordered_set references; + void SetGeneralParameters(u32 _addr, u32 _size, u32 _format) { addr = _addr; @@ -89,6 +95,22 @@ public: hash = _hash; } + // This texture entry is used by the other entry as a sub-texture + void CreateReference(TCacheEntryBase* other_entry) + { + // References are two-way, so they can easily be destroyed later + this->references.emplace(other_entry); + other_entry->references.emplace(this); + } + + void DestroyAllReferences() + { + for (auto& reference : references) + reference->references.erase(this); + + references.clear(); + } + void SetEfbCopy(u32 stride); TCacheEntryBase(const TCacheEntryConfig& c) : config(c) {} @@ -164,7 +186,9 @@ private: static TCacheEntryBase* AllocateTexture(const TCacheEntryConfig& config); static TexCache::iterator GetTexCacheIter(TCacheEntryBase* entry); - static TexCache::iterator FreeTexture(TexCache::iterator t_iter); + + // Removes and unlinks texture from texture cache and returns it to the pool + static TexCache::iterator InvalidateTexture(TexCache::iterator t_iter); static TCacheEntryBase* ReturnEntry(unsigned int stage, TCacheEntryBase* entry);