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 }