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 { /// /// Represents a GPU General Purpose FIFO command processor. /// 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; /// /// Channel memory manager. /// public MemoryManager MemoryManager => _channel.MemoryManager; /// /// 3D Engine. /// public ThreedClass ThreedClass => _3dClass; /// /// Internal GPFIFO state. /// 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; /// /// Creates a new instance of the GPU General Purpose FIFO command processor. /// /// GPU context /// Channel that the GPFIFO processor belongs to 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); } /// /// Processes a command buffer. /// /// Base GPU virtual address of the command buffer /// Command buffer public void Process(ulong baseGpuVa, ReadOnlySpan 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(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(); } /// /// Tries to perform a fast Inline-to-Memory data update. /// If successful, all data will be copied at once, and /// command buffer entries will be consumed. /// /// Command buffer where the data is contained /// Offset at where the data is located, auto-incremented on success /// True if the fast copy was successful, false otherwise [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryFastI2mBufferUpdate(ReadOnlySpan 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; } /// /// Tries to perform a fast constant buffer data update. /// If successful, all data will be copied at once, and + 1 /// command buffer entries will be consumed. /// /// Compressed method to be checked /// Command buffer where is contained /// Offset at where is located /// True if the fast copy was successful, false otherwise [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan 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; } /// /// Sends a uncompressed method for processing by the graphics pipeline. /// /// GPU virtual address where the command word is located /// Method to be processed 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(); } } } } /// /// Writes data directly to the state of the specified class. /// /// ID of the class to write the data into /// State offset in bytes /// Value to be written 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; } } /// /// Sets the shadow ram control value of all sub-channels. /// /// New shadow ram control value public void SetShadowRamControl(int control) { _3dClass.SetShadowRamControl(control); } /// /// Forces a full host state update by marking all state as modified, /// and also requests all GPU resources in use to be rebound. /// public void ForceAllDirty() { _3dClass.ForceStateDirty(); _channel.BufferManager.Rebind(); _channel.TextureManager.Rebind(); } /// /// Perform any deferred draws. /// public void PerformDeferredDraws() { _3dClass.PerformDeferredDraws(); } } }