From 4c0eb91d7e6bdbe42ffa6e950e3288f8066de089 Mon Sep 17 00:00:00 2001
From: riperiperi <rhy3756547@hotmail.com>
Date: Tue, 20 Sep 2022 22:38:48 +0100
Subject: [PATCH] Convert Quads to Triangles in Vulkan (#3715)

* Add Index Buffer conversion for quads to Vulkan

Also adds a reusable repeating pattern index buffer to use for non-indexed
draws, and generalizes the conversion cache for buffers.

* Fix some issues

* End render pass before conversion

* Resume transform feedback after we ensure we're in a pass.

* Always generate UInt32 type indices for topology conversion

* No it's not.

* Remove unused code

* Rely on TopologyRemap to convert quads to tris.

* Remove double newline

* Ensure render pass ends before stride or I8 conversion
---
 Ryujinx.Graphics.Vulkan/BufferHolder.cs       |  25 ++++
 Ryujinx.Graphics.Vulkan/BufferManager.cs      |  10 ++
 Ryujinx.Graphics.Vulkan/BufferState.cs        |  24 +--
 Ryujinx.Graphics.Vulkan/CacheByRange.cs       |  50 ++++++-
 Ryujinx.Graphics.Vulkan/EnumConversion.cs     |   5 +-
 Ryujinx.Graphics.Vulkan/HelperShader.cs       |  81 ++++++++++
 Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs | 139 ++++++++++++++++++
 Ryujinx.Graphics.Vulkan/IndexBufferState.cs   |  97 ++++++++++++
 Ryujinx.Graphics.Vulkan/PipelineBase.cs       | 116 ++++++++++-----
 Ryujinx.Graphics.Vulkan/PipelineConverter.cs  |   2 +-
 Ryujinx.Graphics.Vulkan/VulkanRenderer.cs     |  19 +++
 11 files changed, 503 insertions(+), 65 deletions(-)
 create mode 100644 Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
 create mode 100644 Ryujinx.Graphics.Vulkan/IndexBufferState.cs

diff --git a/Ryujinx.Graphics.Vulkan/BufferHolder.cs b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
index a2fc0c398f..f449c10261 100644
--- a/Ryujinx.Graphics.Vulkan/BufferHolder.cs
+++ b/Ryujinx.Graphics.Vulkan/BufferHolder.cs
@@ -370,6 +370,7 @@ namespace Ryujinx.Graphics.Vulkan
             {
                 holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
 
+                _gd.PipelineInternal.EndRenderPass();
                 _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
 
                 _cachedConvertedBuffers.Add(offset, size, key, holder);
@@ -388,6 +389,7 @@ namespace Ryujinx.Graphics.Vulkan
 
                 holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
 
+                _gd.PipelineInternal.EndRenderPass();
                 _gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
 
                 key.SetBuffer(holder.GetBuffer());
@@ -398,6 +400,29 @@ namespace Ryujinx.Graphics.Vulkan
             return holder.GetBuffer();
         }
 
+        public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
+        {
+            var key = new TopologyConversionCacheKey(_gd, pattern, indexSize);
+
+            if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
+            {
+                // The destination index size is always I32.
+
+                int indexCount = size / indexSize;
+
+                int convertedCount = pattern.GetConvertedCount(indexCount);
+
+                holder = _gd.BufferManager.Create(_gd, convertedCount * 4);
+
+                _gd.PipelineInternal.EndRenderPass();
+                _gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount);
+
+                _cachedConvertedBuffers.Add(offset, size, key, holder);
+            }
+
+            return holder.GetBuffer();
+        }
+
         public void Dispose()
         {
             _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
diff --git a/Ryujinx.Graphics.Vulkan/BufferManager.cs b/Ryujinx.Graphics.Vulkan/BufferManager.cs
index 43bd9877d9..65883fd0d5 100644
--- a/Ryujinx.Graphics.Vulkan/BufferManager.cs
+++ b/Ryujinx.Graphics.Vulkan/BufferManager.cs
@@ -140,6 +140,16 @@ namespace Ryujinx.Graphics.Vulkan
             return null;
         }
 
+        public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, BufferHandle handle, int offset, int size, IndexBufferPattern pattern, int indexSize)
+        {
+            if (TryGetBuffer(handle, out var holder))
+            {
+                return holder.GetBufferTopologyConversion(cbs, offset, size, pattern, indexSize);
+            }
+
+            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 1790017a36..88858e8633 100644
--- a/Ryujinx.Graphics.Vulkan/BufferState.cs
+++ b/Ryujinx.Graphics.Vulkan/BufferState.cs
@@ -1,5 +1,4 @@
-using Silk.NET.Vulkan;
-using System;
+using System;
 
 namespace Ryujinx.Graphics.Vulkan
 {
@@ -9,38 +8,17 @@ namespace Ryujinx.Graphics.Vulkan
 
         private readonly int _offset;
         private readonly int _size;
-        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;
-            _type = type;
-            buffer?.IncrementReferenceCount();
-        }
-
         public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
         {
             _buffer = buffer;
-
             _offset = offset;
             _size = size;
-            _type = IndexType.Uint16;
             buffer?.IncrementReferenceCount();
         }
 
-        public void BindIndexBuffer(Vk api, CommandBufferScoped cbs)
-        {
-            if (_buffer != null)
-            {
-                api.CmdBindIndexBuffer(cbs.CommandBuffer, _buffer.Get(cbs, _offset, _size).Value, (ulong)_offset, _type);
-            }
-        }
-
         public void BindTransformFeedbackBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding)
         {
             if (_buffer != null)
diff --git a/Ryujinx.Graphics.Vulkan/CacheByRange.cs b/Ryujinx.Graphics.Vulkan/CacheByRange.cs
index f9edca8a2d..4c47e1c177 100644
--- a/Ryujinx.Graphics.Vulkan/CacheByRange.cs
+++ b/Ryujinx.Graphics.Vulkan/CacheByRange.cs
@@ -10,14 +10,25 @@ namespace Ryujinx.Graphics.Vulkan
 
     struct I8ToI16CacheKey : ICacheKey
     {
-        public I8ToI16CacheKey() { }
+        // Used to notify the pipeline that bindings have invalidated on dispose.
+        private readonly VulkanRenderer _gd;
+        private Auto<DisposableBuffer> _buffer;
+
+        public I8ToI16CacheKey(VulkanRenderer gd)
+        {
+            _gd = gd;
+            _buffer = null;
+        }
 
         public bool KeyEqual(ICacheKey other)
         {
             return other is I8ToI16CacheKey;
         }
 
-        public void Dispose() { }
+        public void Dispose()
+        {
+            _gd.PipelineInternal.DirtyIndexBuffer(_buffer);
+        }
     }
 
     struct AlignedVertexBufferCacheKey : ICacheKey
@@ -55,6 +66,41 @@ namespace Ryujinx.Graphics.Vulkan
         }
     }
 
+    struct TopologyConversionCacheKey : ICacheKey
+    {
+        private IndexBufferPattern _pattern;
+        private int _indexSize;
+
+        // Used to notify the pipeline that bindings have invalidated on dispose.
+        private readonly VulkanRenderer _gd;
+        private Auto<DisposableBuffer> _buffer;
+
+        public TopologyConversionCacheKey(VulkanRenderer gd, IndexBufferPattern pattern, int indexSize)
+        {
+            _gd = gd;
+            _pattern = pattern;
+            _indexSize = indexSize;
+            _buffer = null;
+        }
+
+        public bool KeyEqual(ICacheKey other)
+        {
+            return other is TopologyConversionCacheKey entry &&
+                entry._pattern == _pattern &&
+                entry._indexSize == _indexSize;
+        }
+
+        public void SetBuffer(Auto<DisposableBuffer> buffer)
+        {
+            _buffer = buffer;
+        }
+
+        public void Dispose()
+        {
+            _gd.PipelineInternal.DirtyIndexBuffer(_buffer);
+        }
+    }
+
     struct CacheByRange<T> where T : IDisposable
     {
         private struct Entry
diff --git a/Ryujinx.Graphics.Vulkan/EnumConversion.cs b/Ryujinx.Graphics.Vulkan/EnumConversion.cs
index ab40cb10e9..804cd70c4c 100644
--- a/Ryujinx.Graphics.Vulkan/EnumConversion.cs
+++ b/Ryujinx.Graphics.Vulkan/EnumConversion.cs
@@ -2,6 +2,7 @@
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.Shader;
 using Silk.NET.Vulkan;
+using System;
 
 namespace Ryujinx.Graphics.Vulkan
 {
@@ -179,8 +180,8 @@ namespace Ryujinx.Graphics.Vulkan
                 GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
                 GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
                 GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
-                GAL.PrimitiveTopology.Quads => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan, // Emulated with triangle fans
-                GAL.PrimitiveTopology.QuadStrip => Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip, // Emulated with triangle strips
+                GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."),
+                GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."),
                 _ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)
             };
         }
diff --git a/Ryujinx.Graphics.Vulkan/HelperShader.cs b/Ryujinx.Graphics.Vulkan/HelperShader.cs
index 2eec92f035..0201de0ad4 100644
--- a/Ryujinx.Graphics.Vulkan/HelperShader.cs
+++ b/Ryujinx.Graphics.Vulkan/HelperShader.cs
@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader.Translation;
 using Ryujinx.Graphics.Vulkan.Shaders;
 using Silk.NET.Vulkan;
 using System;
+using System.Collections.Generic;
 using VkFormat = Silk.NET.Vulkan.Format;
 
 namespace Ryujinx.Graphics.Vulkan
@@ -399,6 +400,86 @@ namespace Ryujinx.Graphics.Vulkan
                 newSize);
         }
 
+        public unsafe void ConvertIndexBuffer(VulkanRenderer gd,
+            CommandBufferScoped cbs,
+            BufferHolder src,
+            BufferHolder dst,
+            IndexBufferPattern pattern,
+            int indexSize,
+            int srcOffset,
+            int indexCount)
+        {
+            int convertedCount = pattern.GetConvertedCount(indexCount);
+            int outputIndexSize = 4;
+
+            // TODO: Do this with a compute shader?
+            var srcBuffer = src.GetBuffer().Get(cbs, srcOffset, indexCount * indexSize).Value;
+            var dstBuffer = dst.GetBuffer().Get(cbs, 0, convertedCount * outputIndexSize).Value;
+
+            gd.Api.CmdFillBuffer(cbs.CommandBuffer, dstBuffer, 0, Vk.WholeSize, 0);
+
+            var bufferCopy = new List<BufferCopy>();
+            int outputOffset = 0;
+
+            // Try to merge copies of adjacent indices to reduce copy count.
+            int sequenceStart = 0;
+            int sequenceLength = 0;
+
+            foreach (var index in pattern.GetIndexMapping(indexCount))
+            {
+                if (sequenceLength > 0)
+                {
+                    if (index == sequenceStart + sequenceLength && indexSize == outputIndexSize)
+                    {
+                        sequenceLength++;
+                        continue;
+                    }
+
+                    // Commit the copy so far.
+                    bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
+                    outputOffset += outputIndexSize * sequenceLength;
+                }
+
+                sequenceStart = index;
+                sequenceLength = 1;
+            }
+
+            if (sequenceLength > 0)
+            {
+                // Commit final pending copy.
+                bufferCopy.Add(new BufferCopy((ulong)(srcOffset + sequenceStart * indexSize), (ulong)outputOffset, (ulong)(indexSize * sequenceLength)));
+            }
+
+            var bufferCopyArray = bufferCopy.ToArray();
+
+            BufferHolder.InsertBufferBarrier(
+                gd,
+                cbs.CommandBuffer,
+                dstBuffer,
+                BufferHolder.DefaultAccessFlags,
+                AccessFlags.AccessTransferWriteBit,
+                PipelineStageFlags.PipelineStageAllCommandsBit,
+                PipelineStageFlags.PipelineStageTransferBit,
+                0,
+                convertedCount * outputIndexSize);
+
+            fixed (BufferCopy* pBufferCopy = bufferCopyArray)
+            {
+                gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, (uint)bufferCopyArray.Length, pBufferCopy);
+            }
+
+            BufferHolder.InsertBufferBarrier(
+                gd,
+                cbs.CommandBuffer,
+                dstBuffer,
+                AccessFlags.AccessTransferWriteBit,
+                BufferHolder.DefaultAccessFlags,
+                PipelineStageFlags.PipelineStageTransferBit,
+                PipelineStageFlags.PipelineStageAllCommandsBit,
+                0,
+                convertedCount * outputIndexSize);
+        }
+
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)
diff --git a/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs b/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
new file mode 100644
index 0000000000..8439e79dbf
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
@@ -0,0 +1,139 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+    internal class IndexBufferPattern : IDisposable
+    {
+        public int PrimitiveVertices { get; }
+        public int PrimitiveVerticesOut { get; }
+        public int BaseIndex { get; }
+        public int[] OffsetIndex { get; }
+        public int IndexStride { get; }
+        public bool RepeatStart { get; }
+
+        private VulkanRenderer _gd;
+        private int _currentSize;
+        private BufferHandle _repeatingBuffer;
+
+        public IndexBufferPattern(VulkanRenderer gd,
+            int primitiveVertices,
+            int primitiveVerticesOut,
+            int baseIndex,
+            int[] offsetIndex,
+            int indexStride,
+            bool repeatStart)
+        {
+            PrimitiveVertices = primitiveVertices;
+            PrimitiveVerticesOut = primitiveVerticesOut;
+            BaseIndex = baseIndex;
+            OffsetIndex = offsetIndex;
+            IndexStride = indexStride;
+            RepeatStart = repeatStart;
+
+            _gd = gd;
+        }
+
+        public int GetPrimitiveCount(int vertexCount)
+        {
+            return Math.Max(0, ((vertexCount - BaseIndex) + IndexStride - 1) / IndexStride);
+        }
+
+        public int GetConvertedCount(int indexCount)
+        {
+            int primitiveCount = GetPrimitiveCount(indexCount);
+            return primitiveCount * OffsetIndex.Length;
+        }
+
+        public IEnumerable<int> GetIndexMapping(int indexCount)
+        {
+            int primitiveCount = GetPrimitiveCount(indexCount);
+            int index = BaseIndex;
+
+            for (int i = 0; i < primitiveCount; i++)
+            {
+                if (RepeatStart)
+                {
+                    // Used for triangle fan
+                    yield return 0;
+                }
+
+                for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
+                {
+                    yield return index + OffsetIndex[j];
+                }
+
+                index += IndexStride;
+            }
+        }
+
+        public BufferHandle GetRepeatingBuffer(int vertexCount, out int indexCount)
+        {
+            int primitiveCount = GetPrimitiveCount(vertexCount);
+            indexCount = primitiveCount * PrimitiveVerticesOut;
+
+            int expectedSize = primitiveCount * OffsetIndex.Length;
+
+            if (expectedSize <= _currentSize && _repeatingBuffer != BufferHandle.Null)
+            {
+                return _repeatingBuffer;
+            }
+
+            // Expand the repeating pattern to the number of requested primitives.
+            BufferHandle newBuffer = _gd.CreateBuffer(expectedSize * sizeof(int));
+
+            // Copy the old data to the new one.
+            if (_repeatingBuffer != BufferHandle.Null)
+            {
+                _gd.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int));
+                _gd.DeleteBuffer(_repeatingBuffer);
+            }
+
+            _repeatingBuffer = newBuffer;
+
+            // Add the additional repeats on top.
+            int newPrimitives = primitiveCount;
+            int oldPrimitives = (_currentSize) / OffsetIndex.Length;
+
+            int[] newData;
+
+            newPrimitives -= oldPrimitives;
+            newData = new int[expectedSize - _currentSize];
+
+            int outOffset = 0;
+            int index = oldPrimitives * IndexStride + BaseIndex;
+
+            for (int i = 0; i < newPrimitives; i++)
+            {
+                if (RepeatStart)
+                {
+                    // Used for triangle fan
+                    newData[outOffset++] = 0;
+                }
+
+                for (int j = RepeatStart ? 1 : 0; j < OffsetIndex.Length; j++)
+                {
+                    newData[outOffset++] = index + OffsetIndex[j];
+                }
+
+                index += IndexStride;
+            }
+
+            _gd.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast<int, byte>(newData));
+            _currentSize = expectedSize;
+
+            return newBuffer;
+        }
+
+        public void Dispose()
+        {
+            if (_repeatingBuffer != BufferHandle.Null)
+            {
+                _gd.DeleteBuffer(_repeatingBuffer);
+                _repeatingBuffer = BufferHandle.Null;
+            }
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Vulkan/IndexBufferState.cs b/Ryujinx.Graphics.Vulkan/IndexBufferState.cs
new file mode 100644
index 0000000000..1a112d4d71
--- /dev/null
+++ b/Ryujinx.Graphics.Vulkan/IndexBufferState.cs
@@ -0,0 +1,97 @@
+using Silk.NET.Vulkan;
+using System;
+
+namespace Ryujinx.Graphics.Vulkan
+{
+    internal struct IndexBufferState
+    {
+        public static IndexBufferState Null => new IndexBufferState(GAL.BufferHandle.Null, 0, 0);
+
+        private readonly int _offset;
+        private readonly int _size;
+        private readonly IndexType _type;
+
+        private readonly GAL.BufferHandle _handle;
+        private Auto<DisposableBuffer> _buffer;
+
+        public IndexBufferState(GAL.BufferHandle handle, int offset, int size, IndexType type)
+        {
+            _handle = handle;
+            _offset = offset;
+            _size = size;
+            _type = type;
+            _buffer = null;
+        }
+
+        public IndexBufferState(GAL.BufferHandle handle, int offset, int size)
+        {
+            _handle = handle;
+            _offset = offset;
+            _size = size;
+            _type = IndexType.Uint16;
+            _buffer = null;
+        }
+
+        public void BindIndexBuffer(VulkanRenderer gd, CommandBufferScoped cbs)
+        {
+            Auto<DisposableBuffer> autoBuffer;
+            int offset, size;
+            IndexType type = _type;
+
+            if (_type == IndexType.Uint8Ext && !gd.Capabilities.SupportsIndexTypeUint8)
+            {
+                // Index type is not supported. Convert to I16.
+                autoBuffer = gd.BufferManager.GetBufferI8ToI16(cbs, _handle, _offset, _size);
+
+                type = IndexType.Uint16;
+                offset = 0;
+                size = _size * 2;
+            }
+            else
+            {
+                autoBuffer = gd.BufferManager.GetBuffer(cbs.CommandBuffer, _handle, false, out int _);
+
+                offset = _offset;
+                size = _size;
+            }
+
+            _buffer = autoBuffer;
+
+            if (autoBuffer != null)
+            {
+                gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, offset, size).Value, (ulong)offset, type);
+            }
+        }
+
+        public void BindConvertedIndexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, int firstIndex, int indexCount, int convertedCount, IndexBufferPattern pattern)
+        {
+            Auto<DisposableBuffer> autoBuffer;
+
+            // Convert the index buffer using the given pattern.
+            int indexSize = _type switch
+            {
+                IndexType.Uint32 => 4,
+                IndexType.Uint16 => 2,
+                _ => 1,
+            };
+
+            int firstIndexOffset = firstIndex * indexSize;
+
+            autoBuffer = gd.BufferManager.GetBufferTopologyConversion(cbs, _handle, _offset + firstIndexOffset, indexCount * indexSize, pattern, indexSize);
+
+            int size = convertedCount * 4;
+
+            _buffer = autoBuffer;
+
+            if (autoBuffer != null)
+            {
+                gd.Api.CmdBindIndexBuffer(cbs.CommandBuffer, autoBuffer.Get(cbs, 0, size).Value, 0, IndexType.Uint32);
+            }
+        }
+
+        public bool BoundEquals(Auto<DisposableBuffer> buffer)
+        {
+            return _buffer == buffer;
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.Vulkan/PipelineBase.cs b/Ryujinx.Graphics.Vulkan/PipelineBase.cs
index 5c666b09d8..39acc5d9e9 100644
--- a/Ryujinx.Graphics.Vulkan/PipelineBase.cs
+++ b/Ryujinx.Graphics.Vulkan/PipelineBase.cs
@@ -51,13 +51,16 @@ namespace Ryujinx.Graphics.Vulkan
 
         private readonly DescriptorSetUpdater _descriptorSetUpdater;
 
-        private BufferState _indexBuffer;
+        private IndexBufferState _indexBuffer;
+        private IndexBufferPattern _indexBufferPattern;
         private readonly BufferState[] _transformFeedbackBuffers;
         private readonly VertexBufferState[] _vertexBuffers;
         private ulong _vertexBuffersDirty;
         protected Rectangle<int> ClearScissor;
 
         public SupportBufferUpdater SupportBufferUpdater;
+        public IndexBufferPattern QuadsToTrisPattern;
+        public IndexBufferPattern TriFanToTrisPattern;
 
         private bool _needsIndexBufferRebind;
         private bool _needsTransformFeedbackBuffersRebind;
@@ -107,6 +110,9 @@ namespace Ryujinx.Graphics.Vulkan
         {
             SupportBufferUpdater = new SupportBufferUpdater(Gd);
             SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount);
+
+            QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
+            TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
         }
 
         public unsafe void Barrier()
@@ -245,6 +251,14 @@ namespace Ryujinx.Graphics.Vulkan
             }
         }
 
+        public void DirtyIndexBuffer(Auto<DisposableBuffer> buffer)
+        {
+            if (_indexBuffer.BoundEquals(buffer))
+            {
+                _needsIndexBufferRebind = true;
+            }
+        }
+
         public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
         {
             if (!_program.IsLinked)
@@ -267,24 +281,59 @@ namespace Ryujinx.Graphics.Vulkan
 
             RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
             BeginRenderPass();
-            ResumeTransformFeedbackInternal();
             DrawCount++;
 
-            if (_topology == GAL.PrimitiveTopology.Quads)
+            if (Gd.TopologyUnsupported(_topology))
             {
-                int quadsCount = vertexCount / 4;
+                // Temporarily bind a conversion pattern as an index buffer.
+                _needsIndexBufferRebind = true;
 
-                for (int i = 0; i < quadsCount; i++)
+                IndexBufferPattern pattern = _topology switch
                 {
-                    Gd.Api.CmdDraw(CommandBuffer, 4, (uint)instanceCount, (uint)(firstVertex + i * 4), (uint)firstInstance);
-                }
+                    GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
+                    GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern,
+                    _ => throw new NotSupportedException($"Unsupported topology: {_topology}")
+                };
+
+                BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
+                var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, handle, false);
+
+                Gd.Api.CmdBindIndexBuffer(CommandBuffer, buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value, 0, Silk.NET.Vulkan.IndexType.Uint32);
+
+                BeginRenderPass(); // May have been interrupted to set buffer data.
+                ResumeTransformFeedbackInternal();
+
+                Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
             }
             else
             {
+                ResumeTransformFeedbackInternal();
+
                 Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance);
             }
         }
 
+        private void UpdateIndexBufferPattern()
+        {
+            IndexBufferPattern pattern = null;
+
+            if (Gd.TopologyUnsupported(_topology))
+            {
+                pattern = _topology switch
+                {
+                    GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
+                    GAL.PrimitiveTopology.TriangleFan => TriFanToTrisPattern,
+                    _ => throw new NotSupportedException($"Unsupported topology: {_topology}")
+                };
+            }
+
+            if (_indexBufferPattern != pattern)
+            {
+                _indexBufferPattern = pattern;
+                _needsIndexBufferRebind = true;
+            }
+        }
+
         public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
         {
             if (!_program.IsLinked)
@@ -292,22 +341,34 @@ namespace Ryujinx.Graphics.Vulkan
                 return;
             }
 
+            UpdateIndexBufferPattern();
             RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
             BeginRenderPass();
-            ResumeTransformFeedbackInternal();
             DrawCount++;
 
-            if (_topology == GAL.PrimitiveTopology.Quads)
+            if (_indexBufferPattern != null)
             {
-                int quadsCount = indexCount / 4;
+                // Convert the index buffer into a supported topology.
+                IndexBufferPattern pattern = _indexBufferPattern;
 
-                for (int i = 0; i < quadsCount; i++)
+                int convertedCount = pattern.GetConvertedCount(indexCount);
+
+                if (_needsIndexBufferRebind)
                 {
-                    Gd.Api.CmdDrawIndexed(CommandBuffer, 4, (uint)instanceCount, (uint)(firstIndex + i * 4), firstVertex, (uint)firstInstance);
+                    _indexBuffer.BindConvertedIndexBuffer(Gd, Cbs, firstIndex, indexCount, convertedCount, pattern);
+
+                    _needsIndexBufferRebind = false;
                 }
+
+                BeginRenderPass(); // May have been interrupted to set buffer data.
+                ResumeTransformFeedbackInternal();
+
+                Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)convertedCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
             }
             else
             {
+                ResumeTransformFeedbackInternal();
+
                 Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance);
             }
         }
@@ -500,34 +561,16 @@ namespace Ryujinx.Graphics.Vulkan
 
         public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type)
         {
-            _indexBuffer.Dispose();
-
             if (buffer.Handle != BufferHandle.Null)
             {
-                Auto<DisposableBuffer> ib = null;
-                int offset = buffer.Offset;
-                int size = buffer.Size;
-
-                if (type == GAL.IndexType.UByte && !Gd.Capabilities.SupportsIndexTypeUint8)
-                {
-                    ib = Gd.BufferManager.GetBufferI8ToI16(Cbs, buffer.Handle, offset, size);
-                    offset = 0;
-                    size *= 2;
-                    type = GAL.IndexType.UShort;
-                }
-                else
-                {
-                    ib = Gd.BufferManager.GetBuffer(CommandBuffer, buffer.Handle, false);
-                }
-
-                _indexBuffer = new BufferState(ib, offset, size, type.Convert());
+                _indexBuffer = new IndexBufferState(buffer.Handle, buffer.Offset, buffer.Size, type.Convert());
             }
             else
             {
-                _indexBuffer = BufferState.Null;
+                _indexBuffer = IndexBufferState.Null;
             }
 
-            _indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
+            _needsIndexBufferRebind = true;
         }
 
         public void SetLineParameters(float width, bool smooth)
@@ -584,7 +627,7 @@ namespace Ryujinx.Graphics.Vulkan
         {
             _topology = topology;
 
-            var vkTopology = topology.Convert();
+            var vkTopology = Gd.TopologyRemap(topology).Convert();
 
             _newState.Topology = vkTopology;
 
@@ -1127,9 +1170,9 @@ namespace Ryujinx.Graphics.Vulkan
             // Commit changes to the support buffer before drawing.
             SupportBufferUpdater.Commit();
 
-            if (_needsIndexBufferRebind)
+            if (_needsIndexBufferRebind && _indexBufferPattern == null)
             {
-                _indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
+                _indexBuffer.BindIndexBuffer(Gd, Cbs);
                 _needsIndexBufferRebind = false;
             }
 
@@ -1265,7 +1308,6 @@ namespace Ryujinx.Graphics.Vulkan
             {
                 _renderPass?.Dispose();
                 _framebuffer?.Dispose();
-                _indexBuffer.Dispose();
                 _newState.Dispose();
                 _descriptorSetUpdater.Dispose();
 
diff --git a/Ryujinx.Graphics.Vulkan/PipelineConverter.cs b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
index c09303514e..3ff111e877 100644
--- a/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
+++ b/Ryujinx.Graphics.Vulkan/PipelineConverter.cs
@@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Vulkan
 
             pipeline.StencilTestEnable = state.StencilTest.TestEnable;
 
-            pipeline.Topology = state.Topology.Convert();
+            pipeline.Topology = gd.TopologyRemap(state.Topology).Convert();
 
             int vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
             int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
diff --git a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
index 3776be9eed..d92fff49ec 100644
--- a/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
+++ b/Ryujinx.Graphics.Vulkan/VulkanRenderer.cs
@@ -471,6 +471,25 @@ namespace Ryujinx.Graphics.Vulkan
             Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
         }
 
+        public GAL.PrimitiveTopology TopologyRemap(GAL.PrimitiveTopology topology)
+        {
+            return topology switch
+            {
+                GAL.PrimitiveTopology.Quads => GAL.PrimitiveTopology.Triangles,
+                GAL.PrimitiveTopology.QuadStrip => GAL.PrimitiveTopology.TriangleStrip,
+                _ => topology
+            };
+        }
+
+        public bool TopologyUnsupported(GAL.PrimitiveTopology topology)
+        {
+            return topology switch
+            {
+                GAL.PrimitiveTopology.Quads => true,
+                _ => false
+            };
+        }
+
         public void Initialize(GraphicsDebugLevel logLevel)
         {
             SetupContext(logLevel);