diff --git a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
index 8e9f168d8f..47772ea848 100644
--- a/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
+++ b/Ryujinx.Common/Configuration/Hid/KeyboardHotkeys.cs
@@ -3,5 +3,6 @@
     public struct KeyboardHotkeys
     {
         public Key ToggleVsync { get; set; }
+        public Key Screenshot { get; set; }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index d03cb4c01b..18f10915b1 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -6,6 +6,8 @@ namespace Ryujinx.Graphics.GAL
 {
     public interface IRenderer : IDisposable
     {
+        event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
+
         IPipeline Pipeline { get; }
 
         IWindow Window { get; }
@@ -44,5 +46,7 @@ namespace Ryujinx.Graphics.GAL
         void WaitSync(ulong id);
 
         void Initialize(GraphicsDebugLevel logLevel);
+
+        void Screenshot();
     }
 }
diff --git a/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs b/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs
new file mode 100644
index 0000000000..227d64b69b
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/ScreenCaptureImageInfo.cs
@@ -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; }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index a2be43730b..001cac8d62 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -28,6 +28,8 @@ namespace Ryujinx.Graphics.OpenGL
 
         private Sync _sync;
 
+        public event EventHandler<ScreenCaptureImageInfo> ScreenCaptured;
+
         internal ResourcePool ResourcePool { get; }
 
         internal int BufferCount { get; private set; }
@@ -196,5 +198,15 @@ namespace Ryujinx.Graphics.OpenGL
         {
             _sync.Wait(id);
         }
+
+        public void Screenshot()
+        {
+            _window.ScreenCaptureRequested = true;
+        }
+
+        public void OnScreenCaptured(ScreenCaptureImageInfo bitmap)
+        {
+            ScreenCaptured?.Invoke(this, bitmap);
+        }
     }
 }
diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
index b7525ae51c..35b04d6d9d 100644
--- a/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -16,6 +16,8 @@ namespace Ryujinx.Graphics.OpenGL
 
         internal BackgroundContextWorker BackgroundContext { get; private set; }
 
+        internal bool ScreenCaptureRequested { get; set; }
+
         public Window(Renderer renderer)
         {
             _renderer = renderer;
@@ -106,6 +108,13 @@ namespace Ryujinx.Graphics.OpenGL
             int dstY0 = crop.FlipY ? dstPaddingY : _height - 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(
                 srcX0,
                 srcY0,
@@ -159,6 +168,16 @@ namespace Ryujinx.Graphics.OpenGL
             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()
         {
             BackgroundContext.Dispose();
diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json
index 033186fe48..3d4ea23b13 100644
--- a/Ryujinx/Config.json
+++ b/Ryujinx/Config.json
@@ -1,5 +1,5 @@
 {
-  "version": 27,
+  "version": 28,
   "enable_file_log": true,
   "res_scale": 1,
   "res_scale_custom": 1,
@@ -57,7 +57,8 @@
   "enable_keyboard": false,
   "enable_mouse": false,
   "hotkeys": {
-    "toggle_vsync": "Tab"
+    "toggle_vsync": "Tab",
+    "screenshot": "F8"
   },
   "keyboard_config": [],
   "controller_config": [],
diff --git a/Ryujinx/Configuration/ConfigurationFileFormat.cs b/Ryujinx/Configuration/ConfigurationFileFormat.cs
index 04a5181557..4634dafebb 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 = 27;
+        public const int CurrentVersion = 28;
 
         public int Version { get; set; }
 
diff --git a/Ryujinx/Configuration/ConfigurationState.cs b/Ryujinx/Configuration/ConfigurationState.cs
index 1769dfa9e5..c98fbcca25 100644
--- a/Ryujinx/Configuration/ConfigurationState.cs
+++ b/Ryujinx/Configuration/ConfigurationState.cs
@@ -542,7 +542,8 @@ namespace Ryujinx.Configuration
             Hid.EnableMouse.Value                  = false;
             Hid.Hotkeys.Value = new KeyboardHotkeys
             {
-                ToggleVsync = Key.Tab
+                ToggleVsync = Key.Tab,
+                Screenshot = Key.F8
             };
             Hid.InputConfig.Value = new List<InputConfig>
             {
@@ -845,6 +846,19 @@ namespace Ryujinx.Configuration
                 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;
             Graphics.ResScale.Value                = configurationFileFormat.ResScale;
             Graphics.ResScaleCustom.Value          = configurationFileFormat.ResScaleCustom;
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 113ac6398f..eb1e10b3a1 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -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.PTC;
+
 using Gtk;
+
 using LibHac.Common;
 using LibHac.FsSystem;
 using LibHac.FsSystem.NcaUtils;
 using LibHac.Ns;
+
 using Ryujinx.Audio.Backends.Dummy;
 using Ryujinx.Audio.Backends.OpenAL;
 using Ryujinx.Audio.Backends.SDL2;
@@ -31,13 +42,6 @@ using Ryujinx.Ui.Applet;
 using Ryujinx.Ui.Helper;
 using Ryujinx.Ui.Widgets;
 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;
 
@@ -96,6 +100,7 @@ namespace Ryujinx.Ui
         [GUI] MenuItem        _stopEmulation;
         [GUI] MenuItem        _simulateWakeUpMessage;
         [GUI] MenuItem        _scanAmiibo;
+        [GUI] MenuItem        _takeScreenshot;
         [GUI] MenuItem        _fullScreen;
         [GUI] CheckMenuItem   _startFullScreen;
         [GUI] CheckMenuItem   _favToggle;
@@ -1377,7 +1382,8 @@ namespace Ryujinx.Ui
 
         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)
@@ -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)
         {
             if (((AmiiboWindow)sender).AmiiboId != "" && ((AmiiboWindow)sender).Response == ResponseType.Ok)
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index e974d878cd..7bf38f4730 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -1,14 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!-- Generated with glade 3.22.1 -->
+<!-- Generated with glade 3.38.2 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
   <object class="GtkApplicationWindow" id="_mainWin">
     <property name="can_focus">False</property>
     <property name="title" translatable="yes">Ryujinx</property>
     <property name="window_position">center</property>
-    <child type="titlebar">
-      <placeholder/>
-    </child>
     <child>
       <object class="GtkBox" id="_box">
         <property name="visible">True</property>
@@ -332,6 +329,15 @@
                         <signal name="activate" handler="Scan_Amiibo" swapped="no"/>
                       </object>
                     </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>
                 </child>
               </object>
@@ -450,7 +456,7 @@
                         <property name="can_focus">True</property>
                         <property name="reorderable">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">
                           <object class="GtkTreeSelection" id="_gameTableSelection"/>
                         </child>
@@ -484,7 +490,7 @@
                         <property name="visible">True</property>
                         <property name="can_focus">False</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>
                           <object class="GtkImage">
                             <property name="name">RefreshList</property>
@@ -547,8 +553,7 @@
                       <object class="GtkEventBox">
                         <property name="visible">True</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>
                           <object class="GtkLabel" id="_vSyncStatus">
                             <property name="visible">True</property>
@@ -581,8 +586,7 @@
                       <object class="GtkEventBox">
                         <property name="visible">True</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>
                           <object class="GtkLabel" id="_dockedMode">
                             <property name="visible">True</property>
@@ -614,8 +618,7 @@
                       <object class="GtkEventBox">
                         <property name="visible">True</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>
                           <object class="GtkLabel" id="_aspectRatio">
                             <property name="visible">True</property>
@@ -713,35 +716,6 @@
                     <property name="fill">True</property>
                     <property name="position">1</property>
                   </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>
                   <object class="GtkBox">
@@ -783,6 +757,33 @@
                     <property name="position">4</property>
                   </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>
+                  </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>
               <packing>
                 <property name="expand">False</property>
diff --git a/Ryujinx/Ui/RendererWidgetBase.cs b/Ryujinx/Ui/RendererWidgetBase.cs
index 699f06c518..dee5cbb6c9 100644
--- a/Ryujinx/Ui/RendererWidgetBase.cs
+++ b/Ryujinx/Ui/RendererWidgetBase.cs
@@ -4,6 +4,7 @@ using Gdk;
 using Gtk;
 using Ryujinx.Common;
 using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
 using Ryujinx.Configuration;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.HLE.HOS.Services.Hid;
@@ -11,13 +12,19 @@ using Ryujinx.Input;
 using Ryujinx.Input.GTK3;
 using Ryujinx.Input.HLE;
 using Ryujinx.Ui.Widgets;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.Formats.Png;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
 using System;
 using System.Diagnostics;
-using System.Linq;
+using System.IO;
 using System.Threading;
+using System.Threading.Tasks;
 
 namespace Ryujinx.Ui
 {
+    using Image = SixLabors.ImageSharp.Image;
     using Key = Input.Key;
     using Switch = HLE.Switch;
 
@@ -33,6 +40,8 @@ namespace Ryujinx.Ui
         public Switch Device { get; private set; }
         public IRenderer Renderer { get; private set; }
 
+        public bool ScreenshotRequested { get; set; }
+
         public static event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
 
         private bool _isActive;
@@ -290,10 +299,56 @@ namespace Ryujinx.Ui
             Renderer = Device.Gpu.Renderer;
             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);
             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()
         {
             Gtk.Window parent = Toplevel as Gtk.Window;
@@ -490,6 +545,14 @@ namespace Ryujinx.Ui
                     Device.EnableDeviceVsync = !Device.EnableDeviceVsync;
                 }
 
+                if ((currentHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot) &&
+                    !_prevHotkeyState.HasFlag(KeyboardHotkeyState.Screenshot)) || ScreenshotRequested)
+                {
+                    ScreenshotRequested = false;
+
+                    Renderer.Screenshot();
+                }
+
                 _prevHotkeyState = currentHotkeyState;
             }
 
@@ -516,7 +579,8 @@ namespace Ryujinx.Ui
         private enum KeyboardHotkeyState
         {
             None,
-            ToggleVSync
+            ToggleVSync,
+            Screenshot
         }
 
         private KeyboardHotkeyState GetHotkeyState()
@@ -527,6 +591,11 @@ namespace Ryujinx.Ui
             {
                 state |= KeyboardHotkeyState.ToggleVSync;
             }
+            
+            if (_keyboardInterface.IsPressed((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot))
+            {
+                state |= KeyboardHotkeyState.Screenshot;
+            }
 
             return state;
         }
diff --git a/Ryujinx/_schema.json b/Ryujinx/_schema.json
index f819b9d6c9..47da28b1ca 100644
--- a/Ryujinx/_schema.json
+++ b/Ryujinx/_schema.json
@@ -1455,7 +1455,8 @@
       "type": "object",
       "title": "Hotkey Controls",
       "required": [
-        "toggle_vsync"
+        "toggle_vsync",
+        "screenshot"        
       ],
       "properties": {
         "toggle_vsync": {
@@ -1463,6 +1464,12 @@
           "$ref": "#/definitions/key",
           "title": "Toggle VSync",
           "default": "Tab"
+        },
+        "screenshot": {
+          "$id": "#/properties/hotkeys/properties/screenshot",
+          "$ref": "#/definitions/key",
+          "title": "Screenshot",
+          "default": "F8"
         }
       }
     },