From 4c0eb91d7e6bdbe42ffa6e950e3288f8066de089 Mon Sep 17 00:00:00 2001 From: riperiperi 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 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 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 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 _buffer; - public BufferState(Auto buffer, int offset, int size, IndexType type) - { - _buffer = buffer; - - _offset = offset; - _size = size; - _type = type; - buffer?.IncrementReferenceCount(); - } - public BufferState(Auto 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 _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 _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 buffer) + { + _buffer = buffer; + } + + public void Dispose() + { + _gd.PipelineInternal.DirtyIndexBuffer(_buffer); + } + } + struct CacheByRange 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(); + 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 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(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 _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 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 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 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 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 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 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);