Add Screenshot Feature (#2354)
* Add internal screenshot capabilities * update version notice
This commit is contained in:
parent
a79b39b913
commit
28618c58d7
12 changed files with 220 additions and 56 deletions
|
@ -3,5 +3,6 @@
|
||||||
public struct KeyboardHotkeys
|
public struct KeyboardHotkeys
|
||||||
{
|
{
|
||||||
public Key ToggleVsync { get; set; }
|
public Key ToggleVsync { get; set; }
|
||||||
|
public Key Screenshot { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,6 +6,8 @@ namespace Ryujinx.Graphics.GAL
|
||||||
{
|
{
|
||||||
public interface IRenderer : IDisposable
|
public interface IRenderer : IDisposable
|
||||||
{
|
{
|
||||||
|
event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
||||||
|
|
||||||
IPipeline Pipeline { get; }
|
IPipeline Pipeline { get; }
|
||||||
|
|
||||||
IWindow Window { get; }
|
IWindow Window { get; }
|
||||||
|
@ -44,5 +46,7 @@ namespace Ryujinx.Graphics.GAL
|
||||||
void WaitSync(ulong id);
|
void WaitSync(ulong id);
|
||||||
|
|
||||||
void Initialize(GraphicsDebugLevel logLevel);
|
void Initialize(GraphicsDebugLevel logLevel);
|
||||||
|
|
||||||
|
void Screenshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs
Normal file
22
Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public struct ScreenCaptureImageInfo
|
||||||
|
{
|
||||||
|
public ScreenCaptureImageInfo(int width, int height, bool isBgra, byte[] data, bool flipX, bool flipY)
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
IsBgra = isBgra;
|
||||||
|
Data = data;
|
||||||
|
FlipX = flipX;
|
||||||
|
FlipY = flipY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Width { get; }
|
||||||
|
public int Height { get; }
|
||||||
|
public byte[] Data { get; }
|
||||||
|
public bool IsBgra { get; }
|
||||||
|
public bool FlipX { get; }
|
||||||
|
public bool FlipY { get; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
private Sync _sync;
|
private Sync _sync;
|
||||||
|
|
||||||
|
public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
|
||||||
|
|
||||||
internal ResourcePool ResourcePool { get; }
|
internal ResourcePool ResourcePool { get; }
|
||||||
|
|
||||||
internal int BufferCount { get; private set; }
|
internal int BufferCount { get; private set; }
|
||||||
|
@ -196,5 +198,15 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
{
|
{
|
||||||
_sync.Wait(id);
|
_sync.Wait(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Screenshot()
|
||||||
|
{
|
||||||
|
_window.ScreenCaptureRequested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnScreenCaptured(ScreenCaptureImageInfo bitmap)
|
||||||
|
{
|
||||||
|
ScreenCaptured?.Invoke(this, bitmap);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
|
|
||||||
internal BackgroundContextWorker BackgroundContext { get; private set; }
|
internal BackgroundContextWorker BackgroundContext { get; private set; }
|
||||||
|
|
||||||
|
internal bool ScreenCaptureRequested { get; set; }
|
||||||
|
|
||||||
public Window(Renderer renderer)
|
public Window(Renderer renderer)
|
||||||
{
|
{
|
||||||
_renderer = renderer;
|
_renderer = renderer;
|
||||||
|
@ -106,6 +108,13 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
|
int dstY0 = crop.FlipY ? dstPaddingY : _height - dstPaddingY;
|
||||||
int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
|
int dstY1 = crop.FlipY ? _height - dstPaddingY : dstPaddingY;
|
||||||
|
|
||||||
|
if (ScreenCaptureRequested)
|
||||||
|
{
|
||||||
|
CaptureFrame(srcX0, srcY0, srcX1, srcY1, view.Format.IsBgra8(), crop.FlipX, crop.FlipY);
|
||||||
|
|
||||||
|
ScreenCaptureRequested = false;
|
||||||
|
}
|
||||||
|
|
||||||
GL.BlitFramebuffer(
|
GL.BlitFramebuffer(
|
||||||
srcX0,
|
srcX0,
|
||||||
srcY0,
|
srcY0,
|
||||||
|
@ -159,6 +168,16 @@ namespace Ryujinx.Graphics.OpenGL
|
||||||
BackgroundContext = new BackgroundContextWorker(baseContext);
|
BackgroundContext = new BackgroundContextWorker(baseContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CaptureFrame(int x, int y, int width, int height, bool isBgra, bool flipX, bool flipY)
|
||||||
|
{
|
||||||
|
long size = Math.Abs(4 * width * height);
|
||||||
|
byte[] bitmap = new byte[size];
|
||||||
|
|
||||||
|
GL.ReadPixels(x, y, width, height, isBgra ? PixelFormat.Bgra : PixelFormat.Rgba, PixelType.UnsignedByte, bitmap);
|
||||||
|
|
||||||
|
_renderer.OnScreenCaptured(new ScreenCaptureImageInfo(width, height, isBgra, bitmap, flipX, flipY));
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
BackgroundContext.Dispose();
|
BackgroundContext.Dispose();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": 27,
|
"version": 28,
|
||||||
"enable_file_log": true,
|
"enable_file_log": true,
|
||||||
"res_scale": 1,
|
"res_scale": 1,
|
||||||
"res_scale_custom": 1,
|
"res_scale_custom": 1,
|
||||||
|
@ -57,7 +57,8 @@
|
||||||
"enable_keyboard": false,
|
"enable_keyboard": false,
|
||||||
"enable_mouse": false,
|
"enable_mouse": false,
|
||||||
"hotkeys": {
|
"hotkeys": {
|
||||||
"toggle_vsync": "Tab"
|
"toggle_vsync": "Tab",
|
||||||
|
"screenshot": "F8"
|
||||||
},
|
},
|
||||||
"keyboard_config": [],
|
"keyboard_config": [],
|
||||||
"controller_config": [],
|
"controller_config": [],
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The current version of the file format
|
/// The current version of the file format
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const int CurrentVersion = 27;
|
public const int CurrentVersion = 28;
|
||||||
|
|
||||||
public int Version { get; set; }
|
public int Version { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -542,7 +542,8 @@ namespace Ryujinx.Configuration
|
||||||
Hid.EnableMouse.Value = false;
|
Hid.EnableMouse.Value = false;
|
||||||
Hid.Hotkeys.Value = new KeyboardHotkeys
|
Hid.Hotkeys.Value = new KeyboardHotkeys
|
||||||
{
|
{
|
||||||
ToggleVsync = Key.Tab
|
ToggleVsync = Key.Tab,
|
||||||
|
Screenshot = Key.F8
|
||||||
};
|
};
|
||||||
Hid.InputConfig.Value = new List<InputConfig>
|
Hid.InputConfig.Value = new List<InputConfig>
|
||||||
{
|
{
|
||||||
|
@ -845,6 +846,19 @@ namespace Ryujinx.Configuration
|
||||||
configurationFileUpdated = true;
|
configurationFileUpdated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (configurationFileFormat.Version < 28)
|
||||||
|
{
|
||||||
|
Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 28.");
|
||||||
|
|
||||||
|
configurationFileFormat.Hotkeys = new KeyboardHotkeys
|
||||||
|
{
|
||||||
|
ToggleVsync = Key.Tab,
|
||||||
|
Screenshot = Key.F8
|
||||||
|
};
|
||||||
|
|
||||||
|
configurationFileUpdated = true;
|
||||||
|
}
|
||||||
|
|
||||||
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog;
|
||||||
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
Graphics.ResScale.Value = configurationFileFormat.ResScale;
|
||||||
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom;
|
||||||
|
|
|
@ -1,10 +1,21 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using ARMeilleure.Translation.PTC;
|
using ARMeilleure.Translation.PTC;
|
||||||
|
|
||||||
using Gtk;
|
using Gtk;
|
||||||
|
|
||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.FsSystem.NcaUtils;
|
using LibHac.FsSystem.NcaUtils;
|
||||||
using LibHac.Ns;
|
using LibHac.Ns;
|
||||||
|
|
||||||
using Ryujinx.Audio.Backends.Dummy;
|
using Ryujinx.Audio.Backends.Dummy;
|
||||||
using Ryujinx.Audio.Backends.OpenAL;
|
using Ryujinx.Audio.Backends.OpenAL;
|
||||||
using Ryujinx.Audio.Backends.SDL2;
|
using Ryujinx.Audio.Backends.SDL2;
|
||||||
|
@ -31,13 +42,6 @@ using Ryujinx.Ui.Applet;
|
||||||
using Ryujinx.Ui.Helper;
|
using Ryujinx.Ui.Helper;
|
||||||
using Ryujinx.Ui.Widgets;
|
using Ryujinx.Ui.Widgets;
|
||||||
using Ryujinx.Ui.Windows;
|
using Ryujinx.Ui.Windows;
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
using GUI = Gtk.Builder.ObjectAttribute;
|
using GUI = Gtk.Builder.ObjectAttribute;
|
||||||
|
|
||||||
|
@ -96,6 +100,7 @@ namespace Ryujinx.Ui
|
||||||
[GUI] MenuItem _stopEmulation;
|
[GUI] MenuItem _stopEmulation;
|
||||||
[GUI] MenuItem _simulateWakeUpMessage;
|
[GUI] MenuItem _simulateWakeUpMessage;
|
||||||
[GUI] MenuItem _scanAmiibo;
|
[GUI] MenuItem _scanAmiibo;
|
||||||
|
[GUI] MenuItem _takeScreenshot;
|
||||||
[GUI] MenuItem _fullScreen;
|
[GUI] MenuItem _fullScreen;
|
||||||
[GUI] CheckMenuItem _startFullScreen;
|
[GUI] CheckMenuItem _startFullScreen;
|
||||||
[GUI] CheckMenuItem _favToggle;
|
[GUI] CheckMenuItem _favToggle;
|
||||||
|
@ -1377,7 +1382,8 @@ namespace Ryujinx.Ui
|
||||||
|
|
||||||
private void ActionMenu_StateChanged(object o, StateChangedArgs args)
|
private void ActionMenu_StateChanged(object o, StateChangedArgs args)
|
||||||
{
|
{
|
||||||
_scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _);
|
_scanAmiibo.Sensitive = _emulationContext != null && _emulationContext.System.SearchingForAmiibo(out int _);
|
||||||
|
_takeScreenshot.Sensitive = _emulationContext != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Scan_Amiibo(object sender, EventArgs args)
|
private void Scan_Amiibo(object sender, EventArgs args)
|
||||||
|
@ -1402,6 +1408,14 @@ namespace Ryujinx.Ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Take_Screenshot(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
if (_emulationContext != null && RendererWidget != null)
|
||||||
|
{
|
||||||
|
RendererWidget.ScreenshotRequested = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args)
|
private void AmiiboWindow_DeleteEvent(object sender, DeleteEventArgs args)
|
||||||
{
|
{
|
||||||
if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok)
|
if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok)
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.22.1 -->
|
<!-- Generated with glade 3.38.2 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.20"/>
|
<requires lib="gtk+" version="3.20"/>
|
||||||
<object class="GtkApplicationWindow" id="_mainWin">
|
<object class="GtkApplicationWindow" id="_mainWin">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="title" translatable="yes">Ryujinx</property>
|
<property name="title" translatable="yes">Ryujinx</property>
|
||||||
<property name="window_position">center</property>
|
<property name="window_position">center</property>
|
||||||
<child type="titlebar">
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox" id="_box">
|
<object class="GtkBox" id="_box">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -332,6 +329,15 @@
|
||||||
<signal name="activate" handler="Scan_Amiibo" swapped="no"/>
|
<signal name="activate" handler="Scan_Amiibo" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_takeScreenshot">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="tooltip_text" translatable="yes">Take a screenshot</property>
|
||||||
|
<property name="label" translatable="yes">Take Screenshot</property>
|
||||||
|
<signal name="activate" handler="Take_Screenshot" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -450,7 +456,7 @@
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="reorderable">True</property>
|
<property name="reorderable">True</property>
|
||||||
<property name="hover_selection">True</property>
|
<property name="hover_selection">True</property>
|
||||||
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
|
<signal name="row_activated" handler="Row_Activated" swapped="no"/>
|
||||||
<child internal-child="selection">
|
<child internal-child="selection">
|
||||||
<object class="GtkTreeSelection" id="_gameTableSelection"/>
|
<object class="GtkTreeSelection" id="_gameTableSelection"/>
|
||||||
</child>
|
</child>
|
||||||
|
@ -484,7 +490,7 @@
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_left">5</property>
|
<property name="margin_left">5</property>
|
||||||
<signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/>
|
<signal name="button_release_event" handler="RefreshList_Pressed" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="name">RefreshList</property>
|
<property name="name">RefreshList</property>
|
||||||
|
@ -547,8 +553,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_left">0</property>
|
<signal name="button_release_event" handler="VSyncStatus_Clicked" swapped="no"/>
|
||||||
<signal name="button-release-event" handler="VSyncStatus_Clicked" swapped="no"/>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_vSyncStatus">
|
<object class="GtkLabel" id="_vSyncStatus">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -581,8 +586,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_left">0</property>
|
<signal name="button_release_event" handler="DockedMode_Clicked" swapped="no"/>
|
||||||
<signal name="button-release-event" handler="DockedMode_Clicked" swapped="no"/>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_dockedMode">
|
<object class="GtkLabel" id="_dockedMode">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -614,8 +618,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_left">0</property>
|
<signal name="button_release_event" handler="AspectRatio_Clicked" swapped="no"/>
|
||||||
<signal name="button-release-event" handler="AspectRatio_Clicked" swapped="no"/>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_aspectRatio">
|
<object class="GtkLabel" id="_aspectRatio">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -713,35 +716,6 @@
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">1</property>
|
<property name="position">1</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="_loadingStatusLabel">
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="margin_left">5</property>
|
|
||||||
<property name="margin_right">5</property>
|
|
||||||
<property name="label" translatable="yes">0/0 </property>
|
|
||||||
<property name="visible">False</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">11</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
|
||||||
<object class="GtkProgressBar" id="_loadingStatusBar">
|
|
||||||
<property name="width_request">200</property>
|
|
||||||
<property name="can_focus">False</property>
|
|
||||||
<property name="margin_left">5</property>
|
|
||||||
<property name="margin_right">5</property>
|
|
||||||
<property name="margin_bottom">6</property>
|
|
||||||
<property name="visible">False</property>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">12</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
|
@ -783,6 +757,33 @@
|
||||||
<property name="position">4</property>
|
<property name="position">4</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="_loadingStatusLabel">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">5</property>
|
||||||
|
<property name="margin_right">5</property>
|
||||||
|
<property name="label" translatable="yes">0/0 </property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">11</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkProgressBar" id="_loadingStatusBar">
|
||||||
|
<property name="width_request">200</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_left">5</property>
|
||||||
|
<property name="margin_right">5</property>
|
||||||
|
<property name="margin_bottom">6</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">12</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Gdk;
|
||||||
using Gtk;
|
using Gtk;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Configuration;
|
using Ryujinx.Configuration;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
|
@ -11,13 +12,19 @@ using Ryujinx.Input;
|
||||||
using Ryujinx.Input.GTK3;
|
using Ryujinx.Input.GTK3;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using Ryujinx.Ui.Widgets;
|
using Ryujinx.Ui.Widgets;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.Formats.Png;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Processing;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Ui
|
namespace Ryujinx.Ui
|
||||||
{
|
{
|
||||||
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
using Key = Input.Key;
|
using Key = Input.Key;
|
||||||
using Switch = HLE.Switch;
|
using Switch = HLE.Switch;
|
||||||
|
|
||||||
|
@ -33,6 +40,8 @@ namespace Ryujinx.Ui
|
||||||
public Switch Device { get; private set; }
|
public Switch Device { get; private set; }
|
||||||
public IRenderer Renderer { get; private set; }
|
public IRenderer Renderer { get; private set; }
|
||||||
|
|
||||||
|
public bool ScreenshotRequested { get; set; }
|
||||||
|
|
||||||
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||||
|
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
|
@ -290,10 +299,56 @@ namespace Ryujinx.Ui
|
||||||
Renderer = Device.Gpu.Renderer;
|
Renderer = Device.Gpu.Renderer;
|
||||||
Renderer?.Window.SetSize(_windowWidth, _windowHeight);
|
Renderer?.Window.SetSize(_windowWidth, _windowHeight);
|
||||||
|
|
||||||
|
if (Renderer != null)
|
||||||
|
{
|
||||||
|
Renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
}
|
||||||
|
|
||||||
NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
NpadManager.Initialize(device, ConfigurationState.Instance.Hid.InputConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse);
|
||||||
TouchScreenManager.Initialize(device);
|
TouchScreenManager.Initialize(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void Renderer_ScreenCaptured(object sender, ScreenCaptureImageInfo e)
|
||||||
|
{
|
||||||
|
if (e.Data.Length > 0)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
var currentTime = DateTime.Now;
|
||||||
|
string filename = $"ryujinx_capture_{currentTime.Year}-{currentTime.Month:D2}-{currentTime.Day:D2}_{currentTime.Hour:D2}-{currentTime.Minute:D2}-{currentTime.Second:D2}.png";
|
||||||
|
string directory = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyPictures), "Ryujinx");
|
||||||
|
string path = System.IO.Path.Combine(directory, filename);
|
||||||
|
|
||||||
|
Directory.CreateDirectory(directory);
|
||||||
|
|
||||||
|
Image image = e.IsBgra ? Image.LoadPixelData<Bgra32>(e.Data, e.Width, e.Height)
|
||||||
|
: Image.LoadPixelData<Rgba32>(e.Data, e.Width, e.Height);
|
||||||
|
|
||||||
|
if (e.FlipX)
|
||||||
|
{
|
||||||
|
image.Mutate(x => x.Flip(FlipMode.Horizontal));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.FlipY)
|
||||||
|
{
|
||||||
|
image.Mutate(x => x.Flip(FlipMode.Vertical));
|
||||||
|
}
|
||||||
|
|
||||||
|
image.SaveAsPng(path, new PngEncoder()
|
||||||
|
{
|
||||||
|
ColorType = PngColorType.Rgb
|
||||||
|
});
|
||||||
|
|
||||||
|
image.Dispose();
|
||||||
|
|
||||||
|
Logger.Notice.Print(LogClass.Application, $"Screenshot saved to {path}", "Screenshot");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Render()
|
public void Render()
|
||||||
{
|
{
|
||||||
Gtk.Window parent = Toplevel as Gtk.Window;
|
Gtk.Window parent = Toplevel as Gtk.Window;
|
||||||
|
@ -490,6 +545,14 @@ namespace Ryujinx.Ui
|
||||||
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((currentHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot) &&
|
||||||
|
!_prevHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot)) || ScreenshotRequested)
|
||||||
|
{
|
||||||
|
ScreenshotRequested = false;
|
||||||
|
|
||||||
|
Renderer.Screenshot();
|
||||||
|
}
|
||||||
|
|
||||||
_prevHotkeyState = currentHotkeyState;
|
_prevHotkeyState = currentHotkeyState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,7 +579,8 @@ namespace Ryujinx.Ui
|
||||||
private enum KeyboardHotkeyState
|
private enum KeyboardHotkeyState
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
ToggleVSync
|
ToggleVSync,
|
||||||
|
Screenshot
|
||||||
}
|
}
|
||||||
|
|
||||||
private KeyboardHotkeyState GetHotkeyState()
|
private KeyboardHotkeyState GetHotkeyState()
|
||||||
|
@ -528,6 +592,11 @@ namespace Ryujinx.Ui
|
||||||
state |= KeyboardHotkeyState.ToggleVSync;
|
state |= KeyboardHotkeyState.ToggleVSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot))
|
||||||
|
{
|
||||||
|
state |= KeyboardHotkeyState.Screenshot;
|
||||||
|
}
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1455,7 +1455,8 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Hotkey Controls",
|
"title": "Hotkey Controls",
|
||||||
"required": [
|
"required": [
|
||||||
"toggle_vsync"
|
"toggle_vsync",
|
||||||
|
"screenshot"
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"toggle_vsync": {
|
"toggle_vsync": {
|
||||||
|
@ -1463,6 +1464,12 @@
|
||||||
"$ref": "#/definitions/key",
|
"$ref": "#/definitions/key",
|
||||||
"title": "Toggle VSync",
|
"title": "Toggle VSync",
|
||||||
"default": "Tab"
|
"default": "Tab"
|
||||||
|
},
|
||||||
|
"screenshot": {
|
||||||
|
"$id": "#/properties/hotkeys/properties/screenshot",
|
||||||
|
"$ref": "#/definitions/key",
|
||||||
|
"title": "Screenshot",
|
||||||
|
"default": "F8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Reference in a new issue