Compare commits

...

2 Commits

Author SHA1 Message Date
gdkchan
f6d5499a16 Fix some Vulkan validation errors (#4357) 2023-02-08 14:34:22 +01:00
gdkchan
26bf13a65d Limit texture cache based on total texture size (#4350)
* Limit texture cache based on total texture size

* Formatting
2023-02-08 14:19:43 +01:00
8 changed files with 66 additions and 98 deletions

View File

@@ -33,10 +33,12 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
class AutoDeleteCache : IEnumerable<Texture>
{
private const int MinCountForDeletion = 32;
private const int MaxCapacity = 2048;
private const ulong MaxTextureSizeCapacity = 512 * 1024 * 1024; // MB;
private readonly LinkedList<Texture> _textures;
private readonly ConcurrentQueue<Texture> _deferredRemovals;
private ulong _totalSize;
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
private HashSet<ShortTextureCacheEntry> _shortCache;
@@ -49,7 +51,6 @@ namespace Ryujinx.Graphics.Gpu.Image
public AutoDeleteCache()
{
_textures = new LinkedList<Texture>();
_deferredRemovals = new ConcurrentQueue<Texture>();
_shortCacheBuilder = 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>
public void Add(Texture texture)
{
texture.IncrementReferenceCount();
_totalSize += texture.Size;
texture.IncrementReferenceCount();
texture.CacheNode = _textures.AddLast(texture);
if (_textures.Count > MaxCapacity)
if (_textures.Count > MaxCapacity ||
(_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion))
{
Texture oldestTexture = _textures.First.Value;
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);
}
RemoveLeastUsedTexture();
}
}
@@ -120,6 +99,11 @@ namespace Ryujinx.Graphics.Gpu.Image
texture.CacheNode = _textures.AddLast(texture);
}
if (_totalSize > MaxTextureSizeCapacity && _textures.Count >= MinCountForDeletion)
{
RemoveLeastUsedTexture();
}
}
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>
/// Removes a texture from the cache.
/// </summary>
@@ -148,20 +157,13 @@ namespace Ryujinx.Graphics.Gpu.Image
_textures.Remove(texture.CacheNode);
_totalSize -= texture.Size;
texture.CacheNode = null;
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>
/// Attempt to find a texture on the short duration cache.
/// </summary>

View File

@@ -1637,13 +1637,6 @@ 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);
}
}
/// <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>
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
/// </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>
/// Flush modified ranges for a given texture.
/// </summary>

View File

@@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
int attachmentCount = 0;
int colorCount = 0;
int maxColorAttachmentIndex = -1;
for (int i = 0; i < state.AttachmentEnable.Length; i++)
{
@@ -36,6 +37,7 @@ namespace Ryujinx.Graphics.Vulkan
attachmentIndices[attachmentCount++] = i;
colorCount++;
maxColorAttachmentIndex = i;
}
}
@@ -73,12 +75,11 @@ namespace Ryujinx.Graphics.Vulkan
if (colorAttachmentsCount != 0)
{
int maxAttachmentIndex = Constants.MaxRenderTargets - 1;
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
subpass.ColorAttachmentCount = (uint)maxColorAttachmentIndex + 1;
subpass.PColorAttachments = &attachmentReferences[0];
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
for (int i = 0; i <= maxAttachmentIndex; i++)
for (int i = 0; i <= maxColorAttachmentIndex; i++)
{
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
}

View File

@@ -10,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
{
private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB
private readonly List<QueryPool> _activeQueries;
private readonly List<(QueryPool, bool)> _activeQueries;
private CounterQueueEvent _activeConditionalRender;
private readonly List<BufferedQuery> _pendingQueryCopies;
@@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Vulkan
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
{
_activeQueries = new List<QueryPool>();
_activeQueries = new List<(QueryPool, bool)>();
_pendingQueryCopies = new();
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
@@ -202,7 +202,7 @@ namespace Ryujinx.Graphics.Vulkan
AutoFlush.RegisterFlush(DrawCount);
EndRenderPass();
foreach (var queryPool in _activeQueries)
foreach ((var queryPool, _) in _activeQueries)
{
Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0);
}
@@ -220,10 +220,12 @@ namespace Ryujinx.Graphics.Vulkan
// Restore per-command buffer state.
foreach (var queryPool in _activeQueries)
foreach ((var queryPool, var isOcclusion) in _activeQueries)
{
bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion;
Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1);
Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, Gd.Capabilities.SupportsPreciseOcclusionQueries ? QueryControlFlags.PreciseBit : 0);
Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0);
}
Gd.ResetCounterPool();
@@ -231,7 +233,7 @@ namespace Ryujinx.Graphics.Vulkan
Restore();
}
public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool fromSamplePool)
public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool)
{
if (needsReset)
{
@@ -247,16 +249,24 @@ namespace Ryujinx.Graphics.Vulkan
}
}
Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, Gd.Capabilities.SupportsPreciseOcclusionQueries ? QueryControlFlags.PreciseBit : 0);
bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion;
Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0);
_activeQueries.Add(pool);
_activeQueries.Add((pool, isOcclusion));
}
public void EndQuery(QueryPool pool)
{
Gd.Api.CmdEndQuery(CommandBuffer, pool, 0);
_activeQueries.Remove(pool);
for (int i = 0; i < _activeQueries.Count; i++)
{
if (_activeQueries[i].Item1.Handle == pool.Handle)
{
_activeQueries.RemoveAt(i);
break;
}
}
}
public void CopyQueryResults(BufferedQuery query)

View File

@@ -100,7 +100,8 @@ namespace Ryujinx.Graphics.Vulkan.Queries
if (_isSupported)
{
bool needsReset = resetSequence == null || _resetSequence == null || resetSequence.Value != _resetSequence.Value;
_pipeline.BeginQuery(this, _queryPool, needsReset, _type == CounterType.SamplesPassed && resetSequence != null);
bool isOcclusion = _type == CounterType.SamplesPassed;
_pipeline.BeginQuery(this, _queryPool, needsReset, isOcclusion, isOcclusion && resetSequence != null);
}
_resetSequence = null;
}

View File

@@ -90,7 +90,7 @@ namespace Ryujinx.Graphics.Vulkan
var componentMapping = new ComponentMapping(swizzleR, swizzleG, swizzleB, swizzleA);
var aspectFlags = info.Format.ConvertAspectFlags(info.DepthStencilMode);
var aspectFlagsDepth = info.Format.ConvertAspectFlags(DepthStencilMode.Depth);
var aspectFlagsDepth = info.Format.ConvertAspectFlags();
var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);