using Ryujinx.Common; using Ryujinx.Graphics.Device; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.GPFifo; using Ryujinx.Graphics.Gpu.Memory; using Ryujinx.Graphics.Gpu.Shader; using Ryujinx.Graphics.Gpu.Synchronization; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; namespace Ryujinx.Graphics.Gpu { /// /// GPU emulation context. /// public sealed class GpuContext : IDisposable { private const int NsToTicksFractionNumerator = 384; private const int NsToTicksFractionDenominator = 625; /// /// Event signaled when the host emulation context is ready to be used by the gpu context. /// public ManualResetEvent HostInitalized { get; } /// /// Host renderer. /// public IRenderer Renderer { get; } /// /// GPU General Purpose FIFO queue. /// public GPFifoDevice GPFifo { get; } /// /// GPU synchronization manager. /// public SynchronizationManager Synchronization { get; } /// /// Presentation window. /// public Window Window { get; } /// /// Internal sequence number, used to avoid needless resource data updates /// in the middle of a command buffer before synchronizations. /// internal int SequenceNumber { get; private set; } /// /// Internal sync number, used to denote points at which host synchronization can be requested. /// internal ulong SyncNumber { get; private set; } /// /// Actions to be performed when a CPU waiting syncpoint or barrier is triggered. /// If there are more than 0 items when this happens, a host sync object will be generated for the given , /// and the SyncNumber will be incremented. /// internal List SyncActions { get; } /// /// Actions to be performed when a CPU waiting syncpoint is triggered. /// If there are more than 0 items when this happens, a host sync object will be generated for the given , /// and the SyncNumber will be incremented. /// internal List SyncpointActions { get; } /// /// Buffer migrations that are currently in-flight. These are checked whenever sync is created to determine if buffer migration /// copies have completed on the GPU, and their data can be freed. /// internal List BufferMigrations { get; } /// /// Queue with deferred actions that must run on the render thread. /// internal Queue DeferredActions { get; } /// /// Registry with physical memories that can be used with this GPU context, keyed by owner process ID. /// internal ConcurrentDictionary PhysicalMemoryRegistry { get; } /// /// Support buffer updater. /// internal SupportBufferUpdater SupportBufferUpdater { get; } /// /// Host hardware capabilities. /// internal Capabilities Capabilities; /// /// Event for signalling shader cache loading progress. /// public event Action ShaderCacheStateChanged; private Thread _gpuThread; private bool _pendingSync; private long _modifiedSequence; private readonly ulong _firstTimestamp; private readonly ManualResetEvent _gpuReadyEvent; /// /// Creates a new instance of the GPU emulation context. /// /// Host renderer public GpuContext(IRenderer renderer) { Renderer = renderer; GPFifo = new GPFifoDevice(this); Synchronization = new SynchronizationManager(); Window = new Window(this); HostInitalized = new ManualResetEvent(false); _gpuReadyEvent = new ManualResetEvent(false); SyncActions = new List(); SyncpointActions = new List(); BufferMigrations = new List(); DeferredActions = new Queue(); PhysicalMemoryRegistry = new ConcurrentDictionary(); SupportBufferUpdater = new SupportBufferUpdater(renderer); _firstTimestamp = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds); } /// /// Creates a new GPU channel. /// /// The GPU channel public GpuChannel CreateChannel() { return new GpuChannel(this); } /// /// Creates a new GPU memory manager. /// /// ID of the process that owns the memory manager /// The memory manager /// Thrown when is invalid public MemoryManager CreateMemoryManager(ulong pid) { if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) { throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid)); } return new MemoryManager(physicalMemory); } /// /// Creates a new device memory manager. /// /// ID of the process that owns the memory manager /// The memory manager /// Thrown when is invalid public DeviceMemoryManager CreateDeviceMemoryManager(ulong pid) { if (!PhysicalMemoryRegistry.TryGetValue(pid, out var physicalMemory)) { throw new ArgumentException("The PID is invalid or the process was not registered", nameof(pid)); } return physicalMemory.CreateDeviceMemoryManager(); } /// /// Registers virtual memory used by a process for GPU memory access, caching and read/write tracking. /// /// ID of the process that owns /// Virtual memory owned by the process /// Thrown if was already registered public void RegisterProcess(ulong pid, Cpu.IVirtualMemoryManagerTracked cpuMemory) { var physicalMemory = new PhysicalMemory(this, cpuMemory); if (!PhysicalMemoryRegistry.TryAdd(pid, physicalMemory)) { throw new ArgumentException("The PID was already registered", nameof(pid)); } physicalMemory.ShaderCache.ShaderCacheStateChanged += ShaderCacheStateUpdate; } /// /// Unregisters a process, indicating that its memory will no longer be used, and that caches can be freed. /// /// ID of the process public void UnregisterProcess(ulong pid) { if (PhysicalMemoryRegistry.TryRemove(pid, out var physicalMemory)) { physicalMemory.ShaderCache.ShaderCacheStateChanged -= ShaderCacheStateUpdate; physicalMemory.Dispose(); } } /// /// Converts a nanoseconds timestamp value to Maxwell time ticks. /// /// /// The frequency is 614400000 Hz. /// /// Timestamp in nanoseconds /// Maxwell ticks private static ulong ConvertNanosecondsToTicks(ulong nanoseconds) { // We need to divide first to avoid overflows. // We fix up the result later by calculating the difference and adding // that to the result. ulong divided = nanoseconds / NsToTicksFractionDenominator; ulong rounded = divided * NsToTicksFractionDenominator; ulong errorBias = (nanoseconds - rounded) * NsToTicksFractionNumerator / NsToTicksFractionDenominator; return divided * NsToTicksFractionNumerator + errorBias; } /// /// Gets a sequence number for resource modification ordering. This increments on each call. /// /// A sequence number for resource modification ordering internal long GetModifiedSequence() { return _modifiedSequence++; } /// /// Gets the value of the GPU timer. /// /// The current GPU timestamp internal ulong GetTimestamp() { // Guest timestamp will start at 0, instead of host value. ulong ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds) - _firstTimestamp; if (GraphicsConfig.FastGpuTime) { // Divide by some amount to report time as if operations were performed faster than they really are. // This can prevent some games from switching to a lower resolution because rendering is too slow. ticks /= 256; } return ticks; } /// /// Shader cache state update handler. /// /// Current state of the shader cache load process /// Number of the current shader being processed /// Total number of shaders to process private void ShaderCacheStateUpdate(ShaderCacheState state, int current, int total) { ShaderCacheStateChanged?.Invoke(state, current, total); } /// /// Initialize the GPU shader cache. /// public void InitializeShaderCache(CancellationToken cancellationToken) { HostInitalized.WaitOne(); foreach (var physicalMemory in PhysicalMemoryRegistry.Values) { physicalMemory.ShaderCache.Initialize(cancellationToken); } _gpuReadyEvent.Set(); } /// /// Waits until the GPU is ready to receive commands. /// public void WaitUntilGpuReady() { _gpuReadyEvent.WaitOne(); } /// /// Sets the current thread as the main GPU thread. /// public void SetGpuThread() { _gpuThread = Thread.CurrentThread; Capabilities = Renderer.GetCapabilities(); } /// /// Checks if the current thread is the GPU thread. /// /// True if the thread is the GPU thread, false otherwise public bool IsGpuThread() { return _gpuThread == Thread.CurrentThread; } /// /// Processes the queue of shaders that must save their binaries to the disk cache. /// public void ProcessShaderCacheQueue() { foreach (var physicalMemory in PhysicalMemoryRegistry.Values) { physicalMemory.ShaderCache.ProcessShaderCacheQueue(); } } /// /// Advances internal sequence number. /// This forces the update of any modified GPU resource. /// internal void AdvanceSequence() { SequenceNumber++; } /// /// Registers a buffer migration. These are checked to see if they can be disposed when the sync number increases, /// and the migration copy has completed. /// /// The buffer migration internal void RegisterBufferMigration(BufferMigration migration) { BufferMigrations.Add(migration); _pendingSync = true; } /// /// Registers an action to be performed the next time a syncpoint is incremented. /// This will also ensure a host sync object is created, and is incremented. /// /// The resource with action to be performed on sync object creation /// True if the sync action should only run when syncpoints are incremented internal void RegisterSyncAction(ISyncActionHandler action, bool syncpointOnly = false) { if (syncpointOnly) { SyncpointActions.Add(action); } else { SyncActions.Add(action); _pendingSync = true; } } /// /// Creates a host sync object if there are any pending sync actions. The actions will then be called. /// If no actions are present, a host sync object is not created. /// /// Modifiers for how host sync should be created internal void CreateHostSyncIfNeeded(HostSyncFlags flags) { bool syncpoint = flags.HasFlag(HostSyncFlags.Syncpoint); bool strict = flags.HasFlag(HostSyncFlags.Strict); bool force = flags.HasFlag(HostSyncFlags.Force); if (BufferMigrations.Count > 0) { ulong currentSyncNumber = Renderer.GetCurrentSync(); for (int i = 0; i < BufferMigrations.Count; i++) { BufferMigration migration = BufferMigrations[i]; long diff = (long)(currentSyncNumber - migration.SyncNumber); if (diff >= 0) { migration.Dispose(); BufferMigrations.RemoveAt(i--); } } } if (force || _pendingSync || (syncpoint && SyncpointActions.Count > 0)) { foreach (var action in SyncActions) { action.SyncPreAction(syncpoint); } foreach (var action in SyncpointActions) { action.SyncPreAction(syncpoint); } Renderer.CreateSync(SyncNumber, strict); SyncNumber++; SyncActions.RemoveAll(action => action.SyncAction(syncpoint)); SyncpointActions.RemoveAll(action => action.SyncAction(syncpoint)); } _pendingSync = false; } /// /// Performs deferred actions. /// This is useful for actions that must run on the render thread, such as resource disposal. /// internal void RunDeferredActions() { while (DeferredActions.TryDequeue(out Action action)) { action(); } } /// /// Disposes all GPU resources currently cached. /// It's an error to push any GPU commands after disposal. /// Additionally, the GPU commands FIFO must be empty for disposal, /// and processing of all commands must have finished. /// public void Dispose() { GPFifo.Dispose(); HostInitalized.Dispose(); _gpuReadyEvent.Dispose(); // Has to be disposed before processing deferred actions, as it will produce some. foreach (var physicalMemory in PhysicalMemoryRegistry.Values) { physicalMemory.Dispose(); } SupportBufferUpdater.Dispose(); PhysicalMemoryRegistry.Clear(); RunDeferredActions(); Renderer.Dispose(); } } }