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
{
///
/// Macro High-level emulation.
///
class MacroHLE : IMacroEE
{
private readonly GPFifoProcessor _processor;
private readonly MacroHLEFunctionName _functionName;
///
/// Arguments FIFO.
///
public Queue Fifo { get; }
///
/// Creates a new instance of the HLE macro handler.
///
/// GPU context the macro is being executed on
/// GPU memory manager
/// 3D engine where this macro is being called
/// Name of the HLE macro function to be called
public MacroHLE(GPFifoProcessor processor, MacroHLEFunctionName functionName)
{
_processor = processor;
_functionName = functionName;
Fifo = new Queue();
}
///
/// Executes a macro program until it exits.
///
/// Code of the program to execute
/// GPU state at the time of the call
/// Optional argument passed to the program, 0 if not used
public void Execute(ReadOnlySpan code, IDeviceState state, int arg0)
{
switch (_functionName)
{
case MacroHLEFunctionName.MultiDrawElementsIndirectCount:
MultiDrawElementsIndirectCount(state, arg0);
break;
default:
throw new NotImplementedException(_functionName.ToString());
}
}
///
/// Performs a indirect multi-draw, with parameters from a GPU buffer.
///
/// GPU state at the time of the call
/// First argument of the call
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(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;
}
int indirectBufferSize = maxDrawCount * stride;
ulong indirectBufferGpuVa = 0;
int indexCount = 0;
for (int i = 0; i < maxDrawCount; i++)
{
var count = FetchParam();
var instanceCount = FetchParam();
var firstIndex = FetchParam();
var baseVertex = FetchParam();
var baseInstance = 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();
}
}
}
// 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);
_processor.ThreedClass.MultiDrawIndirectCount(indexCount, topology, indirectBuffer, parameterBuffer, maxDrawCount, stride);
}
///
/// Fetches a arguments from the arguments FIFO.
///
/// The call argument, or a 0 value with null address if the FIFO is empty
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;
}
///
/// Performs a GPU method call.
///
/// Current GPU state
/// Address, in words, of the method
/// Call argument
private static void Send(IDeviceState state, int methAddr, int value)
{
state.Write(methAddr * 4, value);
}
}
}