diff --git a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs index 4a1615f0..379eb715 100644 --- a/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs @@ -4,6 +4,28 @@ using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image { + /// + /// An entry on the short duration texture cache. + /// + class ShortTextureCacheEntry + { + public readonly TextureDescriptor Descriptor; + public readonly int InvalidatedSequence; + public readonly Texture Texture; + + /// + /// Create a new entry on the short duration texture cache. + /// + /// Last descriptor that referenced the texture + /// The texture + public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture) + { + Descriptor = descriptor; + InvalidatedSequence = texture.InvalidatedSequence; + Texture = texture; + } + } + /// /// A texture cache that automatically removes older textures that are not used for some time. /// The cache works with a rotated list with a fixed size. When new textures are added, the @@ -16,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Image private readonly LinkedList _textures; private readonly ConcurrentQueue _deferredRemovals; + private HashSet _shortCacheBuilder; + private HashSet _shortCache; + + private Dictionary _shortCacheLookup; + /// /// Creates a new instance of the automatic deletion cache. /// @@ -23,6 +50,11 @@ namespace Ryujinx.Graphics.Gpu.Image { _textures = new LinkedList(); _deferredRemovals = new ConcurrentQueue(); + + _shortCacheBuilder = new HashSet(); + _shortCache = new HashSet(); + + _shortCacheLookup = new Dictionary(); } /// @@ -130,6 +162,85 @@ namespace Ryujinx.Graphics.Gpu.Image _deferredRemovals.Enqueue(texture); } + /// + /// Attempt to find a texture on the short duration cache. + /// + /// The texture descriptor + /// The texture if found, null otherwise + public Texture FindShortCache(in TextureDescriptor descriptor) + { + if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry)) + { + if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence) + { + return entry.Texture; + } + else + { + _shortCacheLookup.Remove(descriptor); + } + } + + return null; + } + + /// + /// Removes a texture from the short duration cache. + /// + /// Texture to remove from the short cache + public void RemoveShortCache(Texture texture) + { + bool removed = _shortCache.Remove(texture.ShortCacheEntry); + removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry); + + if (removed) + { + texture.DecrementReferenceCount(); + + _shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor); + texture.ShortCacheEntry = null; + } + } + + /// + /// Adds a texture to the short duration cache. + /// It starts in the builder set, and it is moved into the deletion set on next process. + /// + /// Texture to add to the short cache + /// Last used texture descriptor + public void AddShortCache(Texture texture, ref TextureDescriptor descriptor) + { + var entry = new ShortTextureCacheEntry(descriptor, texture); + + _shortCacheBuilder.Add(entry); + _shortCacheLookup.Add(entry.Descriptor, entry); + + texture.ShortCacheEntry = entry; + + texture.IncrementReferenceCount(); + } + + /// + /// Delete textures from the short duration cache. + /// Moves the builder set to be deleted on next process. + /// + public void ProcessShortCache() + { + HashSet toRemove = _shortCache; + + foreach (var entry in toRemove) + { + entry.Texture.DecrementReferenceCount(); + + _shortCacheLookup.Remove(entry.Descriptor); + entry.Texture.ShortCacheEntry = null; + } + + toRemove.Clear(); + _shortCache = _shortCacheBuilder; + _shortCacheBuilder = toRemove; + } + public IEnumerator GetEnumerator() { return _textures.GetEnumerator(); diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs index ddd69807..ee4c051f 100644 --- a/Ryujinx.Graphics.Gpu/Image/Pool.cs +++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs @@ -91,7 +91,17 @@ namespace Ryujinx.Graphics.Gpu.Image /// A reference to the descriptor public ref readonly T2 GetDescriptorRef(int id) { - return ref MemoryMarshal.Cast(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0]; + return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize); + } + + /// + /// Gets a reference to the descriptor for a given address. + /// + /// Address of the descriptor + /// A reference to the descriptor + public ref readonly T2 GetDescriptorRefAddress(ulong address) + { + return ref MemoryMarshal.Cast(PhysicalMemory.GetSpan(address, DescriptorSize))[0]; } /// diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs index f0c31be6..cfe57756 100644 --- a/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -138,6 +138,10 @@ namespace Ryujinx.Graphics.Gpu.Image public LinkedListNode CacheNode { get; set; } /// + /// Entry for this texture in the short duration cache, if present. + /// + public ShortTextureCacheEntry ShortCacheEntry { get; set; } + /// Physical memory ranges where the texture data is located. /// public MultiRange Range { get; private set; } @@ -1555,6 +1559,20 @@ namespace Ryujinx.Graphics.Gpu.Image _poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id }); } _referenceCount++; + + if (ShortCacheEntry != null) + { + _physicalMemory.TextureCache.RemoveShortCache(this); + } + } + + /// + /// Indicates that the texture has one reference left, and will delete on reference decrement. + /// + /// True if there is one reference remaining, false otherwise + public bool HasOneReference() + { + return _referenceCount == 1; } /// @@ -1624,6 +1642,14 @@ namespace Ryujinx.Graphics.Gpu.Image _poolOwners.Clear(); } + if (ShortCacheEntry != null && _context.IsGpuThread()) + { + // If this is called from another thread (unmapped), the short cache will + // have to remove this texture on a future tick. + + _physicalMemory.TextureCache.RemoveShortCache(this); + } + InvalidatedSequence++; } diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs index c020f4c8..49adecdc 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs @@ -894,6 +894,16 @@ namespace Ryujinx.Graphics.Gpu.Image } } + /// + /// Attempt to find a texture on the short duration cache. + /// + /// The texture descriptor + /// The texture if found, null otherwise + public Texture FindShortCache(in TextureDescriptor descriptor) + { + return _cache.FindShortCache(descriptor); + } + /// /// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null. /// @@ -1178,6 +1188,33 @@ namespace Ryujinx.Graphics.Gpu.Image _cache.RemoveDeferred(texture); } + /// + /// Adds a texture to the short duration cache. This typically keeps it alive for two ticks. + /// + /// Texture to add to the short cache + /// Last used texture descriptor + public void AddShortCache(Texture texture, ref TextureDescriptor descriptor) + { + _cache.AddShortCache(texture, ref descriptor); + } + + /// + /// Removes a texture from the short duration cache. + /// + /// Texture to remove from the short cache + public void RemoveShortCache(Texture texture) + { + _cache.RemoveShortCache(texture); + } + + /// + /// Ticks periodic elements of the texture cache. + /// + public void Tick() + { + _cache.ProcessShortCache(); + } + /// /// 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/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs index 52cc8ee0..3e35f8d2 100644 --- a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs +++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs @@ -1,3 +1,4 @@ +using System; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; @@ -6,7 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Image /// /// Maxwell texture descriptor, as stored on the GPU texture pool memory region. /// - struct TextureDescriptor : ITextureDescriptor + struct TextureDescriptor : ITextureDescriptor, IEquatable { #pragma warning disable CS0649 public uint Word0; @@ -249,5 +250,24 @@ namespace Ryujinx.Graphics.Gpu.Image { return Unsafe.As>(ref this).Equals(Unsafe.As>(ref other)); } + + /// + /// Check if two descriptors are equal. + /// + /// The descriptor to compare against + /// True if they are equal, false otherwise + public bool Equals(TextureDescriptor other) + { + return Equals(ref other); + } + + /// + /// Gets a hash code for this descriptor. + /// + /// The hash code for this descriptor. + public override int GetHashCode() + { + return Unsafe.As>(ref this).GetHashCode(); + } } } diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs index 4d2544e2..fc99fc99 100644 --- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs +++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs @@ -52,16 +52,21 @@ namespace Ryujinx.Graphics.Gpu.Image if (texture == null) { - TextureInfo info = GetInfo(descriptor, out int layerSize); + texture = PhysicalMemory.TextureCache.FindShortCache(descriptor); - ProcessDereferenceQueue(); - - texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize); - - // If this happens, then the texture address is invalid, we can't add it to the cache. if (texture == null) { - return ref descriptor; + TextureInfo info = GetInfo(descriptor, out int layerSize); + + ProcessDereferenceQueue(); + + texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize); + + // If this happens, then the texture address is invalid, we can't add it to the cache. + if (texture == null) + { + return ref descriptor; + } } texture.IncrementReferenceCount(this, id); @@ -208,15 +213,21 @@ namespace Ryujinx.Graphics.Gpu.Image if (texture != null) { - TextureDescriptor descriptor = PhysicalMemory.Read(address); + ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id]; + ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address); // If the descriptors are the same, the texture is the same, // we don't need to remove as it was not modified. Just continue. - if (descriptor.Equals(ref DescriptorCache[id])) + if (descriptor.Equals(ref cachedDescriptor)) { continue; } + if (texture.HasOneReference()) + { + _channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor); + } + texture.DecrementReferenceCount(this, id); Items[id] = null; diff --git a/Ryujinx.Graphics.Gpu/Window.cs b/Ryujinx.Graphics.Gpu/Window.cs index c116d946..90f8e40f 100644 --- a/Ryujinx.Graphics.Gpu/Window.cs +++ b/Ryujinx.Graphics.Gpu/Window.cs @@ -204,6 +204,8 @@ namespace Ryujinx.Graphics.Gpu Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range); + pt.Cache.Tick(); + texture.SynchronizeMemory(); ImageCrop crop = pt.Crop;