/ src / Ryujinx.Graphics.Vulkan / CommandBufferPool.cs
CommandBufferPool.cs
  1  using Silk.NET.Vulkan;
  2  using System;
  3  using System.Collections.Generic;
  4  using System.Diagnostics;
  5  using System.Threading;
  6  using Semaphore = Silk.NET.Vulkan.Semaphore;
  7  
  8  namespace Ryujinx.Graphics.Vulkan
  9  {
 10      class CommandBufferPool : IDisposable
 11      {
 12          public const int MaxCommandBuffers = 16;
 13  
 14          private readonly int _totalCommandBuffers;
 15          private readonly int _totalCommandBuffersMask;
 16  
 17          private readonly Vk _api;
 18          private readonly Device _device;
 19          private readonly Queue _queue;
 20          private readonly object _queueLock;
 21          private readonly bool _concurrentFenceWaitUnsupported;
 22          private readonly CommandPool _pool;
 23          private readonly Thread _owner;
 24  
 25          public bool OwnedByCurrentThread => _owner == Thread.CurrentThread;
 26  
 27          private struct ReservedCommandBuffer
 28          {
 29              public bool InUse;
 30              public bool InConsumption;
 31              public int SubmissionCount;
 32              public CommandBuffer CommandBuffer;
 33              public FenceHolder Fence;
 34  
 35              public List<IAuto> Dependants;
 36              public List<MultiFenceHolder> Waitables;
 37  
 38              public void Initialize(Vk api, Device device, CommandPool pool)
 39              {
 40                  var allocateInfo = new CommandBufferAllocateInfo
 41                  {
 42                      SType = StructureType.CommandBufferAllocateInfo,
 43                      CommandBufferCount = 1,
 44                      CommandPool = pool,
 45                      Level = CommandBufferLevel.Primary,
 46                  };
 47  
 48                  api.AllocateCommandBuffers(device, in allocateInfo, out CommandBuffer);
 49  
 50                  Dependants = new List<IAuto>();
 51                  Waitables = new List<MultiFenceHolder>();
 52              }
 53          }
 54  
 55          private readonly ReservedCommandBuffer[] _commandBuffers;
 56  
 57          private readonly int[] _queuedIndexes;
 58          private int _queuedIndexesPtr;
 59          private int _queuedCount;
 60          private int _inUseCount;
 61  
 62          public unsafe CommandBufferPool(
 63              Vk api,
 64              Device device,
 65              Queue queue,
 66              object queueLock,
 67              uint queueFamilyIndex,
 68              bool concurrentFenceWaitUnsupported,
 69              bool isLight = false)
 70          {
 71              _api = api;
 72              _device = device;
 73              _queue = queue;
 74              _queueLock = queueLock;
 75              _concurrentFenceWaitUnsupported = concurrentFenceWaitUnsupported;
 76              _owner = Thread.CurrentThread;
 77  
 78              var commandPoolCreateInfo = new CommandPoolCreateInfo
 79              {
 80                  SType = StructureType.CommandPoolCreateInfo,
 81                  QueueFamilyIndex = queueFamilyIndex,
 82                  Flags = CommandPoolCreateFlags.TransientBit |
 83                          CommandPoolCreateFlags.ResetCommandBufferBit,
 84              };
 85  
 86              api.CreateCommandPool(device, in commandPoolCreateInfo, null, out _pool).ThrowOnError();
 87  
 88              // We need at least 2 command buffers to get texture data in some cases.
 89              _totalCommandBuffers = isLight ? 2 : MaxCommandBuffers;
 90              _totalCommandBuffersMask = _totalCommandBuffers - 1;
 91  
 92              _commandBuffers = new ReservedCommandBuffer[_totalCommandBuffers];
 93  
 94              _queuedIndexes = new int[_totalCommandBuffers];
 95              _queuedIndexesPtr = 0;
 96              _queuedCount = 0;
 97  
 98              for (int i = 0; i < _totalCommandBuffers; i++)
 99              {
100                  _commandBuffers[i].Initialize(api, device, _pool);
101                  WaitAndDecrementRef(i);
102              }
103          }
104  
105          public void AddDependant(int cbIndex, IAuto dependant)
106          {
107              dependant.IncrementReferenceCount();
108              _commandBuffers[cbIndex].Dependants.Add(dependant);
109          }
110  
111          public void AddWaitable(MultiFenceHolder waitable)
112          {
113              lock (_commandBuffers)
114              {
115                  for (int i = 0; i < _totalCommandBuffers; i++)
116                  {
117                      ref var entry = ref _commandBuffers[i];
118  
119                      if (entry.InConsumption)
120                      {
121                          AddWaitable(i, waitable);
122                      }
123                  }
124              }
125          }
126  
127          public void AddInUseWaitable(MultiFenceHolder waitable)
128          {
129              lock (_commandBuffers)
130              {
131                  for (int i = 0; i < _totalCommandBuffers; i++)
132                  {
133                      ref var entry = ref _commandBuffers[i];
134  
135                      if (entry.InUse)
136                      {
137                          AddWaitable(i, waitable);
138                      }
139                  }
140              }
141          }
142  
143          public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
144          {
145              ref var entry = ref _commandBuffers[cbIndex];
146              if (waitable.AddFence(cbIndex, entry.Fence))
147              {
148                  entry.Waitables.Add(waitable);
149              }
150          }
151  
152          public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size)
153          {
154              lock (_commandBuffers)
155              {
156                  for (int i = 0; i < _totalCommandBuffers; i++)
157                  {
158                      ref var entry = ref _commandBuffers[i];
159  
160                      if (entry.InUse &&
161                          waitable.HasFence(i) &&
162                          waitable.IsBufferRangeInUse(i, offset, size))
163                      {
164                          return true;
165                      }
166                  }
167              }
168  
169              return false;
170          }
171  
172          public bool IsFenceOnRentedCommandBuffer(FenceHolder fence)
173          {
174              lock (_commandBuffers)
175              {
176                  for (int i = 0; i < _totalCommandBuffers; i++)
177                  {
178                      ref var entry = ref _commandBuffers[i];
179  
180                      if (entry.InUse && entry.Fence == fence)
181                      {
182                          return true;
183                      }
184                  }
185              }
186  
187              return false;
188          }
189  
190          public FenceHolder GetFence(int cbIndex)
191          {
192              return _commandBuffers[cbIndex].Fence;
193          }
194  
195          public int GetSubmissionCount(int cbIndex)
196          {
197              return _commandBuffers[cbIndex].SubmissionCount;
198          }
199  
200          private int FreeConsumed(bool wait)
201          {
202              int freeEntry = 0;
203  
204              while (_queuedCount > 0)
205              {
206                  int index = _queuedIndexes[_queuedIndexesPtr];
207  
208                  ref var entry = ref _commandBuffers[index];
209  
210                  if (wait || !entry.InConsumption || entry.Fence.IsSignaled())
211                  {
212                      WaitAndDecrementRef(index);
213  
214                      wait = false;
215                      freeEntry = index;
216  
217                      _queuedCount--;
218                      _queuedIndexesPtr = (_queuedIndexesPtr + 1) % _totalCommandBuffers;
219                  }
220                  else
221                  {
222                      break;
223                  }
224              }
225  
226              return freeEntry;
227          }
228  
229          public CommandBufferScoped ReturnAndRent(CommandBufferScoped cbs)
230          {
231              Return(cbs);
232              return Rent();
233          }
234  
235          public CommandBufferScoped Rent()
236          {
237              lock (_commandBuffers)
238              {
239                  int cursor = FreeConsumed(_inUseCount + _queuedCount == _totalCommandBuffers);
240  
241                  for (int i = 0; i < _totalCommandBuffers; i++)
242                  {
243                      ref var entry = ref _commandBuffers[cursor];
244  
245                      if (!entry.InUse && !entry.InConsumption)
246                      {
247                          entry.InUse = true;
248  
249                          _inUseCount++;
250  
251                          var commandBufferBeginInfo = new CommandBufferBeginInfo
252                          {
253                              SType = StructureType.CommandBufferBeginInfo,
254                          };
255  
256                          _api.BeginCommandBuffer(entry.CommandBuffer, in commandBufferBeginInfo).ThrowOnError();
257  
258                          return new CommandBufferScoped(this, entry.CommandBuffer, cursor);
259                      }
260  
261                      cursor = (cursor + 1) & _totalCommandBuffersMask;
262                  }
263              }
264  
265              throw new InvalidOperationException($"Out of command buffers (In use: {_inUseCount}, queued: {_queuedCount}, total: {_totalCommandBuffers})");
266          }
267  
268          public void Return(CommandBufferScoped cbs)
269          {
270              Return(cbs, null, null, null);
271          }
272  
273          public unsafe void Return(
274              CommandBufferScoped cbs,
275              ReadOnlySpan<Semaphore> waitSemaphores,
276              ReadOnlySpan<PipelineStageFlags> waitDstStageMask,
277              ReadOnlySpan<Semaphore> signalSemaphores)
278          {
279              lock (_commandBuffers)
280              {
281                  int cbIndex = cbs.CommandBufferIndex;
282  
283                  ref var entry = ref _commandBuffers[cbIndex];
284  
285                  Debug.Assert(entry.InUse);
286                  Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
287                  entry.InUse = false;
288                  entry.InConsumption = true;
289                  entry.SubmissionCount++;
290                  _inUseCount--;
291  
292                  var commandBuffer = entry.CommandBuffer;
293  
294                  _api.EndCommandBuffer(commandBuffer).ThrowOnError();
295  
296                  fixed (Semaphore* pWaitSemaphores = waitSemaphores, pSignalSemaphores = signalSemaphores)
297                  {
298                      fixed (PipelineStageFlags* pWaitDstStageMask = waitDstStageMask)
299                      {
300                          SubmitInfo sInfo = new()
301                          {
302                              SType = StructureType.SubmitInfo,
303                              WaitSemaphoreCount = !waitSemaphores.IsEmpty ? (uint)waitSemaphores.Length : 0,
304                              PWaitSemaphores = pWaitSemaphores,
305                              PWaitDstStageMask = pWaitDstStageMask,
306                              CommandBufferCount = 1,
307                              PCommandBuffers = &commandBuffer,
308                              SignalSemaphoreCount = !signalSemaphores.IsEmpty ? (uint)signalSemaphores.Length : 0,
309                              PSignalSemaphores = pSignalSemaphores,
310                          };
311  
312                          lock (_queueLock)
313                          {
314                              _api.QueueSubmit(_queue, 1, in sInfo, entry.Fence.GetUnsafe()).ThrowOnError();
315                          }
316                      }
317                  }
318  
319                  int ptr = (_queuedIndexesPtr + _queuedCount) % _totalCommandBuffers;
320                  _queuedIndexes[ptr] = cbIndex;
321                  _queuedCount++;
322              }
323          }
324  
325          private void WaitAndDecrementRef(int cbIndex, bool refreshFence = true)
326          {
327              ref var entry = ref _commandBuffers[cbIndex];
328  
329              if (entry.InConsumption)
330              {
331                  entry.Fence.Wait();
332                  entry.InConsumption = false;
333              }
334  
335              foreach (var dependant in entry.Dependants)
336              {
337                  dependant.DecrementReferenceCount(cbIndex);
338              }
339  
340              foreach (var waitable in entry.Waitables)
341              {
342                  waitable.RemoveFence(cbIndex);
343                  waitable.RemoveBufferUses(cbIndex);
344              }
345  
346              entry.Dependants.Clear();
347              entry.Waitables.Clear();
348              entry.Fence?.Dispose();
349  
350              if (refreshFence)
351              {
352                  entry.Fence = new FenceHolder(_api, _device, _concurrentFenceWaitUnsupported);
353              }
354              else
355              {
356                  entry.Fence = null;
357              }
358          }
359  
360          public unsafe void Dispose()
361          {
362              for (int i = 0; i < _totalCommandBuffers; i++)
363              {
364                  WaitAndDecrementRef(i, refreshFence: false);
365              }
366  
367              _api.DestroyCommandPool(_device, _pool, null);
368          }
369      }
370  }