From c6d82209abeacd2336cde99e5a02b4596e70da83 Mon Sep 17 00:00:00 2001 From: riperiperi Date: Fri, 9 Sep 2022 00:30:19 +0100 Subject: [PATCH] Restride vertex buffer when stride causes attributes to misalign in Vulkan. (#3679) * Vertex Buffer Alignment part 1 * Update CacheByRange * Add Stride Change compute shader, fix storage buffers in helpers * An AMD exclusive * Reword * Change rules - stride conversion when attrs misalign * Fix stupid mistake * Fix background pipeline compile * Improve a few things. * Fix some feedback * Address Feedback (the shader binary didn't change when i changed the source to use the subgroup size) * Fix bug where rewritten buffer would be disposed instantly. --- Ryujinx.Graphics.GAL/Format.cs | 184 +++++++++++++ Ryujinx.Graphics.Vulkan/BufferHolder.cs | 32 ++- Ryujinx.Graphics.Vulkan/BufferManager.cs | 10 + Ryujinx.Graphics.Vulkan/BufferState.cs | 34 +-- Ryujinx.Graphics.Vulkan/CacheByRange.cs | 116 ++++++++- .../DescriptorSetUpdater.cs | 37 ++- .../HardwareCapabilities.cs | 3 + Ryujinx.Graphics.Vulkan/HelperShader.cs | 115 +++++++-- Ryujinx.Graphics.Vulkan/PipelineBase.cs | 124 +++++++-- Ryujinx.Graphics.Vulkan/PipelineConverter.cs | 19 +- Ryujinx.Graphics.Vulkan/PipelineFull.cs | 17 +- .../PipelineHelperShader.cs | 10 + .../PipelineLayoutFactory.cs | 19 +- .../ChangeBufferStrideShaderSource.comp | 64 +++++ .../Shaders/ShaderBinaries.cs | 243 ++++++++++++++++++ Ryujinx.Graphics.Vulkan/VertexBufferState.cs | 131 ++++++++++ .../VulkanInitialization.cs | 1 + Ryujinx.Graphics.Vulkan/VulkanRenderer.cs | 30 +++ 18 files changed, 1069 insertions(+), 120 deletions(-) create mode 100644 Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp create mode 100644 Ryujinx.Graphics.Vulkan/VertexBufferState.cs diff --git a/Ryujinx.Graphics.GAL/Format.cs b/Ryujinx.Graphics.GAL/Format.cs index e455048a..8a50f22d 100644 --- a/Ryujinx.Graphics.GAL/Format.cs +++ b/Ryujinx.Graphics.GAL/Format.cs @@ -151,6 +151,190 @@ namespace Ryujinx.Graphics.GAL public static class FormatExtensions { + /// + /// The largest scalar size for a buffer format. + /// + public const int MaxBufferFormatScalarSize = 4; + + /// + /// Gets the byte size for a single component of this format, or its packed size. + /// + /// Texture format + /// Byte size for a single component, or packed size + public static int GetScalarSize(this Format format) + { + switch (format) + { + case Format.R8Unorm: + case Format.R8Snorm: + case Format.R8Uint: + case Format.R8Sint: + case Format.R8G8Unorm: + case Format.R8G8Snorm: + case Format.R8G8Uint: + case Format.R8G8Sint: + case Format.R8G8B8Unorm: + case Format.R8G8B8Snorm: + case Format.R8G8B8Uint: + case Format.R8G8B8Sint: + case Format.R8G8B8A8Unorm: + case Format.R8G8B8A8Snorm: + case Format.R8G8B8A8Uint: + case Format.R8G8B8A8Sint: + case Format.R8G8B8A8Srgb: + case Format.R4G4Unorm: + case Format.R8Uscaled: + case Format.R8Sscaled: + case Format.R8G8Uscaled: + case Format.R8G8Sscaled: + case Format.R8G8B8Uscaled: + case Format.R8G8B8Sscaled: + case Format.R8G8B8A8Uscaled: + case Format.R8G8B8A8Sscaled: + case Format.B8G8R8A8Unorm: + case Format.B8G8R8A8Srgb: + return 1; + + case Format.R16Float: + case Format.R16Unorm: + case Format.R16Snorm: + case Format.R16Uint: + case Format.R16Sint: + case Format.R16G16Float: + case Format.R16G16Unorm: + case Format.R16G16Snorm: + case Format.R16G16Uint: + case Format.R16G16Sint: + case Format.R16G16B16Float: + case Format.R16G16B16Unorm: + case Format.R16G16B16Snorm: + case Format.R16G16B16Uint: + case Format.R16G16B16Sint: + case Format.R16G16B16A16Float: + case Format.R16G16B16A16Unorm: + case Format.R16G16B16A16Snorm: + case Format.R16G16B16A16Uint: + case Format.R16G16B16A16Sint: + case Format.R4G4B4A4Unorm: + case Format.R5G5B5X1Unorm: + case Format.R5G5B5A1Unorm: + case Format.R5G6B5Unorm: + case Format.R16Uscaled: + case Format.R16Sscaled: + case Format.R16G16Uscaled: + case Format.R16G16Sscaled: + case Format.R16G16B16Uscaled: + case Format.R16G16B16Sscaled: + case Format.R16G16B16A16Uscaled: + case Format.R16G16B16A16Sscaled: + case Format.B5G6R5Unorm: + case Format.B5G5R5A1Unorm: + case Format.A1B5G5R5Unorm: + return 2; + + case Format.R32Float: + case Format.R32Uint: + case Format.R32Sint: + case Format.R32G32Float: + case Format.R32G32Uint: + case Format.R32G32Sint: + case Format.R32G32B32Float: + case Format.R32G32B32Uint: + case Format.R32G32B32Sint: + case Format.R32G32B32A32Float: + case Format.R32G32B32A32Uint: + case Format.R32G32B32A32Sint: + case Format.R10G10B10A2Unorm: + case Format.R10G10B10A2Uint: + case Format.R11G11B10Float: + case Format.R9G9B9E5Float: + case Format.R32Uscaled: + case Format.R32Sscaled: + case Format.R32G32Uscaled: + case Format.R32G32Sscaled: + case Format.R32G32B32Uscaled: + case Format.R32G32B32Sscaled: + case Format.R32G32B32A32Uscaled: + case Format.R32G32B32A32Sscaled: + case Format.R10G10B10A2Snorm: + case Format.R10G10B10A2Sint: + case Format.R10G10B10A2Uscaled: + case Format.R10G10B10A2Sscaled: + return 4; + + case Format.S8Uint: + return 1; + case Format.D16Unorm: + return 2; + case Format.S8UintD24Unorm: + case Format.D32Float: + case Format.D24UnormS8Uint: + return 4; + case Format.D32FloatS8Uint: + return 8; + + case Format.Bc1RgbaUnorm: + case Format.Bc1RgbaSrgb: + return 8; + + case Format.Bc2Unorm: + case Format.Bc3Unorm: + case Format.Bc2Srgb: + case Format.Bc3Srgb: + case Format.Bc4Unorm: + case Format.Bc4Snorm: + case Format.Bc5Unorm: + case Format.Bc5Snorm: + case Format.Bc7Unorm: + case Format.Bc7Srgb: + case Format.Bc6HSfloat: + case Format.Bc6HUfloat: + return 16; + + case Format.Etc2RgbUnorm: + case Format.Etc2RgbPtaUnorm: + case Format.Etc2RgbSrgb: + case Format.Etc2RgbPtaSrgb: + return 8; + + case Format.Etc2RgbaUnorm: + case Format.Etc2RgbaSrgb: + return 16; + + case Format.Astc4x4Unorm: + case Format.Astc5x4Unorm: + case Format.Astc5x5Unorm: + case Format.Astc6x5Unorm: + case Format.Astc6x6Unorm: + case Format.Astc8x5Unorm: + case Format.Astc8x6Unorm: + case Format.Astc8x8Unorm: + case Format.Astc10x5Unorm: + case Format.Astc10x6Unorm: + case Format.Astc10x8Unorm: + case Format.Astc10x10Unorm: + case Format.Astc12x10Unorm: + case Format.Astc12x12Unorm: + case Format.Astc4x4Srgb: + case Format.Astc5x4Srgb: + case Format.Astc5x5Srgb: + case Format.Astc6x5Srgb: + case Format.Astc6x6Srgb: + case Format.Astc8x5Srgb: + case Format.Astc8x6Srgb: + case Format.Astc8x8Srgb: + case Format.Astc10x5Srgb: + case Format.Astc10x6Srgb: + case Format.Astc10x8Srgb: + case Format.Astc10x10Srgb: + case Format.Astc12x10Srgb: + case Format.Astc12x12Srgb: + return 16; + } + + return 1; + } + /// /// Checks if the texture format is valid to use as image format. /// diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs index a366e4ac..a2fc0c39 100644 --- a/Ryujinx.Graphics.Vulkan/BufferHolder.cs +++ b/Ryujinx.Graphics.Vulkan/BufferHolder.cs @@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.Vulkan private readonly Auto _allocationAuto; private readonly ulong _bufferHandle; - private CacheByRange _cachedConvertedIndexBuffers; + private CacheByRange _cachedConvertedBuffers; public int Size { get; } @@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Vulkan { if (isWrite) { - _cachedConvertedIndexBuffers.Clear(); + _cachedConvertedBuffers.Clear(); } return _buffer; @@ -364,13 +364,35 @@ namespace Ryujinx.Graphics.Vulkan public Auto GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size) { - if (!_cachedConvertedIndexBuffers.TryGetValue(offset, size, out var holder)) + var key = new I8ToI16CacheKey(); + + if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder)) { holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3); _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size); - _cachedConvertedIndexBuffers.Add(offset, size, holder); + _cachedConvertedBuffers.Add(offset, size, key, holder); + } + + return holder.GetBuffer(); + } + + public Auto GetAlignedVertexBuffer(CommandBufferScoped cbs, int offset, int size, int stride, int alignment) + { + var key = new AlignedVertexBufferCacheKey(_gd, stride, alignment); + + if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder)) + { + int alignedStride = (stride + (alignment - 1)) & -alignment; + + holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride); + + _gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride); + + key.SetBuffer(holder.GetBuffer()); + + _cachedConvertedBuffers.Add(offset, size, key, holder); } return holder.GetBuffer(); @@ -382,7 +404,7 @@ namespace Ryujinx.Graphics.Vulkan _buffer.Dispose(); _allocationAuto.Dispose(); - _cachedConvertedIndexBuffers.Dispose(); + _cachedConvertedBuffers.Dispose(); } } } diff --git a/Ryujinx.Graphics.Vulkan/BufferManager.cs b/Ryujinx.Graphics.Vulkan/BufferManager.cs index 77f60db9..43bd9877 100644 --- a/Ryujinx.Graphics.Vulkan/BufferManager.cs +++ b/Ryujinx.Graphics.Vulkan/BufferManager.cs @@ -130,6 +130,16 @@ namespace Ryujinx.Graphics.Vulkan return null; } + public Auto GetAlignedVertexBuffer(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, int stride, int alignment) + { + if (TryGetBuffer(handle, out var holder)) + { + return holder.GetAlignedVertexBuffer(cbs, offset, size, stride, alignment); + } + + return null; + } + public Auto GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, out int size) { if (TryGetBuffer(handle, out var holder)) diff --git a/Ryujinx.Graphics.Vulkan/BufferState.cs b/Ryujinx.Graphics.Vulkan/BufferState.cs index c91ed7a1..1790017a 100644 --- a/Ryujinx.Graphics.Vulkan/BufferState.cs +++ b/Ryujinx.Graphics.Vulkan/BufferState.cs @@ -7,28 +7,28 @@ namespace Ryujinx.Graphics.Vulkan { public static BufferState Null => new BufferState(null, 0, 0); - private readonly Auto _buffer; private readonly int _offset; private readonly int _size; - private readonly ulong _stride; private readonly IndexType _type; + private readonly Auto _buffer; + public BufferState(Auto buffer, int offset, int size, IndexType type) { _buffer = buffer; + _offset = offset; _size = size; - _stride = 0; _type = type; buffer?.IncrementReferenceCount(); } - public BufferState(Auto buffer, int offset, int size, ulong stride = 0UL) + public BufferState(Auto buffer, int offset, int size) { _buffer = buffer; + _offset = offset; _size = size; - _stride = stride; _type = IndexType.Uint16; buffer?.IncrementReferenceCount(); } @@ -51,30 +51,6 @@ namespace Ryujinx.Graphics.Vulkan } } - public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding) - { - if (_buffer != null) - { - var buffer = _buffer.Get(cbs, _offset, _size).Value; - - if (gd.Capabilities.SupportsExtendedDynamicState) - { - gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( - cbs.CommandBuffer, - binding, - 1, - buffer, - (ulong)_offset, - (ulong)_size, - _stride); - } - else - { - gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset); - } - } - } - public void Dispose() { _buffer?.DecrementReferenceCount(); diff --git a/Ryujinx.Graphics.Vulkan/CacheByRange.cs b/Ryujinx.Graphics.Vulkan/CacheByRange.cs index f3f503da..f9edca8a 100644 --- a/Ryujinx.Graphics.Vulkan/CacheByRange.cs +++ b/Ryujinx.Graphics.Vulkan/CacheByRange.cs @@ -3,29 +3,110 @@ using System.Collections.Generic; namespace Ryujinx.Graphics.Vulkan { - struct CacheByRange where T : IDisposable + interface ICacheKey : IDisposable { - private Dictionary _ranges; + bool KeyEqual(ICacheKey other); + } - public void Add(int offset, int size, T value) + struct I8ToI16CacheKey : ICacheKey + { + public I8ToI16CacheKey() { } + + public bool KeyEqual(ICacheKey other) { - EnsureInitialized(); - _ranges.Add(PackRange(offset, size), value); + return other is I8ToI16CacheKey; } - public bool TryGetValue(int offset, int size, out T value) + public void Dispose() { } + } + + struct AlignedVertexBufferCacheKey : ICacheKey + { + private readonly int _stride; + private readonly int _alignment; + + // Used to notify the pipeline that bindings have invalidated on dispose. + private readonly VulkanRenderer _gd; + private Auto _buffer; + + public AlignedVertexBufferCacheKey(VulkanRenderer gd, int stride, int alignment) { - EnsureInitialized(); - return _ranges.TryGetValue(PackRange(offset, size), out value); + _gd = gd; + _stride = stride; + _alignment = alignment; + _buffer = null; + } + + public bool KeyEqual(ICacheKey other) + { + return other is AlignedVertexBufferCacheKey entry && + entry._stride == _stride && + entry._alignment == _alignment; + } + + public void SetBuffer(Auto buffer) + { + _buffer = buffer; + } + + public void Dispose() + { + _gd.PipelineInternal.DirtyVertexBuffer(_buffer); + } + } + + struct CacheByRange where T : IDisposable + { + private struct Entry + { + public ICacheKey Key; + public T Value; + + public Entry(ICacheKey key, T value) + { + Key = key; + Value = value; + } + } + + private Dictionary> _ranges; + + public void Add(int offset, int size, ICacheKey key, T value) + { + List entries = GetEntries(offset, size); + + entries.Add(new Entry(key, value)); + } + + public bool TryGetValue(int offset, int size, ICacheKey key, out T value) + { + List entries = GetEntries(offset, size); + + foreach (Entry entry in entries) + { + if (entry.Key.KeyEqual(key)) + { + value = entry.Value; + + return true; + } + } + + value = default; + return false; } public void Clear() { if (_ranges != null) { - foreach (T value in _ranges.Values) + foreach (List entries in _ranges.Values) { - value.Dispose(); + foreach (Entry entry in entries) + { + entry.Key.Dispose(); + entry.Value.Dispose(); + } } _ranges.Clear(); @@ -33,12 +114,23 @@ namespace Ryujinx.Graphics.Vulkan } } - private void EnsureInitialized() + private List GetEntries(int offset, int size) { if (_ranges == null) { - _ranges = new Dictionary(); + _ranges = new Dictionary>(); } + + ulong key = PackRange(offset, size); + + List value; + if (!_ranges.TryGetValue(key, out value)) + { + value = new List(); + _ranges.Add(key, value); + } + + return value; } private static ulong PackRange(int offset, int size) diff --git a/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs b/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs index f708f794..9e372311 100644 --- a/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs +++ b/Ryujinx.Graphics.Vulkan/DescriptorSetUpdater.cs @@ -185,6 +185,34 @@ namespace Ryujinx.Graphics.Vulkan SignalDirty(DirtyFlags.Storage); } + public void SetStorageBuffers(CommandBuffer commandBuffer, int first, ReadOnlySpan> buffers) + { + for (int i = 0; i < buffers.Length; i++) + { + var vkBuffer = buffers[i]; + int index = first + i; + + ref Auto currentVkBuffer = ref _storageBufferRefs[index]; + + DescriptorBufferInfo info = new DescriptorBufferInfo() + { + Offset = 0, + Range = Vk.WholeSize + }; + ref DescriptorBufferInfo currentInfo = ref _storageBuffers[index]; + + if (vkBuffer != currentVkBuffer || currentInfo.Offset != info.Offset || currentInfo.Range != info.Range) + { + _storageSet[index] = false; + + currentInfo = info; + currentVkBuffer = vkBuffer; + } + } + + SignalDirty(DirtyFlags.Storage); + } + public void SetTextureAndSampler(CommandBufferScoped cbs, ShaderStage stage, int binding, ITexture texture, ISampler sampler) { if (texture == null) @@ -388,7 +416,14 @@ namespace Ryujinx.Graphics.Vulkan } ReadOnlySpan storageBuffers = _storageBuffers; - dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count)); + if (program.HasMinimalLayout) + { + dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer); + } + else + { + dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count)); + } } else if (setIndex == PipelineBase.TextureSetIndex) { diff --git a/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs index 5721962d..0c40aa71 100644 --- a/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs +++ b/Ryujinx.Graphics.Vulkan/HardwareCapabilities.cs @@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Vulkan public readonly bool SupportsFragmentShaderInterlock; public readonly bool SupportsGeometryShaderPassthrough; public readonly bool SupportsSubgroupSizeControl; + public readonly bool SupportsShaderInt8; public readonly bool SupportsConditionalRendering; public readonly bool SupportsExtendedDynamicState; public readonly bool SupportsMultiView; @@ -29,6 +30,7 @@ namespace Ryujinx.Graphics.Vulkan bool supportsFragmentShaderInterlock, bool supportsGeometryShaderPassthrough, bool supportsSubgroupSizeControl, + bool supportsShaderInt8, bool supportsConditionalRendering, bool supportsExtendedDynamicState, bool supportsMultiView, @@ -47,6 +49,7 @@ namespace Ryujinx.Graphics.Vulkan SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock; SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough; SupportsSubgroupSizeControl = supportsSubgroupSizeControl; + SupportsShaderInt8 = supportsShaderInt8; SupportsConditionalRendering = supportsConditionalRendering; SupportsExtendedDynamicState = supportsExtendedDynamicState; SupportsMultiView = supportsMultiView; diff --git a/Ryujinx.Graphics.Vulkan/HelperShader.cs b/Ryujinx.Graphics.Vulkan/HelperShader.cs index 8465c744..2eec92f0 100644 --- a/Ryujinx.Graphics.Vulkan/HelperShader.cs +++ b/Ryujinx.Graphics.Vulkan/HelperShader.cs @@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Vulkan private readonly IProgram _programColorBlit; private readonly IProgram _programColorBlitClearAlpha; private readonly IProgram _programColorClear; + private readonly IProgram _programStrideChange; public HelperShader(VulkanRenderer gd, Device device) { @@ -39,14 +40,14 @@ namespace Ryujinx.Graphics.Vulkan _programColorBlit = gd.CreateProgramWithMinimalLayout(new[] { - new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Glsl), - new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, fragmentBindings, ShaderStage.Fragment, TargetLanguage.Glsl), + new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ShaderBinaries.ColorBlitFragmentShaderSource, fragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); _programColorBlitClearAlpha = gd.CreateProgramWithMinimalLayout(new[] { - new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Glsl), - new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, fragmentBindings, ShaderStage.Fragment, TargetLanguage.Glsl), + new ShaderSource(ShaderBinaries.ColorBlitVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ShaderBinaries.ColorBlitClearAlphaFragmentShaderSource, fragmentBindings, ShaderStage.Fragment, TargetLanguage.Spirv), }); var fragmentBindings2 = new ShaderBindings( @@ -57,8 +58,19 @@ namespace Ryujinx.Graphics.Vulkan _programColorClear = gd.CreateProgramWithMinimalLayout(new[] { - new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Glsl), - new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, fragmentBindings2, ShaderStage.Fragment, TargetLanguage.Glsl), + new ShaderSource(ShaderBinaries.ColorClearVertexShaderSource, vertexBindings, ShaderStage.Vertex, TargetLanguage.Spirv), + new ShaderSource(ShaderBinaries.ColorClearFragmentShaderSource, fragmentBindings2, ShaderStage.Fragment, TargetLanguage.Spirv), + }); + + var strideChangeBindings = new ShaderBindings( + new[] { 0 }, + new[] { 1, 2 }, + Array.Empty(), + Array.Empty()); + + _programStrideChange = gd.CreateProgramWithMinimalLayout(new[] + { + new ShaderSource(ShaderBinaries.ChangeBufferStrideShaderSource, strideChangeBindings, ShaderStage.Compute, TargetLanguage.Spirv), }); } @@ -163,7 +175,7 @@ namespace Ryujinx.Graphics.Vulkan _pipeline.SetViewports(viewports, false); _pipeline.SetPrimitiveTopology(GAL.PrimitiveTopology.TriangleStrip); _pipeline.Draw(4, 1, 0, 0); - _pipeline.Finish(); + _pipeline.Finish(gd, cbs); gd.BufferManager.Delete(bufferHandle); } @@ -291,45 +303,100 @@ namespace Ryujinx.Graphics.Vulkan public unsafe void ConvertI8ToI16(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size) { - // TODO: Do this with a compute shader? - var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, size).Value; - var dstBuffer = dst.GetBuffer().Get(cbs, 0, size * 2).Value; + ChangeStride(gd, cbs, src, dst, srcOffset, size, 1, 2); + } - gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0); + public unsafe void ChangeStride(VulkanRenderer gd, CommandBufferScoped cbs, BufferHolder src, BufferHolder dst, int srcOffset, int size, int stride, int newStride) + { + bool supportsUint8 = gd.Capabilities.SupportsShaderInt8; - var bufferCopy = new BufferCopy[size]; + int elems = size / stride; + int newSize = elems * newStride; - for (ulong i = 0; i < (ulong)size; i++) - { - bufferCopy[i] = new BufferCopy((ulong)srcOffset + i, i * 2, 1); - } + var srcBufferAuto = src.GetBuffer(); + var dstBufferAuto = dst.GetBuffer(); + + var srcBuffer = srcBufferAuto.Get(cbs, srcOffset, size).Value; + var dstBuffer = dstBufferAuto.Get(cbs, 0, newSize).Value; + + var access = supportsUint8 ? AccessFlags.AccessShaderWriteBit : AccessFlags.AccessTransferWriteBit; + var stage = supportsUint8 ? PipelineStageFlags.PipelineStageComputeShaderBit : PipelineStageFlags.PipelineStageTransferBit; BufferHolder.InsertBufferBarrier( gd, cbs.CommandBuffer, dstBuffer, BufferHolder.DefaultAccessFlags, - AccessFlags.AccessTransferWriteBit, + access, PipelineStageFlags.PipelineStageAllCommandsBit, - PipelineStageFlags.PipelineStageTransferBit, + stage, 0, - size * 2); + newSize); - fixed (BufferCopy* pBufferCopy = bufferCopy) + if (supportsUint8) { - gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)size, pBufferCopy); + const int ParamsBufferSize = 16; + + Span shaderParams = stackalloc int[ParamsBufferSize / sizeof(int)]; + + shaderParams[0] = stride; + shaderParams[1] = newStride; + shaderParams[2] = size; + shaderParams[3] = srcOffset; + + var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false); + + gd.BufferManager.SetData(bufferHandle, 0, shaderParams); + + _pipeline.SetCommandBuffer(cbs); + + Span cbRanges = stackalloc BufferRange[1]; + + cbRanges[0] = new BufferRange(bufferHandle, 0, ParamsBufferSize); + + _pipeline.SetUniformBuffers(0, cbRanges); + + Span> sbRanges = new Auto[2]; + + sbRanges[0] = srcBufferAuto; + sbRanges[1] = dstBufferAuto; + + _pipeline.SetStorageBuffers(1, sbRanges); + + _pipeline.SetProgram(_programStrideChange); + _pipeline.DispatchCompute(1, 1, 1); + + gd.BufferManager.Delete(bufferHandle); + + _pipeline.Finish(gd, cbs); + } + else + { + gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0); + + var bufferCopy = new BufferCopy[elems]; + + for (ulong i = 0; i < (ulong)elems; i++) + { + bufferCopy[i] = new BufferCopy((ulong)srcOffset + i * (ulong)stride, i * (ulong)newStride, (ulong)stride); + } + + fixed (BufferCopy* pBufferCopy = bufferCopy) + { + gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)elems, pBufferCopy); + } } BufferHolder.InsertBufferBarrier( gd, cbs.CommandBuffer, dstBuffer, - AccessFlags.AccessTransferWriteBit, + access, BufferHolder.DefaultAccessFlags, - PipelineStageFlags.PipelineStageTransferBit, + stage, PipelineStageFlags.PipelineStageAllCommandsBit, 0, - size * 2); + newSize); } protected virtual void Dispose(bool disposing) diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs index 30eeafb8..769d4594 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs @@ -2,6 +2,7 @@ using Ryujinx.Graphics.Shader; using Silk.NET.Vulkan; using System; +using System.Numerics; namespace Ryujinx.Graphics.Vulkan { @@ -50,14 +51,14 @@ namespace Ryujinx.Graphics.Vulkan private BufferState _indexBuffer; private readonly BufferState[] _transformFeedbackBuffers; - private readonly BufferState[] _vertexBuffers; + private readonly VertexBufferState[] _vertexBuffers; + private ulong _vertexBuffersDirty; protected Rectangle ClearScissor; public SupportBufferUpdater SupportBufferUpdater; private bool _needsIndexBufferRebind; private bool _needsTransformFeedbackBuffersRebind; - private bool _needsVertexBuffersRebind; private bool _tfEnabled; private bool _tfActive; @@ -79,14 +80,14 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater = new DescriptorSetUpdater(gd, this); _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers]; - _vertexBuffers = new BufferState[Constants.MaxVertexBuffers + 1]; + _vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1]; const int EmptyVbSize = 16; using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize); emptyVb.SetData(0, new byte[EmptyVbSize]); - _vertexBuffers[0] = new BufferState(emptyVb.GetBuffer(), 0, EmptyVbSize, 0UL); - _needsVertexBuffersRebind = true; + _vertexBuffers[0] = new VertexBufferState(emptyVb.GetBuffer(), 0, EmptyVbSize, 0); + _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length); ClearScissor = new Rectangle(0, 0, 0xffff, 0xffff); @@ -229,6 +230,17 @@ namespace Ryujinx.Graphics.Vulkan BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size); } + public void DirtyVertexBuffer(Auto buffer) + { + for (int i = 0; i < _vertexBuffers.Length; i++) + { + if (_vertexBuffers[i].BoundEquals(buffer)) + { + _vertexBuffersDirty |= 1UL << i; + } + } + } + public void DispatchCompute(int groupsX, int groupsY, int groupsZ) { if (!_program.IsLinked) @@ -345,6 +357,11 @@ namespace Ryujinx.Graphics.Vulkan _tfEnabled = false; } + public bool IsCommandBufferActive(CommandBuffer cb) + { + return CommandBuffer.Handle == cb.Handle; + } + public void MultiDrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride) { if (!Gd.Capabilities.SupportsIndirectParameters) @@ -689,6 +706,11 @@ namespace Ryujinx.Graphics.Vulkan _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers); } + public void SetStorageBuffers(int first, ReadOnlySpan> buffers) + { + _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers); + } + public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler) { _descriptorSetUpdater.SetTextureAndSampler(Cbs, stage, binding, texture, sampler); @@ -732,12 +754,22 @@ namespace Ryujinx.Graphics.Vulkan { var formatCapabilities = Gd.FormatCapabilities; + Span newVbScalarSizes = stackalloc int[Constants.MaxVertexBuffers]; + int count = Math.Min(Constants.MaxVertexAttributes, vertexAttribs.Length); + uint dirtyVbSizes = 0; for (int i = 0; i < count; i++) { var attribute = vertexAttribs[i]; - var bufferIndex = attribute.IsZero ? 0 : attribute.BufferIndex + 1; + var rawIndex = attribute.BufferIndex; + var bufferIndex = attribute.IsZero ? 0 : rawIndex + 1; + + if (!attribute.IsZero) + { + newVbScalarSizes[rawIndex] = Math.Max(newVbScalarSizes[rawIndex], attribute.Format.GetScalarSize()); + dirtyVbSizes |= 1u << rawIndex; + } _newState.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription( (uint)i, @@ -746,6 +778,21 @@ namespace Ryujinx.Graphics.Vulkan (uint)attribute.Offset); } + while (dirtyVbSizes != 0) + { + int dirtyBit = BitOperations.TrailingZeroCount(dirtyVbSizes); + + ref var buffer = ref _vertexBuffers[dirtyBit + 1]; + + if (buffer.AttributeScalarAlignment != newVbScalarSizes[dirtyBit]) + { + _vertexBuffersDirty |= 1UL << (dirtyBit + 1); + buffer.AttributeScalarAlignment = newVbScalarSizes[dirtyBit]; + } + + dirtyVbSizes &= ~(1u << dirtyBit); + } + _newState.VertexAttributeDescriptionsCount = (uint)count; SignalStateChange(); } @@ -792,14 +839,37 @@ namespace Ryujinx.Graphics.Vulkan } } - _vertexBuffers[binding].Dispose(); - _vertexBuffers[binding] = new BufferState( - vb, - vertexBuffer.Buffer.Offset, - vbSize, - (ulong)vertexBuffer.Stride); + ref var buffer = ref _vertexBuffers[binding]; + int oldScalarAlign = buffer.AttributeScalarAlignment; - _vertexBuffers[binding].BindVertexBuffer(Gd, Cbs, (uint)binding); + buffer.Dispose(); + + if ((vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0) + { + buffer = new VertexBufferState( + vb, + descriptorIndex, + vertexBuffer.Buffer.Offset, + vbSize, + vertexBuffer.Stride); + + buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState); + } + else + { + // May need to be rewritten. Bind this buffer before draw. + + buffer = new VertexBufferState( + vertexBuffer.Buffer.Handle, + descriptorIndex, + vertexBuffer.Buffer.Offset, + vbSize, + vertexBuffer.Stride); + + _vertexBuffersDirty |= 1UL << binding; + } + + buffer.AttributeScalarAlignment = oldScalarAlign; } } } @@ -907,7 +977,7 @@ namespace Ryujinx.Graphics.Vulkan { _needsIndexBufferRebind = true; _needsTransformFeedbackBuffersRebind = true; - _needsVertexBuffersRebind = true; + _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length); _descriptorSetUpdater.SignalCommandBufferChange(); _dynamicState.ForceAllDirty(); @@ -1053,13 +1123,6 @@ namespace Ryujinx.Graphics.Vulkan // Commit changes to the support buffer before drawing. SupportBufferUpdater.Commit(); - if (_stateDirty || Pbp != pbp) - { - CreatePipeline(pbp); - _stateDirty = false; - Pbp = pbp; - } - if (_needsIndexBufferRebind) { _indexBuffer.BindIndexBuffer(Gd.Api, Cbs); @@ -1078,14 +1141,23 @@ namespace Ryujinx.Graphics.Vulkan _needsTransformFeedbackBuffersRebind = false; } - if (_needsVertexBuffersRebind) + if (_vertexBuffersDirty != 0) { - for (int i = 0; i < Constants.MaxVertexBuffers + 1; i++) + while (_vertexBuffersDirty != 0) { - _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i); - } + int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty); - _needsVertexBuffersRebind = false; + _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState); + + _vertexBuffersDirty &= ~(1u << i); + } + } + + if (_stateDirty || Pbp != pbp) + { + CreatePipeline(pbp); + _stateDirty = false; + Pbp = pbp; } _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, pbp); diff --git a/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs index 315df1b1..c0930351 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineConverter.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs @@ -202,6 +202,9 @@ namespace Ryujinx.Graphics.Vulkan pipeline.Topology = state.Topology.Convert(); int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount); + int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount); + + Span vbScalarSizes = stackalloc int[vbCount]; for (int i = 0; i < vaCount; i++) { @@ -213,13 +216,16 @@ namespace Ryujinx.Graphics.Vulkan (uint)bufferIndex, gd.FormatCapabilities.ConvertToVertexVkFormat(attribute.Format), (uint)attribute.Offset); + + if (!attribute.IsZero && bufferIndex < vbCount) + { + vbScalarSizes[bufferIndex - 1] = Math.Max(attribute.Format.GetScalarSize(), vbScalarSizes[bufferIndex - 1]); + } } int descriptorIndex = 1; pipeline.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex); - int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount); - for (int i = 0; i < vbCount; i++) { var vertexBuffer = state.VertexBuffers[i]; @@ -228,10 +234,17 @@ namespace Ryujinx.Graphics.Vulkan { var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex; + int alignedStride = vertexBuffer.Stride; + + if (gd.NeedsVertexBufferAlignment(vbScalarSizes[i], out int alignment)) + { + alignedStride = (vertexBuffer.Stride + (alignment - 1)) & -alignment; + } + // TODO: Support divisor > 1 pipeline.Internal.VertexBindingDescriptions[descriptorIndex++] = new VertexInputBindingDescription( (uint)i + 1, - (uint)vertexBuffer.Stride, + (uint)alignedStride, inputRate); } } diff --git a/Ryujinx.Graphics.Vulkan/PipelineFull.cs b/Ryujinx.Graphics.Vulkan/PipelineFull.cs index 4c76caf2..ca3a33ef 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineFull.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineFull.cs @@ -199,6 +199,16 @@ namespace Ryujinx.Graphics.Vulkan } } + public void Restore() + { + if (Pipeline != null) + { + Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value); + } + + SignalCommandBufferChange(); + } + public void FlushCommandsImpl() { EndRenderPass(); @@ -220,18 +230,13 @@ namespace Ryujinx.Graphics.Vulkan // Restore per-command buffer state. - if (Pipeline != null) - { - Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value); - } - foreach (var queryPool in _activeQueries) { Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1); Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, 0); } - SignalCommandBufferChange(); + Restore(); } public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset) diff --git a/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs b/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs index f874a962..b2ee145d 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineHelperShader.cs @@ -40,5 +40,15 @@ namespace Ryujinx.Graphics.Vulkan { EndRenderPass(); } + + public void Finish(VulkanRenderer gd, CommandBufferScoped cbs) + { + Finish(); + + if (gd.PipelineInternal.IsCommandBufferActive(cbs.CommandBuffer)) + { + gd.PipelineInternal.Restore(); + } + } } } diff --git a/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs b/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs index 541f3a25..a064df7a 100644 --- a/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs +++ b/Ryujinx.Graphics.Vulkan/PipelineLayoutFactory.cs @@ -142,18 +142,20 @@ namespace Ryujinx.Graphics.Vulkan int stagesCount = shaders.Length; int uCount = 0; + int sCount = 0; int tCount = 0; int iCount = 0; foreach (var shader in shaders) { uCount += shader.Bindings.UniformBufferBindings.Count; + sCount += shader.Bindings.StorageBufferBindings.Count; tCount += shader.Bindings.TextureBindings.Count; iCount += shader.Bindings.ImageBindings.Count; } DescriptorSetLayoutBinding* uLayoutBindings = stackalloc DescriptorSetLayoutBinding[uCount]; - DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[stagesCount]; + DescriptorSetLayoutBinding* sLayoutBindings = stackalloc DescriptorSetLayoutBinding[sCount]; DescriptorSetLayoutBinding* tLayoutBindings = stackalloc DescriptorSetLayoutBinding[tCount]; DescriptorSetLayoutBinding* iLayoutBindings = stackalloc DescriptorSetLayoutBinding[iCount]; @@ -180,22 +182,11 @@ namespace Ryujinx.Graphics.Vulkan } } - void SetStorage(DescriptorSetLayoutBinding* bindings, ref int start, int count) - { - bindings[start++] = new DescriptorSetLayoutBinding - { - Binding = (uint)start, - DescriptorType = DescriptorType.StorageBuffer, - DescriptorCount = (uint)count, - StageFlags = stageFlags - }; - } - // TODO: Support buffer textures and images here. // This is only used for the helper shaders on the backend, and we don't use buffer textures on them // so far, so it's not really necessary right now. Set(uLayoutBindings, DescriptorType.UniformBuffer, ref uIndex, shader.Bindings.UniformBufferBindings); - SetStorage(sLayoutBindings, ref sIndex, shader.Bindings.StorageBufferBindings.Count); + Set(sLayoutBindings, DescriptorType.StorageBuffer, ref sIndex, shader.Bindings.StorageBufferBindings); Set(tLayoutBindings, DescriptorType.CombinedImageSampler, ref tIndex, shader.Bindings.TextureBindings); Set(iLayoutBindings, DescriptorType.StorageImage, ref iIndex, shader.Bindings.ImageBindings); } @@ -213,7 +204,7 @@ namespace Ryujinx.Graphics.Vulkan { SType = StructureType.DescriptorSetLayoutCreateInfo, PBindings = sLayoutBindings, - BindingCount = (uint)stagesCount + BindingCount = (uint)sCount }; var tDescriptorSetLayoutCreateInfo = new DescriptorSetLayoutCreateInfo() diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp b/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp new file mode 100644 index 00000000..081fc119 --- /dev/null +++ b/Ryujinx.Graphics.Vulkan/Shaders/ChangeBufferStrideShaderSource.comp @@ -0,0 +1,64 @@ +#version 450 core + +#extension GL_EXT_shader_8bit_storage : require + +layout (local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout (std140, set = 0, binding = 0) uniform stride_arguments +{ + ivec4 stride_arguments_data; +}; + +layout (std430, set = 1, binding = 1) buffer in_s +{ + uint8_t[] in_data; +}; + +layout (std430, set = 1, binding = 2) buffer out_s +{ + uint8_t[] out_data; +}; + +void main() +{ + // Determine what slice of the stride copies this invocation will perform. + + int sourceStride = stride_arguments_data.x; + int targetStride = stride_arguments_data.y; + int bufferSize = stride_arguments_data.z; + int sourceOffset = stride_arguments_data.w; + + int strideRemainder = targetStride - sourceStride; + int invocations = int(gl_WorkGroupSize.x); + + int copiesRequired = bufferSize / sourceStride; + + // Find the copies that this invocation should perform. + + // - Copies that all invocations perform. + int allInvocationCopies = copiesRequired / invocations; + + // - Extra remainder copy that this invocation performs. + int index = int(gl_LocalInvocationID.x); + int extra = (index < (copiesRequired % invocations)) ? 1 : 0; + + int copyCount = allInvocationCopies + extra; + + // Finally, get the starting offset. Make sure to count extra copies. + + int startCopy = allInvocationCopies * index + min(copiesRequired % invocations, index); + + int srcOffset = sourceOffset + startCopy * sourceStride; + int dstOffset = startCopy * targetStride; + + // Perform the copies for this region + for (int i=0; i new VertexBufferState(null, 0, 0, 0); + + private readonly int _offset; + private readonly int _size; + private readonly int _stride; + + private readonly BufferHandle _handle; + private Auto _buffer; + + internal readonly int DescriptorIndex; + internal int AttributeScalarAlignment; + + public VertexBufferState(Auto buffer, int descriptorIndex, int offset, int size, int stride = 0) + { + _buffer = buffer; + _handle = BufferHandle.Null; + + _offset = offset; + _size = size; + _stride = stride; + + DescriptorIndex = descriptorIndex; + AttributeScalarAlignment = 1; + + buffer?.IncrementReferenceCount(); + } + + public VertexBufferState(BufferHandle handle, int descriptorIndex, int offset, int size, int stride = 0) + { + // This buffer state may be rewritten at bind time, so it must be retrieved on bind. + + _buffer = null; + _handle = handle; + + _offset = offset; + _size = size; + _stride = stride; + + DescriptorIndex = descriptorIndex; + AttributeScalarAlignment = 1; + } + + public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state) + { + var autoBuffer = _buffer; + + if (_handle != BufferHandle.Null) + { + // May need to restride the vertex buffer. + + if (gd.NeedsVertexBufferAlignment(AttributeScalarAlignment, out int alignment) && (_stride % alignment) != 0) + { + autoBuffer = gd.BufferManager.GetAlignedVertexBuffer(cbs, _handle, _offset, _size, _stride, alignment); + int stride = (_stride + (alignment - 1)) & -alignment; + + var buffer = autoBuffer.Get(cbs, _offset, _size).Value; + + if (gd.Capabilities.SupportsExtendedDynamicState) + { + gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( + cbs.CommandBuffer, + binding, + 1, + buffer, + 0, + (ulong)(_size / _stride) * (ulong)stride, + (ulong)stride); + } + else + { + gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, 0); + } + + _buffer = autoBuffer; + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)stride; + + return; + } + else + { + autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int _); + + // The original stride must be reapplied in case it was rewritten. + state.Internal.VertexBindingDescriptions[DescriptorIndex].Stride = (uint)_stride; + } + } + + if (autoBuffer != null) + { + var buffer = autoBuffer.Get(cbs, _offset, _size).Value; + + if (gd.Capabilities.SupportsExtendedDynamicState) + { + gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2( + cbs.CommandBuffer, + binding, + 1, + buffer, + (ulong)_offset, + (ulong)_size, + (ulong)_stride); + } + else + { + gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset); + } + } + } + + public bool BoundEquals(Auto buffer) + { + return _buffer == buffer; + } + + public void Dispose() + { + // Only dispose if this buffer is not refetched on each bind. + + if (_handle == BufferHandle.Null) + { + _buffer?.DecrementReferenceCount(); + } + } + } +} diff --git a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs index 889ce7e2..54d98386 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanInitialization.cs @@ -28,6 +28,7 @@ namespace Ryujinx.Graphics.Vulkan "VK_EXT_fragment_shader_interlock", "VK_EXT_index_type_uint8", "VK_EXT_robustness2", + "VK_KHR_shader_float16_int8", "VK_EXT_shader_subgroup_ballot", "VK_EXT_subgroup_size_control", "VK_NV_geometry_shader_passthrough" diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs index b2f69636..bacb74cc 100644 --- a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs +++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs @@ -188,11 +188,22 @@ namespace Ryujinx.Graphics.Vulkan SType = StructureType.PhysicalDeviceRobustness2FeaturesExt }; + PhysicalDeviceShaderFloat16Int8FeaturesKHR featuresShaderInt8 = new PhysicalDeviceShaderFloat16Int8FeaturesKHR() + { + SType = StructureType.PhysicalDeviceShaderFloat16Int8Features + }; + if (supportedExtensions.Contains("VK_EXT_robustness2")) { features2.PNext = &featuresRobustness2; } + if (supportedExtensions.Contains("VK_KHR_shader_float16_int8")) + { + featuresShaderInt8.PNext = features2.PNext; + features2.PNext = &featuresShaderInt8; + } + Api.GetPhysicalDeviceFeatures2(_physicalDevice, &features2); Capabilities = new HardwareCapabilities( @@ -202,6 +213,7 @@ namespace Ryujinx.Graphics.Vulkan supportedExtensions.Contains("VK_EXT_fragment_shader_interlock"), supportedExtensions.Contains("VK_NV_geometry_shader_passthrough"), supportedExtensions.Contains("VK_EXT_subgroup_size_control"), + featuresShaderInt8.ShaderInt8, supportedExtensions.Contains(ExtConditionalRendering.ExtensionName), supportedExtensions.Contains(ExtExtendedDynamicState.ExtensionName), features2.Features.MultiViewport, @@ -506,6 +518,24 @@ namespace Ryujinx.Graphics.Vulkan PrintGpuInformation(); } + public bool NeedsVertexBufferAlignment(int attrScalarAlignment, out int alignment) + { + if (Vendor != Vendor.Nvidia) + { + // Vulkan requires that vertex attributes are globally aligned by their component size, + // so buffer strides that don't divide by the largest scalar element are invalid. + // Guest applications do this, NVIDIA GPUs are OK with it, others are not. + + alignment = attrScalarAlignment; + + return true; + } + + alignment = 1; + + return false; + } + public void PreFrame() { _syncManager.Cleanup();