diff --git a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index b6694bcb36..1e60c8fb44 100644 --- a/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/src/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -52,6 +52,12 @@ namespace Ryujinx.Graphics.Vulkan public readonly uint VertexBufferAlignment; public readonly uint SubTexelPrecisionBits; public readonly ulong MinResourceAlignment; + public readonly uint MaxPerStageSampledImages; + public readonly uint MaxPerStageSamplers; + public readonly uint MaxPerStageStorageBuffers; + public readonly uint MaxPerStageStorageImages; + public readonly uint MaxPerStageUniformBuffers; + public readonly uint MaxPerStageResources; public HardwareCapabilities( bool supportsIndexTypeUint8, @@ -89,7 +95,13 @@ namespace Ryujinx.Graphics.Vulkan PortabilitySubsetFlags portabilitySubset, uint vertexBufferAlignment, uint subTexelPrecisionBits, - ulong minResourceAlignment) + ulong minResourceAlignment, + uint maxPerStageSampledImages, + uint maxPerStageSamplers, + uint maxPerStageStorageBuffers, + uint maxPerStageStorageImages, + uint maxPerStageUniformBuffers, + uint maxPerStageResources) { SupportsIndexTypeUint8 = supportsIndexTypeUint8; SupportsCustomBorderColor = supportsCustomBorderColor; @@ -127,6 +139,12 @@ namespace Ryujinx.Graphics.Vulkan VertexBufferAlignment = vertexBufferAlignment; SubTexelPrecisionBits = subTexelPrecisionBits; MinResourceAlignment = minResourceAlignment; + MaxPerStageSampledImages = maxPerStageSampledImages; + MaxPerStageSamplers = maxPerStageSamplers; + MaxPerStageStorageBuffers = maxPerStageStorageBuffers; + MaxPerStageStorageImages = maxPerStageStorageImages; + MaxPerStageUniformBuffers = maxPerStageUniformBuffers; + MaxPerStageResources = maxPerStageResources; } } } diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs index fc7ddafa4f..0b299931b1 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutCacheEntry.cs @@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan private readonly Device _device; public DescriptorSetLayout[] DescriptorSetLayouts { get; } + public bool[] DescriptorSetLayoutsUpdateAfterBind { get; } public PipelineLayout PipelineLayout { get; } private readonly int[] _consumedDescriptorsPerSet; @@ -98,7 +99,11 @@ namespace Ryujinx.Graphics.Vulkan ReadOnlyCollection setDescriptors, bool usePushDescriptors) : this(gd, device, setDescriptors.Count) { - (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); + 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][]; @@ -153,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan _poolSizes[setIndex], setIndex, _consumedDescriptorsPerSet[setIndex], - false); + DescriptorSetLayoutsUpdateAfterBind[setIndex]); list.Add(dsc); isNew = true; @@ -196,7 +201,7 @@ namespace Ryujinx.Graphics.Vulkan _poolSizes[setIndex], setIndex, _consumedDescriptorsPerSet[setIndex], - false); + DescriptorSetLayoutsUpdateAfterBind[setIndex]); cacheIndex = list.Count; list.Add(new ManualDescriptorSetEntry(dsc, cbs.CommandBufferIndex)); diff --git a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs index 8bf286c654..0a6e070715 100644 --- a/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs +++ b/src/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs @@ -1,18 +1,117 @@ +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Silk.NET.Vulkan; +using System; using System.Collections.ObjectModel; namespace Ryujinx.Graphics.Vulkan { + record struct ResourceLayouts(DescriptorSetLayout[] DescriptorSetLayouts, bool[] DescriptorSetLayoutsUpdateAfterBind, PipelineLayout PipelineLayout); + static class PipelineLayoutFactory { - public static unsafe (DescriptorSetLayout[], PipelineLayout) Create( + private struct ResourceCounts + { + private Array5 _uniformBuffersCount; + private Array5 _storageBuffersCount; + private Array5 _texturesCount; + private Array5 _imagesCount; + private Array5 _samplersCount; + private Array5 _totalCount; + + private void AddToStage(in ResourceDescriptor descriptor, int stageIndex) + { + switch (descriptor.Type) + { + case ResourceType.UniformBuffer: + _uniformBuffersCount[stageIndex] += descriptor.Count; + break; + case ResourceType.StorageBuffer: + _storageBuffersCount[stageIndex] += descriptor.Count; + break; + case ResourceType.Texture: + case ResourceType.TextureAndSampler: + case ResourceType.BufferTexture: + _texturesCount[stageIndex] += descriptor.Count; + break; + case ResourceType.Image: + case ResourceType.BufferImage: + _imagesCount[stageIndex] += descriptor.Count; + break; + case ResourceType.Sampler: + _samplersCount[stageIndex] += descriptor.Count; + break; + } + + _totalCount[stageIndex] += descriptor.Count; + } + + public void Add(in ResourceDescriptor descriptor) + { + if (descriptor.Stages.HasFlag(ResourceStages.Vertex) || descriptor.Stages.HasFlag(ResourceStages.Compute)) + { + AddToStage(descriptor, 0); + } + + if (descriptor.Stages.HasFlag(ResourceStages.TessellationControl)) + { + AddToStage(descriptor, 1); + } + + if (descriptor.Stages.HasFlag(ResourceStages.TessellationEvaluation)) + { + AddToStage(descriptor, 2); + } + + if (descriptor.Stages.HasFlag(ResourceStages.Geometry)) + { + AddToStage(descriptor, 3); + } + + if (descriptor.Stages.HasFlag(ResourceStages.Fragment)) + { + AddToStage(descriptor, 4); + } + } + + private static int Sum(ReadOnlySpan values) + { + int sum = 0; + + foreach (int value in values) + { + sum += value; + } + + return sum; + } + + public bool IsExceedingAnyMaxLimit(VulkanRenderer gd) + { + int maxUniformBuffers = Sum(_uniformBuffersCount.AsSpan()); + int maxStorageBuffers = Sum(_storageBuffersCount.AsSpan()); + int maxTextures = Sum(_texturesCount.AsSpan()); + int maxImages = Sum(_imagesCount.AsSpan()); + int maxSamplers = Sum(_samplersCount.AsSpan()); + int maxTotal = Sum(_totalCount.AsSpan()); + + return (uint)maxUniformBuffers > gd.Capabilities.MaxPerStageUniformBuffers || + (uint)maxStorageBuffers > gd.Capabilities.MaxPerStageStorageBuffers || + (uint)maxTextures > gd.Capabilities.MaxPerStageSampledImages || + (uint)maxImages > gd.Capabilities.MaxPerStageStorageImages || + (uint)maxSamplers > gd.Capabilities.MaxPerStageSamplers || + (uint)maxTotal > gd.Capabilities.MaxPerStageResources; + } + } + + public static unsafe ResourceLayouts Create( VulkanRenderer gd, Device device, ReadOnlyCollection setDescriptors, bool usePushDescriptors) { DescriptorSetLayout[] layouts = new DescriptorSetLayout[setDescriptors.Count]; + bool[] updateAfterBindFlags = new bool[setDescriptors.Count]; bool isMoltenVk = gd.IsMoltenVk; @@ -20,6 +119,7 @@ namespace Ryujinx.Graphics.Vulkan { ResourceDescriptorCollection rdc = setDescriptors[setIndex]; + ResourceCounts counts = new(); ResourceStages activeStages = ResourceStages.None; if (isMoltenVk) @@ -35,7 +135,6 @@ namespace Ryujinx.Graphics.Vulkan for (int descIndex = 0; descIndex < rdc.Descriptors.Count; descIndex++) { ResourceDescriptor descriptor = rdc.Descriptors[descIndex]; - ResourceStages stages = descriptor.Stages; if (descriptor.Type == ResourceType.StorageBuffer && isMoltenVk) @@ -52,16 +151,34 @@ namespace Ryujinx.Graphics.Vulkan DescriptorCount = (uint)descriptor.Count, StageFlags = stages.Convert(), }; + + counts.Add(descriptor); } fixed (DescriptorSetLayoutBinding* pLayoutBindings = layoutBindings) { + DescriptorSetLayoutCreateFlags flags = DescriptorSetLayoutCreateFlags.None; + + if (usePushDescriptors && setIndex == 0) + { + flags = DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr; + } + + if (counts.IsExceedingAnyMaxLimit(gd)) + { + // Some vendors (like Intel) have low per-stage limits. + // We must set the flag if we exceed those limits. + flags |= DescriptorSetLayoutCreateFlags.UpdateAfterBindPoolBit; + + updateAfterBindFlags[setIndex] = true; + } + var descriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo { SType = StructureType.DescriptorSetLayoutCreateInfo, PBindings = pLayoutBindings, BindingCount = (uint)layoutBindings.Length, - Flags = usePushDescriptors && setIndex == 0 ? DescriptorSetLayoutCreateFlags.PushDescriptorBitKhr : DescriptorSetLayoutCreateFlags.None, + Flags = flags, }; gd.Api.CreateDescriptorSetLayout(device, descriptorSetLayoutCreateInfo, null, out layouts[setIndex]).ThrowOnError(); @@ -82,7 +199,7 @@ namespace Ryujinx.Graphics.Vulkan gd.Api.CreatePipelineLayout(device, &pipelineLayoutCreateInfo, null, out layout).ThrowOnError(); } - return (layouts, layout); + return new ResourceLayouts(layouts, updateAfterBindFlags, layout); } } } diff --git a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index 86a347e019..e35ff7ad0d 100644 --- a/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -402,7 +402,13 @@ namespace Ryujinx.Graphics.Vulkan portabilityFlags, vertexBufferAlignment, properties.Limits.SubTexelPrecisionBits, - minResourceAlignment); + minResourceAlignment, + properties.Limits.MaxPerStageDescriptorSampledImages, + properties.Limits.MaxPerStageDescriptorSamplers, + properties.Limits.MaxPerStageDescriptorStorageBuffers, + properties.Limits.MaxPerStageDescriptorStorageImages, + properties.Limits.MaxPerStageDescriptorUniformBuffers, + properties.Limits.MaxPerStageResources); IsSharedMemory = MemoryAllocator.IsDeviceMemoryShared(_physicalDevice);