diff options
author | riperiperi <rhy3756547@hotmail.com> | 2022-11-18 14:58:24 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-18 14:58:24 +0000 |
commit | 187372cbde56a8892e4810d8c99d6e2debd96ad5 (patch) | |
tree | d93914a3af02f74ddb838b3c74b7bda3206418d4 /Ryujinx.Graphics.Gpu/Memory/BufferCache.cs | |
parent | 022d4953356ba999c447a4165891052eba8e307e (diff) |
Prune ForceDirty and CheckModified caches on unmap (#3862)
* Prune ForceDirty and CheckModified caches on unmap
Since we're now using this for modified checks on the HLE indirect draw method, I'm worried that leaving these to forever gather cache entries isn't the best idea for performance in the long term, and it could keep old buffer objects alive for longer than they should be.
This PR adds the ability to prune invalid entries before checking these caches, and queues it whenever gpu memory is unmapped. It also aligns modified checks to the page size, as I figured it would be possible for a huge number of overlapping over a game's runtime.
This prevents Super Mario Odyssey from having 10s of thousands of entries in the modified cache in Metro Kingdom, and them duplicating when entering and leaving a building (should be cleared, as they were unmapped).
* Address Feedback
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Memory/BufferCache.cs')
-rw-r--r-- | Ryujinx.Graphics.Gpu/Memory/BufferCache.cs | 78 |
1 files changed, 72 insertions, 6 deletions
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs index 894d009c..85ed49d5 100644 --- a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs +++ b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs @@ -28,6 +28,7 @@ namespace Ryujinx.Graphics.Gpu.Memory private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache; private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache; + private bool _pruneCaches; public event Action NotifyBuffersModified; @@ -136,6 +137,11 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <param name="size">Size in bytes of the buffer</param> public void ForceDirty(MemoryManager memoryManager, ulong gpuVa, ulong size) { + if (_pruneCaches) + { + Prune(); + } + if (!_dirtyCache.TryGetValue(gpuVa, out BufferCacheEntry result) || result.EndGpuAddress < gpuVa + size || result.UnmappedSequence != result.Buffer.UnmappedSequence) @@ -158,17 +164,29 @@ namespace Ryujinx.Graphics.Gpu.Memory /// <returns>True if modified, false otherwise</returns> public bool CheckModified(MemoryManager memoryManager, ulong gpuVa, ulong size, out ulong outAddr) { - if (!_modifiedCache.TryGetValue(gpuVa, out BufferCacheEntry result) || - result.EndGpuAddress < gpuVa + size || + if (_pruneCaches) + { + Prune(); + } + + // Align the address to avoid creating too many entries on the quick lookup dictionary. + ulong mask = BufferAlignmentMask; + ulong alignedGpuVa = gpuVa & (~mask); + ulong alignedEndGpuVa = (gpuVa + size + mask) & (~mask); + + size = alignedEndGpuVa - alignedGpuVa; + + if (!_modifiedCache.TryGetValue(alignedGpuVa, out BufferCacheEntry result) || + result.EndGpuAddress < alignedEndGpuVa || result.UnmappedSequence != result.Buffer.UnmappedSequence) { - ulong address = TranslateAndCreateBuffer(memoryManager, gpuVa, size); - result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size)); + ulong address = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size); + result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size)); - _modifiedCache[gpuVa] = result; + _modifiedCache[alignedGpuVa] = result; } - outAddr = result.Address; + outAddr = result.Address | (gpuVa & mask); return result.Buffer.IsModified(result.Address, size); } @@ -436,6 +454,54 @@ namespace Ryujinx.Graphics.Gpu.Memory } /// <summary> + /// Prune any invalid entries from a quick access dictionary. + /// </summary> + /// <param name="dictionary">Dictionary to prune</param> + /// <param name="toDelete">List used to track entries to delete</param> + private void Prune(Dictionary<ulong, BufferCacheEntry> dictionary, ref List<ulong> toDelete) + { + foreach (var entry in dictionary) + { + if (entry.Value.UnmappedSequence != entry.Value.Buffer.UnmappedSequence) + { + (toDelete ??= new()).Add(entry.Key); + } + } + + if (toDelete != null) + { + foreach (ulong entry in toDelete) + { + dictionary.Remove(entry); + } + } + } + + /// <summary> + /// Prune any invalid entries from the quick access dictionaries. + /// </summary> + private void Prune() + { + List<ulong> toDelete = null; + + Prune(_dirtyCache, ref toDelete); + + toDelete?.Clear(); + + Prune(_modifiedCache, ref toDelete); + + _pruneCaches = false; + } + + /// <summary> + /// Queues a prune of invalid entries the next time a dictionary cache is accessed. + /// </summary> + public void QueuePrune() + { + _pruneCaches = true; + } + + /// <summary> /// Disposes all buffers in the cache. /// It's an error to use the buffer manager after disposal. /// </summary> |