using System.Collections; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu.Image { /// /// An entry on the short duration texture cache. /// class ShortTextureCacheEntry { public bool IsAutoDelete; 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; } /// /// 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; } } /// /// 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 /// old ones at the bottom of the list are deleted. /// class AutoDeleteCache : IEnumerable { private const int MinCountForDeletion = 32; private const int MaxCapacity = 2048; private const ulong MaxTextureSizeCapacity = 1024 * 1024 * 1024; // MB; private readonly LinkedList _textures; private ulong _totalSize; private HashSet _shortCacheBuilder; private HashSet _shortCache; private readonly Dictionary _shortCacheLookup; /// /// Creates a new instance of the automatic deletion cache. /// public AutoDeleteCache() { _textures = new LinkedList(); _shortCacheBuilder = new HashSet(); _shortCache = new HashSet(); _shortCacheLookup = new Dictionary(); } /// /// Adds a new texture to the cache, even if the texture added is already on the cache. /// /// /// Using this method is only recommended if you know that the texture is not yet on the cache, /// otherwise it would store the same texture more than once. /// /// The texture to be added to the cache public void Add(Texture texture) { _totalSize += texture.Size; texture.IncrementReferenceCount(); texture.CacheNode = _textures.AddLast(texture); if (_textures.Count > MaxCapacity || (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)) { RemoveLeastUsedTexture(); } } /// /// Adds a new texture to the cache, or just moves it to the top of the list if the /// texture is already on the cache. /// /// /// Moving the texture to the top of the list prevents it from being deleted, /// as the textures on the bottom of the list are deleted when new ones are added. /// /// The texture to be added, or moved to the top public void Lift(Texture texture) { if (texture.CacheNode != null) { if (texture.CacheNode != _textures.Last) { _textures.Remove(texture.CacheNode); texture.CacheNode = _textures.AddLast(texture); } if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion) { RemoveLeastUsedTexture(); } } else { Add(texture); } } /// /// Removes the least used texture from the cache. /// private void RemoveLeastUsedTexture() { Texture oldestTexture = _textures.First.Value; _totalSize -= oldestTexture.Size; if (!oldestTexture.CheckModified(false)) { // The texture must be flushed if it falls out of the auto delete cache. // Flushes out of the auto delete cache do not trigger write tracking, // as it is expected that other overlapping textures exist that have more up-to-date contents. oldestTexture.Group.SynchronizeDependents(oldestTexture); oldestTexture.FlushModified(false); } _textures.RemoveFirst(); oldestTexture.DecrementReferenceCount(); oldestTexture.CacheNode = null; } /// /// 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) { return false; } // Remove our reference to this texture. if (flush) { texture.FlushModified(false); } _textures.Remove(texture.CacheNode); _totalSize -= texture.Size; texture.CacheNode = null; return texture.DecrementReferenceCount(); } /// /// 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(); if (!texture.ShortCacheEntry.IsAutoDelete) { _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(); } /// /// 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. /// public void ProcessShortCache() { HashSet toRemove = _shortCache; foreach (var entry in toRemove) { entry.Texture.DecrementReferenceCount(); if (entry.IsAutoDelete) { Remove(entry.Texture, false); } else { _shortCacheLookup.Remove(entry.Descriptor); } entry.Texture.ShortCacheEntry = null; } toRemove.Clear(); _shortCache = _shortCacheBuilder; _shortCacheBuilder = toRemove; } public IEnumerator GetEnumerator() { return _textures.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return _textures.GetEnumerator(); } } }