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 { /// /// 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 { /// /// Processor used to process the command buffer. Contains channel state. /// public GPFifoProcessor Processor; /// /// 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; /// /// Get the entries for the command buffer from memory. /// /// The memory manager used to fetch the data /// If true, flushes potential GPU written data before reading the command buffer /// The fetched data private ReadOnlySpan GetWords(MemoryManager memoryManager, bool flush) { return MemoryMarshal.Cast(memoryManager.GetSpan(EntryAddress, (int)EntryCount * 4, flush)); } /// /// Prefetch the command buffer. /// /// The memory manager used to fetch the data public void Prefetch(MemoryManager memoryManager) { Words = GetWords(memoryManager, true).ToArray(); } /// /// Fetch the command buffer. /// /// The memory manager used to fetch the data /// If true, flushes potential GPU written data before reading the command buffer /// The command buffer words public ReadOnlySpan Fetch(MemoryManager memoryManager, bool flush) { return Words ?? GetWords(memoryManager, flush); } } private readonly ConcurrentQueue _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; /// /// 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); } /// /// 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. /// /// Processor used to process /// The command buffer containing the prefetched commands 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 }); } /// /// Create a CommandBuffer from a GPFIFO entry. /// /// Processor used to process the command buffer pointed to by /// The GPFIFO entry /// A new CommandBuffer based on the GPFIFO entry 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 }; } /// /// Pushes GPFIFO entries. /// /// Processor used to process the command buffers pointed to by /// GPFIFO entries internal void PushEntries(GPFifoProcessor processor, ReadOnlySpan entries) { bool beforeBarrier = true; for (int index = 0; index < entries.Length; index++) { ulong entry = entries[index]; CommandBuffer commandBuffer = CreateCommandBuffer(processor, Unsafe.As(ref entry)); if (beforeBarrier && commandBuffer.Type == CommandBufferType.Prefetch) { commandBuffer.Prefetch(processor.MemoryManager); } 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() { // 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 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; } /// /// Sets the number of flushes that should be skipped for subsequent command buffers. /// /// /// This can improve performance when command buffer data only needs to be consumed by the GPU. /// /// The amount of flushes that should be skipped internal void SetFlushSkips(int count) { _flushSkips = count; } /// /// Interrupts command processing. This will break out of the DispatchCalls loop. /// public void Interrupt() { _interrupt = true; _event.Set(); } /// /// Disposes of resources used for GPFifo command processing. /// public void Dispose() => _event.Dispose(); } }