using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Vulkan { class PipelineLayoutCacheEntry { private const int MaxPoolSizesPerSet = 8; private readonly VulkanRenderer _gd; private readonly Device _device; public DescriptorSetLayout[] DescriptorSetLayouts { get; } public bool[] DescriptorSetLayoutsUpdateAfterBind { get; } public PipelineLayout PipelineLayout { get; } private readonly int[] _consumedDescriptorsPerSet; private readonly DescriptorPoolSize[][] _poolSizes; private readonly DescriptorSetManager _descriptorSetManager; private readonly List>[][] _dsCache; private List>[] _currentDsCache; private readonly int[] _dsCacheCursor; private int _dsLastCbIndex; private int _dsLastSubmissionCount; private struct ManualDescriptorSetEntry { public Auto DescriptorSet; public uint CbRefMask; public bool InUse; public ManualDescriptorSetEntry(Auto descriptorSet, int cbIndex) { DescriptorSet = descriptorSet; CbRefMask = 1u << cbIndex; InUse = true; } } private readonly struct PendingManualDsConsumption { public FenceHolder Fence { get; } public int CommandBufferIndex { get; } public int SetIndex { get; } public int CacheIndex { get; } public PendingManualDsConsumption(FenceHolder fence, int commandBufferIndex, int setIndex, int cacheIndex) { Fence = fence; CommandBufferIndex = commandBufferIndex; SetIndex = setIndex; CacheIndex = cacheIndex; fence.Get(); } } private readonly List[] _manualDsCache; private readonly Queue _pendingManualDsConsumptions; private readonly Queue[] _freeManualDsCacheEntries; private readonly Dictionary _pdTemplates; private readonly ResourceDescriptorCollection _pdDescriptors; private long _lastPdUsage; private DescriptorSetTemplate _lastPdTemplate; private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) { _gd = gd; _device = device; _dsCache = new List>[CommandBufferPool.MaxCommandBuffers][]; for (int i = 0; i < CommandBufferPool.MaxCommandBuffers; i++) { _dsCache[i] = new List>[setsCount]; for (int j = 0; j < _dsCache[i].Length; j++) { _dsCache[i][j] = new List>(); } } _dsCacheCursor = new int[setsCount]; _manualDsCache = new List[setsCount]; _pendingManualDsConsumptions = new Queue(); _freeManualDsCacheEntries = new Queue[setsCount]; } public PipelineLayoutCacheEntry( VulkanRenderer gd, Device device, ReadOnlyCollection setDescriptors, bool usePushDescriptors) : this(gd, device, setDescriptors.Count) { ResourceLayouts layouts = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); DescriptorSetLayouts = layouts.DescriptorSetLayouts; DescriptorSetLayoutsUpdateAfterBind = layouts.DescriptorSetLayoutsUpdateAfterBind; PipelineLayout = layouts.PipelineLayout; _consumedDescriptorsPerSet = new int[setDescriptors.Count]; _poolSizes = new DescriptorPoolSize[setDescriptors.Count][]; Span poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet]; for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++) { int count = 0; foreach (var descriptor in setDescriptors[setIndex].Descriptors) { count += descriptor.Count; } _consumedDescriptorsPerSet[setIndex] = count; _poolSizes[setIndex] = GetDescriptorPoolSizes(poolSizes, setDescriptors[setIndex], DescriptorSetManager.MaxSets).ToArray(); } if (usePushDescriptors) { _pdDescriptors = setDescriptors[0]; _pdTemplates = new(); } _descriptorSetManager = new DescriptorSetManager(_device, setDescriptors.Count); } public void UpdateCommandBufferIndex(int commandBufferIndex) { int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex); if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount) { _dsLastCbIndex = commandBufferIndex; _dsLastSubmissionCount = submissionCount; Array.Clear(_dsCacheCursor); } _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 = _descriptorSetManager.AllocateDescriptorSet( _gd.Api, DescriptorSetLayouts[setIndex], _poolSizes[setIndex], setIndex, _consumedDescriptorsPerSet[setIndex], DescriptorSetLayoutsUpdateAfterBind[setIndex]); list.Add(dsc); isNew = true; return dsc; } isNew = false; return list[index]; } public Auto GetNewManualDescriptorSetCollection(CommandBufferScoped cbs, int setIndex, out int cacheIndex) { FreeCompletedManualDescriptorSets(); var list = _manualDsCache[setIndex] ??= new(); var span = CollectionsMarshal.AsSpan(list); Queue freeQueue = _freeManualDsCacheEntries[setIndex]; // Do we have at least one freed descriptor set? If so, just use that. if (freeQueue != null && freeQueue.TryDequeue(out int freeIndex)) { ref ManualDescriptorSetEntry entry = ref span[freeIndex]; Debug.Assert(!entry.InUse && entry.CbRefMask == 0); entry.InUse = true; entry.CbRefMask = 1u << cbs.CommandBufferIndex; cacheIndex = freeIndex; _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, freeIndex)); return entry.DescriptorSet; } // Otherwise create a new descriptor set, and add to our pending queue for command buffer consumption tracking. var dsc = _descriptorSetManager.AllocateDescriptorSet( _gd.Api, DescriptorSetLayouts[setIndex], _poolSizes[setIndex], setIndex, _consumedDescriptorsPerSet[setIndex], DescriptorSetLayoutsUpdateAfterBind[setIndex]); cacheIndex = list.Count; list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex)); _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex)); return dsc; } public void UpdateManualDescriptorSetCollectionOwnership(CommandBufferScoped cbs, int setIndex, int cacheIndex) { FreeCompletedManualDescriptorSets(); var list = _manualDsCache[setIndex]; var span = CollectionsMarshal.AsSpan(list); ref var entry = ref span[cacheIndex]; uint cbMask = 1u << cbs.CommandBufferIndex; if ((entry.CbRefMask & cbMask) == 0) { entry.CbRefMask |= cbMask; _pendingManualDsConsumptions.Enqueue(new PendingManualDsConsumption(cbs.GetFence(), cbs.CommandBufferIndex, setIndex, cacheIndex)); } } private void FreeCompletedManualDescriptorSets() { FenceHolder signalledFence = null; while (_pendingManualDsConsumptions.TryPeek(out var pds) && (pds.Fence == signalledFence || pds.Fence.IsSignaled())) { signalledFence = pds.Fence; // Already checked - don't need to do it again. var dequeued = _pendingManualDsConsumptions.Dequeue(); Debug.Assert(dequeued.Fence == pds.Fence); pds.Fence.Put(); var span = CollectionsMarshal.AsSpan(_manualDsCache[dequeued.SetIndex]); ref var entry = ref span[dequeued.CacheIndex]; entry.CbRefMask &= ~(1u << dequeued.CommandBufferIndex); if (!entry.InUse && entry.CbRefMask == 0) { // If not in use by any array, and not bound to any command buffer, the descriptor set can be re-used immediately. (_freeManualDsCacheEntries[dequeued.SetIndex] ??= new()).Enqueue(dequeued.CacheIndex); } } } public void ReleaseManualDescriptorSetCollection(int setIndex, int cacheIndex) { var list = _manualDsCache[setIndex]; var span = CollectionsMarshal.AsSpan(list); span[cacheIndex].InUse = false; if (span[cacheIndex].CbRefMask == 0) { // 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. (_freeManualDsCacheEntries[setIndex] ??= new()).Enqueue(cacheIndex); } } private static Span GetDescriptorPoolSizes(Span output, ResourceDescriptorCollection setDescriptor, uint multiplier) { int count = 0; for (int index = 0; index < setDescriptor.Descriptors.Count; index++) { ResourceDescriptor descriptor = setDescriptor.Descriptors[index]; DescriptorType descriptorType = descriptor.Type.Convert(); bool found = false; for (int poolSizeIndex = 0; poolSizeIndex < count; poolSizeIndex++) { if (output[poolSizeIndex].Type == descriptorType) { output[poolSizeIndex].DescriptorCount += (uint)descriptor.Count * multiplier; found = true; break; } } if (!found) { output[count++] = new DescriptorPoolSize() { Type = descriptorType, DescriptorCount = (uint)descriptor.Count, }; } } return output[..count]; } public DescriptorSetTemplate GetPushDescriptorTemplate(PipelineBindPoint pbp, long updateMask) { if (_lastPdUsage == updateMask && _lastPdTemplate != null) { // Most likely result is that it asks to update the same buffers. return _lastPdTemplate; } if (!_pdTemplates.TryGetValue(updateMask, out DescriptorSetTemplate template)) { template = new DescriptorSetTemplate(_gd, _device, _pdDescriptors, updateMask, this, pbp, 0); _pdTemplates.Add(updateMask, template); } _lastPdUsage = updateMask; _lastPdTemplate = template; return template; } protected virtual unsafe void Dispose(bool disposing) { if (disposing) { if (_pdTemplates != null) { foreach (DescriptorSetTemplate template in _pdTemplates.Values) { template.Dispose(); } } for (int i = 0; i < _dsCache.Length; i++) { for (int j = 0; j < _dsCache[i].Length; j++) { for (int k = 0; k < _dsCache[i][j].Count; k++) { _dsCache[i][j][k].Dispose(); } _dsCache[i][j].Clear(); } } for (int i = 0; i < _manualDsCache.Length; i++) { if (_manualDsCache[i] == null) { continue; } for (int j = 0; j < _manualDsCache[i].Count; j++) { _manualDsCache[i][j].DescriptorSet.Dispose(); } _manualDsCache[i].Clear(); } _gd.Api.DestroyPipelineLayout(_device, PipelineLayout, null); for (int i = 0; i < DescriptorSetLayouts.Length; i++) { _gd.Api.DestroyDescriptorSetLayout(_device, DescriptorSetLayouts[i], null); } while (_pendingManualDsConsumptions.TryDequeue(out var pds)) { pds.Fence.Put(); } _descriptorSetManager.Dispose(); } } public void Dispose() { Dispose(true); } } }