Vulkan: Use push descriptors for uniform bindings when possible (#6154)

* Fix Push Descriptors

* Use push descriptor templates

* Use reserved bindings

* Formatting

* Disable when using MVK

("my heart will go on" starts playing as thousands of mac users shed a tear in unison)

* Introduce limit on push descriptor binding number

The bitmask used for updating push descriptors is ulong, so only 64 bindings can be tracked for now.

* Address feedback

* Fix logic for binding rejection

Should only offset limit when reserved bindings are less than the requested one.

* Workaround pascal and older nv bug

* Add GPU number detection for nvidia

* Only do workaround if it's valid to do so.
This commit is contained in:
riperiperi 2024-02-17 00:41:30 +00:00 committed by GitHub
parent e37735ed26
commit 4218311e6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 395 additions and 16 deletions

View file

@ -108,18 +108,25 @@ namespace Ryujinx.Graphics.Vulkan
_shaders = internalShaders;
bool usePushDescriptors = !isMinimal && VulkanConfiguration.UsePushDescriptors && _gd.Capabilities.SupportsPushDescriptors;
bool usePushDescriptors = !isMinimal &&
VulkanConfiguration.UsePushDescriptors &&
_gd.Capabilities.SupportsPushDescriptors &&
!IsCompute &&
CanUsePushDescriptors(gd, resourceLayout, IsCompute);
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, resourceLayout.Sets, usePushDescriptors);
ReadOnlyCollection<ResourceDescriptorCollection> sets = usePushDescriptors ?
BuildPushDescriptorSets(gd, resourceLayout.Sets) : resourceLayout.Sets;
_plce = gd.PipelineLayoutCache.GetOrCreate(gd, device, sets, usePushDescriptors);
HasMinimalLayout = isMinimal;
UsePushDescriptors = usePushDescriptors;
Stages = stages;
ClearSegments = BuildClearSegments(resourceLayout.Sets);
ClearSegments = BuildClearSegments(sets);
BindingSegments = BuildBindingSegments(resourceLayout.SetUsages);
Templates = BuildTemplates();
Templates = BuildTemplates(usePushDescriptors);
_compileTask = Task.CompletedTask;
_firstBackgroundUse = false;
@ -139,6 +146,76 @@ namespace Ryujinx.Graphics.Vulkan
_firstBackgroundUse = !fromCache;
}
private static bool CanUsePushDescriptors(VulkanRenderer gd, ResourceLayout layout, bool isCompute)
{
// If binding 3 is immediately used, use an alternate set of reserved bindings.
ReadOnlyCollection<ResourceUsage> uniformUsage = layout.SetUsages[0].Usages;
bool hasBinding3 = uniformUsage.Any(x => x.Binding == 3);
int[] reserved = isCompute ? Array.Empty<int>() : gd.GetPushDescriptorReservedBindings(hasBinding3);
// Can't use any of the reserved usages.
for (int i = 0; i < uniformUsage.Count; i++)
{
var binding = uniformUsage[i].Binding;
if (reserved.Contains(binding) ||
binding >= Constants.MaxPushDescriptorBinding ||
binding >= gd.Capabilities.MaxPushDescriptors + reserved.Count(id => id < binding))
{
return false;
}
}
return true;
}
private static ReadOnlyCollection<ResourceDescriptorCollection> BuildPushDescriptorSets(
VulkanRenderer gd,
ReadOnlyCollection<ResourceDescriptorCollection> sets)
{
// The reserved bindings were selected when determining if push descriptors could be used.
int[] reserved = gd.GetPushDescriptorReservedBindings(false);
var result = new ResourceDescriptorCollection[sets.Count];
for (int i = 0; i < sets.Count; i++)
{
if (i == 0)
{
// Push descriptors apply here. Remove reserved bindings.
ResourceDescriptorCollection original = sets[i];
var pdUniforms = new ResourceDescriptor[original.Descriptors.Count];
int j = 0;
foreach (ResourceDescriptor descriptor in original.Descriptors)
{
if (reserved.Contains(descriptor.Binding))
{
// If the binding is reserved, set its descriptor count to 0.
pdUniforms[j++] = new ResourceDescriptor(
descriptor.Binding,
0,
descriptor.Type,
descriptor.Stages);
}
else
{
pdUniforms[j++] = descriptor;
}
}
result[i] = new ResourceDescriptorCollection(new(pdUniforms));
}
else
{
result[i] = sets[i];
}
}
return new(result);
}
private static ResourceBindingSegment[][] BuildClearSegments(ReadOnlyCollection<ResourceDescriptorCollection> sets)
{
ResourceBindingSegment[][] segments = new ResourceBindingSegment[sets.Count][];
@ -243,12 +320,18 @@ namespace Ryujinx.Graphics.Vulkan
return segments;
}
private DescriptorSetTemplate[] BuildTemplates()
private DescriptorSetTemplate[] BuildTemplates(bool usePushDescriptors)
{
var templates = new DescriptorSetTemplate[BindingSegments.Length];
for (int setIndex = 0; setIndex < BindingSegments.Length; setIndex++)
{
if (usePushDescriptors && setIndex == 0)
{
// Push descriptors get updated using templates owned by the pipeline layout.
continue;
}
ResourceBindingSegment[] segments = BindingSegments[setIndex];
if (segments != null && segments.Length > 0)
@ -433,6 +516,11 @@ namespace Ryujinx.Graphics.Vulkan
return null;
}
public DescriptorSetTemplate GetPushDescriptorTemplate(long updateMask)
{
return _plce.GetPushDescriptorTemplate(IsCompute ? PipelineBindPoint.Compute : PipelineBindPoint.Graphics, updateMask);
}
public void AddComputePipeline(ref SpecData key, Auto<DisposablePipeline> pipeline)
{
(_computePipelineCache ??= new()).Add(ref key, pipeline);
@ -493,6 +581,11 @@ namespace Ryujinx.Graphics.Vulkan
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
}
public bool HasSameLayout(ShaderCollection other)
{
return other != null && _plce == other._plce;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)