/ src / Ryujinx.Graphics.Gpu / Engine / GPFifo / GPFifoProcessor.cs
GPFifoProcessor.cs
  1  using Ryujinx.Graphics.Device;
  2  using Ryujinx.Graphics.Gpu.Engine.Compute;
  3  using Ryujinx.Graphics.Gpu.Engine.Dma;
  4  using Ryujinx.Graphics.Gpu.Engine.InlineToMemory;
  5  using Ryujinx.Graphics.Gpu.Engine.Threed;
  6  using Ryujinx.Graphics.Gpu.Engine.Twod;
  7  using Ryujinx.Graphics.Gpu.Image;
  8  using Ryujinx.Graphics.Gpu.Memory;
  9  using System;
 10  using System.Runtime.CompilerServices;
 11  
 12  namespace Ryujinx.Graphics.Gpu.Engine.GPFifo
 13  {
 14      /// <summary>
 15      /// Represents a GPU General Purpose FIFO command processor.
 16      /// </summary>
 17      class GPFifoProcessor : IDisposable
 18      {
 19          private const int MacrosCount = 0x80;
 20          private const int MacroIndexMask = MacrosCount - 1;
 21  
 22          private const int LoadInlineDataMethodOffset = 0x6d;
 23          private const int UniformBufferUpdateDataMethodOffset = 0x8e4;
 24  
 25          private readonly GpuChannel _channel;
 26  
 27          /// <summary>
 28          /// Channel memory manager.
 29          /// </summary>
 30          public MemoryManager MemoryManager => _channel.MemoryManager;
 31  
 32          /// <summary>
 33          /// Channel texture manager.
 34          /// </summary>
 35          public TextureManager TextureManager => _channel.TextureManager;
 36  
 37          /// <summary>
 38          /// 3D Engine.
 39          /// </summary>
 40          public ThreedClass ThreedClass => _3dClass;
 41  
 42          /// <summary>
 43          /// Internal GPFIFO state.
 44          /// </summary>
 45          private struct DmaState
 46          {
 47              public int Method;
 48              public int SubChannel;
 49              public int MethodCount;
 50              public bool NonIncrementing;
 51              public bool IncrementOnce;
 52          }
 53  
 54          private DmaState _state;
 55  
 56          private readonly ThreedClass _3dClass;
 57          private readonly ComputeClass _computeClass;
 58          private readonly InlineToMemoryClass _i2mClass;
 59          private readonly TwodClass _2dClass;
 60          private readonly DmaClass _dmaClass;
 61  
 62          private readonly GPFifoClass _fifoClass;
 63  
 64          /// <summary>
 65          /// Creates a new instance of the GPU General Purpose FIFO command processor.
 66          /// </summary>
 67          /// <param name="context">GPU context</param>
 68          /// <param name="channel">Channel that the GPFIFO processor belongs to</param>
 69          public GPFifoProcessor(GpuContext context, GpuChannel channel)
 70          {
 71              _channel = channel;
 72  
 73              _fifoClass = new GPFifoClass(context, this);
 74              _3dClass = new ThreedClass(context, channel, _fifoClass);
 75              _computeClass = new ComputeClass(context, channel, _3dClass);
 76              _i2mClass = new InlineToMemoryClass(context, channel);
 77              _2dClass = new TwodClass(channel);
 78              _dmaClass = new DmaClass(context, channel, _3dClass);
 79          }
 80  
 81          /// <summary>
 82          /// Processes a command buffer.
 83          /// </summary>
 84          /// <param name="baseGpuVa">Base GPU virtual address of the command buffer</param>
 85          /// <param name="commandBuffer">Command buffer</param>
 86          public void Process(ulong baseGpuVa, ReadOnlySpan<int> commandBuffer)
 87          {
 88              for (int index = 0; index < commandBuffer.Length; index++)
 89              {
 90                  int command = commandBuffer[index];
 91  
 92                  ulong gpuVa = baseGpuVa + (ulong)index * 4;
 93  
 94                  if (_state.MethodCount != 0)
 95                  {
 96                      if (TryFastI2mBufferUpdate(commandBuffer, ref index))
 97                      {
 98                          continue;
 99                      }
100  
101                      Send(gpuVa, _state.Method, command, _state.SubChannel, _state.MethodCount <= 1);
102  
103                      if (!_state.NonIncrementing)
104                      {
105                          _state.Method++;
106                      }
107  
108                      if (_state.IncrementOnce)
109                      {
110                          _state.NonIncrementing = true;
111                      }
112  
113                      _state.MethodCount--;
114                  }
115                  else
116                  {
117                      CompressedMethod meth = Unsafe.As<int, CompressedMethod>(ref command);
118  
119                      if (TryFastUniformBufferUpdate(meth, commandBuffer, index))
120                      {
121                          index += meth.MethodCount;
122                          continue;
123                      }
124  
125                      switch (meth.SecOp)
126                      {
127                          case SecOp.IncMethod:
128                          case SecOp.NonIncMethod:
129                          case SecOp.OneInc:
130                              _state.Method = meth.MethodAddress;
131                              _state.SubChannel = meth.MethodSubchannel;
132                              _state.MethodCount = meth.MethodCount;
133                              _state.IncrementOnce = meth.SecOp == SecOp.OneInc;
134                              _state.NonIncrementing = meth.SecOp == SecOp.NonIncMethod;
135                              break;
136                          case SecOp.ImmdDataMethod:
137                              Send(gpuVa, meth.MethodAddress, meth.ImmdData, meth.MethodSubchannel, true);
138                              break;
139                      }
140                  }
141              }
142  
143              _3dClass.FlushUboDirty();
144          }
145  
146          /// <summary>
147          /// Tries to perform a fast Inline-to-Memory data update.
148          /// If successful, all data will be copied at once, and <see cref="DmaState.MethodCount"/>
149          /// command buffer entries will be consumed.
150          /// </summary>
151          /// <param name="commandBuffer">Command buffer where the data is contained</param>
152          /// <param name="offset">Offset at <paramref name="commandBuffer"/> where the data is located, auto-incremented on success</param>
153          /// <returns>True if the fast copy was successful, false otherwise</returns>
154          [MethodImpl(MethodImplOptions.AggressiveInlining)]
155          private bool TryFastI2mBufferUpdate(ReadOnlySpan<int> commandBuffer, ref int offset)
156          {
157              if (_state.Method == LoadInlineDataMethodOffset && _state.NonIncrementing && _state.SubChannel <= 2)
158              {
159                  int availableCount = commandBuffer.Length - offset;
160                  int consumeCount = Math.Min(_state.MethodCount, availableCount);
161  
162                  var data = commandBuffer.Slice(offset, consumeCount);
163  
164                  if (_state.SubChannel == 0)
165                  {
166                      _3dClass.LoadInlineData(data);
167                  }
168                  else if (_state.SubChannel == 1)
169                  {
170                      _computeClass.LoadInlineData(data);
171                  }
172                  else /* if (_state.SubChannel == 2) */
173                  {
174                      _i2mClass.LoadInlineData(data);
175                  }
176  
177                  offset += consumeCount - 1;
178                  _state.MethodCount -= consumeCount;
179  
180                  return true;
181              }
182  
183              return false;
184          }
185  
186          /// <summary>
187          /// Tries to perform a fast constant buffer data update.
188          /// If successful, all data will be copied at once, and <see cref="CompressedMethod.MethodCount"/> + 1
189          /// command buffer entries will be consumed.
190          /// </summary>
191          /// <param name="meth">Compressed method to be checked</param>
192          /// <param name="commandBuffer">Command buffer where <paramref name="meth"/> is contained</param>
193          /// <param name="offset">Offset at <paramref name="commandBuffer"/> where <paramref name="meth"/> is located</param>
194          /// <returns>True if the fast copy was successful, false otherwise</returns>
195          [MethodImpl(MethodImplOptions.AggressiveInlining)]
196          private bool TryFastUniformBufferUpdate(CompressedMethod meth, ReadOnlySpan<int> commandBuffer, int offset)
197          {
198              int availableCount = commandBuffer.Length - offset;
199  
200              if (meth.MethodAddress == UniformBufferUpdateDataMethodOffset &&
201                  meth.MethodCount < availableCount &&
202                  meth.SecOp == SecOp.NonIncMethod)
203              {
204                  _3dClass.ConstantBufferUpdate(commandBuffer.Slice(offset + 1, meth.MethodCount));
205  
206                  return true;
207              }
208  
209              return false;
210          }
211  
212          /// <summary>
213          /// Sends a uncompressed method for processing by the graphics pipeline.
214          /// </summary>
215          /// <param name="gpuVa">GPU virtual address where the command word is located</param>
216          /// <param name="meth">Method to be processed</param>
217          private void Send(ulong gpuVa, int offset, int argument, int subChannel, bool isLastCall)
218          {
219              if (offset < 0x60)
220              {
221                  _fifoClass.Write(offset * 4, argument);
222              }
223              else if (offset < 0xe00)
224              {
225                  offset *= 4;
226  
227                  switch (subChannel)
228                  {
229                      case 0:
230                          _3dClass.Write(offset, argument);
231                          break;
232                      case 1:
233                          _computeClass.Write(offset, argument);
234                          break;
235                      case 2:
236                          _i2mClass.Write(offset, argument);
237                          break;
238                      case 3:
239                          _2dClass.Write(offset, argument);
240                          break;
241                      case 4:
242                          _dmaClass.Write(offset, argument);
243                          break;
244                  }
245              }
246              else
247              {
248                  IDeviceState state = subChannel switch
249                  {
250                      0 => _3dClass,
251                      3 => _2dClass,
252                      _ => null,
253                  };
254  
255                  if (state != null)
256                  {
257                      int macroIndex = (offset >> 1) & MacroIndexMask;
258  
259                      if ((offset & 1) != 0)
260                      {
261                          _fifoClass.MmePushArgument(macroIndex, gpuVa, argument);
262                      }
263                      else
264                      {
265                          _fifoClass.MmeStart(macroIndex, argument);
266                      }
267  
268                      if (isLastCall)
269                      {
270                          _fifoClass.CallMme(macroIndex, state);
271  
272                          _3dClass.PerformDeferredDraws();
273                      }
274                  }
275              }
276          }
277  
278          /// <summary>
279          /// Writes data directly to the state of the specified class.
280          /// </summary>
281          /// <param name="classId">ID of the class to write the data into</param>
282          /// <param name="offset">State offset in bytes</param>
283          /// <param name="value">Value to be written</param>
284          public void Write(ClassId classId, int offset, int value)
285          {
286              switch (classId)
287              {
288                  case ClassId.Threed:
289                      _3dClass.Write(offset, value);
290                      break;
291                  case ClassId.Compute:
292                      _computeClass.Write(offset, value);
293                      break;
294                  case ClassId.InlineToMemory:
295                      _i2mClass.Write(offset, value);
296                      break;
297                  case ClassId.Twod:
298                      _2dClass.Write(offset, value);
299                      break;
300                  case ClassId.Dma:
301                      _dmaClass.Write(offset, value);
302                      break;
303                  case ClassId.GPFifo:
304                      _fifoClass.Write(offset, value);
305                      break;
306              }
307          }
308  
309          /// <summary>
310          /// Sets the shadow ram control value of all sub-channels.
311          /// </summary>
312          /// <param name="control">New shadow ram control value</param>
313          public void SetShadowRamControl(int control)
314          {
315              _3dClass.SetShadowRamControl(control);
316          }
317  
318          /// <summary>
319          /// Forces a full host state update by marking all state as modified,
320          /// and also requests all GPU resources in use to be rebound.
321          /// </summary>
322          public void ForceAllDirty()
323          {
324              _3dClass.ForceStateDirty();
325              _channel.BufferManager.Rebind();
326              _channel.TextureManager.Rebind();
327          }
328  
329          /// <summary>
330          /// Perform any deferred draws.
331          /// </summary>
332          public void PerformDeferredDraws()
333          {
334              _3dClass.PerformDeferredDraws();
335          }
336  
337          protected virtual void Dispose(bool disposing)
338          {
339              if (disposing)
340              {
341                  _3dClass.Dispose();
342              }
343          }
344  
345          public void Dispose()
346          {
347              Dispose(true);
348              GC.SuppressFinalize(this);
349          }
350      }
351  }