Support non-index quad draws
Fixes Deltarune
This commit is contained in:
parent
ab1e02c56a
commit
0c562a2c50
2 changed files with 221 additions and 10 deletions
141
src/Ryujinx.Graphics.Metal/IndexBufferPattern.cs
Normal file
141
src/Ryujinx.Graphics.Metal/IndexBufferPattern.cs
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Metal
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
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 readonly MetalRenderer _renderer;
|
||||||
|
private int _currentSize;
|
||||||
|
private BufferHandle _repeatingBuffer;
|
||||||
|
|
||||||
|
public IndexBufferPattern(MetalRenderer renderer,
|
||||||
|
int primitiveVertices,
|
||||||
|
int primitiveVerticesOut,
|
||||||
|
int baseIndex,
|
||||||
|
int[] offsetIndex,
|
||||||
|
int indexStride,
|
||||||
|
bool repeatStart)
|
||||||
|
{
|
||||||
|
PrimitiveVertices = primitiveVertices;
|
||||||
|
PrimitiveVerticesOut = primitiveVerticesOut;
|
||||||
|
BaseIndex = baseIndex;
|
||||||
|
OffsetIndex = offsetIndex;
|
||||||
|
IndexStride = indexStride;
|
||||||
|
RepeatStart = repeatStart;
|
||||||
|
|
||||||
|
_renderer = renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetPrimitiveCount(int vertexCount)
|
||||||
|
{
|
||||||
|
return Math.Max(0, (vertexCount - BaseIndex) / 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 = _renderer.BufferManager.CreateWithHandle(expectedSize * sizeof(int));
|
||||||
|
|
||||||
|
// Copy the old data to the new one.
|
||||||
|
if (_repeatingBuffer != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_renderer.Pipeline.CopyBuffer(_repeatingBuffer, newBuffer, 0, 0, _currentSize * sizeof(int));
|
||||||
|
_renderer.BufferManager.Delete(_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderer.SetBufferData(newBuffer, _currentSize * sizeof(int), MemoryMarshal.Cast<int, byte>(newData));
|
||||||
|
_currentSize = expectedSize;
|
||||||
|
|
||||||
|
return newBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_repeatingBuffer != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
_renderer.BufferManager.Delete(_repeatingBuffer);
|
||||||
|
_repeatingBuffer = BufferHandle.Null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
public readonly Action EndRenderPassDelegate;
|
public readonly Action EndRenderPassDelegate;
|
||||||
public MTLCommandBuffer CommandBuffer;
|
public MTLCommandBuffer CommandBuffer;
|
||||||
|
|
||||||
|
public IndexBufferPattern QuadsToTrisPattern;
|
||||||
|
public IndexBufferPattern TriFanToTrisPattern;
|
||||||
|
|
||||||
internal CommandBufferScoped? PreloadCbs { get; private set; }
|
internal CommandBufferScoped? PreloadCbs { get; private set; }
|
||||||
internal CommandBufferScoped Cbs { get; private set; }
|
internal CommandBufferScoped Cbs { get; private set; }
|
||||||
internal MTLCommandEncoder? CurrentEncoder { get; private set; }
|
internal MTLCommandEncoder? CurrentEncoder { get; private set; }
|
||||||
|
@ -49,6 +52,9 @@ namespace Ryujinx.Graphics.Metal
|
||||||
internal void InitEncoderStateManager(BufferManager bufferManager)
|
internal void InitEncoderStateManager(BufferManager bufferManager)
|
||||||
{
|
{
|
||||||
_encoderStateManager = new EncoderStateManager(_device, bufferManager, this);
|
_encoderStateManager = new EncoderStateManager(_device, bufferManager, this);
|
||||||
|
|
||||||
|
QuadsToTrisPattern = new IndexBufferPattern(_renderer, 4, 6, 0, [0, 1, 2, 0, 2, 3], 4, false);
|
||||||
|
TriFanToTrisPattern = new IndexBufferPattern(_renderer, 3, 3, 2, [int.MinValue, -1, 0], 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveState()
|
public void SaveState()
|
||||||
|
@ -360,25 +366,89 @@ namespace Ryujinx.Graphics.Metal
|
||||||
|
|
||||||
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
|
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
|
||||||
{
|
{
|
||||||
|
if (vertexCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
|
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
|
||||||
|
|
||||||
// TODO: Support topology re-indexing to provide support for TriangleFans
|
if (TopologyUnsupported(_encoderStateManager.Topology))
|
||||||
var primitiveType = _encoderStateManager.Topology.Convert();
|
{
|
||||||
|
var pattern = GetIndexBufferPattern();
|
||||||
|
|
||||||
renderCommandEncoder.DrawPrimitives(
|
BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
|
||||||
primitiveType,
|
var buffer = _renderer.BufferManager.GetBuffer(handle, false);
|
||||||
(ulong)firstVertex,
|
var mtlBuffer = buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value;
|
||||||
(ulong)vertexCount,
|
|
||||||
(ulong)instanceCount,
|
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
|
||||||
(ulong)firstInstance);
|
|
||||||
|
renderCommandEncoder.DrawIndexedPrimitives(
|
||||||
|
primitiveType,
|
||||||
|
(ulong)indexCount,
|
||||||
|
MTLIndexType.UInt32,
|
||||||
|
mtlBuffer,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
|
||||||
|
|
||||||
|
renderCommandEncoder.DrawPrimitives(
|
||||||
|
primitiveType,
|
||||||
|
(ulong)firstVertex,
|
||||||
|
(ulong)vertexCount,
|
||||||
|
(ulong)instanceCount,
|
||||||
|
(ulong)firstInstance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private IndexBufferPattern GetIndexBufferPattern()
|
||||||
|
{
|
||||||
|
return _encoderStateManager.Topology switch
|
||||||
|
{
|
||||||
|
PrimitiveTopology.Quads => QuadsToTrisPattern,
|
||||||
|
PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => TriFanToTrisPattern,
|
||||||
|
_ => throw new NotSupportedException($"Unsupported topology: {_encoderStateManager.Topology}"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrimitiveTopology TopologyRemap(PrimitiveTopology topology)
|
||||||
|
{
|
||||||
|
return topology switch
|
||||||
|
{
|
||||||
|
PrimitiveTopology.Quads => PrimitiveTopology.Triangles,
|
||||||
|
PrimitiveTopology.QuadStrip => PrimitiveTopology.TriangleStrip,
|
||||||
|
PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => PrimitiveTopology.Triangles,
|
||||||
|
_ => topology,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TopologyUnsupported(PrimitiveTopology topology)
|
||||||
|
{
|
||||||
|
return topology switch
|
||||||
|
{
|
||||||
|
PrimitiveTopology.Quads or PrimitiveTopology.TriangleFan or PrimitiveTopology.Polygon => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (indexCount == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
|
var renderCommandEncoder = GetOrCreateRenderEncoder(true);
|
||||||
|
|
||||||
// TODO: Support topology re-indexing to provide support for TriangleFans
|
// TODO: Reindex unsupported topologies
|
||||||
var primitiveType = _encoderStateManager.Topology.Convert();
|
if (TopologyUnsupported(_encoderStateManager.Topology))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Gpu, $"Drawing indexed with unsupported topology: {_encoderStateManager.Topology}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var primitiveType = TopologyRemap(_encoderStateManager.Topology).Convert();
|
||||||
|
|
||||||
var indexBuffer = _encoderStateManager.IndexBuffer;
|
var indexBuffer = _encoderStateManager.IndexBuffer;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue