From 2c94ac455ead867aac0a7a689a55d814a8bcc0da Mon Sep 17 00:00:00 2001 From: riperiperi Date: Mon, 1 May 2023 20:27:51 +0100 Subject: [PATCH] GPU: Keep rendered textures without any pool references alive (#4662) * GPU: Keep sampled textures without any pool references alive Occasionally games are very wasteful and clear/write to a texture without ever sampling it. As rendered textures in NVN games seem to all have overlapping memory ranges, the texture will eventually get overwritten. Normally, this would trigger a removal from the auto delete cache, but a pool entry would keep the texture alive. However, with these textures that are never used, they will get deleted immediately and recreated on the next frame. This change makes it so the ShortTextureCache can keep textures that have naver had a pool reference alive for a few frames, so they're not constantly being created and deleted. This improves performance in Zelda BOTW a little. * Cleanup --- .../Image/AutoDeleteCache.cs | 48 +++++++++++++++++-- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 10 +++- .../Image/TextureCache.cs | 22 ++++++++- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index a0b9f57bd..2465efb0f 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -1,5 +1,4 @@ using System.Collections; -using System.Collections.Concurrent; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image @@ -9,6 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// class ShortTextureCacheEntry { + public bool IsAutoDelete; public readonly TextureDescriptor Descriptor; public readonly int InvalidatedSequence; public readonly Texture Texture; @@ -24,6 +24,17 @@ namespace Ryujinx.Graphics.Gpu.Image InvalidatedSequence = texture.InvalidatedSequence; Texture = texture; } + + /// + /// Create a new entry on the short duration texture cache from the auto delete cache. + /// + /// The texture + public ShortTextureCacheEntry(Texture texture) + { + IsAutoDelete = true; + InvalidatedSequence = texture.InvalidatedSequence; + Texture = texture; + } } /// @@ -199,7 +210,11 @@ namespace Ryujinx.Graphics.Gpu.Image { texture.DecrementReferenceCount(); - _shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor); + if (!texture.ShortCacheEntry.IsAutoDelete) + { + _shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor); + } + texture.ShortCacheEntry = null; } } @@ -222,6 +237,25 @@ namespace Ryujinx.Graphics.Gpu.Image texture.IncrementReferenceCount(); } + /// + /// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks. + /// On expiry, it will be removed from the AutoDeleteCache. + /// + /// Texture to add to the short cache + public void AddShortCache(Texture texture) + { + if (texture.ShortCacheEntry != null) + { + var entry = new ShortTextureCacheEntry(texture); + + _shortCacheBuilder.Add(entry); + + texture.ShortCacheEntry = entry; + + texture.IncrementReferenceCount(); + } + } + /// /// Delete textures from the short duration cache. /// Moves the builder set to be deleted on next process. @@ -234,7 +268,15 @@ namespace Ryujinx.Graphics.Gpu.Image { entry.Texture.DecrementReferenceCount(); - _shortCacheLookup.Remove(entry.Descriptor); + if (entry.IsAutoDelete) + { + Remove(entry.Texture, false); + } + else + { + _shortCacheLookup.Remove(entry.Descriptor); + } + entry.Texture.ShortCacheEntry = null; } diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index 3b2579889..6c9de8d6a 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -144,6 +144,11 @@ namespace Ryujinx.Graphics.Gpu.Image /// public ShortTextureCacheEntry ShortCacheEntry { get; set; } + /// + /// Whether this texture has ever been referenced by a pool. + /// + public bool HadPoolOwner { get; private set; } + /// Physical memory ranges where the texture data is located. /// public MultiRange Range { get; private set; } @@ -1506,10 +1511,13 @@ namespace Ryujinx.Graphics.Gpu.Image /// GPU VA of the pool reference public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa) { + HadPoolOwner = true; + lock (_poolOwners) { _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa }); } + _referenceCount++; if (ShortCacheEntry != null) @@ -1594,7 +1602,7 @@ namespace Ryujinx.Graphics.Gpu.Image _poolOwners.Clear(); } - if (ShortCacheEntry != null && _context.IsGpuThread()) + if (ShortCacheEntry != null && !ShortCacheEntry.IsAutoDelete && _context.IsGpuThread()) { // If this is called from another thread (unmapped), the short cache will // have to remove this texture on a future tick. diff --git a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 97d78a349..fc2c07e55 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -848,7 +848,17 @@ namespace Ryujinx.Graphics.Gpu.Image if (overlapInCache) { - _cache.Remove(overlap, flush); + if (flush || overlap.HadPoolOwner || overlap.IsView) + { + _cache.Remove(overlap, flush); + } + else + { + // This texture has only ever been referenced in the AutoDeleteCache. + // Keep this texture alive with the short duration cache, as it may be used often but not sampled. + + _cache.AddShortCache(overlap); + } } removeOverlap = modified; @@ -1198,6 +1208,16 @@ namespace Ryujinx.Graphics.Gpu.Image _cache.AddShortCache(texture, ref descriptor); } + /// + /// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks. + /// On expiry, it will be removed from the AutoDeleteCache. + /// + /// Texture to add to the short cache + public void AddShortCache(Texture texture) + { + _cache.AddShortCache(texture); + } + /// /// Removes a texture from the short duration cache. ///