/ src / Ryujinx.Graphics.Vulkan / DescriptorSetManager.cs
DescriptorSetManager.cs
  1  using Silk.NET.Vulkan;
  2  using System;
  3  using System.Diagnostics;
  4  
  5  namespace Ryujinx.Graphics.Vulkan
  6  {
  7      class DescriptorSetManager : IDisposable
  8      {
  9          public const uint MaxSets = 8;
 10  
 11          public class DescriptorPoolHolder : IDisposable
 12          {
 13              public Vk Api { get; }
 14              public Device Device { get; }
 15  
 16              private readonly DescriptorPool _pool;
 17              private int _freeDescriptors;
 18              private int _totalSets;
 19              private int _setsInUse;
 20              private bool _done;
 21  
 22              public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
 23              {
 24                  Api = api;
 25                  Device = device;
 26  
 27                  foreach (var poolSize in poolSizes)
 28                  {
 29                      _freeDescriptors += (int)poolSize.DescriptorCount;
 30                  }
 31  
 32                  fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
 33                  {
 34                      var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
 35                      {
 36                          SType = StructureType.DescriptorPoolCreateInfo,
 37                          Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
 38                          MaxSets = MaxSets,
 39                          PoolSizeCount = (uint)poolSizes.Length,
 40                          PPoolSizes = pPoolsSize,
 41                      };
 42  
 43                      Api.CreateDescriptorPool(device, in descriptorPoolCreateInfo, null, out _pool).ThrowOnError();
 44                  }
 45              }
 46  
 47              public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
 48              {
 49                  TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
 50                  return dsc;
 51              }
 52  
 53              public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
 54              {
 55                  return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
 56              }
 57  
 58              private unsafe bool TryAllocateDescriptorSets(
 59                  ReadOnlySpan<DescriptorSetLayout> layouts,
 60                  int consumedDescriptors,
 61                  bool isTry,
 62                  out DescriptorSetCollection dsc)
 63              {
 64                  Debug.Assert(!_done);
 65  
 66                  DescriptorSet[] descriptorSets = new DescriptorSet[layouts.Length];
 67  
 68                  fixed (DescriptorSet* pDescriptorSets = descriptorSets)
 69                  {
 70                      fixed (DescriptorSetLayout* pLayouts = layouts)
 71                      {
 72                          var descriptorSetAllocateInfo = new DescriptorSetAllocateInfo
 73                          {
 74                              SType = StructureType.DescriptorSetAllocateInfo,
 75                              DescriptorPool = _pool,
 76                              DescriptorSetCount = (uint)layouts.Length,
 77                              PSetLayouts = pLayouts,
 78                          };
 79  
 80                          var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
 81                          if (isTry && result == Result.ErrorOutOfPoolMemory)
 82                          {
 83                              _totalSets = (int)MaxSets;
 84                              _done = true;
 85                              DestroyIfDone();
 86                              dsc = default;
 87                              return false;
 88                          }
 89  
 90                          result.ThrowOnError();
 91                      }
 92                  }
 93  
 94                  _freeDescriptors -= consumedDescriptors;
 95                  _totalSets += layouts.Length;
 96                  _setsInUse += layouts.Length;
 97  
 98                  dsc = new DescriptorSetCollection(this, descriptorSets);
 99                  return true;
100              }
101  
102              public void FreeDescriptorSets(DescriptorSetCollection dsc)
103              {
104                  _setsInUse -= dsc.SetsCount;
105                  Debug.Assert(_setsInUse >= 0);
106                  DestroyIfDone();
107              }
108  
109              public bool CanFit(int setsCount, int descriptorsCount)
110              {
111                  // Try to determine if an allocation with the given parameters will succeed.
112                  // An allocation may fail if the sets count or descriptors count exceeds the available counts
113                  // of the pool.
114                  // Not getting that right is not fatal, it will just create a new pool and try again,
115                  // but it is less efficient.
116  
117                  if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
118                  {
119                      return true;
120                  }
121  
122                  _done = true;
123                  DestroyIfDone();
124                  return false;
125              }
126  
127              private unsafe void DestroyIfDone()
128              {
129                  if (_done && _setsInUse == 0)
130                  {
131                      Api.DestroyDescriptorPool(Device, _pool, null);
132                  }
133              }
134  
135              protected virtual void Dispose(bool disposing)
136              {
137                  if (disposing)
138                  {
139                      unsafe
140                      {
141                          Api.DestroyDescriptorPool(Device, _pool, null);
142                      }
143                  }
144              }
145  
146              public void Dispose()
147              {
148                  GC.SuppressFinalize(this);
149                  Dispose(true);
150              }
151          }
152  
153          private readonly Device _device;
154          private readonly DescriptorPoolHolder[] _currentPools;
155  
156          public DescriptorSetManager(Device device, int poolCount)
157          {
158              _device = device;
159              _currentPools = new DescriptorPoolHolder[poolCount];
160          }
161  
162          public Auto<DescriptorSetCollection> AllocateDescriptorSet(
163              Vk api,
164              DescriptorSetLayout layout,
165              ReadOnlySpan<DescriptorPoolSize> poolSizes,
166              int poolIndex,
167              int consumedDescriptors,
168              bool updateAfterBind)
169          {
170              Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
171              layouts[0] = layout;
172              return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
173          }
174  
175          public Auto<DescriptorSetCollection> AllocateDescriptorSets(
176              Vk api,
177              ReadOnlySpan<DescriptorSetLayout> layouts,
178              ReadOnlySpan<DescriptorPoolSize> poolSizes,
179              int poolIndex,
180              int consumedDescriptors,
181              bool updateAfterBind)
182          {
183              // If we fail the first time, just create a new pool and try again.
184  
185              var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
186              if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
187              {
188                  pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
189                  dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
190              }
191  
192              return new Auto<DescriptorSetCollection>(dsc);
193          }
194  
195          private DescriptorPoolHolder GetPool(
196              Vk api,
197              ReadOnlySpan<DescriptorPoolSize> poolSizes,
198              int poolIndex,
199              int setsCount,
200              int descriptorsCount,
201              bool updateAfterBind)
202          {
203              ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
204  
205              if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
206              {
207                  currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
208              }
209  
210              return currentPool;
211          }
212  
213          protected virtual void Dispose(bool disposing)
214          {
215              if (disposing)
216              {
217                  for (int index = 0; index < _currentPools.Length; index++)
218                  {
219                      _currentPools[index]?.Dispose();
220                      _currentPools[index] = null;
221                  }
222              }
223          }
224  
225          public void Dispose()
226          {
227              GC.SuppressFinalize(this);
228              Dispose(true);
229          }
230      }
231  }