diff --git a/Directory.Packages.props b/Directory.Packages.props
index fc27404c24..9fe233966f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
index 905c61d667..8ef77902c2 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs
@@ -28,42 +28,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
private SchedulingState _state;
- private AutoResetEvent _idleInterruptEvent;
- private readonly object _idleInterruptEventLock;
-
private KThread _previousThread;
private KThread _currentThread;
- private readonly KThread _idleThread;
+
+ private int _coreIdleLock;
+ private bool _idleSignalled = true;
+ private bool _idleActive = true;
+ private long _idleTimeRunning;
public KThread PreviousThread => _previousThread;
public KThread CurrentThread => _currentThread;
public long LastContextSwitchTime { get; private set; }
- public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
+ public long TotalIdleTimeTicks => _idleTimeRunning;
public KScheduler(KernelContext context, int coreId)
{
_context = context;
_coreId = coreId;
- _idleInterruptEvent = new AutoResetEvent(false);
- _idleInterruptEventLock = new object();
-
- KThread idleThread = CreateIdleThread(context, coreId);
-
- _currentThread = idleThread;
- _idleThread = idleThread;
-
- idleThread.StartHostThread();
- idleThread.SchedulerWaitEvent.Set();
- }
-
- private KThread CreateIdleThread(KernelContext context, int cpuCore)
- {
- KThread idleThread = new(context);
-
- idleThread.Initialize(0UL, 0UL, 0UL, PrioritiesCount, cpuCore, null, ThreadType.Dummy, IdleThreadLoop);
-
- return idleThread;
+ _currentThread = null;
}
public static ulong SelectThreads(KernelContext context)
@@ -237,39 +220,64 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
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();
- }
+ threadToSignal?.Context.RequestInterrupt();
// If the core is idle, ensure that the idle thread is awaken.
- context.Schedulers[coreToSignal]._idleInterruptEvent.Set();
+ context.Schedulers[coreToSignal].NotifyIdleThread();
scheduledCoresMask &= ~(1UL << coreToSignal);
}
}
- private void IdleThreadLoop()
+ private void ActivateIdleThread()
{
- while (_context.Running)
+ while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0)
+ {
+ Thread.SpinWait(1);
+ }
+
+ Thread.MemoryBarrier();
+
+ // Signals that idle thread is now active on this core.
+ _idleActive = true;
+
+ TryLeaveIdle();
+
+ Interlocked.Exchange(ref _coreIdleLock, 0);
+ }
+
+ private void NotifyIdleThread()
+ {
+ while (Interlocked.CompareExchange(ref _coreIdleLock, 1, 0) != 0)
+ {
+ Thread.SpinWait(1);
+ }
+
+ Thread.MemoryBarrier();
+
+ // Signals that the idle core may be able to exit idle.
+ _idleSignalled = true;
+
+ TryLeaveIdle();
+
+ Interlocked.Exchange(ref _coreIdleLock, 0);
+ }
+
+ public void TryLeaveIdle()
+ {
+ if (_idleSignalled && _idleActive)
{
_state.NeedsScheduling = false;
Thread.MemoryBarrier();
- KThread nextThread = PickNextThread(_state.SelectedThread);
+ KThread nextThread = PickNextThread(null, _state.SelectedThread);
- if (_idleThread != nextThread)
+ if (nextThread != null)
{
- _idleThread.SchedulerWaitEvent.Reset();
- WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent);
+ _idleActive = false;
+ nextThread.SchedulerWaitEvent.Set();
}
- _idleInterruptEvent.WaitOne();
- }
-
- lock (_idleInterruptEventLock)
- {
- _idleInterruptEvent.Dispose();
- _idleInterruptEvent = null;
+ _idleSignalled = false;
}
}
@@ -292,20 +300,37 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// 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();
+ _context.Schedulers[core].NotifyIdleThread();
}
- KThread nextThread = PickNextThread(selectedThread);
+ KThread nextThread = PickNextThread(KernelStatic.GetCurrentThread(), 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);
+
+ if (nextThread == null)
+ {
+ ActivateIdleThread();
+ currentThread.SchedulerWaitEvent.WaitOne();
+ }
+ else
+ {
+ WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, currentThread.SchedulerWaitEvent);
+ }
}
else
{
// Allow the next thread to run.
- nextThread.SchedulerWaitEvent.Set();
+
+ if (nextThread == null)
+ {
+ ActivateIdleThread();
+ }
+ else
+ {
+ 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
@@ -319,7 +344,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
}
- private KThread PickNextThread(KThread selectedThread)
+ private KThread PickNextThread(KThread currentThread, KThread selectedThread)
{
while (true)
{
@@ -335,7 +360,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// on the core, as the scheduled thread will handle the next switch.
if (selectedThread.ThreadContext.Lock())
{
- SwitchTo(selectedThread);
+ SwitchTo(currentThread, selectedThread);
if (!_state.NeedsScheduling)
{
@@ -346,15 +371,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
else
{
- return _idleThread;
+ return null;
}
}
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;
+ SwitchTo(currentThread, null);
+ return null;
}
_state.NeedsScheduling = false;
@@ -363,12 +388,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
}
}
- private void SwitchTo(KThread nextThread)
+ private void SwitchTo(KThread currentThread, KThread nextThread)
{
- KProcess currentProcess = KernelStatic.GetCurrentProcess();
- KThread currentThread = KernelStatic.GetCurrentThread();
-
- nextThread ??= _idleThread;
+ KProcess currentProcess = currentThread?.Owner;
if (currentThread != nextThread)
{
@@ -376,7 +398,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
long currentTicks = PerformanceCounter.ElapsedTicks;
long ticksDelta = currentTicks - previousTicks;
- currentThread.AddCpuTime(ticksDelta);
+ if (currentThread == null)
+ {
+ Interlocked.Add(ref _idleTimeRunning, ticksDelta);
+ }
+ else
+ {
+ currentThread.AddCpuTime(ticksDelta);
+ }
currentProcess?.AddCpuTime(ticksDelta);
@@ -386,13 +415,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
_previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
}
- else if (currentThread == _idleThread)
+ else if (currentThread == null)
{
_previousThread = null;
}
}
- if (nextThread.CurrentCore != _coreId)
+ if (nextThread != null && nextThread.CurrentCore != _coreId)
{
nextThread.CurrentCore = _coreId;
}
@@ -645,11 +674,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
public void Dispose()
{
- // Ensure that the idle thread is not blocked and can exit.
- lock (_idleInterruptEventLock)
- {
- _idleInterruptEvent?.Set();
- }
+ // No resources to dispose for now.
}
}
}
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 12383fb8a1..835bf5d405 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -143,9 +143,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
PreferredCore = cpuCore;
AffinityMask |= 1UL << cpuCore;
- SchedFlags = type == ThreadType.Dummy
- ? ThreadSchedState.Running
- : ThreadSchedState.None;
+ SchedFlags = ThreadSchedState.None;
ActiveCore = cpuCore;
ObjSyncResult = KernelResult.ThreadNotStarted;
@@ -1055,6 +1053,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// 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();
diff --git a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
index 83093570b7..e2dfd2ffbb 100644
--- a/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
+++ b/src/Ryujinx.HLE/HOS/Kernel/Threading/ThreadType.cs
@@ -2,7 +2,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
{
enum ThreadType
{
- Dummy,
Kernel,
Kernel2,
User,
diff --git a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
index d1f572d5c8..bf34ddd17a 100644
--- a/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
+++ b/src/Ryujinx.Horizon/Ryujinx.Horizon.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -16,10 +16,4 @@
-
-
-
- NU1605
-
-
diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
index 5d27985826..2146362dfc 100644
--- a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
+++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
@@ -14,6 +14,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
{
partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
{
+ static HardwareOpusDecoder()
+ {
+ OpusCodecFactory.AttemptToUseNativeLibrary = false;
+ }
+
[StructLayout(LayoutKind.Sequential)]
private struct OpusPacketHeader
{
@@ -30,60 +35,87 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
}
}
- private interface IDecoder
+ private interface IDecoder : IDisposable
{
int SampleRate { get; }
int ChannelsCount { get; }
- int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
+ int Decode(ReadOnlySpan inData, Span outPcm, int frameSize);
void ResetState();
}
private class Decoder : IDecoder
{
- private readonly OpusDecoder _decoder;
+ private readonly IOpusDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels;
public Decoder(int sampleRate, int channelsCount)
{
- _decoder = new OpusDecoder(sampleRate, channelsCount);
+ _decoder = OpusCodecFactory.CreateDecoder(sampleRate, channelsCount);
}
- public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+ public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize)
{
- return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
+ return _decoder.Decode(inData, outPcm, frameSize);
}
public void ResetState()
{
_decoder.ResetState();
}
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _decoder?.Dispose();
+ }
+ }
}
private class MultiSampleDecoder : IDecoder
{
- private readonly OpusMSDecoder _decoder;
+ private readonly IOpusMultiStreamDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
- public int ChannelsCount { get; }
+ public int ChannelsCount => _decoder.NumChannels;
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
{
- ChannelsCount = channelsCount;
- _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
+ _decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
}
- public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+ public int Decode(ReadOnlySpan inData, Span outPcm, int frameSize)
{
- return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
+ return _decoder.DecodeMultistream(inData, outPcm, frameSize, false);
}
public void ResetState()
{
_decoder.ResetState();
}
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _decoder?.Dispose();
+ }
+ }
}
private readonly IDecoder _decoder;
@@ -221,7 +253,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
{
timeTaken = 0;
- Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples);
+ Span outPcmSpace = MemoryMarshal.Cast(output);
+ Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples);
if (withPerf)
{
@@ -229,14 +262,12 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
timeTaken = 0;
}
- MemoryMarshal.Cast(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
-
return result;
}
- private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet)
+ private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan packet)
{
- int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
+ int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate);
numSamples = result;
@@ -256,12 +287,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
IDecoder decoder,
bool reset,
ReadOnlySpan input,
- out short[] outPcmData,
+ Span outPcmData,
int outputSize,
out int outConsumed,
out int outSamples)
{
- outPcmData = null;
outConsumed = 0;
outSamples = 0;
@@ -281,7 +311,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
return CodecResult.InvalidLength;
}
- byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray();
+ ReadOnlySpan opusData = input.Slice(headerSize, (int)header.Length);
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
@@ -292,8 +322,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
return CodecResult.InvalidLength;
}
- outPcmData = new short[numSamples * decoder.ChannelsCount];
-
if (reset)
{
decoder.ResetState();
@@ -301,13 +329,22 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
try
{
- outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
+ outSamples = decoder.Decode(opusData, outPcmData, numSamples);
outConsumed = (int)totalSize;
}
- catch (OpusException)
+ catch (OpusException e)
{
- // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases...
- return CodecResult.InvalidLength;
+ switch (e.OpusErrorCode)
+ {
+ case OpusError.OPUS_BUFFER_TOO_SMALL:
+ return CodecResult.InvalidLength;
+ case OpusError.OPUS_BAD_ARG:
+ return CodecResult.OpusBadArg;
+ case OpusError.OPUS_INVALID_PACKET:
+ return CodecResult.OpusInvalidPacket;
+ default:
+ return CodecResult.InvalidLength;
+ }
}
}
@@ -324,6 +361,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
_workBufferHandle = 0;
}
+
+ _decoder?.Dispose();
}
}