aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ARMeilleure/State/ExecutionContext.cs10
-rw-r--r--Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs9
-rw-r--r--Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs9
-rw-r--r--Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs8
-rw-r--r--Ryujinx.Audio/AudioManager.cs14
-rw-r--r--Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs5
-rw-r--r--Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs8
-rw-r--r--Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs1
-rw-r--r--Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs5
-rw-r--r--Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs8
-rw-r--r--Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs1
-rw-r--r--Ryujinx.HLE/HOS/Horizon.cs35
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs55
-rw-r--r--Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs67
-rw-r--r--Ryujinx/Config.json3
-rw-r--r--Ryujinx/Configuration/ConfigurationFileFormat.cs2
-rw-r--r--Ryujinx/Configuration/ConfigurationState.cs18
-rw-r--r--Ryujinx/Ui/MainWindow.cs32
-rw-r--r--Ryujinx/Ui/MainWindow.glade38
-rw-r--r--Ryujinx/Ui/RendererWidgetBase.cs16
-rw-r--r--Ryujinx/_schema.json21
21 files changed, 311 insertions, 54 deletions
diff --git a/ARMeilleure/State/ExecutionContext.cs b/ARMeilleure/State/ExecutionContext.cs
index 9a221569..a6f74cd0 100644
--- a/ARMeilleure/State/ExecutionContext.cs
+++ b/ARMeilleure/State/ExecutionContext.cs
@@ -145,6 +145,16 @@ namespace ARMeilleure.State
_nativeContext.SetCounter(0);
}
+ public static void SuspendCounter()
+ {
+ _tickCounter.Stop();
+ }
+
+ public static void ResumeCounter()
+ {
+ _tickCounter.Start();
+ }
+
public void Dispose()
{
_nativeContext.Dispose();
diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
index 60c364da..453208a1 100644
--- a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
private readonly ALDevice _device;
private readonly ALContext _context;
private readonly ManualResetEvent _updateRequiredEvent;
+ private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<OpenALHardwareDeviceSession, byte> _sessions;
private bool _stillRunning;
private Thread _updaterThread;
@@ -24,6 +25,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
_device = ALC.OpenDevice("");
_context = ALC.CreateContext(_device, new ALContextAttributes());
_updateRequiredEvent = new ManualResetEvent(false);
+ _pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<OpenALHardwareDeviceSession, byte>();
_stillRunning = true;
@@ -88,6 +90,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
return _updateRequiredEvent;
}
+ public ManualResetEvent GetPauseEvent()
+ {
+ return _pauseEvent;
+ }
+
private void Update()
{
ALC.MakeContextCurrent(_context);
@@ -132,6 +139,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
ALC.DestroyContext(_context);
ALC.CloseDevice(_device);
+
+ _pauseEvent.Dispose();
}
}
diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
index 13062ad1..77545b57 100644
--- a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs
@@ -15,11 +15,13 @@ namespace Ryujinx.Audio.Backends.SDL2
public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
{
private readonly ManualResetEvent _updateRequiredEvent;
+ private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
public SDL2HardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
+ _pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
SDL2Driver.Instance.Initialize();
@@ -44,6 +46,11 @@ namespace Ryujinx.Audio.Backends.SDL2
return _updateRequiredEvent;
}
+ public ManualResetEvent GetPauseEvent()
+ {
+ return _pauseEvent;
+ }
+
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
@@ -136,6 +143,8 @@ namespace Ryujinx.Audio.Backends.SDL2
}
SDL2Driver.Instance.Dispose();
+
+ _pauseEvent.Dispose();
}
}
diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
index 20aa4cbf..cde5b3d4 100644
--- a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly SoundIO _audioContext;
private readonly SoundIODevice _audioDevice;
private readonly ManualResetEvent _updateRequiredEvent;
+ private readonly ManualResetEvent _pauseEvent;
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
private int _disposeState;
@@ -22,6 +23,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
{
_audioContext = new SoundIO();
_updateRequiredEvent = new ManualResetEvent(false);
+ _pauseEvent = new ManualResetEvent(true);
_sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
_audioContext.Connect();
@@ -123,6 +125,11 @@ namespace Ryujinx.Audio.Backends.SoundIo
return _updateRequiredEvent;
}
+ public ManualResetEvent GetPauseEvent()
+ {
+ return _pauseEvent;
+ }
+
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
@@ -218,6 +225,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
_audioContext.Disconnect();
_audioContext.Dispose();
+ _pauseEvent.Dispose();
}
}
diff --git a/Ryujinx.Audio/AudioManager.cs b/Ryujinx.Audio/AudioManager.cs
index ab25150a..84e5b4f7 100644
--- a/Ryujinx.Audio/AudioManager.cs
+++ b/Ryujinx.Audio/AudioManager.cs
@@ -45,6 +45,8 @@ namespace Ryujinx.Audio
/// </summary>
private Thread _workerThread;
+ private bool _isRunning;
+
/// <summary>
/// Create a new <see cref="AudioManager"/>.
/// </summary>
@@ -52,6 +54,7 @@ namespace Ryujinx.Audio
{
_updateRequiredEvents = new ManualResetEvent[2];
_actions = new Action[2];
+ _isRunning = false;
// Termination event.
_updateRequiredEvents[1] = new ManualResetEvent(false);
@@ -72,6 +75,7 @@ namespace Ryujinx.Audio
throw new InvalidOperationException();
}
+ _isRunning = true;
_workerThread.Start();
}
@@ -96,7 +100,7 @@ namespace Ryujinx.Audio
/// </summary>
private void Update()
{
- while (true)
+ while (_isRunning)
{
int index = WaitHandle.WaitAny(_updateRequiredEvents);
@@ -118,6 +122,14 @@ namespace Ryujinx.Audio
}
}
+ /// <summary>
+ /// Stop updating the <see cref="AudioManager"/> without stopping the worker thread.
+ /// </summary>
+ public void StopUpdates()
+ {
+ _isRunning = false;
+ }
+
public void Dispose()
{
Dispose(true);
diff --git a/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs
index c0305f8a..0ae6a620 100644
--- a/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs
@@ -47,6 +47,11 @@ namespace Ryujinx.Audio.Backends.CompatLayer
return _realDriver.GetUpdateRequiredEvent();
}
+ public ManualResetEvent GetPauseEvent()
+ {
+ return _realDriver.GetPauseEvent();
+ }
+
private uint SelectHardwareChannelCount(uint targetChannelCount)
{
if (_realDriver.SupportsChannelCount(targetChannelCount))
diff --git a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs
index f24b359c..d729d3f6 100644
--- a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs
@@ -27,10 +27,12 @@ namespace Ryujinx.Audio.Backends.Dummy
public class DummyHardwareDeviceDriver : IHardwareDeviceDriver
{
private ManualResetEvent _updateRequiredEvent;
+ private ManualResetEvent _pauseEvent;
public DummyHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
+ _pauseEvent = new ManualResetEvent(true);
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
@@ -60,6 +62,11 @@ namespace Ryujinx.Audio.Backends.Dummy
return _updateRequiredEvent;
}
+ public ManualResetEvent GetPauseEvent()
+ {
+ return _pauseEvent;
+ }
+
public void Dispose()
{
Dispose(true);
@@ -70,6 +77,7 @@ namespace Ryujinx.Audio.Backends.Dummy
if (disposing)
{
// NOTE: The _updateRequiredEvent will be disposed somewhere else.
+ _pauseEvent.Dispose();
}
}
diff --git a/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs
index 70738c90..1a53fa9b 100644
--- a/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs
+++ b/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs
@@ -36,6 +36,7 @@ namespace Ryujinx.Audio.Integration
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
ManualResetEvent GetUpdateRequiredEvent();
+ ManualResetEvent GetPauseEvent();
bool SupportsDirection(Direction direction);
bool SupportsSampleRate(uint sampleRate);
diff --git a/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
index ea975056..e15165b9 100644
--- a/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
+++ b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs
@@ -55,6 +55,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
private long _playbackEnds;
private ManualResetEvent _event;
+ private ManualResetEvent _pauseEvent;
+
public AudioProcessor()
{
_event = new ManualResetEvent(false);
@@ -94,6 +96,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
_sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax];
_event.Reset();
_lastTime = PerformanceCounter.ElapsedNanoseconds;
+ _pauseEvent = deviceDriver.GetPauseEvent();
StartThread();
@@ -202,6 +205,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
while (true)
{
+ _pauseEvent?.WaitOne();
+
MailboxMessage message = _mailbox.ReceiveMessage();
if (message == MailboxMessage.Stop)
diff --git a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
index 71d0f318..f471a2e7 100644
--- a/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
+++ b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
@@ -215,6 +215,14 @@ namespace Ryujinx.Audio.Renderer.Server
}
/// <summary>
+ /// Stop sending commands to the <see cref="AudioProcessor"/> without stopping the worker thread.
+ /// </summary>
+ public void StopSendingCommands()
+ {
+ _isRunning = false;
+ }
+
+ /// <summary>
/// Worker main function. This is used to dispatch audio renderer commands to the <see cref="AudioProcessor"/>.
/// </summary>
private void SendCommands()
diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
index 38f5ad36..dd0d7c21 100644
--- a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
+++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
@@ -5,5 +5,6 @@
public Key ToggleVsync { get; set; }
public Key Screenshot { get; set; }
public Key ShowUi { get; set; }
+ public Key Pause { get; set; }
}
}
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs
index 851d7e13..05ebdbb9 100644
--- a/Ryujinx.HLE/HOS/Horizon.cs
+++ b/Ryujinx.HLE/HOS/Horizon.cs
@@ -113,6 +113,8 @@ namespace Ryujinx.HLE.HOS
internal LibHacHorizonManager LibHacHorizonManager { get; private set; }
+ public bool IsPaused { get; private set; }
+
public Horizon(Switch device)
{
KernelContext = new KernelContext(
@@ -385,6 +387,12 @@ namespace Ryujinx.HLE.HOS
{
_isDisposed = true;
+ // "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
+ AudioRendererManager.StopSendingCommands();
+ AudioManager.StopUpdates();
+
+ TogglePauseEmulation(false);
+
KProcess terminationProcess = new KProcess(KernelContext);
KThread terminationThread = new KThread(KernelContext);
@@ -444,5 +452,32 @@ namespace Ryujinx.HLE.HOS
KernelContext.Dispose();
}
}
+
+ public void TogglePauseEmulation(bool pause)
+ {
+ lock (KernelContext.Processes)
+ {
+ foreach (KProcess process in KernelContext.Processes.Values)
+ {
+ if (process.Flags.HasFlag(ProcessCreationFlags.IsApplication))
+ {
+ // Only game process should be paused.
+ process.SetActivity(pause);
+ }
+ }
+
+ if (pause && !IsPaused)
+ {
+ Device.AudioDeviceDriver.GetPauseEvent().Reset();
+ ARMeilleure.State.ExecutionContext.SuspendCounter();
+ }
+ else if (!pause && IsPaused)
+ {
+ Device.AudioDeviceDriver.GetPauseEvent().Set();
+ ARMeilleure.State.ExecutionContext.ResumeCounter();
+ }
+ }
+ IsPaused = pause;
+ }
}
}
diff --git a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
index fbdd812e..a3691808 100644
--- a/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Process/KProcess.cs
@@ -1082,5 +1082,60 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
}
protected override void Destroy() => Context.Dispose();
+
+ public KernelResult SetActivity(bool pause)
+ {
+ KernelContext.CriticalSection.Enter();
+
+ if (State != ProcessState.Exiting && State != ProcessState.Exited)
+ {
+ if (pause)
+ {
+ if (IsPaused)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ thread.Suspend(ThreadSchedState.ProcessPauseFlag);
+ }
+ }
+
+ IsPaused = true;
+ }
+ else
+ {
+ if (!IsPaused)
+ {
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
+
+ lock (_threadingLock)
+ {
+ foreach (KThread thread in _threads)
+ {
+ thread.Resume(ThreadSchedState.ProcessPauseFlag);
+ }
+ }
+
+ IsPaused = false;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.Success;
+ }
+
+ KernelContext.CriticalSection.Leave();
+
+ return KernelResult.InvalidState;
+ }
}
} \ No newline at end of file
diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
index 7224cca1..396a79ba 100644
--- a/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Threading/KThread.cs
@@ -471,6 +471,29 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
KernelContext.CriticalSection.Leave();
}
+ public void Suspend(ThreadSchedState type)
+ {
+ _forcePauseFlags |= type;
+
+ CombineForcePauseFlags();
+ }
+
+ public void Resume(ThreadSchedState type)
+ {
+ ThreadSchedState oldForcePauseFlags = _forcePauseFlags;
+
+ _forcePauseFlags &= ~type;
+
+ if ((oldForcePauseFlags & ~type) == ThreadSchedState.None)
+ {
+ ThreadSchedState oldSchedFlags = SchedFlags;
+
+ SchedFlags &= ThreadSchedState.LowMask;
+
+ AdjustScheduling(oldSchedFlags);
+ }
+ }
+
public KernelResult SetActivity(bool pause)
{
KernelResult result = KernelResult.Success;
@@ -495,9 +518,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// Pause, the force pause flag should be clear (thread is NOT paused).
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) == 0)
{
- _forcePauseFlags |= ThreadSchedState.ThreadPauseFlag;
-
- CombineForcePauseFlags();
+ Suspend(ThreadSchedState.ThreadPauseFlag);
}
else
{
@@ -509,18 +530,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
// Unpause, the force pause flag should be set (thread is paused).
if ((_forcePauseFlags & ThreadSchedState.ThreadPauseFlag) != 0)
{
- ThreadSchedState oldForcePauseFlags = _forcePauseFlags;
-
- _forcePauseFlags &= ~ThreadSchedState.ThreadPauseFlag;
-
- if ((oldForcePauseFlags & ~ThreadSchedState.ThreadPauseFlag) == ThreadSchedState.None)
- {
- ThreadSchedState oldSchedFlags = SchedFlags;
-
- SchedFlags &= ThreadSchedState.LowMask;
-
- AdjustScheduling(oldSchedFlags);
- }
+ Resume(ThreadSchedState.ThreadPauseFlag);
}
else
{
@@ -832,19 +842,22 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
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
+ if (!_forcedUnschedulable)
{
- _schedulerWaitEvent.Reset();
+ // 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;
diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json
index d98a8e0c..6c8a226a 100644
--- a/Ryujinx/Config.json
+++ b/Ryujinx/Config.json
@@ -60,7 +60,8 @@
"hotkeys": {
"toggle_vsync": "Tab",
"screenshot": "F8",
- "show_ui": "F4"
+ "show_ui": "F4",
+ "pause": "F5"
},
"keyboard_config": [],
"controller_config": [],
diff --git a/Ryujinx/Configuration/ConfigurationFileFormat.cs b/Ryujinx/Configuration/ConfigurationFileFormat.cs
index fbfa9c60..dda86ff5 100644
--- a/Ryujinx/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx/Configuration/ConfigurationFileFormat.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
- public const int CurrentVersion = 31;
+ public const int CurrentVersion = 32;
public int Version { get; set; }
diff --git a/Ryujinx/Configuration/ConfigurationState.cs b/Ryujinx/Configuration/ConfigurationState.cs
index 1476f623..9cf3f650 100644
--- a/Ryujinx/Configuration/ConfigurationState.cs
+++ b/Ryujinx/Configuration/ConfigurationState.cs
@@ -554,7 +554,8 @@ namespace Ryujinx.Configuration
{
ToggleVsync = Key.Tab,
Screenshot = Key.F8,
- ShowUi = Key.F4
+ ShowUi = Key.F4,
+ Pause = Key.F5
};
Hid.InputConfig.Value = new List<InputConfig>
{
@@ -913,6 +914,21 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
+ if (configurationFileFormat.Version < 32)
+ {
+ Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 32.");
+
+ configurationFileFormat.Hotkeys = new KeyboardHotkeys
+ {
+ ToggleVsync = configurationFileFormat.Hotkeys.ToggleVsync,
+ Screenshot = configurationFileFormat.Hotkeys.Screenshot,
+ ShowUi = configurationFileFormat.Hotkeys.ShowUi,
+ Pause = Key.F5
+ };
+
+ configurationFileUpdated = true;
+ }
+
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
Graphics.BackendThreading.Value = configurationFileFormat.BackendThreading;
Graphics.ResScale.Value = configurationFileFormat.ResScale;
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 41916b3c..6506f324 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -99,6 +99,8 @@ namespace Ryujinx.Ui
[GUI] MenuItem _loadApplicationFolder;
[GUI] MenuItem _appletMenu;
[GUI] MenuItem _actionMenu;
+ [GUI] MenuItem _pauseEmulation;
+ [GUI] MenuItem _resumeEmulation;
[GUI] MenuItem _stopEmulation;
[GUI] MenuItem _simulateWakeUpMessage;
[GUI] MenuItem _scanAmiibo;
@@ -211,6 +213,7 @@ namespace Ryujinx.Ui
}
_actionMenu.Sensitive = false;
+ _pauseEmulation.Sensitive = false;
if (ConfigurationState.Instance.Ui.GuiColumns.FavColumn) _favToggle.Active = true;
if (ConfigurationState.Instance.Ui.GuiColumns.IconColumn) _iconToggle.Active = true;
@@ -1281,9 +1284,38 @@ namespace Ryujinx.Ui
UpdateGameMetadata(_emulationContext.Application.TitleIdText);
}
+ _pauseEmulation.Visible = true;
+ _pauseEmulation.Sensitive = false;
+ _resumeEmulation.Visible = false;
RendererWidget?.Exit();
}
+ private void PauseEmulation_Pressed(object sender, EventArgs args)
+ {
+ _pauseEmulation.Visible = false;
+ _resumeEmulation.Visible = true;
+ _emulationContext.System.TogglePauseEmulation(true);
+ }
+
+ private void ResumeEmulation_Pressed(object sender, EventArgs args)
+ {
+ _pauseEmulation.Visible = true;
+ _resumeEmulation.Visible = false;
+ _emulationContext.System.TogglePauseEmulation(false);
+ }
+
+ public void ActivatePauseMenu()
+ {
+ _pauseEmulation.Sensitive = true;
+ }
+
+ public void TogglePause()
+ {
+ _pauseEmulation.Visible ^= true;
+ _resumeEmulation.Visible ^= true;
+ _emulationContext.System.TogglePauseEmulation(_resumeEmulation.Visible);
+ }
+
private void Installer_File_Pressed(object o, EventArgs args)
{
FileChooserDialog fileChooser = new FileChooserDialog("Choose the firmware file to open", this, FileChooserAction.Open, "Cancel", ResponseType.Cancel, "Open", ResponseType.Accept);
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index ff95506a..2fa00688 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -294,15 +294,35 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
- <object class="GtkMenuItem" id="_stopEmulation">
- <property name="visible">True</property>
- <property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes">Stop emulation of the current game and return to game selection</property>
- <property name="label" translatable="yes">Stop Emulation</property>
- <property name="use_underline">True</property>
- <signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
- </object>
- </child>
+ <object class="GtkMenuItem" id="_pauseEmulation">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Pause emulation</property>
+ <property name="label" translatable="yes">Pause Emulation</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="PauseEmulation_Pressed" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="_resumeEmulation">
+ <property name="visible">False</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Resume emulation</property>
+ <property name="label" translatable="yes">Resume Emulation</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="ResumeEmulation_Pressed" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="_stopEmulation">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Stop emulation of the current game and return to game selection</property>
+ <property name="label" translatable="yes">Stop Emulation</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="StopEmulation_Pressed" swapped="no"/>
+ </object>
+ </child>
<child>
<object class="GtkSeparatorMenuItem">
<property name="visible">True</property>
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index da10ba47..2ddd44dc 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -389,6 +389,8 @@ namespace Ryujinx.Ui
Device.Gpu.InitializeShaderCache();
Translator.IsReadyForTranslation.Set();
+ (Toplevel as MainWindow)?.ActivatePauseMenu();
+
while (_isActive)
{
if (_isStopped)
@@ -590,6 +592,12 @@ namespace Ryujinx.Ui
(Toplevel as MainWindow).ToggleExtraWidgets(true);
}
+ if (currentHotkeyState.HasFlag(KeyboardHotkeyState.Pause) &&
+ !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Pause))
+ {
+ (Toplevel as MainWindow)?.TogglePause();
+ }
+
_prevHotkeyState = currentHotkeyState;
}
@@ -618,7 +626,8 @@ namespace Ryujinx.Ui
None = 0,
ToggleVSync = 1 << 0,
Screenshot = 1 << 1,
- ShowUi = 1 << 2
+ ShowUi = 1 << 2,
+ Pause = 1 << 3
}
private KeyboardHotkeyState GetHotkeyState()
@@ -640,6 +649,11 @@ namespace Ryujinx.Ui
state |= KeyboardHotkeyState.ShowUi;
}
+ if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause))
+ {
+ state |= KeyboardHotkeyState.Pause;
+ }
+
return state;
}
}
diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json
index 356916a7..3cedfcb0 100644
--- a/Ryujinx/_schema.json
+++ b/Ryujinx/_schema.json
@@ -908,18 +908,6 @@
}
},
"properties": {
- "backend_threading": {
- "$id": "#/properties/backend_threading",
- "type": "string",
- "title": "Backend Threading",
- "description": "Whether backend threading is enabled or not. 'Auto' selects the most appropriate option for the current OS, vendor and backend.",
- "default": "Auto",
- "examples": [
- "Auto",
- "Off",
- "On"
- ]
- },
"res_scale": {
"$id": "#/properties/res_scale",
"type": "integer",
@@ -1468,7 +1456,8 @@
"title": "Hotkey Controls",
"required": [
"toggle_vsync",
- "screenshot"
+ "screenshot",
+ "pause"
],
"properties": {
"toggle_vsync": {
@@ -1482,6 +1471,12 @@
"$ref": "#/definitions/key",
"title": "Screenshot",
"default": "F8"
+ },
+ "pause": {
+ "$id": "#/properties/hotkeys/properties/pause",
+ "$ref": "#/definitions/key",
+ "title": "Toggle Pause",
+ "default": "F5"
}
}
},