using Ryujinx.Common.Logging; using System; using System.Threading; namespace Ryujinx.Graphics.Gpu.Synchronization { /// /// GPU synchronization manager. /// public class SynchronizationManager { /// /// The maximum number of syncpoints supported by the GM20B. /// public const int MaxHardwareSyncpoints = 192; /// /// Array containing all hardware syncpoints. /// private readonly Syncpoint[] _syncpoints; public SynchronizationManager() { _syncpoints = new Syncpoint[MaxHardwareSyncpoints]; for (uint i = 0; i < _syncpoints.Length; i++) { _syncpoints[i] = new Syncpoint(i); } } /// /// Increment the value of a syncpoint with a given id. /// /// The id of the syncpoint /// Thrown when id >= MaxHardwareSyncpoints /// The incremented value of the syncpoint public uint IncrementSyncpoint(uint id) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); return _syncpoints[id].Increment(); } /// /// Get the value of a syncpoint with a given id. /// /// The id of the syncpoint /// Thrown when id >= MaxHardwareSyncpoints /// The value of the syncpoint public uint GetSyncpointValue(uint id) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); return _syncpoints[id].Value; } /// /// Register a new callback on a syncpoint with a given id at a target threshold. /// The callback will be called once the threshold is reached and will automatically be unregistered. /// /// The id of the syncpoint /// The target threshold /// The callback to call when the threshold is reached /// Thrown when id >= MaxHardwareSyncpoints /// The created SyncpointWaiterHandle object or null if already past threshold public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action callback) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); return _syncpoints[id].RegisterCallback(threshold, callback); } /// /// Unregister a callback on a given syncpoint. /// /// The id of the syncpoint /// The waiter information to unregister /// Thrown when id >= MaxHardwareSyncpoints public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); _syncpoints[id].UnregisterCallback(waiterInformation); } /// /// Wait on a syncpoint with a given id at a target threshold. /// The callback will be called once the threshold is reached and will automatically be unregistered. /// /// The id of the syncpoint /// The target threshold /// The timeout /// Thrown when id >= MaxHardwareSyncpoints /// True if timed out public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout) { ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints); // TODO: Remove this when GPU channel scheduling will be implemented. if (timeout == Timeout.InfiniteTimeSpan) { timeout = TimeSpan.FromSeconds(1); } using ManualResetEvent waitEvent = new(false); var info = _syncpoints[id].RegisterCallback(threshold, (x) => waitEvent.Set()); if (info == null) { return false; } bool signaled = waitEvent.WaitOne(timeout); if (!signaled && info != null) { Logger.Error?.Print(LogClass.Gpu, $"Wait on syncpoint {id} for threshold {threshold} took more than {timeout.TotalMilliseconds}ms, resuming execution..."); _syncpoints[id].UnregisterCallback(info); } return !signaled; } } }