/ src / Ryujinx.Graphics.Vulkan / PipelineLayoutCacheEntry.cs
PipelineLayoutCacheEntry.cs
  1  using Ryujinx.Graphics.GAL;
  2  using Silk.NET.Vulkan;
  3  using System;
  4  using System.Collections.Generic;
  5  using System.Collections.ObjectModel;
  6  using System.Diagnostics;
  7  using System.Runtime.InteropServices;
  8  
  9  namespace Ryujinx.Graphics.Vulkan
 10  {
 11      class PipelineLayoutCacheEntry
 12      {
 13          private const int MaxPoolSizesPerSet = 8;
 14  
 15          private readonly VulkanRenderer _gd;
 16          private readonly Device _device;
 17  
 18          public DescriptorSetLayout[] DescriptorSetLayouts { get; }
 19          public bool[] DescriptorSetLayoutsUpdateAfterBind { get; }
 20          public PipelineLayout PipelineLayout { get; }
 21  
 22          private readonly int[] _consumedDescriptorsPerSet;
 23          private readonly DescriptorPoolSize[][] _poolSizes;
 24  
 25          private readonly DescriptorSetManager _descriptorSetManager;
 26  
 27          private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
 28          private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
 29          private readonly int[] _dsCacheCursor;
 30          private int _dsLastCbIndex;
 31          private int _dsLastSubmissionCount;
 32  
 33          private struct ManualDescriptorSetEntry
 34          {
 35              public Auto<DescriptorSetCollection> DescriptorSet;
 36              public uint CbRefMask;
 37              public bool InUse;
 38  
 39              public ManualDescriptorSetEntry(Auto<DescriptorSetCollection> descriptorSet, int cbIndex)
 40              {
 41                  DescriptorSet = descriptorSet;
 42                  CbRefMask = 1u << cbIndex;
 43                  InUse = true;
 44              }
 45          }
 46  
 47          private readonly struct PendingManualDsConsumption
 48          {
 49              public FenceHolder Fence { get; }
 50              public int CommandBufferIndex { get; }
 51              public int SetIndex { get; }
 52              public int CacheIndex { get; }
 53  
 54              public PendingManualDsConsumption(FenceHolder fence, int commandBufferIndex, int setIndex, int cacheIndex)
 55              {
 56                  Fence = fence;
 57                  CommandBufferIndex = commandBufferIndex;
 58                  SetIndex = setIndex;
 59                  CacheIndex = cacheIndex;
 60                  fence.Get();
 61              }
 62          }
 63  
 64          private readonly List<ManualDescriptorSetEntry>[] _manualDsCache;
 65          private readonly Queue<PendingManualDsConsumption> _pendingManualDsConsumptions;
 66          private readonly Queue<int>[] _freeManualDsCacheEntries;
 67  
 68          private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
 69          private readonly ResourceDescriptorCollection _pdDescriptors;
 70          private long _lastPdUsage;
 71          private DescriptorSetTemplate _lastPdTemplate;
 72  
 73          private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
 74          {
 75              _gd = gd;
 76              _device = device;
 77  
 78              _dsCache = new List<Auto<DescriptorSetCollection>>[CommandBufferPool.MaxCommandBuffers][];
 79  
 80              for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++)
 81              {
 82                  _dsCache[i] = new List<Auto<DescriptorSetCollection>>[setsCount];
 83  
 84                  for (int j = 0; j < _dsCache[i].Length; j++)
 85                  {
 86                      _dsCache[i][j] = new List<Auto<DescriptorSetCollection>>();
 87                  }
 88              }
 89  
 90              _dsCacheCursor = new int[setsCount];
 91              _manualDsCache = new List<ManualDescriptorSetEntry>[setsCount];
 92              _pendingManualDsConsumptions = new Queue<PendingManualDsConsumption>();
 93              _freeManualDsCacheEntries = new Queue<int>[setsCount];
 94          }
 95  
 96          public PipelineLayoutCacheEntry(
 97              VulkanRenderer gd,
 98              Device device,
 99              ReadOnlyCollection<ResourceDescriptorCollection> setDescriptors,
100              bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
101          {
102              ResourceLayouts layouts = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
103  
104              DescriptorSetLayouts = layouts.DescriptorSetLayouts;
105              DescriptorSetLayoutsUpdateAfterBind = layouts.DescriptorSetLayoutsUpdateAfterBind;
106              PipelineLayout = layouts.PipelineLayout;
107  
108              _consumedDescriptorsPerSet = new int[setDescriptors.Count];
109              _poolSizes = new DescriptorPoolSize[setDescriptors.Count][];
110  
111              Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
112  
113              for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
114              {
115                  int count = 0;
116  
117                  foreach (var descriptor in setDescriptors[setIndex].Descriptors)
118                  {
119                      count += descriptor.Count;
120                  }
121  
122                  _consumedDescriptorsPerSet[setIndex] = count;
123                  _poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray();
124              }
125  
126              if (usePushDescriptors)
127              {
128                  _pdDescriptors = setDescriptors[0];
129                  _pdTemplates = new();
130              }
131  
132              _descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count);
133          }
134  
135          public void UpdateCommandBufferIndex(int commandBufferIndex)
136          {
137              int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
138  
139              if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
140              {
141                  _dsLastCbIndex = commandBufferIndex;
142                  _dsLastSubmissionCount = submissionCount;
143                  Array.Clear(_dsCacheCursor);
144              }
145  
146              _currentDsCache = _dsCache[commandBufferIndex];
147          }
148  
149          public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
150          {
151              var list = _currentDsCache[setIndex];
152              int index = _dsCacheCursor[setIndex]++;
153              if (index == list.Count)
154              {
155                  var dsc = _descriptorSetManager.AllocateDescriptorSet(
156                      _gd.Api,
157                      DescriptorSetLayouts[setIndex],
158                      _poolSizes[setIndex],
159                      setIndex,
160                      _consumedDescriptorsPerSet[setIndex],
161                      DescriptorSetLayoutsUpdateAfterBind[setIndex]);
162  
163                  list.Add(dsc);
164                  isNew = true;
165                  return dsc;
166              }
167  
168              isNew = false;
169              return list[index];
170          }
171  
172          public Auto<DescriptorSetCollection> GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex)
173          {
174              FreeCompletedManualDescriptorSets();
175  
176              var list = _manualDsCache[setIndex] ??= new();
177              var span = CollectionsMarshal.AsSpan(list);
178  
179              Queue<int> freeQueue = _freeManualDsCacheEntries[setIndex];
180  
181              // Do we have at least one freed descriptor set? If so, just use that.
182              if (freeQueue != null && freeQueue.TryDequeue(out int freeIndex))
183              {
184                  ref ManualDescriptorSetEntry entry = ref span[freeIndex];
185  
186                  Debug.Assert(!entry.InUse && entry.CbRefMask == 0);
187  
188                  entry.InUse = true;
189                  entry.CbRefMask = 1u << cbs.CommandBufferIndex;
190                  cacheIndex = freeIndex;
191  
192                  _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, freeIndex));
193  
194                  return entry.DescriptorSet;
195              }
196  
197              // Otherwise create a new descriptor set, and add to our pending queue for command buffer consumption tracking.
198              var dsc = _descriptorSetManager.AllocateDescriptorSet(
199                  _gd.Api,
200                  DescriptorSetLayouts[setIndex],
201                  _poolSizes[setIndex],
202                  setIndex,
203                  _consumedDescriptorsPerSet[setIndex],
204                  DescriptorSetLayoutsUpdateAfterBind[setIndex]);
205  
206              cacheIndex = list.Count;
207              list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex));
208              _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
209  
210              return dsc;
211          }
212  
213          public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex)
214          {
215              FreeCompletedManualDescriptorSets();
216  
217              var list = _manualDsCache[setIndex];
218              var span = CollectionsMarshal.AsSpan(list);
219              ref var entry = ref span[cacheIndex];
220  
221              uint cbMask = 1u << cbs.CommandBufferIndex;
222  
223              if ((entry.CbRefMask & cbMask) == 0)
224              {
225                  entry.CbRefMask |= cbMask;
226  
227                  _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex));
228              }
229          }
230  
231          private void FreeCompletedManualDescriptorSets()
232          {
233              FenceHolder signalledFence = null;
234              while (_pendingManualDsConsumptions.TryPeek(out var pds) && (pds.Fence == signalledFence || pds.Fence.IsSignaled()))
235              {
236                  signalledFence = pds.Fence; // Already checked - don't need to do it again.
237                  var dequeued = _pendingManualDsConsumptions.Dequeue();
238                  Debug.Assert(dequeued.Fence == pds.Fence);
239                  pds.Fence.Put();
240  
241                  var span = CollectionsMarshal.AsSpan(_manualDsCache[dequeued.SetIndex]);
242                  ref var entry = ref span[dequeued.CacheIndex];
243                  entry.CbRefMask &= ~(1u << dequeued.CommandBufferIndex);
244  
245                  if (!entry.InUse && entry.CbRefMask == 0)
246                  {
247                      // If not in use by any array, and not bound to any command buffer, the descriptor set can be re-used immediately.
248                      (_freeManualDsCacheEntries[dequeued.SetIndex] ??= new()).Enqueue(dequeued.CacheIndex);
249                  }
250              }
251          }
252  
253          public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex)
254          {
255              var list = _manualDsCache[setIndex];
256              var span = CollectionsMarshal.AsSpan(list);
257  
258              span[cacheIndex].InUse = false;
259  
260              if (span[cacheIndex].CbRefMask == 0)
261              {
262                  // This is no longer in use by any array, so if not bound to any command buffer, the descriptor set can be re-used immediately.
263                  (_freeManualDsCacheEntries[setIndex] ??= new()).Enqueue(cacheIndex);
264              }
265          }
266  
267          private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, ResourceDescriptorCollection setDescriptor, uint multiplier)
268          {
269              int count = 0;
270  
271              for (int index = 0; index < setDescriptor.Descriptors.Count; index++)
272              {
273                  ResourceDescriptor descriptor = setDescriptor.Descriptors[index];
274                  DescriptorType descriptorType = descriptor.Type.Convert();
275  
276                  bool found = false;
277  
278                  for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++)
279                  {
280                      if (output[poolSizeIndex].Type == descriptorType)
281                      {
282                          output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier;
283                          found = true;
284                          break;
285                      }
286                  }
287  
288                  if (!found)
289                  {
290                      output[count++] = new DescriptorPoolSize()
291                      {
292                          Type = descriptorType,
293                          DescriptorCount = (uint)descriptor.Count,
294                      };
295                  }
296              }
297  
298              return output[..count];
299          }
300  
301          public DescriptorSetTemplate GetPushDescriptorTemplate(PipelineBindPoint pbp, long updateMask)
302          {
303              if (_lastPdUsage == updateMask && _lastPdTemplate != null)
304              {
305                  // Most likely result is that it asks to update the same buffers.
306                  return _lastPdTemplate;
307              }
308  
309              if (!_pdTemplates.TryGetValue(updateMask, out DescriptorSetTemplate template))
310              {
311                  template = new DescriptorSetTemplate(_gd, _device, _pdDescriptors, updateMask, this, pbp, 0);
312  
313                  _pdTemplates.Add(updateMask, template);
314              }
315  
316              _lastPdUsage = updateMask;
317              _lastPdTemplate = template;
318  
319              return template;
320          }
321  
322          protected virtual unsafe void Dispose(bool disposing)
323          {
324              if (disposing)
325              {
326                  if (_pdTemplates != null)
327                  {
328                      foreach (DescriptorSetTemplate template in _pdTemplates.Values)
329                      {
330                          template.Dispose();
331                      }
332                  }
333  
334                  for (int i = 0; i < _dsCache.Length; i++)
335                  {
336                      for (int j = 0; j < _dsCache[i].Length; j++)
337                      {
338                          for (int k = 0; k < _dsCache[i][j].Count; k++)
339                          {
340                              _dsCache[i][j][k].Dispose();
341                          }
342  
343                          _dsCache[i][j].Clear();
344                      }
345                  }
346  
347                  for (int i = 0; i < _manualDsCache.Length; i++)
348                  {
349                      if (_manualDsCache[i] == null)
350                      {
351                          continue;
352                      }
353  
354                      for (int j = 0; j < _manualDsCache[i].Count; j++)
355                      {
356                          _manualDsCache[i][j].DescriptorSet.Dispose();
357                      }
358  
359                      _manualDsCache[i].Clear();
360                  }
361  
362                  _gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null);
363  
364                  for (int i = 0; i < DescriptorSetLayouts.Length; i++)
365                  {
366                      _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null);
367                  }
368  
369                  while (_pendingManualDsConsumptions.TryDequeue(out var pds))
370                  {
371                      pds.Fence.Put();
372                  }
373  
374                  _descriptorSetManager.Dispose();
375              }
376          }
377  
378          public void Dispose()
379          {
380              Dispose(true);
381          }
382      }
383  }