Compare commits

..

2 Commits

Author SHA1 Message Date
31ed061bea Vulkan: Improve texture barrier usage, timing and batching (#6240)
* WIP barrier batch

* Add store op to image usage barrier

* Dispose the barrier batch

* Fix encoding?

* Handle read and write on the load op barrier.

Load op consumes read accesses but does not add one, as the only other operation that can read is another load.

* Simplify null check

* Insert barriers on program change in case stale bindings are reintroduced

* Not sure how I messed this one up

* Improve location of bindings barrier update

This is also important for emergency deferred clear

* Update src/Ryujinx.Graphics.Vulkan/BarrierBatch.cs

Co-authored-by: Mary Guillemard <thog@protonmail.com>

---------

Co-authored-by: Mary Guillemard <thog@protonmail.com>
2024-02-17 00:21:37 -03:00
4218311e6a 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.
2024-02-16 21:41:30 -03:00
26 changed files with 837 additions and 192 deletions

View File

@ -58,7 +58,7 @@ namespace Ryujinx.Graphics.GAL
void SetIndexBuffer(BufferRange buffer, IndexType type);
void SetImage(int binding, ITexture texture, Format imageFormat);
void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat);
void SetLineParameters(float width, bool smooth);

View File

@ -1,17 +1,20 @@
using Ryujinx.Graphics.GAL.Multithreading.Model;
using Ryujinx.Graphics.GAL.Multithreading.Resources;
using Ryujinx.Graphics.Shader;
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
{
struct SetImageCommand : IGALCommand, IGALCommand<SetImageCommand>
{
public readonly CommandType CommandType => CommandType.SetImage;
private ShaderStage _stage;
private int _binding;
private TableRef<ITexture> _texture;
private Format _imageFormat;
public void Set(int binding, TableRef<ITexture> texture, Format imageFormat)
public void Set(ShaderStage stage, int binding, TableRef<ITexture> texture, Format imageFormat)
{
_stage = stage;
_binding = binding;
_texture = texture;
_imageFormat = imageFormat;
@ -19,7 +22,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands
public static void Run(ref SetImageCommand command, ThreadedRenderer threaded, IRenderer renderer)
{
renderer.Pipeline.SetImage(command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
renderer.Pipeline.SetImage(command._stage, command._binding, command._texture.GetAs<ThreadedTexture>(threaded)?.Base, command._imageFormat);
}
}
}

View File

@ -177,9 +177,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading
_renderer.QueueCommand();
}
public void SetImage(int binding, ITexture texture, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
{
_renderer.New<SetImageCommand>().Set(binding, Ref(texture), imageFormat);
_renderer.New<SetImageCommand>().Set(stage, binding, Ref(texture), imageFormat);
_renderer.QueueCommand();
}

View File

@ -634,7 +634,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.Texture = hostTextureRebind;
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTextureRebind, format);
}
continue;
@ -692,7 +692,7 @@ namespace Ryujinx.Graphics.Gpu.Image
state.ImageFormat = format;
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTexture, format);
_context.Renderer.Pipeline.SetImage(stage, bindingInfo.Binding, hostTexture, format);
}
state.CachedTexture = texture;

View File

@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (binding.IsImage)
{
_context.Renderer.Pipeline.SetImage(binding.BindingInfo.Binding, binding.Texture, binding.Format);
_context.Renderer.Pipeline.SetImage(binding.Stage, binding.BindingInfo.Binding, binding.Texture, binding.Format);
}
else
{

View File

@ -935,7 +935,7 @@ namespace Ryujinx.Graphics.OpenGL
SetFrontFace(_frontFace = frontFace.Convert());
}
public void SetImage(int binding, ITexture texture, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture texture, Format imageFormat)
{
if ((uint)binding < SavedImages)
{

View File

@ -0,0 +1,225 @@
using Silk.NET.Vulkan;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Vulkan
{
internal class BarrierBatch : IDisposable
{
private const int MaxBarriersPerCall = 16;
private readonly VulkanRenderer _gd;
private readonly NativeArray<MemoryBarrier> _memoryBarrierBatch = new(MaxBarriersPerCall);
private readonly NativeArray<BufferMemoryBarrier> _bufferBarrierBatch = new(MaxBarriersPerCall);
private readonly NativeArray<ImageMemoryBarrier> _imageBarrierBatch = new(MaxBarriersPerCall);
private readonly List<BarrierWithStageFlags<MemoryBarrier>> _memoryBarriers = new();
private readonly List<BarrierWithStageFlags<BufferMemoryBarrier>> _bufferBarriers = new();
private readonly List<BarrierWithStageFlags<ImageMemoryBarrier>> _imageBarriers = new();
private int _queuedBarrierCount;
public BarrierBatch(VulkanRenderer gd)
{
_gd = gd;
}
private readonly record struct StageFlags : IEquatable<StageFlags>
{
public readonly PipelineStageFlags Source;
public readonly PipelineStageFlags Dest;
public StageFlags(PipelineStageFlags source, PipelineStageFlags dest)
{
Source = source;
Dest = dest;
}
}
private readonly struct BarrierWithStageFlags<T> where T : unmanaged
{
public readonly StageFlags Flags;
public readonly T Barrier;
public BarrierWithStageFlags(StageFlags flags, T barrier)
{
Flags = flags;
Barrier = barrier;
}
public BarrierWithStageFlags(PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags, T barrier)
{
Flags = new StageFlags(srcStageFlags, dstStageFlags);
Barrier = barrier;
}
}
private void QueueBarrier<T>(List<BarrierWithStageFlags<T>> list, T barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags) where T : unmanaged
{
list.Add(new BarrierWithStageFlags<T>(srcStageFlags, dstStageFlags, barrier));
_queuedBarrierCount++;
}
public void QueueBarrier(MemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
{
QueueBarrier(_memoryBarriers, barrier, srcStageFlags, dstStageFlags);
}
public void QueueBarrier(BufferMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
{
QueueBarrier(_bufferBarriers, barrier, srcStageFlags, dstStageFlags);
}
public void QueueBarrier(ImageMemoryBarrier barrier, PipelineStageFlags srcStageFlags, PipelineStageFlags dstStageFlags)
{
QueueBarrier(_imageBarriers, barrier, srcStageFlags, dstStageFlags);
}
public unsafe void Flush(CommandBuffer cb, bool insideRenderPass, Action endRenderPass)
{
while (_queuedBarrierCount > 0)
{
int memoryCount = 0;
int bufferCount = 0;
int imageCount = 0;
bool hasBarrier = false;
StageFlags flags = default;
static void AddBarriers<T>(
Span<T> target,
ref int queuedBarrierCount,
ref bool hasBarrier,
ref StageFlags flags,
ref int count,
List<BarrierWithStageFlags<T>> list) where T : unmanaged
{
int firstMatch = -1;
for (int i = 0; i < list.Count; i++)
{
BarrierWithStageFlags<T> barrier = list[i];
if (!hasBarrier)
{
flags = barrier.Flags;
hasBarrier = true;
target[count++] = barrier.Barrier;
queuedBarrierCount--;
firstMatch = i;
if (count >= target.Length)
{
break;
}
}
else
{
if (flags.Equals(barrier.Flags))
{
target[count++] = barrier.Barrier;
queuedBarrierCount--;
if (firstMatch == -1)
{
firstMatch = i;
}
if (count >= target.Length)
{
break;
}
}
else
{
// Delete consumed barriers from the first match to the current non-match.
if (firstMatch != -1)
{
int deleteCount = i - firstMatch;
list.RemoveRange(firstMatch, deleteCount);
i -= deleteCount;
firstMatch = -1;
}
}
}
}
if (firstMatch == 0)
{
list.Clear();
}
else if (firstMatch != -1)
{
int deleteCount = list.Count - firstMatch;
list.RemoveRange(firstMatch, deleteCount);
}
}
if (insideRenderPass)
{
// Image barriers queued in the batch are meant to be globally scoped,
// but inside a render pass they're scoped to just the range of the render pass.
// On MoltenVK, we just break the rules and always use image barrier.
// On desktop GPUs, all barriers are globally scoped, so we just replace it with a generic memory barrier.
// TODO: On certain GPUs, we need to split render pass so the barrier scope is global. When this is done,
// notify the resource that it should add a barrier as soon as a render pass ends to avoid this in future.
if (!_gd.IsMoltenVk)
{
foreach (var barrier in _imageBarriers)
{
_memoryBarriers.Add(new BarrierWithStageFlags<MemoryBarrier>(
barrier.Flags,
new MemoryBarrier()
{
SType = StructureType.MemoryBarrier,
SrcAccessMask = barrier.Barrier.SrcAccessMask,
DstAccessMask = barrier.Barrier.DstAccessMask
}));
}
_imageBarriers.Clear();
}
}
AddBarriers(_memoryBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref memoryCount, _memoryBarriers);
AddBarriers(_bufferBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref bufferCount, _bufferBarriers);
AddBarriers(_imageBarrierBatch.AsSpan(), ref _queuedBarrierCount, ref hasBarrier, ref flags, ref imageCount, _imageBarriers);
if (hasBarrier)
{
PipelineStageFlags srcStageFlags = flags.Source;
if (insideRenderPass)
{
// Inside a render pass, barrier stages can only be from rasterization.
srcStageFlags &= ~PipelineStageFlags.ComputeShaderBit;
}
_gd.Api.CmdPipelineBarrier(
cb,
srcStageFlags,
flags.Dest,
0,
(uint)memoryCount,
_memoryBarrierBatch.Pointer,
(uint)bufferCount,
_bufferBarrierBatch.Pointer,
(uint)imageCount,
_imageBarrierBatch.Pointer);
}
}
}
public void Dispose()
{
_memoryBarrierBatch.Dispose();
_bufferBarrierBatch.Dispose();
_imageBarrierBatch.Dispose();
}
}
}

View File

@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan
public const int MaxStorageBufferBindings = MaxStorageBuffersPerStage * MaxShaderStages;
public const int MaxTextureBindings = MaxTexturesPerStage * MaxShaderStages;
public const int MaxImageBindings = MaxImagesPerStage * MaxShaderStages;
public const int MaxPushDescriptorBinding = 64;
public const ulong SparseBufferAlignment = 0x10000;
}

View File

@ -1,19 +1,32 @@
using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan;
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
namespace Ryujinx.Graphics.Vulkan
{
class DescriptorSetTemplate : IDisposable
{
/// <summary>
/// Renderdoc seems to crash when doing a templated uniform update with count > 1 on a push descriptor.
/// When this is true, consecutive buffers are always updated individually.
/// </summary>
private const bool RenderdocPushCountBug = true;
private readonly VulkanRenderer _gd;
private readonly Device _device;
public readonly DescriptorUpdateTemplate Template;
public readonly int Size;
public unsafe DescriptorSetTemplate(VulkanRenderer gd, Device device, ResourceBindingSegment[] segments, PipelineLayoutCacheEntry plce, PipelineBindPoint pbp, int setIndex)
public unsafe DescriptorSetTemplate(
VulkanRenderer gd,
Device device,
ResourceBindingSegment[] segments,
PipelineLayoutCacheEntry plce,
PipelineBindPoint pbp,
int setIndex)
{
_gd = gd;
_device = device;
@ -137,6 +150,93 @@ namespace Ryujinx.Graphics.Vulkan
Template = result;
}
public unsafe DescriptorSetTemplate(
VulkanRenderer gd,
Device device,
ResourceDescriptorCollection descriptors,
long updateMask,
PipelineLayoutCacheEntry plce,
PipelineBindPoint pbp,
int setIndex)
{
_gd = gd;
_device = device;
// Create a template from the set usages. Assumes the descriptor set is updated in segment order then binding order.
int segmentCount = BitOperations.PopCount((ulong)updateMask);
DescriptorUpdateTemplateEntry* entries = stackalloc DescriptorUpdateTemplateEntry[segmentCount];
int entry = 0;
nuint structureOffset = 0;
void AddBinding(int binding, int count)
{
entries[entry++] = new DescriptorUpdateTemplateEntry()
{
DescriptorType = DescriptorType.UniformBuffer,
DstBinding = (uint)binding,
DescriptorCount = (uint)count,
Offset = structureOffset,
Stride = (nuint)Unsafe.SizeOf<DescriptorBufferInfo>()
};
structureOffset += (nuint)(Unsafe.SizeOf<DescriptorBufferInfo>() * count);
}
int startBinding = 0;
int bindingCount = 0;
foreach (ResourceDescriptor descriptor in descriptors.Descriptors)
{
for (int i = 0; i < descriptor.Count; i++)
{
int binding = descriptor.Binding + i;
if ((updateMask & (1L << binding)) != 0)
{
if (bindingCount > 0 && (RenderdocPushCountBug || startBinding + bindingCount != binding))
{
AddBinding(startBinding, bindingCount);
bindingCount = 0;
}
if (bindingCount == 0)
{
startBinding = binding;
}
bindingCount++;
}
}
}
if (bindingCount > 0)
{
AddBinding(startBinding, bindingCount);
}
Size = (int)structureOffset;
var info = new DescriptorUpdateTemplateCreateInfo()
{
SType = StructureType.DescriptorUpdateTemplateCreateInfo,
DescriptorUpdateEntryCount = (uint)entry,
PDescriptorUpdateEntries = entries,
TemplateType = DescriptorUpdateTemplateType.PushDescriptorsKhr,
DescriptorSetLayout = plce.DescriptorSetLayouts[setIndex],
PipelineBindPoint = pbp,
PipelineLayout = plce.PipelineLayout,
Set = (uint)setIndex,
};
DescriptorUpdateTemplate result;
gd.Api.CreateDescriptorUpdateTemplate(device, &info, null, &result).ThrowOnError();
Template = result;
}
public unsafe void Dispose()
{
_gd.Api.DestroyDescriptorUpdateTemplate(_device, Template, null);

View File

@ -52,11 +52,23 @@ namespace Ryujinx.Graphics.Vulkan
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, template.Size));
}
public DescriptorSetTemplateWriter Begin(int maxSize)
{
EnsureSize(maxSize);
return new DescriptorSetTemplateWriter(new Span<byte>(_data.Pointer, maxSize));
}
public void Commit(VulkanRenderer gd, Device device, DescriptorSet set)
{
gd.Api.UpdateDescriptorSetWithTemplate(device, set, _activeTemplate.Template, _data.Pointer);
}
public void CommitPushDescriptor(VulkanRenderer gd, CommandBufferScoped cbs, DescriptorSetTemplate template, PipelineLayout layout)
{
gd.PushDescriptorApi.CmdPushDescriptorSetWithTemplate(cbs.CommandBuffer, template.Template, layout, 0, _data.Pointer);
}
public void Dispose()
{
_data?.Dispose();

View File

@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader;
using Silk.NET.Vulkan;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
using Format = Ryujinx.Graphics.GAL.Format;
using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
@ -34,6 +35,36 @@ namespace Ryujinx.Graphics.Vulkan
}
}
private record struct TextureRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public Auto<DisposableSampler> Sampler;
public TextureRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view, Auto<DisposableSampler> sampler)
{
Stage = stage;
Storage = storage;
View = view;
Sampler = sampler;
}
}
private record struct ImageRef
{
public ShaderStage Stage;
public TextureStorage Storage;
public Auto<DisposableImageView> View;
public ImageRef(ShaderStage stage, TextureStorage storage, Auto<DisposableImageView> view)
{
Stage = stage;
Storage = storage;
View = view;
}
}
private readonly VulkanRenderer _gd;
private readonly Device _device;
private readonly PipelineBase _pipeline;
@ -41,9 +72,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly BufferRef[] _uniformBufferRefs;
private readonly BufferRef[] _storageBufferRefs;
private readonly Auto<DisposableImageView>[] _textureRefs;
private readonly Auto<DisposableSampler>[] _samplerRefs;
private readonly Auto<DisposableImageView>[] _imageRefs;
private readonly TextureRef[] _textureRefs;
private readonly ImageRef[] _imageRefs;
private readonly TextureBuffer[] _bufferTextureRefs;
private readonly TextureBuffer[] _bufferImageRefs;
private readonly Format[] _bufferImageFormats;
@ -61,6 +91,8 @@ namespace Ryujinx.Graphics.Vulkan
private BitMapStruct<Array2<long>> _storageSet;
private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored;
private readonly int[] _uniformSetPd;
private int _pdSequence = 1;
private bool _updateDescriptorCacheCbIndex;
@ -92,9 +124,8 @@ namespace Ryujinx.Graphics.Vulkan
_uniformBufferRefs = new BufferRef[Constants.MaxUniformBufferBindings];
_storageBufferRefs = new BufferRef[Constants.MaxStorageBufferBindings];
_textureRefs = new Auto<DisposableImageView>[Constants.MaxTextureBindings * 2];
_samplerRefs = new Auto<DisposableSampler>[Constants.MaxTextureBindings * 2];
_imageRefs = new Auto<DisposableImageView>[Constants.MaxImageBindings * 2];
_textureRefs = new TextureRef[Constants.MaxTextureBindings * 2];
_imageRefs = new ImageRef[Constants.MaxImageBindings * 2];
_bufferTextureRefs = new TextureBuffer[Constants.MaxTextureBindings * 2];
_bufferImageRefs = new TextureBuffer[Constants.MaxImageBindings * 2];
_bufferImageFormats = new Format[Constants.MaxImageBindings * 2];
@ -106,6 +137,8 @@ namespace Ryujinx.Graphics.Vulkan
_bufferTextures = new BufferView[Constants.MaxTexturesPerStage];
_bufferImages = new BufferView[Constants.MaxImagesPerStage];
_uniformSetPd = new int[Constants.MaxUniformBufferBindings];
var initialImageInfo = new DescriptorImageInfo
{
ImageLayout = ImageLayout.General,
@ -193,6 +226,7 @@ namespace Ryujinx.Graphics.Vulkan
if (BindingOverlaps(ref info, bindingOffset, offset, size))
{
_uniformSet.Clear(binding);
_uniformSetPd[binding] = 0;
SignalDirty(DirtyFlags.Uniform);
}
}
@ -223,14 +257,68 @@ namespace Ryujinx.Graphics.Vulkan
});
}
public void SetProgram(ShaderCollection program)
public void InsertBindingBarriers(CommandBufferScoped cbs)
{
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.TextureSetIndex])
{
if (segment.Type == ResourceType.TextureAndSampler)
{
for (int i = 0; i < segment.Count; i++)
{
ref var texture = ref _textureRefs[segment.Binding + i];
texture.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, texture.Stage.ConvertToPipelineStageFlags());
}
}
}
foreach (ResourceBindingSegment segment in _program.BindingSegments[PipelineBase.ImageSetIndex])
{
if (segment.Type == ResourceType.Image)
{
for (int i = 0; i < segment.Count; i++)
{
ref var image = ref _imageRefs[segment.Binding + i];
image.Storage?.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, image.Stage.ConvertToPipelineStageFlags());
}
}
}
}
public void AdvancePdSequence()
{
if (++_pdSequence == 0)
{
_pdSequence = 1;
}
}
public void SetProgram(CommandBufferScoped cbs, ShaderCollection program, bool isBound)
{
if (!program.HasSameLayout(_program))
{
// When the pipeline layout changes, push descriptor bindings are invalidated.
AdvancePdSequence();
if (_gd.IsNvidiaPreTuring && !program.UsePushDescriptors && _program?.UsePushDescriptors == true && isBound)
{
// On older nvidia GPUs, we need to clear out the active push descriptor bindings when switching
// to normal descriptors. Keeping them bound can prevent buffers from binding properly in future.
ClearAndBindUniformBufferPd(cbs);
}
}
_program = program;
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All;
}
public void SetImage(int binding, ITexture image, Format imageFormat)
public void SetImage(
CommandBufferScoped cbs,
ShaderStage stage,
int binding,
ITexture image,
Format imageFormat)
{
if (image is TextureBuffer imageBuffer)
{
@ -239,11 +327,13 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (image is TextureView view)
{
_imageRefs[binding] = view.GetView(imageFormat).GetIdentityImageView();
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_imageRefs[binding] = new(stage, view.Storage, view.GetView(imageFormat).GetIdentityImageView());
}
else
{
_imageRefs[binding] = null;
_imageRefs[binding] = default;
_bufferImageRefs[binding] = null;
_bufferImageFormats[binding] = default;
}
@ -253,7 +343,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetImage(int binding, Auto<DisposableImageView> image)
{
_imageRefs[binding] = image;
_imageRefs[binding] = new(ShaderStage.Compute, null, image);
SignalDirty(DirtyFlags.Image);
}
@ -338,15 +428,13 @@ namespace Ryujinx.Graphics.Vulkan
}
else if (texture is TextureView view)
{
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = view.GetImageView();
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
_textureRefs[binding] = new(stage, view.Storage, view.GetImageView(), ((SamplerHolder)sampler)?.GetSampler());
}
else
{
_textureRefs[binding] = null;
_samplerRefs[binding] = null;
_textureRefs[binding] = default;
_bufferTextureRefs[binding] = null;
}
@ -362,10 +450,9 @@ namespace Ryujinx.Graphics.Vulkan
{
if (texture is TextureView view)
{
view.Storage.InsertWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
view.Storage.QueueWriteToReadBarrier(cbs, AccessFlags.ShaderReadBit, stage.ConvertToPipelineStageFlags());
_textureRefs[binding] = view.GetIdentityImageView();
_samplerRefs[binding] = ((SamplerHolder)sampler)?.GetSampler();
_textureRefs[binding] = new(stage, view.Storage, view.GetIdentityImageView(), ((SamplerHolder)sampler)?.GetSampler());
SignalDirty(DirtyFlags.Texture);
}
@ -402,6 +489,7 @@ namespace Ryujinx.Graphics.Vulkan
if (!currentBufferRef.Equals(newRef) || currentInfo.Range != info.Range)
{
_uniformSet.Clear(index);
_uniformSetPd[index] = 0;
currentInfo = info;
currentBufferRef = newRef;
@ -579,9 +667,10 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
ref var texture = ref textures[i];
ref var refs = ref _textureRefs[binding + i];
texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
texture.ImageView = refs.View?.Get(cbs).Value ?? default;
texture.Sampler = refs.Sampler?.Get(cbs).Value ?? default;
if (texture.ImageView.Handle == 0)
{
@ -616,7 +705,7 @@ namespace Ryujinx.Graphics.Vulkan
for (int i = 0; i < count; i++)
{
images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
images[i].ImageView = _imageRefs[binding + i].View?.Get(cbs).Value ?? default;
}
tu.Push<DescriptorImageInfo>(images[..count]);
@ -671,15 +760,19 @@ namespace Ryujinx.Graphics.Vulkan
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
{
int sequence = _pdSequence;
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
var dummyBuffer = _dummyBuffer?.GetBuffer();
long updatedBindings = 0;
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
bool doUpdate = false;
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
for (int i = 0; i < count; i++)
{
@ -688,17 +781,58 @@ namespace Ryujinx.Graphics.Vulkan
if (_uniformSet.Set(index))
{
ref BufferRef buffer = ref _uniformBufferRefs[index];
UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
doUpdate = true;
bool mirrored = UpdateBuffer(cbs, ref _uniformBuffers[index], ref buffer, dummyBuffer, true);
_uniformMirrored.Set(index, mirrored);
}
if (_uniformSetPd[index] != sequence)
{
// Need to set this push descriptor (even if the buffer binding has not changed)
_uniformSetPd[index] = sequence;
updatedBindings |= 1L << index;
writer.Push(MemoryMarshal.CreateReadOnlySpan(ref _uniformBuffers[index], 1));
}
}
}
if (doUpdate)
if (updatedBindings > 0)
{
DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings);
_templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout);
}
}
private void ClearAndBindUniformBufferPd(CommandBufferScoped cbs)
{
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
long updatedBindings = 0;
DescriptorSetTemplateWriter writer = _templateUpdater.Begin(32 * Unsafe.SizeOf<DescriptorBufferInfo>());
foreach (ResourceBindingSegment segment in bindingSegments)
{
int binding = segment.Binding;
int count = segment.Count;
for (int i = 0; i < count; i++)
{
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
int index = binding + i;
updatedBindings |= 1L << index;
var bufferInfo = new DescriptorBufferInfo();
writer.Push(MemoryMarshal.CreateReadOnlySpan(ref bufferInfo, 1));
}
}
if (updatedBindings > 0)
{
DescriptorSetTemplate template = _program.GetPushDescriptorTemplate(updatedBindings);
_templateUpdater.CommitPushDescriptor(_gd, cbs, template, _program.PipelineLayout);
}
}
private void Initialize(CommandBufferScoped cbs, int setIndex, DescriptorSetCollection dsc)
@ -724,6 +858,7 @@ namespace Ryujinx.Graphics.Vulkan
_uniformSet.Clear();
_storageSet.Clear();
AdvancePdSequence();
}
private static void SwapBuffer(BufferRef[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)

View File

@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();

View File

@ -75,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
_pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();

View File

@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
_pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
@ -229,7 +229,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
_pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
_pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();

View File

@ -243,41 +243,6 @@ namespace Ryujinx.Graphics.Vulkan
return new Auto<DisposableFramebuffer>(new DisposableFramebuffer(api, _device, framebuffer), null, _attachments);
}
public void UpdateModifications()
{
if (_colors != null)
{
for (int index = 0; index < _colors.Length; index++)
{
_colors[index].Storage.SetModification(
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit);
}
}
_depthStencil?.Storage.SetModification(
AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.LateFragmentTestsBit);
}
public void InsertClearBarrier(CommandBufferScoped cbs, int index)
{
_colorsCanonical?[index]?.Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.ColorAttachmentWriteBit,
PipelineStageFlags.ColorAttachmentOutputBit,
insideRenderPass: true);
}
public void InsertClearBarrierDS(CommandBufferScoped cbs)
{
_depthStencil?.Storage?.InsertReadToWriteBarrier(
cbs,
AccessFlags.DepthStencilAttachmentWriteBit,
PipelineStageFlags.LateFragmentTestsBit,
insideRenderPass: true);
}
public TextureView[] GetAttachmentViews()
{
var result = new TextureView[_attachments.Length];
@ -297,23 +262,20 @@ namespace Ryujinx.Graphics.Vulkan
return new RenderPassCacheKey(_depthStencil, _colorsCanonical);
}
public void InsertLoadOpBarriers(CommandBufferScoped cbs)
public void InsertLoadOpBarriers(VulkanRenderer gd, CommandBufferScoped cbs)
{
if (_colors != null)
{
foreach (var color in _colors)
{
// If Clear or DontCare were used, this would need to be write bit.
color.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.ColorAttachmentReadBit, PipelineStageFlags.ColorAttachmentOutputBit);
color.Storage?.SetModification(AccessFlags.ColorAttachmentWriteBit, PipelineStageFlags.ColorAttachmentOutputBit);
color.Storage?.QueueLoadOpBarrier(cbs, false);
}
}
if (_depthStencil != null)
{
_depthStencil.Storage?.InsertWriteToReadBarrier(cbs, AccessFlags.DepthStencilAttachmentReadBit, PipelineStageFlags.EarlyFragmentTestsBit);
_depthStencil.Storage?.SetModification(AccessFlags.DepthStencilAttachmentWriteBit, PipelineStageFlags.LateFragmentTestsBit);
}
_depthStencil?.Storage?.QueueLoadOpBarrier(cbs, true);
gd.Barriers.Flush(cbs.CommandBuffer, false, null);
}
public (Auto<DisposableRenderPass> renderPass, Auto<DisposableFramebuffer> framebuffer) GetPassAndFramebuffer(

View File

@ -34,6 +34,7 @@ namespace Ryujinx.Graphics.Vulkan
public readonly bool SupportsMultiView;
public readonly bool SupportsNullDescriptors;
public readonly bool SupportsPushDescriptors;
public readonly uint MaxPushDescriptors;
public readonly bool SupportsPrimitiveTopologyListRestart;
public readonly bool SupportsPrimitiveTopologyPatchListRestart;
public readonly bool SupportsTransformFeedback;
@ -71,6 +72,7 @@ namespace Ryujinx.Graphics.Vulkan
bool supportsMultiView,
bool supportsNullDescriptors,
bool supportsPushDescriptors,
uint maxPushDescriptors,
bool supportsPrimitiveTopologyListRestart,
bool supportsPrimitiveTopologyPatchListRestart,
bool supportsTransformFeedback,
@ -107,6 +109,7 @@ namespace Ryujinx.Graphics.Vulkan
SupportsMultiView = supportsMultiView;
SupportsNullDescriptors = supportsNullDescriptors;
SupportsPushDescriptors = supportsPushDescriptors;
MaxPushDescriptors = maxPushDescriptors;
SupportsPrimitiveTopologyListRestart = supportsPrimitiveTopologyListRestart;
SupportsPrimitiveTopologyPatchListRestart = supportsPrimitiveTopologyPatchListRestart;
SupportsTransformFeedback = supportsTransformFeedback;

View File

@ -1039,7 +1039,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, dstLevel + l);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(0, dstView, dstFormat);
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, dstFormat);
int dispatchX = (Math.Min(srcView.Info.Width, dstView.Info.Width) + 31) / 32;
int dispatchY = (Math.Min(srcView.Info.Height, dstView.Info.Height) + 31) / 32;
@ -1168,7 +1168,7 @@ namespace Ryujinx.Graphics.Vulkan
var dstView = Create2DLayerView(dst, dstLayer + z, 0);
_pipeline.SetTextureAndSamplerIdentitySwizzle(ShaderStage.Compute, 0, srcView, null);
_pipeline.SetImage(0, dstView, format);
_pipeline.SetImage(ShaderStage.Compute, 0, dstView, format);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);

View File

@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Vulkan
private PipelineState _newState;
private bool _graphicsStateDirty;
private bool _computeStateDirty;
private bool _bindingBarriersDirty;
private PrimitiveTopology _topology;
private ulong _currentPipelineHandle;
@ -248,14 +249,14 @@ namespace Ryujinx.Graphics.Vulkan
CreateRenderPass();
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
BeginRenderPass();
var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
FramebufferParams.InsertClearBarrier(Cbs, index);
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
}
@ -286,13 +287,13 @@ namespace Ryujinx.Graphics.Vulkan
CreateRenderPass();
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
BeginRenderPass();
var attachment = new ClearAttachment(flags, 0, clearValue);
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
FramebufferParams.InsertClearBarrierDS(Cbs);
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
}
@ -887,9 +888,9 @@ namespace Ryujinx.Graphics.Vulkan
SignalStateChange();
}
public void SetImage(int binding, ITexture image, Format imageFormat)
public void SetImage(ShaderStage stage, int binding, ITexture image, Format imageFormat)
{
_descriptorSetUpdater.SetImage(binding, image, imageFormat);
_descriptorSetUpdater.SetImage(Cbs, stage, binding, image, imageFormat);
}
public void SetImage(int binding, Auto<DisposableImageView> image)
@ -976,7 +977,8 @@ namespace Ryujinx.Graphics.Vulkan
_program = internalProgram;
_descriptorSetUpdater.SetProgram(internalProgram);
_descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0);
_bindingBarriersDirty = true;
_newState.PipelineLayout = internalProgram.PipelineLayout;
_newState.StagesCount = (uint)stages.Length;
@ -1066,7 +1068,6 @@ namespace Ryujinx.Graphics.Vulkan
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
{
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
FramebufferParams?.UpdateModifications();
CreateRenderPass();
SignalStateChange();
SignalAttachmentChange();
@ -1520,8 +1521,18 @@ namespace Ryujinx.Graphics.Vulkan
CreatePipeline(PipelineBindPoint.Compute);
_computeStateDirty = false;
Pbp = PipelineBindPoint.Compute;
if (_bindingBarriersDirty)
{
// Stale barriers may have been activated by switching program. Emit any that are relevant.
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
_bindingBarriersDirty = false;
}
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
}
@ -1575,8 +1586,18 @@ namespace Ryujinx.Graphics.Vulkan
_graphicsStateDirty = false;
Pbp = PipelineBindPoint.Graphics;
if (_bindingBarriersDirty)
{
// Stale barriers may have been activated by switching program. Emit any that are relevant.
_descriptorSetUpdater.InsertBindingBarriers(Cbs);
_bindingBarriersDirty = false;
}
}
Gd.Barriers.Flush(Cbs.CommandBuffer, RenderPassActive, EndRenderPassDelegate);
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
return true;
@ -1630,6 +1651,8 @@ namespace Ryujinx.Graphics.Vulkan
{
if (!RenderPassActive)
{
FramebufferParams.InsertLoadOpBarriers(Gd, Cbs);
var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
var clearValue = new ClearValue();

View File

@ -269,6 +269,7 @@ namespace Ryujinx.Graphics.Vulkan
PreloadCbs = null;
}
Gd.Barriers.Flush(Cbs.CommandBuffer, false, null);
CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer;
Gd.RegisterFlush();

View File

@ -31,6 +31,11 @@ namespace Ryujinx.Graphics.Vulkan
private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
private readonly Dictionary<long, DescriptorSetTemplate> _pdTemplates;
private readonly ResourceDescriptorCollection _pdDescriptors;
private long _lastPdUsage;
private DescriptorSetTemplate _lastPdTemplate;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{
_gd = gd;
@ -72,6 +77,12 @@ namespace Ryujinx.Graphics.Vulkan
_consumedDescriptorsPerSet[setIndex] = count;
}
if (usePushDescriptors)
{
_pdDescriptors = setDescriptors[0];
_pdTemplates = new();
}
}
public void UpdateCommandBufferIndex(int commandBufferIndex)
@ -143,10 +154,39 @@ namespace Ryujinx.Graphics.Vulkan
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++)

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)

View File

@ -433,99 +433,65 @@ namespace Ryujinx.Graphics.Vulkan
return FormatCapabilities.IsD24S8(Info.Format) && VkFormat == VkFormat.D32SfloatS8Uint;
}
public void SetModification(AccessFlags accessFlags, PipelineStageFlags stage)
public void QueueLoadOpBarrier(CommandBufferScoped cbs, bool depthStencil)
{
_lastModificationAccess = accessFlags;
_lastModificationStage = stage;
}
PipelineStageFlags srcStageFlags = _lastReadStage | _lastModificationStage;
PipelineStageFlags dstStageFlags = depthStencil ?
PipelineStageFlags.EarlyFragmentTestsBit | PipelineStageFlags.LateFragmentTestsBit :
PipelineStageFlags.ColorAttachmentOutputBit;
public void InsertReadToWriteBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags, bool insideRenderPass)
{
var lastReadStage = _lastReadStage;
AccessFlags srcAccessFlags = _lastModificationAccess | _lastReadAccess;
AccessFlags dstAccessFlags = depthStencil ?
AccessFlags.DepthStencilAttachmentWriteBit | AccessFlags.DepthStencilAttachmentReadBit :
AccessFlags.ColorAttachmentWriteBit | AccessFlags.ColorAttachmentReadBit;
if (insideRenderPass)
if (srcAccessFlags != AccessFlags.None)
{
// We can't have barrier from compute inside a render pass,
// as it is invalid to specify compute in the subpass dependency stage mask.
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
_imageAuto.Get(cbs).Value,
srcAccessFlags,
dstAccessFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
lastReadStage &= ~PipelineStageFlags.ComputeShaderBit;
}
_gd.Barriers.QueueBarrier(barrier, srcStageFlags, dstStageFlags);
if (lastReadStage != PipelineStageFlags.None)
{
// This would result in a validation error, but is
// required on MoltenVK as the generic barrier results in
// severe texture flickering in some scenarios.
if (_gd.IsMoltenVk)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastReadAccess,
dstAccessFlags,
_lastReadStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
}
else
{
TextureView.InsertMemoryBarrier(
_gd.Api,
cbs.CommandBuffer,
_lastReadAccess,
dstAccessFlags,
lastReadStage,
dstStageFlags);
}
_lastReadAccess = AccessFlags.None;
_lastReadStage = PipelineStageFlags.None;
_lastReadAccess = AccessFlags.None;
}
_lastModificationStage = depthStencil ?
PipelineStageFlags.LateFragmentTestsBit :
PipelineStageFlags.ColorAttachmentOutputBit;
_lastModificationAccess = depthStencil ?
AccessFlags.DepthStencilAttachmentWriteBit :
AccessFlags.ColorAttachmentWriteBit;
}
public void InsertWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
public void QueueWriteToReadBarrier(CommandBufferScoped cbs, AccessFlags dstAccessFlags, PipelineStageFlags dstStageFlags)
{
_lastReadAccess |= dstAccessFlags;
_lastReadStage |= dstStageFlags;
if (_lastModificationAccess != AccessFlags.None)
{
// This would result in a validation error, but is
// required on MoltenVK as the generic barrier results in
// severe texture flickering in some scenarios.
if (_gd.IsMoltenVk)
{
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
TextureView.InsertImageBarrier(
_gd.Api,
cbs.CommandBuffer,
_imageAuto.Get(cbs).Value,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
}
else
{
TextureView.InsertMemoryBarrier(
_gd.Api,
cbs.CommandBuffer,
_lastModificationAccess,
dstAccessFlags,
_lastModificationStage,
dstStageFlags);
}
ImageAspectFlags aspectFlags = Info.Format.ConvertAspectFlags();
ImageMemoryBarrier barrier = TextureView.GetImageBarrier(
_imageAuto.Get(cbs).Value,
_lastModificationAccess,
dstAccessFlags,
aspectFlags,
0,
0,
_info.GetLayers(),
_info.Levels);
_gd.Barriers.QueueBarrier(barrier, _lastModificationStage, dstStageFlags);
_lastModificationAccess = AccessFlags.None;
}

View File

@ -497,6 +497,30 @@ namespace Ryujinx.Graphics.Vulkan
null);
}
public static ImageMemoryBarrier GetImageBarrier(
Image image,
AccessFlags srcAccessMask,
AccessFlags dstAccessMask,
ImageAspectFlags aspectFlags,
int firstLayer,
int firstLevel,
int layers,
int levels)
{
return new()
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = srcAccessMask,
DstAccessMask = dstAccessMask,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image,
OldLayout = ImageLayout.General,
NewLayout = ImageLayout.General,
SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
};
}
public static unsafe void InsertImageBarrier(
Vk api,
CommandBuffer commandBuffer,
@ -511,18 +535,15 @@ namespace Ryujinx.Graphics.Vulkan
int layers,
int levels)
{
ImageMemoryBarrier memoryBarrier = new()
{
SType = StructureType.ImageMemoryBarrier,
SrcAccessMask = srcAccessMask,
DstAccessMask = dstAccessMask,
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
Image = image,
OldLayout = ImageLayout.General,
NewLayout = ImageLayout.General,
SubresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, (uint)levels, (uint)firstLayer, (uint)layers),
};
ImageMemoryBarrier memoryBarrier = GetImageBarrier(
image,
srcAccessMask,
dstAccessMask,
aspectFlags,
firstLayer,
firstLevel,
layers,
levels);
api.CmdPipelineBarrier(
commandBuffer,

View File

@ -20,6 +20,9 @@ namespace Ryujinx.Graphics.Vulkan
[GeneratedRegex("Radeon (((HD|R(5|7|9|X)) )?((M?[2-6]\\d{2}(\\D|$))|([7-8]\\d{3}(\\D|$))|Fury|Nano))|(Pro Duo)")]
public static partial Regex AmdGcnRegex();
[GeneratedRegex("NVIDIA GeForce (R|G)?TX? (\\d{3}\\d?)M?")]
public static partial Regex NvidiaConsumerClassRegex();
public static Vendor FromId(uint id)
{
return id switch

View File

@ -4,7 +4,7 @@ namespace Ryujinx.Graphics.Vulkan
{
public const bool UseFastBufferUpdates = true;
public const bool UseUnsafeBlit = true;
public const bool UsePushDescriptors = false;
public const bool UsePushDescriptors = true;
public const bool ForceD24S8Unsupported = false;
public const bool ForceRGB16IntFloatUnsupported = false;

View File

@ -68,6 +68,8 @@ namespace Ryujinx.Graphics.Vulkan
internal HelperShader HelperShader { get; private set; }
internal PipelineFull PipelineInternal => _pipeline;
internal BarrierBatch Barriers { get; private set; }
public IPipeline Pipeline => _pipeline;
public IWindow Window => _window;
@ -76,10 +78,15 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Func<string[]> _getRequiredExtensions;
private readonly string _preferredGpuId;
private int[] _pdReservedBindings;
private readonly static int[] _pdReservedBindingsNvn = { 3, 18, 21, 36, 30 };
private readonly static int[] _pdReservedBindingsOgl = { 17, 18, 34, 35, 36 };
internal Vendor Vendor { get; private set; }
internal bool IsAmdWindows { get; private set; }
internal bool IsIntelWindows { get; private set; }
internal bool IsAmdGcn { get; private set; }
internal bool IsNvidiaPreTuring { get; private set; }
internal bool IsMoltenVk { get; private set; }
internal bool IsTBDR { get; private set; }
internal bool IsSharedMemory { get; private set; }
@ -191,6 +198,19 @@ namespace Ryujinx.Graphics.Vulkan
SType = StructureType.PhysicalDevicePortabilitySubsetPropertiesKhr,
};
bool supportsPushDescriptors = _physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName);
PhysicalDevicePushDescriptorPropertiesKHR propertiesPushDescriptor = new PhysicalDevicePushDescriptorPropertiesKHR()
{
SType = StructureType.PhysicalDevicePushDescriptorPropertiesKhr
};
if (supportsPushDescriptors)
{
propertiesPushDescriptor.PNext = properties2.PNext;
properties2.PNext = &propertiesPushDescriptor;
}
PhysicalDeviceFeatures2 features2 = new()
{
SType = StructureType.PhysicalDeviceFeatures2,
@ -320,7 +340,8 @@ namespace Ryujinx.Graphics.Vulkan
_physicalDevice.IsDeviceExtensionPresent(ExtExtendedDynamicState.ExtensionName),
features2.Features.MultiViewport && !(IsMoltenVk && Vendor == Vendor.Amd), // Workaround for AMD on MoltenVK issue
featuresRobustness2.NullDescriptor || IsMoltenVk,
_physicalDevice.IsDeviceExtensionPresent(KhrPushDescriptor.ExtensionName),
supportsPushDescriptors && !IsMoltenVk,
propertiesPushDescriptor.MaxPushDescriptors,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyListRestart,
featuresPrimitiveTopologyListRestart.PrimitiveTopologyPatchListRestart,
supportsTransformFeedback,
@ -362,6 +383,8 @@ namespace Ryujinx.Graphics.Vulkan
HelperShader = new HelperShader(this, _device);
Barriers = new BarrierBatch(this);
_counters = new Counters(this, _device, _pipeline);
}
@ -400,6 +423,25 @@ namespace Ryujinx.Graphics.Vulkan
_initialized = true;
}
internal int[] GetPushDescriptorReservedBindings(bool isOgl)
{
// The first call of this method determines what push descriptor layout is used for all shaders on this renderer.
// This is chosen to minimize shaders that can't fit their uniforms on the device's max number of push descriptors.
if (_pdReservedBindings == null)
{
if (Capabilities.MaxPushDescriptors <= Constants.MaxUniformBuffersPerStage * 2)
{
_pdReservedBindings = isOgl ? _pdReservedBindingsOgl : _pdReservedBindingsNvn;
}
else
{
_pdReservedBindings = Array.Empty<int>();
}
}
return _pdReservedBindings;
}
public BufferHandle CreateBuffer(int size, BufferAccess access)
{
return BufferManager.CreateWithHandle(this, size, access.HasFlag(BufferAccess.SparseCompatible), access.Convert(), default, access == BufferAccess.Stream);
@ -716,6 +758,20 @@ namespace Ryujinx.Graphics.Vulkan
IsAmdGcn = !IsMoltenVk && Vendor == Vendor.Amd && VendorUtils.AmdGcnRegex().IsMatch(GpuRenderer);
if (Vendor == Vendor.Nvidia)
{
var match = VendorUtils.NvidiaConsumerClassRegex().Match(GpuRenderer);
if (match != null && int.TryParse(match.Groups[2].Value, out int gpuNumber))
{
IsNvidiaPreTuring = gpuNumber < 2000;
}
else if (GpuDriver.Contains("TITAN") && !GpuDriver.Contains("RTX"))
{
IsNvidiaPreTuring = true;
}
}
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
}
@ -862,6 +918,7 @@ namespace Ryujinx.Graphics.Vulkan
BufferManager.Dispose();
DescriptorSetManager.Dispose();
PipelineLayoutCache.Dispose();
Barriers.Dispose();
MemoryAllocator.Dispose();