diff options
author | riperiperi <rhy3756547@hotmail.com> | 2023-11-30 10:39:42 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-30 15:39:42 -0300 |
commit | 1be668e68a1937f2af239e2707ab914286018892 (patch) | |
tree | d3ecb590d444ab9b392928ae1e223930313615e6 /src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs | |
parent | 21cd4c0c00e4a06e399c93419c8f9eff0e663bfb (diff) |
HLE: Add OS-specific precise sleep methods to reduce spinwaiting (#5948)1.1.1094
* 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.
Diffstat (limited to 'src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs')
-rw-r--r-- | src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs | 92 |
1 files changed, 92 insertions, 0 deletions
diff --git a/src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs b/src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs new file mode 100644 index 00000000..87c10d18 --- /dev/null +++ b/src/Ryujinx.Common/PreciseSleep/WindowsSleepEvent.cs @@ -0,0 +1,92 @@ +using Ryujinx.Common.SystemInterop; +using System; +using System.Runtime.Versioning; +using System.Threading; + +namespace Ryujinx.Common.PreciseSleep +{ + /// <summary> + /// A precise sleep event that uses Windows specific methods to increase clock resolution beyond 1ms, + /// use the clock's phase for more precise waits, and potentially align timepoints with it. + /// </summary> + [SupportedOSPlatform("windows")] + internal class WindowsSleepEvent : IPreciseSleepEvent + { + /// <summary> + /// The clock can drift a bit, so add this to encourage the clock to still wait if the next tick is forecasted slightly before it. + /// </summary> + private const long ErrorBias = 50000; + + /// <summary> + /// Allowed to be 0.05ms away from the clock granularity to reduce precision. + /// </summary> + private const long ClockAlignedBias = 50000; + + /// <summary> + /// The fraction of clock granularity above the timepoint that will align it down to the lower timepoint. + /// Currently set to the lower 1/4, so for 0.5ms granularity: 0.1ms would be rounded down, 0.2 ms would be rounded up. + /// </summary> + private const long ReverseTimePointFraction = 4; + + private readonly AutoResetEvent _waitEvent = new(false); + private readonly WindowsGranularTimer _timer = WindowsGranularTimer.Instance; + + /// <summary> + /// Set to true to disable timepoint realignment. + /// </summary> + public bool Precise { get; set; } = false; + + public long AdjustTimePoint(long timePoint, long timeoutNs) + { + if (Precise || timePoint == long.MaxValue) + { + return timePoint; + } + + // Does the timeout align with the host clock? + + long granularity = _timer.GranularityNs; + long misalignment = timeoutNs % granularity; + + if ((misalignment < ClockAlignedBias || misalignment > granularity - ClockAlignedBias) && timeoutNs > ClockAlignedBias) + { + // Inaccurate sleep for 0.5ms increments, typically. + + (long low, long high) = _timer.ReturnNearestTicks(timePoint); + + if (timePoint - low < _timer.GranularityTicks / ReverseTimePointFraction) + { + timePoint = low; + } + else + { + timePoint = high; + } + } + + return timePoint; + } + + public bool SleepUntil(long timePoint) + { + return _timer.SleepUntilTimePoint(_waitEvent, timePoint + (ErrorBias * PerformanceCounter.TicksPerMillisecond) / 1_000_000); + } + + public void Sleep() + { + _waitEvent.WaitOne(); + } + + public void Signal() + { + _waitEvent.Set(); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + + _waitEvent.Dispose(); + } + } +} |