From 4744bde0e59578baeb5f94126087953f6d8ddba0 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 25 Sep 2023 21:00:02 -0300 Subject: [PATCH] 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 --- .../CommandBufferPool.cs | 7 ++ .../DescriptorSetManager.cs | 103 ++++++++++++------ .../DescriptorSetUpdater.cs | 12 +- .../PipelineLayoutCacheEntry.cs | 93 +++++++++++++--- .../ShaderCollection.cs | 13 ++- src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 2 +- 6 files changed, 173 insertions(+), 57 deletions(-) diff --git a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs index 17eeef68a..4d3b8640f 100644 --- a/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs +++ b/src/Ryujinx.Graphics.Vulkan/CommandBufferPool.cs @@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan { public bool InUse; public bool InConsumption; + public int SubmissionCount; public CommandBuffer CommandBuffer; public FenceHolder Fence; public SemaphoreHolder Semaphore; @@ -193,6 +194,11 @@ namespace Ryujinx.Graphics.Vulkan return _commandBuffers[cbIndex].Fence; } + public int GetSubmissionCount(int cbIndex) + { + return _commandBuffers[cbIndex].SubmissionCount; + } + private int FreeConsumed(bool wait) { int freeEntry = 0; @@ -282,6 +288,7 @@ namespace Ryujinx.Graphics.Vulkan Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle); entry.InUse = false; entry.InConsumption = true; + entry.SubmissionCount++; _inUseCount--; var commandBuffer = entry.CommandBuffer; diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs index 2f7b604c4..7594384d6 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetManager.cs @@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan { class DescriptorSetManager : IDisposable { - private const uint DescriptorPoolMultiplier = 16; + public const uint MaxSets = 16; public class DescriptorPoolHolder : IDisposable { @@ -14,36 +14,28 @@ namespace Ryujinx.Graphics.Vulkan public Device Device { get; } private readonly DescriptorPool _pool; - private readonly uint _capacity; + private int _freeDescriptors; private int _totalSets; private int _setsInUse; private bool _done; - public unsafe DescriptorPoolHolder(Vk api, Device device) + public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan poolSizes, bool updateAfterBind) { Api = api; Device = device; - var poolSizes = new[] + foreach (var poolSize in poolSizes) { - new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier), - 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; + _freeDescriptors += (int)poolSize.DescriptorCount; + } fixed (DescriptorPoolSize* pPoolsSize = poolSizes) { var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo { SType = StructureType.DescriptorPoolCreateInfo, - MaxSets = maxSets, + Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None, + MaxSets = MaxSets, PoolSizeCount = (uint)poolSizes.Length, PPoolSizes = pPoolsSize, }; @@ -52,18 +44,22 @@ namespace Ryujinx.Graphics.Vulkan } } - public DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan layouts) + public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan layouts, int consumedDescriptors) { - TryAllocateDescriptorSets(layouts, isTry: false, out var dsc); + TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc); return dsc; } - public bool TryAllocateDescriptorSets(ReadOnlySpan layouts, out DescriptorSetCollection dsc) + public bool TryAllocateDescriptorSets(ReadOnlySpan 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 layouts, bool isTry, out DescriptorSetCollection dsc) + private unsafe bool TryAllocateDescriptorSets( + ReadOnlySpan layouts, + int consumedDescriptors, + bool isTry, + out DescriptorSetCollection dsc) { Debug.Assert(!_done); @@ -84,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets); if (isTry && result == Result.ErrorOutOfPoolMemory) { - _totalSets = (int)_capacity; + _totalSets = (int)MaxSets; _done = true; DestroyIfDone(); dsc = default; @@ -95,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan } } + _freeDescriptors -= consumedDescriptors; _totalSets += layouts.Length; _setsInUse += layouts.Length; @@ -109,9 +106,15 @@ namespace Ryujinx.Graphics.Vulkan 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; } @@ -148,46 +151,74 @@ namespace Ryujinx.Graphics.Vulkan } private readonly Device _device; - private DescriptorPoolHolder _currentPool; + private readonly DescriptorPoolHolder[] _currentPools; - public DescriptorSetManager(Device device) + public DescriptorSetManager(Device device, int poolCount) { _device = device; + _currentPools = new DescriptorPoolHolder[poolCount]; } - public Auto AllocateDescriptorSet(Vk api, DescriptorSetLayout layout) + public Auto AllocateDescriptorSet( + Vk api, + DescriptorSetLayout layout, + ReadOnlySpan poolSizes, + int poolIndex, + int consumedDescriptors, + bool updateAfterBind) { Span layouts = stackalloc DescriptorSetLayout[1]; layouts[0] = layout; - return AllocateDescriptorSets(api, layouts); + return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind); } - public Auto AllocateDescriptorSets(Vk api, ReadOnlySpan layouts) + public Auto AllocateDescriptorSets( + Vk api, + ReadOnlySpan layouts, + ReadOnlySpan poolSizes, + int poolIndex, + int consumedDescriptors, + bool updateAfterBind) { // 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(dsc); } - private DescriptorPoolHolder GetPool(Vk api, int requiredCount) + private DescriptorPoolHolder GetPool( + Vk api, + ReadOnlySpan 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) { if (disposing) { - _currentPool?.Dispose(); + for (int index = 0; index < _currentPools.Length; index++) + { + _currentPools[index]?.Dispose(); + _currentPools[index] = null; + } } } diff --git a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index 14e4c02f0..a9a92df1d 100644 --- a/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/src/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -59,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan private BitMapStruct> _uniformMirrored; private BitMapStruct> _storageMirrored; + private bool _updateDescriptorCacheCbIndex; + [Flags] private enum DirtyFlags { @@ -218,6 +220,7 @@ namespace Ryujinx.Graphics.Vulkan public void SetProgram(ShaderCollection program) { _program = program; + _updateDescriptorCacheCbIndex = true; _dirty = DirtyFlags.All; } @@ -490,7 +493,13 @@ namespace Ryujinx.Graphics.Vulkan 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) { @@ -697,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan public void SignalCommandBufferChange() { + _updateDescriptorCacheCbIndex = true; _dirty = DirtyFlags.All; _uniformSet.Clear(); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index eeb25dc0f..2840dda0f 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -7,15 +8,28 @@ namespace Ryujinx.Graphics.Vulkan { 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 Device _device; public DescriptorSetLayout[] DescriptorSetLayouts { get; } public PipelineLayout PipelineLayout { get; } + private readonly int[] _consumedDescriptorsPerSet; + private readonly List>[][] _dsCache; + private List>[] _currentDsCache; private readonly int[] _dsCacheCursor; private int _dsLastCbIndex; + private int _dsLastSubmissionCount; private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) { @@ -44,29 +58,55 @@ namespace Ryujinx.Graphics.Vulkan bool usePushDescriptors) : this(gd, device, setDescriptors.Count) { (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 GetNewDescriptorSetCollection( - VulkanRenderer gd, - int commandBufferIndex, - int setIndex, - out bool isNew) + public void UpdateCommandBufferIndex(int commandBufferIndex) { - if (_dsLastCbIndex != commandBufferIndex) + int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex); + + if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount) { _dsLastCbIndex = commandBufferIndex; - - for (int i = 0; i < _dsCacheCursor.Length; i++) - { - _dsCacheCursor[i] = 0; - } + _dsLastSubmissionCount = submissionCount; + Array.Clear(_dsCacheCursor); } - var list = _dsCache[commandBufferIndex][setIndex]; + _currentDsCache = _dsCache[commandBufferIndex]; + } + + public Auto GetNewDescriptorSetCollection(int setIndex, out bool isNew) + { + var list = _currentDsCache[setIndex]; int index = _dsCacheCursor[setIndex]++; if (index == list.Count) { - var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]); + Span 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); isNew = true; return dsc; @@ -76,6 +116,33 @@ namespace Ryujinx.Graphics.Vulkan return list[index]; } + private static Span GetDescriptorPoolSizes(Span 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) { if (disposing) diff --git a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs index 346fd9166..0cb80ac71 100644 --- a/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs +++ b/src/Ryujinx.Graphics.Vulkan/ShaderCollection.cs @@ -464,13 +464,14 @@ namespace Ryujinx.Graphics.Vulkan return true; } - public Auto GetNewDescriptorSetCollection( - VulkanRenderer gd, - int commandBufferIndex, - int setIndex, - out bool isNew) + public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex) { - return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew); + _plce.UpdateCommandBufferIndex(commandBufferIndex); + } + + public Auto GetNewDescriptorSetCollection(int setIndex, out bool isNew) + { + return _plce.GetNewDescriptorSetCollection(setIndex, out isNew); } protected virtual void Dispose(bool disposing) diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index ac598c587..a483dc599 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); - DescriptorSetManager = new DescriptorSetManager(_device); + DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts); PipelineLayoutCache = new PipelineLayoutCache();