Implement HLE macro for DrawElementsIndirect (#3748)

* Implement HLE macro for DrawElementsIndirect

* Shader cache version bump

* Use GL_ARB_shader_draw_parameters extension on OpenGL

* Fix DrawIndexedIndirectCount on Vulkan when extension is not supported

* Implement DrawIndex

* Alignment

* Fix some validation errors

* Rename BaseIds to DrawParameters

* Fix incorrect index buffer and vertex buffer size in some cases

* Add HLE macros for DrawArraysInstanced and DrawElementsInstanced

* Perform a regular draw when indirect data is not modified

* Use non-indirect draw methods if indirect buffer was not GPU modified

* Only check if draw parameters match if the shader actually uses them

* Expose Macro HLE setting on GUI

* Reset FirstVertex and FirstInstance after draw

* Update shader cache version again since some people already tested this

* PR feedback

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
This commit is contained in:
gdkchan 2022-11-16 14:53:04 -03:00 committed by GitHub
parent b8de72de8f
commit f1d1670b0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 2336 additions and 277 deletions

View file

@ -62,10 +62,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
}
}
if (_hleFunction == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
// We don't consume the parameter buffer value, so we don't need to flush it.
// Doing so improves performance if the value was written by a GPU shader.
if (_hleFunction == MacroHLEFunctionName.DrawElementsIndirect)
{
context.GPFifo.SetFlushSkips(1);
}
else if (_hleFunction == MacroHLEFunctionName.MultiDrawElementsIndirectCount)
{
// We don't consume the parameter buffer value, so we don't need to flush it.
// Doing so improves performance if the value was written by a GPU shader.
context.GPFifo.SetFlushSkips(2);
}
}

View file

@ -16,6 +16,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
private const int ColorStructSize = 0x40;
private const int ZetaLayerCountOffset = 0x1230;
private const int IndirectDataEntrySize = 0x10;
private const int IndirectIndexedDataEntrySize = 0x14;
private readonly GPFifoProcessor _processor;
private readonly MacroHLEFunctionName _functionName;
@ -27,9 +30,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
/// <summary>
/// Creates a new instance of the HLE macro handler.
/// </summary>
/// <param name="context">GPU context the macro is being executed on</param>
/// <param name="memoryManager">GPU memory manager</param>
/// <param name="engine">3D engine where this macro is being called</param>
/// <param name="processor">GPU GP FIFO command processor</param>
/// <param name="functionName">Name of the HLE macro function to be called</param>
public MacroHLE(GPFifoProcessor processor, MacroHLEFunctionName functionName)
{
@ -55,12 +56,24 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
case MacroHLEFunctionName.ClearDepthStencil:
ClearDepthStencil(state, arg0);
break;
case MacroHLEFunctionName.DrawArraysInstanced:
DrawArraysInstanced(state, arg0);
break;
case MacroHLEFunctionName.DrawElementsInstanced:
DrawElementsInstanced(state, arg0);
break;
case MacroHLEFunctionName.DrawElementsIndirect:
DrawElementsIndirect(state, arg0);
break;
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
MultiDrawElementsIndirectCount(state, arg0);
break;
default:
throw new NotImplementedException(_functionName.ToString());
}
// It should be empty at this point, but clear it just to be safe.
Fifo.Clear();
}
/// <summary>
@ -89,7 +102,118 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
}
/// <summary>
/// Performs a indirect multi-draw, with parameters from a GPU buffer.
/// Performs a draw.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void DrawArraysInstanced(IDeviceState state, int arg0)
{
var topology = (PrimitiveTopology)arg0;
var count = FetchParam();
var instanceCount = FetchParam();
var firstVertex = FetchParam();
var firstInstance = FetchParam();
if (ShouldSkipDraw(state, instanceCount.Word))
{
return;
}
_processor.ThreedClass.Draw(
topology,
count.Word,
instanceCount.Word,
0,
firstVertex.Word,
firstInstance.Word,
indexed: false);
}
/// <summary>
/// Performs a indexed draw.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void DrawElementsInstanced(IDeviceState state, int arg0)
{
var topology = (PrimitiveTopology)arg0;
var count = FetchParam();
var instanceCount = FetchParam();
var firstIndex = FetchParam();
var firstVertex = FetchParam();
var firstInstance = FetchParam();
if (ShouldSkipDraw(state, instanceCount.Word))
{
return;
}
_processor.ThreedClass.Draw(
topology,
count.Word,
instanceCount.Word,
firstIndex.Word,
firstVertex.Word,
firstInstance.Word,
indexed: true);
}
/// <summary>
/// Performs a indirect indexed draw, with parameters from a GPU buffer.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void DrawElementsIndirect(IDeviceState state, int arg0)
{
var topology = (PrimitiveTopology)arg0;
var count = FetchParam();
var instanceCount = FetchParam();
var firstIndex = FetchParam();
var firstVertex = FetchParam();
var firstInstance = FetchParam();
ulong indirectBufferGpuVa = count.GpuVa;
var bufferCache = _processor.MemoryManager.Physical.BufferCache;
bool useBuffer = bufferCache.CheckModified(_processor.MemoryManager, indirectBufferGpuVa, IndirectIndexedDataEntrySize, out ulong indirectBufferAddress);
if (useBuffer)
{
int indexCount = firstIndex.Word + count.Word;
_processor.ThreedClass.DrawIndirect(
topology,
indirectBufferAddress,
0,
1,
IndirectIndexedDataEntrySize,
indexCount,
Threed.IndirectDrawType.DrawIndexedIndirect);
}
else
{
if (ShouldSkipDraw(state, instanceCount.Word))
{
return;
}
_processor.ThreedClass.Draw(
topology,
count.Word,
instanceCount.Word,
firstIndex.Word,
firstVertex.Word,
firstInstance.Word,
indexed: true);
}
}
/// <summary>
/// Performs a indirect indexed multi-draw, with parameters from a GPU buffer.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
@ -132,8 +256,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
return;
}
int indirectBufferSize = maxDrawCount * stride;
ulong indirectBufferGpuVa = 0;
int indexCount = 0;
@ -142,8 +264,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
var count = FetchParam();
var instanceCount = FetchParam();
var firstIndex = FetchParam();
var baseVertex = FetchParam();
var baseInstance = FetchParam();
var firstVertex = FetchParam();
var firstInstance = FetchParam();
if (i == 0)
{
@ -161,15 +283,32 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
}
}
// It should be empty at this point, but clear it just to be safe.
Fifo.Clear();
var bufferCache = _processor.MemoryManager.Physical.BufferCache;
var parameterBuffer = bufferCache.GetGpuBufferRange(_processor.MemoryManager, parameterBufferGpuVa, 4);
var indirectBuffer = bufferCache.GetGpuBufferRange(_processor.MemoryManager, indirectBufferGpuVa, (ulong)indirectBufferSize);
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)stride;
_processor.ThreedClass.MultiDrawIndirectCount(indexCount, topology, indirectBuffer, parameterBuffer, maxDrawCount, stride);
ulong indirectBufferAddress = bufferCache.TranslateAndCreateBuffer(_processor.MemoryManager, indirectBufferGpuVa, indirectBufferSize);
ulong parameterBufferAddress = bufferCache.TranslateAndCreateBuffer(_processor.MemoryManager, parameterBufferGpuVa, 4);
_processor.ThreedClass.DrawIndirect(
topology,
indirectBufferAddress,
parameterBufferAddress,
maxDrawCount,
stride,
indexCount,
Threed.IndirectDrawType.DrawIndexedIndirectCount);
}
/// <summary>
/// Checks if the draw should be skipped, because the masked instance count is zero.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="instanceCount">Draw instance count</param>
/// <returns>True if the draw should be skipped, false otherwise</returns>
private static bool ShouldSkipDraw(IDeviceState state, int instanceCount)
{
return (Read(state, 0xd1b) & instanceCount) == 0;
}
/// <summary>
@ -189,14 +328,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
}
/// <summary>
/// Performs a GPU method call.
/// Reads data from a GPU register.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="methAddr">Address, in words, of the method</param>
/// <param name="value">Call argument</param>
private static void Send(IDeviceState state, int methAddr, int value)
/// <param name="reg">Register offset to read</param>
/// <returns>GPU register value</returns>
private static int Read(IDeviceState state, int reg)
{
state.Write(methAddr * 4, value);
return state.Read(reg * 4);
}
}
}

View file

@ -8,6 +8,9 @@
None,
ClearColor,
ClearDepthStencil,
DrawArraysInstanced,
DrawElementsInstanced,
DrawElementsIndirect,
MultiDrawElementsIndirectCount
}
}

View file

@ -44,17 +44,29 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
}
}
private static readonly TableEntry[] Table = new TableEntry[]
private static readonly TableEntry[] _table = new TableEntry[]
{
new TableEntry(MacroHLEFunctionName.ClearColor, new Hash128(0xA9FB28D1DC43645A, 0xB177E5D2EAE67FB0), 0x28),
new TableEntry(MacroHLEFunctionName.ClearDepthStencil, new Hash128(0x1B96CB77D4879F4F, 0x8557032FE0C965FB), 0x24),
new TableEntry(MacroHLEFunctionName.DrawArraysInstanced, new Hash128(0x197FB416269DBC26, 0x34288C01DDA82202), 0x48),
new TableEntry(MacroHLEFunctionName.DrawElementsInstanced, new Hash128(0x1A501FD3D54EC8E0, 0x6CF570CF79DA74D6), 0x5c),
new TableEntry(MacroHLEFunctionName.DrawElementsIndirect, new Hash128(0x86A3E8E903AF8F45, 0xD35BBA07C23860A4), 0x7c),
new TableEntry(MacroHLEFunctionName.MultiDrawElementsIndirectCount, new Hash128(0x890AF57ED3FB1C37, 0x35D0C95C61F5386F), 0x19C)
};
/// <summary>
/// Checks if the host supports all features required by the HLE macro.
/// </summary>
/// <param name="caps">Host capabilities</param>
/// <param name="name">Name of the HLE macro to be checked</param>
/// <returns>True if the host supports the HLE macro, false otherwise</returns>
private static bool IsMacroHLESupported(Capabilities caps, MacroHLEFunctionName name)
{
if (name == MacroHLEFunctionName.ClearColor ||
name == MacroHLEFunctionName.ClearDepthStencil)
name == MacroHLEFunctionName.ClearDepthStencil ||
name == MacroHLEFunctionName.DrawArraysInstanced ||
name == MacroHLEFunctionName.DrawElementsInstanced ||
name == MacroHLEFunctionName.DrawElementsIndirect)
{
return true;
}
@ -77,15 +89,20 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
{
var mc = MemoryMarshal.Cast<int, byte>(code);
for (int i = 0; i < Table.Length; i++)
for (int i = 0; i < _table.Length; i++)
{
ref var entry = ref Table[i];
ref var entry = ref _table[i];
var hash = XXHash128.ComputeHash(mc.Slice(0, entry.Length));
if (hash == entry.Hash)
{
name = entry.Name;
return IsMacroHLESupported(caps, name);
if (IsMacroHLESupported(caps, entry.Name))
{
name = entry.Name;
return true;
}
break;
}
}