using System; using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Ryujinx.Graphics.Gpu.Engine.GPFifo { /// /// Represents a GPU General Purpose FIFO device. /// public sealed class GPFifoDevice : IDisposable { /// /// Indicates if the command buffer has pre-fetch enabled. /// private enum CommandBufferType { Prefetch, NoPrefetch } /// /// Command buffer data. /// private struct CommandBuffer { /// /// The type of the command buffer. /// public CommandBufferType Type; /// /// Fetched data. /// public int[] Words; /// /// The GPFIFO entry address (used in mode). /// public ulong EntryAddress; /// /// The count of entries inside this GPFIFO entry. /// public uint EntryCount; /// /// Fetch the command buffer. /// public void Fetch(GpuContext context) { if (Words == null) { Words = MemoryMarshal.Cast(context.MemoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, true)).ToArray(); } } } private readonly ConcurrentQueue _commandBufferQueue; private CommandBuffer _currentCommandBuffer; private readonly bool _ibEnable; private readonly GpuContext _context; private readonly AutoResetEvent _event; private readonly GPFifoProcessor _processor; private bool _interrupt; /// /// Creates a new instance of the GPU General Purpose FIFO device. /// /// GPU context that the GPFIFO belongs to internal GPFifoDevice(GpuContext context) { _commandBufferQueue = new ConcurrentQueue(); _ibEnable = true; _context = context; _event = new AutoResetEvent(false); _processor = new GPFifoProcessor(context); } /// /// Signal the FIFO that there are new entries to process. /// public void SignalNewEntries() { _event.Set(); } /// /// Push a GPFIFO entry in the form of a prefetched command buffer. /// It is intended to be used by nvservices to handle special cases. /// /// The command buffer containing the prefetched commands public void PushHostCommandBuffer(int[] commandBuffer) { _commandBufferQueue.Enqueue(new CommandBuffer { Type = CommandBufferType.Prefetch, Words = commandBuffer, EntryAddress = ulong.MaxValue, EntryCount = (uint)commandBuffer.Length }); } /// /// Create a CommandBuffer from a GPFIFO entry. /// /// The GPFIFO entry /// A new CommandBuffer based on the GPFIFO entry private CommandBuffer CreateCommandBuffer(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 { Type = type, Words = null, EntryAddress = startAddress, EntryCount = (uint)entry.Entry1Length }; } /// /// Pushes GPFIFO entries. /// /// GPFIFO entries public void PushEntries(ReadOnlySpan entries) { bool beforeBarrier = true; for (int index = 0; index < entries.Length; index++) { ulong entry = entries[index]; CommandBuffer commandBuffer = CreateCommandBuffer(Unsafe.As(ref entry)); if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) { commandBuffer.Fetch(_context); } if (commandBuffer.Type == CommandBufferType.NoPrefetch) { beforeBarrier = false; } _commandBufferQueue.Enqueue(commandBuffer); } } /// /// Waits until commands are pushed to the FIFO. /// /// True if commands were received, false if wait timed out public bool WaitForCommands() { return !_commandBufferQueue.IsEmpty || (_event.WaitOne(8) && !_commandBufferQueue.IsEmpty); } /// /// Processes commands pushed to the FIFO. /// public void DispatchCalls() { while (_ibEnable && !_interrupt && _commandBufferQueue.TryDequeue(out CommandBuffer entry)) { _currentCommandBuffer = entry; _currentCommandBuffer.Fetch(_context); _processor.Process(_currentCommandBuffer.Words); } _interrupt = false; } /// /// Interrupts command processing. This will break out of the DispatchCalls loop. /// public void Interrupt() { _interrupt = true; } /// /// Disposes of resources used for GPFifo command processing. /// public void Dispose() => _event.Dispose(); } }