/ src / Ryujinx.Graphics.Gpu / GpuContext.cs
GpuContext.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Graphics.Device;
  3  using Ryujinx.Graphics.GAL;
  4  using Ryujinx.Graphics.Gpu.Engine.GPFifo;
  5  using Ryujinx.Graphics.Gpu.Memory;
  6  using Ryujinx.Graphics.Gpu.Shader;
  7  using Ryujinx.Graphics.Gpu.Synchronization;
  8  using System;
  9  using System.Collections.Concurrent;
 10  using System.Collections.Generic;
 11  using System.Threading;
 12  
 13  namespace Ryujinx.Graphics.Gpu
 14  {
 15      /// <summary>
 16      /// GPU emulation context.
 17      /// </summary>
 18      public sealed class GpuContext : IDisposable
 19      {
 20          private const int NsToTicksFractionNumerator = 384;
 21          private const int NsToTicksFractionDenominator = 625;
 22  
 23          /// <summary>
 24          /// Event signaled when the host emulation context is ready to be used by the gpu context.
 25          /// </summary>
 26          public ManualResetEvent HostInitalized { get; }
 27  
 28          /// <summary>
 29          /// Host renderer.
 30          /// </summary>
 31          public IRenderer Renderer { get; }
 32  
 33          /// <summary>
 34          /// GPU General Purpose FIFO queue.
 35          /// </summary>
 36          public GPFifoDevice GPFifo { get; }
 37  
 38          /// <summary>
 39          /// GPU synchronization manager.
 40          /// </summary>
 41          public SynchronizationManager Synchronization { get; }
 42  
 43          /// <summary>
 44          /// Presentation window.
 45          /// </summary>
 46          public Window Window { get; }
 47  
 48          /// <summary>
 49          /// Internal sequence number, used to avoid needless resource data updates
 50          /// in the middle of a command buffer before synchronizations.
 51          /// </summary>
 52          internal int SequenceNumber { get; private set; }
 53  
 54          /// <summary>
 55          /// Internal sync number, used to denote points at which host synchronization can be requested.
 56          /// </summary>
 57          internal ulong SyncNumber { get; private set; }
 58  
 59          /// <summary>
 60          /// Actions to be performed when a CPU waiting syncpoint or barrier is triggered.
 61          /// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
 62          /// and the SyncNumber will be incremented.
 63          /// </summary>
 64          internal List<ISyncActionHandler> SyncActions { get; }
 65  
 66          /// <summary>
 67          /// Actions to be performed when a CPU waiting syncpoint is triggered.
 68          /// If there are more than 0 items when this happens, a host sync object will be generated for the given <see cref="SyncNumber"/>,
 69          /// and the SyncNumber will be incremented.
 70          /// </summary>
 71          internal List<ISyncActionHandler> SyncpointActions { get; }
 72  
 73          /// <summary>
 74          /// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration
 75          /// copies have completed on the GPU, and their data can be freed.
 76          /// </summary>
 77          internal List<BufferMigration> BufferMigrations { get; }
 78  
 79          /// <summary>
 80          /// Queue with deferred actions that must run on the render thread.
 81          /// </summary>
 82          internal Queue<Action> DeferredActions { get; }
 83  
 84          /// <summary>
 85          /// Registry with physical memories that can be used with this GPU context, keyed by owner process ID.
 86          /// </summary>
 87          internal ConcurrentDictionary<ulong, PhysicalMemory> PhysicalMemoryRegistry { get; }
 88  
 89          /// <summary>
 90          /// Support buffer updater.
 91          /// </summary>
 92          internal SupportBufferUpdater SupportBufferUpdater { get; }
 93  
 94          /// <summary>
 95          /// Host hardware capabilities.
 96          /// </summary>
 97          internal Capabilities Capabilities;
 98  
 99          /// <summary>
100          /// Event for signalling shader cache loading progress.
101          /// </summary>
102          public event Action<ShaderCacheState, int, int> ShaderCacheStateChanged;
103  
104          private Thread _gpuThread;
105          private bool _pendingSync;
106  
107          private long _modifiedSequence;
108          private readonly ulong _firstTimestamp;
109  
110          private readonly ManualResetEvent _gpuReadyEvent;
111  
112          /// <summary>
113          /// Creates a new instance of the GPU emulation context.
114          /// </summary>
115          /// <param name="renderer">Host renderer</param>
116          public GpuContext(IRenderer renderer)
117          {
118              Renderer = renderer;
119  
120              GPFifo = new GPFifoDevice(this);
121  
122              Synchronization = new SynchronizationManager();
123  
124              Window = new Window(this);
125  
126              HostInitalized = new ManualResetEvent(false);
127              _gpuReadyEvent = new ManualResetEvent(false);
128  
129              SyncActions = new List<ISyncActionHandler>();
130              SyncpointActions = new List<ISyncActionHandler>();
131              BufferMigrations = new List<BufferMigration>();
132  
133              DeferredActions = new Queue<Action>();
134  
135              PhysicalMemoryRegistry = new ConcurrentDictionary<ulong, PhysicalMemory>();
136  
137              SupportBufferUpdater = new SupportBufferUpdater(renderer);
138  
139              _firstTimestamp = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
140          }
141  
142          /// <summary>
143          /// Creates a new GPU channel.
144          /// </summary>
145          /// <returns>The GPU channel</returns>
146          public GpuChannel CreateChannel()
147          {
148              return new GpuChannel(this);
149          }
150  
151          /// <summary>
152          /// Creates a new GPU memory manager.
153          /// </summary>
154          /// <param name="pid">ID of the process that owns the memory manager</param>
155          /// <returns>The memory manager</returns>
156          /// <exception cref="ArgumentException">Thrown when <paramref name="pid"/> is invalid</exception>
157          public MemoryManager CreateMemoryManager(ulong pid)
158          {
159              if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
160              {
161                  throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
162              }
163  
164              return new MemoryManager(physicalMemory);
165          }
166  
167          /// <summary>
168          /// Creates a new device memory manager.
169          /// </summary>
170          /// <param name="pid">ID of the process that owns the memory manager</param>
171          /// <returns>The memory manager</returns>
172          /// <exception cref="ArgumentException">Thrown when <paramref name="pid"/> is invalid</exception>
173          public DeviceMemoryManager CreateDeviceMemoryManager(ulong pid)
174          {
175              if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory))
176              {
177                  throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid));
178              }
179  
180              return physicalMemory.CreateDeviceMemoryManager();
181          }
182  
183          /// <summary>
184          /// Registers virtual memory used by a process for GPU memory access, caching and read/write tracking.
185          /// </summary>
186          /// <param name="pid">ID of the process that owns <paramref name="cpuMemory"/></param>
187          /// <param name="cpuMemory">Virtual memory owned by the process</param>
188          /// <exception cref="ArgumentException">Thrown if <paramref name="pid"/> was already registered</exception>
189          public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory)
190          {
191              var physicalMemory = new PhysicalMemory(this, cpuMemory);
192              if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory))
193              {
194                  throw new ArgumentException("The PID was already registered", nameof(pid));
195              }
196  
197              physicalMemory.ShaderCache.ShaderCacheStateChanged += ShaderCacheStateUpdate;
198          }
199  
200          /// <summary>
201          /// Unregisters a process, indicating that its memory will no longer be used, and that caches can be freed.
202          /// </summary>
203          /// <param name="pid">ID of the process</param>
204          public void UnregisterProcess(ulong pid)
205          {
206              if (PhysicalMemoryRegistry.TryRemove(pid, out var physicalMemory))
207              {
208                  physicalMemory.ShaderCache.ShaderCacheStateChanged -= ShaderCacheStateUpdate;
209                  physicalMemory.Dispose();
210              }
211          }
212  
213          /// <summary>
214          /// Converts a nanoseconds timestamp value to Maxwell time ticks.
215          /// </summary>
216          /// <remarks>
217          /// The frequency is 614400000 Hz.
218          /// </remarks>
219          /// <param name="nanoseconds">Timestamp in nanoseconds</param>
220          /// <returns>Maxwell ticks</returns>
221          private static ulong ConvertNanosecondsToTicks(ulong nanoseconds)
222          {
223              // We need to divide first to avoid overflows.
224              // We fix up the result later by calculating the difference and adding
225              // that to the result.
226              ulong divided = nanoseconds / NsToTicksFractionDenominator;
227  
228              ulong rounded = divided * NsToTicksFractionDenominator;
229  
230              ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator;
231  
232              return divided * NsToTicksFractionNumerator + errorBias;
233          }
234  
235          /// <summary>
236          /// Gets a sequence number for resource modification ordering. This increments on each call.
237          /// </summary>
238          /// <returns>A sequence number for resource modification ordering</returns>
239          internal long GetModifiedSequence()
240          {
241              return _modifiedSequence++;
242          }
243  
244          /// <summary>
245          /// Gets the value of the GPU timer.
246          /// </summary>
247          /// <returns>The current GPU timestamp</returns>
248          internal ulong GetTimestamp()
249          {
250              // Guest timestamp will start at 0, instead of host value.
251              ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds) - _firstTimestamp;
252  
253              if (GraphicsConfig.FastGpuTime)
254              {
255                  // Divide by some amount to report time as if operations were performed faster than they really are.
256                  // This can prevent some games from switching to a lower resolution because rendering is too slow.
257                  ticks /= 256;
258              }
259  
260              return ticks;
261          }
262  
263          /// <summary>
264          /// Shader cache state update handler.
265          /// </summary>
266          /// <param name="state">Current state of the shader cache load process</param>
267          /// <param name="current">Number of the current shader being processed</param>
268          /// <param name="total">Total number of shaders to process</param>
269          private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total)
270          {
271              ShaderCacheStateChanged?.Invoke(state, current, total);
272          }
273  
274          /// <summary>
275          /// Initialize the GPU shader cache.
276          /// </summary>
277          public void InitializeShaderCache(CancellationToken cancellationToken)
278          {
279              HostInitalized.WaitOne();
280  
281              foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
282              {
283                  physicalMemory.ShaderCache.Initialize(cancellationToken);
284              }
285  
286              _gpuReadyEvent.Set();
287          }
288  
289          /// <summary>
290          /// Waits until the GPU is ready to receive commands.
291          /// </summary>
292          public void WaitUntilGpuReady()
293          {
294              _gpuReadyEvent.WaitOne();
295          }
296  
297          /// <summary>
298          /// Sets the current thread as the main GPU thread.
299          /// </summary>
300          public void SetGpuThread()
301          {
302              _gpuThread = Thread.CurrentThread;
303  
304              Capabilities = Renderer.GetCapabilities();
305          }
306  
307          /// <summary>
308          /// Checks if the current thread is the GPU thread.
309          /// </summary>
310          /// <returns>True if the thread is the GPU thread, false otherwise</returns>
311          public bool IsGpuThread()
312          {
313              return _gpuThread == Thread.CurrentThread;
314          }
315  
316          /// <summary>
317          /// Processes the queue of shaders that must save their binaries to the disk cache.
318          /// </summary>
319          public void ProcessShaderCacheQueue()
320          {
321              foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
322              {
323                  physicalMemory.ShaderCache.ProcessShaderCacheQueue();
324              }
325          }
326  
327          /// <summary>
328          /// Advances internal sequence number.
329          /// This forces the update of any modified GPU resource.
330          /// </summary>
331          internal void AdvanceSequence()
332          {
333              SequenceNumber++;
334          }
335  
336          /// <summary>
337          /// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases,
338          /// and the migration copy has completed.
339          /// </summary>
340          /// <param name="migration">The buffer migration</param>
341          internal void RegisterBufferMigration(BufferMigration migration)
342          {
343              BufferMigrations.Add(migration);
344              _pendingSync = true;
345          }
346  
347          /// <summary>
348          /// Registers an action to be performed the next time a syncpoint is incremented.
349          /// This will also ensure a host sync object is created, and <see cref="SyncNumber"/> is incremented.
350          /// </summary>
351          /// <param name="action">The resource with action to be performed on sync object creation</param>
352          /// <param name="syncpointOnly">True if the sync action should only run when syncpoints are incremented</param>
353          internal void RegisterSyncAction(ISyncActionHandler action, bool syncpointOnly = false)
354          {
355              if (syncpointOnly)
356              {
357                  SyncpointActions.Add(action);
358              }
359              else
360              {
361                  SyncActions.Add(action);
362                  _pendingSync = true;
363              }
364          }
365  
366          /// <summary>
367          /// Creates a host sync object if there are any pending sync actions. The actions will then be called.
368          /// If no actions are present, a host sync object is not created.
369          /// </summary>
370          /// <param name="flags">Modifiers for how host sync should be created</param>
371          internal void CreateHostSyncIfNeeded(HostSyncFlags flags)
372          {
373              bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint);
374              bool strict = flags.HasFlag(HostSyncFlags.Strict);
375              bool force = flags.HasFlag(HostSyncFlags.Force);
376  
377              if (BufferMigrations.Count > 0)
378              {
379                  ulong currentSyncNumber = Renderer.GetCurrentSync();
380  
381                  for (int i = 0; i < BufferMigrations.Count; i++)
382                  {
383                      BufferMigration migration = BufferMigrations[i];
384                      long diff = (long)(currentSyncNumber - migration.SyncNumber);
385  
386                      if (diff >= 0)
387                      {
388                          migration.Dispose();
389                          BufferMigrations.RemoveAt(i--);
390                      }
391                  }
392              }
393  
394              if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0))
395              {
396                  foreach (var action in SyncActions)
397                  {
398                      action.SyncPreAction(syncpoint);
399                  }
400  
401                  foreach (var action in SyncpointActions)
402                  {
403                      action.SyncPreAction(syncpoint);
404                  }
405  
406                  Renderer.CreateSync(SyncNumber, strict);
407  
408                  SyncNumber++;
409  
410                  SyncActions.RemoveAll(action => action.SyncAction(syncpoint));
411                  SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint));
412              }
413  
414              _pendingSync = false;
415          }
416  
417          /// <summary>
418          /// Performs deferred actions.
419          /// This is useful for actions that must run on the render thread, such as resource disposal.
420          /// </summary>
421          internal void RunDeferredActions()
422          {
423              while (DeferredActions.TryDequeue(out Action action))
424              {
425                  action();
426              }
427          }
428  
429          /// <summary>
430          /// Disposes all GPU resources currently cached.
431          /// It's an error to push any GPU commands after disposal.
432          /// Additionally, the GPU commands FIFO must be empty for disposal,
433          /// and processing of all commands must have finished.
434          /// </summary>
435          public void Dispose()
436          {
437              GPFifo.Dispose();
438              HostInitalized.Dispose();
439              _gpuReadyEvent.Dispose();
440  
441              // Has to be disposed before processing deferred actions, as it will produce some.
442              foreach (var physicalMemory in PhysicalMemoryRegistry.Values)
443              {
444                  physicalMemory.Dispose();
445              }
446  
447              SupportBufferUpdater.Dispose();
448  
449              PhysicalMemoryRegistry.Clear();
450  
451              RunDeferredActions();
452  
453              Renderer.Dispose();
454          }
455      }
456  }