Reduce the amount of descriptor pool allocations on Vulkan (#5673)

* Reduce the amount of descriptor pool allocations on Vulkan

* Formatting

* Slice can be simplified

* Make GetDescriptorPoolSizes static

* Adjust CanFit calculation so that TryAllocateDescriptorSets never fails

* Remove unused field
This commit is contained in:
gdkchan 2023-09-25 21:00:02 -03:00 committed by GitHub
parent 4a835bb2b9
commit 4744bde0e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 173 additions and 57 deletions

View file

@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public bool InUse; public bool InUse;
public bool InConsumption; public bool InConsumption;
public int SubmissionCount;
public CommandBuffer CommandBuffer; public CommandBuffer CommandBuffer;
public FenceHolder Fence; public FenceHolder Fence;
public SemaphoreHolder Semaphore; public SemaphoreHolder Semaphore;
@ -193,6 +194,11 @@ namespace Ryujinx.Graphics.Vulkan
return _commandBuffers[cbIndex].Fence; return _commandBuffers[cbIndex].Fence;
} }
public int GetSubmissionCount(int cbIndex)
{
return _commandBuffers[cbIndex].SubmissionCount;
}
private int FreeConsumed(bool wait) private int FreeConsumed(bool wait)
{ {
int freeEntry = 0; int freeEntry = 0;
@ -282,6 +288,7 @@ namespace Ryujinx.Graphics.Vulkan
Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle); Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
entry.InUse = false; entry.InUse = false;
entry.InConsumption = true; entry.InConsumption = true;
entry.SubmissionCount++;
_inUseCount--; _inUseCount--;
var commandBuffer = entry.CommandBuffer; var commandBuffer = entry.CommandBuffer;

View file

@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class DescriptorSetManager : IDisposable class DescriptorSetManager : IDisposable
{ {
private const uint DescriptorPoolMultiplier = 16; public const uint MaxSets = 16;
public class DescriptorPoolHolder : IDisposable public class DescriptorPoolHolder : IDisposable
{ {
@ -14,36 +14,28 @@ namespace Ryujinx.Graphics.Vulkan
public Device Device { get; } public Device Device { get; }
private readonly DescriptorPool _pool; private readonly DescriptorPool _pool;
private readonly uint _capacity; private int _freeDescriptors;
private int _totalSets; private int _totalSets;
private int _setsInUse; private int _setsInUse;
private bool _done; private bool _done;
public unsafe DescriptorPoolHolder(Vk api, Device device) public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
{ {
Api = api; Api = api;
Device = device; Device = device;
var poolSizes = new[] foreach (var poolSize in poolSizes)
{ {
new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier), _freeDescriptors += (int)poolSize.DescriptorCount;
new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier), }
new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier),
};
uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
_capacity = maxSets;
fixed (DescriptorPoolSize* pPoolsSize = poolSizes) fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
{ {
var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
{ {
SType = StructureType.DescriptorPoolCreateInfo, SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = maxSets, Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
MaxSets = MaxSets,
PoolSizeCount = (uint)poolSizes.Length, PoolSizeCount = (uint)poolSizes.Length,
PPoolSizes = pPoolsSize, PPoolSizes = pPoolsSize,
}; };
@ -52,18 +44,22 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts) public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
{ {
TryAllocateDescriptorSets(layouts, isTry: false, out var dsc); TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
return dsc; return dsc;
} }
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc) public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
{ {
return TryAllocateDescriptorSets(layouts, isTry: true, out dsc); return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
} }
private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, bool isTry, out DescriptorSetCollection dsc) private unsafe bool TryAllocateDescriptorSets(
ReadOnlySpan<DescriptorSetLayout> layouts,
int consumedDescriptors,
bool isTry,
out DescriptorSetCollection dsc)
{ {
Debug.Assert(!_done); Debug.Assert(!_done);
@ -84,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets); var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
if (isTry && result == Result.ErrorOutOfPoolMemory) if (isTry && result == Result.ErrorOutOfPoolMemory)
{ {
_totalSets = (int)_capacity; _totalSets = (int)MaxSets;
_done = true; _done = true;
DestroyIfDone(); DestroyIfDone();
dsc = default; dsc = default;
@ -95,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
_freeDescriptors -= consumedDescriptors;
_totalSets += layouts.Length; _totalSets += layouts.Length;
_setsInUse += layouts.Length; _setsInUse += layouts.Length;
@ -109,9 +106,15 @@ namespace Ryujinx.Graphics.Vulkan
DestroyIfDone(); DestroyIfDone();
} }
public bool CanFit(int count) public bool CanFit(int setsCount, int descriptorsCount)
{ {
if (_totalSets + count <= _capacity) // Try to determine if an allocation with the given parameters will succeed.
// An allocation may fail if the sets count or descriptors count exceeds the available counts
// of the pool.
// Not getting that right is not fatal, it will just create a new pool and try again,
// but it is less efficient.
if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
{ {
return true; return true;
} }
@ -148,46 +151,74 @@ namespace Ryujinx.Graphics.Vulkan
} }
private readonly Device _device; private readonly Device _device;
private DescriptorPoolHolder _currentPool; private readonly DescriptorPoolHolder[] _currentPools;
public DescriptorSetManager(Device device) public DescriptorSetManager(Device device, int poolCount)
{ {
_device = device; _device = device;
_currentPools = new DescriptorPoolHolder[poolCount];
} }
public Auto<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout) public Auto<DescriptorSetCollection> AllocateDescriptorSet(
Vk api,
DescriptorSetLayout layout,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{ {
Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1]; Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
layouts[0] = layout; layouts[0] = layout;
return AllocateDescriptorSets(api, layouts); return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
} }
public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> layouts) public Auto<DescriptorSetCollection> AllocateDescriptorSets(
Vk api,
ReadOnlySpan<DescriptorSetLayout> layouts,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{ {
// If we fail the first time, just create a new pool and try again. // If we fail the first time, just create a new pool and try again.
if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
{ {
dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts); pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
} }
return new Auto<DescriptorSetCollection>(dsc); return new Auto<DescriptorSetCollection>(dsc);
} }
private DescriptorPoolHolder GetPool(Vk api, int requiredCount) private DescriptorPoolHolder GetPool(
Vk api,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int setsCount,
int descriptorsCount,
bool updateAfterBind)
{ {
if (_currentPool == null || !_currentPool.CanFit(requiredCount)) ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
{ {
_currentPool = new DescriptorPoolHolder(api, _device); currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
} }
return _currentPool; return currentPool;
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {
_currentPool?.Dispose(); for (int index = 0; index < _currentPools.Length; index++)
{
_currentPools[index]?.Dispose();
_currentPools[index] = null;
}
} }
} }

View file

@ -59,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
private BitMapStruct<Array2<long>> _uniformMirrored; private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored; private BitMapStruct<Array2<long>> _storageMirrored;
private bool _updateDescriptorCacheCbIndex;
[Flags] [Flags]
private enum DirtyFlags private enum DirtyFlags
{ {
@ -218,6 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetProgram(ShaderCollection program) public void SetProgram(ShaderCollection program)
{ {
_program = program; _program = program;
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
} }
@ -490,7 +493,13 @@ namespace Ryujinx.Graphics.Vulkan
var dummyBuffer = _dummyBuffer?.GetBuffer(); var dummyBuffer = _dummyBuffer?.GetBuffer();
var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs); if (_updateDescriptorCacheCbIndex)
{
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
if (!program.HasMinimalLayout) if (!program.HasMinimalLayout)
{ {
@ -697,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalCommandBufferChange() public void SignalCommandBufferChange()
{ {
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
_uniformSet.Clear(); _uniformSet.Clear();

View file

@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@ -7,15 +8,28 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class PipelineLayoutCacheEntry class PipelineLayoutCacheEntry
{ {
// Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts.
// It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically.
private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets;
private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets;
private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
private const int MaxPoolSizesPerSet = 2;
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
public DescriptorSetLayout[] DescriptorSetLayouts { get; } public DescriptorSetLayout[] DescriptorSetLayouts { get; }
public PipelineLayout PipelineLayout { get; } public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet;
private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache; private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
private readonly int[] _dsCacheCursor; private readonly int[] _dsCacheCursor;
private int _dsLastCbIndex; private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{ {
@ -44,29 +58,55 @@ namespace Ryujinx.Graphics.Vulkan
bool usePushDescriptors) : this(gd, device, setDescriptors.Count) bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
{ {
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{
int count = 0;
foreach (var descriptor in setDescriptors[setIndex].Descriptors)
{
count += descriptor.Count;
}
_consumedDescriptorsPerSet[setIndex] = count;
}
} }
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection( public void UpdateCommandBufferIndex(int commandBufferIndex)
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
{ {
if (_dsLastCbIndex != commandBufferIndex) int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
{ {
_dsLastCbIndex = commandBufferIndex; _dsLastCbIndex = commandBufferIndex;
_dsLastSubmissionCount = submissionCount;
for (int i = 0; i < _dsCacheCursor.Length; i++) Array.Clear(_dsCacheCursor);
{
_dsCacheCursor[i] = 0;
}
} }
var list = _dsCache[commandBufferIndex][setIndex]; _currentDsCache = _dsCache[commandBufferIndex];
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
var list = _currentDsCache[setIndex];
int index = _dsCacheCursor[setIndex]++; int index = _dsCacheCursor[setIndex]++;
if (index == list.Count) if (index == list.Count)
{ {
var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]); Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex);
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api,
DescriptorSetLayouts[setIndex],
poolSizes,
setIndex,
consumedDescriptors,
false);
list.Add(dsc); list.Add(dsc);
isNew = true; isNew = true;
return dsc; return dsc;
@ -76,6 +116,33 @@ namespace Ryujinx.Graphics.Vulkan
return list[index]; return list[index];
} }
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
{
int count = 1;
switch (setIndex)
{
case PipelineBase.UniformSetIndex:
output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity);
break;
case PipelineBase.StorageSetIndex:
output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity);
break;
case PipelineBase.TextureSetIndex:
output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity);
output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity);
count = 2;
break;
case PipelineBase.ImageSetIndex:
output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity);
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
count = 2;
break;
}
return output[..count];
}
protected virtual unsafe void Dispose(bool disposing) protected virtual unsafe void Dispose(bool disposing)
{ {
if (disposing) if (disposing)

View file

@ -464,13 +464,14 @@ namespace Ryujinx.Graphics.Vulkan
return true; return true;
} }
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection( public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
{ {
return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew); _plce.UpdateCommandBufferIndex(commandBufferIndex);
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)

View file

@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device); DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();