aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorriperiperi <rhy3756547@hotmail.com>2023-04-11 08:23:41 +0100
committerGitHub <noreply@github.com>2023-04-11 09:23:41 +0200
commita64fee29dc6b8e523d61abb7e79ceaa95a558c6c (patch)
tree0caad4bd4df53db26532ba49c8e0835966fe3e0f
parent9ef94c8292beda825fa76e05ad2e561c6d571c95 (diff)
Vulkan: add situational "Fast Flush" mode (#4667)1.1.706
* Flush in the middle of long command buffers. * Vulkan: add situational "Fast Flush" mode The AutoFlushCounter class was added to periodically flush Vulkan command buffers throughout a frame, which reduces latency to the GPU as commands are submitted and processed much sooner. This was done by allowing command buffers to flush when framebuffer attachments changed. However, some games have incredibly long render passes with a large number of draws, and really aggressive data access that forces GPU sync. The Vulkan backend could potentially end up building a single command buffer for 4-5ms if a pass has enough draws, such as in BOTW. In the scenario where sync is waited on immediately after submission, this would have to wait for the completion of a much longer command buffer than usual. The solution is to force command buffer submission periodically in a "fast flush" mode. This will end up splitting render passes, but it will only enable if sync is aggressive enough. This should improve performance in GPU limited scenarios, or in games that aggressively wait on synchronization. In some games, it may only kick in when res scaling. It won't trigger in games like SMO where sync is not an issue. Improves performance in Pokemon Scarlet/Violet (res scaled) and BOTW (in general). * Add conversions in milliseconds next to flush timers.
-rw-r--r--Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs71
-rw-r--r--Ryujinx.Graphics.Vulkan/PipelineBase.cs7
-rw-r--r--Ryujinx.Graphics.Vulkan/SyncManager.cs14
-rw-r--r--Ryujinx.Graphics.Vulkan/VulkanRenderer.cs14
4 files changed, 96 insertions, 10 deletions
diff --git a/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs b/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs
index 953316a6..4e2a9d6b 100644
--- a/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs
+++ b/Ryujinx.Graphics.Vulkan/AutoFlushCounter.cs
@@ -1,4 +1,5 @@
-using System;
+using Ryujinx.Common.Logging;
+using System;
using System.Diagnostics;
using System.Linq;
@@ -7,12 +8,26 @@ namespace Ryujinx.Graphics.Vulkan
internal class AutoFlushCounter
{
// How often to flush on framebuffer change.
- private readonly static long FramebufferFlushTimer = Stopwatch.Frequency / 1000;
+ private readonly static long FramebufferFlushTimer = Stopwatch.Frequency / 1000; // (1ms)
+
+ // How often to flush on draw when fast flush mode is enabled.
+ private readonly static long DrawFlushTimer = Stopwatch.Frequency / 666; // (1.5ms)
+
+ // Average wait time that triggers fast flush mode to be entered.
+ private readonly static long FastFlushEnterThreshold = Stopwatch.Frequency / 666; // (1.5ms)
+
+ // Average wait time that triggers fast flush mode to be exited.
+ private readonly static long FastFlushExitThreshold = Stopwatch.Frequency / 10000; // (0.1ms)
+
+ // Number of frames to average waiting times over.
+ private const int SyncWaitAverageCount = 20;
private const int MinDrawCountForFlush = 10;
private const int MinConsecutiveQueryForFlush = 10;
private const int InitialQueryCountForFlush = 32;
+ private readonly VulkanRenderer _gd;
+
private long _lastFlush;
private ulong _lastDrawCount;
private bool _hasPendingQuery;
@@ -23,6 +38,16 @@ namespace Ryujinx.Graphics.Vulkan
private int _queryCountHistoryIndex;
private int _remainingQueries;
+ private long[] _syncWaitHistory = new long[SyncWaitAverageCount];
+ private int _syncWaitHistoryIndex;
+
+ private bool _fastFlushMode;
+
+ public AutoFlushCounter(VulkanRenderer gd)
+ {
+ _gd = gd;
+ }
+
public void RegisterFlush(ulong drawCount)
{
_lastFlush = Stopwatch.GetTimestamp();
@@ -69,6 +94,32 @@ namespace Ryujinx.Graphics.Vulkan
return _hasPendingQuery;
}
+ public bool ShouldFlushDraw(ulong drawCount)
+ {
+ if (_fastFlushMode)
+ {
+ long draws = (long)(drawCount - _lastDrawCount);
+
+ if (draws < MinDrawCountForFlush)
+ {
+ if (draws == 0)
+ {
+ _lastFlush = Stopwatch.GetTimestamp();
+ }
+
+ return false;
+ }
+
+ long flushTimeout = DrawFlushTimer;
+
+ long now = Stopwatch.GetTimestamp();
+
+ return now > _lastFlush + flushTimeout;
+ }
+
+ return false;
+ }
+
public bool ShouldFlushAttachmentChange(ulong drawCount)
{
_queryCount = 0;
@@ -102,11 +153,27 @@ namespace Ryujinx.Graphics.Vulkan
public void Present()
{
+ // Query flush prediction.
+
_queryCountHistoryIndex = (_queryCountHistoryIndex + 1) % 3;
_remainingQueries = _queryCountHistory.Max() + 10;
_queryCountHistory[_queryCountHistoryIndex] = 0;
+
+ // Fast flush mode toggle.
+
+ _syncWaitHistory[_syncWaitHistoryIndex] = _gd.SyncManager.GetAndResetWaitTicks();
+
+ _syncWaitHistoryIndex = (_syncWaitHistoryIndex + 1) % SyncWaitAverageCount;
+
+ long averageWait = (long)_syncWaitHistory.Average();
+
+ if (_fastFlushMode ? averageWait < FastFlushExitThreshold : averageWait > FastFlushEnterThreshold)
+ {
+ _fastFlushMode = !_fastFlushMode;
+ Logger.Debug?.PrintMsg(LogClass.Gpu, $"Switched fast flush mode: ({_fastFlushMode})");
+ }
}
}
}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 2bec5293..c54d7980 100644
--- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -86,7 +86,7 @@ namespace Ryujinx.Graphics.Vulkan
Gd = gd;
Device = device;
- AutoFlush = new AutoFlushCounter();
+ AutoFlush = new AutoFlushCounter(gd);
var pipelineCacheCreateInfo = new PipelineCacheCreateInfo()
{
@@ -1562,6 +1562,11 @@ namespace Ryujinx.Graphics.Vulkan
private void RecreatePipelineIfNeeded(PipelineBindPoint pbp)
{
+ if (AutoFlush.ShouldFlushDraw(DrawCount))
+ {
+ Gd.FlushAllCommands();
+ }
+
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
// Commit changes to the support buffer before drawing.
diff --git a/Ryujinx.Graphics.Vulkan/SyncManager.cs b/Ryujinx.Graphics.Vulkan/SyncManager.cs
index c046dc3c..432d224f 100644
--- a/Ryujinx.Graphics.Vulkan/SyncManager.cs
+++ b/Ryujinx.Graphics.Vulkan/SyncManager.cs
@@ -1,6 +1,7 @@
using Ryujinx.Common.Logging;
using Silk.NET.Vulkan;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
namespace Ryujinx.Graphics.Vulkan
@@ -26,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device;
private List<SyncHandle> _handles;
private ulong FlushId;
+ private long WaitTicks;
public SyncManager(VulkanRenderer gd, Device device)
{
@@ -130,6 +132,8 @@ namespace Ryujinx.Graphics.Vulkan
return;
}
+ long beforeTicks = Stopwatch.GetTimestamp();
+
if (result.NeedsFlush(FlushId))
{
_gd.InterruptAction(() =>
@@ -142,12 +146,14 @@ namespace Ryujinx.Graphics.Vulkan
}
bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
+
if (!signaled)
{
Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
}
else
{
+ WaitTicks += Stopwatch.GetTimestamp() - beforeTicks;
result.Signalled = true;
}
}
@@ -188,5 +194,13 @@ namespace Ryujinx.Graphics.Vulkan
}
}
}
+
+ public long GetAndResetWaitTicks()
+ {
+ long result = WaitTicks;
+ WaitTicks = 0;
+
+ return result;
+ }
}
}
diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 193cdce3..92b453fb 100644
--- a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -49,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan
internal PipelineLayoutCache PipelineLayoutCache { get; private set; }
internal BackgroundResources BackgroundResources { get; private set; }
internal Action<Action> InterruptAction { get; private set; }
+ internal SyncManager SyncManager { get; private set; }
internal BufferManager BufferManager { get; private set; }
@@ -58,7 +59,6 @@ namespace Ryujinx.Graphics.Vulkan
private VulkanDebugMessenger _debugMessenger;
private Counters _counters;
- private SyncManager _syncManager;
private PipelineFull _pipeline;
@@ -327,7 +327,7 @@ namespace Ryujinx.Graphics.Vulkan
BufferManager = new BufferManager(this, _device);
- _syncManager = new SyncManager(this, _device);
+ SyncManager = new SyncManager(this, _device);
_pipeline = new PipelineFull(this, _device);
_pipeline.Initialize();
@@ -436,7 +436,7 @@ namespace Ryujinx.Graphics.Vulkan
internal void RegisterFlush()
{
- _syncManager.RegisterFlush();
+ SyncManager.RegisterFlush();
}
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
@@ -696,7 +696,7 @@ namespace Ryujinx.Graphics.Vulkan
public void PreFrame()
{
- _syncManager.Cleanup();
+ SyncManager.Cleanup();
}
public ICounterEvent ReportCounter(CounterType type, EventHandler<ulong> resultHandler, bool hostReserved)
@@ -736,7 +736,7 @@ namespace Ryujinx.Graphics.Vulkan
public void CreateSync(ulong id, bool strict)
{
- _syncManager.Create(id, strict);
+ SyncManager.Create(id, strict);
}
public IProgram LoadProgramBinary(byte[] programBinary, bool isFragment, ShaderInfo info)
@@ -746,12 +746,12 @@ namespace Ryujinx.Graphics.Vulkan
public void WaitSync(ulong id)
{
- _syncManager.Wait(id);
+ SyncManager.Wait(id);
}
public ulong GetCurrentSync()
{
- return _syncManager.GetCurrent();
+ return SyncManager.GetCurrent();
}
public void SetInterruptAction(Action<Action> interruptAction)