mirror of
https://git.naxdy.org/Mirror/Ryujinx.git
synced 2025-03-25 09:20:17 +00:00
* feat: add nanosleep for linux and macos * Add Windows 0.5ms sleep - Imprecise waits for longer waits with clock alignment - 1/4 the spin time on vsync timer * Remove old experiment * Fix event leak * Tweaking for MacOS * Linux tweaks, nanosleep vsync improvement * Fix overbias * Cleanup * Fix realignment * Add some docs and some cleanup NanosleepPool needs more, Nanosleep has some benchmark code that needs removed. * Rename "Microsleep" to "PreciseSleep" Might have been confused with "microseconds", which no measurement is performed in. * Remove nanosleep measurement * Remove unused debug logging * Nanosleep Pool Documentation * More cleanup * Whitespace * Formatting * Address Feedback * Allow SleepUntilTimePoint to take EventWaitHandle * Remove `_chrono` stopwatch in SurfaceFlinger * Move spinwaiting logic to PreciseSleepHelper Technically, these achieve different things, but having them here makes them easier to reuse or tune.
160 lines
5.9 KiB
C#
160 lines
5.9 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.Versioning;
|
|
|
|
namespace Ryujinx.Common.PreciseSleep
|
|
{
|
|
/// <summary>
|
|
/// Access to Linux/MacOS nanosleep, with platform specific bias to improve precision.
|
|
/// </summary>
|
|
[SupportedOSPlatform("macos")]
|
|
[SupportedOSPlatform("linux")]
|
|
[SupportedOSPlatform("android")]
|
|
[SupportedOSPlatform("ios")]
|
|
internal static partial class Nanosleep
|
|
{
|
|
private const long LinuxBaseNanosleepBias = 50000; // 0.05ms
|
|
|
|
// Penalty for max allowed sleep duration
|
|
private const long LinuxNanosleepAccuracyPenaltyThreshold = 200000; // 0.2ms
|
|
private const long LinuxNanosleepAccuracyPenalty = 30000; // 0.03ms
|
|
|
|
// Penalty for base sleep duration
|
|
private const long LinuxNanosleepBasePenaltyThreshold = 500000; // 0.5ms
|
|
private const long LinuxNanosleepBasePenalty = 30000; // 0.03ms
|
|
private const long LinuxNanosleepPenaltyPerMillisecond = 18000; // 0.018ms
|
|
private const long LinuxNanosleepPenaltyCap = 18000; // 0.018ms
|
|
|
|
private const long LinuxStrictBiasOffset = 150_000; // 0.15ms
|
|
|
|
// Nanosleep duration is biased depending on the requested timeout on MacOS.
|
|
// These match the results when measuring on an M1 processor at AboveNormal priority.
|
|
private const long MacosBaseNanosleepBias = 5000; // 0.005ms
|
|
private const long MacosBiasPerMillisecond = 140000; // 0.14ms
|
|
private const long MacosBiasMaxNanoseconds = 20_000_000; // 20ms
|
|
private const long MacosStrictBiasOffset = 150_000; // 0.15ms
|
|
|
|
public static long Bias { get; }
|
|
|
|
/// <summary>
|
|
/// Get bias for a given nanosecond timeout.
|
|
/// Some platforms calculate their bias differently, this method can be used to counteract it.
|
|
/// </summary>
|
|
/// <param name="timeoutNs">Nanosecond timeout</param>
|
|
/// <returns>Bias in nanoseconds</returns>
|
|
public static long GetBias(long timeoutNs)
|
|
{
|
|
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
|
{
|
|
long biasNs = Math.Min(timeoutNs, MacosBiasMaxNanoseconds);
|
|
return MacosBaseNanosleepBias + biasNs * MacosBiasPerMillisecond / 1_000_000;
|
|
}
|
|
else
|
|
{
|
|
long bias = LinuxBaseNanosleepBias;
|
|
|
|
if (timeoutNs > LinuxNanosleepBasePenaltyThreshold)
|
|
{
|
|
long penalty = (timeoutNs - LinuxNanosleepBasePenaltyThreshold) * LinuxNanosleepPenaltyPerMillisecond / 1_000_000;
|
|
bias += LinuxNanosleepBasePenalty + Math.Min(LinuxNanosleepPenaltyCap, penalty);
|
|
}
|
|
|
|
return bias;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a stricter bias for a given nanosecond timeout,
|
|
/// which can improve the chances the sleep completes before the timeout.
|
|
/// Some platforms calculate their bias differently, this method can be used to counteract it.
|
|
/// </summary>
|
|
/// <param name="timeoutNs">Nanosecond timeout</param>
|
|
/// <returns>Strict bias in nanoseconds</returns>
|
|
public static long GetStrictBias(long timeoutNs)
|
|
{
|
|
if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
|
|
{
|
|
return GetBias(timeoutNs) + MacosStrictBiasOffset;
|
|
}
|
|
else
|
|
{
|
|
long bias = GetBias(timeoutNs) + LinuxStrictBiasOffset;
|
|
|
|
if (timeoutNs > LinuxNanosleepAccuracyPenaltyThreshold)
|
|
{
|
|
bias += LinuxNanosleepAccuracyPenalty;
|
|
}
|
|
|
|
return bias;
|
|
}
|
|
}
|
|
|
|
static Nanosleep()
|
|
{
|
|
Bias = GetBias(0);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
private struct Timespec
|
|
{
|
|
public long tv_sec; // Seconds
|
|
public long tv_nsec; // Nanoseconds
|
|
}
|
|
|
|
[LibraryImport("libc", SetLastError = true)]
|
|
private static partial int nanosleep(ref Timespec req, ref Timespec rem);
|
|
|
|
/// <summary>
|
|
/// Convert a timeout in nanoseconds to a timespec for nanosleep.
|
|
/// </summary>
|
|
/// <param name="nanoseconds">Timeout in nanoseconds</param>
|
|
/// <returns>Timespec for nanosleep</returns>
|
|
private static Timespec GetTimespecFromNanoseconds(ulong nanoseconds)
|
|
{
|
|
return new Timespec
|
|
{
|
|
tv_sec = (long)(nanoseconds / 1_000_000_000),
|
|
tv_nsec = (long)(nanoseconds % 1_000_000_000)
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sleep for approximately a given time period in nanoseconds.
|
|
/// </summary>
|
|
/// <param name="nanoseconds">Time to sleep for in nanoseconds</param>
|
|
public static void Sleep(long nanoseconds)
|
|
{
|
|
nanoseconds -= GetBias(nanoseconds);
|
|
|
|
if (nanoseconds >= 0)
|
|
{
|
|
Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
|
|
Timespec rem = new();
|
|
|
|
nanosleep(ref req, ref rem);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sleep for at most a given time period in nanoseconds.
|
|
/// Uses a stricter bias to wake before the requested duration.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Due to OS scheduling behaviour, this timeframe may still be missed.
|
|
/// </remarks>
|
|
/// <param name="nanoseconds">Maximum allowed time for sleep</param>
|
|
public static void SleepAtMost(long nanoseconds)
|
|
{
|
|
// Stricter bias to ensure we wake before the timepoint.
|
|
nanoseconds -= GetStrictBias(nanoseconds);
|
|
|
|
if (nanoseconds >= 0)
|
|
{
|
|
Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
|
|
Timespec rem = new();
|
|
|
|
nanosleep(ref req, ref rem);
|
|
}
|
|
}
|
|
}
|
|
}
|