forked from Mirror/Ryujinx
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
This commit is contained in:
parent
da75a9a6ea
commit
4c0eb91d7e
11 changed files with 503 additions and 65 deletions
|
@ -370,6 +370,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
|
holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
|
||||||
|
|
||||||
|
_gd.PipelineInternal.EndRenderPass();
|
||||||
_gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
|
_gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
|
||||||
|
|
||||||
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
_cachedConvertedBuffers.Add(offset, size, key, holder);
|
||||||
|
@ -388,6 +389,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
|
holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
|
||||||
|
|
||||||
|
_gd.PipelineInternal.EndRenderPass();
|
||||||
_gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
|
_gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
|
||||||
|
|
||||||
key.SetBuffer(holder.GetBuffer());
|
key.SetBuffer(holder.GetBuffer());
|
||||||
|
@ -398,6 +400,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return holder.GetBuffer();
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||||
|
|
|
@ -140,6 +140,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return null;
|
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)
|
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, out int size)
|
||||||
{
|
{
|
||||||
if (TryGetBuffer(handle, out var holder))
|
if (TryGetBuffer(handle, out var holder))
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using Silk.NET.Vulkan;
|
using System;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
@ -9,38 +8,17 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private readonly int _offset;
|
private readonly int _offset;
|
||||||
private readonly int _size;
|
private readonly int _size;
|
||||||
private readonly IndexType _type;
|
|
||||||
|
|
||||||
private readonly Auto<DisposableBuffer> _buffer;
|
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)
|
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
_buffer = buffer;
|
_buffer = buffer;
|
||||||
|
|
||||||
_offset = offset;
|
_offset = offset;
|
||||||
_size = size;
|
_size = size;
|
||||||
_type = IndexType.Uint16;
|
|
||||||
buffer?.IncrementReferenceCount();
|
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)
|
public void BindTransformFeedbackBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding)
|
||||||
{
|
{
|
||||||
if (_buffer != null)
|
if (_buffer != null)
|
||||||
|
|
|
@ -10,14 +10,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
struct I8ToI16CacheKey : ICacheKey
|
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)
|
public bool KeyEqual(ICacheKey other)
|
||||||
{
|
{
|
||||||
return other is I8ToI16CacheKey;
|
return other is I8ToI16CacheKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() { }
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_gd.PipelineInternal.DirtyIndexBuffer(_buffer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AlignedVertexBufferCacheKey : ICacheKey
|
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
|
struct CacheByRange<T> where T : IDisposable
|
||||||
{
|
{
|
||||||
private struct Entry
|
private struct Entry
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
|
@ -179,8 +180,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
|
GAL.PrimitiveTopology.TrianglesAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleListWithAdjacency,
|
||||||
GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
|
GAL.PrimitiveTopology.TriangleStripAdjacency => Silk.NET.Vulkan.PrimitiveTopology.TriangleStripWithAdjacency,
|
||||||
GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
|
GAL.PrimitiveTopology.Patches => Silk.NET.Vulkan.PrimitiveTopology.PatchList,
|
||||||
GAL.PrimitiveTopology.Quads => Silk.NET.Vulkan.PrimitiveTopology.TriangleFan, // Emulated with triangle fans
|
GAL.PrimitiveTopology.Quads => throw new NotSupportedException("Quad topology is not available in Vulkan."),
|
||||||
GAL.PrimitiveTopology.QuadStrip => Silk.NET.Vulkan.PrimitiveTopology.TriangleStrip, // Emulated with triangle strips
|
GAL.PrimitiveTopology.QuadStrip => throw new NotSupportedException("QuadStrip topology is not available in Vulkan."),
|
||||||
_ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)
|
_ => LogInvalidAndReturn(topology, nameof(GAL.PrimitiveTopology), Silk.NET.Vulkan.PrimitiveTopology.TriangleList)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||||
using Ryujinx.Graphics.Vulkan.Shaders;
|
using Ryujinx.Graphics.Vulkan.Shaders;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
@ -399,6 +400,86 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
newSize);
|
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)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
|
139
Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
Normal file
139
Ryujinx.Graphics.Vulkan/IndexBufferPattern.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
Ryujinx.Graphics.Vulkan/IndexBufferState.cs
Normal file
97
Ryujinx.Graphics.Vulkan/IndexBufferState.cs
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,13 +51,16 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
||||||
|
|
||||||
private BufferState _indexBuffer;
|
private IndexBufferState _indexBuffer;
|
||||||
|
private IndexBufferPattern _indexBufferPattern;
|
||||||
private readonly BufferState[] _transformFeedbackBuffers;
|
private readonly BufferState[] _transformFeedbackBuffers;
|
||||||
private readonly VertexBufferState[] _vertexBuffers;
|
private readonly VertexBufferState[] _vertexBuffers;
|
||||||
private ulong _vertexBuffersDirty;
|
private ulong _vertexBuffersDirty;
|
||||||
protected Rectangle<int> ClearScissor;
|
protected Rectangle<int> ClearScissor;
|
||||||
|
|
||||||
public SupportBufferUpdater SupportBufferUpdater;
|
public SupportBufferUpdater SupportBufferUpdater;
|
||||||
|
public IndexBufferPattern QuadsToTrisPattern;
|
||||||
|
public IndexBufferPattern TriFanToTrisPattern;
|
||||||
|
|
||||||
private bool _needsIndexBufferRebind;
|
private bool _needsIndexBufferRebind;
|
||||||
private bool _needsTransformFeedbackBuffersRebind;
|
private bool _needsTransformFeedbackBuffersRebind;
|
||||||
|
@ -107,6 +110,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
SupportBufferUpdater = new SupportBufferUpdater(Gd);
|
SupportBufferUpdater = new SupportBufferUpdater(Gd);
|
||||||
SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount);
|
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()
|
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)
|
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
|
||||||
{
|
{
|
||||||
if (!_program.IsLinked)
|
if (!_program.IsLinked)
|
||||||
|
@ -267,24 +281,59 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||||
BeginRenderPass();
|
BeginRenderPass();
|
||||||
ResumeTransformFeedbackInternal();
|
|
||||||
DrawCount++;
|
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
|
else
|
||||||
{
|
{
|
||||||
|
ResumeTransformFeedbackInternal();
|
||||||
|
|
||||||
Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance);
|
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)
|
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
|
||||||
{
|
{
|
||||||
if (!_program.IsLinked)
|
if (!_program.IsLinked)
|
||||||
|
@ -292,22 +341,34 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateIndexBufferPattern();
|
||||||
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
||||||
BeginRenderPass();
|
BeginRenderPass();
|
||||||
ResumeTransformFeedbackInternal();
|
|
||||||
DrawCount++;
|
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
|
else
|
||||||
{
|
{
|
||||||
|
ResumeTransformFeedbackInternal();
|
||||||
|
|
||||||
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance);
|
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)
|
public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type)
|
||||||
{
|
{
|
||||||
_indexBuffer.Dispose();
|
|
||||||
|
|
||||||
if (buffer.Handle != BufferHandle.Null)
|
if (buffer.Handle != BufferHandle.Null)
|
||||||
{
|
{
|
||||||
Auto<DisposableBuffer> ib = null;
|
_indexBuffer = new IndexBufferState(buffer.Handle, buffer.Offset, buffer.Size, type.Convert());
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_indexBuffer = BufferState.Null;
|
_indexBuffer = IndexBufferState.Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
|
_needsIndexBufferRebind = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLineParameters(float width, bool smooth)
|
public void SetLineParameters(float width, bool smooth)
|
||||||
|
@ -584,7 +627,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
_topology = topology;
|
_topology = topology;
|
||||||
|
|
||||||
var vkTopology = topology.Convert();
|
var vkTopology = Gd.TopologyRemap(topology).Convert();
|
||||||
|
|
||||||
_newState.Topology = vkTopology;
|
_newState.Topology = vkTopology;
|
||||||
|
|
||||||
|
@ -1127,9 +1170,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
// Commit changes to the support buffer before drawing.
|
// Commit changes to the support buffer before drawing.
|
||||||
SupportBufferUpdater.Commit();
|
SupportBufferUpdater.Commit();
|
||||||
|
|
||||||
if (_needsIndexBufferRebind)
|
if (_needsIndexBufferRebind && _indexBufferPattern == null)
|
||||||
{
|
{
|
||||||
_indexBuffer.BindIndexBuffer(Gd.Api, Cbs);
|
_indexBuffer.BindIndexBuffer(Gd, Cbs);
|
||||||
_needsIndexBufferRebind = false;
|
_needsIndexBufferRebind = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1265,7 +1308,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
_renderPass?.Dispose();
|
_renderPass?.Dispose();
|
||||||
_framebuffer?.Dispose();
|
_framebuffer?.Dispose();
|
||||||
_indexBuffer.Dispose();
|
|
||||||
_newState.Dispose();
|
_newState.Dispose();
|
||||||
_descriptorSetUpdater.Dispose();
|
_descriptorSetUpdater.Dispose();
|
||||||
|
|
||||||
|
|
|
@ -199,7 +199,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
pipeline.StencilTestEnable = state.StencilTest.TestEnable;
|
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 vaCount = Math.Min(Constants.MaxVertexAttributes, state.VertexAttribCount);
|
||||||
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
|
int vbCount = Math.Min(Constants.MaxVertexBuffers, state.VertexBufferCount);
|
||||||
|
|
|
@ -471,6 +471,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
Logger.Notice.Print(LogClass.Gpu, $"{GpuVendor} {GpuRenderer} ({GpuVersion})");
|
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)
|
public void Initialize(GraphicsDebugLevel logLevel)
|
||||||
{
|
{
|
||||||
SetupContext(logLevel);
|
SetupContext(logLevel);
|
||||||
|
|
Reference in a new issue