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 }