using Ryujinx.Graphics.Device;
using Ryujinx.Graphics.Gpu.Engine.MME;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
{
///
/// Represents a GPU General Purpose FIFO class.
///
class GPFifoClass : IDeviceState
{
private readonly GpuContext _context;
private readonly GPFifoProcessor _parent;
private readonly DeviceState _state;
private bool _createSyncPending;
private const int MacrosCount = 0x80;
// Note: The size of the macro memory is unknown, we just make
// a guess here and use 256kb as the size. Increase if needed.
private const int MacroCodeSize = 256 * 256;
private readonly Macro[] _macros;
private readonly int[] _macroCode;
///
/// Creates a new instance of the GPU General Purpose FIFO class.
///
/// GPU context
/// Parent GPU General Purpose FIFO processor
public GPFifoClass(GpuContext context, GPFifoProcessor parent)
{
_context = context;
_parent = parent;
_state = new DeviceState(new Dictionary
{
{ nameof(GPFifoClassState.Semaphored), new RwCallback(Semaphored, null) },
{ nameof(GPFifoClassState.Syncpointb), new RwCallback(Syncpointb, null) },
{ nameof(GPFifoClassState.WaitForIdle), new RwCallback(WaitForIdle, null) },
{ nameof(GPFifoClassState.SetReference), new RwCallback(SetReference, null) },
{ nameof(GPFifoClassState.LoadMmeInstructionRam), new RwCallback(LoadMmeInstructionRam, null) },
{ nameof(GPFifoClassState.LoadMmeStartAddressRam), new RwCallback(LoadMmeStartAddressRam, null) },
{ nameof(GPFifoClassState.SetMmeShadowRamControl), new RwCallback(SetMmeShadowRamControl, null) },
});
_macros = new Macro[MacrosCount];
_macroCode = new int[MacroCodeSize];
}
///
/// Create any syncs from WaitForIdle command that are currently pending.
///
public void CreatePendingSyncs()
{
if (_createSyncPending)
{
_createSyncPending = false;
_context.CreateHostSyncIfNeeded(HostSyncFlags.None);
}
}
///
/// Reads data from the class registers.
///
/// Register byte offset
/// Data at the specified offset
public int Read(int offset) => _state.Read(offset);
///
/// Writes data to the class registers.
///
/// Register byte offset
/// Data to be written
public void Write(int offset, int data) => _state.Write(offset, data);
///
/// Writes a GPU counter to guest memory.
///
/// Method call argument
public void Semaphored(int argument)
{
ulong address = ((ulong)_state.State.SemaphorebOffsetLower << 2) |
((ulong)_state.State.SemaphoreaOffsetUpper << 32);
int value = _state.State.SemaphorecPayload;
SemaphoredOperation operation = _state.State.SemaphoredOperation;
if (_state.State.SemaphoredReleaseSize == SemaphoredReleaseSize.SixteenBytes)
{
_parent.MemoryManager.Write(address + 4, 0);
_parent.MemoryManager.Write(address + 8, _context.GetTimestamp());
}
// TODO: Acquire operations (Wait), interrupts for invalid combinations.
if (operation == SemaphoredOperation.Release)
{
_parent.MemoryManager.Write(address, value);
}
else if (operation == SemaphoredOperation.Reduction)
{
bool signed = _state.State.SemaphoredFormat == SemaphoredFormat.Signed;
int mem = _parent.MemoryManager.Read(address);
switch (_state.State.SemaphoredReduction)
{
case SemaphoredReduction.Min:
value = signed ? Math.Min(mem, value) : (int)Math.Min((uint)mem, (uint)value);
break;
case SemaphoredReduction.Max:
value = signed ? Math.Max(mem, value) : (int)Math.Max((uint)mem, (uint)value);
break;
case SemaphoredReduction.Xor:
value ^= mem;
break;
case SemaphoredReduction.And:
value &= mem;
break;
case SemaphoredReduction.Or:
value |= mem;
break;
case SemaphoredReduction.Add:
value += mem;
break;
case SemaphoredReduction.Inc:
value = (uint)mem < (uint)value ? mem + 1 : 0;
break;
case SemaphoredReduction.Dec:
value = (uint)mem > 0 && (uint)mem <= (uint)value ? mem - 1 : value;
break;
}
_parent.MemoryManager.Write(address, value);
}
}
///
/// Apply a fence operation on a syncpoint.
///
/// Method call argument
public void Syncpointb(int argument)
{
SyncpointbOperation operation = _state.State.SyncpointbOperation;
uint syncpointId = (uint)_state.State.SyncpointbSyncptIndex;
if (operation == SyncpointbOperation.Wait)
{
uint threshold = (uint)_state.State.SyncpointaPayload;
_context.Synchronization.WaitOnSyncpoint(syncpointId, threshold, Timeout.InfiniteTimeSpan);
}
else if (operation == SyncpointbOperation.Incr)
{
_context.CreateHostSyncIfNeeded(HostSyncFlags.StrictSyncpoint);
_context.Synchronization.IncrementSyncpoint(syncpointId);
}
_context.AdvanceSequence();
}
///
/// Waits for the GPU to be idle.
///
/// Method call argument
public void WaitForIdle(int argument)
{
_parent.PerformDeferredDraws();
_context.Renderer.Pipeline.Barrier();
_createSyncPending = true;
}
///
/// Used as an indirect data barrier on NVN. When used, access to previously written data must be coherent.
///
/// Method call argument
public void SetReference(int argument)
{
_context.Renderer.Pipeline.CommandBufferBarrier();
_context.CreateHostSyncIfNeeded(HostSyncFlags.Strict);
}
///
/// Sends macro code/data to the MME.
///
/// Method call argument
public void LoadMmeInstructionRam(int argument)
{
_macroCode[_state.State.LoadMmeInstructionRamPointer++] = argument;
}
///
/// Binds a macro index to a position for the MME
///
/// Method call argument
public void LoadMmeStartAddressRam(int argument)
{
_macros[_state.State.LoadMmeStartAddressRamPointer++] = new Macro(argument);
}
///
/// Changes the shadow RAM control.
///
/// Method call argument
public void SetMmeShadowRamControl(int argument)
{
_parent.SetShadowRamControl(argument);
}
///
/// Pushes an argument to a macro.
///
/// Index of the macro
/// GPU virtual address where the command word is located
/// Argument to be pushed to the macro
public void MmePushArgument(int index, ulong gpuVa, int argument)
{
_macros[index].PushArgument(gpuVa, argument);
}
///
/// Prepares a macro for execution.
///
/// Index of the macro
/// Initial argument passed to the macro
public void MmeStart(int index, int argument)
{
_macros[index].StartExecution(_context, _parent, _macroCode, argument);
}
///
/// Executes a macro.
///
/// Index of the macro
/// Current GPU state
public void CallMme(int index, IDeviceState state)
{
_macros[index].Execute(_macroCode, state);
}
}
}