diff options
| author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
|---|---|---|
| committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
| commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
| tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Graphics.Gpu/Engine/GPFifo | |
| parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) | |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Engine/GPFifo')
6 files changed, 1170 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs new file mode 100644 index 00000000..458dc8f6 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/CompressedMethod.cs @@ -0,0 +1,41 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + enum TertOp + { + Grp0IncMethod = 0, + Grp0SetSubDevMask = 1, + Grp0StoreSubDevMask = 2, + Grp0UseSubDevMask = 3, + Grp2NonIncMethod = 0 + } + + enum SecOp + { + Grp0UseTert = 0, + IncMethod = 1, + Grp2UseTert = 2, + NonIncMethod = 3, + ImmdDataMethod = 4, + OneInc = 5, + Reserved6 = 6, + EndPbSegment = 7 + } + + struct CompressedMethod + { +#pragma warning disable CS0649 + public uint Method; +#pragma warning restore CS0649 + public int MethodAddressOld => (int)((Method >> 2) & 0x7FF); + public int MethodAddress => (int)((Method >> 0) & 0xFFF); + public int SubdeviceMask => (int)((Method >> 4) & 0xFFF); + public int MethodSubchannel => (int)((Method >> 13) & 0x7); + public TertOp TertOp => (TertOp)((Method >> 16) & 0x3); + public int MethodCountOld => (int)((Method >> 18) & 0x7FF); + public int MethodCount => (int)((Method >> 16) & 0x1FFF); + public int ImmdData => (int)((Method >> 16) & 0x1FFF); + public SecOp SecOp => (SecOp)((Method >> 29) & 0x7); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs new file mode 100644 index 00000000..b1b236e7 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPEntry.cs @@ -0,0 +1,55 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + enum Entry0Fetch + { + Unconditional = 0, + Conditional = 1, + } + + enum Entry1Priv + { + User = 0, + Kernel = 1, + } + + enum Entry1Level + { + Main = 0, + Subroutine = 1, + } + + enum Entry1Sync + { + Proceed = 0, + Wait = 1, + } + + enum Entry1Opcode + { + Nop = 0, + Illegal = 1, + Crc = 2, + PbCrc = 3, + } + + struct GPEntry + { +#pragma warning disable CS0649 + public uint Entry0; +#pragma warning restore CS0649 + public Entry0Fetch Entry0Fetch => (Entry0Fetch)((Entry0 >> 0) & 0x1); + public int Entry0Get => (int)((Entry0 >> 2) & 0x3FFFFFFF); + public int Entry0Operand => (int)(Entry0); +#pragma warning disable CS0649 + public uint Entry1; +#pragma warning restore CS0649 + public int Entry1GetHi => (int)((Entry1 >> 0) & 0xFF); + public Entry1Priv Entry1Priv => (Entry1Priv)((Entry1 >> 8) & 0x1); + public Entry1Level Entry1Level => (Entry1Level)((Entry1 >> 9) & 0x1); + public int Entry1Length => (int)((Entry1 >> 10) & 0x1FFFFF); + public Entry1Sync Entry1Sync => (Entry1Sync)((Entry1 >> 31) & 0x1); + public Entry1Opcode Entry1Opcode => (Entry1Opcode)((Entry1 >> 0) & 0xFF); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs new file mode 100644 index 00000000..e80d98a1 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClass.cs @@ -0,0 +1,248 @@ +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 +{ + /// <summary> + /// Represents a GPU General Purpose FIFO class. + /// </summary> + class GPFifoClass : IDeviceState + { + private readonly GpuContext _context; + private readonly GPFifoProcessor _parent; + private readonly DeviceState<GPFifoClassState> _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; + + /// <summary> + /// Creates a new instance of the GPU General Purpose FIFO class. + /// </summary> + /// <param name="context">GPU context</param> + /// <param name="parent">Parent GPU General Purpose FIFO processor</param> + public GPFifoClass(GpuContext context, GPFifoProcessor parent) + { + _context = context; + _parent = parent; + _state = new DeviceState<GPFifoClassState>(new Dictionary<string, RwCallback> + { + { 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]; + } + + /// <summary> + /// Create any syncs from WaitForIdle command that are currently pending. + /// </summary> + public void CreatePendingSyncs() + { + if (_createSyncPending) + { + _createSyncPending = false; + _context.CreateHostSyncIfNeeded(false, false); + } + } + + /// <summary> + /// Reads data from the class registers. + /// </summary> + /// <param name="offset">Register byte offset</param> + /// <returns>Data at the specified offset</returns> + public int Read(int offset) => _state.Read(offset); + + /// <summary> + /// Writes data to the class registers. + /// </summary> + /// <param name="offset">Register byte offset</param> + /// <param name="data">Data to be written</param> + public void Write(int offset, int data) => _state.Write(offset, data); + + /// <summary> + /// Writes a GPU counter to guest memory. + /// </summary> + /// <param name="argument">Method call argument</param> + 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<int>(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); + } + } + + /// <summary> + /// Apply a fence operation on a syncpoint. + /// </summary> + /// <param name="argument">Method call argument</param> + 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(); + } + + /// <summary> + /// Waits for the GPU to be idle. + /// </summary> + /// <param name="argument">Method call argument</param> + public void WaitForIdle(int argument) + { + _parent.PerformDeferredDraws(); + _context.Renderer.Pipeline.Barrier(); + + _createSyncPending = true; + } + + /// <summary> + /// Used as an indirect data barrier on NVN. When used, access to previously written data must be coherent. + /// </summary> + /// <param name="argument">Method call argument</param> + public void SetReference(int argument) + { + _context.Renderer.Pipeline.CommandBufferBarrier(); + + _context.CreateHostSyncIfNeeded(false, true); + } + + /// <summary> + /// Sends macro code/data to the MME. + /// </summary> + /// <param name="argument">Method call argument</param> + public void LoadMmeInstructionRam(int argument) + { + _macroCode[_state.State.LoadMmeInstructionRamPointer++] = argument; + } + + /// <summary> + /// Binds a macro index to a position for the MME + /// </summary> + /// <param name="argument">Method call argument</param> + public void LoadMmeStartAddressRam(int argument) + { + _macros[_state.State.LoadMmeStartAddressRamPointer++] = new Macro(argument); + } + + /// <summary> + /// Changes the shadow RAM control. + /// </summary> + /// <param name="argument">Method call argument</param> + public void SetMmeShadowRamControl(int argument) + { + _parent.SetShadowRamControl(argument); + } + + /// <summary> + /// Pushes an argument to a macro. + /// </summary> + /// <param name="index">Index of the macro</param> + /// <param name="gpuVa">GPU virtual address where the command word is located</param> + /// <param name="argument">Argument to be pushed to the macro</param> + public void MmePushArgument(int index, ulong gpuVa, int argument) + { + _macros[index].PushArgument(gpuVa, argument); + } + + /// <summary> + /// Prepares a macro for execution. + /// </summary> + /// <param name="index">Index of the macro</param> + /// <param name="argument">Initial argument passed to the macro</param> + public void MmeStart(int index, int argument) + { + _macros[index].StartExecution(_context, _parent, _macroCode, argument); + } + + /// <summary> + /// Executes a macro. + /// </summary> + /// <param name="index">Index of the macro</param> + /// <param name="state">Current GPU state</param> + public void CallMme(int index, IDeviceState state) + { + _macros[index].Execute(_macroCode, state); + } + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs new file mode 100644 index 00000000..07d062eb --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoClassState.cs @@ -0,0 +1,233 @@ +// This file was auto-generated from NVIDIA official Maxwell definitions. + +using Ryujinx.Common.Memory; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// <summary> + /// Semaphore operation. + /// </summary> + enum SemaphoredOperation + { + Acquire = 1, + Release = 2, + AcqGeq = 4, + AcqAnd = 8, + Reduction = 16 + } + + /// <summary> + /// Semaphore acquire switch enable. + /// </summary> + enum SemaphoredAcquireSwitch + { + Disabled = 0, + Enabled = 1 + } + + /// <summary> + /// Semaphore release interrupt wait enable. + /// </summary> + enum SemaphoredReleaseWfi + { + En = 0, + Dis = 1 + } + + /// <summary> + /// Semaphore release structure size. + /// </summary> + enum SemaphoredReleaseSize + { + SixteenBytes = 0, + FourBytes = 1 + } + + /// <summary> + /// Semaphore reduction operation. + /// </summary> + enum SemaphoredReduction + { + Min = 0, + Max = 1, + Xor = 2, + And = 3, + Or = 4, + Add = 5, + Inc = 6, + Dec = 7 + } + + /// <summary> + /// Semaphore format. + /// </summary> + enum SemaphoredFormat + { + Signed = 0, + Unsigned = 1 + } + + /// <summary> + /// Memory Translation Lookaside Buffer Page Directory Buffer invalidation. + /// </summary> + enum MemOpCTlbInvalidatePdb + { + One = 0, + All = 1 + } + + /// <summary> + /// Memory Translation Lookaside Buffer GPC invalidation enable. + /// </summary> + enum MemOpCTlbInvalidateGpc + { + Enable = 0, + Disable = 1 + } + + /// <summary> + /// Memory Translation Lookaside Buffer invalidation target. + /// </summary> + enum MemOpCTlbInvalidateTarget + { + VidMem = 0, + SysMemCoherent = 2, + SysMemNoncoherent = 3 + } + + /// <summary> + /// Memory operation. + /// </summary> + enum MemOpDOperation + { + Membar = 5, + MmuTlbInvalidate = 9, + L2PeermemInvalidate = 13, + L2SysmemInvalidate = 14, + L2CleanComptags = 15, + L2FlushDirty = 16 + } + + /// <summary> + /// Syncpoint operation. + /// </summary> + enum SyncpointbOperation + { + Wait = 0, + Incr = 1 + } + + /// <summary> + /// Syncpoint wait switch enable. + /// </summary> + enum SyncpointbWaitSwitch + { + Dis = 0, + En = 1 + } + + /// <summary> + /// Wait for interrupt scope. + /// </summary> + enum WfiScope + { + CurrentScgType = 0, + All = 1 + } + + /// <summary> + /// Yield operation. + /// </summary> + enum YieldOp + { + Nop = 0, + PbdmaTimeslice = 1, + RunlistTimeslice = 2, + Tsg = 3 + } + + /// <summary> + /// General Purpose FIFO class state. + /// </summary> + struct GPFifoClassState + { +#pragma warning disable CS0649 + public uint SetObject; + public int SetObjectNvclass => (int)((SetObject >> 0) & 0xFFFF); + public int SetObjectEngine => (int)((SetObject >> 16) & 0x1F); + public uint Illegal; + public int IllegalHandle => (int)(Illegal); + public uint Nop; + public int NopHandle => (int)(Nop); + public uint Reserved0C; + public uint Semaphorea; + public int SemaphoreaOffsetUpper => (int)((Semaphorea >> 0) & 0xFF); + public uint Semaphoreb; + public int SemaphorebOffsetLower => (int)((Semaphoreb >> 2) & 0x3FFFFFFF); + public uint Semaphorec; + public int SemaphorecPayload => (int)(Semaphorec); + public uint Semaphored; + public SemaphoredOperation SemaphoredOperation => (SemaphoredOperation)((Semaphored >> 0) & 0x1F); + public SemaphoredAcquireSwitch SemaphoredAcquireSwitch => (SemaphoredAcquireSwitch)((Semaphored >> 12) & 0x1); + public SemaphoredReleaseWfi SemaphoredReleaseWfi => (SemaphoredReleaseWfi)((Semaphored >> 20) & 0x1); + public SemaphoredReleaseSize SemaphoredReleaseSize => (SemaphoredReleaseSize)((Semaphored >> 24) & 0x1); + public SemaphoredReduction SemaphoredReduction => (SemaphoredReduction)((Semaphored >> 27) & 0xF); + public SemaphoredFormat SemaphoredFormat => (SemaphoredFormat)((Semaphored >> 31) & 0x1); + public uint NonStallInterrupt; + public int NonStallInterruptHandle => (int)(NonStallInterrupt); + public uint FbFlush; + public int FbFlushHandle => (int)(FbFlush); + public uint Reserved28; + public uint Reserved2C; + public uint MemOpC; + public int MemOpCOperandLow => (int)((MemOpC >> 2) & 0x3FFFFFFF); + public MemOpCTlbInvalidatePdb MemOpCTlbInvalidatePdb => (MemOpCTlbInvalidatePdb)((MemOpC >> 0) & 0x1); + public MemOpCTlbInvalidateGpc MemOpCTlbInvalidateGpc => (MemOpCTlbInvalidateGpc)((MemOpC >> 1) & 0x1); + public MemOpCTlbInvalidateTarget MemOpCTlbInvalidateTarget => (MemOpCTlbInvalidateTarget)((MemOpC >> 10) & 0x3); + public int MemOpCTlbInvalidateAddrLo => (int)((MemOpC >> 12) & 0xFFFFF); + public uint MemOpD; + public int MemOpDOperandHigh => (int)((MemOpD >> 0) & 0xFF); + public MemOpDOperation MemOpDOperation => (MemOpDOperation)((MemOpD >> 27) & 0x1F); + public int MemOpDTlbInvalidateAddrHi => (int)((MemOpD >> 0) & 0xFF); + public uint Reserved38; + public uint Reserved3C; + public uint Reserved40; + public uint Reserved44; + public uint Reserved48; + public uint Reserved4C; + public uint SetReference; + public int SetReferenceCount => (int)(SetReference); + public uint Reserved54; + public uint Reserved58; + public uint Reserved5C; + public uint Reserved60; + public uint Reserved64; + public uint Reserved68; + public uint Reserved6C; + public uint Syncpointa; + public int SyncpointaPayload => (int)(Syncpointa); + public uint Syncpointb; + public SyncpointbOperation SyncpointbOperation => (SyncpointbOperation)((Syncpointb >> 0) & 0x1); + public SyncpointbWaitSwitch SyncpointbWaitSwitch => (SyncpointbWaitSwitch)((Syncpointb >> 4) & 0x1); + public int SyncpointbSyncptIndex => (int)((Syncpointb >> 8) & 0xFFF); + public uint Wfi; + public WfiScope WfiScope => (WfiScope)((Wfi >> 0) & 0x1); + public uint CrcCheck; + public int CrcCheckValue => (int)(CrcCheck); + public uint Yield; + public YieldOp YieldOp => (YieldOp)((Yield >> 0) & 0x3); + // TODO: Eventually move this to per-engine state. + public Array31<uint> Reserved84; + public uint NoOperation; + public uint SetNotifyA; + public uint SetNotifyB; + public uint Notify; + public uint WaitForIdle; + public uint LoadMmeInstructionRamPointer; + public uint LoadMmeInstructionRam; + public uint LoadMmeStartAddressRamPointer; + public uint LoadMmeStartAddressRam; + public uint SetMmeShadowRamControl; +#pragma warning restore CS0649 + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs new file mode 100644 index 00000000..cd29a9da --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoDevice.cs @@ -0,0 +1,262 @@ +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// <summary> + /// Represents a GPU General Purpose FIFO device. + /// </summary> + public sealed class GPFifoDevice : IDisposable + { + /// <summary> + /// Indicates if the command buffer has pre-fetch enabled. + /// </summary> + private enum CommandBufferType + { + Prefetch, + NoPrefetch + } + + /// <summary> + /// Command buffer data. + /// </summary> + private struct CommandBuffer + { + /// <summary> + /// Processor used to process the command buffer. Contains channel state. + /// </summary> + public GPFifoProcessor Processor; + + /// <summary> + /// The type of the command buffer. + /// </summary> + public CommandBufferType Type; + + /// <summary> + /// Fetched data. + /// </summary> + public int[] Words; + + /// <summary> + /// The GPFIFO entry address (used in <see cref="CommandBufferType.NoPrefetch"/> mode). + /// </summary> + public ulong EntryAddress; + + /// <summary> + /// The count of entries inside this GPFIFO entry. + /// </summary> + public uint EntryCount; + + /// <summary> + /// Get the entries for the command buffer from memory. + /// </summary> + /// <param name="memoryManager">The memory manager used to fetch the data</param> + /// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param> + /// <returns>The fetched data</returns> + private ReadOnlySpan<int> GetWords(MemoryManager memoryManager, bool flush) + { + return MemoryMarshal.Cast<byte, int>(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)); + } + + /// <summary> + /// Prefetch the command buffer. + /// </summary> + /// <param name="memoryManager">The memory manager used to fetch the data</param> + public void Prefetch(MemoryManager memoryManager) + { + Words = GetWords(memoryManager, true).ToArray(); + } + + /// <summary> + /// Fetch the command buffer. + /// </summary> + /// <param name="memoryManager">The memory manager used to fetch the data</param> + /// <param name="flush">If true, flushes potential GPU written data before reading the command buffer</param> + /// <returns>The command buffer words</returns> + public ReadOnlySpan<int> Fetch(MemoryManager memoryManager, bool flush) + { + return Words ?? GetWords(memoryManager, flush); + } + } + + private readonly ConcurrentQueue<CommandBuffer> _commandBufferQueue; + + private CommandBuffer _currentCommandBuffer; + private GPFifoProcessor _prevChannelProcessor; + + private readonly bool _ibEnable; + private readonly GpuContext _context; + private readonly AutoResetEvent _event; + + private bool _interrupt; + private int _flushSkips; + + /// <summary> + /// Creates a new instance of the GPU General Purpose FIFO device. + /// </summary> + /// <param name="context">GPU context that the GPFIFO belongs to</param> + internal GPFifoDevice(GpuContext context) + { + _commandBufferQueue = new ConcurrentQueue<CommandBuffer>(); + _ibEnable = true; + _context = context; + _event = new AutoResetEvent(false); + } + + /// <summary> + /// Signal the FIFO that there are new entries to process. + /// </summary> + public void SignalNewEntries() + { + _event.Set(); + } + + /// <summary> + /// Push a GPFIFO entry in the form of a prefetched command buffer. + /// It is intended to be used by nvservices to handle special cases. + /// </summary> + /// <param name="processor">Processor used to process <paramref name="commandBuffer"/></param> + /// <param name="commandBuffer">The command buffer containing the prefetched commands</param> + internal void PushHostCommandBuffer(GPFifoProcessor processor, int[] commandBuffer) + { + _commandBufferQueue.Enqueue(new CommandBuffer + { + Processor = processor, + Type = CommandBufferType.Prefetch, + Words = commandBuffer, + EntryAddress = ulong.MaxValue, + EntryCount = (uint)commandBuffer.Length + }); + } + + /// <summary> + /// Create a CommandBuffer from a GPFIFO entry. + /// </summary> + /// <param name="processor">Processor used to process the command buffer pointed to by <paramref name="entry"/></param> + /// <param name="entry">The GPFIFO entry</param> + /// <returns>A new CommandBuffer based on the GPFIFO entry</returns> + private static CommandBuffer CreateCommandBuffer(GPFifoProcessor processor, GPEntry entry) + { + CommandBufferType type = CommandBufferType.Prefetch; + + if (entry.Entry1Sync == Entry1Sync.Wait) + { + type = CommandBufferType.NoPrefetch; + } + + ulong startAddress = ((ulong)entry.Entry0Get << 2) | ((ulong)entry.Entry1GetHi << 32); + + return new CommandBuffer + { + Processor = processor, + Type = type, + Words = null, + EntryAddress = startAddress, + EntryCount = (uint)entry.Entry1Length + }; + } + + /// <summary> + /// Pushes GPFIFO entries. + /// </summary> + /// <param name="processor">Processor used to process the command buffers pointed to by <paramref name="entries"/></param> + /// <param name="entries">GPFIFO entries</param> + internal void PushEntries(GPFifoProcessor processor, ReadOnlySpan<ulong> entries) + { + bool beforeBarrier = true; + + for (int index = 0; index < entries.Length; index++) + { + ulong entry = entries[index]; + + CommandBuffer commandBuffer = CreateCommandBuffer(processor, Unsafe.As<ulong, GPEntry>(ref entry)); + + if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) + { + commandBuffer.Prefetch(processor.MemoryManager); + } + + if (commandBuffer.Type == CommandBufferType.NoPrefetch) + { + beforeBarrier = false; + } + + _commandBufferQueue.Enqueue(commandBuffer); + } + } + + /// <summary> + /// Waits until commands are pushed to the FIFO. + /// </summary> + /// <returns>True if commands were received, false if wait timed out</returns> + public bool WaitForCommands() + { + return !_commandBufferQueue.IsEmpty || (_event.WaitOne(8) && !_commandBufferQueue.IsEmpty); + } + + /// <summary> + /// Processes commands pushed to the FIFO. + /// </summary> + public void DispatchCalls() + { + // Use this opportunity to also dispose any pending channels that were closed. + _context.RunDeferredActions(); + + // Process command buffers. + while (_ibEnable && !_interrupt && _commandBufferQueue.TryDequeue(out CommandBuffer entry)) + { + bool flushCommandBuffer = true; + + if (_flushSkips != 0) + { + _flushSkips--; + flushCommandBuffer = false; + } + + _currentCommandBuffer = entry; + ReadOnlySpan<int> words = entry.Fetch(entry.Processor.MemoryManager, flushCommandBuffer); + + // If we are changing the current channel, + // we need to force all the host state to be updated. + if (_prevChannelProcessor != entry.Processor) + { + _prevChannelProcessor = entry.Processor; + entry.Processor.ForceAllDirty(); + } + + entry.Processor.Process(entry.EntryAddress, words); + } + + _interrupt = false; + } + + /// <summary> + /// Sets the number of flushes that should be skipped for subsequent command buffers. + /// </summary> + /// <remarks> + /// This can improve performance when command buffer data only needs to be consumed by the GPU. + /// </remarks> + /// <param name="count">The amount of flushes that should be skipped</param> + internal void SetFlushSkips(int count) + { + _flushSkips = count; + } + + /// <summary> + /// Interrupts command processing. This will break out of the DispatchCalls loop. + /// </summary> + public void Interrupt() + { + _interrupt = true; + } + + /// <summary> + /// Disposes of resources used for GPFifo command processing. + /// </summary> + public void Dispose() => _event.Dispose(); + } +} diff --git a/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs new file mode 100644 index 00000000..3fb3feee --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Engine/GPFifo/GPFifoProcessor.cs @@ -0,0 +1,331 @@ +using Ryujinx.Graphics.Device; +using Ryujinx.Graphics.Gpu.Engine.Compute; +using Ryujinx.Graphics.Gpu.Engine.Dma; +using Ryujinx.Graphics.Gpu.Engine.InlineToMemory; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Twod; +using Ryujinx.Graphics.Gpu.Memory; +using System; +using System.Runtime.CompilerServices; + +namespace Ryujinx.Graphics.Gpu.Engine.GPFifo +{ + /// <summary> + /// Represents a GPU General Purpose FIFO command processor. + /// </summary> + class GPFifoProcessor + { + private const int MacrosCount = 0x80; + private const int MacroIndexMask = MacrosCount - 1; + + private const int LoadInlineDataMethodOffset = 0x6d; + private const int UniformBufferUpdateDataMethodOffset = 0x8e4; + + private readonly GpuChannel _channel; + + /// <summary> + /// Channel memory manager. + /// </summary> + public MemoryManager MemoryManager => _channel.MemoryManager; + + /// <summary> + /// 3D Engine. + /// </summary> + public ThreedClass ThreedClass => _3dClass; + + /// <summary> + /// Internal GPFIFO state. + /// </summary> + private struct DmaState + { + public int Method; + public int SubChannel; + public int MethodCount; + public bool NonIncrementing; + public bool IncrementOnce; + } + + private DmaState _state; + + private readonly ThreedClass _3dClass; + private readonly ComputeClass _computeClass; + private readonly InlineToMemoryClass _i2mClass; + private readonly TwodClass _2dClass; + private readonly DmaClass _dmaClass; + + private readonly GPFifoClass _fifoClass; + + /// <summary> + /// Creates a new instance of the GPU General Purpose FIFO command processor. + /// </summary> + /// <param name="context">GPU context</param> + /// <param name="channel">Channel that the GPFIFO processor belongs to</param> + public GPFifoProcessor(GpuContext context, GpuChannel channel) + { + _channel = channel; + + _fifoClass = new GPFifoClass(context, this); + _3dClass = new ThreedClass(context, channel, _fifoClass); + _computeClass = new ComputeClass(context, channel, _3dClass); + _i2mClass = new InlineToMemoryClass(context, channel); + _2dClass = new TwodClass(channel); + _dmaClass = new DmaClass(context, channel, _3dClass); + } + + /// <summary> + /// Processes a command buffer. + /// </summary> + /// <param name="baseGpuVa">Base GPU virtual address of the command buffer</param> + /// <param name="commandBuffer">Command buffer</param> + public void Process(ulong baseGpuVa, ReadOnlySpan<int> commandBuffer) + { + for (int index = 0; index < commandBuffer.Length; index++) + { + int command = commandBuffer[index]; + + ulong gpuVa = baseGpuVa + (ulong)index * 4; + + if (_state.MethodCount != 0) + { + if (TryFastI2mBufferUpdate(commandBuffer, ref index)) + { + continue; + } + + Send(gpuVa, _state.Method, command, _state.SubChannel, _state.MethodCount <= 1); + + if (!_state.NonIncrementing) + { + _state.Method++; + } + + if (_state.IncrementOnce) + { + _state.NonIncrementing = true; + } + + _state.MethodCount--; + } + else + { + CompressedMethod meth = Unsafe.As<int, CompressedMethod>(ref command); + + if (TryFastUniformBufferUpdate(meth, commandBuffer, index)) + { + index += meth.MethodCount; + continue; + } + + switch (meth.SecOp) + { + case SecOp.IncMethod: + case SecOp.NonIncMethod: + case SecOp.OneInc: + _state.Method = meth.MethodAddress; + _state.SubChannel = meth.MethodSubchannel; + _state.MethodCount = meth.MethodCount; + _state.IncrementOnce = meth.SecOp == SecOp.OneInc; + _state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod; + break; + case SecOp.ImmdDataMethod: + Send(gpuVa, meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, true); + break; + } + } + } + + _3dClass.FlushUboDirty(); + } + + /// <summary> + /// Tries to perform a fast Inline-to-Memory data update. + /// If successful, all data will be copied at once, and <see cref="DmaState.MethodCount"/> + /// command buffer entries will be consumed. + /// </summary> + /// <param name="commandBuffer">Command buffer where the data is contained</param> + /// <param name="offset">Offset at <paramref name="commandBuffer"/> where the data is located, auto-incremented on success</param> + /// <returns>True if the fast copy was successful, false otherwise</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryFastI2mBufferUpdate(ReadOnlySpan<int> commandBuffer, ref int offset) + { + if (_state.Method == LoadInlineDataMethodOffset && _state.NonIncrementing && _state.SubChannel <= 2) + { + int availableCount = commandBuffer.Length - offset; + int consumeCount = Math.Min(_state.MethodCount, availableCount); + + var data = commandBuffer.Slice(offset, consumeCount); + + if (_state.SubChannel == 0) + { + _3dClass.LoadInlineData(data); + } + else if (_state.SubChannel == 1) + { + _computeClass.LoadInlineData(data); + } + else /* if (_state.SubChannel == 2) */ + { + _i2mClass.LoadInlineData(data); + } + + offset += consumeCount - 1; + _state.MethodCount -= consumeCount; + + return true; + } + + return false; + } + + /// <summary> + /// Tries to perform a fast constant buffer data update. + /// If successful, all data will be copied at once, and <see cref="CompressedMethod.MethodCount"/> + 1 + /// command buffer entries will be consumed. + /// </summary> + /// <param name="meth">Compressed method to be checked</param> + /// <param name="commandBuffer">Command buffer where <paramref name="meth"/> is contained</param> + /// <param name="offset">Offset at <paramref name="commandBuffer"/> where <paramref name="meth"/> is located</param> + /// <returns>True if the fast copy was successful, false otherwise</returns> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan<int> commandBuffer, int offset) + { + int availableCount = commandBuffer.Length - offset; + + if (meth.MethodAddress == UniformBufferUpdateDataMethodOffset && + meth.MethodCount < availableCount && + meth.SecOp == SecOp.NonIncMethod) + { + _3dClass.ConstantBufferUpdate(commandBuffer.Slice(offset + 1, meth.MethodCount)); + + return true; + } + + return false; + } + + /// <summary> + /// Sends a uncompressed method for processing by the graphics pipeline. + /// </summary> + /// <param name="gpuVa">GPU virtual address where the command word is located</param> + /// <param name="meth">Method to be processed</param> + private void Send(ulong gpuVa, int offset, int argument, int subChannel, bool isLastCall) + { + if (offset < 0x60) + { + _fifoClass.Write(offset * 4, argument); + } + else if (offset < 0xe00) + { + offset *= 4; + + switch (subChannel) + { + case 0: + _3dClass.Write(offset, argument); + break; + case 1: + _computeClass.Write(offset, argument); + break; + case 2: + _i2mClass.Write(offset, argument); + break; + case 3: + _2dClass.Write(offset, argument); + break; + case 4: + _dmaClass.Write(offset, argument); + break; + } + } + else + { + IDeviceState state = subChannel switch + { + 0 => _3dClass, + 3 => _2dClass, + _ => null + }; + + if (state != null) + { + int macroIndex = (offset >> 1) & MacroIndexMask; + + if ((offset & 1) != 0) + { + _fifoClass.MmePushArgument(macroIndex, gpuVa, argument); + } + else + { + _fifoClass.MmeStart(macroIndex, argument); + } + + if (isLastCall) + { + _fifoClass.CallMme(macroIndex, state); + + _3dClass.PerformDeferredDraws(); + } + } + } + } + + /// <summary> + /// Writes data directly to the state of the specified class. + /// </summary> + /// <param name="classId">ID of the class to write the data into</param> + /// <param name="offset">State offset in bytes</param> + /// <param name="value">Value to be written</param> + public void Write(ClassId classId, int offset, int value) + { + switch (classId) + { + case ClassId.Threed: + _3dClass.Write(offset, value); + break; + case ClassId.Compute: + _computeClass.Write(offset, value); + break; + case ClassId.InlineToMemory: + _i2mClass.Write(offset, value); + break; + case ClassId.Twod: + _2dClass.Write(offset, value); + break; + case ClassId.Dma: + _dmaClass.Write(offset, value); + break; + case ClassId.GPFifo: + _fifoClass.Write(offset, value); + break; + } + } + + /// <summary> + /// Sets the shadow ram control value of all sub-channels. + /// </summary> + /// <param name="control">New shadow ram control value</param> + public void SetShadowRamControl(int control) + { + _3dClass.SetShadowRamControl(control); + } + + /// <summary> + /// Forces a full host state update by marking all state as modified, + /// and also requests all GPU resources in use to be rebound. + /// </summary> + public void ForceAllDirty() + { + _3dClass.ForceStateDirty(); + _channel.BufferManager.Rebind(); + _channel.TextureManager.Rebind(); + } + + /// <summary> + /// Perform any deferred draws. + /// </summary> + public void PerformDeferredDraws() + { + _3dClass.PerformDeferredDraws(); + } + } +} |
