/ src / Ryujinx.Graphics.Vulkan / SyncManager.cs
SyncManager.cs
  1  using Ryujinx.Common.Logging;
  2  using Silk.NET.Vulkan;
  3  using System.Collections.Generic;
  4  using System.Diagnostics;
  5  using System.Linq;
  6  
  7  namespace Ryujinx.Graphics.Vulkan
  8  {
  9      class SyncManager
 10      {
 11          private class SyncHandle
 12          {
 13              public ulong ID;
 14              public MultiFenceHolder Waitable;
 15              public ulong FlushId;
 16              public bool Signalled;
 17  
 18              public bool NeedsFlush(ulong currentFlushId)
 19              {
 20                  return (long)(FlushId - currentFlushId) >= 0;
 21              }
 22          }
 23  
 24          private ulong _firstHandle;
 25  
 26          private readonly VulkanRenderer _gd;
 27          private readonly Device _device;
 28          private readonly List<SyncHandle> _handles;
 29          private ulong _flushId;
 30          private long _waitTicks;
 31  
 32          public SyncManager(VulkanRenderer gd, Device device)
 33          {
 34              _gd = gd;
 35              _device = device;
 36              _handles = new List<SyncHandle>();
 37          }
 38  
 39          public void RegisterFlush()
 40          {
 41              _flushId++;
 42          }
 43  
 44          public void Create(ulong id, bool strict)
 45          {
 46              ulong flushId = _flushId;
 47              MultiFenceHolder waitable = new();
 48              if (strict || _gd.InterruptAction == null)
 49              {
 50                  _gd.FlushAllCommands();
 51                  _gd.CommandBufferPool.AddWaitable(waitable);
 52              }
 53              else
 54              {
 55                  // Don't flush commands, instead wait for the current command buffer to finish.
 56                  // If this sync is waited on before the command buffer is submitted, interrupt the gpu thread and flush it manually.
 57  
 58                  _gd.CommandBufferPool.AddInUseWaitable(waitable);
 59              }
 60  
 61              SyncHandle handle = new()
 62              {
 63                  ID = id,
 64                  Waitable = waitable,
 65                  FlushId = flushId,
 66              };
 67  
 68              lock (_handles)
 69              {
 70                  _handles.Add(handle);
 71              }
 72          }
 73  
 74          public ulong GetCurrent()
 75          {
 76              lock (_handles)
 77              {
 78                  ulong lastHandle = _firstHandle;
 79  
 80                  foreach (SyncHandle handle in _handles)
 81                  {
 82                      lock (handle)
 83                      {
 84                          if (handle.Waitable == null)
 85                          {
 86                              continue;
 87                          }
 88  
 89                          if (handle.ID > lastHandle)
 90                          {
 91                              bool signaled = handle.Signalled || handle.Waitable.WaitForFences(_gd.Api, _device, 0);
 92                              if (signaled)
 93                              {
 94                                  lastHandle = handle.ID;
 95                                  handle.Signalled = true;
 96                              }
 97                          }
 98                      }
 99                  }
100  
101                  return lastHandle;
102              }
103          }
104  
105          public void Wait(ulong id)
106          {
107              SyncHandle result = null;
108  
109              lock (_handles)
110              {
111                  if ((long)(_firstHandle - id) > 0)
112                  {
113                      return; // The handle has already been signalled or deleted.
114                  }
115  
116                  foreach (SyncHandle handle in _handles)
117                  {
118                      if (handle.ID == id)
119                      {
120                          result = handle;
121                          break;
122                      }
123                  }
124              }
125  
126              if (result != null)
127              {
128                  if (result.Waitable == null)
129                  {
130                      return;
131                  }
132  
133                  long beforeTicks = Stopwatch.GetTimestamp();
134  
135                  if (result.NeedsFlush(_flushId))
136                  {
137                      _gd.InterruptAction(() =>
138                      {
139                          if (result.NeedsFlush(_flushId))
140                          {
141                              _gd.FlushAllCommands();
142                          }
143                      });
144                  }
145  
146                  lock (result)
147                  {
148                      if (result.Waitable == null)
149                      {
150                          return;
151                      }
152  
153                      bool signaled = result.Signalled || result.Waitable.WaitForFences(_gd.Api, _device, 1000000000);
154  
155                      if (!signaled)
156                      {
157                          Logger.Error?.PrintMsg(LogClass.Gpu, $"VK Sync Object {result.ID} failed to signal within 1000ms. Continuing...");
158                      }
159                      else
160                      {
161                          _waitTicks += Stopwatch.GetTimestamp() - beforeTicks;
162                          result.Signalled = true;
163                      }
164                  }
165              }
166          }
167  
168          public void Cleanup()
169          {
170              // Iterate through handles and remove any that have already been signalled.
171  
172              while (true)
173              {
174                  SyncHandle first = null;
175                  lock (_handles)
176                  {
177                      first = _handles.FirstOrDefault();
178                  }
179  
180                  if (first == null || first.NeedsFlush(_flushId))
181                  {
182                      break;
183                  }
184  
185                  bool signaled = first.Waitable.WaitForFences(_gd.Api, _device, 0);
186                  if (signaled)
187                  {
188                      // Delete the sync object.
189                      lock (_handles)
190                      {
191                          lock (first)
192                          {
193                              _firstHandle = first.ID + 1;
194                              _handles.RemoveAt(0);
195                              first.Waitable = null;
196                          }
197                      }
198                  }
199                  else
200                  {
201                      // This sync handle and any following have not been reached yet.
202                      break;
203                  }
204              }
205          }
206  
207          public long GetAndResetWaitTicks()
208          {
209              long result = _waitTicks;
210              _waitTicks = 0;
211  
212              return result;
213          }
214      }
215  }