From 94a64f2aea3225d83a2aa1e61ed8d4bf8be49e5c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 10 Jan 2023 22:53:56 -0300 Subject: [PATCH] Remove textures from cache on unmap if not mapped and modified (#4211) --- Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs | 26 +++++++++++++++++++ Ryujinx.Graphics.Gpu/Image/Texture.cs | 7 +++++ Ryujinx.Graphics.Gpu/Image/TextureCache.cs | 13 ++++++++++ Ryujinx.Graphics.Gpu/Image/TextureGroup.cs | 26 +++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 277f39b98..4a1615f04 100644 --- a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image @@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Image private const int MaxCapacity = 2048; private readonly LinkedList _textures; + private readonly ConcurrentQueue _deferredRemovals; /// /// Creates a new instance of the automatic deletion cache. @@ -20,6 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Image public AutoDeleteCache() { _textures = new LinkedList(); + _deferredRemovals = new ConcurrentQueue(); } /// @@ -56,6 +59,14 @@ namespace Ryujinx.Graphics.Gpu.Image oldestTexture.CacheNode = null; } + + if (_deferredRemovals.Count > 0) + { + while (_deferredRemovals.TryDequeue(out Texture textureToRemove)) + { + Remove(textureToRemove, false); + } + } } /// @@ -84,6 +95,12 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Removes a texture from the cache. + /// + /// The texture to be removed from the cache + /// True to remove the texture if it was on the cache + /// True if the texture was found and removed, false otherwise public bool Remove(Texture texture, bool flush) { if (texture.CacheNode == null) @@ -104,6 +121,15 @@ namespace Ryujinx.Graphics.Gpu.Image return texture.DecrementReferenceCount(); } + /// + /// Queues removal of a texture from the cache in a thread safe way. + /// + /// The texture to be removed from the cache + public void RemoveDeferred(Texture texture) + { + _deferredRemovals.Enqueue(texture); + } + public IEnumerator GetEnumerator() { return _textures.GetEnumerator(); diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index 0995314d0..f0c31be6f 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -1676,6 +1676,13 @@ namespace Ryujinx.Graphics.Gpu.Image } RemoveFromPools(true); + + // We only want to remove if there's no mapped region of the texture that was modified by the GPU, + // otherwise we could lose data. + if (!Group.AnyModified(this)) + { + _physicalMemory.TextureCache.QueueAutoDeleteCacheRemoval(this); + } } /// diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index 16bfc6931..c020f4c82 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -1165,6 +1165,19 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Queues the removal of a texture from the auto delete cache. + /// + /// + /// This function is thread safe and can be called from any thread. + /// The texture will be deleted on the next time the cache is used. + /// + /// The texture to be removed + public void QueueAutoDeleteCacheRemoval(Texture texture) + { + _cache.RemoveDeferred(texture); + } + /// /// Disposes all textures and samplers in the cache. /// It's an error to use the texture cache after disposal. diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs index ca54dc2f2..cd17564a0 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs @@ -434,6 +434,32 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Checks if a texture was modified by the GPU. + /// + /// The texture to be checked + /// True if any region of the texture was modified by the GPU, false otherwise + public bool AnyModified(Texture texture) + { + bool anyModified = false; + + EvaluateRelevantHandles(texture, (baseHandle, regionCount, split) => + { + for (int i = 0; i < regionCount; i++) + { + TextureGroupHandle group = _handles[baseHandle + i]; + + if (group.Modified) + { + anyModified = true; + break; + } + } + }); + + return anyModified; + } + /// /// Flush modified ranges for a given texture. ///