From c6d82209abeacd2336cde99e5a02b4596e70da83 Mon Sep 17 00:00:00 2001
From: riperiperi <rhy3756547@hotmail.com>
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 e455048acd..8a50f22d4e 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
     {
+        /// <summary>
+        /// The largest scalar size for a buffer format.
+        /// </summary>
+        public const int MaxBufferFormatScalarSize = 4;
+
+        /// <summary>
+        /// Gets the byte size for a single component of this format, or its packed size.
+        /// </summary>
+        /// <param name="format">Texture format</param>
+        /// <returns>Byte size for a single component, or packed size</returns>
+        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;
+        }
+
         /// <summary>
         /// Checks if the texture format is valid to use as image format.
         /// </summary>
diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
index a366e4acd7..a2fc0c398f 100644
--- a/Ryujinx.Graphics.Vulkan/BufferHolder.cs
+++ b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
@@ -27,7 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
         private readonly Auto<MemoryAllocation> _allocationAuto;
         private readonly ulong _bufferHandle;
 
-        private CacheByRange<BufferHolder> _cachedConvertedIndexBuffers;
+        private CacheByRange<BufferHolder> _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<DisposableBuffer> 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<DisposableBuffer> 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 77f60db906..43bd9877d9 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<DisposableBuffer> 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<DisposableBuffer> 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 c91ed7a1f4..1790017a36 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<DisposableBuffer> _buffer;
         private readonly int _offset;
         private readonly int _size;
-        private readonly ulong _stride;
         private readonly IndexType _type;
 
+        private readonly Auto<DisposableBuffer> _buffer;
+
         public BufferState(Auto<DisposableBuffer> buffer, int offset, int size, IndexType type)
         {
             _buffer = buffer;
+
             _offset = offset;
             _size = size;
-            _stride = 0;
             _type = type;
             buffer?.IncrementReferenceCount();
         }
 
-        public BufferState(Auto<DisposableBuffer> buffer, int offset, int size, ulong stride = 0UL)
+        public BufferState(Auto<DisposableBuffer> 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 f3f503da40..f9edca8a2d 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<T> where T : IDisposable
+    interface ICacheKey : IDisposable
     {
-        private Dictionary<ulong, T> _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<DisposableBuffer> _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<DisposableBuffer> buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void Dispose()
+        {
+            _gd.PipelineInternal.DirtyVertexBuffer(_buffer);
+        }
+    }
+
+    struct CacheByRange<T> where T : IDisposable
+    {
+        private struct Entry
+        {
+            public ICacheKey Key;
+            public T Value;
+
+            public Entry(ICacheKey key, T value)
+            {
+                Key = key;
+                Value = value;
+            }
+        }
+
+        private Dictionary<ulong, List<Entry>> _ranges;
+
+        public void Add(int offset, int size, ICacheKey key, T value)
+        {
+            List<Entry> entries = GetEntries(offset, size);
+
+            entries.Add(new Entry(key, value));
+        }
+
+        public bool TryGetValue(int offset, int size, ICacheKey key, out T value)
+        {
+            List<Entry> 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<Entry> 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<Entry> GetEntries(int offset, int size)
         {
             if (_ranges == null)
             {
-                _ranges = new Dictionary<ulong, T>();
+                _ranges = new Dictionary<ulong, List<Entry>>();
             }
+
+            ulong key = PackRange(offset, size);
+
+            List<Entry> value;
+            if (!_ranges.TryGetValue(key, out value))
+            {
+                value = new List<Entry>();
+                _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 f708f79489..9e37231165 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<Auto<DisposableBuffer>> buffers)
+        {
+            for (int i = 0; i < buffers.Length; i++)
+            {
+                var vkBuffer = buffers[i];
+                int index = first + i;
+
+                ref Auto<DisposableBuffer> 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<DescriptorBufferInfo> 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 5721962dea..0c40aa71b7 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 8465c7444c..2eec92f035 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<int>(),
+                Array.Empty<int>());
+
+            _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<int> 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<int>(bufferHandle, 0, shaderParams);
+
+                _pipeline.SetCommandBuffer(cbs);
+
+                Span<BufferRange> cbRanges = stackalloc BufferRange[1];
+
+                cbRanges[0] = new BufferRange(bufferHandle, 0, ParamsBufferSize);
+
+                _pipeline.SetUniformBuffers(0, cbRanges);
+
+                Span<Auto<DisposableBuffer>> sbRanges = new Auto<DisposableBuffer>[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 30eeafb86e..769d4594ef 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<int> 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<int>(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<DisposableBuffer> 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<Auto<DisposableBuffer>> 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<int> 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 315df1b19a..c09303514e 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<int> 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 4c76caf2d4..ca3a33ef87 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 f874a962e0..b2ee145d18 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 541f3a2580..a064df7a95 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 0000000000..081fc119fa
--- /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<copyCount; i++) {
+        for (int j=0; j<sourceStride; j++) {
+            out_data[dstOffset++] = in_data[srcOffset++];
+        }
+
+        for (int j=0; j<strideRemainder; j++) {
+            out_data[dstOffset++] = uint8_t(0);
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs b/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs
index 03c6b65217..b7064f78c6 100644
--- a/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs
+++ b/Ryujinx.Graphics.Vulkan/Shaders/ShaderBinaries.cs
@@ -2,6 +2,249 @@ namespace Ryujinx.Graphics.Vulkan.Shaders
 {
     static class ShaderBinaries
     {
+        public static readonly byte[] ChangeBufferStrideShaderSource = new byte[]
+        {
+            0x03, 0x02, 0x23, 0x07, 0x00, 0x05, 0x01, 0x00, 0x0A, 0x00, 0x0D, 0x00, 0x8E, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00,
+            0x60, 0x11, 0x00, 0x00, 0x0B, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x4C, 0x53, 0x4C,
+            0x2E, 0x73, 0x74, 0x64, 0x2E, 0x34, 0x35, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x09, 0x00, 0x05, 0x00, 0x00, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x30, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x10, 0x00, 0x06, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0xC2, 0x01, 0x00, 0x00,
+            0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x45, 0x58, 0x54, 0x5F, 0x73, 0x68, 0x61, 0x64, 0x65,
+            0x72, 0x5F, 0x38, 0x62, 0x69, 0x74, 0x5F, 0x73, 0x74, 0x6F, 0x72, 0x61, 0x67, 0x65, 0x00, 0x00,
+            0x04, 0x00, 0x0A, 0x00, 0x47, 0x4C, 0x5F, 0x47, 0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x63, 0x70,
+            0x70, 0x5F, 0x73, 0x74, 0x79, 0x6C, 0x65, 0x5F, 0x6C, 0x69, 0x6E, 0x65, 0x5F, 0x64, 0x69, 0x72,
+            0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x00, 0x04, 0x00, 0x08, 0x00, 0x47, 0x4C, 0x5F, 0x47,
+            0x4F, 0x4F, 0x47, 0x4C, 0x45, 0x5F, 0x69, 0x6E, 0x63, 0x6C, 0x75, 0x64, 0x65, 0x5F, 0x64, 0x69,
+            0x72, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x00, 0x05, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00,
+            0x6D, 0x61, 0x69, 0x6E, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00,
+            0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x72, 0x69, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x07, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x5F, 0x61,
+            0x72, 0x67, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0x00,
+            0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x5F, 0x61,
+            0x72, 0x67, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x73, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00,
+            0x13, 0x00, 0x00, 0x00, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x74, 0x72, 0x69, 0x64, 0x65,
+            0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x17, 0x00, 0x00, 0x00, 0x62, 0x75, 0x66, 0x66,
+            0x65, 0x72, 0x53, 0x69, 0x7A, 0x65, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x1B, 0x00, 0x00, 0x00,
+            0x73, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x06, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x73, 0x74, 0x72, 0x69, 0x64, 0x65, 0x52, 0x65,
+            0x6D, 0x61, 0x69, 0x6E, 0x64, 0x65, 0x72, 0x00, 0x05, 0x00, 0x05, 0x00, 0x23, 0x00, 0x00, 0x00,
+            0x69, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x00, 0x05, 0x00, 0x06, 0x00,
+            0x25, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x70, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72,
+            0x65, 0x64, 0x00, 0x00, 0x05, 0x00, 0x07, 0x00, 0x29, 0x00, 0x00, 0x00, 0x61, 0x6C, 0x6C, 0x49,
+            0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x43, 0x6F, 0x70, 0x69, 0x65, 0x73, 0x00,
+            0x05, 0x00, 0x04, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x08, 0x00, 0x30, 0x00, 0x00, 0x00, 0x67, 0x6C, 0x5F, 0x4C, 0x6F, 0x63, 0x61, 0x6C,
+            0x49, 0x6E, 0x76, 0x6F, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x49, 0x44, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x04, 0x00, 0x35, 0x00, 0x00, 0x00, 0x65, 0x78, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x05, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x63, 0x6F, 0x70, 0x79, 0x43, 0x6F, 0x75, 0x6E,
+            0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x42, 0x00, 0x00, 0x00, 0x73, 0x74, 0x61, 0x72,
+            0x74, 0x43, 0x6F, 0x70, 0x79, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00, 0x4C, 0x00, 0x00, 0x00,
+            0x73, 0x72, 0x63, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x00,
+            0x52, 0x00, 0x00, 0x00, 0x64, 0x73, 0x74, 0x4F, 0x66, 0x66, 0x73, 0x65, 0x74, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x03, 0x00, 0x56, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
+            0x5F, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00, 0x6A, 0x00, 0x00, 0x00,
+            0x6F, 0x75, 0x74, 0x5F, 0x73, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x6A, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x6F, 0x75, 0x74, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x00,
+            0x05, 0x00, 0x03, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x04, 0x00,
+            0x70, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x73, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x05, 0x00,
+            0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x6E, 0x5F, 0x64, 0x61, 0x74, 0x61, 0x00,
+            0x05, 0x00, 0x03, 0x00, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0x00,
+            0x7B, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0A, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
+            0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x30, 0x00, 0x00, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x69, 0x00, 0x00, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x6A, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
+            0x6A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x6C, 0x00, 0x00, 0x00,
+            0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x6C, 0x00, 0x00, 0x00,
+            0x21, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x6F, 0x00, 0x00, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x70, 0x00, 0x00, 0x00,
+            0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00,
+            0x70, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x72, 0x00, 0x00, 0x00,
+            0x22, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x72, 0x00, 0x00, 0x00,
+            0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x8D, 0x00, 0x00, 0x00,
+            0x0B, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00,
+            0x09, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00,
+            0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x0B, 0x00, 0x00, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
+            0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
+            0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
+            0x18, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00,
+            0x1C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x24, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x2E, 0x00, 0x00, 0x00,
+            0x0E, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x2F, 0x00, 0x00, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x2F, 0x00, 0x00, 0x00,
+            0x30, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x31, 0x00, 0x00, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02, 0x00, 0x3A, 0x00, 0x00, 0x00,
+            0x2B, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+            0x15, 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x1D, 0x00, 0x03, 0x00, 0x69, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00,
+            0x6A, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x6B, 0x00, 0x00, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x6B, 0x00, 0x00, 0x00,
+            0x6C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x03, 0x00, 0x6F, 0x00, 0x00, 0x00,
+            0x68, 0x00, 0x00, 0x00, 0x1E, 0x00, 0x03, 0x00, 0x70, 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x71, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00,
+            0x3B, 0x00, 0x04, 0x00, 0x71, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x04, 0x00, 0x75, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00,
+            0x2B, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+            0x2C, 0x00, 0x06, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x8D, 0x00, 0x00, 0x00, 0x8C, 0x00, 0x00, 0x00,
+            0x14, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
+            0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
+            0x05, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x4C, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00,
+            0x07, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
+            0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+            0x08, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x00,
+            0x15, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x13, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+            0x10, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+            0x18, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00,
+            0x19, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x17, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00,
+            0x41, 0x00, 0x06, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
+            0x0D, 0x00, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x1E, 0x00, 0x00, 0x00, 0x1D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x1B, 0x00, 0x00, 0x00,
+            0x1E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+            0x13, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
+            0x08, 0x00, 0x00, 0x00, 0x82, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
+            0x20, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x1F, 0x00, 0x00, 0x00,
+            0x22, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x23, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+            0x87, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00,
+            0x27, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x25, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+            0x87, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
+            0x2B, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x29, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00,
+            0x41, 0x00, 0x05, 0x00, 0x31, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
+            0x0F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
+            0x32, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+            0x33, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+            0x8B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00,
+            0x38, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00,
+            0x36, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0xA9, 0x00, 0x06, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x35, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
+            0x8B, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00,
+            0x47, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
+            0x2D, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x07, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00,
+            0x01, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00,
+            0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00,
+            0x4A, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x42, 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+            0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00,
+            0x4F, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00,
+            0x4D, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x4C, 0x00, 0x00, 0x00,
+            0x51, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
+            0x42, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+            0x13, 0x00, 0x00, 0x00, 0x84, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00,
+            0x53, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x52, 0x00, 0x00, 0x00,
+            0x55, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x56, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+            0xF9, 0x00, 0x02, 0x00, 0x57, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x57, 0x00, 0x00, 0x00,
+            0xF6, 0x00, 0x04, 0x00, 0x59, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0xF9, 0x00, 0x02, 0x00, 0x5B, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x5B, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00,
+            0xB1, 0x00, 0x05, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x5C, 0x00, 0x00, 0x00,
+            0x5D, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00,
+            0x59, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x58, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+            0x5F, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x60, 0x00, 0x00, 0x00,
+            0xF8, 0x00, 0x02, 0x00, 0x60, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x04, 0x00, 0x62, 0x00, 0x00, 0x00,
+            0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00, 0x64, 0x00, 0x00, 0x00,
+            0xF8, 0x00, 0x02, 0x00, 0x64, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x65, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x66, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00, 0x3A, 0x00, 0x00, 0x00,
+            0x67, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0xFA, 0x00, 0x04, 0x00,
+            0x67, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00,
+            0x61, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00,
+            0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00,
+            0x6D, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x52, 0x00, 0x00, 0x00,
+            0x6E, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00,
+            0x4C, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
+            0x73, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x4C, 0x00, 0x00, 0x00,
+            0x74, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x75, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00,
+            0x72, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x68, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00,
+            0x75, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
+            0x6D, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x78, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
+            0xF9, 0x00, 0x02, 0x00, 0x63, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x63, 0x00, 0x00, 0x00,
+            0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00,
+            0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00,
+            0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00,
+            0xF9, 0x00, 0x02, 0x00, 0x60, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x62, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
+            0x7C, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x7C, 0x00, 0x00, 0x00, 0xF6, 0x00, 0x04, 0x00,
+            0x7E, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
+            0x80, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x80, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0xB1, 0x00, 0x05, 0x00,
+            0x3A, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00,
+            0xFA, 0x00, 0x04, 0x00, 0x83, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00,
+            0xF8, 0x00, 0x02, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x84, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x85, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x03, 0x00,
+            0x52, 0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x71, 0x00, 0x04, 0x00, 0x68, 0x00, 0x00, 0x00,
+            0x86, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x41, 0x00, 0x06, 0x00, 0x75, 0x00, 0x00, 0x00,
+            0x87, 0x00, 0x00, 0x00, 0x6C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x87, 0x00, 0x00, 0x00, 0x86, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
+            0x7F, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x89, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
+            0x7C, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x7E, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
+            0x5A, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x04, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x80, 0x00, 0x05, 0x00,
+            0x06, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0x8A, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x00,
+            0x3E, 0x00, 0x03, 0x00, 0x56, 0x00, 0x00, 0x00, 0x8B, 0x00, 0x00, 0x00, 0xF9, 0x00, 0x02, 0x00,
+            0x57, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x02, 0x00, 0x59, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x01, 0x00,
+            0x38, 0x00, 0x01, 0x00
+        };
+
         public static readonly byte[] ColorBlitClearAlphaFragmentShaderSource = new byte[]
         {
             0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x08, 0x00, 0x1B, 0x00, 0x00, 0x00,
diff --git a/Ryujinx.Graphics.Vulkan/VertexBufferState.cs b/Ryujinx.Graphics.Vulkan/VertexBufferState.cs
new file mode 100644
index 0000000000..5710f0b12e
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/VertexBufferState.cs
@@ -0,0 +1,131 @@
+using BufferHandle = Ryujinx.Graphics.GAL.BufferHandle;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+    internal struct VertexBufferState
+    {
+        public static VertexBufferState Null => new VertexBufferState(null, 0, 0, 0);
+
+        private readonly int _offset;
+        private readonly int _size;
+        private readonly int _stride;
+
+        private readonly BufferHandle _handle;
+        private Auto<DisposableBuffer> _buffer;
+
+        internal readonly int DescriptorIndex;
+        internal int AttributeScalarAlignment;
+
+        public VertexBufferState(Auto<DisposableBuffer> 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<DisposableBuffer> 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 889ce7e268..54d98386db 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 b2f69636fa..bacb74cc43 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();