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 }