Răsfoiți Sursa

Rewrite scheduler context switch code (#1786)

* Rewrite scheduler context switch code

* Fix race in UnmapIpcRestorePermission

* Fix thread exit issue that could leave the scheduler in a invalid state

* Change context switch method to not wait on guest thread, remove spin wait, use SignalAndWait to pass control

* Remove multi-core setting (it is always on now)

* Re-enable assert

* Remove multicore from default config and schema

* Fix race in KTimeManager
gdkchan 5 ani în urmă
părinte
comite
48278905d1
37 a modificat fișierele cu 1023 adăugiri și 1103 ștergeri
  1. 11 20
      Ryujinx.Common/Configuration/ConfigurationState.cs
  2. 2 2
      Ryujinx.Common/PerformanceCounter.cs
  3. 1 22
      Ryujinx.HLE/HOS/Horizon.cs
  4. 12 3
      Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs
  5. 37 50
      Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
  6. 5 5
      Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs
  7. 2 2
      Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs
  8. 3 3
      Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs
  9. 3 3
      Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs
  10. 2 1
      Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs
  11. 34 5
      Ryujinx.HLE/HOS/Kernel/KernelContext.cs
  12. 41 16
      Ryujinx.HLE/HOS/Kernel/KernelStatic.cs
  13. 7 7
      Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs
  14. 1 1
      Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs
  15. 2 2
      Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs
  16. 26 12
      Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
  17. 84 89
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs
  18. 1 1
      Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs
  19. 0 66
      Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs
  20. 0 150
      Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs
  21. 10 10
      Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs
  22. 1 1
      Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs
  23. 0 79
      Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs
  24. 21 49
      Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs
  25. 17 29
      Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs
  26. 483 123
      Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
  27. 1 1
      Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs
  28. 142 271
      Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
  29. 19 0
      Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs
  30. 24 20
      Ryujinx.HLE/HOS/Services/ServerBase.cs
  31. 4 15
      Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs
  32. 0 5
      Ryujinx.HLE/Switch.cs
  33. 1 2
      Ryujinx/Config.json
  34. 25 0
      Ryujinx/THIRDPARTY.md
  35. 1 8
      Ryujinx/Ui/SettingsWindow.cs
  36. 0 18
      Ryujinx/Ui/SettingsWindow.glade
  37. 0 12
      Ryujinx/_schema.json

+ 11 - 20
Ryujinx.Common/Configuration/ConfigurationState.cs

@@ -25,7 +25,7 @@ namespace Ryujinx.Configuration
                 public ReactiveObject<bool> DevColumn        { get; private set; }
                 public ReactiveObject<bool> VersionColumn    { get; private set; }
                 public ReactiveObject<bool> TimePlayedColumn { get; private set; }
-                public ReactiveObject<bool> LastPlayedColumn { get; private set; } 
+                public ReactiveObject<bool> LastPlayedColumn { get; private set; }
                 public ReactiveObject<bool> FileExtColumn    { get; private set; }
                 public ReactiveObject<bool> FileSizeColumn   { get; private set; }
                 public ReactiveObject<bool> PathColumn       { get; private set; }
@@ -198,11 +198,6 @@ namespace Ryujinx.Configuration
             /// </summary>
             public ReactiveObject<bool> EnableDockedMode { get; private set; }
 
-            /// <summary>
-            /// Enables or disables multi-core scheduling of threads
-            /// </summary>
-            public ReactiveObject<bool> EnableMulticoreScheduling { get; private set; }
-
             /// <summary>
             /// Enables or disables profiled translation cache persistency
             /// </summary>
@@ -230,17 +225,16 @@ namespace Ryujinx.Configuration
 
             public SystemSection()
             {
-                Language                  = new ReactiveObject<Language>();
-                Region                    = new ReactiveObject<Region>();
-                TimeZone                  = new ReactiveObject<string>();
-                SystemTimeOffset          = new ReactiveObject<long>();
-                EnableDockedMode          = new ReactiveObject<bool>();
-                EnableMulticoreScheduling = new ReactiveObject<bool>();
-                EnablePtc                 = new ReactiveObject<bool>();
-                EnableFsIntegrityChecks   = new ReactiveObject<bool>();
-                FsGlobalAccessLogMode     = new ReactiveObject<int>();
-                AudioBackend              = new ReactiveObject<AudioBackend>();
-                IgnoreMissingServices     = new ReactiveObject<bool>();
+                Language                = new ReactiveObject<Language>();
+                Region                  = new ReactiveObject<Region>();
+                TimeZone                = new ReactiveObject<string>();
+                SystemTimeOffset        = new ReactiveObject<long>();
+                EnableDockedMode        = new ReactiveObject<bool>();
+                EnablePtc               = new ReactiveObject<bool>();
+                EnableFsIntegrityChecks = new ReactiveObject<bool>();
+                FsGlobalAccessLogMode   = new ReactiveObject<int>();
+                AudioBackend            = new ReactiveObject<AudioBackend>();
+                IgnoreMissingServices   = new ReactiveObject<bool>();
             }
         }
 
@@ -414,7 +408,6 @@ namespace Ryujinx.Configuration
                 CheckUpdatesOnStart       = CheckUpdatesOnStart,
                 EnableVsync               = Graphics.EnableVsync,
                 EnableShaderCache         = Graphics.EnableShaderCache,
-                EnableMulticoreScheduling = System.EnableMulticoreScheduling,
                 EnablePtc                 = System.EnablePtc,
                 EnableFsIntegrityChecks   = System.EnableFsIntegrityChecks,
                 FsGlobalAccessLogMode     = System.FsGlobalAccessLogMode,
@@ -476,7 +469,6 @@ namespace Ryujinx.Configuration
             CheckUpdatesOnStart.Value              = true;
             Graphics.EnableVsync.Value             = true;
             Graphics.EnableShaderCache.Value       = true;
-            System.EnableMulticoreScheduling.Value = true;
             System.EnablePtc.Value                 = false;
             System.EnableFsIntegrityChecks.Value   = true;
             System.FsGlobalAccessLogMode.Value     = 0;
@@ -788,7 +780,6 @@ namespace Ryujinx.Configuration
             CheckUpdatesOnStart.Value              = configurationFileFormat.CheckUpdatesOnStart;
             Graphics.EnableVsync.Value             = configurationFileFormat.EnableVsync;
             Graphics.EnableShaderCache.Value       = configurationFileFormat.EnableShaderCache;
-            System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling;
             System.EnablePtc.Value                 = configurationFileFormat.EnablePtc;
             System.EnableFsIntegrityChecks.Value   = configurationFileFormat.EnableFsIntegrityChecks;
             System.FsGlobalAccessLogMode.Value     = configurationFileFormat.FsGlobalAccessLogMode;

+ 2 - 2
Ryujinx.Common/PerformanceCounter.cs

@@ -32,7 +32,7 @@ namespace Ryujinx.Common
         public static long TicksPerMillisecond { get; }
 
         /// <summary>
-        /// Gets the number of milliseconds elapsed since the system started.
+        /// Gets the number of ticks elapsed since the system started.
         /// </summary>
         public static long ElapsedTicks
         {
@@ -76,7 +76,7 @@ namespace Ryujinx.Common
             TicksPerHour        = TicksPerMinute * 60;
             TicksPerDay         = TicksPerHour * 24;
 
-            _ticksToNs = 1000000000.0 / (double)Stopwatch.Frequency;
+            _ticksToNs = 1000000000.0 / Stopwatch.Frequency;
         }
     }
 }

+ 1 - 22
Ryujinx.HLE/HOS/Horizon.cs

@@ -82,9 +82,6 @@ namespace Ryujinx.HLE.HOS
 
         public Keyset KeySet => Device.FileSystem.KeySet;
 
-#pragma warning disable CS0649
-        private bool _hasStarted;
-#pragma warning restore CS0649
         private bool _isDisposed;
 
         public bool EnablePtc { get; set; }
@@ -300,22 +297,6 @@ namespace Ryujinx.HLE.HOS
             VsyncEvent.ReadableEvent.Signal();
         }
 
-        public void EnableMultiCoreScheduling()
-        {
-            if (!_hasStarted)
-            {
-                KernelContext.Scheduler.MultiCoreScheduling = true;
-            }
-        }
-
-        public void DisableMultiCoreScheduling()
-        {
-            if (!_hasStarted)
-            {
-                KernelContext.Scheduler.MultiCoreScheduling = false;
-            }
-        }
-
         public void Dispose()
         {
             Dispose(true);
@@ -346,9 +327,7 @@ namespace Ryujinx.HLE.HOS
                     }
 
                     // Exit ourself now!
-                    KernelContext.Scheduler.ExitThread(terminationThread);
-                    KernelContext.Scheduler.GetCurrentThread().Exit();
-                    KernelContext.Scheduler.RemoveThread(terminationThread);
+                    KernelStatic.GetCurrentThread().Exit();
                 });
 
                 terminationThread.Start();

+ 12 - 3
Ryujinx.HLE/HOS/Kernel/Common/KAutoObject.cs

@@ -1,3 +1,4 @@
+using System.Diagnostics;
 using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Kernel.Common
@@ -47,17 +48,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
         public void IncrementReferenceCount()
         {
-            Interlocked.Increment(ref _referenceCount);
+            int newRefCount = Interlocked.Increment(ref _referenceCount);
+
+            Debug.Assert(newRefCount >= 2);
         }
 
         public void DecrementReferenceCount()
         {
-            if (Interlocked.Decrement(ref _referenceCount) == 0)
+            int newRefCount = Interlocked.Decrement(ref _referenceCount);
+
+            Debug.Assert(newRefCount >= 0);
+
+            if (newRefCount == 0)
             {
                 Destroy();
             }
         }
 
-        protected virtual void Destroy() { }
+        protected virtual void Destroy()
+        {
+        }
     }
 }

+ 37 - 50
Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs

@@ -10,9 +10,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
     {
         private class WaitingObject
         {
-            public IKFutureSchedulerObject Object { get; private set; }
-
-            public long TimePoint { get; private set; }
+            public IKFutureSchedulerObject Object { get; }
+            public long TimePoint { get; }
 
             public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
             {
@@ -21,16 +20,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             }
         }
 
-        private List<WaitingObject> _waitingObjects;
-
+        private readonly KernelContext _context;
+        private readonly List<WaitingObject> _waitingObjects;
         private AutoResetEvent _waitEvent;
-
         private bool _keepRunning;
 
-        public KTimeManager()
+        public KTimeManager(KernelContext context)
         {
+            _context = context;
             _waitingObjects = new List<WaitingObject>();
-
             _keepRunning = true;
 
             Thread work = new Thread(WaitAndCheckScheduledObjects)
@@ -45,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
         {
             long timePoint = PerformanceCounter.ElapsedMilliseconds + ConvertNanosecondsToMilliseconds(timeout);
 
-            lock (_waitingObjects)
+            lock (_context.CriticalSection.Lock)
             {
                 _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
             }
@@ -53,33 +51,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             _waitEvent.Set();
         }
 
-        public static long ConvertNanosecondsToMilliseconds(long time)
-        {
-            time /= 1000000;
-
-            if ((ulong)time > int.MaxValue)
-            {
-                return int.MaxValue;
-            }
-
-            return time;
-        }
-
-        public static long ConvertMillisecondsToNanoseconds(long time)
-        {
-            return time * 1000000;
-        }
-
-        public static long ConvertMillisecondsToTicks(long time)
-        {
-            return time * 19200;
-        }
-
-        public void UnscheduleFutureInvocation(IKFutureSchedulerObject Object)
+        public void UnscheduleFutureInvocation(IKFutureSchedulerObject schedulerObj)
         {
-            lock (_waitingObjects)
+            lock (_context.CriticalSection.Lock)
             {
-                _waitingObjects.RemoveAll(x => x.Object == Object);
+                _waitingObjects.RemoveAll(x => x.Object == schedulerObj);
             }
         }
 
@@ -91,7 +67,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
                 {
                     WaitingObject next;
 
-                    lock (_waitingObjects)
+                    lock (_context.CriticalSection.Lock)
                     {
                         next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
                     }
@@ -109,16 +85,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
                         if (timeUp)
                         {
-                            lock (_waitingObjects)
+                            lock (_context.CriticalSection.Lock)
                             {
-                                timeUp = _waitingObjects.Remove(next);
+                                if (_waitingObjects.Remove(next))
+                                {
+                                    next.Object.TimeUp();
+                                }
                             }
                         }
-
-                        if (timeUp)
-                        {
-                            next.Object.TimeUp();
-                        }
                     }
                     else
                     {
@@ -128,19 +102,32 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             }
         }
 
-        public void Dispose()
+        public static long ConvertNanosecondsToMilliseconds(long time)
+        {
+            time /= 1000000;
+
+            if ((ulong)time > int.MaxValue)
+            {
+                return int.MaxValue;
+            }
+
+            return time;
+        }
+
+        public static long ConvertMillisecondsToNanoseconds(long time)
         {
-            Dispose(true);
+            return time * 1000000;
         }
 
-        protected virtual void Dispose(bool disposing)
+        public static long ConvertHostTicksToTicks(long time)
         {
-            if (disposing)
-            {
-                _keepRunning = false;
+            return (long)((time / (double)PerformanceCounter.TicksPerSecond) * 19200000.0);
+        }
 
-                _waitEvent?.Set();
-            }
+        public void Dispose()
+        {
+            _keepRunning = false;
+            _waitEvent?.Set();
         }
     }
 }

+ 5 - 5
Ryujinx.HLE/HOS/Kernel/Common/KernelTransfer.cs

@@ -8,7 +8,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
     {
         public static bool UserToKernelInt32(KernelContext context, ulong address, out int value)
         {
-            KProcess currentProcess = context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (currentProcess.CpuMemory.IsMapped(address) &&
                 currentProcess.CpuMemory.IsMapped(address + 3))
@@ -25,7 +25,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
         public static bool UserToKernelInt32Array(KernelContext context, ulong address, Span<int> values)
         {
-            KProcess currentProcess = context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             for (int index = 0; index < values.Length; index++, address += 4)
             {
@@ -45,7 +45,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
         public static bool UserToKernelString(KernelContext context, ulong address, int size, out string value)
         {
-            KProcess currentProcess = context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (currentProcess.CpuMemory.IsMapped(address) &&
                 currentProcess.CpuMemory.IsMapped(address + (ulong)size - 1))
@@ -62,7 +62,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
         public static bool KernelToUserInt32(KernelContext context, ulong address, int value)
         {
-            KProcess currentProcess = context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (currentProcess.CpuMemory.IsMapped(address) &&
                 currentProcess.CpuMemory.IsMapped(address + 3))
@@ -77,7 +77,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
 
         public static bool KernelToUserInt64(KernelContext context, ulong address, long value)
         {
-            KProcess currentProcess = context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (currentProcess.CpuMemory.IsMapped(address) &&
                 currentProcess.CpuMemory.IsMapped(address + 7))

+ 2 - 2
Ryujinx.HLE/HOS/Kernel/Ipc/KClientPort.cs

@@ -24,7 +24,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
         {
             clientSession = null;
 
-            KProcess currentProcess = KernelContext.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (currentProcess.ResourceLimit != null &&
                !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))
@@ -60,7 +60,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
         {
             clientSession = null;
 
-            KProcess currentProcess = KernelContext.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (currentProcess.ResourceLimit != null &&
                !currentProcess.ResourceLimit.Reserve(LimitableResource.Session, 1))

+ 3 - 3
Ryujinx.HLE/HOS/Kernel/Ipc/KClientSession.cs

@@ -25,13 +25,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
             State = ChannelState.Open;
 
-            CreatorProcess = context.Scheduler.GetCurrentProcess();
+            CreatorProcess = KernelStatic.GetCurrentProcess();
             CreatorProcess.IncrementReferenceCount();
         }
 
         public KernelResult SendSyncRequest(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
         {
-            KThread currentThread = KernelContext.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize);
 
@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         public KernelResult SendAsyncRequest(KWritableEvent asyncEvent, ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
         {
-            KThread currentThread = KernelContext.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             KSessionRequest request = new KSessionRequest(currentThread, customCmdBuffAddr, customCmdBuffSize, asyncEvent);
 

+ 3 - 3
Ryujinx.HLE/HOS/Kernel/Ipc/KServerSession.cs

@@ -214,7 +214,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         public KernelResult Receive(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
         {
-            KThread  serverThread  = KernelContext.Scheduler.GetCurrentThread();
+            KThread  serverThread  = KernelStatic.GetCurrentThread();
             KProcess serverProcess = serverThread.Owner;
 
             KernelContext.CriticalSection.Enter();
@@ -594,7 +594,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         public KernelResult Reply(ulong customCmdBuffAddr = 0, ulong customCmdBuffSize = 0)
         {
-            KThread  serverThread  = KernelContext.Scheduler.GetCurrentThread();
+            KThread  serverThread  = KernelStatic.GetCurrentThread();
             KProcess serverProcess = serverThread.Owner;
 
             KernelContext.CriticalSection.Enter();
@@ -889,7 +889,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         private MessageHeader GetServerMessageHeader(Message serverMsg)
         {
-            KProcess currentProcess = KernelContext.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             uint word0 = currentProcess.CpuMemory.Read<uint>(serverMsg.Address + 0);
             uint word1 = currentProcess.CpuMemory.Read<uint>(serverMsg.Address + 4);

+ 2 - 1
Ryujinx.HLE/HOS/Kernel/Ipc/KSession.cs

@@ -1,6 +1,5 @@
 using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Process;
-using System;
 
 namespace Ryujinx.HLE.HOS.Kernel.Ipc
 {
@@ -13,6 +12,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Ipc
 
         public KSession(KernelContext context, KClientPort parentPort = null) : base(context)
         {
+            IncrementReferenceCount();
+
             ServerSession = new KServerSession(context, this);
             ClientSession = new KClientSession(context, this, parentPort);
 

+ 34 - 5
Ryujinx.HLE/HOS/Kernel/KernelContext.cs

@@ -19,6 +19,8 @@ namespace Ryujinx.HLE.HOS.Kernel
 
         public bool KernelInitialized { get; }
 
+        public bool Running { get; private set; }
+
         public Switch Device { get; }
         public MemoryBlock Memory { get; }
         public Syscall Syscall { get; }
@@ -34,7 +36,8 @@ namespace Ryujinx.HLE.HOS.Kernel
         public KSlabHeap UserSlabHeapPages { get; }
 
         public KCriticalSection CriticalSection { get; }
-        public KScheduler Scheduler { get; }
+        public KScheduler[] Schedulers { get; }
+        public KPriorityQueue PriorityQueue { get; }
         public KTimeManager TimeManager { get; }
         public KSynchronization Synchronization { get; }
         public KContextIdManager ContextIdManager { get; }
@@ -42,6 +45,8 @@ namespace Ryujinx.HLE.HOS.Kernel
         public ConcurrentDictionary<long, KProcess> Processes { get; }
         public ConcurrentDictionary<string, KAutoObject> AutoObjectNames { get; }
 
+        public bool ThreadReselectionRequested { get; set; }
+
         private long _kipId;
         private long _processId;
         private long _threadUid;
@@ -51,6 +56,8 @@ namespace Ryujinx.HLE.HOS.Kernel
             Device = device;
             Memory = memory;
 
+            Running = true;
+
             Syscall = new Syscall(this);
 
             SyscallHandler = new SyscallHandler(this);
@@ -70,12 +77,18 @@ namespace Ryujinx.HLE.HOS.Kernel
                 KernelConstants.UserSlabHeapSize);
 
             CriticalSection = new KCriticalSection(this);
-            Scheduler = new KScheduler(this);
-            TimeManager = new KTimeManager();
+            Schedulers = new KScheduler[KScheduler.CpuCoresCount];
+            PriorityQueue = new KPriorityQueue();
+            TimeManager = new KTimeManager(this);
             Synchronization = new KSynchronization(this);
             ContextIdManager = new KContextIdManager();
 
-            Scheduler.StartAutoPreemptionThread();
+            for (int core = 0; core < KScheduler.CpuCoresCount; core++)
+            {
+                Schedulers[core] = new KScheduler(this, core);
+            }
+
+            StartPreemptionThread();
 
             KernelInitialized = true;
 
@@ -86,6 +99,16 @@ namespace Ryujinx.HLE.HOS.Kernel
             _processId = KernelConstants.InitialProcessId;
         }
 
+        private void StartPreemptionThread()
+        {
+            void PreemptionThreadStart()
+            {
+                KScheduler.PreemptionThreadLoop(this);
+            }
+
+            new Thread(PreemptionThreadStart) { Name = "HLE.PreemptionThread" }.Start();
+        }
+
         public long NewThreadUid()
         {
             return Interlocked.Increment(ref _threadUid) - 1;
@@ -103,7 +126,13 @@ namespace Ryujinx.HLE.HOS.Kernel
 
         public void Dispose()
         {
-            Scheduler.Dispose();
+            Running = false;
+
+            for (int i = 0; i < KScheduler.CpuCoresCount; i++)
+            {
+                Schedulers[i].Dispose();
+            }
+
             TimeManager.Dispose();
         }
     }

+ 41 - 16
Ryujinx.HLE/HOS/Kernel/KernelStatic.cs

@@ -1,6 +1,9 @@
-using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Kernel.Process;
+using Ryujinx.HLE.HOS.Kernel.Threading;
 using System;
-using System.Threading.Tasks;
+using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Kernel
 {
@@ -9,30 +12,52 @@ namespace Ryujinx.HLE.HOS.Kernel
         [ThreadStatic]
         private static KernelContext Context;
 
-        public static void YieldUntilCompletion(Action action)
+        [ThreadStatic]
+        private static KThread CurrentThread;
+
+        public static KernelResult StartInitialProcess(
+            KernelContext context,
+            ProcessCreationInfo creationInfo,
+            ReadOnlySpan<int> capabilities,
+            int mainThreadPriority,
+            ThreadStart customThreadStart)
         {
-            YieldUntilCompletion(Task.Factory.StartNew(action));
-        }
+            KProcess process = new KProcess(context);
 
-        public static void YieldUntilCompletion(Task task)
-        {
-            KThread currentThread = Context.Scheduler.GetCurrentThread();
+            KernelResult result = process.Initialize(
+                creationInfo,
+                capabilities,
+                context.ResourceLimit,
+                MemoryRegion.Service,
+                null,
+                customThreadStart);
 
-            Context.CriticalSection.Enter();
+            if (result != KernelResult.Success)
+            {
+                return result;
+            }
 
-            currentThread.Reschedule(ThreadSchedState.Paused);
+            process.DefaultCpuCore = 3;
 
-            task.ContinueWith((antecedent) =>
-            {
-                currentThread.Reschedule(ThreadSchedState.Running);
-            });
+            context.Processes.TryAdd(process.Pid, process);
 
-            Context.CriticalSection.Leave();
+            return process.Start(mainThreadPriority, 0x1000UL);
         }
 
-        internal static void SetKernelContext(KernelContext context)
+        internal static void SetKernelContext(KernelContext context, KThread thread)
         {
             Context = context;
+            CurrentThread = thread;
+        }
+
+        internal static KThread GetCurrentThread()
+        {
+            return CurrentThread;
+        }
+
+        internal static KProcess GetCurrentProcess()
+        {
+            return GetCurrentThread().Owner;
         }
     }
 }

+ 7 - 7
Ryujinx.HLE/HOS/Kernel/Memory/KMemoryManager.cs

@@ -728,7 +728,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
                 return KernelResult.OutOfMemory;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             lock (_blocks)
             {
@@ -1225,7 +1225,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
 
                 ulong remainingPages = remainingSize / PageSize;
 
-                KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                 if (currentProcess.ResourceLimit != null &&
                    !currentProcess.ResourceLimit.Reserve(LimitableResource.Memory, remainingSize))
@@ -1355,7 +1355,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
 
                     PhysicalMemoryUsage -= heapMappedSize;
 
-                    KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                    KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                     currentProcess.ResourceLimit?.Release(LimitableResource.Memory, heapMappedSize);
 
@@ -1504,7 +1504,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
                     attributeMask | MemoryAttribute.Uncached,
                     attributeExpected))
                 {
-                    KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                    KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                     serverAddress = currentProcess.MemoryManager.GetDramAddressFromVa(serverAddress);
 
@@ -2111,11 +2111,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
                         }
                     }
                 }
-            }
 
-            InsertBlock(addressRounded, pagesCount, RestoreIpcMappingPermissions);
+                InsertBlock(addressRounded, pagesCount, RestoreIpcMappingPermissions);
 
-            return KernelResult.Success;
+                return KernelResult.Success;
+            }
         }
 
         public KernelResult BorrowIpcBuffer(ulong address, ulong size)

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/Memory/KTransferMemory.cs

@@ -28,7 +28,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
 
         public KernelResult Initialize(ulong address, ulong size, KMemoryPermission permission)
         {
-            KProcess creator = KernelContext.Scheduler.GetCurrentProcess();
+            KProcess creator = KernelStatic.GetCurrentProcess();
 
             _creator = creator;
 

+ 2 - 2
Ryujinx.HLE/HOS/Kernel/Process/KHandleTable.cs

@@ -236,7 +236,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         {
             if (handle == SelfThreadHandle)
             {
-                return _context.Scheduler.GetCurrentThread();
+                return KernelStatic.GetCurrentThread();
             }
             else
             {
@@ -248,7 +248,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
         {
             if (handle == SelfProcessHandle)
             {
-                return _context.Scheduler.GetCurrentProcess();
+                return KernelStatic.GetCurrentProcess();
             }
             else
             {

+ 26 - 12
Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs

@@ -78,6 +78,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         public bool IsPaused { get; private set; }
 
+        private long _totalTimeRunning;
+
+        public long TotalTimeRunning => _totalTimeRunning;
+
         private IProcessContextFactory _contextFactory;
         public IProcessContext Context { get; private set; }
         public IVirtualMemoryManager CpuMemory => Context.AddressSpace;
@@ -112,11 +116,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             KPageList pageList,
             KResourceLimit resourceLimit,
             MemoryRegion memRegion,
-            IProcessContextFactory contextFactory)
+            IProcessContextFactory contextFactory,
+            ThreadStart customThreadStart = null)
         {
             ResourceLimit = resourceLimit;
             _memRegion = memRegion;
             _contextFactory = contextFactory ?? new ProcessContextFactory();
+            _customThreadStart = customThreadStart;
 
             AddressSpaceType addrSpaceType = (AddressSpaceType)((int)(creationInfo.Flags & ProcessCreationFlags.AddressSpaceMask) >> (int)ProcessCreationFlags.AddressSpaceShift);
 
@@ -176,9 +182,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
                 throw new InvalidOperationException($"Invalid KIP Id {Pid}.");
             }
 
-            result = ParseProcessInfo(creationInfo);
-
-            return result;
+            return ParseProcessInfo(creationInfo);
         }
 
         public KernelResult Initialize(
@@ -192,6 +196,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             ResourceLimit = resourceLimit;
             _memRegion = memRegion;
             _contextFactory = contextFactory ?? new ProcessContextFactory();
+            _customThreadStart = customThreadStart;
 
             ulong personalMmHeapSize = GetPersonalMmHeapSize((ulong)creationInfo.SystemResourcePagesCount, memRegion);
 
@@ -299,8 +304,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
                 CleanUpForError();
             }
 
-            _customThreadStart = customThreadStart;
-
             return result;
         }
 
@@ -751,8 +754,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         private void InterruptHandler(object sender, EventArgs e)
         {
-            KernelContext.Scheduler.ContextSwitch();
-            KernelContext.Scheduler.GetCurrentThread().HandlePostSyscall();
+            KThread currentThread = KernelStatic.GetCurrentThread();
+
+            if (currentThread.IsSchedulable)
+            {
+                KernelContext.Schedulers[currentThread.CurrentCore].Schedule();
+            }
+
+            currentThread.HandlePostSyscall();
         }
 
         public void IncrementThreadCount()
@@ -828,6 +837,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
             return personalMmHeapPagesCount * KMemoryManager.PageSize;
         }
 
+        public void AddCpuTime(long ticks)
+        {
+            Interlocked.Add(ref _totalTimeRunning, ticks);
+        }
+
         public void AddThread(KThread thread)
         {
             lock (_threadingLock)
@@ -893,7 +907,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
             if (shallTerminate)
             {
-                UnpauseAndTerminateAllThreadsExcept(KernelContext.Scheduler.GetCurrentThread());
+                UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread());
 
                 HandleTable.Destroy();
 
@@ -929,7 +943,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
             if (shallTerminate)
             {
-                UnpauseAndTerminateAllThreadsExcept(KernelContext.Scheduler.GetCurrentThread());
+                UnpauseAndTerminateAllThreadsExcept(KernelStatic.GetCurrentThread());
 
                 HandleTable.Destroy();
 
@@ -1058,7 +1072,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         private bool InvalidAccessHandler(ulong va)
         {
-            KernelContext.Scheduler.GetCurrentThreadOrNull()?.PrintGuestStackTrace();
+            KernelStatic.GetCurrentThread().PrintGuestStackTrace();
 
             Logger.Error?.Print(LogClass.Cpu, $"Invalid memory access at virtual address 0x{va:X16}.");
 
@@ -1067,7 +1081,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
 
         private void UndefinedInstructionHandler(object sender, InstUndefinedEventArgs e)
         {
-            KernelContext.Scheduler.GetCurrentThreadOrNull()?.PrintGuestStackTrace();
+            KernelStatic.GetCurrentThread().PrintGuestStackTrace();
 
             throw new UndefinedInstructionException(e.Address, e.OpCode);
         }

+ 84 - 89
Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs

@@ -26,7 +26,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult GetProcessId(int handle, out long pid)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KProcess process = currentProcess.HandleTable.GetKProcess(handle);
 
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidThread;
             }
 
-            KHandleTable handleTable = _context.Scheduler.GetCurrentProcess().HandleTable;
+            KHandleTable handleTable = KernelStatic.GetCurrentProcess().HandleTable;
 
             KProcess process = new KProcess(_context);
 
@@ -137,7 +137,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult StartProcess(int handle, int priority, int cpuCore, ulong mainThreadStackSize)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess().HandleTable.GetObject<KProcess>(handle);
+            KProcess process = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KProcess>(handle);
 
             if (process == null)
             {
@@ -198,7 +198,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.NotFound;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KernelResult result = currentProcess.HandleTable.ReserveHandle(out handle);
 
@@ -225,7 +225,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult SendSyncRequest(int handle)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KClientSession session = currentProcess.HandleTable.GetObject<KClientSession>(handle);
 
@@ -254,7 +254,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemState;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KernelResult result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize);
 
@@ -303,7 +303,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemState;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KernelResult result = currentProcess.MemoryManager.BorrowIpcBuffer(messagePtr, messageSize);
 
@@ -363,7 +363,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
             serverSessionHandle = 0;
             clientSessionHandle = 0;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KResourceLimit resourceLimit = currentProcess.ResourceLimit;
 
@@ -424,7 +424,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             sessionHandle = 0;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KServerPort serverPort = currentProcess.HandleTable.GetObject<KServerPort>(portHandle);
 
@@ -485,7 +485,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.MaximumExceeded;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             ulong copySize = (ulong)((long)handlesCount * 4);
 
@@ -513,7 +513,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             handleIndex = 0;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KSynchronizationObject[] syncObjs = new KSynchronizationObject[handles.Length];
 
@@ -582,7 +582,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.MaximumExceeded;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             ulong copySize = (ulong)((long)handlesCount * 4);
 
@@ -681,7 +681,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
             KPort port = new KPort(_context, maxSessions, isLight, (long)namePtr);
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ClientPort, out clientPortHandle);
 
@@ -733,7 +733,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
             KPort port = new KPort(_context, maxSessions, false, 0);
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KernelResult result = currentProcess.HandleTable.GenerateHandle(port.ServerPort, out handle);
 
@@ -756,7 +756,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             clientSessionHandle = 0;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KClientPort clientPort = currentProcess.HandleTable.GetObject<KClientPort>(clientPortHandle);
 
@@ -814,7 +814,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidSize;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             return process.MemoryManager.SetHeapSize(size, out position);
         }
@@ -843,7 +843,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidCombination;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KernelResult result = process.MemoryManager.SetMemoryAttribute(
                 position,
@@ -871,7 +871,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemState;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!currentProcess.MemoryManager.InsideAddrSpace(src, size))
             {
@@ -885,7 +885,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemRange;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             return process.MemoryManager.Map(dst, src, size);
         }
@@ -907,7 +907,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemState;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!currentProcess.MemoryManager.InsideAddrSpace(src, size))
             {
@@ -921,14 +921,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemRange;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             return process.MemoryManager.Unmap(dst, src, size);
         }
 
         public KernelResult QueryMemory(ulong infoPtr, ulong position, out ulong pageInfo)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KMemoryInfo blkInfo = process.MemoryManager.QueryMemory(position);
 
@@ -968,7 +968,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidPermission;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject<KSharedMemory>(handle);
 
@@ -1009,7 +1009,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemState;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KSharedMemory sharedMemory = currentProcess.HandleTable.GetObject<KSharedMemory>(handle);
 
@@ -1056,7 +1056,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidPermission;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KResourceLimit resourceLimit = process.ResourceLimit;
 
@@ -1112,7 +1112,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemRange;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0)
             {
@@ -1125,7 +1125,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemRange;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             return process.MemoryManager.MapPhysicalMemory(address, size);
         }
@@ -1147,7 +1147,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemRange;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if ((currentProcess.PersonalMmHeapPagesCount & 0xfffffffffffff) == 0)
             {
@@ -1160,7 +1160,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidMemRange;
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             return process.MemoryManager.UnmapPhysicalMemory(address, size);
         }
@@ -1177,7 +1177,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidSize;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
 
@@ -1214,7 +1214,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidSize;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
 
@@ -1259,7 +1259,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidPermission;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KProcess targetProcess = currentProcess.HandleTable.GetObject<KProcess>(handle);
 
@@ -1285,7 +1285,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult TerminateProcess(int handle)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             process = process.HandleTable.GetObject<KProcess>(handle);
 
@@ -1293,7 +1293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
             if (process != null)
             {
-                if (process == _context.Scheduler.GetCurrentProcess())
+                if (process == KernelStatic.GetCurrentProcess())
                 {
                     result = KernelResult.Success;
                     process.DecrementToZeroWhileTerminatingCurrent();
@@ -1314,12 +1314,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public void ExitProcess()
         {
-            _context.Scheduler.GetCurrentProcess().TerminateCurrentProcess();
+            KernelStatic.GetCurrentProcess().TerminateCurrentProcess();
         }
 
         public KernelResult SignalEvent(int handle)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KWritableEvent writableEvent = process.HandleTable.GetObject<KWritableEvent>(handle);
 
@@ -1343,7 +1343,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             KernelResult result;
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KWritableEvent writableEvent = process.HandleTable.GetObject<KWritableEvent>(handle);
 
@@ -1363,14 +1363,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult CloseHandle(int handle)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             return currentProcess.HandleTable.CloseHandle(handle) ? KernelResult.Success : KernelResult.InvalidHandle;
         }
 
         public KernelResult ResetSignal(int handle)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             KReadableEvent readableEvent = currentProcess.HandleTable.GetObject<KReadableEvent>(handle);
 
@@ -1399,12 +1399,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public ulong GetSystemTick()
         {
-            return _context.Scheduler.GetCurrentThread().Context.CntpctEl0;
+            return KernelStatic.GetCurrentThread().Context.CntpctEl0;
         }
 
         public void Break(ulong reason)
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             if ((reason & (1UL << 31)) == 0)
             {
@@ -1429,7 +1429,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public void OutputDebugString(ulong strPtr, ulong size)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             string str = MemoryHelper.ReadAsciiString(process.CpuMemory, (long)strPtr, (long)size);
 
@@ -1466,7 +1466,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.InvalidCombination;
                         }
 
-                        KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                        KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                         KProcess process = currentProcess.HandleTable.GetKProcess(handle);
 
@@ -1537,7 +1537,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.InvalidCombination;
                         }
 
-                        value = _context.Scheduler.GetCurrentProcess().Debug ? 1 : 0;
+                        value = KernelStatic.GetCurrentProcess().Debug ? 1 : 0;
 
                         break;
                     }
@@ -1554,7 +1554,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.InvalidCombination;
                         }
 
-                        KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                        KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                         if (currentProcess.ResourceLimit != null)
                         {
@@ -1581,14 +1581,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.InvalidHandle;
                         }
 
-                        int currentCore = _context.Scheduler.GetCurrentThread().CurrentCore;
+                        int currentCore = KernelStatic.GetCurrentThread().CurrentCore;
 
                         if (subId != -1 && subId != currentCore)
                         {
                             return KernelResult.InvalidCombination;
                         }
 
-                        value = _context.Scheduler.CoreContexts[currentCore].TotalIdleTimeTicks;
+                        value = KTimeManager.ConvertHostTicksToTicks(_context.Schedulers[currentCore].TotalIdleTimeTicks);
 
                         break;
                     }
@@ -1605,8 +1605,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.InvalidCombination;
                         }
 
-                        KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
-
+                        KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                         value = currentProcess.RandomEntropy[subId];
 
@@ -1620,14 +1619,14 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.InvalidCombination;
                         }
 
-                        KThread thread = _context.Scheduler.GetCurrentProcess().HandleTable.GetKThread(handle);
+                        KThread thread = KernelStatic.GetCurrentProcess().HandleTable.GetKThread(handle);
 
                         if (thread == null)
                         {
                             return KernelResult.InvalidHandle;
                         }
 
-                        KThread currentThread = _context.Scheduler.GetCurrentThread();
+                        KThread currentThread = KernelStatic.GetCurrentThread();
 
                         int currentCore = currentThread.CurrentCore;
 
@@ -1636,13 +1635,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                             return KernelResult.Success;
                         }
 
-                        KCoreContext coreContext = _context.Scheduler.CoreContexts[currentCore];
+                        KScheduler scheduler = _context.Schedulers[currentCore];
 
-                        long timeDelta = PerformanceCounter.ElapsedMilliseconds - coreContext.LastContextSwitchTime;
+                        long timeDelta = PerformanceCounter.ElapsedTicks - scheduler.LastContextSwitchTime;
 
                         if (subId != -1)
                         {
-                            value = KTimeManager.ConvertMillisecondsToTicks(timeDelta);
+                            value = KTimeManager.ConvertHostTicksToTicks(timeDelta);
                         }
                         else
                         {
@@ -1653,7 +1652,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                                 totalTimeRunning += timeDelta;
                             }
 
-                            value = KTimeManager.ConvertMillisecondsToTicks(totalTimeRunning);
+                            value = KTimeManager.ConvertHostTicksToTicks(totalTimeRunning);
                         }
 
                         break;
@@ -1669,7 +1668,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             KEvent Event = new KEvent(_context);
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KernelResult result = process.HandleTable.GenerateHandle(Event.WritableEvent, out wEventHandle);
 
@@ -1701,7 +1700,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
             if (maxCount != 0)
             {
-                KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                 ulong copySize = (ulong)maxCount * 8;
 
@@ -1807,7 +1806,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             handle = 0;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (cpuCore == -2)
             {
@@ -1844,7 +1843,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
             if (result == KernelResult.Success)
             {
-                KProcess process = _context.Scheduler.GetCurrentProcess();
+                KProcess process = KernelStatic.GetCurrentProcess();
 
                 result = process.HandleTable.GenerateHandle(thread, out handle);
             }
@@ -1860,7 +1859,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult StartThread(int handle)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -1887,35 +1886,31 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public void ExitThread()
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
-
-            _context.Scheduler.ExitThread(currentThread);
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             currentThread.Exit();
         }
 
         public void SleepThread(long timeout)
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
-
             if (timeout < 1)
             {
                 switch (timeout)
                 {
-                    case 0: currentThread.Yield(); break;
-                    case -1: currentThread.YieldWithLoadBalancing(); break;
-                    case -2: currentThread.YieldAndWaitForLoadBalancing(); break;
+                    case 0: KScheduler.Yield(_context); break;
+                    case -1: KScheduler.YieldWithLoadBalancing(_context); break;
+                    case -2: KScheduler.YieldToAnyThread(_context); break;
                 }
             }
             else
             {
-                currentThread.Sleep(timeout);
+                KernelStatic.GetCurrentThread().Sleep(timeout);
             }
         }
 
         public KernelResult GetThreadPriority(int handle, out int priority)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -1937,7 +1932,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
         {
             // TODO: NPDM check.
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -1953,7 +1948,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult GetThreadCoreMask(int handle, out int preferredCore, out long affinityMask)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -1975,7 +1970,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult SetThreadCoreMask(int handle, int preferredCore, long affinityMask)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (preferredCore == -2)
             {
@@ -2009,7 +2004,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 }
             }
 
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -2023,12 +2018,12 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public int GetCurrentProcessorNumber()
         {
-            return _context.Scheduler.GetCurrentThread().CurrentCore;
+            return KernelStatic.GetCurrentThread().CurrentCore;
         }
 
         public KernelResult GetThreadId(int handle, out long threadUid)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -2048,7 +2043,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult SetThreadActivity(int handle, bool pause)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetObject<KThread>(handle);
 
@@ -2062,7 +2057,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidHandle;
             }
 
-            if (thread == _context.Scheduler.GetCurrentThread())
+            if (thread == KernelStatic.GetCurrentThread())
             {
                 return KernelResult.InvalidThread;
             }
@@ -2072,8 +2067,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult GetThreadContext3(ulong address, int handle)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             KThread thread = currentProcess.HandleTable.GetObject<KThread>(handle);
 
@@ -2190,13 +2185,13 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.MaximumExceeded;
             }
 
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             var syncObjs = new Span<KSynchronizationObject>(currentThread.WaitSyncObjects).Slice(0, handlesCount);
 
             if (handlesCount != 0)
             {
-                KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+                KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
                 if (currentProcess.MemoryManager.AddrSpaceStart > handlesPtr)
                 {
@@ -2267,7 +2262,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult CancelSynchronization(int handle)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
+            KProcess process = KernelStatic.GetCurrentProcess();
 
             KThread thread = process.HandleTable.GetKThread(handle);
 
@@ -2293,7 +2288,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidAddress;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             return currentProcess.AddressArbiter.ArbitrateLock(ownerHandle, mutexAddress, requesterHandle);
         }
@@ -2310,7 +2305,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidAddress;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             return currentProcess.AddressArbiter.ArbitrateUnlock(mutexAddress);
         }
@@ -2331,7 +2326,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidAddress;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             return currentProcess.AddressArbiter.WaitProcessWideKeyAtomic(
                 mutexAddress,
@@ -2342,7 +2337,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         public KernelResult SignalProcessWideKey(ulong address, int count)
         {
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             currentProcess.AddressArbiter.SignalProcessWideKey(address, count);
 
@@ -2361,7 +2356,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidAddress;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             return type switch
             {
@@ -2387,7 +2382,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
                 return KernelResult.InvalidAddress;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             return type switch
             {

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/SupervisorCall/SyscallHandler.cs

@@ -49,7 +49,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
 
         private void PostSvcHandler()
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             currentThread.HandlePostSyscall();
         }

+ 0 - 66
Ryujinx.HLE/HOS/Kernel/Threading/HleCoreManager.cs

@@ -1,66 +0,0 @@
-using System.Collections.Concurrent;
-using System.Threading;
-
-namespace Ryujinx.HLE.HOS.Kernel.Threading
-{
-    class HleCoreManager
-    {
-        private class PausableThread
-        {
-            public ManualResetEvent Event { get; private set; }
-
-            public bool IsExiting { get; set; }
-
-            public PausableThread()
-            {
-                Event = new ManualResetEvent(false);
-            }
-        }
-
-        private ConcurrentDictionary<Thread, PausableThread> _threads;
-
-        public HleCoreManager()
-        {
-            _threads = new ConcurrentDictionary<Thread, PausableThread>();
-        }
-
-        public void Set(Thread thread)
-        {
-            GetThread(thread).Event.Set();
-        }
-
-        public void Reset(Thread thread)
-        {
-            GetThread(thread).Event.Reset();
-        }
-
-        public void Wait(Thread thread)
-        {
-            PausableThread pausableThread = GetThread(thread);
-
-            if (!pausableThread.IsExiting)
-            {
-                pausableThread.Event.WaitOne();
-            }
-        }
-
-        public void Exit(Thread thread)
-        {
-            GetThread(thread).IsExiting = true;
-        }
-
-        private PausableThread GetThread(Thread thread)
-        {
-            return _threads.GetOrAdd(thread, (key) => new PausableThread());
-        }
-
-        public void RemoveThread(Thread thread)
-        {
-            if (_threads.TryRemove(thread, out PausableThread pausableThread))
-            {
-                pausableThread.Event.Set();
-                pausableThread.Event.Dispose();
-            }
-        }
-    }
-}

+ 0 - 150
Ryujinx.HLE/HOS/Kernel/Threading/HleScheduler.cs

@@ -1,150 +0,0 @@
-using System;
-using System.Threading;
-
-namespace Ryujinx.HLE.HOS.Kernel.Threading
-{
-    partial class KScheduler
-    {
-        private const int RoundRobinTimeQuantumMs = 10;
-
-        private int _currentCore;
-
-        public bool MultiCoreScheduling { get; set; }
-
-        public HleCoreManager CoreManager { get; private set; }
-
-        private bool _keepPreempting;
-
-        public void StartAutoPreemptionThread()
-        {
-            Thread preemptionThread = new Thread(PreemptCurrentThread)
-            {
-                Name = "HLE.PreemptionThread"
-            };
-
-            _keepPreempting = true;
-
-            preemptionThread.Start();
-        }
-
-        public void ContextSwitch()
-        {
-            lock (CoreContexts)
-            {
-                if (MultiCoreScheduling)
-                {
-                    int selectedCount = 0;
-
-                    for (int core = 0; core < CpuCoresCount; core++)
-                    {
-                        KCoreContext coreContext = CoreContexts[core];
-
-                        if (coreContext.ContextSwitchNeeded && (coreContext.CurrentThread?.IsCurrentHostThread() ?? false))
-                        {
-                            coreContext.ContextSwitch();
-                        }
-
-                        if (coreContext.CurrentThread?.IsCurrentHostThread() ?? false)
-                        {
-                            selectedCount++;
-                        }
-                    }
-
-                    if (selectedCount == 0)
-                    {
-                        CoreManager.Reset(Thread.CurrentThread);
-                    }
-                    else if (selectedCount == 1)
-                    {
-                        CoreManager.Set(Thread.CurrentThread);
-                    }
-                    else
-                    {
-                        throw new InvalidOperationException("Thread scheduled in more than one core!");
-                    }
-                }
-                else
-                {
-                    KThread currentThread = CoreContexts[_currentCore].CurrentThread;
-
-                    bool hasThreadExecuting = currentThread != null;
-
-                    if (hasThreadExecuting)
-                    {
-                        // If this is not the thread that is currently executing, we need
-                        // to request an interrupt to allow safely starting another thread.
-                        if (!currentThread.IsCurrentHostThread())
-                        {
-                            currentThread.Context.RequestInterrupt();
-
-                            return;
-                        }
-
-                        CoreManager.Reset(currentThread.HostThread);
-                    }
-
-                    // Advance current core and try picking a thread,
-                    // keep advancing if it is null.
-                    for (int core = 0; core < 4; core++)
-                    {
-                        _currentCore = (_currentCore + 1) % CpuCoresCount;
-
-                        KCoreContext coreContext = CoreContexts[_currentCore];
-
-                        coreContext.UpdateCurrentThread();
-
-                        if (coreContext.CurrentThread != null)
-                        {
-                            CoreManager.Set(coreContext.CurrentThread.HostThread);
-
-                            coreContext.CurrentThread.Execute();
-
-                            break;
-                        }
-                    }
-
-                    // If nothing was running before, then we are on a "external"
-                    // HLE thread, we don't need to wait.
-                    if (!hasThreadExecuting)
-                    {
-                        return;
-                    }
-                }
-            }
-
-            CoreManager.Wait(Thread.CurrentThread);
-        }
-
-        private void PreemptCurrentThread()
-        {
-            // Preempts current thread every 10 milliseconds on a round-robin fashion,
-            // when multi core scheduling is disabled, to try ensuring that all threads
-            // gets a chance to run.
-            while (_keepPreempting)
-            {
-                lock (CoreContexts)
-                {
-                    KThread currentThread = CoreContexts[_currentCore].CurrentThread;
-
-                    currentThread?.Context.RequestInterrupt();
-                }
-
-                PreemptThreads();
-
-                Thread.Sleep(RoundRobinTimeQuantumMs);
-            }
-        }
-
-        public void ExitThread(KThread thread)
-        {
-            thread.Context.StopRunning();
-
-            CoreManager.Exit(thread.HostThread);
-        }
-
-        public void RemoveThread(KThread thread)
-        {
-            CoreManager.RemoveThread(thread.HostThread);
-        }
-    }
-}

+ 10 - 10
Ryujinx.HLE/HOS/Kernel/Threading/KAddressArbiter.cs

@@ -25,14 +25,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         public KernelResult ArbitrateLock(int ownerHandle, ulong mutexAddress, int requesterHandle)
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             _context.CriticalSection.Enter();
 
             currentThread.SignaledObj   = null;
             currentThread.ObjSyncResult = KernelResult.Success;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!KernelTransfer.UserToKernelInt32(_context, mutexAddress, out int mutexValue))
             {
@@ -81,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         {
             _context.CriticalSection.Enter();
 
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             (KernelResult result, KThread newOwnerThread) = MutexUnlock(currentThread, mutexAddress);
 
@@ -104,7 +104,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         {
             _context.CriticalSection.Enter();
 
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             currentThread.SignaledObj   = null;
             currentThread.ObjSyncResult = KernelResult.TimedOut;
@@ -227,7 +227,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         {
             ulong address = requester.MutexAddress;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!currentProcess.CpuMemory.IsMapped(address))
             {
@@ -293,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         public KernelResult WaitForAddressIfEqual(ulong address, int value, long timeout)
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             _context.CriticalSection.Enter();
 
@@ -368,7 +368,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             bool  shouldDecrement,
             long  timeout)
         {
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             _context.CriticalSection.Enter();
 
@@ -383,7 +383,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             currentThread.SignaledObj   = null;
             currentThread.ObjSyncResult = KernelResult.TimedOut;
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!KernelTransfer.UserToKernelInt32(_context, address, out int currentValue))
             {
@@ -483,7 +483,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         {
             _context.CriticalSection.Enter();
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!currentProcess.CpuMemory.IsMapped(address))
             {
@@ -544,7 +544,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 offset = 1;
             }
 
-            KProcess currentProcess = _context.Scheduler.GetCurrentProcess();
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
 
             if (!currentProcess.CpuMemory.IsMapped(address))
             {

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/Threading/KConditionVariable.cs

@@ -7,7 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
     {
         public static void Wait(KernelContext context, LinkedList<KThread> threadList, object mutex, long timeout)
         {
-            KThread currentThread = context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             context.CriticalSection.Enter();
 

+ 0 - 79
Ryujinx.HLE/HOS/Kernel/Threading/KCoreContext.cs

@@ -1,79 +0,0 @@
-using Ryujinx.Common;
-
-namespace Ryujinx.HLE.HOS.Kernel.Threading
-{
-    class KCoreContext
-    {
-        private KScheduler _scheduler;
-
-        private HleCoreManager _coreManager;
-
-        public bool ContextSwitchNeeded { get; private set; }
-
-        public long LastContextSwitchTime { get; private set; }
-
-        public long TotalIdleTimeTicks { get; private set; } //TODO
-
-        public KThread CurrentThread  { get; private set; }
-        public KThread SelectedThread { get; private set; }
-
-        public KCoreContext(KScheduler scheduler, HleCoreManager coreManager)
-        {
-            _scheduler   = scheduler;
-            _coreManager = coreManager;
-        }
-
-        public void SelectThread(KThread thread)
-        {
-            SelectedThread = thread;
-
-            if (SelectedThread != CurrentThread)
-            {
-                ContextSwitchNeeded = true;
-            }
-        }
-
-        public void UpdateCurrentThread()
-        {
-            ContextSwitchNeeded = false;
-
-            LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds;
-
-            CurrentThread = SelectedThread;
-
-            if (CurrentThread != null)
-            {
-                long currentTime = PerformanceCounter.ElapsedMilliseconds;
-
-                CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime;
-                CurrentThread.LastScheduledTime = currentTime;
-            }
-        }
-
-        public void ContextSwitch()
-        {
-            ContextSwitchNeeded = false;
-
-            LastContextSwitchTime = PerformanceCounter.ElapsedMilliseconds;
-
-            if (CurrentThread != null)
-            {
-                _coreManager.Reset(CurrentThread.HostThread);
-            }
-
-            CurrentThread = SelectedThread;
-
-            if (CurrentThread != null)
-            {
-                long currentTime = PerformanceCounter.ElapsedMilliseconds;
-
-                CurrentThread.TotalTimeRunning += currentTime - CurrentThread.LastScheduledTime;
-                CurrentThread.LastScheduledTime = currentTime;
-
-                _coreManager.Set(CurrentThread.HostThread);
-
-                CurrentThread.Execute();
-            }
-        }
-    }
-}

+ 21 - 49
Ryujinx.HLE/HOS/Kernel/Threading/KCriticalSection.cs

@@ -5,21 +5,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
     class KCriticalSection
     {
         private readonly KernelContext _context;
-
-        public object LockObj { get; private set; }
-
+        private readonly object _lock;
         private int _recursionCount;
 
+        public object Lock => _lock;
+
         public KCriticalSection(KernelContext context)
         {
             _context = context;
-
-            LockObj = new object();
+            _lock = new object();
         }
 
         public void Enter()
         {
-            Monitor.Enter(LockObj);
+            Monitor.Enter(_lock);
 
             _recursionCount++;
         }
@@ -31,61 +30,34 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 return;
             }
 
-            bool doContextSwitch = false;
-
             if (--_recursionCount == 0)
             {
-                if (_context.Scheduler.ThreadReselectionRequested)
-                {
-                    _context.Scheduler.SelectThreads();
-                }
+                ulong scheduledCoresMask = KScheduler.SelectThreads(_context);
 
-                Monitor.Exit(LockObj);
+                Monitor.Exit(_lock);
 
-                if (_context.Scheduler.MultiCoreScheduling)
+                KThread currentThread = KernelStatic.GetCurrentThread();
+                bool isCurrentThreadSchedulable = currentThread != null && currentThread.IsSchedulable;
+                if (isCurrentThreadSchedulable)
                 {
-                    lock (_context.Scheduler.CoreContexts)
-                    {
-                        for (int core = 0; core < KScheduler.CpuCoresCount; core++)
-                        {
-                            KCoreContext coreContext = _context.Scheduler.CoreContexts[core];
-
-                            if (coreContext.ContextSwitchNeeded)
-                            {
-                                KThread currentThread = coreContext.CurrentThread;
-
-                                if (currentThread == null)
-                                {
-                                    // Nothing is running, we can perform the context switch immediately.
-                                    coreContext.ContextSwitch();
-                                }
-                                else if (currentThread.IsCurrentHostThread())
-                                {
-                                    // Thread running on the current core, context switch will block.
-                                    doContextSwitch = true;
-                                }
-                                else
-                                {
-                                    // Thread running on another core, request a interrupt.
-                                    currentThread.Context.RequestInterrupt();
-                                }
-                            }
-                        }
-                    }
+                    KScheduler.EnableScheduling(_context, scheduledCoresMask);
                 }
                 else
                 {
-                    doContextSwitch = true;
+                    KScheduler.EnableSchedulingFromForeignThread(_context, scheduledCoresMask);
+
+                    // If the thread exists but is not schedulable, we still want to suspend
+                    // it if it's not runnable. That allows the kernel to still block HLE threads
+                    // even if they are not scheduled on guest cores.
+                    if (currentThread != null && !currentThread.IsSchedulable && currentThread.Context.Running)
+                    {
+                        currentThread.SchedulerWaitEvent.WaitOne();
+                    }
                 }
             }
             else
             {
-                Monitor.Exit(LockObj);
-            }
-
-            if (doContextSwitch)
-            {
-                _context.Scheduler.ContextSwitch();
+                Monitor.Exit(_lock);
             }
         }
     }

+ 17 - 29
Ryujinx.HLE/HOS/Kernel/Threading/KSchedulingData.cs → Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs

@@ -1,8 +1,9 @@
 using System.Collections.Generic;
+using System.Numerics;
 
 namespace Ryujinx.HLE.HOS.Kernel.Threading
 {
-    class KSchedulingData
+    class KPriorityQueue
     {
         private LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
         private LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
@@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         private long[] _scheduledPrioritiesPerCore;
         private long[] _suggestedPrioritiesPerCore;
 
-        public KSchedulingData()
+        public KPriorityQueue()
         {
             _suggestedThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
             _scheduledThreadsPerPrioPerCore = new LinkedList<KThread>[KScheduler.PrioritiesCount][];
@@ -45,7 +46,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         {
             long prioMask = prios[core];
 
-            int prio = CountTrailingZeros(prioMask);
+            int prio = BitOperations.TrailingZeroCount(prioMask);
 
             prioMask &= ~(1L << prio);
 
@@ -62,42 +63,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                     node = node.Next;
                 }
 
-                prio = CountTrailingZeros(prioMask);
+                prio = BitOperations.TrailingZeroCount(prioMask);
 
                 prioMask &= ~(1L << prio);
             }
         }
 
-        private int CountTrailingZeros(long value)
-        {
-            int count = 0;
-
-            while (((value >> count) & 0xf) == 0 && count < 64)
-            {
-                count += 4;
-            }
-
-            while (((value >> count) & 1) == 0 && count < 64)
-            {
-                count++;
-            }
-
-            return count;
-        }
-
         public void TransferToCore(int prio, int dstCore, KThread thread)
         {
-            bool schedulable = thread.DynamicPriority < KScheduler.PrioritiesCount;
-
-            int srcCore = thread.CurrentCore;
-
-            thread.CurrentCore = dstCore;
-
-            if (srcCore == dstCore || !schedulable)
+            int srcCore = thread.ActiveCore;
+            if (srcCore == dstCore)
             {
                 return;
             }
 
+            thread.ActiveCore = dstCore;
+
             if (srcCore >= 0)
             {
                 Unschedule(prio, srcCore, thread);
@@ -168,13 +149,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             _scheduledPrioritiesPerCore[core] |= 1L << prio;
         }
 
-        public void Reschedule(int prio, int core, KThread thread)
+        public KThread Reschedule(int prio, int core, KThread thread)
         {
+            if (prio >= KScheduler.PrioritiesCount)
+            {
+                return null;
+            }
+
             LinkedList<KThread> queue = ScheduledQueue(prio, core);
 
             queue.Remove(thread.SiblingsPerCore[core]);
 
             thread.SiblingsPerCore[core] = queue.AddLast(thread);
+
+            return queue.First.Value;
         }
 
         public void Unschedule(int prio, int core, KThread thread)

+ 483 - 123
Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs

@@ -1,7 +1,10 @@
+using Ryujinx.Common;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Numerics;
+using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Kernel.Threading
 {
@@ -10,130 +13,88 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
         public const int PrioritiesCount = 64;
         public const int CpuCoresCount   = 4;
 
-        private const int PreemptionPriorityCores012 = 59;
-        private const int PreemptionPriorityCore3    = 63;
+        private const int RoundRobinTimeQuantumMs = 10;
+
+        private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 };
 
         private readonly KernelContext _context;
+        private readonly int _coreId;
+
+        private struct SchedulingState
+        {
+            public bool NeedsScheduling;
+            public KThread SelectedThread;
+        }
+
+        private SchedulingState _state;
 
-        public KSchedulingData SchedulingData { get; private set; }
+        private AutoResetEvent _idleInterruptEvent;
+        private readonly object _idleInterruptEventLock;
 
-        public KCoreContext[] CoreContexts { get; private set; }
+        private KThread _previousThread;
+        private KThread _currentThread;
+        private readonly KThread _idleThread;
 
-        public bool ThreadReselectionRequested { get; set; }
+        public KThread PreviousThread => _previousThread;
+        public long LastContextSwitchTime { get; private set; }
+        public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
 
-        public KScheduler(KernelContext context)
+        public KScheduler(KernelContext context, int coreId)
         {
             _context = context;
+            _coreId = coreId;
 
-            SchedulingData = new KSchedulingData();
+            _idleInterruptEvent = new AutoResetEvent(false);
+            _idleInterruptEventLock = new object();
 
-            CoreManager = new HleCoreManager();
+            KThread idleThread = CreateIdleThread(context, coreId);
 
-            CoreContexts = new KCoreContext[CpuCoresCount];
+            _currentThread = idleThread;
+            _idleThread = idleThread;
 
-            for (int core = 0; core < CpuCoresCount; core++)
-            {
-                CoreContexts[core] = new KCoreContext(this, CoreManager);
-            }
+            idleThread.StartHostThread();
+            idleThread.SchedulerWaitEvent.Set();
         }
 
-        private void PreemptThreads()
+        private KThread CreateIdleThread(KernelContext context, int cpuCore)
         {
-            _context.CriticalSection.Enter();
+            KThread idleThread = new KThread(context);
 
-            PreemptThread(PreemptionPriorityCores012, 0);
-            PreemptThread(PreemptionPriorityCores012, 1);
-            PreemptThread(PreemptionPriorityCores012, 2);
-            PreemptThread(PreemptionPriorityCore3,    3);
+            idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop);
 
-            _context.CriticalSection.Leave();
+            return idleThread;
         }
 
-        private void PreemptThread(int prio, int core)
+        public static ulong SelectThreads(KernelContext context)
         {
-            IEnumerable<KThread> scheduledThreads = SchedulingData.ScheduledThreads(core);
-
-            KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
-
-            // Yield priority queue.
-            if (selectedThread != null)
-            {
-                SchedulingData.Reschedule(prio, core, selectedThread);
-            }
-
-            IEnumerable<KThread> SuitableCandidates()
-            {
-                foreach (KThread thread in SchedulingData.SuggestedThreads(core))
-                {
-                    int srcCore = thread.CurrentCore;
-
-                    if (srcCore >= 0)
-                    {
-                        KThread highestPrioSrcCore = SchedulingData.ScheduledThreads(srcCore).FirstOrDefault();
-
-                        if (highestPrioSrcCore != null && highestPrioSrcCore.DynamicPriority < 2)
-                        {
-                            break;
-                        }
-
-                        if (highestPrioSrcCore == thread)
-                        {
-                            continue;
-                        }
-                    }
-
-                    // If the candidate was scheduled after the current thread, then it's not worth it.
-                    if (selectedThread == null || selectedThread.LastScheduledTime >= thread.LastScheduledTime)
-                    {
-                        yield return thread;
-                    }
-                }
-            }
-
-            // Select candidate threads that could run on this core.
-            // Only take into account threads that are not yet selected.
-            KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio);
-
-            if (dst != null)
+            if (context.ThreadReselectionRequested)
             {
-                SchedulingData.TransferToCore(prio, core, dst);
-
-                selectedThread = dst;
+                return SelectThreadsImpl(context);
             }
-
-            // If the priority of the currently selected thread is lower than preemption priority,
-            // then allow threads with lower priorities to be selected aswell.
-            if (selectedThread != null && selectedThread.DynamicPriority > prio)
+            else
             {
-                Func<KThread, bool> predicate = x => x.DynamicPriority >= selectedThread.DynamicPriority;
-
-                dst = SuitableCandidates().FirstOrDefault(predicate);
-
-                if (dst != null)
-                {
-                    SchedulingData.TransferToCore(dst.DynamicPriority, core, dst);
-                }
+                return 0UL;
             }
-
-            ThreadReselectionRequested = true;
         }
 
-        public void SelectThreads()
+        private static ulong SelectThreadsImpl(KernelContext context)
         {
-            ThreadReselectionRequested = false;
+            context.ThreadReselectionRequested = false;
+
+            ulong scheduledCoresMask = 0UL;
 
             for (int core = 0; core < CpuCoresCount; core++)
             {
-                KThread thread = SchedulingData.ScheduledThreads(core).FirstOrDefault();
+                KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
 
-                CoreContexts[core].SelectThread(thread);
+                scheduledCoresMask |= context.Schedulers[core].SelectThread(thread);
             }
 
             for (int core = 0; core < CpuCoresCount; core++)
             {
                 // If the core is not idle (there's already a thread running on it),
                 // then we don't need to attempt load balancing.
-                if (SchedulingData.ScheduledThreads(core).Any())
+                if (context.PriorityQueue.ScheduledThreads(core).Any())
                 {
                     continue;
                 }
@@ -146,16 +107,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
                 // Select candidate threads that could run on this core.
                 // Give preference to threads that are not yet selected.
-                foreach (KThread thread in SchedulingData.SuggestedThreads(core))
+                foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
                 {
-                    if (thread.CurrentCore < 0 || thread != CoreContexts[thread.CurrentCore].SelectedThread)
+                    if (suggested.ActiveCore < 0 || suggested != context.Schedulers[suggested.ActiveCore]._state.SelectedThread)
                     {
-                        dst = thread;
-
+                        dst = suggested;
                         break;
                     }
 
-                    srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = thread.CurrentCore;
+                    srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
                 }
 
                 // Not yet selected candidate found.
@@ -165,9 +125,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                     // threads, we should skip load balancing entirely.
                     if (dst.DynamicPriority >= 2)
                     {
-                        SchedulingData.TransferToCore(dst.DynamicPriority, core, dst);
+                        context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst);
 
-                        CoreContexts[core].SelectThread(dst);
+                        scheduledCoresMask |= context.Schedulers[core].SelectThread(dst);
                     }
 
                     continue;
@@ -179,80 +139,480 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 {
                     int srcCore = srcCoresHighestPrioThreads[index];
 
-                    KThread src = SchedulingData.ScheduledThreads(srcCore).ElementAtOrDefault(1);
+                    KThread src = context.PriorityQueue.ScheduledThreads(srcCore).ElementAtOrDefault(1);
 
                     if (src != null)
                     {
                         // Run the second thread on the queue on the source core,
                         // move the first one to the current core.
-                        KThread origSelectedCoreSrc = CoreContexts[srcCore].SelectedThread;
+                        KThread origSelectedCoreSrc = context.Schedulers[srcCore]._state.SelectedThread;
 
-                        CoreContexts[srcCore].SelectThread(src);
+                        scheduledCoresMask |= context.Schedulers[srcCore].SelectThread(src);
 
-                        SchedulingData.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc);
+                        context.PriorityQueue.TransferToCore(origSelectedCoreSrc.DynamicPriority, core, origSelectedCoreSrc);
 
-                        CoreContexts[core].SelectThread(origSelectedCoreSrc);
+                        scheduledCoresMask |= context.Schedulers[core].SelectThread(origSelectedCoreSrc);
                     }
                 }
             }
+
+            return scheduledCoresMask;
+        }
+
+        private ulong SelectThread(KThread nextThread)
+        {
+            KThread previousThread = _state.SelectedThread;
+
+            if (previousThread != nextThread)
+            {
+                if (previousThread != null)
+                {
+                    previousThread.LastScheduledTime = PerformanceCounter.ElapsedTicks;
+                }
+
+                _state.SelectedThread = nextThread;
+                _state.NeedsScheduling = true;
+                return 1UL << _coreId;
+            }
+            else
+            {
+                return 0UL;
+            }
+        }
+
+        public static void EnableScheduling(KernelContext context, ulong scheduledCoresMask)
+        {
+            KScheduler currentScheduler = context.Schedulers[KernelStatic.GetCurrentThread().CurrentCore];
+
+            // Note that "RescheduleCurrentCore" will block, so "RescheduleOtherCores" must be done first.
+            currentScheduler.RescheduleOtherCores(scheduledCoresMask);
+            currentScheduler.RescheduleCurrentCore();
+        }
+
+        public static void EnableSchedulingFromForeignThread(KernelContext context, ulong scheduledCoresMask)
+        {
+            RescheduleOtherCores(context, scheduledCoresMask);
+        }
+
+        private void RescheduleCurrentCore()
+        {
+            if (_state.NeedsScheduling)
+            {
+                Schedule();
+            }
+        }
+
+        private void RescheduleOtherCores(ulong scheduledCoresMask)
+        {
+            RescheduleOtherCores(_context, scheduledCoresMask & ~(1UL << _coreId));
+        }
+
+        private static void RescheduleOtherCores(KernelContext context, ulong scheduledCoresMask)
+        {
+            while (scheduledCoresMask != 0)
+            {
+                int coreToSignal = BitOperations.TrailingZeroCount(scheduledCoresMask);
+
+                KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
+
+                // Request the thread running on that core to stop and reschedule, if we have one.
+                if (threadToSignal != context.Schedulers[coreToSignal]._idleThread)
+                {
+                    threadToSignal.Context.RequestInterrupt();
+                }
+
+                // If the core is idle, ensure that the idle thread is awaken.
+                context.Schedulers[coreToSignal]._idleInterruptEvent.Set();
+
+                scheduledCoresMask &= ~(1UL << coreToSignal);
+            }
+        }
+
+        private void IdleThreadLoop()
+        {
+            while (_context.Running)
+            {
+                _state.NeedsScheduling = false;
+                Thread.MemoryBarrier();
+                KThread nextThread = PickNextThread(_state.SelectedThread);
+
+                if (_idleThread != nextThread)
+                {
+                    _idleThread.SchedulerWaitEvent.Reset();
+                    WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent);
+                }
+
+                _idleInterruptEvent.WaitOne();
+            }
+
+            lock (_idleInterruptEventLock)
+            {
+                _idleInterruptEvent.Dispose();
+                _idleInterruptEvent = null;
+            }
         }
 
-        public KThread GetCurrentThread()
+        public void Schedule()
         {
-            return GetCurrentThreadOrNull() ?? GetDummyThread();
+            _state.NeedsScheduling = false;
+            Thread.MemoryBarrier();
+            KThread currentThread = KernelStatic.GetCurrentThread();
+            KThread selectedThread = _state.SelectedThread;
+
+            // If the thread is already scheduled and running on the core, we have nothing to do.
+            if (currentThread == selectedThread)
+            {
+                return;
+            }
+
+            currentThread.SchedulerWaitEvent.Reset();
+            currentThread.ThreadContext.Unlock();
+
+            // Wake all the threads that might be waiting until this thread context is unlocked.
+            for (int core = 0; core < CpuCoresCount; core++)
+            {
+                _context.Schedulers[core]._idleInterruptEvent.Set();
+            }
+
+            KThread nextThread = PickNextThread(selectedThread);
+
+            if (currentThread.Context.Running)
+            {
+                // Wait until this thread is scheduled again, and allow the next thread to run.
+                WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
+            }
+            else
+            {
+                // Allow the next thread to run.
+                nextThread.SchedulerWaitEvent.Set();
+
+                // We don't need to wait since the thread is exiting, however we need to
+                // make sure this thread will never call the scheduler again, since it is
+                // no longer assigned to a core.
+                currentThread.MakeUnschedulable();
+
+                // Just to be sure, set the core to a invalid value.
+                // This will trigger a exception if it attempts to call schedule again,
+                // rather than leaving the scheduler in a invalid state.
+                currentThread.CurrentCore = -1;
+            }
         }
 
-        public KThread GetCurrentThreadOrNull()
+        private KThread PickNextThread(KThread selectedThread)
         {
-            lock (CoreContexts)
+            while (true)
             {
-                for (int core = 0; core < CpuCoresCount; core++)
+                if (selectedThread != null)
                 {
-                    if (CoreContexts[core].CurrentThread?.IsCurrentHostThread() ?? false)
+                    // Try to run the selected thread.
+                    // We need to acquire the context lock to be sure the thread is not
+                    // already running on another core. If it is, then we return here
+                    // and the caller should try again once there is something available for scheduling.
+                    // The thread currently running on the core should have been requested to
+                    // interrupt so this is not expected to take long.
+                    // The idle thread must also be paused if we are scheduling a thread
+                    // on the core, as the scheduled thread will handle the next switch.
+                    if (selectedThread.ThreadContext.Lock())
                     {
-                        return CoreContexts[core].CurrentThread;
+                        SwitchTo(selectedThread);
+
+                        if (!_state.NeedsScheduling)
+                        {
+                            return selectedThread;
+                        }
+
+                        selectedThread.ThreadContext.Unlock();
                     }
+                    else
+                    {
+                        return _idleThread;
+                    }
+                }
+                else
+                {
+                    // The core is idle now, make sure that the idle thread can run
+                    // and switch the core when a thread is available.
+                    SwitchTo(null);
+                    return _idleThread;
                 }
+
+                _state.NeedsScheduling = false;
+                Thread.MemoryBarrier();
+                selectedThread = _state.SelectedThread;
+            }
+        }
+
+        private void SwitchTo(KThread nextThread)
+        {
+            KProcess currentProcess = KernelStatic.GetCurrentProcess();
+            KThread currentThread = KernelStatic.GetCurrentThread();
+
+            nextThread ??= _idleThread;
+
+            if (currentThread == nextThread)
+            {
+                return;
             }
 
-            return null;
+            long previousTicks = LastContextSwitchTime;
+            long currentTicks = PerformanceCounter.ElapsedTicks;
+            long ticksDelta = currentTicks - previousTicks;
+
+            currentThread.AddCpuTime(ticksDelta);
+
+            if (currentProcess != null)
+            {
+                currentProcess.AddCpuTime(ticksDelta);
+            }
+
+            LastContextSwitchTime = currentTicks;
+
+            if (currentProcess != null)
+            {
+                _previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
+            }
+            else if (currentThread == _idleThread)
+            {
+                _previousThread = null;
+            }
+
+            if (nextThread.CurrentCore != _coreId)
+            {
+                nextThread.CurrentCore = _coreId;
+            }
+
+            _currentThread = nextThread;
         }
 
-        private KThread _dummyThread;
+        public static void PreemptionThreadLoop(KernelContext context)
+        {
+            while (context.Running)
+            {
+                context.CriticalSection.Enter();
 
-        private KThread GetDummyThread()
+                for (int core = 0; core < CpuCoresCount; core++)
+                {
+                    RotateScheduledQueue(context, core, PreemptionPriorities[core]);
+                }
+
+                context.CriticalSection.Leave();
+
+                Thread.Sleep(RoundRobinTimeQuantumMs);
+            }
+        }
+
+        private static void RotateScheduledQueue(KernelContext context, int core, int prio)
         {
-            if (_dummyThread != null)
+            IEnumerable<KThread> scheduledThreads = context.PriorityQueue.ScheduledThreads(core);
+
+            KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
+            KThread nextThread = null;
+
+            // Yield priority queue.
+            if (selectedThread != null)
+            {
+                nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread);
+            }
+
+            IEnumerable<KThread> SuitableCandidates()
             {
-                return _dummyThread;
+                foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+                {
+                    int suggestedCore = suggested.ActiveCore;
+                    if (suggestedCore >= 0)
+                    {
+                        KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault();
+
+                        if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
+                        {
+                            continue;
+                        }
+                    }
+
+                    // If the candidate was scheduled after the current thread, then it's not worth it.
+                    if (nextThread == selectedThread ||
+                        nextThread == null ||
+                        nextThread.LastScheduledTime >= suggested.LastScheduledTime)
+                    {
+                        yield return suggested;
+                    }
+                }
             }
 
-            KProcess dummyProcess = new KProcess(_context);
+            // Select candidate threads that could run on this core.
+            // Only take into account threads that are not yet selected.
+            KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio);
 
-            dummyProcess.HandleTable.Initialize(1024);
+            if (dst != null)
+            {
+                context.PriorityQueue.TransferToCore(prio, core, dst);
+            }
+
+            // If the priority of the currently selected thread is lower or same as the preemption priority,
+            // then try to migrate a thread with lower priority.
+            KThread bestCandidate = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
+
+            if (bestCandidate != null && bestCandidate.DynamicPriority >= prio)
+            {
+                dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority < bestCandidate.DynamicPriority);
+
+                if (dst != null)
+                {
+                    context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst);
+                }
+            }
+
+            context.ThreadReselectionRequested = true;
+        }
+
+        public static void Yield(KernelContext context)
+        {
+            KThread currentThread = KernelStatic.GetCurrentThread();
+
+            context.CriticalSection.Enter();
+
+            if (currentThread.SchedFlags != ThreadSchedState.Running)
+            {
+                context.CriticalSection.Leave();
+                return;
+            }
 
-            KThread dummyThread = new KThread(_context);
+            KThread nextThread = context.PriorityQueue.Reschedule(currentThread.DynamicPriority, currentThread.ActiveCore, currentThread);
 
-            dummyThread.Initialize(0, 0, 0, 44, 0, dummyProcess, ThreadType.Dummy);
+            if (nextThread != currentThread)
+            {
+                context.ThreadReselectionRequested = true;
+            }
 
-            return _dummyThread = dummyThread;
+            context.CriticalSection.Leave();
         }
 
-        public KProcess GetCurrentProcess()
+        public static void YieldWithLoadBalancing(KernelContext context)
         {
-            return GetCurrentThread().Owner;
+            KThread currentThread = KernelStatic.GetCurrentThread();
+
+            context.CriticalSection.Enter();
+
+            if (currentThread.SchedFlags != ThreadSchedState.Running)
+            {
+                context.CriticalSection.Leave();
+                return;
+            }
+
+            int prio = currentThread.DynamicPriority;
+            int core = currentThread.ActiveCore;
+
+            // Move current thread to the end of the queue.
+            KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread);
+
+            IEnumerable<KThread> SuitableCandidates()
+            {
+                foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+                {
+                    int suggestedCore = suggested.ActiveCore;
+                    if (suggestedCore >= 0)
+                    {
+                        KThread selectedSuggestedCore = context.Schedulers[suggestedCore]._state.SelectedThread;
+
+                        if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
+                        {
+                            continue;
+                        }
+                    }
+
+                    // If the candidate was scheduled after the current thread, then it's not worth it,
+                    // unless the priority is higher than the current one.
+                    if (suggested.LastScheduledTime <= nextThread.LastScheduledTime ||
+                        suggested.DynamicPriority < nextThread.DynamicPriority)
+                    {
+                        yield return suggested;
+                    }
+                }
+            }
+
+            KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio);
+
+            if (dst != null)
+            {
+                context.PriorityQueue.TransferToCore(dst.DynamicPriority, core, dst);
+
+                context.ThreadReselectionRequested = true;
+            }
+            else if (currentThread != nextThread)
+            {
+                context.ThreadReselectionRequested = true;
+            }
+
+            context.CriticalSection.Leave();
         }
 
-        public void Dispose()
+        public static void YieldToAnyThread(KernelContext context)
         {
-            Dispose(true);
+            KThread currentThread = KernelStatic.GetCurrentThread();
+
+            context.CriticalSection.Enter();
+
+            if (currentThread.SchedFlags != ThreadSchedState.Running)
+            {
+                context.CriticalSection.Leave();
+                return;
+            }
+
+            int core = currentThread.ActiveCore;
+
+            context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread);
+
+            if (!context.PriorityQueue.ScheduledThreads(core).Any())
+            {
+                KThread selectedThread = null;
+
+                foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
+                {
+                    int suggestedCore = suggested.ActiveCore;
+
+                    if (suggestedCore < 0)
+                    {
+                        continue;
+                    }
+
+                    KThread firstCandidate = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault();
+
+                    if (firstCandidate == suggested)
+                    {
+                        continue;
+                    }
+
+                    if (firstCandidate == null || firstCandidate.DynamicPriority >= 2)
+                    {
+                        context.PriorityQueue.TransferToCore(suggested.DynamicPriority, core, suggested);
+                    }
+
+                    selectedThread = suggested;
+                    break;
+                }
+
+                if (currentThread != selectedThread)
+                {
+                    context.ThreadReselectionRequested = true;
+                }
+            }
+            else
+            {
+                context.ThreadReselectionRequested = true;
+            }
+
+            context.CriticalSection.Leave();
         }
 
-        protected virtual void Dispose(bool disposing)
+        public void Dispose()
         {
-            if (disposing)
+            // Ensure that the idle thread is not blocked and can exit.
+            lock (_idleInterruptEventLock)
             {
-                _keepPreempting = false;
+                if (_idleInterruptEvent != null)
+                {
+                    _idleInterruptEvent.Set();
+                }
             }
         }
     }

+ 1 - 1
Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs

@@ -43,7 +43,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 return result;
             }
 
-            KThread currentThread = _context.Scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
             if (currentThread.ShallBeTerminated ||
                 currentThread.SchedFlags == ThreadSchedState.TerminationPending)

+ 142 - 271
Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs

@@ -4,8 +4,7 @@ using Ryujinx.HLE.HOS.Kernel.Common;
 using Ryujinx.HLE.HOS.Kernel.Process;
 using System;
 using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using System.Numerics;
 using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Kernel.Threading
@@ -14,17 +13,24 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
     {
         public const int MaxWaitSyncObjects = 64;
 
-        private int _hostThreadRunning;
+        private ManualResetEvent _schedulerWaitEvent;
+
+        public ManualResetEvent SchedulerWaitEvent => _schedulerWaitEvent;
 
         public Thread HostThread { get; private set; }
 
         public ARMeilleure.State.ExecutionContext Context { get; private set; }
 
+        public KThreadContext ThreadContext { get; private set; }
+
+        public int DynamicPriority { get; set; }
         public long AffinityMask { get; set; }
 
         public long ThreadUid { get; private set; }
 
-        public long TotalTimeRunning { get; set; }
+        private long _totalTimeRunning;
+
+        public long TotalTimeRunning => _totalTimeRunning;
 
         public KSynchronizationObject SignaledObj { get; set; }
 
@@ -32,6 +38,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         private ulong _entrypoint;
         private ThreadStart _customThreadStart;
+        private bool _forcedUnschedulable;
+
+        public bool IsSchedulable => _customThreadStart == null && !_forcedUnschedulable;
 
         public ulong MutexAddress { get; set; }
 
@@ -65,11 +74,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
         public KernelResult ObjSyncResult { get; set; }
 
-        public int DynamicPriority { get; set; }
-        public int CurrentCore { get; set; }
         public int BasePriority { get; set; }
         public int PreferredCore { get; set; }
 
+        public int CurrentCore { get; set; }
+        public int ActiveCore { get; set; }
+
         private long _affinityMaskOverride;
         private int _preferredCoreOverride;
 #pragma warning disable CS0649
@@ -86,26 +96,21 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             set => _shallBeTerminated = value ? 1 : 0;
         }
 
+        public bool TerminationRequested => ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending;
+
         public bool SyncCancelled { get; set; }
         public bool WaitingSync { get; set; }
 
-        private bool _hasExited;
+        private int _hasExited;
         private bool _hasBeenInitialized;
         private bool _hasBeenReleased;
 
         public bool WaitingInArbitration { get; set; }
 
-        private KScheduler _scheduler;
-
-        private KSchedulingData _schedulingData;
-
         public long LastPc { get; set; }
 
         public KThread(KernelContext context) : base(context)
         {
-            _scheduler = KernelContext.Scheduler;
-            _schedulingData = KernelContext.Scheduler.SchedulingData;
-
             WaitSyncObjects = new KSynchronizationObject[MaxWaitSyncObjects];
             WaitSyncHandles = new int[MaxWaitSyncObjects];
 
@@ -119,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             ulong argsPtr,
             ulong stackTop,
             int priority,
-            int defaultCpuCore,
+            int cpuCore,
             KProcess owner,
             ThreadType type,
             ThreadStart customThreadStart = null)
@@ -129,20 +134,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 throw new ArgumentException($"Invalid thread type \"{type}\".");
             }
 
-            PreferredCore = defaultCpuCore;
+            ThreadContext = new KThreadContext();
 
-            AffinityMask |= 1L << defaultCpuCore;
+            PreferredCore = cpuCore;
+            AffinityMask |= 1L << cpuCore;
 
             SchedFlags = type == ThreadType.Dummy
                 ? ThreadSchedState.Running
                 : ThreadSchedState.None;
 
-            CurrentCore = PreferredCore;
-
+            ActiveCore = cpuCore;
+            ObjSyncResult = KernelResult.ThreadNotStarted;
             DynamicPriority = priority;
             BasePriority = priority;
-
-            ObjSyncResult = KernelResult.ThreadNotStarted;
+            CurrentCore = cpuCore;
 
             _entrypoint = entrypoint;
             _customThreadStart = customThreadStart;
@@ -179,41 +184,38 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
             Context = CpuContext.CreateExecutionContext();
 
-            bool isAarch32 = !Owner.Flags.HasFlag(ProcessCreationFlags.Is64Bit);
-
-            Context.IsAarch32 = isAarch32;
+            Context.IsAarch32 = !is64Bits;
 
             Context.SetX(0, argsPtr);
 
-            if (isAarch32)
+            if (is64Bits)
             {
-                Context.SetX(13, (uint)stackTop);
+                Context.SetX(31, stackTop);
             }
             else
             {
-                Context.SetX(31, stackTop);
+                Context.SetX(13, (uint)stackTop);
             }
 
             Context.CntfrqEl0 = 19200000;
             Context.Tpidr = (long)_tlsAddress;
 
-            owner.SubscribeThreadEventHandlers(Context);
-
             ThreadUid = KernelContext.NewThreadUid();
 
-            HostThread.Name = $"HLE.HostThread.{ThreadUid}";
+            HostThread.Name = customThreadStart != null ? $"HLE.OsThread.{ThreadUid}" : $"HLE.GuestThread.{ThreadUid}";
 
             _hasBeenInitialized = true;
 
             if (owner != null)
             {
+                owner.SubscribeThreadEventHandlers(Context);
                 owner.AddThread(this);
 
                 if (owner.IsPaused)
                 {
                     KernelContext.CriticalSection.Enter();
 
-                    if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
+                    if (TerminationRequested)
                     {
                         KernelContext.CriticalSection.Leave();
 
@@ -237,7 +239,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             {
                 KernelContext.CriticalSection.Enter();
 
-                if (!ShallBeTerminated && SchedFlags != ThreadSchedState.TerminationPending)
+                if (!TerminationRequested)
                 {
                     _forcePauseFlags |= ThreadSchedState.KernelInitPauseFlag;
 
@@ -253,20 +255,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
             if (!ShallBeTerminated)
             {
-                KThread currentThread = KernelContext.Scheduler.GetCurrentThread();
+                KThread currentThread = KernelStatic.GetCurrentThread();
 
-                while (SchedFlags != ThreadSchedState.TerminationPending &&
-                       currentThread.SchedFlags != ThreadSchedState.TerminationPending &&
-                       !currentThread.ShallBeTerminated)
+                while (SchedFlags != ThreadSchedState.TerminationPending && (currentThread == null || !currentThread.TerminationRequested))
                 {
                     if ((SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.None)
                     {
                         result = KernelResult.InvalidState;
-
                         break;
                     }
 
-                    if (currentThread._forcePauseFlags == ThreadSchedState.None)
+                    if (currentThread == null || currentThread._forcePauseFlags == ThreadSchedState.None)
                     {
                         if (Owner != null && _forcePauseFlags != ThreadSchedState.None)
                         {
@@ -275,8 +274,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
                         SetNewSchedFlags(ThreadSchedState.Running);
 
-                        result = KernelResult.Success;
+                        StartHostThread();
 
+                        result = KernelResult.Success;
                         break;
                     }
                     else
@@ -299,28 +299,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             return result;
         }
 
-        public void Exit()
-        {
-            // TODO: Debug event.
-
-            if (Owner != null)
-            {
-                Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1);
-
-                _hasBeenReleased = true;
-            }
-
-            KernelContext.CriticalSection.Enter();
-
-            _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
-
-            ExitImpl();
-
-            KernelContext.CriticalSection.Leave();
-
-            DecrementReferenceCount();
-        }
-
         public ThreadSchedState PrepareForTermination()
         {
             KernelContext.CriticalSection.Enter();
@@ -387,9 +365,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
             do
             {
-                if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
+                if (TerminationRequested)
                 {
-                    KernelContext.Scheduler.ExitThread(this);
                     Exit();
 
                     // As the death of the thread is handled by the CPU emulator, we differ from the official kernel and return here.
@@ -398,7 +375,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
                 KernelContext.CriticalSection.Enter();
 
-                if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
+                if (TerminationRequested)
                 {
                     state = ThreadSchedState.TerminationPending;
                 }
@@ -416,200 +393,74 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             } while (state == ThreadSchedState.TerminationPending);
         }
 
-        private void ExitImpl()
+        public void Exit()
         {
-            KernelContext.CriticalSection.Enter();
-
-            SetNewSchedFlags(ThreadSchedState.TerminationPending);
-
-            _hasExited = true;
+            // TODO: Debug event.
 
-            Signal();
+            if (Owner != null)
+            {
+                Owner.ResourceLimit?.Release(LimitableResource.Thread, 0, 1);
 
-            KernelContext.CriticalSection.Leave();
-        }
+                _hasBeenReleased = true;
+            }
 
-        public KernelResult Sleep(long timeout)
-        {
             KernelContext.CriticalSection.Enter();
 
-            if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
-            {
-                KernelContext.CriticalSection.Leave();
+            _forcePauseFlags &= ~ThreadSchedState.ForcePauseMask;
 
-                return KernelResult.ThreadTerminating;
-            }
+            bool decRef = ExitImpl();
 
-            SetNewSchedFlags(ThreadSchedState.Paused);
-
-            if (timeout > 0)
-            {
-                KernelContext.TimeManager.ScheduleFutureInvocation(this, timeout);
-            }
+            Context.StopRunning();
 
             KernelContext.CriticalSection.Leave();
 
-            if (timeout > 0)
+            if (decRef)
             {
-                KernelContext.TimeManager.UnscheduleFutureInvocation(this);
+                DecrementReferenceCount();
             }
-
-            return 0;
         }
 
-        public void Yield()
+        private bool ExitImpl()
         {
             KernelContext.CriticalSection.Enter();
 
-            if (SchedFlags != ThreadSchedState.Running)
-            {
-                KernelContext.CriticalSection.Leave();
-
-                KernelContext.Scheduler.ContextSwitch();
-
-                return;
-            }
+            SetNewSchedFlags(ThreadSchedState.TerminationPending);
 
-            if (DynamicPriority < KScheduler.PrioritiesCount)
-            {
-                // Move current thread to the end of the queue.
-                _schedulingData.Reschedule(DynamicPriority, CurrentCore, this);
-            }
+            bool decRef = Interlocked.Exchange(ref _hasExited, 1) == 0;
 
-            _scheduler.ThreadReselectionRequested = true;
+            Signal();
 
             KernelContext.CriticalSection.Leave();
 
-            KernelContext.Scheduler.ContextSwitch();
+            return decRef;
         }
 
-        public void YieldWithLoadBalancing()
+        public KernelResult Sleep(long timeout)
         {
             KernelContext.CriticalSection.Enter();
 
-            if (SchedFlags != ThreadSchedState.Running)
+            if (ShallBeTerminated || SchedFlags == ThreadSchedState.TerminationPending)
             {
                 KernelContext.CriticalSection.Leave();
 
-                KernelContext.Scheduler.ContextSwitch();
-
-                return;
-            }
-
-            int prio = DynamicPriority;
-            int core = CurrentCore;
-
-            KThread nextThreadOnCurrentQueue = null;
-
-            if (DynamicPriority < KScheduler.PrioritiesCount)
-            {
-                // Move current thread to the end of the queue.
-                _schedulingData.Reschedule(prio, core, this);
-
-                Func<KThread, bool> predicate = x => x.DynamicPriority == prio;
-
-                nextThreadOnCurrentQueue = _schedulingData.ScheduledThreads(core).FirstOrDefault(predicate);
-            }
-
-            IEnumerable<KThread> SuitableCandidates()
-            {
-                foreach (KThread thread in _schedulingData.SuggestedThreads(core))
-                {
-                    int srcCore = thread.CurrentCore;
-
-                    if (srcCore >= 0)
-                    {
-                        KThread selectedSrcCore = _scheduler.CoreContexts[srcCore].SelectedThread;
-
-                        if (selectedSrcCore == thread || ((selectedSrcCore?.DynamicPriority ?? 2) < 2))
-                        {
-                            continue;
-                        }
-                    }
-
-                    // If the candidate was scheduled after the current thread, then it's not worth it,
-                    // unless the priority is higher than the current one.
-                    if (nextThreadOnCurrentQueue.LastScheduledTime >= thread.LastScheduledTime ||
-                        nextThreadOnCurrentQueue.DynamicPriority < thread.DynamicPriority)
-                    {
-                        yield return thread;
-                    }
-                }
+                return KernelResult.ThreadTerminating;
             }
 
-            KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio);
-
-            if (dst != null)
-            {
-                _schedulingData.TransferToCore(dst.DynamicPriority, core, dst);
-
-                _scheduler.ThreadReselectionRequested = true;
-            }
+            SetNewSchedFlags(ThreadSchedState.Paused);
 
-            if (this != nextThreadOnCurrentQueue)
+            if (timeout > 0)
             {
-                _scheduler.ThreadReselectionRequested = true;
+                KernelContext.TimeManager.ScheduleFutureInvocation(this, timeout);
             }
 
             KernelContext.CriticalSection.Leave();
 
-            KernelContext.Scheduler.ContextSwitch();
-        }
-
-        public void YieldAndWaitForLoadBalancing()
-        {
-            KernelContext.CriticalSection.Enter();
-
-            if (SchedFlags != ThreadSchedState.Running)
-            {
-                KernelContext.CriticalSection.Leave();
-
-                KernelContext.Scheduler.ContextSwitch();
-
-                return;
-            }
-
-            int core = CurrentCore;
-
-            _schedulingData.TransferToCore(DynamicPriority, -1, this);
-
-            KThread selectedThread = null;
-
-            if (!_schedulingData.ScheduledThreads(core).Any())
-            {
-                foreach (KThread thread in _schedulingData.SuggestedThreads(core))
-                {
-                    if (thread.CurrentCore < 0)
-                    {
-                        continue;
-                    }
-
-                    KThread firstCandidate = _schedulingData.ScheduledThreads(thread.CurrentCore).FirstOrDefault();
-
-                    if (firstCandidate == thread)
-                    {
-                        continue;
-                    }
-
-                    if (firstCandidate == null || firstCandidate.DynamicPriority >= 2)
-                    {
-                        _schedulingData.TransferToCore(thread.DynamicPriority, core, thread);
-
-                        selectedThread = thread;
-                    }
-
-                    break;
-                }
-            }
-
-            if (selectedThread != this)
+            if (timeout > 0)
             {
-                _scheduler.ThreadReselectionRequested = true;
+                KernelContext.TimeManager.UnscheduleFutureInvocation(this);
             }
 
-            KernelContext.CriticalSection.Leave();
-
-            KernelContext.Scheduler.ContextSwitch();
+            return 0;
         }
 
         public void SetPriority(int priority)
@@ -751,17 +602,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
 
                 if (oldAffinityMask != newAffinityMask)
                 {
-                    int oldCore = CurrentCore;
+                    int oldCore = ActiveCore;
 
-                    if (CurrentCore >= 0 && ((AffinityMask >> CurrentCore) & 1) == 0)
+                    if (oldCore >= 0 && ((AffinityMask >> oldCore) & 1) == 0)
                     {
                         if (PreferredCore < 0)
                         {
-                            CurrentCore = HighestSetCore(AffinityMask);
+                            ActiveCore = sizeof(ulong) * 8 - 1 - BitOperations.LeadingZeroCount((ulong)AffinityMask);
                         }
                         else
                         {
-                            CurrentCore = PreferredCore;
+                            ActiveCore = PreferredCore;
                         }
                     }
 
@@ -774,19 +625,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             return KernelResult.Success;
         }
 
-        private static int HighestSetCore(long mask)
-        {
-            for (int core = KScheduler.CpuCoresCount - 1; core >= 0; core--)
-            {
-                if (((mask >> core) & 1) != 0)
-                {
-                    return core;
-                }
-            }
-
-            return -1;
-        }
-
         private void CombineForcePauseFlags()
         {
             ThreadSchedState oldFlags = SchedFlags;
@@ -995,92 +833,112 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 return;
             }
 
+            if (!IsSchedulable)
+            {
+                // Ensure our thread is running and we have an event.
+                StartHostThread();
+
+                // If the thread is not schedulable, we want to just run or pause
+                // it directly as we don't care about priority or the core it is
+                // running on in this case.
+                if (SchedFlags == ThreadSchedState.Running)
+                {
+                    _schedulerWaitEvent.Set();
+                }
+                else
+                {
+                    _schedulerWaitEvent.Reset();
+                }
+
+                return;
+            }
+
             if (oldFlags == ThreadSchedState.Running)
             {
                 // Was running, now it's stopped.
-                if (CurrentCore >= 0)
+                if (ActiveCore >= 0)
                 {
-                    _schedulingData.Unschedule(DynamicPriority, CurrentCore, this);
+                    KernelContext.PriorityQueue.Unschedule(DynamicPriority, ActiveCore, this);
                 }
 
                 for (int core = 0; core < KScheduler.CpuCoresCount; core++)
                 {
-                    if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+                    if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
                     {
-                        _schedulingData.Unsuggest(DynamicPriority, core, this);
+                        KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this);
                     }
                 }
             }
             else if (SchedFlags == ThreadSchedState.Running)
             {
                 // Was stopped, now it's running.
-                if (CurrentCore >= 0)
+                if (ActiveCore >= 0)
                 {
-                    _schedulingData.Schedule(DynamicPriority, CurrentCore, this);
+                    KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this);
                 }
 
                 for (int core = 0; core < KScheduler.CpuCoresCount; core++)
                 {
-                    if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+                    if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
                     {
-                        _schedulingData.Suggest(DynamicPriority, core, this);
+                        KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this);
                     }
                 }
             }
 
-            _scheduler.ThreadReselectionRequested = true;
+            KernelContext.ThreadReselectionRequested = true;
         }
 
         private void AdjustSchedulingForNewPriority(int oldPriority)
         {
-            if (SchedFlags != ThreadSchedState.Running)
+            if (SchedFlags != ThreadSchedState.Running || !IsSchedulable)
             {
                 return;
             }
 
             // Remove thread from the old priority queues.
-            if (CurrentCore >= 0)
+            if (ActiveCore >= 0)
             {
-                _schedulingData.Unschedule(oldPriority, CurrentCore, this);
+                KernelContext.PriorityQueue.Unschedule(oldPriority, ActiveCore, this);
             }
 
             for (int core = 0; core < KScheduler.CpuCoresCount; core++)
             {
-                if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+                if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
                 {
-                    _schedulingData.Unsuggest(oldPriority, core, this);
+                    KernelContext.PriorityQueue.Unsuggest(oldPriority, core, this);
                 }
             }
 
             // Add thread to the new priority queues.
-            KThread currentThread = _scheduler.GetCurrentThread();
+            KThread currentThread = KernelStatic.GetCurrentThread();
 
-            if (CurrentCore >= 0)
+            if (ActiveCore >= 0)
             {
                 if (currentThread == this)
                 {
-                    _schedulingData.SchedulePrepend(DynamicPriority, CurrentCore, this);
+                    KernelContext.PriorityQueue.SchedulePrepend(DynamicPriority, ActiveCore, this);
                 }
                 else
                 {
-                    _schedulingData.Schedule(DynamicPriority, CurrentCore, this);
+                    KernelContext.PriorityQueue.Schedule(DynamicPriority, ActiveCore, this);
                 }
             }
 
             for (int core = 0; core < KScheduler.CpuCoresCount; core++)
             {
-                if (core != CurrentCore && ((AffinityMask >> core) & 1) != 0)
+                if (core != ActiveCore && ((AffinityMask >> core) & 1) != 0)
                 {
-                    _schedulingData.Suggest(DynamicPriority, core, this);
+                    KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this);
                 }
             }
 
-            _scheduler.ThreadReselectionRequested = true;
+            KernelContext.ThreadReselectionRequested = true;
         }
 
         private void AdjustSchedulingForNewAffinity(long oldAffinityMask, int oldCore)
         {
-            if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount)
+            if (SchedFlags != ThreadSchedState.Running || DynamicPriority >= KScheduler.PrioritiesCount || !IsSchedulable)
             {
                 return;
             }
@@ -1092,11 +950,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 {
                     if (core == oldCore)
                     {
-                        _schedulingData.Unschedule(DynamicPriority, core, this);
+                        KernelContext.PriorityQueue.Unschedule(DynamicPriority, core, this);
                     }
                     else
                     {
-                        _schedulingData.Unsuggest(DynamicPriority, core, this);
+                        KernelContext.PriorityQueue.Unsuggest(DynamicPriority, core, this);
                     }
                 }
             }
@@ -1106,18 +964,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             {
                 if (((AffinityMask >> core) & 1) != 0)
                 {
-                    if (core == CurrentCore)
+                    if (core == ActiveCore)
                     {
-                        _schedulingData.Schedule(DynamicPriority, core, this);
+                        KernelContext.PriorityQueue.Schedule(DynamicPriority, core, this);
                     }
                     else
                     {
-                        _schedulingData.Suggest(DynamicPriority, core, this);
+                        KernelContext.PriorityQueue.Suggest(DynamicPriority, core, this);
                     }
                 }
             }
 
-            _scheduler.ThreadReselectionRequested = true;
+            KernelContext.ThreadReselectionRequested = true;
         }
 
         public void SetEntryArguments(long argsPtr, int threadHandle)
@@ -1141,17 +999,32 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
             Logger.Info?.Print(LogClass.Cpu, $"Guest stack trace:\n{GetGuestStackTrace()}\n");
         }
 
-        public void Execute()
+        public void AddCpuTime(long ticks)
+        {
+            Interlocked.Add(ref _totalTimeRunning, ticks);
+        }
+
+        public void StartHostThread()
         {
-            if (Interlocked.CompareExchange(ref _hostThreadRunning, 1, 0) == 0)
+            if (_schedulerWaitEvent == null)
             {
-                HostThread.Start();
+                var schedulerWaitEvent = new ManualResetEvent(false);
+
+                if (Interlocked.Exchange(ref _schedulerWaitEvent, schedulerWaitEvent) == null)
+                {
+                    HostThread.Start();
+                }
+                else
+                {
+                    schedulerWaitEvent.Dispose();
+                }
             }
         }
 
         private void ThreadStart()
         {
-            KernelStatic.SetKernelContext(KernelContext);
+            _schedulerWaitEvent.WaitOne();
+            KernelStatic.SetKernelContext(KernelContext, this);
 
             if (_customThreadStart != null)
             {
@@ -1162,20 +1035,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
                 Owner.Context.Execute(Context, _entrypoint);
             }
 
-            KernelContext.Scheduler.ExitThread(this);
-            KernelContext.Scheduler.RemoveThread(this);
-
             Context.Dispose();
+            _schedulerWaitEvent.Dispose();
         }
 
-        public bool IsCurrentHostThread()
+        public void MakeUnschedulable()
         {
-            return Thread.CurrentThread == HostThread;
+            _forcedUnschedulable = true;
         }
 
         public override bool IsSignaled()
         {
-            return _hasExited;
+            return _hasExited != 0;
         }
 
         protected override void Destroy()

+ 19 - 0
Ryujinx.HLE/HOS/Kernel/Threading/KThreadContext.cs

@@ -0,0 +1,19 @@
+using System.Threading;
+
+namespace Ryujinx.HLE.HOS.Kernel.Threading
+{
+    class KThreadContext
+    {
+        private int _locked;
+
+        public bool Lock()
+        {
+            return Interlocked.Exchange(ref _locked, 1) == 0;
+        }
+
+        public void Unlock()
+        {
+            Interlocked.Exchange(ref _locked, 0);
+        }
+    }
+}

+ 24 - 20
Ryujinx.HLE/HOS/Services/ServerBase.cs

@@ -30,7 +30,7 @@ namespace Ryujinx.HLE.HOS.Services
         };
 
         private readonly KernelContext _context;
-        private readonly KProcess _selfProcess;
+        private KProcess _selfProcess;
 
         private readonly List<int> _sessionHandles = new List<int>();
         private readonly List<int> _portHandles = new List<int>();
@@ -55,11 +55,7 @@ namespace Ryujinx.HLE.HOS.Services
 
             ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
 
-            context.Syscall.CreateProcess(creationInfo, DefaultCapabilities, out int handle, null, ServerLoop);
-
-            _selfProcess = context.Scheduler.GetCurrentProcess().HandleTable.GetKProcess(handle);
-
-            context.Syscall.StartProcess(handle, 44, 3, 0x1000);
+            KernelStatic.StartInitialProcess(context, creationInfo, DefaultCapabilities, 44, ServerLoop);
         }
 
         private void AddPort(int serverPortHandle, IpcService obj)
@@ -82,6 +78,8 @@ namespace Ryujinx.HLE.HOS.Services
 
         private void ServerLoop()
         {
+            _selfProcess = KernelStatic.GetCurrentProcess();
+
             if (SmObject != null)
             {
                 _context.Syscall.ManageNamedPort("sm:", 50, out int serverPortHandle);
@@ -95,7 +93,7 @@ namespace Ryujinx.HLE.HOS.Services
                 InitDone.Dispose();
             }
 
-            KThread thread = _context.Scheduler.GetCurrentThread();
+            KThread thread = KernelStatic.GetCurrentThread();
             ulong messagePtr = thread.TlsAddress;
             _context.Syscall.SetHeapSize(0x200000, out ulong heapAddr);
 
@@ -107,18 +105,14 @@ namespace Ryujinx.HLE.HOS.Services
 
             while (true)
             {
-                int[] handles = _portHandles.ToArray();
+                int[] portHandles = _portHandles.ToArray();
+                int[] sessionHandles = _sessionHandles.ToArray();
+                int[] handles = new int[portHandles.Length + sessionHandles.Length];
 
-                for (int i = 0; i < handles.Length; i++)
-                {
-                    if (_context.Syscall.AcceptSession(handles[i], out int serverSessionHandle) == KernelResult.Success)
-                    {
-                        AddSessionObj(serverSessionHandle, _ports[handles[i]]);
-                    }
-                }
-
-                handles = _sessionHandles.ToArray();
+                portHandles.CopyTo(handles, 0);
+                sessionHandles.CopyTo(handles, portHandles.Length);
 
+                // We still need a timeout here to allow the service to pick up and listen new sessions...
                 var rc = _context.Syscall.ReplyAndReceive(handles, replyTargetHandle, 1000000L, out int signaledIndex);
 
                 thread.HandlePostSyscall();
@@ -130,8 +124,9 @@ namespace Ryujinx.HLE.HOS.Services
 
                 replyTargetHandle = 0;
 
-                if (rc == KernelResult.Success && signaledIndex != -1)
+                if (rc == KernelResult.Success && signaledIndex >= portHandles.Length)
                 {
+                    // We got a IPC request, process it, pass to the appropriate service if needed.
                     int signaledHandle = handles[signaledIndex];
 
                     if (Process(signaledHandle, heapAddr))
@@ -141,6 +136,15 @@ namespace Ryujinx.HLE.HOS.Services
                 }
                 else
                 {
+                    if (rc == KernelResult.Success)
+                    {
+                        // We got a new connection, accept the session to allow servicing future requests.
+                        if (_context.Syscall.AcceptSession(handles[signaledIndex], out int serverSessionHandle) == KernelResult.Success)
+                        {
+                            AddSessionObj(serverSessionHandle, _ports[handles[signaledIndex]]);
+                        }
+                    }
+
                     _selfProcess.CpuMemory.Write(messagePtr + 0x0, 0);
                     _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
                     _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
@@ -150,8 +154,8 @@ namespace Ryujinx.HLE.HOS.Services
 
         private bool Process(int serverSessionHandle, ulong recvListAddr)
         {
-            KProcess process = _context.Scheduler.GetCurrentProcess();
-            KThread thread = _context.Scheduler.GetCurrentThread();
+            KProcess process = KernelStatic.GetCurrentProcess();
+            KThread thread = KernelStatic.GetCurrentThread();
             ulong messagePtr = thread.TlsAddress;
             ulong messageSize = 0x100;
 

+ 4 - 15
Ryujinx.HLE/HOS/Services/SurfaceFlinger/BufferQueueCore.cs

@@ -184,11 +184,7 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
         public void WaitDequeueEvent()
         {
-            Monitor.Exit(Lock);
-
-            KernelStatic.YieldUntilCompletion(WaitForLock);
-
-            Monitor.Enter(Lock);
+            WaitForLock();
         }
 
         public void SignalIsAllocatingEvent()
@@ -198,21 +194,14 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger
 
         public void WaitIsAllocatingEvent()
         {
-            Monitor.Exit(Lock);
-
-            KernelStatic.YieldUntilCompletion(WaitForLock);
-
-            Monitor.Enter(Lock);
+            WaitForLock();
         }
 
         private void WaitForLock()
         {
-            lock (Lock)
+            if (Active)
             {
-                if (Active)
-                {
-                    Monitor.Wait(Lock);
-                }
+                Monitor.Wait(Lock);
             }
         }
 

+ 0 - 5
Ryujinx.HLE/Switch.cs

@@ -115,11 +115,6 @@ namespace Ryujinx.HLE
 
             System.PerformanceState.PerformanceMode = System.State.DockedMode ? PerformanceMode.Boost : PerformanceMode.Default;
 
-            if (ConfigurationState.Instance.System.EnableMulticoreScheduling)
-            {
-                System.EnableMultiCoreScheduling();
-            }
-
             System.EnablePtc = ConfigurationState.Instance.System.EnablePtc;
 
             System.FsIntegrityCheckLevel = GetIntegrityCheckLevel();

+ 1 - 2
Ryujinx/Config.json

@@ -1,4 +1,4 @@
-{ 
+{
   "version": 17,
   "res_scale": 1,
   "res_scale_custom": 1,
@@ -22,7 +22,6 @@
   "check_updates_on_start": true,
   "enable_vsync": true,
   "enable_shader_cache": true,
-  "enable_multicore_scheduling": true,
   "enable_ptc": false,
   "enable_fs_integrity_checks": true,
   "fs_global_access_log_mode": 0,

+ 25 - 0
Ryujinx/THIRDPARTY.md

@@ -200,4 +200,29 @@ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+```
+
+# Atmosphère (MIT)
+```
+MIT License
+
+Copyright (c) 2018-2020 Atmosphère-NX
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
 ```

+ 1 - 8
Ryujinx/Ui/SettingsWindow.cs

@@ -43,7 +43,6 @@ namespace Ryujinx.Ui
         [GUI] CheckButton     _checkUpdatesToggle;
         [GUI] CheckButton     _vSyncToggle;
         [GUI] CheckButton     _shaderCacheToggle;
-        [GUI] CheckButton     _multiSchedToggle;
         [GUI] CheckButton     _ptcToggle;
         [GUI] CheckButton     _fsicToggle;
         [GUI] CheckButton     _ignoreToggle;
@@ -188,11 +187,6 @@ namespace Ryujinx.Ui
                 _shaderCacheToggle.Click();
             }
 
-            if (ConfigurationState.Instance.System.EnableMulticoreScheduling)
-            {
-                _multiSchedToggle.Click();
-            }
-
             if (ConfigurationState.Instance.System.EnablePtc)
             {
                 _ptcToggle.Click();
@@ -401,7 +395,6 @@ namespace Ryujinx.Ui
             ConfigurationState.Instance.CheckUpdatesOnStart.Value              = _checkUpdatesToggle.Active;
             ConfigurationState.Instance.Graphics.EnableVsync.Value             = _vSyncToggle.Active;
             ConfigurationState.Instance.Graphics.EnableShaderCache.Value       = _shaderCacheToggle.Active;
-            ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active;
             ConfigurationState.Instance.System.EnablePtc.Value                 = _ptcToggle.Active;
             ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value   = _fsicToggle.Active;
             ConfigurationState.Instance.System.IgnoreMissingServices.Value     = _ignoreToggle.Active;
@@ -490,7 +483,7 @@ namespace Ryujinx.Ui
                     foreach (string directory in fileChooser.Filenames)
                     {
                         bool directoryAdded = false;
-                        
+
                         if (_gameDirsBoxStore.GetIterFirst(out TreeIter treeIter))
                         {
                             do

+ 0 - 18
Ryujinx/Ui/SettingsWindow.glade

@@ -1439,24 +1439,6 @@
                                     <property name="position">4</property>
                                   </packing>
                                 </child>
-                                <child>
-                                  <object class="GtkCheckButton" id="_multiSchedToggle">
-                                    <property name="label" translatable="yes">Enable Multicore Scheduling</property>
-                                    <property name="visible">True</property>
-                                    <property name="can-focus">True</property>
-                                    <property name="receives-default">False</property>
-                                    <property name="tooltip-text" translatable="yes">Enables or disables multi-core scheduling of threads</property>
-                                    <property name="halign">start</property>
-                                    <property name="margin-top">5</property>
-                                    <property name="margin-bottom">5</property>
-                                    <property name="draw-indicator">True</property>
-                                  </object>
-                                  <packing>
-                                    <property name="expand">False</property>
-                                    <property name="fill">True</property>
-                                    <property name="position">5</property>
-                                  </packing>
-                                </child>
                                 <child>
                                   <object class="GtkCheckButton" id="_ptcToggle">
                                     <property name="label" translatable="yes">Enable Profiled Persistent Translation Cache</property>

+ 0 - 12
Ryujinx/_schema.json

@@ -18,7 +18,6 @@
     "system_region",
     "docked_mode",
     "enable_vsync",
-    "enable_multicore_scheduling",
     "enable_ptc",
     "enable_fs_integrity_checks",
     "fs_global_access_log_mode",
@@ -1196,17 +1195,6 @@
         false
       ]
     },
-    "enable_multicore_scheduling": {
-      "$id": "#/properties/enable_multicore_scheduling",
-      "type": "boolean",
-      "title": "Enable Multicore Scheduling",
-      "description": "Enables or disables multi-core scheduling of threads",
-      "default": true,
-      "examples": [
-        true,
-        false
-      ]
-    },
     "enable_ptc": {
       "$id": "#/properties/enable_ptc",
       "type": "boolean",