From 3aa3c4261a57e0daa595decbe689973bd0be473d Mon Sep 17 00:00:00 2001
From: emmauss <emmausssss@gmail.com>
Date: Wed, 12 Feb 2020 00:56:19 +0000
Subject: [PATCH] Add inbuilt Opengl renderer to window (#922)

* add gl rendering widget

* embed renderer into main window

* add input

* fix mouse input

* fix mouse coords

* refresh game list after closing game, remove profiler method

* rebase, hide game list progress bar while game is running

* Some bug fixes

Changelog:

- Reapply some changes that got lost while rebasing from #904
- Make sure to guarantee exclusivity on the GL context (fixing multiple
possible race conditions on Windows)
- Avoid making GLRenderer disposed multiple time

* add fullscreen,  enable input on focus, disable aplha

* addressed comments

* Disable transparency in the window

* fix fullscreen state, fix focus, addressed comments

* nit

* addressed nit

Co-authored-by: Thog <thog@protonmail.com>
---
 Ryujinx.Audio/Ryujinx.Audio.csproj            |   2 +-
 Ryujinx.Common/Ryujinx.Common.csproj          |   3 +-
 .../Ryujinx.Graphics.OpenGL.csproj            |   2 +-
 Ryujinx.sln                                   |  11 +-
 Ryujinx/Program.cs                            |   7 +
 Ryujinx/Ryujinx.csproj                        |   3 +-
 Ryujinx/Ui/GLRenderer.cs                      | 499 ++++++++++++++++++
 Ryujinx/Ui/GLScreen.cs                        | 375 -------------
 Ryujinx/Ui/MainWindow.cs                      | 147 ++++--
 Ryujinx/Ui/MainWindow.glade                   | 129 +++--
 Ryujinx/Ui/ScopedGlContext.cs                 |  35 ++
 11 files changed, 744 insertions(+), 469 deletions(-)
 create mode 100644 Ryujinx/Ui/GLRenderer.cs
 delete mode 100644 Ryujinx/Ui/GLScreen.cs
 create mode 100644 Ryujinx/Ui/ScopedGlContext.cs

diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj
index b541043c7..80940c770 100644
--- a/Ryujinx.Audio/Ryujinx.Audio.csproj
+++ b/Ryujinx.Audio/Ryujinx.Audio.csproj
@@ -27,7 +27,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
+    <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.12" />
     <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
   </ItemGroup>
 
diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj
index 43a853a40..1b7cf8afb 100644
--- a/Ryujinx.Common/Ryujinx.Common.csproj
+++ b/Ryujinx.Common/Ryujinx.Common.csproj
@@ -28,7 +28,8 @@
 
   <ItemGroup>
     <PackageReference Include="JsonPrettyPrinter" Version="1.0.1.1" />
-    <PackageReference Include="Utf8Json" Version="1.3.7" />
+    <PackageReference Include="Utf8Json" Version="1.3.7" /><PackageReference Include="OpenTK.NetStandard" Version="1.0.5.12" />
+    <PackageReference Include="SharpFontCore" Version="0.1.1" />
   </ItemGroup>
 
 </Project>
diff --git a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
index f2a937773..275400555 100644
--- a/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
+++ b/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -7,7 +7,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
+    <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.12" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/Ryujinx.sln b/Ryujinx.sln
index f023368ba..40a086e5f 100644
--- a/Ryujinx.sln
+++ b/Ryujinx.sln
@@ -32,7 +32,8 @@ EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", "Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj", "{03B955CD-AD84-4B93-AAA7-BF17923BBAA5}"
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}"
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Debugger", "Ryujinx.Debugger\Ryujinx.Debugger.csproj", "{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -170,6 +171,14 @@ Global
 		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
 		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{2E02B7F3-245E-43B1-AE5B-44167A0FDA20}.Release|Any CPU.Build.0 = Release|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Debug|Any CPU.ActiveCfg = Profile Debug|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Debug|Any CPU.Build.0 = Profile Debug|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Release|Any CPU.ActiveCfg = Profile Release|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Profile Release|Any CPU.Build.0 = Profile Release|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{79E4EE34-9C5F-4BE6-8529-A49D32B5B0CC}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs
index 24fbb9b8b..4aaa5e9f2 100644
--- a/Ryujinx/Program.cs
+++ b/Ryujinx/Program.cs
@@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
 using Ryujinx.Configuration;
 using Ryujinx.Debugger.Profiler;
 using Ryujinx.Ui;
+using OpenTK;
 using System;
 using System.IO;
 
@@ -12,6 +13,12 @@ namespace Ryujinx
     {
         static void Main(string[] args)
         {
+            Toolkit.Init(new ToolkitOptions
+            {
+                Backend = PlatformBackend.PreferNative,
+                EnableHighResolution = true
+            });
+
             Console.Title = "Ryujinx Console";
 
             string systemPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine);
diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj
index cc8e6d545..bde01b243 100644
--- a/Ryujinx/Ryujinx.csproj
+++ b/Ryujinx/Ryujinx.csproj
@@ -72,9 +72,10 @@
 
   <ItemGroup>
     <PackageReference Include="DiscordRichPresence" Version="1.0.147" />
+    <PackageReference Include="GLWidget" Version="1.0.0" />
     <PackageReference Include="GtkSharp" Version="3.22.25.56" />
     <PackageReference Include="GtkSharp.Dependencies" Version="1.1.0" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'" />
-    <PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
+    <PackageReference Include="OpenTK.NetStandard" Version="1.0.5.12" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs
new file mode 100644
index 000000000..dff72b364
--- /dev/null
+++ b/Ryujinx/Ui/GLRenderer.cs
@@ -0,0 +1,499 @@
+using Gdk;
+using OpenTK;
+using OpenTK.Graphics;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
+using OpenTK.Platform;
+using Ryujinx.Configuration;
+using Ryujinx.Graphics.OpenGL;
+using Ryujinx.HLE;
+using Ryujinx.HLE.Input;
+using Ryujinx.Ui;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Ryujinx.Ui
+{
+    public class GLRenderer : GLWidget
+    {
+        private const int TouchScreenWidth = 1280;
+        private const int TouchScreenHeight = 720;
+        private const int TargetFps = 60;
+
+        public ManualResetEvent WaitEvent { get; set; }
+
+        public bool IsActive   { get; set; }
+        public bool IsStopped  { get; set; }
+        public bool IsFocused  { get; set; }
+
+        private double _mouseX;
+        private double _mouseY;
+        private bool _mousePressed;
+
+        private bool _titleEvent;
+
+        private bool _toggleFullscreen;
+
+        private string _newTitle;
+
+        private readonly long _ticksPerFrame;
+
+        private long _ticks = 0;
+
+        private System.Diagnostics.Stopwatch _chrono;
+
+        private Switch _device;
+
+        private Renderer _renderer;
+
+        private HotkeyButtons _prevHotkeyButtons = 0;
+
+        private Input.NpadController _primaryController;
+
+        public GLRenderer(Switch device) 
+            : base (new GraphicsMode(new ColorFormat(24)), 
+            3, 3, 
+            GraphicsContextFlags.ForwardCompatible)
+        {
+            WaitEvent = new ManualResetEvent(false);
+
+            _device = device;
+
+            this.Initialized += GLRenderer_Initialized;
+            this.Destroyed += GLRenderer_Destroyed;
+
+            Initialize();
+
+            _chrono = new System.Diagnostics.Stopwatch();
+
+            _ticksPerFrame = System.Diagnostics.Stopwatch.Frequency / TargetFps;
+
+            _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls);
+
+            AddEvents((int)(Gdk.EventMask.ButtonPressMask 
+                          | Gdk.EventMask.ButtonReleaseMask 
+                          | Gdk.EventMask.PointerMotionMask 
+                          | Gdk.EventMask.KeyPressMask
+                          | Gdk.EventMask.KeyReleaseMask));
+
+            this.Shown += Renderer_Shown;
+        }
+
+        private void Parent_FocusOutEvent(object o, Gtk.FocusOutEventArgs args)
+        {
+            IsFocused = false;
+        }
+
+        private void Parent_FocusInEvent(object o, Gtk.FocusInEventArgs args)
+        {
+            IsFocused = true;
+        }
+
+        private void GLRenderer_Destroyed(object sender, EventArgs e)
+        {
+            Exit();
+
+            this.Dispose();
+        }
+
+        protected void Renderer_Shown(object sender, EventArgs e)
+        {
+            IsFocused = this.ParentWindow.State.HasFlag(Gdk.WindowState.Focused);
+        }
+
+        public void HandleScreenState(KeyboardState keyboard)
+        {
+            bool toggleFullscreen = keyboard.IsKeyDown(OpenTK.Input.Key.F11) 
+                               || ((keyboard.IsKeyDown(OpenTK.Input.Key.AltLeft) 
+                               ||   keyboard.IsKeyDown(OpenTK.Input.Key.AltRight)) 
+                               &&   keyboard.IsKeyDown(OpenTK.Input.Key.Enter));
+
+            if (toggleFullscreen == _toggleFullscreen)
+            {
+                return;
+            }
+
+            _toggleFullscreen = toggleFullscreen;
+
+            Gtk.Application.Invoke(delegate
+            {
+                if (this.ParentWindow.State.HasFlag(Gdk.WindowState.Fullscreen))
+                {
+                    if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape) || _toggleFullscreen)
+                    {
+                        this.ParentWindow.Unfullscreen();
+                        (this.Toplevel as MainWindow)?.ToggleExtraWidgets(true);
+                    }
+                }
+                else
+                {
+                    if (keyboard.IsKeyDown(OpenTK.Input.Key.Escape))
+                    {
+                        Exit();
+                    }
+
+                    if (_toggleFullscreen)
+                    {
+                        this.ParentWindow.Fullscreen();
+                        (this.Toplevel as MainWindow)?.ToggleExtraWidgets(false);
+                    }
+                }
+            });
+        }
+
+        private void GLRenderer_Initialized(object sender, EventArgs e)
+        {
+            // Release the GL exclusivity that OpenTK gave us.
+            GraphicsContext.MakeCurrent(null);
+
+            WaitEvent.Set();
+        }
+
+        protected override bool OnConfigureEvent(EventConfigure evnt)
+        {
+            var result = base.OnConfigureEvent(evnt);
+
+            _renderer.Window.SetSize(AllocatedWidth, AllocatedHeight);
+
+            return result;
+        }
+
+        public void Start()
+        {
+            IsRenderHandler = true;
+
+            _chrono.Restart();
+
+            IsActive = true;
+
+            Gtk.Window parent = this.Toplevel as Gtk.Window;
+
+            parent.FocusInEvent += Parent_FocusInEvent;
+            parent.FocusOutEvent += Parent_FocusOutEvent;
+
+            Gtk.Application.Invoke(delegate
+            {
+                parent.Present();
+            });
+
+            Thread renderLoopThread = new Thread(Render)
+            {
+                Name = "GUI.RenderLoop"
+            };
+            renderLoopThread.Start();
+
+            MainLoop();
+
+            renderLoopThread.Join();
+
+            Exit();
+        }
+
+        protected override bool OnButtonPressEvent(EventButton evnt)
+        {
+            _mouseX = evnt.X;
+            _mouseY = evnt.Y;
+
+            if (evnt.Button == 1)
+            {
+                _mousePressed = true;
+            }
+
+            return false;
+        }
+
+        protected override bool OnButtonReleaseEvent(EventButton evnt)
+        {
+            if (evnt.Button == 1)
+            {
+                _mousePressed = false;
+            }
+
+            return false;
+        }
+
+        protected override bool OnMotionNotifyEvent(EventMotion evnt)
+        {
+            if (evnt.Device.InputSource == InputSource.Mouse)
+            {
+                _mouseX = evnt.X;
+                _mouseY = evnt.Y;
+            }
+
+            return false;
+        }
+
+        public void Exit()
+        {
+            if (IsStopped)
+            {
+                return;
+            }
+
+            IsStopped = true;
+            IsActive = false;
+
+            using (ScopedGlContext scopedGLContext = new ScopedGlContext(WindowInfo, GraphicsContext))
+            {
+                _device.DisposeGpu();
+            }
+
+            WaitEvent.Set();
+        }
+
+        public void Initialize()
+        {
+            if (!(_device.Gpu.Renderer is Renderer))
+            {
+                throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GLRenderer!");
+            }
+
+            _renderer = (Renderer)_device.Gpu.Renderer;
+        }
+
+        public void Render()
+        {
+            using (ScopedGlContext scopedGLContext = new ScopedGlContext(WindowInfo, GraphicsContext))
+            {
+                _renderer.Initialize();
+
+                SwapBuffers();
+            }
+
+            while (IsActive)
+            {
+                if (IsStopped)
+                {
+                    return;
+                }
+
+                using (ScopedGlContext scopedGLContext = new ScopedGlContext(WindowInfo, GraphicsContext))
+                {
+                    _ticks += _chrono.ElapsedTicks;
+
+                    _chrono.Restart();
+
+                    if (_device.WaitFifo())
+                    {
+                        _device.ProcessFrame();
+                    }
+
+                    if (_ticks >= _ticksPerFrame)
+                    {
+                        _device.PresentFrame(SwapBuffers);
+
+                        _device.Statistics.RecordSystemFrameTime();
+
+                        double hostFps = _device.Statistics.GetSystemFrameRate();
+                        double gameFps = _device.Statistics.GetGameFrameRate();
+
+                        string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
+                            : " | " + _device.System.TitleName;
+
+                        string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty
+                            : " | " + _device.System.TitleIdText.ToUpper();
+
+                        _newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
+                            $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
+
+                        _titleEvent = true;
+
+                        _device.System.SignalVsync();
+
+                        _device.VsyncEvent.Set();
+
+                        _ticks = Math.Min(_ticks - _ticksPerFrame, _ticksPerFrame);
+                    }
+                }
+            }
+        }
+
+        public void SwapBuffers()
+        {
+            OpenTK.Graphics.GraphicsContext.CurrentContext.SwapBuffers();
+        }
+
+        public void MainLoop()
+        {
+            while (IsActive)
+            {
+                if (_titleEvent)
+                {
+                    _titleEvent = false;
+
+                    Gtk.Application.Invoke(delegate
+                    {
+                        this.ParentWindow.Title = _newTitle;
+                    });
+                }
+
+                if (IsFocused)
+                {
+                    UpdateFrame();
+                }
+
+                // Polling becomes expensive if it's not slept
+                Thread.Sleep(1);
+            }
+        }
+
+        private bool UpdateFrame()
+        {
+            if (!IsActive)
+            {
+                return true;
+            }
+
+            if (IsStopped)
+            {
+                return false;
+            }
+
+            HotkeyButtons currentHotkeyButtons = 0;
+            ControllerButtons currentButton = 0;
+            JoystickPosition leftJoystick;
+            JoystickPosition rightJoystick;
+            HLE.Input.Keyboard? hidKeyboard = null;
+
+            KeyboardState keyboard = OpenTK.Input.Keyboard.GetState();
+
+            Gtk.Application.Invoke(delegate
+            {
+                HandleScreenState(keyboard);
+            });
+
+            int leftJoystickDx = 0;
+            int leftJoystickDy = 0;
+            int rightJoystickDx = 0;
+            int rightJoystickDy = 0;
+
+            // Normal Input
+            currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
+            currentButton = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
+
+            if (ConfigurationState.Instance.Hid.EnableKeyboard)
+            {
+                hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
+            }
+
+            (leftJoystickDx, leftJoystickDy) = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
+            (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
+
+            if (!hidKeyboard.HasValue)
+            {
+                hidKeyboard = new HLE.Input.Keyboard
+                {
+                    Modifier = 0,
+                    Keys = new int[0x8]
+                };
+            }
+
+            currentButton |= _primaryController.GetButtons();
+
+            // Keyboard has priority stick-wise
+            if (leftJoystickDx == 0 && leftJoystickDy == 0)
+            {
+                (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick();
+            }
+
+            if (rightJoystickDx == 0 && rightJoystickDy == 0)
+            {
+                (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick();
+            }
+
+            leftJoystick = new JoystickPosition
+            {
+                Dx = leftJoystickDx,
+                Dy = leftJoystickDy
+            };
+
+            rightJoystick = new JoystickPosition
+            {
+                Dx = rightJoystickDx,
+                Dy = rightJoystickDy
+            };
+
+            currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
+
+            bool hasTouch = false;
+
+            // Get screen touch position from left mouse click
+            // OpenTK always captures mouse events, even if out of focus, so check if window is focused.
+            if (IsFocused && _mousePressed)
+            {
+                int screenWidth = AllocatedWidth;
+                int screenHeight = AllocatedHeight;
+
+                if (AllocatedWidth > (AllocatedHeight * TouchScreenWidth) / TouchScreenHeight)
+                {
+                    screenWidth = (AllocatedHeight * TouchScreenWidth) / TouchScreenHeight;
+                }
+                else
+                {
+                    screenHeight = (AllocatedWidth * TouchScreenHeight) / TouchScreenWidth;
+                }
+
+                int startX = (AllocatedWidth - screenWidth) >> 1;
+                int startY = (AllocatedHeight - screenHeight) >> 1;
+
+                int endX = startX + screenWidth;
+                int endY = startY + screenHeight;
+
+
+                if (_mouseX >= startX &&
+                    _mouseY >= startY &&
+                    _mouseX < endX &&
+                    _mouseY < endY)
+                {
+                    int screenMouseX = (int)_mouseX - startX;
+                    int screenMouseY = (int)_mouseY - startY;
+
+                    int mX = (screenMouseX * TouchScreenWidth) / screenWidth;
+                    int mY = (screenMouseY * TouchScreenHeight) / screenHeight;
+
+                    TouchPoint currentPoint = new TouchPoint
+                    {
+                        X = mX,
+                        Y = mY,
+
+                        // Placeholder values till more data is acquired
+                        DiameterX = 10,
+                        DiameterY = 10,
+                        Angle = 90
+                    };
+
+                    hasTouch = true;
+
+                    _device.Hid.SetTouchPoints(currentPoint);
+                }
+            }
+
+            if (!hasTouch)
+            {
+                _device.Hid.SetTouchPoints();
+            }
+
+            if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue)
+            {
+                _device.Hid.WriteKeyboard(hidKeyboard.Value);
+            }
+
+            BaseController controller = _device.Hid.PrimaryController;
+
+            controller.SendInput(currentButton, leftJoystick, rightJoystick);
+
+            // Toggle vsync
+            if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
+                !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
+            {
+                _device.EnableDeviceVsync = !_device.EnableDeviceVsync;
+            }
+
+            _prevHotkeyButtons = currentHotkeyButtons;
+
+            return true;
+        }
+    }
+}
diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs
deleted file mode 100644
index 5e83458ae..000000000
--- a/Ryujinx/Ui/GLScreen.cs
+++ /dev/null
@@ -1,375 +0,0 @@
-using OpenTK;
-using OpenTK.Graphics;
-using OpenTK.Input;
-using Ryujinx.Configuration;
-using Ryujinx.Graphics.OpenGL;
-using Ryujinx.HLE;
-using Ryujinx.HLE.Input;
-using System;
-using System.Threading;
-
-using Stopwatch = System.Diagnostics.Stopwatch;
-
-namespace Ryujinx.Ui
-{
-    public class GlScreen : GameWindow
-    {
-        private const int TouchScreenWidth  = 1280;
-        private const int TouchScreenHeight = 720;
-
-        private const int TargetFps = 60;
-
-        private Switch _device;
-
-        private Renderer _renderer;
-
-        private HotkeyButtons _prevHotkeyButtons = 0;
-
-        private KeyboardState? _keyboard = null;
-
-        private MouseState? _mouse = null;
-
-        private Input.NpadController _primaryController;
-
-        private Thread _renderThread;
-
-        private bool _resizeEvent;
-
-        private bool _titleEvent;
-
-        private string _newTitle;
-
-        public GlScreen(Switch device)
-            : base(1280, 720,
-            new GraphicsMode(), "Ryujinx", 0,
-            DisplayDevice.Default, 3, 3,
-            GraphicsContextFlags.ForwardCompatible)
-        {
-            _device = device;
-
-            if (!(device.Gpu.Renderer is Renderer))
-            {
-                throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GlScreen!");
-            }
-
-            _renderer = (Renderer)device.Gpu.Renderer;
-
-            _primaryController = new Input.NpadController(ConfigurationState.Instance.Hid.JoystickControls);
-
-            Location = new Point(
-                (DisplayDevice.Default.Width  / 2) - (Width  / 2),
-                (DisplayDevice.Default.Height / 2) - (Height / 2));
-        }
-
-        private void RenderLoop()
-        {
-            MakeCurrent();
-
-            _renderer.Initialize();
-
-            Stopwatch chrono = new Stopwatch();
-
-            chrono.Start();
-
-            long ticksPerFrame = Stopwatch.Frequency / TargetFps;
-
-            long ticks = 0;
-
-            while (Exists && !IsExiting)
-            {
-                if (_device.WaitFifo())
-                {
-                    _device.ProcessFrame();
-                }
-
-                if (_resizeEvent)
-                {
-                    _resizeEvent = false;
-
-                    _renderer.Window.SetSize(Width, Height);
-                }
-
-                ticks += chrono.ElapsedTicks;
-
-                chrono.Restart();
-
-                if (ticks >= ticksPerFrame)
-                {
-                    RenderFrame();
-
-                    // Queue max. 1 vsync
-                    ticks = Math.Min(ticks - ticksPerFrame, ticksPerFrame);
-                }
-            }
-
-            _device.DisposeGpu();
-        }
-
-        public void MainLoop()
-        {
-            VSync = VSyncMode.Off;
-
-            Visible = true;
-
-            Context.MakeCurrent(null);
-
-            // OpenTK doesn't like sleeps in its thread, to avoid this a renderer thread is created
-            _renderThread = new Thread(RenderLoop)
-            {
-                Name = "GUI.RenderThread"
-            };
-
-            _renderThread.Start();
-
-            while (Exists && !IsExiting)
-            {
-                ProcessEvents();
-
-                if (!IsExiting)
-                {
-                    UpdateFrame();
-
-                    if (_titleEvent)
-                    {
-                        _titleEvent = false;
-
-                        Title = _newTitle;
-                    }
-                }
-
-                // Polling becomes expensive if it's not slept
-                Thread.Sleep(1);
-            }
-        }
-
-        private new void UpdateFrame()
-        {
-            HotkeyButtons       currentHotkeyButtons = 0;
-            ControllerButtons   currentButton = 0;
-            JoystickPosition    leftJoystick;
-            JoystickPosition    rightJoystick;
-            HLE.Input.Keyboard? hidKeyboard = null;
-
-            int leftJoystickDx  = 0;
-            int leftJoystickDy  = 0;
-            int rightJoystickDx = 0;
-            int rightJoystickDy = 0;
-
-            // Keyboard Input
-            if (_keyboard.HasValue)
-            {
-                KeyboardState keyboard = _keyboard.Value;
-
-                // Normal Input
-                currentHotkeyButtons = KeyboardControls.GetHotkeyButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
-                currentButton        = KeyboardControls.GetButtons(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
-
-                if (ConfigurationState.Instance.Hid.EnableKeyboard)
-                {
-                    hidKeyboard = KeyboardControls.GetKeysDown(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
-                }
-
-                (leftJoystickDx, leftJoystickDy)   = KeyboardControls.GetLeftStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
-                (rightJoystickDx, rightJoystickDy) = KeyboardControls.GetRightStick(ConfigurationState.Instance.Hid.KeyboardControls, keyboard);
-            }
-
-            if (!hidKeyboard.HasValue)
-            {
-                hidKeyboard = new HLE.Input.Keyboard
-                {
-                    Modifier = 0,
-                    Keys     = new int[0x8]
-                };
-            }
-
-            currentButton |= _primaryController.GetButtons();
-
-            // Keyboard has priority stick-wise
-            if (leftJoystickDx == 0 && leftJoystickDy == 0)
-            {
-                (leftJoystickDx, leftJoystickDy) = _primaryController.GetLeftStick();
-            }
-
-            if (rightJoystickDx == 0 && rightJoystickDy == 0)
-            {
-                (rightJoystickDx, rightJoystickDy) = _primaryController.GetRightStick();
-            }
-
-            leftJoystick = new JoystickPosition
-            {
-                Dx = leftJoystickDx,
-                Dy = leftJoystickDy
-            };
-
-            rightJoystick = new JoystickPosition
-            {
-                Dx = rightJoystickDx,
-                Dy = rightJoystickDy
-            };
-
-            currentButton |= _device.Hid.UpdateStickButtons(leftJoystick, rightJoystick);
-
-            bool hasTouch = false;
-
-            // Get screen touch position from left mouse click
-            // OpenTK always captures mouse events, even if out of focus, so check if window is focused.
-            if (Focused && _mouse?.LeftButton == ButtonState.Pressed)
-            {
-                MouseState mouse = _mouse.Value;
-
-                int scrnWidth  = Width;
-                int scrnHeight = Height;
-
-                if (Width > (Height * TouchScreenWidth) / TouchScreenHeight)
-                {
-                    scrnWidth = (Height * TouchScreenWidth) / TouchScreenHeight;
-                }
-                else
-                {
-                    scrnHeight = (Width * TouchScreenHeight) / TouchScreenWidth;
-                }
-
-                int startX = (Width  - scrnWidth)  >> 1;
-                int startY = (Height - scrnHeight) >> 1;
-
-                int endX = startX + scrnWidth;
-                int endY = startY + scrnHeight;
-
-                if (mouse.X >= startX &&
-                    mouse.Y >= startY &&
-                    mouse.X <  endX   &&
-                    mouse.Y <  endY)
-                {
-                    int scrnMouseX = mouse.X - startX;
-                    int scrnMouseY = mouse.Y - startY;
-
-                    int mX = (scrnMouseX * TouchScreenWidth)  / scrnWidth;
-                    int mY = (scrnMouseY * TouchScreenHeight) / scrnHeight;
-
-                    TouchPoint currentPoint = new TouchPoint
-                    {
-                        X = mX,
-                        Y = mY,
-
-                        // Placeholder values till more data is acquired
-                        DiameterX = 10,
-                        DiameterY = 10,
-                        Angle     = 90
-                    };
-
-                    hasTouch = true;
-
-                    _device.Hid.SetTouchPoints(currentPoint);
-                }
-            }
-
-            if (!hasTouch)
-            {
-                _device.Hid.SetTouchPoints();
-            }
-
-            if (ConfigurationState.Instance.Hid.EnableKeyboard && hidKeyboard.HasValue)
-            {
-                _device.Hid.WriteKeyboard(hidKeyboard.Value);
-            }
-
-            BaseController controller = _device.Hid.PrimaryController;
-
-            controller.SendInput(currentButton, leftJoystick, rightJoystick);
-
-            // Toggle vsync
-            if (currentHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync) &&
-                !_prevHotkeyButtons.HasFlag(HotkeyButtons.ToggleVSync))
-            {
-                _device.EnableDeviceVsync = !_device.EnableDeviceVsync;
-            }
-
-            _prevHotkeyButtons = currentHotkeyButtons;
-        }
-
-        private new void RenderFrame()
-        {
-            _device.PresentFrame(SwapBuffers);
-
-            _device.Statistics.RecordSystemFrameTime();
-
-            double hostFps = _device.Statistics.GetSystemFrameRate();
-            double gameFps = _device.Statistics.GetGameFrameRate();
-
-            string titleNameSection = string.IsNullOrWhiteSpace(_device.System.TitleName) ? string.Empty
-                : " | " + _device.System.TitleName;
-
-            string titleIdSection = string.IsNullOrWhiteSpace(_device.System.TitleIdText) ? string.Empty
-                : " | " + _device.System.TitleIdText.ToUpper();
-
-            _newTitle = $"Ryujinx{titleNameSection}{titleIdSection} | Host FPS: {hostFps:0.0} | Game FPS: {gameFps:0.0} | " +
-                $"Game Vsync: {(_device.EnableDeviceVsync ? "On" : "Off")}";
-
-            _titleEvent = true;
-
-            _device.System.SignalVsync();
-
-            _device.VsyncEvent.Set();
-        }
-
-        protected override void OnUnload(EventArgs e)
-        {
-            _renderThread.Join();
-
-            base.OnUnload(e);
-        }
-
-        protected override void OnResize(EventArgs e)
-        {
-            _resizeEvent = true;
-        }
-
-        protected override void OnKeyDown(KeyboardKeyEventArgs e)
-        {
-            bool toggleFullscreen = e.Key == Key.F11 ||
-                (e.Modifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Enter);
-
-            if (WindowState == WindowState.Fullscreen)
-            {
-                if (e.Key == Key.Escape || toggleFullscreen)
-                {
-                    WindowState = WindowState.Normal;
-                }
-            }
-            else
-            {
-                if (e.Key == Key.Escape)
-                {
-                    Exit();
-                }
-
-                if (toggleFullscreen)
-                {
-                    WindowState = WindowState.Fullscreen;
-                }
-            }
-
-            _keyboard = e.Keyboard;
-        }
-
-        protected override void OnKeyUp(KeyboardKeyEventArgs e)
-        {
-            _keyboard = e.Keyboard;
-        }
-
-        protected override void OnMouseDown(MouseButtonEventArgs e)
-        {
-            _mouse = e.Mouse;
-        }
-
-        protected override void OnMouseUp(MouseButtonEventArgs e)
-        {
-            _mouse = e.Mouse;
-        }
-
-        protected override void OnMouseMove(MouseMoveEventArgs e)
-        {
-            _mouse = e.Mouse;
-        }
-    }
-}
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 734103fed..6c771bb96 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -8,7 +8,6 @@ using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.OpenGL;
 using Ryujinx.HLE.FileSystem;
 using Ryujinx.HLE.FileSystem.Content;
-using Ryujinx.HLE.FileSystem;
 using System;
 using System.Diagnostics;
 using System.IO;
@@ -30,7 +29,7 @@ namespace Ryujinx.Ui
 
         private static HLE.Switch _emulationContext;
 
-        private static GlScreen _screen;
+        private static GLRenderer _gLWidget;
 
         private static AutoResetEvent _screenExitStatus = new AutoResetEvent(false);
 
@@ -43,32 +42,38 @@ namespace Ryujinx.Ui
 
         private static TreeView _treeView;
 
-        private static Debugger.Debugger _debugger;
+        private static Ryujinx.Debugger.Debugger _debugger;
 
 #pragma warning disable CS0649
 #pragma warning disable IDE0044
-        [GUI] Window        _mainWin;
-        [GUI] CheckMenuItem _fullScreen;
-        [GUI] MenuItem      _stopEmulation;
-        [GUI] CheckMenuItem _favToggle;
-        [GUI] MenuItem      _firmwareInstallFile;
-        [GUI] MenuItem      _firmwareInstallDirectory;
-        [GUI] CheckMenuItem _iconToggle;
-        [GUI] CheckMenuItem _appToggle;
-        [GUI] CheckMenuItem _developerToggle;
-        [GUI] CheckMenuItem _versionToggle;
-        [GUI] CheckMenuItem _timePlayedToggle;
-        [GUI] CheckMenuItem _lastPlayedToggle;
-        [GUI] CheckMenuItem _fileExtToggle;
-        [GUI] CheckMenuItem _fileSizeToggle;
-        [GUI] CheckMenuItem _pathToggle;
-        [GUI] TreeView      _gameTable;
-        [GUI] TreeSelection _gameTableSelection;
-        [GUI] Label         _progressLabel;
-        [GUI] Label         _firmwareVersionLabel;
-        [GUI] LevelBar      _progressBar;
-        [GUI] MenuItem      _openDebugger;
-        [GUI] MenuItem      _toolsMenu;
+
+        [GUI] Window         _mainWin;
+        [GUI] MenuBar        _menuBar;
+        [GUI] Box            _footerBox;
+        [GUI] MenuItem       _fullScreen;
+        [GUI] MenuItem       _stopEmulation;
+        [GUI] CheckMenuItem  _favToggle;
+        [GUI] MenuItem       _firmwareInstallFile;
+        [GUI] MenuItem       _firmwareInstallDirectory;
+        [GUI] MenuItem       _openDebugger;
+        [GUI] CheckMenuItem  _iconToggle;
+        [GUI] CheckMenuItem  _appToggle;
+        [GUI] CheckMenuItem  _developerToggle;
+        [GUI] CheckMenuItem  _versionToggle;
+        [GUI] CheckMenuItem  _timePlayedToggle;
+        [GUI] CheckMenuItem  _lastPlayedToggle;
+        [GUI] CheckMenuItem  _fileExtToggle;
+        [GUI] CheckMenuItem  _fileSizeToggle;
+        [GUI] CheckMenuItem  _pathToggle;
+        [GUI] TreeView       _gameTable;
+        [GUI] ScrolledWindow _gameTableWindow;
+        [GUI] TreeSelection  _gameTableSelection;
+        [GUI] Label          _progressLabel;
+        [GUI] Label          _firmwareVersionLabel;
+        [GUI] LevelBar       _progressBar;
+        [GUI] Box            _viewBox;
+        [GUI] Box            _listStatusBox;
+
 #pragma warning restore CS0649
 #pragma warning restore IDE0044
 
@@ -130,7 +135,7 @@ namespace Ryujinx.Ui
             _debugger = new Debugger.Debugger();
             _openDebugger.Activated += _openDebugger_Opened;
 #else
-            _openDebugger.Visible = false;
+            _openDebugger.Hide();
 #endif
 
             _gameTable.Model = _tableStore = new ListStore(
@@ -154,6 +159,8 @@ namespace Ryujinx.Ui
             UpdateGameTable();
 
             Task.Run(RefreshFirmwareLabel);
+
+            _fullScreen.Activated += FullScreen_Toggled;
         }
 
 #if USE_DEBUGGING
@@ -384,27 +391,85 @@ namespace Ryujinx.Ui
         {
             device.Hid.InitializePrimaryController(ConfigurationState.Instance.Hid.ControllerType);
 
-            using (_screen = new GlScreen(device))
+            _gLWidget?.Exit();
+            _gLWidget?.Dispose();
+            _gLWidget = new GLRenderer(_emulationContext);
+
+            Application.Invoke(delegate
             {
-                _screen.MainLoop();
-            }
+                _viewBox.Remove(_gameTableWindow);
+                _gLWidget.Expand = true;
+                _viewBox.Child = _gLWidget;
+
+                _gLWidget.ShowAll();
+                _listStatusBox.Hide();
+            });
+
+            _gLWidget.WaitEvent.WaitOne();
+
+            _gLWidget.Start();
+
+            Application.Invoke(delegate
+            {
+                _viewBox.Remove(_gLWidget);
+                _gLWidget.Exit();
+
+                if(_gLWidget.Window != this.Window && _gLWidget.Window != null)
+                {
+                    _gLWidget.Window.Dispose();
+                }
+
+                _viewBox.Add(_gameTableWindow);
+
+                _gameTableWindow.Expand = true;
+
+                this.Window.Title = "Ryujinx";
+
+                _listStatusBox.ShowAll();
+
+                UpdateColumns();
+                UpdateGameTable();
+
+                Task.Run(RefreshFirmwareLabel);
+            });
 
             device.Dispose();
 
             _emulationContext = null;
-            _screen           = null;
             _gameLoaded       = false;
+            _gLWidget         = null;
 
             DiscordIntegrationModule.SwitchToMainMenu();
 
-            _screenExitStatus.Set();
-
             Application.Invoke(delegate
             {
                 _stopEmulation.Sensitive            = false;
                 _firmwareInstallFile.Sensitive      = true;
                 _firmwareInstallDirectory.Sensitive = true;
             });
+
+            _screenExitStatus.Set();
+        }
+
+        public void ToggleExtraWidgets(bool show)
+        {
+            if (_gLWidget != null)
+            {
+                if (show)
+                {
+                    _menuBar.ShowAll();
+                    _footerBox.ShowAll();
+                }
+                else
+                {
+                    _menuBar.Hide();
+                    _footerBox.Hide();
+                }
+            }
+
+            bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen);
+
+            _fullScreen.Label = !fullScreenToggled ? "Exit Fullscreen" : "Enter Fullscreen";
         }
 
         private static void UpdateGameMetadata(string titleId)
@@ -439,9 +504,9 @@ namespace Ryujinx.Ui
             {
                 UpdateGameMetadata(device.System.TitleIdText);
 
-                if (_screen != null)
+                if (_gLWidget != null)
                 {
-                    _screen.Exit();
+                    _gLWidget.Exit();
                     _screenExitStatus.WaitOne();
                 }
             }
@@ -605,7 +670,7 @@ namespace Ryujinx.Ui
 
         private void StopEmulation_Pressed(object sender, EventArgs args)
         {
-            _screen?.Exit();
+            _gLWidget?.Exit();
         }
 
         private void Installer_File_Pressed(object o, EventArgs args)
@@ -803,13 +868,23 @@ namespace Ryujinx.Ui
 
         private void FullScreen_Toggled(object o, EventArgs args)
         {
-            if (_fullScreen.Active)
+            bool fullScreenToggled = this.Window.State.HasFlag(Gdk.WindowState.Fullscreen);
+
+            if (!fullScreenToggled)
             {
                 Fullscreen();
+
+                _fullScreen.Label = "Exit Fullscreen";
+
+                ToggleExtraWidgets(false);
             }
             else
             {
                 Unfullscreen();
+
+                _fullScreen.Label = "Enter Fullscreen";
+
+                ToggleExtraWidgets(true);
             }
         }
 
diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade
index d3cdc5934..8477f392a 100644
--- a/Ryujinx/Ui/MainWindow.glade
+++ b/Ryujinx/Ui/MainWindow.glade
@@ -17,7 +17,7 @@
         <property name="can_focus">False</property>
         <property name="orientation">vertical</property>
         <child>
-          <object class="GtkMenuBar" id="MenuBar">
+          <object class="GtkMenuBar" id="_menuBar">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <child>
@@ -97,13 +97,11 @@
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <child>
-                      <object class="GtkCheckMenuItem" id="_fullScreen">
+                      <object class="GtkMenuItem" id="_fullScreen">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="tooltip_text" translatable="yes">Fullscreens the window</property>
-                        <property name="label" translatable="yes">Fullscreen</property>
+                        <property name="label" translatable="yes">Enter Fullscreen</property>
                         <property name="use_underline">True</property>
-                        <signal name="toggled" handler="FullScreen_Toggled" swapped="no"/>
                       </object>
                     </child>
                     <child>
@@ -364,21 +362,35 @@
             <property name="can_focus">False</property>
             <property name="orientation">vertical</property>
             <child>
-              <object class="GtkScrolledWindow" id="_gameTableWindow">
+              <object class="GtkBox" id="_viewBox">
+                <property name="width_request">1280</property>
+                <property name="height_request">720</property>
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="shadow_type">in</property>
+                <property name="can_focus">False</property>
+                <property name="orientation">vertical</property>
                 <child>
-                  <object class="GtkTreeView" id="_gameTable">
+                  <object class="GtkScrolledWindow" id="_gameTableWindow">
                     <property name="visible">True</property>
                     <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"/>
-                    <child internal-child="selection">
-                      <object class="GtkTreeSelection" id="_gameTableSelection"/>
+                    <property name="shadow_type">in</property>
+                    <child>
+                      <object class="GtkTreeView" id="_gameTable">
+                        <property name="visible">True</property>
+                        <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"/>
+                        <child internal-child="selection">
+                          <object class="GtkTreeSelection" id="_gameTableSelection"/>
+                        </child>
+                      </object>
                     </child>
                   </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
                 </child>
               </object>
               <packing>
@@ -388,59 +400,70 @@
               </packing>
             </child>
             <child>
-              <object class="GtkBox" id="FooterBox">
+              <object class="GtkBox" id="_footerBox">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <child>
-                  <object class="GtkEventBox">
+                  <object class="GtkBox" id="_listStatusBox">
                     <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"/>
                     <child>
-                      <object class="GtkImage">
-                        <property name="name">RefreshList</property>
+                      <object class="GtkEventBox">
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="stock">gtk-refresh</property>
+                        <property name="margin_left">5</property>
+                        <signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/>
+                        <child>
+                          <object class="GtkImage">
+                            <property name="name">RefreshList</property>
+                            <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="stock">gtk-refresh</property>
+                          </object>
+                        </child>
                       </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">False</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="_progressLabel">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="margin_left">10</property>
+                        <property name="margin_right">5</property>
+                        <property name="margin_top">2</property>
+                        <property name="margin_bottom">2</property>
+                        <property name="label" translatable="yes">0/0 Games Loaded</property>
+                      </object>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLevelBar" id="_progressBar">
+                        <property name="width_request">200</property>
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="halign">start</property>
+                        <property name="margin_left">10</property>
+                        <property name="margin_right">5</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">2</property>
+                      </packing>
                     </child>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="_progressLabel">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="margin_left">10</property>
-                    <property name="margin_right">5</property>
-                    <property name="margin_top">2</property>
-                    <property name="margin_bottom">2</property>
-                    <property name="label" translatable="yes">0/0 Games Loaded</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLevelBar" id="_progressBar">
-                    <property name="width_request">200</property>
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="halign">start</property>
-                    <property name="margin_left">10</property>
-                    <property name="margin_right">5</property>
                   </object>
                   <packing>
                     <property name="expand">True</property>
                     <property name="fill">True</property>
-                    <property name="position">2</property>
+                    <property name="position">1</property>
                   </packing>
                 </child>
                 <child>
diff --git a/Ryujinx/Ui/ScopedGlContext.cs b/Ryujinx/Ui/ScopedGlContext.cs
new file mode 100644
index 000000000..8dc9246ef
--- /dev/null
+++ b/Ryujinx/Ui/ScopedGlContext.cs
@@ -0,0 +1,35 @@
+using OpenTK.Graphics;
+using OpenTK.Platform;
+using System;
+using System.Threading;
+
+namespace Ryujinx.Ui
+{
+    class ScopedGlContext : IDisposable
+    {
+        private IGraphicsContext _graphicsContext;
+
+        private static readonly object _lock = new object();
+
+        public ScopedGlContext(IWindowInfo windowInfo, IGraphicsContext graphicsContext)
+        {
+            _graphicsContext = graphicsContext;
+
+            Monitor.Enter(_lock);
+
+            MakeCurrent(windowInfo);
+        }
+
+        private void MakeCurrent(IWindowInfo windowInfo)
+        {
+            _graphicsContext.MakeCurrent(windowInfo);
+        }
+
+        public void Dispose()
+        {
+            MakeCurrent(null);
+
+            Monitor.Exit(_lock);
+        }
+    }
+}