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();