using Ryujinx.Graphics.Device; using Ryujinx.Graphics.Gpu.Engine.MME; 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 int _previousSubChannel; 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(false, false); } } /// /// 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(true, true); _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(false, true); } /// /// 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); } } }