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)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
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)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
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)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
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)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
_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)
{
if (id >= MaxHardwareSyncpoints)
{
throw new ArgumentOutOfRangeException(nameof(id));
}
// 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;
}
}
}