aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
diff options
context:
space:
mode:
authorMary <me@thog.eu>2021-11-28 13:15:26 +0100
committerGitHub <noreply@github.com>2021-11-28 13:15:26 +0100
commit7b040e51b078a16e979ad962ba1265f3be4bcb1d (patch)
treeb5ff26415d0b4c1f1ab21ae62695bf82852f44b3 /Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
parent786fb04d2005577fc17c5851e6d651054a1b80c4 (diff)
kernel: Fix sleep timing accuracy (#2828)
* kernel: Fix sleep timing accuracy This commit corrects some mistake while comparing reversing of kernel 13.x with our own. WaitAndCheckScheduledObjects timing accuracy was also improved. * Make KTimeManager.WaitAndCheckScheduledObjects spin wait for sub milliseconds Fix performance regression on Pokemon Let's Go games and possibly others. * Address rip's comment * kernel: Fix issues with timeout of -1 (0xFFFFFFFF) Fixes possible hang on Pokemon DP and possibly others
Diffstat (limited to 'Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs')
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs60
1 files changed, 54 insertions, 6 deletions
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
index f7a1c24a..4eb736f2 100644
--- a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
@@ -8,6 +8,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
{
class KTimeManager : IDisposable
{
+ public static readonly long DefaultTimeIncrementNanoseconds = ConvertGuestTicksToNanoseconds(2);
+
private class WaitingObject
{
public IKFutureSchedulerObject Object { get; }
@@ -24,6 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
private readonly List<WaitingObject> _waitingObjects;
private AutoResetEvent _waitEvent;
private bool _keepRunning;
+ private long _enforceWakeupFromSpinWait;
public KTimeManager(KernelContext context)
{
@@ -41,11 +44,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
public void ScheduleFutureInvocation(IKFutureSchedulerObject schedulerObj, long timeout)
{
- long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout);
+ long timePoint = PerformanceCounter.ElapsedTicks + ConvertNanosecondsToHostTicks(timeout);
lock (_context.CriticalSection.Lock)
{
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
+
+ if (timeout < 1000000)
+ {
+ Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
+ }
}
_waitEvent.Set();
@@ -61,27 +69,51 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
private void WaitAndCheckScheduledObjects()
{
+ SpinWait spinWait = new SpinWait();
+ WaitingObject next;
+
using (_waitEvent = new AutoResetEvent(false))
{
while (_keepRunning)
{
- WaitingObject next;
-
lock (_context.CriticalSection.Lock)
{
+ Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
+
next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
}
if (next != null)
{
- long timePoint = PerformanceCounter.ElapsedMilliseconds;
+ long timePoint = PerformanceCounter.ElapsedTicks;
if (next.TimePoint > timePoint)
{
- _waitEvent.WaitOne((int)(next.TimePoint - timePoint));
+ long ms = Math.Min((next.TimePoint - timePoint) / PerformanceCounter.TicksPerMillisecond, int.MaxValue);
+
+ if (ms > 0)
+ {
+ _waitEvent.WaitOne((int)ms);
+ }
+ else
+ {
+ while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks <= next.TimePoint)
+ {
+ if (spinWait.NextSpinWillYield)
+ {
+ Thread.Yield();
+
+ spinWait.Reset();
+ }
+
+ spinWait.SpinOnce();
+ }
+
+ spinWait.Reset();
+ }
}
- bool timeUp = PerformanceCounter.ElapsedMilliseconds >= next.TimePoint;
+ bool timeUp = PerformanceCounter.ElapsedTicks >= next.TimePoint;
if (timeUp)
{
@@ -119,6 +151,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
return time * 1000000;
}
+ public static long ConvertNanosecondsToHostTicks(long ns)
+ {
+ long nsDiv = ns / 1000000000;
+ long nsMod = ns % 1000000000;
+ long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000;
+ long tickMod = PerformanceCounter.TicksPerSecond % 1000000000;
+
+ long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000;
+ return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
+ }
+
+ public static long ConvertGuestTicksToNanoseconds(long ticks)
+ {
+ return (long)Math.Ceiling(ticks * (1000000000.0 / 19200000.0));
+ }
+
public static long ConvertHostTicksToTicks(long time)
{
return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);