Merge branch 'master' into MoreDynamicStatesTesting
This commit is contained in:
commit
cfa0b47e31
6 changed files with 158 additions and 102 deletions
|
@ -11,7 +11,7 @@
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<PackageVersion Include="Concentus" Version="2.2.0" />
|
||||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
<PackageVersion Include="DynamicData" Version="8.4.1" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
|
|
|
@ -28,42 +28,25 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
|
|
||||||
private SchedulingState _state;
|
private SchedulingState _state;
|
||||||
|
|
||||||
private AutoResetEvent _idleInterruptEvent;
|
|
||||||
private readonly object _idleInterruptEventLock;
|
|
||||||
|
|
||||||
private KThread _previousThread;
|
private KThread _previousThread;
|
||||||
private KThread _currentThread;
|
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 PreviousThread => _previousThread;
|
||||||
public KThread CurrentThread => _currentThread;
|
public KThread CurrentThread => _currentThread;
|
||||||
public long LastContextSwitchTime { get; private set; }
|
public long LastContextSwitchTime { get; private set; }
|
||||||
public long TotalIdleTimeTicks => _idleThread.TotalTimeRunning;
|
public long TotalIdleTimeTicks => _idleTimeRunning;
|
||||||
|
|
||||||
public KScheduler(KernelContext context, int coreId)
|
public KScheduler(KernelContext context, int coreId)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_coreId = coreId;
|
_coreId = coreId;
|
||||||
|
|
||||||
_idleInterruptEvent = new AutoResetEvent(false);
|
_currentThread = null;
|
||||||
_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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ulong SelectThreads(KernelContext context)
|
public static ulong SelectThreads(KernelContext context)
|
||||||
|
@ -237,39 +220,64 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
|
KThread threadToSignal = context.Schedulers[coreToSignal]._currentThread;
|
||||||
|
|
||||||
// Request the thread running on that core to stop and reschedule, if we have one.
|
// 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.
|
// If the core is idle, ensure that the idle thread is awaken.
|
||||||
context.Schedulers[coreToSignal]._idleInterruptEvent.Set();
|
context.Schedulers[coreToSignal].NotifyIdleThread();
|
||||||
|
|
||||||
scheduledCoresMask &= ~(1UL << coreToSignal);
|
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;
|
_state.NeedsScheduling = false;
|
||||||
Thread.MemoryBarrier();
|
Thread.MemoryBarrier();
|
||||||
KThread nextThread = PickNextThread(_state.SelectedThread);
|
KThread nextThread = PickNextThread(null, _state.SelectedThread);
|
||||||
|
|
||||||
if (_idleThread != nextThread)
|
if (nextThread != null)
|
||||||
{
|
{
|
||||||
_idleThread.SchedulerWaitEvent.Reset();
|
_idleActive = false;
|
||||||
WaitHandle.SignalAndWait(nextThread.SchedulerWaitEvent, _idleThread.SchedulerWaitEvent);
|
nextThread.SchedulerWaitEvent.Set();
|
||||||
}
|
}
|
||||||
|
|
||||||
_idleInterruptEvent.WaitOne();
|
_idleSignalled = false;
|
||||||
}
|
|
||||||
|
|
||||||
lock (_idleInterruptEventLock)
|
|
||||||
{
|
|
||||||
_idleInterruptEvent.Dispose();
|
|
||||||
_idleInterruptEvent = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,20 +300,37 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
// Wake all the threads that might be waiting until this thread context is unlocked.
|
// Wake all the threads that might be waiting until this thread context is unlocked.
|
||||||
for (int core = 0; core < CpuCoresCount; core++)
|
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)
|
if (currentThread.Context.Running)
|
||||||
{
|
{
|
||||||
// Wait until this thread is scheduled again, and allow the next thread to run.
|
// 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
|
else
|
||||||
{
|
{
|
||||||
// Allow the next thread to run.
|
// 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
|
// 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
|
// 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)
|
while (true)
|
||||||
{
|
{
|
||||||
|
@ -335,7 +360,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
// on the core, as the scheduled thread will handle the next switch.
|
// on the core, as the scheduled thread will handle the next switch.
|
||||||
if (selectedThread.ThreadContext.Lock())
|
if (selectedThread.ThreadContext.Lock())
|
||||||
{
|
{
|
||||||
SwitchTo(selectedThread);
|
SwitchTo(currentThread, selectedThread);
|
||||||
|
|
||||||
if (!_state.NeedsScheduling)
|
if (!_state.NeedsScheduling)
|
||||||
{
|
{
|
||||||
|
@ -346,15 +371,15 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return _idleThread;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The core is idle now, make sure that the idle thread can run
|
// The core is idle now, make sure that the idle thread can run
|
||||||
// and switch the core when a thread is available.
|
// and switch the core when a thread is available.
|
||||||
SwitchTo(null);
|
SwitchTo(currentThread, null);
|
||||||
return _idleThread;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
_state.NeedsScheduling = false;
|
_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();
|
KProcess currentProcess = currentThread?.Owner;
|
||||||
KThread currentThread = KernelStatic.GetCurrentThread();
|
|
||||||
|
|
||||||
nextThread ??= _idleThread;
|
|
||||||
|
|
||||||
if (currentThread != nextThread)
|
if (currentThread != nextThread)
|
||||||
{
|
{
|
||||||
|
@ -376,7 +398,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
long currentTicks = PerformanceCounter.ElapsedTicks;
|
long currentTicks = PerformanceCounter.ElapsedTicks;
|
||||||
long ticksDelta = currentTicks - previousTicks;
|
long ticksDelta = currentTicks - previousTicks;
|
||||||
|
|
||||||
currentThread.AddCpuTime(ticksDelta);
|
if (currentThread == null)
|
||||||
|
{
|
||||||
|
Interlocked.Add(ref _idleTimeRunning, ticksDelta);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentThread.AddCpuTime(ticksDelta);
|
||||||
|
}
|
||||||
|
|
||||||
currentProcess?.AddCpuTime(ticksDelta);
|
currentProcess?.AddCpuTime(ticksDelta);
|
||||||
|
|
||||||
|
@ -386,13 +415,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
{
|
{
|
||||||
_previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
|
_previousThread = !currentThread.TerminationRequested && currentThread.ActiveCore == _coreId ? currentThread : null;
|
||||||
}
|
}
|
||||||
else if (currentThread == _idleThread)
|
else if (currentThread == null)
|
||||||
{
|
{
|
||||||
_previousThread = null;
|
_previousThread = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nextThread.CurrentCore != _coreId)
|
if (nextThread != null && nextThread.CurrentCore != _coreId)
|
||||||
{
|
{
|
||||||
nextThread.CurrentCore = _coreId;
|
nextThread.CurrentCore = _coreId;
|
||||||
}
|
}
|
||||||
|
@ -645,11 +674,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Ensure that the idle thread is not blocked and can exit.
|
// No resources to dispose for now.
|
||||||
lock (_idleInterruptEventLock)
|
|
||||||
{
|
|
||||||
_idleInterruptEvent?.Set();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,9 +143,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
PreferredCore = cpuCore;
|
PreferredCore = cpuCore;
|
||||||
AffinityMask |= 1UL << cpuCore;
|
AffinityMask |= 1UL << cpuCore;
|
||||||
|
|
||||||
SchedFlags = type == ThreadType.Dummy
|
SchedFlags = ThreadSchedState.None;
|
||||||
? ThreadSchedState.Running
|
|
||||||
: ThreadSchedState.None;
|
|
||||||
|
|
||||||
ActiveCore = cpuCore;
|
ActiveCore = cpuCore;
|
||||||
ObjSyncResult = KernelResult.ThreadNotStarted;
|
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
|
// 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
|
// it directly as we don't care about priority or the core it is
|
||||||
// running on in this case.
|
// running on in this case.
|
||||||
|
|
||||||
if (SchedFlags == ThreadSchedState.Running)
|
if (SchedFlags == ThreadSchedState.Running)
|
||||||
{
|
{
|
||||||
_schedulerWaitEvent.Set();
|
_schedulerWaitEvent.Set();
|
||||||
|
|
|
@ -2,7 +2,6 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
||||||
{
|
{
|
||||||
enum ThreadType
|
enum ThreadType
|
||||||
{
|
{
|
||||||
Dummy,
|
|
||||||
Kernel,
|
Kernel,
|
||||||
Kernel2,
|
Kernel2,
|
||||||
User,
|
User,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
@ -16,10 +16,4 @@
|
||||||
<PackageReference Include="Concentus" />
|
<PackageReference Include="Concentus" />
|
||||||
<PackageReference Include="LibHac" />
|
<PackageReference Include="LibHac" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Due to Concentus. -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<NoWarn>NU1605</NoWarn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -14,6 +14,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
{
|
{
|
||||||
partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
|
partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable
|
||||||
{
|
{
|
||||||
|
static HardwareOpusDecoder()
|
||||||
|
{
|
||||||
|
OpusCodecFactory.AttemptToUseNativeLibrary = false;
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
private struct OpusPacketHeader
|
private struct OpusPacketHeader
|
||||||
{
|
{
|
||||||
|
@ -30,60 +35,87 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface IDecoder
|
private interface IDecoder : IDisposable
|
||||||
{
|
{
|
||||||
int SampleRate { get; }
|
int SampleRate { get; }
|
||||||
int ChannelsCount { get; }
|
int ChannelsCount { get; }
|
||||||
|
|
||||||
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
|
int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize);
|
||||||
void ResetState();
|
void ResetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Decoder : IDecoder
|
private class Decoder : IDecoder
|
||||||
{
|
{
|
||||||
private readonly OpusDecoder _decoder;
|
private readonly IOpusDecoder _decoder;
|
||||||
|
|
||||||
public int SampleRate => _decoder.SampleRate;
|
public int SampleRate => _decoder.SampleRate;
|
||||||
public int ChannelsCount => _decoder.NumChannels;
|
public int ChannelsCount => _decoder.NumChannels;
|
||||||
|
|
||||||
public Decoder(int sampleRate, int channelsCount)
|
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<byte> inData, Span<short> outPcm, int frameSize)
|
||||||
{
|
{
|
||||||
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
|
return _decoder.Decode(inData, outPcm, frameSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
_decoder.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 class MultiSampleDecoder : IDecoder
|
||||||
{
|
{
|
||||||
private readonly OpusMSDecoder _decoder;
|
private readonly IOpusMultiStreamDecoder _decoder;
|
||||||
|
|
||||||
public int SampleRate => _decoder.SampleRate;
|
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)
|
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
|
||||||
{
|
{
|
||||||
ChannelsCount = channelsCount;
|
_decoder = OpusCodecFactory.CreateMultiStreamDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
||||||
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
|
public int Decode(ReadOnlySpan<byte> inData, Span<short> outPcm, int frameSize)
|
||||||
{
|
{
|
||||||
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
|
return _decoder.DecodeMultistream(inData, outPcm, frameSize, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetState()
|
public void ResetState()
|
||||||
{
|
{
|
||||||
_decoder.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;
|
private readonly IDecoder _decoder;
|
||||||
|
@ -221,7 +253,8 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
{
|
{
|
||||||
timeTaken = 0;
|
timeTaken = 0;
|
||||||
|
|
||||||
Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples);
|
Span<short> outPcmSpace = MemoryMarshal.Cast<byte, short>(output);
|
||||||
|
Result result = DecodeInterleaved(_decoder, reset, input, outPcmSpace, output.Length, out outConsumed, out outSamples);
|
||||||
|
|
||||||
if (withPerf)
|
if (withPerf)
|
||||||
{
|
{
|
||||||
|
@ -229,14 +262,12 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
timeTaken = 0;
|
timeTaken = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]);
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, byte[] packet)
|
private static Result GetPacketNumSamples(IDecoder decoder, out int numSamples, ReadOnlySpan<byte> packet)
|
||||||
{
|
{
|
||||||
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
|
int result = OpusPacketInfo.GetNumSamples(packet, decoder.SampleRate);
|
||||||
|
|
||||||
numSamples = result;
|
numSamples = result;
|
||||||
|
|
||||||
|
@ -256,12 +287,11 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
IDecoder decoder,
|
IDecoder decoder,
|
||||||
bool reset,
|
bool reset,
|
||||||
ReadOnlySpan<byte> input,
|
ReadOnlySpan<byte> input,
|
||||||
out short[] outPcmData,
|
Span<short> outPcmData,
|
||||||
int outputSize,
|
int outputSize,
|
||||||
out int outConsumed,
|
out int outConsumed,
|
||||||
out int outSamples)
|
out int outSamples)
|
||||||
{
|
{
|
||||||
outPcmData = null;
|
|
||||||
outConsumed = 0;
|
outConsumed = 0;
|
||||||
outSamples = 0;
|
outSamples = 0;
|
||||||
|
|
||||||
|
@ -281,7 +311,7 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
return CodecResult.InvalidLength;
|
return CodecResult.InvalidLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray();
|
ReadOnlySpan<byte> opusData = input.Slice(headerSize, (int)header.Length);
|
||||||
|
|
||||||
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
|
Result result = GetPacketNumSamples(decoder, out int numSamples, opusData);
|
||||||
|
|
||||||
|
@ -292,8 +322,6 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
return CodecResult.InvalidLength;
|
return CodecResult.InvalidLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
outPcmData = new short[numSamples * decoder.ChannelsCount];
|
|
||||||
|
|
||||||
if (reset)
|
if (reset)
|
||||||
{
|
{
|
||||||
decoder.ResetState();
|
decoder.ResetState();
|
||||||
|
@ -301,13 +329,22 @@ namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
|
outSamples = decoder.Decode(opusData, outPcmData, numSamples);
|
||||||
outConsumed = (int)totalSize;
|
outConsumed = (int)totalSize;
|
||||||
}
|
}
|
||||||
catch (OpusException)
|
catch (OpusException e)
|
||||||
{
|
{
|
||||||
// TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases...
|
switch (e.OpusErrorCode)
|
||||||
return CodecResult.InvalidLength;
|
{
|
||||||
|
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;
|
_workBufferHandle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_decoder?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue