Limit texture cache based on total texture size (#4350)

* Limit texture cache based on total texture size

* Formatting
This commit is contained in:
gdkchan 2023-02-08 10:19:43 -03:00 committed by GitHub
parent 96cf242bcf
commit 26bf13a65d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 40 additions and 84 deletions

View file

@ -33,10 +33,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary> /// </summary>
class AutoDeleteCache : IEnumerable<Texture> class AutoDeleteCache : IEnumerable<Texture>
{ {
private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048; private const int MaxCapacity = 2048;
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB;
private readonly LinkedList<Texture> _textures; private readonly LinkedList<Texture> _textures;
private readonly ConcurrentQueue<Texture> _deferredRemovals; private ulong _totalSize;
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder; private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
private HashSet<ShortTextureCacheEntry> _shortCache; private HashSet<ShortTextureCacheEntry> _shortCache;
@ -49,7 +51,6 @@ namespace Ryujinx.Graphics.Gpu.Image
public AutoDeleteCache() public AutoDeleteCache()
{ {
_textures = new LinkedList<Texture>(); _textures = new LinkedList<Texture>();
_deferredRemovals = new ConcurrentQueue<Texture>();
_shortCacheBuilder = new HashSet<ShortTextureCacheEntry>(); _shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
_shortCache = new HashSet<ShortTextureCacheEntry>(); _shortCache = new HashSet<ShortTextureCacheEntry>();
@ -67,37 +68,15 @@ namespace Ryujinx.Graphics.Gpu.Image
/// <param name="texture">The texture to be added to the cache</param> /// <param name="texture">The texture to be added to the cache</param>
public void Add(Texture texture) public void Add(Texture texture)
{ {
texture.IncrementReferenceCount(); _totalSize += texture.Size;
texture.IncrementReferenceCount();
texture.CacheNode = _textures.AddLast(texture); texture.CacheNode = _textures.AddLast(texture);
if (_textures.Count > MaxCapacity) if (_textures.Count > MaxCapacity ||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
{ {
Texture oldestTexture = _textures.First.Value; RemoveLeastUsedTexture();
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;
}
if (_deferredRemovals.Count > 0)
{
while (_deferredRemovals.TryDequeue(out Texture textureToRemove))
{
Remove(textureToRemove, false);
}
} }
} }
@ -120,6 +99,11 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.CacheNode = _textures.AddLast(texture); texture.CacheNode = _textures.AddLast(texture);
} }
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
{
RemoveLeastUsedTexture();
}
} }
else else
{ {
@ -127,6 +111,31 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Removes the least used texture from the cache.
/// </summary>
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;
}
/// <summary> /// <summary>
/// Removes a texture from the cache. /// Removes a texture from the cache.
/// </summary> /// </summary>
@ -148,20 +157,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures.Remove(texture.CacheNode); _textures.Remove(texture.CacheNode);
_totalSize -= texture.Size;
texture.CacheNode = null; texture.CacheNode = null;
return texture.DecrementReferenceCount(); return texture.DecrementReferenceCount();
} }
/// <summary>
/// Queues removal of a texture from the cache in a thread safe way.
/// </summary>
/// <param name="texture">The texture to be removed from the cache</param>
public void RemoveDeferred(Texture texture)
{
_deferredRemovals.Enqueue(texture);
}
/// <summary> /// <summary>
/// Attempt to find a texture on the short duration cache. /// Attempt to find a texture on the short duration cache.
/// </summary> /// </summary>

View file

@ -1637,13 +1637,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
RemoveFromPools(true); 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);
}
} }
/// <summary> /// <summary>

View file

@ -1079,19 +1079,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Queues the removal of a texture from the auto delete cache.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <param name="texture">The texture to be removed</param>
public void QueueAutoDeleteCacheRemoval(Texture texture)
{
_cache.RemoveDeferred(texture);
}
/// <summary> /// <summary>
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks. /// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
/// </summary> /// </summary>

View file

@ -434,32 +434,6 @@ namespace Ryujinx.Graphics.Gpu.Image
} }
} }
/// <summary>
/// Checks if a texture was modified by the GPU.
/// </summary>
/// <param name="texture">The texture to be checked</param>
/// <returns>True if any region of the texture was modified by the GPU, false otherwise</returns>
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;
}
/// <summary> /// <summary>
/// Flush modified ranges for a given texture. /// Flush modified ranges for a given texture.
/// </summary> /// </summary>