RyuKen/Ryujinx.Graphics.Gpu/Engine/MME/MacroHLE.cs
gdkchan f1d1670b0b
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>
2022-11-16 14:53:04 -03:00

341 lines
12 KiB
C#

using Ryujinx.Common.Logging;
using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gpu.Engine.MME
{
/// <summary>
/// Macro High-level emulation.
/// </summary>
class MacroHLE : IMacroEE
{
private const int ColorLayerCountOffset = 0x818;
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;
/// <summary>
/// Arguments FIFO.
/// </summary>
public Queue<FifoWord> Fifo { get; }
/// <summary>
/// Creates a new instance of the HLE macro handler.
/// </summary>
/// <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)
{
_processor = processor;
_functionName = functionName;
Fifo = new Queue<FifoWord>();
}
/// <summary>
/// Executes a macro program until it exits.
/// </summary>
/// <param name="code">Code of the program to execute</param>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">Optional argument passed to the program, 0 if not used</param>
public void Execute(ReadOnlySpan<int> code, IDeviceState state, int arg0)
{
switch (_functionName)
{
case MacroHLEFunctionName.ClearColor:
ClearColor(state, arg0);
break;
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>
/// Clears one bound color target.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void ClearColor(IDeviceState state, int arg0)
{
int index = (arg0 >> 6) & 0xf;
int layerCount = state.Read(ColorLayerCountOffset + index * ColorStructSize);
_processor.ThreedClass.Clear(arg0, layerCount);
}
/// <summary>
/// Clears the current depth-stencil target.
/// </summary>
/// <param name="state">GPU state at the time of the call</param>
/// <param name="arg0">First argument of the call</param>
private void ClearDepthStencil(IDeviceState state, int arg0)
{
int layerCount = state.Read(ZetaLayerCountOffset);
_processor.ThreedClass.Clear(arg0, layerCount);
}
/// <summary>
/// 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>
private void MultiDrawElementsIndirectCount(IDeviceState state, int arg0)
{
int arg1 = FetchParam().Word;
int arg2 = FetchParam().Word;
int arg3 = FetchParam().Word;
int startDraw = arg0;
int endDraw = arg1;
var topology = (PrimitiveTopology)arg2;
int paddingWords = arg3;
int stride = paddingWords * 4 + 0x14;
ulong parameterBufferGpuVa = FetchParam().GpuVa;
int maxDrawCount = endDraw - startDraw;
if (startDraw != 0)
{
int drawCount = _processor.MemoryManager.Read<int>(parameterBufferGpuVa, tracked: true);
// Calculate maximum draw count based on the previous draw count and current draw count.
if ((uint)drawCount <= (uint)startDraw)
{
// The start draw is past our total draw count, so all draws were already performed.
maxDrawCount = 0;
}
else
{
// Perform just the missing number of draws.
maxDrawCount = (int)Math.Min((uint)maxDrawCount, (uint)(drawCount - startDraw));
}
}
if (maxDrawCount == 0)
{
Fifo.Clear();
return;
}
ulong indirectBufferGpuVa = 0;
int indexCount = 0;
for (int i = 0; i < maxDrawCount; i++)
{
var count = FetchParam();
var instanceCount = FetchParam();
var firstIndex = FetchParam();
var firstVertex = FetchParam();
var firstInstance = FetchParam();
if (i == 0)
{
indirectBufferGpuVa = count.GpuVa;
}
indexCount = Math.Max(indexCount, count.Word + firstIndex.Word);
if (i != maxDrawCount - 1)
{
for (int j = 0; j < paddingWords; j++)
{
FetchParam();
}
}
}
var bufferCache = _processor.MemoryManager.Physical.BufferCache;
ulong indirectBufferSize = (ulong)maxDrawCount * (ulong)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>
/// Fetches a arguments from the arguments FIFO.
/// </summary>
/// <returns>The call argument, or a 0 value with null address if the FIFO is empty</returns>
private FifoWord FetchParam()
{
if (!Fifo.TryDequeue(out var value))
{
Logger.Warning?.Print(LogClass.Gpu, "Macro attempted to fetch an inexistent argument.");
return new FifoWord(0UL, 0);
}
return value;
}
/// <summary>
/// Reads data from a GPU register.
/// </summary>
/// <param name="state">Current GPU state</param>
/// <param name="reg">Register offset to read</param>
/// <returns>GPU register value</returns>
private static int Read(IDeviceState state, int reg)
{
return state.Read(reg * 4);
}
}
}